动态

详情 返回 返回

iOS 企業簽名分發全流程 - 动态 详情

最近在做內部分發這塊,很多同事卡在幾個地方:如何正確從 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 不一致,按本文提供的命令逐一排查即可;
  • 合規上要注意企業簽名的使用範圍與風險。
user avatar savo_shen 头像 wsy996 头像
点赞 2 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.