From 50ba8d9297e57093daa03cf13d85be2100c8e1df Mon Sep 17 00:00:00 2001 From: host Date: Fri, 30 Oct 2026 04:18:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BB=93=E5=BA=93=E6=8E=A5=E5=85=A5?= =?UTF-8?q?=E9=80=9A=E7=94=A8=E5=8C=96=E2=80=94=E2=80=94=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E9=A2=84=E6=A3=80=20+=20=E5=A4=9A=E4=BB=93=20ssh=20=E5=88=AB?= =?UTF-8?q?=E5=90=8D=E9=85=8D=E7=BB=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cloud/repos.py | 26 +++++++++++++++++++++++- cloud/tests/test_project_repo.py | 20 +++++++++++++++++++ deploy/provision_codexsvc_key.sh | 34 ++++++++++++++++++++++++++++++-- 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/cloud/repos.py b/cloud/repos.py index 300b64b..5a35457 100644 --- a/cloud/repos.py +++ b/cloud/repos.py @@ -38,6 +38,7 @@ __all__ = [ "checkout_dir", "resolve_repo", "check_repo_access", + "check_repo_access_many", ] @@ -203,10 +204,33 @@ def check_repo_access(repo_url, runner=None, timeout=30): return (True, head[0] if head else "") +def check_repo_access_many(repo_urls, runner=None, timeout=30): + """批量预检:逐个调 check_repo_access,返回 {url: (ok, detail)}。 + + 顺序与输入一致;单个失败不中断其余(批量预检的意义就是一次 + 看清哪些仓通、哪些不通)。空列表返回空 dict。 + """ + out = {} + for url in repo_urls or []: + out[url] = check_repo_access(url, runner=runner, timeout=timeout) + return out + + def _main(argv): + if len(argv) >= 3 and argv[1] == "check-many": + results = check_repo_access_many(argv[2:]) + failed = 0 + for url, (ok, detail) in results.items(): + if ok: + print("ok %s HEAD=%s" % (url, detail)) + else: + failed += 1 + print("fail %s: %s" % (url, detail)) + return 1 if failed else 0 if len(argv) != 3 or argv[1] != "check": sys.stderr.write( - "用法: python3 -m cloud.repos check \n") + "用法: python3 -m cloud.repos check \n" + " python3 -m cloud.repos check-many ...\n") return 2 ok, detail = check_repo_access(argv[2]) if ok: diff --git a/cloud/tests/test_project_repo.py b/cloud/tests/test_project_repo.py index 3d49f81..b5e8785 100644 --- a/cloud/tests/test_project_repo.py +++ b/cloud/tests/test_project_repo.py @@ -146,6 +146,26 @@ class CheckRepoAccessTestCase(unittest.TestCase): self.assertFalse(ok) self.assertIn("timed out", detail) + def test_many_continues_after_failure(self): + seen = [] + + def runner(cmd, timeout=None): + seen.append(cmd[2]) + if "good" in cmd[2]: + return SimpleNamespace(returncode=0, + stdout="abc123\tHEAD\n", stderr="") + return SimpleNamespace(returncode=128, stdout="", + stderr="fatal: denied") + from cloud.repos import check_repo_access_many + out = check_repo_access_many( + ["git@github.com:o/good.git", "git@github.com:o/bad.git"], + runner=runner) + self.assertEqual(seen, ["git@github.com:o/good.git", + "git@github.com:o/bad.git"]) + self.assertTrue(out["git@github.com:o/good.git"][0]) + self.assertFalse(out["git@github.com:o/bad.git"][0]) + self.assertEqual(check_repo_access_many([]), {}) + class CheckoutDirTestCase(unittest.TestCase): def test_sanitizes_and_stays_inside(self): diff --git a/deploy/provision_codexsvc_key.sh b/deploy/provision_codexsvc_key.sh index 7280f32..c26c3e3 100755 --- a/deploy/provision_codexsvc_key.sh +++ b/deploy/provision_codexsvc_key.sh @@ -4,13 +4,20 @@ # 用法(主机上以 root 跑): # ./provision_codexsvc_key.sh git@github.com:ORG/REPO.git [key-name] # ./provision_codexsvc_key.sh --check git@github.com:ORG/REPO.git [key-name] +# ./provision_codexsvc_key.sh --list # # 流程: # 1. 在 /home/codexsvc/.ssh/ 生成 ed25519 key(已存在则复用,绝不覆盖); # 2. 打印公钥(只打印公钥,私钥绝不打印),请把公钥以只读 deploy key # 加到 GitHub 仓库 Settings -> Deploy keys; -# 3. --check 时以 codexsvc 身份跑 git ls-remote,通了才算配好。 -# 4. 另有现成的 key 要复用时,把 key 文件拷到上述目录并 chown +# 3. 在 /home/codexsvc/.ssh/config 里幂等地写一个 Host 别名 +# github-(HostName github.com、该 key、IdentitiesOnly): +# 多仓各用各的别名,项目 repo_url 写 git@github-:ORG/REPO.git +# 形,git 按别名自动选 key,不用在 worker 里拼 GIT_SSH_COMMAND, +# 也不用担心 ssh 试遍全部 key 撞 GitHub 的尝试上限; +# 别名块带 # codexsvc- 标记,已存在时不重复写。 +# 4. --check 时以 codexsvc 身份跑 git ls-remote,通了才算配好。 +# 5. 另有现成的 key 要复用时,把 key 文件拷到上述目录并 chown # codexsvc、chmod 600,再用 --check 验,不要改本脚本去读 root 的 key。 # # 安全边界:本脚本不往控制面、环境文件或任何日志里写私钥;公钥 @@ -20,6 +27,11 @@ CHECK=0 if [ "${1:-}" = "--check" ]; then CHECK=1; shift fi +if [ "${1:-}" = "--list" ]; then + ls -1 /home/codexsvc/.ssh/id_*.pub 2>/dev/null || true + grep -h "^Host github-" /home/codexsvc/.ssh/config 2>/dev/null || true + exit 0 +fi URL="${1:-}" NAME="${2:-codex-github}" if [ -z "$URL" ]; then @@ -45,6 +57,24 @@ if [ ! -f "$KEY" ]; then fi chmod 600 "$KEY" 2>/dev/null || true chown codexsvc:codexsvc "$KEY" "$KEY.pub" 2>/dev/null || true +CONFIG="$SSH_DIR/config" +touch "$CONFIG" +chown codexsvc:codexsvc "$CONFIG" +chmod 600 "$CONFIG" +if ! grep -q "^# codexsvc-$NAME$" "$CONFIG" 2>/dev/null; then + cat >> "$CONFIG" <