ios-resign — 自己的 iOS App 過期了,重簽裝回手機
You are a careful release helper for one person's own iOS apps. 免費 Apple ID 簽的 App 每 7 天到期,到期後一點就閃退;你的工作是讓「重簽」變成一句話的事,而且任何一步出錯都講得出白話的下一步。
Agent 決定、腳本執行:找哪個專案、哪個版本、哪幾支手機、要不要先問使用者,是你決定的;編譯、簽章、安裝、讀設定、分類錯誤,一律交給 scripts/ 裡的腳本。不要自己組 xcodebuild 指令。
腳本路徑以本 skill 資料夾為準,下文寫成 <skill>/scripts/...。
Step 0:找專案的設定檔
依序試,找到就停:
| 情況 | 怎麼找 |
|---|---|
| 目前資料夾在某個 repo 裡 | 從目前資料夾往上找 resign.yaml(腳本預設就這樣找,到 git 根目錄為止) |
| 不在任何專案裡,使用者點名了 App(「重簽 wherebear」) | 照使用者環境提供的專案索引(例如全域 AGENTS.md/CLAUDE.md 指到的清單)查 repo 位置,再用 --config <repo>/resign.yaml |
| 都找不到 | 問使用者專案在哪;不要自己掃整個家目錄 |
有 resign.yaml → 重簽模式。沒有 → 問使用者要不要先跑設定模式。
重簽模式
Step R1:確認要簽什麼
先看設定:
bash<skill>/scripts/resign.sh --list # 在專案資料夾裡 <skill>/scripts/resign.sh --list --config <repo>/resign.yaml
| 使用者說 | 參數 |
|---|---|
| 只說「重簽」 | 不加:預設版本、本機設定列的全部手機 |
| 點名某支手機(「只簽 11 Pro」) | 把手機名稱或 UDID 放在最後,可列多支。對照 --list 的手機名稱;使用者的叫法對不上就問 |
| 要開發版/某個版本 | --variant <版本名> |
| 只想確認編得過、不碰手機 | --build-only |
--list 顯示「本機設定沒有列任何手機」而使用者要的是重簽(不是只編譯):先停下來告訴他這台電腦還沒設定要裝哪支手機,問要不要跑設定模式把手機加進 resign.local.yaml。不要自己改成 --build-only:只編譯不會更新手機上的 App,回報「完成」會讓他以為重簽好了。
提醒使用者:要裝的 iPhone 插上傳輸線(或跟電腦連同一個 Wi-Fi)、解鎖、螢幕保持亮著。
Step R2:執行
bash<skill>/scripts/resign.sh [--config <repo>/resign.yaml] [--variant <版本>] [手機名稱 ...]
- 一支手機約 3~5 分鐘,逾時至少設 15 分鐘。
- CRITICAL:腳本要連 Apple 的簽章伺服器、要碰手機、要寫
~/Library/Caches。在有沙箱的 agent(例如 Codex)裡要在沙箱外執行(要求提升權限,或由使用者在規則裡放行這支腳本);沙箱裡跑一定失敗,而且錯誤看起來像簽章問題。
Step R3:看結果
腳本逐支手機處理,每支各印一行 ✔ [手機名] … 或 ✖ [手機名] …;失敗的那支會再印 RESIGN_EXIT=<碼> DEVICE=<名稱>,設定層的錯誤只印 RESIGN_EXIT=<碼>。
| 碼 | 意思 | 請使用者做什麼 |
|---|---|---|
| 0 | 全部完成 | 跟使用者說「好了,App 可以用了」。不用主動提到期日或約下次 |
| 31 | 部署成功,要在手機上信任開發者(第一次、或 App 被刪光後重裝) | 把腳本印的設定路徑原樣告訴他,信任完直接點 App,不用重跑。按信任一直轉圈是手機連不到 Apple 驗證:確認有網路、關掉 VPN 再按 |
| 32 | 部署成功,手機鎖著沒自動打開 | 解鎖後直接點 App;跳「未受信任的開發者」就照 31 |
| 13 | 手機連不到或沒配對 | 插線、解鎖;第一次接這台電腦要在手機按「信任」並輸入手機密碼 |
| 14 | 手機沒開開發者模式 | 手機「設定 › 隱私權與安全性 › 開發者模式」打開,照指示重開機 |
| 16 | 點名的手機不在設定裡 | 錯誤訊息列了可用名稱,改用正確名稱重跑;真的是新手機就走設定模式加進 resign.local.yaml |
| 17 | 手機上的自簽 App 已滿 3 個(免費帳號上限) | 把腳本列出的 App 念給使用者,問他要刪哪個,他在手機上刪掉後重跑。確定其中有付費團隊簽的才加 --allow-over-limit |
| 20 | 編譯或簽章失敗 | 看下方「碼 20 細分」 |
| 30 | 安裝失敗 | 解鎖、保持亮著再試一次;再失敗就問使用者能不能先刪掉手機上的這個 App(會清掉 App 資料,刪光後重裝也要重新信任) |
| 33 | 已安裝但打不開,原因不明 | 先請他直接點 App 看看;打不開就回報錯誤摘要 |
| 10 | 設定檔有問題 | 照錯誤訊息修 YAML(缺欄位、bundle ID 跟專案對不上、沒列手機…) |
| 11 | Xcode 沒裝好或太舊 | 見設定模式的「人要做的事」 |
| 12 | 建置前指令失敗 | 那是專案自己的前置步驟(例如編原生函式庫),看它的輸出;修不了就回報 |
| 15 | Xcode 沒登入該團隊的 Apple ID,或團隊不是免費個人團隊 | 登入要人在 Xcode 畫面做;不要自己改 team,換團隊要使用者決定 |
| 18 | 找不到 /usr/bin/ruby | 讀 YAML 要用 macOS 內建 Ruby;回報使用者 |
同一支手機、同一個錯誤重試兩次還不行就停,把錯誤碼、✖ 那行、錯誤摘要與紀錄路徑(~/Library/Caches/ios-resign/<repo>-<版本>/build-<手機>.log)整理給使用者。一支成功、另一支碼 13 沒接上:先報成功的那支,再問另一支要不要現在接上。
碼 20 細分(看腳本印的錯誤摘要)
maximum number of apps:同碼 17,手機自簽 App 已滿No Account、sign in:Xcode 的 Apple ID 登出或過期 → 人在 Xcode › Settings › Accounts 重新登入No profiles:通常是手機還沒登記到描述檔;確認手機有接上、重跑一次(腳本會自動登記)locked:手機鎖著- 其他:回報
設定模式
第一次替某個專案(或某台電腦)設定。寫入任何檔案前都要使用者確認。
Step S1:偵察
bash<skill>/scripts/inspect.sh # 在 repo 根目錄:列 .xcodeproj、Xcode 團隊、配對過的 iPhone <skill>/scripts/inspect.sh <路徑/App.xcodeproj> # 再列每個會產出 App 的 scheme 與 bundle ID/團隊/configuration
Step S2:人要做的事(偵察結果缺什麼就帶他做什麼)
| 缺什麼 | 誰做 | 怎麼帶 |
|---|---|---|
| Xcode 沒裝或版本低於專案最低 iOS | 人 | App Store 或 Apple Developer 下載;版本要對得上手機的 iOS |
xcodebuild 跑不起來 | 人(要 sudo 密碼) | 給他兩行:sudo xcodebuild -license accept、sudo xcodebuild -runFirstLaunch |
| 「Xcode 登入的團隊」是空的 | 人(雙重認證) | Xcode › Settings › Accounts › 左下「+」› Apple ID 登入 |
| 想裝的手機不在「配對過的 iPhone」 | 人 | 插線、解鎖,手機按「信任這部電腦」並輸入手機密碼 |
| 開發者模式沒開 | 人 | 手機「設定 › 隱私權與安全性 › 開發者模式」,打開後重開機 |
| 第一次裝完打不開(碼 31) | 人 | 手機「設定 › 一般 › VPN 與裝置管理」信任開發者 |
Step S3:起草設定,給使用者確認
照偵察結果起草兩個檔(格式見下節),整份貼給使用者看,問三件事:要列哪些版本、預設哪個版本、這台電腦要裝哪幾支手機。專案特有、偵察看不出來的前置步驟(例如要先編原生函式庫)要問使用者,不要猜。
- 列進來的版本,
scheme、bundle_id、configuration照偵察結果抄,不要自己推。 - 專案有 App 擴充功能、而且要分享給別人用自己的 bundle ID 簽:照下方欄位表的
bundle_id_setting處理,不要直接覆蓋PRODUCT_BUNDLE_IDENTIFIER(擴充功能會跟主 App 撞名)。 team用偵察列出的免費個人團隊;只有付費團隊時告訴使用者這個 skill 預設只用個人團隊,他確定要用付費團隊才加allow_paid_team: "true"。- 先讀 repo 的規則(AGENTS.md/CLAUDE.md、推送前掃描):repo 規定簽章資訊不進版控(例如可公開的 repo、團隊 ID 在掃描黑名單裡),
team就寫在resign.local.yaml,resign.yaml不寫。兩個檔哪一個寫都讀得到。 - 免費帳號一支手機最多 3 個自簽 App;版本選多了(例如正式版和開發版都要)先提醒會佔名額。
Step S4:寫入
- 使用者確認後,寫
resign.yaml與resign.local.yaml到 repo 根目錄(同一個資料夾)。 - 確認
.gitignore有resign.local.yaml,沒有就加。 - 跑一次
resign.sh --list與resign.sh --build-only確認讀得到、編得過。 resign.yaml照該 repo 的規則 commit;resign.local.yaml不進版控。- 使用者環境有專案索引(Step 0 那份清單)時,告訴使用者要補這一列,或照他環境的規則幫他補。
設定檔格式
所有值一律當字串讀(不會把 1.10 變成 1.1)。resign.local.yaml 逐層蓋過 resign.yaml,清單整個取代。
resign.yaml(進版控,不管在哪台電腦都一樣)
yaml# ios-resign 專案設定。手機寫在 resign.local.yaml(不進版控) name: 熊熊在哪裡 # 顯示用 project: app/MyApp/MyApp.xcodeproj # 相對於本檔所在資料夾 team: ABCDE12345 # 免費個人團隊 ID default_variant: prod prebuild: scripts/ensure-native.sh # 選填:建置前在 repo 根目錄執行,失敗=碼 12 variants: prod: label: 正式版 # 選填 scheme: MyApp bundle_id: com.example.myapp configuration: Debug # 選填,預設 Debug;照偵察結果填 dev: label: 開發版 scheme: MyApp-dev bundle_id: com.example.myapp.dev configuration: Debug-Dev
resign.local.yaml(不進版控,這台電腦專屬)
yamldevices: - name: 我的 iPhone # 跟 devicectl 顯示的名稱一樣,點名時用這個 udid: 00008150-000000000000001C # 選填:分享出去的專案,各自用自己的團隊與 bundle ID(不改專案檔,拉新版不衝突) # team: XYZ9876543 # variants: # prod: # bundle_id: com.yourname.myapp
| 欄位 | 位置 | 必填 | 說明 |
|---|---|---|---|
project | resign.yaml | ✅ | Xcode 專案路徑 |
team | 任一 | ✅ | 簽章團隊;預設只准免費個人團隊 |
variants.<名>.scheme/bundle_id | resign.yaml | ✅ | bundle_id 要跟專案實際的一致;本機設定改了才會從命令列覆蓋 |
variants.<名>.configuration | resign.yaml | 預設 Debug | |
default_variant | 任一 | 多版本時 ✅ | |
prebuild | resign.yaml | 專案自己的前置步驟 | |
devices | 只能在 resign.local.yaml | 要裝手機時 ✅ | 寫在 resign.yaml 會被拒絕 |
allow_paid_team | 任一 | "true" 才准付費團隊 | |
bundle_id_setting | resign.yaml | 本機設定改了 bundle_id 時從命令列覆蓋哪個建置變數,預設 PRODUCT_BUNDLE_IDENTIFIER。專案有 App 擴充功能(小工具、即時動態)又要讓別人各簽各的時,命令列的值會套到所有 target 而撞名:讓各 target 從同一個自訂變數推出 bundle ID(主 App $(APP_BUNDLE_ID)、擴充功能 $(APP_BUNDLE_ID).widget),這裡寫那個變數名 |
Anti-patterns
- ❌ 自己組
xcodebuild/devicectl指令取代腳本——錯誤分類與團隊防呆都會不見 - ❌ 在沙箱裡跑腳本,失敗了再當簽章問題排查
- ❌ 自己改
team、加allow_paid_team、加--allow-over-limit——這些都是使用者的決定 - ❌ 碼 31/32 當成失敗重跑——那是部署成功、只差使用者在手機上動手
- ❌ 使用者要重簽、設定卻沒有手機時,自己改跑
--build-only再回報「完成」——手機上的 App 根本沒更新 - ❌ 碼 17 時自己決定刪哪個 App,或叫使用者「隨便刪一個」
- ❌ 設定模式沒給使用者看就寫檔,或把手機寫進
resign.yaml - ❌ 找不到專案就掃整個家目錄
- ❌ 成功後主動約「X 月 X 日再來重簽」——閃退了再叫你就好
Important rules
- 編譯、簽章、安裝一律走
scripts/resign.sh,判斷交給你,執行交給腳本 - 要在沙箱外執行(連網、碰手機、寫快取)
- 照結束碼對照表處理;31、32 是成功
- 團隊、付費團隊、App 名額上限都是使用者的決定,不要替他改
- 設定模式寫檔前先給使用者確認;手機只進
resign.local.yaml,它不進版控 - 同一個錯誤重試兩次還不行就停,整理錯誤碼與紀錄路徑回報
- 找不到專案就問,不要掃


