网罗开发 (小红书、快手、视频号同名)

  大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。

图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG

我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。

展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!


前言

最近在做内部分发这块,很多同事卡在几个地方:如何正确从 Xcode / CI 打包出 enterprise .ipa,怎么生成 manifest.plist,把二者托管在 HTTPS 上,然后通过 itms-services:// 给同事一键安装。下面把流程从头到尾讲清楚——口语化、贴近实操,并附上可运行的 Demo(Python + shell),能直接放到 CI 里跑。

先把整个流程捋一遍

你要做的事其实就是三件:

  1. 在 Apple 企业账号里搞好证书和描述文件(In-House)。
  2. 用 Xcode / xcodebuild 把 App 打包并导出 enterprise .ipa
  3. .ipamanifest.plist 放到 HTTPS 可访问的位置(S3/NGINX/CloudFront),然后通过 itms-services://?action=download-manifest&url=<manifest> 给设备安装。

下面逐步把每一环的实操、常见坑、以及可运行代码给你。

先决条件

  • 公司必须有 Apple Enterprise Developer Program(企业账号,不是个人或公司普通账号)。
  • 开发机器 / CI 需要安装 Xcode(或能跑 xcodebuild 的 macOS 机器)。
  • 一个 HTTPS 域名(必须是可信 CA 的 TLS),或者使用 S3 + CloudFront 并绑定证书。iOS 会拒绝不受信任证书。
  • 推荐把证书、私钥、描述文件等用安全方式存放在 CI 的 secret 管理里,不要明文在脚本里。

证书与描述文件

  1. 在 Apple 企业账号里创建 In-House(Enterprise)分发证书:

    • 在开发者账号页面 → Certificates → Add → 选择 “In-House and Ad Hoc” → 上传 CSR(Certificate Signing Request) → 下载证书 .cer
    • 把证书和对应私钥导入到 macOS Keychain(或在 CI 上生成 .p12 并导入)。
  2. 创建 App ID(Bundle ID)并生成 In-House provisioning profile:

    • Apple Developer → Identifiers → 新增 App ID(如 com.example.app)。
    • Profiles → 新建 In-House Profile,选择上一步的 App ID 和 In-House 证书 → 下载 .mobileprovision
  3. 在本地或 CI 上导入证书(示例命令,CI 上用 secrets):

# 假设你有 base64 编码的 p12 和密码(CI secrets)
echo "$P12_BASE64" | base64 --decode > cert.p12
security create-keychain -p "$KEYCHAIN_PW" build.keychain
security import cert.p12 -k ~/Library/Keychains/build.keychain -P "$P12_PWD" -T /usr/bin/codesign
security list-keychains -s ~/Library/Keychains/build.keychain
security unlock-keychain -p "$KEYCHAIN_PW" ~/Library/Keychains/build.keychain

# 允许 codesign 使用私钥(CI 常见操作)
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PW" ~/Library/Keychains/build.keychain

提示:如果不做 set-key-partition-list,CI 上 codesign 可能会报“not permitted to use private key”之类的错误。

从 Xcode 导出 enterprise IPA(GUI / CLI 都给你)

GUI(Xcode)

  • 打开项目 → Product → Archive。
  • 在 Organizer 里选择新生成的 archive → Distribute App → Enterprise → 选择正确的 provisioning profile → Export → 得到 .ipa

CLI(xcodebuild,适合 CI)

  1. 先 archive:
xcodebuild -workspace MyApp.xcworkspace \
  -scheme MyApp \
  -configuration Release \
  -archivePath /tmp/MyApp.xcarchive \
  clean archive
  1. 然后导出 .ipa,需要一个 exportOptions.plist,示例放在下面:
    exportOptions.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>method</key>
  <string>enterprise</string>
  <key>teamID</key>
  <string>YOUR_TEAM_ID</string>
  <key>provisioningProfiles</key>
  <dict>
    <key>com.example.myapp</key>
    <string>My Enterprise Provisioning Profile Name</string>
  </dict>
</dict>
</plist>

导出命令:

xcodebuild -exportArchive \
  -archivePath /tmp/MyApp.xcarchive \
  -exportOptionsPlist exportOptions.plist \
  -exportPath /tmp/MyAppExport
# /tmp/MyAppExport 会包含 MyApp.ipa

Fastlane(推荐 CI)

Fastfile 中使用 gym(示例):

lane :enterprise_build do
  match(type: "enterprise") # 或者使用手动管理的证书
  gym(
    workspace: "MyApp.xcworkspace",
    scheme: "MyApp",
    export_method: "enterprise",
    export_options: {
      provisioningProfiles: { "com.example.myapp" => "My Enterprise Provisioning Profile Name" }
    }
  )
end

manifest.plist:格式、示例与生成

iOS 内部分发不会直接用 .ipa 链接安装,而是通过 manifest.plist 指定 .ipa 的 URL 与元数据。下面给标准模板并逐字段解释。

manifest.plist(示例)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
   "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>items</key>
  <array>
    <dict>
      <key>assets</key>
      <array>
        <dict>
          <key>kind</key>
          <string>software-package</string>
          <key>url</key>
          <string>https://dl.company.com/apps/MyApp-1.2.3.ipa</string>
        </dict>
      </array>
      <key>metadata</key>
      <dict>
        <key>bundle-identifier</key>
        <string>com.example.myapp</string>
        <key>bundle-version</key>
        <string>1.2.3</string>
        <key>kind</key>
        <string>software</string>
        <key>title</key>
        <string>MyApp</string>
      </dict>
    </dict>
  </array>
</dict>
</plist>

字段要点总结:

  • url 必须是 HTTPS 且能直接访问 .ipa(返回 200,不要返回 HTML 登录页或 302 重定向)。
  • bundle-identifier 必须与 .ipaInfo.plistCFBundleIdentifier 一致。
  • bundle-version 要匹配 CFBundleShortVersionString(否则可能出现版本不一致问题)。

可运行 Demo

Python 脚本自动生成 manifest.plist

把这个脚本放到你的机器或 CI 上。它会把你提供的 .ipa URL、bundle id、version、title 写成 manifest.plist

gen_manifest.py

#!/usr/bin/env python3
"""
gen_manifest.py
Generate manifest.plist for iOS enterprise distribution.

Usage:
python3 gen_manifest.py \
  --ipa-url https://dl.company.com/apps/MyApp-1.2.3.ipa \
  --bundle-id com.example.myapp \
  --version 1.2.3 \
  --title "MyApp" \
  --output manifest.plist
"""
import argparse
import plistlib
from pathlib import Path

def build_manifest(ipa_url: str, bundle_id: str, version: str, title: str):
    # Build the dict matching the required plist structure
    manifest = {
        "items": [
            {
                "assets": [
                    {
                        "kind": "software-package",
                        "url": ipa_url
                    }
                ],
                "metadata": {
                    "bundle-identifier": bundle_id,
                    "bundle-version": version,
                    "kind": "software",
                    "title": title
                }
            }
        ]
    }
    return manifest

def main():
    parser = argparse.ArgumentParser(description="Generate manifest.plist for enterprise distribution")
    parser.add_argument("--ipa-url", required=True, help="HTTPS URL to the .ipa")
    parser.add_argument("--bundle-id", required=True, help="Bundle ID, e.g. com.example.myapp")
    parser.add_argument("--version", required=True, help="App version, e.g. 1.2.3")
    parser.add_argument("--title", required=True, help="App title")
    parser.add_argument("--output", default="manifest.plist", help="Output filename")
    args = parser.parse_args()

    manifest = build_manifest(args.ipa_url, args.bundle_id, args.version, args.title)
    out_path = Path(args.output)
    with out_path.open("wb") as f:
        plistlib.dump(manifest, f)
    print(f"Generated {out_path.resolve()}")

if __name__ == "__main__":
    main()

使用示例

python3 gen_manifest.py \
  --ipa-url "https://dl.company.com/apps/MyApp-1.2.3.ipa" \
  --bundle-id com.example.myapp \
  --version 1.2.3 \
  --title "MyApp" \
  --output manifest.plist

脚本会生成 manifest.plist,接下来把它和 .ipa 都上传到 HTTPS 服务(S3/CloudFront 或自建 nginx),然后拼装安装链接:

itms-services://?action=download-manifest&url=https://dl.company.com/apps/manifest.plist

CI 集成示例脚本

CI 集成示例脚本(打包 → 上传 → 生成 manifest → 输出安装链接)

下面是一个简单的 CI shell 脚本(假设你已经有 .ipabuild/MyApp.ipa),并且使用 AWS CLI 上传到 S3。需要在 CI 环境中配置 AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_DEFAULT_REGION

ci_publish.sh

#!/usr/bin/env bash
set -euo pipefail

IPA_PATH="./build/MyApp.ipa"
S3_BUCKET="s3://your-bucket/path"
DIST_BASE="https://dl.example.com/path"  # CloudFront domain or https domain to serve files

# Upload .ipa
aws s3 cp "$IPA_PATH" "${S3_BUCKET}/MyApp-1.2.3.ipa" \
  --acl public-read --content-type application/octet-stream

# Generate manifest locally (use python script)
python3 gen_manifest.py \
  --ipa-url "${DIST_BASE}/MyApp-1.2.3.ipa" \
  --bundle-id com.example.myapp \
  --version 1.2.3 \
  --title "MyApp" \
  --output manifest.plist

# Upload manifest
aws s3 cp manifest.plist "${S3_BUCKET}/manifest.plist" \
  --acl public-read --content-type application/xml

echo "Published. Install link:"
echo "itms-services://?action=download-manifest&url=${DIST_BASE}/manifest.plist"

把这个脚本放到你的 CI(GitLab CI / Jenkins / GitHub Actions)里,跑完会输出最终安装链接,发给 QA 即可。

托管建议(S3 + CloudFront / nginx 示例)

  • 推荐用 S3 + CloudFront:稳定、支持 HTTPS(ACM),比较省心。上传时设置 Content-Type.ipa → application/octet-stream, manifest.plist → application/xml
  • 自建 nginx:确保启用 TLS(可信证书),示例 nginx 配置片段:
server {
    listen 443 ssl;
    server_name dl.example.com;

    ssl_certificate /etc/ssl/certs/your-cert.pem;
    ssl_certificate_key /etc/ssl/private/your-key.pem;

    location /apps/ {
        root /var/www;
        types {
            application/octet-stream ipa;
            application/xml plist;
        }
        add_header Cache-Control "max-age=3600";
    }
}

注意:iOS 对证书要求严格,使用自签名证书通常会失败(除非设备信任该 CA),所以优先用 Let’s Encrypt / ACM /正规 CA。

终端测试与故障排查(最常见的错误 & 验证命令)

  1. 先在手机 Safari 打开 manifest.plist 链接,确认能访问(会显示 XML 内容)。
  2. curl 验证(在你本地或 CI 上):
curl -I https://dl.example.com/path/manifest.plist
# 确认返回 200,Content-Type: application/xml
curl -I https://dl.example.com/path/MyApp-1.2.3.ipa
# 确认返回 200
  1. 检查 HTTPS 证书链
openssl s_client -connect dl.example.com:443 -showcerts
  1. 查看本地可用的 codesign identity(调试签名问题)
security find-identity -v -p codesigning
  1. 检查 IPA 内的 bundle id / version(在 macOS 上):
unzip -q MyApp.ipa -d /tmp/myapp_unpack
plutil -p /tmp/myapp_unpack/Payload/MyApp.app/Info.plist
# 或者使用 /usr/libexec/PlistBuddy
/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" /tmp/myapp_unpack/Payload/MyApp.app/Info.plist
/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" /tmp/myapp_unpack/Payload/MyApp.app/Info.plist

确认这些值与 manifest.plist 中一致。

  1. 常见错误与含义
  • “未能检索清单 / Could not retrieve manifest” → manifest URL 不可访问 / HTTPS 证书问题 / 返回 HTML 而不是 XML。
  • “无法安装应用 / App installation failed” → 签名或描述文件问题(bundle id 与 profile 不匹配、证书失效或被吊销)。
  • “应用安装后闪退” → 检查崩溃日志(Xcode Devices 窗口或 Console.app)。

用户安装体验

  1. 用 iPhone 的 Safari 打开 itms-services://?action=download-manifest&url=https://.../manifest.plist(通常是你发的二维码或安装页按钮)。
  2. 点击安装,系统提示确认,点击安装。
  3. 如果是该企业证书第一次在设备上使用,需要去 设置 → 通用 → 描述文件与设备管理(或 “设备管理”)里信任该企业证书。你最好在安装页上写这个步骤并配图。

合规与安全提示

  • Apple 明确要求企业账号只用于“公司内部分发给公司员工或受管理设备”,不能对外公开发布给任意用户。滥用会被吊销企业证书。
  • 证书与私钥要谨慎保管。CI 中导入私钥要限制权限,并定期轮换。
  • 记录每次发布的版本、谁可以访问、过期提醒,建议结合 MDM 管理设备/应用权限。

场景演示

企业内部需要把一个新版本快速下发给 200 名 QA 和 10 台测试设备。推荐流程:

  1. CI(Jenkins/Fastlane)跑完单元测试后触发 enterprise lane:archive → export ipa。
  2. CI 上传 ipa 到 S3 + CloudFront(HTTPS)。
  3. CI 调用 gen_manifest.py 生成 manifest,上传到 S3。
  4. CI 在 Slack 里发出安装链接(itms-services://…)。
  5. QA 用手机打开并安装。需要时结合 MDM 强制下发并撤回。

这样既快速又可控,还能把证书管理集中在 CI 的安全区。

总结

  • 必须保证证书、描述文件正确并在 Keychain/CI 中可用;
  • .ipamanifest.plist 必须通过可信 HTTPS 提供;
  • 推荐把 manifest 生成和上传放进 CI 流程;
  • 常见问题通常是证书、HTTPS、manifest 格式或 bundle id 不一致,按本文提供的命令逐一排查即可;
  • 合规上要注意企业签名的使用范围与风险。
Logo

加入「COC·上海城市开发者社区」,成就更好的自己!

更多推荐