我在個人開發的 Minecraft 伺服器監控 App「MineWatch」(官方網站、App 的整體介紹在這裡)的 iOS 端,把 UIKit 的共用元件拆到另一個名為「YoLibrary」的儲存庫中,並透過 Swift Package Manager 參照它。原本我想把對 YoLibrary 的依賴從 SSH 參照改成 SSH-free 的形式,結果經歷了兩次嘗試,以及一次讓我的既有認知在一天內被推翻的事件。這篇文章就是在整理那段經過。
url: 參照(SSH)引入,但每多一台 CI 節點,就得多發一組個人的 SSH 金鑰,所以我想把它改成 SSH-free。multiple similar targets 而失敗。~/.netrc 默默生效所以沒發現,直到在隔離的 Docker 建置裡才第一次以 401 的形式浮現。YoLibrary 是放在 GitHub 個人帳號下的 private 儲存庫。從 MineWatch 端用 SwiftPM 參照時,如果直接用原生的 https://github.com/...,會被當成另一個帳號(工作用帳號)來驗證,結果被擋下來,所以我一直是透過 SSH 的 github-personal alias 來參照。這樣雖然能運作,但每多一台 Jenkins 建置節點,就得把個人的 SSH 金鑰發到那台節點上。以個人開發的金鑰管理來說,這是我想避免的架構。
我最先嘗試的是,改成依附在我架設於 Nexus 上的 Swift Package Registry。做法是把 YoLibrary 的 Package.swift 中,原本以 URL 宣告的依賴(例如 swift-openapi-runtime)改成用 Registry 的識別子(id:)來宣告,並透過 Nexus 的 swift-hosted 來解析。
但 MineWatch 本體也直接用一般的 url: 方式依賴同一個 swift-openapi-runtime。當這兩種參照方式混在一起時,建置就會出現以下錯誤而失敗:
multiple similar targets 'OpenAPIRuntime' appear in registry package
'apple.swift-openapi-runtime' and source control package 'swift-openapi-runtime'
SwiftPM 無法把經由 Registry 解出來的 OpenAPIRuntime,和經由原始碼控制解出來、但名稱相同的 OpenAPIRuntime 視為同一個套件。即使把 project.yml 裡的套件鍵名改成跟 Registry 識別子字串一致,問題也還是會重現,因此我判斷這不是設定錯誤,而是 SwiftPM 的 Registry 轉換機制本身還沒解決的行為。此外,XcodeGen 本身也不支援經由 Registry 的遠端參照,這是更根本的限制。
這次嘗試在同一天內就撤退了。YoLibrary 這邊,在改成 Registry id: 參照的 commit 之後,很快又改回一般的 url: 參照。
fix: 將依賴宣告從 Registry 的 id: 參照改回 url: 參照 (#8)
嘗試透過 Nexus swift-hosted 進行 Registry 解析,但會和依賴它的
MineWatch 端直接依賴(apple.swift-openapi-runtime)被判定成「看起來很像
但其實不同」的東西,導致建置以 multiple similar targets 失敗,問題尚未解決,
因此放棄。改回一般的 SCM 參照(url:)。
到這個時間點,我把這個議題先以「優先度低、暫緩」的狀態結案了。後來回頭檢查時,才發現其實有兩個內容相同的 issue(重複)。之所以沒有急著處理,是因為目前靠既有的 SSH 參照就能運作,實際痛點還不算大。
幾天後,在做另一項工作(導入 App Check)時,我順手重新挑戰了這件事。這次不再用 Registry,而是採用保留 GitHub 為正本,僅將 main 分支與 tag 自動鏡像到 Forgejo(git.sk4869.info),然後用 .package(url:) 以 HTTPS 參照的方式。SwiftPM 的解析方式不變,所以可以避開 Registry 轉換的 bug。
YoLibrary:
url: https://git.sk4869.info/honoka4869/YoLibrary.git
from: 1.5.0
在加入這個變更後,程式碼裡的註解是這樣寫的:
Forgejo 端的儲存庫是 public,所以 consumer 端可以在沒有驗證資訊的情況下 clone(之所以不直接用 GitHub 本體的 SSH 參照,是為了避免每多一台 consumer 端 CI 節點,就得多發一把個人的 SSH 金鑰)。
我原本以為 SSH 金鑰的配發問題已經解決了。
但就在這個變更後僅僅 1 天,YoLibrary 端的 README 就做了下面這個修正:
-`url:` 形式のまま HTTPS で参照する形にした。Forgejo 側リポジトリは public
-なので、consumer 側は認証情報なしで clone できる(GitHub 本体を SSH で
-参照しないのは、consumer 側の CI ノードを増やすたびに個人の SSH 鍵を配って
-回る必要が無いようにするため)。
+`url:` 形式のまま HTTPS で参照する形にした。GitHub 本体を SSH で参照しない
+のは、consumer 側の CI ノードを増やすたびに個人の SSH 鍵を配って回る必要が
+無いようにするため。
+
+Forgejo 側リポジトリは private なので、consumer 側で読み取り専用の
+Personal Access Token を `.netrc` に設定しておく必要がある。
「public」這個前提本身就是錯的,實際上它是 private。YoLibrary 端的文件很快就修正了,但 MineWatch 端程式碼裡的註解,卻沒有跟著更新,最後就這樣留了下來。
在我沒察覺到這個落差的情況下過了幾天,當我加入另一個依賴時,類似的問題又以不同的形式重現了。MineWatch 的 API 會用我自己在另一個儲存庫開發與公開的 Python client,來對 Minecraft 伺服器執行指令(RCON)。一開始,這個依賴也是直接透過 git+https 參照(指向 git.sk4869.info 上的儲存庫)引入的。
在開發機和 Jenkins 的 Mac agent 上,這個依賴都能正常解析。原因很單純:開發機的 ~/.netrc 裡,已經有之前作業時設定好的認證資訊。也就是說,本機建置與 Mac 上的 CI 都會自動使用它來通過驗證。
問題是在用 Kaniko 建置 API 的容器映像時才浮現。Docker 的建置 context 是隔離的,所以主機上的 ~/.netrc 不會帶進去。每次要抓這個依賴時,都會因為驗證錯誤而失敗。
MineWatch 端的 api/Dockerfile 在用 Kaniko 建置時,嘗試依賴 RUN --mount=type=secret
(BuildKit 專用功能,Kaniko 尚未實作)來傳遞 mc-rcon-py 的 git+https 擷取驗證,
結果每次都以 401 失敗
修正方式一開始是想從 Jenkins 的 Credential 透過環境變數把驗證資訊傳進去。不過考慮到 Kaniko 的限制(不能使用 BuildKit 專用的 RUN --mount=type=secret),我判斷這種每次建置都要搬運驗證資訊的作法本身就很脆弱,所以最後把這個依賴從 git 直接參照改成發佈到 Nexus 的 pypi-hosted 儲存庫。只要打上版本 tag,就能用一般的套件名稱與版本號來解析,從此也不再需要對 Forgejo 做驗證。
回顧這一連串事件後,我注意到一件事:「public」這個錯誤前提,在撰寫本文時仍然留在 MineWatch 端的程式碼裡。 ios/project.yml 的註解現在仍寫著:
# 改成使用自動鏡像(YoLibrary 儲存庫的 mirror-to-forgejo.yml、
# 只同步 main + tags)的方式。鏡像站是 public,所以
# consumer 端可以在沒有驗證資訊的情況下 clone。
實際上,iOS 的建置是直接跑在 Jenkins 的同一台 Mac agent 上,所以現在仍然會吃到和開發機相同的 ~/.netrc,因此功能上並沒有出問題。結果就是「因為還能動,所以就沒有改」,只留下錯誤的敘述。
另外,專案規範整理文件裡也還留著這段:
YoLibrary 是透過 SSH 的
github-personalalias 來做版本固定參照(使用個人帳號的金鑰;直接用 github.com 會被擋下來)。
這是舊的參照方式、也就是在切到 Forgejo 之前的說明,現在還原封不動地留著。實際的參照目標,已經像 project.yml 所寫的那樣,改成 HTTPS 的 Forgejo 鏡像了。
~/.netrc)。一開始以為「public」的前提,當天就被證明是錯的,但只修了文件的一邊。這畢竟是個人開發環境的配置,如果要直接套到團隊或組織環境,請先確認 CI 節點的驗證資訊配發與輪替政策。
在我寫完這篇文章後,已經把 ios/project.yml 的註解與 CLAUDE.md 之間的落差修正了。原本「鏡像站是 public」的敘述已經刪除,改成符合實際情況的說法:Forgejo 端是 private,且需要在 ~/.netrc 中設定唯讀的 Personal Access Token。CLAUDE.md 也從切到 Forgejo 之前那種舊的「透過 SSH 的 github-personal alias」說明,更新成現在以 HTTPS 鏡像參照的描述。
寫這篇文章本身,反而成了發現那些還沒修正的落差的契機。
JQIT 的工程師有 95% 以上是從零經驗錄用的。
如果有興趣,也歡迎來公司網站逛逛。
零經驗也能學習!一起挑戰吧!
也有經營 note 和 X ↓
原文出處:https://qiita.com/jqit-yukiono/items/9d8a5b91dbe3b81cb1b2