最新版本 iOS 企业签名分发全流程
最近在做内部分发这块,很多同事卡在几个地方:如何正确从 Xcode / CI 打包出 enterprise .ipa,怎么生成 manifest.plist,把二者托管在 HTTPS 上,然后通过 itms-services:// 给同事一键安装。下面把流程从头到尾讲清楚——口语化、贴近实操,并附上可运行的 Demo(Python + shell),能直接放到 CI 里跑。
大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括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 里跑。
先把整个流程捋一遍
你要做的事其实就是三件:
- 在 Apple 企业账号里搞好证书和描述文件(In-House)。
- 用 Xcode / xcodebuild 把 App 打包并导出 enterprise
.ipa
。 - 把
.ipa
和manifest.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 管理里,不要明文在脚本里。
证书与描述文件
-
在 Apple 企业账号里创建 In-House(Enterprise)分发证书:
- 在开发者账号页面 → Certificates → Add → 选择 “In-House and Ad Hoc” → 上传 CSR(Certificate Signing Request) → 下载证书
.cer
。 - 把证书和对应私钥导入到 macOS Keychain(或在 CI 上生成
.p12
并导入)。
- 在开发者账号页面 → Certificates → Add → 选择 “In-House and Ad Hoc” → 上传 CSR(Certificate Signing Request) → 下载证书
-
创建 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
。
- Apple Developer → Identifiers → 新增 App ID(如
-
在本地或 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)
- 先 archive:
xcodebuild -workspace MyApp.xcworkspace \
-scheme MyApp \
-configuration Release \
-archivePath /tmp/MyApp.xcarchive \
clean archive
- 然后导出
.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
必须与.ipa
里Info.plist
的CFBundleIdentifier
一致。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 脚本(假设你已经有 .ipa
在 build/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。
终端测试与故障排查(最常见的错误 & 验证命令)
- 先在手机 Safari 打开 manifest.plist 链接,确认能访问(会显示 XML 内容)。
- 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
- 检查 HTTPS 证书链:
openssl s_client -connect dl.example.com:443 -showcerts
- 查看本地可用的 codesign identity(调试签名问题):
security find-identity -v -p codesigning
- 检查 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
中一致。
- 常见错误与含义:
- “未能检索清单 / Could not retrieve manifest” → manifest URL 不可访问 / HTTPS 证书问题 / 返回 HTML 而不是 XML。
- “无法安装应用 / App installation failed” → 签名或描述文件问题(bundle id 与 profile 不匹配、证书失效或被吊销)。
- “应用安装后闪退” → 检查崩溃日志(Xcode Devices 窗口或 Console.app)。
用户安装体验
- 用 iPhone 的 Safari 打开
itms-services://?action=download-manifest&url=https://.../manifest.plist
(通常是你发的二维码或安装页按钮)。 - 点击安装,系统提示确认,点击安装。
- 如果是该企业证书第一次在设备上使用,需要去 设置 → 通用 → 描述文件与设备管理(或 “设备管理”)里信任该企业证书。你最好在安装页上写这个步骤并配图。
合规与安全提示
- Apple 明确要求企业账号只用于“公司内部分发给公司员工或受管理设备”,不能对外公开发布给任意用户。滥用会被吊销企业证书。
- 证书与私钥要谨慎保管。CI 中导入私钥要限制权限,并定期轮换。
- 记录每次发布的版本、谁可以访问、过期提醒,建议结合 MDM 管理设备/应用权限。
场景演示
企业内部需要把一个新版本快速下发给 200 名 QA 和 10 台测试设备。推荐流程:
- CI(Jenkins/Fastlane)跑完单元测试后触发 enterprise lane:archive → export ipa。
- CI 上传 ipa 到 S3 + CloudFront(HTTPS)。
- CI 调用
gen_manifest.py
生成 manifest,上传到 S3。 - CI 在 Slack 里发出安装链接(itms-services://…)。
- QA 用手机打开并安装。需要时结合 MDM 强制下发并撤回。
这样既快速又可控,还能把证书管理集中在 CI 的安全区。
总结
- 必须保证证书、描述文件正确并在 Keychain/CI 中可用;
.ipa
与manifest.plist
必须通过可信 HTTPS 提供;- 推荐把 manifest 生成和上传放进 CI 流程;
- 常见问题通常是证书、HTTPS、manifest 格式或 bundle id 不一致,按本文提供的命令逐一排查即可;
- 合规上要注意企业签名的使用范围与风险。
更多推荐
所有评论(0)