GitProxy 2.3 常用 CLI 命令与 REST API 详解

面向 GitProxy 2.3 管理员与自动化场景的命令行与接口手册:服务端 gitproxy 启动参数、管理台 REST API(认证/用户/仓库/推送审批)与推送人 CLI 的实测用法,并给出 git push 触发审批的完整闭环回显。

GitProxyGitProxyGitOps2026/10/26

GitProxy 2.3 常用 CLI 命令与 REST API 详解

目标:讲清 GitProxy v2.3.0 的三套操作面——服务端命令行(启动与配置校验)、管理台 REST API(认证 / 用户 / 仓库 / 推送审批四域)、推送人 CLI(@finos/git-proxy-cli),每条命令都给出真实执行回显,并用 git push 把「推送 → 审批 → 落地 / 驳回 / 取消」全状态闭环实测一遍。已在 Ubuntu 22.04(x86_64)+ GitProxy v2.3.0 实测通过。

一、环境信息

项值
操作系统Ubuntu 22.04 LTS(x86_64),GitProxy 部署机兼工作机
GitProxyv2.3.0(node dist/index.js --version 实测 2.3.0,运行时 Node.js v24.14.0)
部署形态Docker Compose + Gitea 1.23.8 上游,见《GitProxy 2.3 Docker Compose 部署指南》
端口Git 代理 8149(HTTP)/ 8151(HTTPS);管理台 8150(HTTP)/ 8152(HTTPS);Gitea 8147
域名gitproxy.local、gitea.local(部署篇 4.4 节已写入服务器 /etc/hosts)
演示账号admin(管理员)、reviewer(审批人)、dev1(本篇新建的普通账号),口令均为 q1w2e3r4
Gitea Token属主 admin,作用域 read:user + write:repository(交互式录入,不落盘)
配置文件/opt/gitproxy-2.3/config/proxy.config.json,csrfProtection: true、rateLimit 60 秒 1000 次,逐键语义见《GitProxy 2.3 配置参数详解》

本篇 API 命令统一以 http://gitproxy.local:8150 为基地址(与部署篇的 /etc/hosts 记录一致,等价于 127.0.0.1:8150)。

二、工具概览

GitProxy v2.3.0 提供三个操作面,底层共享同一套数据与权限模型:

操作面入口使用者典型用途
服务端命令行node dist/index.js [options](GitProxy 安装目录内)运维启动服务、指定配置文件、改配置前校验合法性
REST API管理台 8150 端口,/api/auth(认证)与 /api/v1(业务)两组前缀管理台 UI、脚本/自动化账号与仓库管理、审批、状态查询
推送人 CLIgit-proxy-cli(@finos/git-proxy-cli 包,v2.3.0)推送人登录、查询待审批推送、命令行内审批

三者关系:管理台 UI 与本篇所有 curl 命令调用的是同一批 REST 端点;推送人 CLI 是这些端点的薄封装(不实现 CSRF 获取,详见 7.4 节);服务端命令行只负责进程生命周期,与 API 无关。

三、前置条件

  1. 已按《GitProxy 2.3 Docker Compose 部署指南》完成部署:健康检查正常(GET /api/v1/healthcheck 返回 {"message":"ok"}),admin / reviewer 账号与仓库授权矩阵就绪(部署篇 4.7 节);
  2. /etc/hosts 已含 gitproxy.local 与 gitea.local 两条记录;
  3. 本机装有 curl 与 git 2.34.1(git --version 实测),用于 API 调用与推送闭环;
  4. Gitea 侧已为 admin 签发 Personal Access Token(作用域 read:user + write:repository;网页操作:Settings → Applications → Generate New Token)。推送命令用交互式录入避免明文:
read -s -p "Gitea Token: " GITEA_TOKEN && echo

四、服务端命令行详解

服务端入口是 GitProxy 安装目录下的 node dist/index.js(部署篇 compose 的启动命令即 node --enable-source-maps dist/index.js --config /app/proxy.config.json;--enable-source-maps 是 Node.js 参数,不是 gitproxy 参数)。以下命令均在安装目录内执行。

4.1 命令总览(--help)

node dist/index.js --help
Configuration sources are disabled
Configuration loaded successfully
Usage: index.js [options]

Options:
      --help      Show help                                            [boolean]
      --version   Show version number                                  [boolean]
  -v, --validate  Check the proxy.config.json file in the current working
                  directory for validation errors.                     [boolean]
  -c, --config    Path to custom git-proxy configuration file.
                               [string] [default: "/app/dist/proxy.config.json"]

三点解读:

  • 前两行是启动日志而非帮助文本的一部分:每次执行(包括 --help)都会先加载配置,Configuration sources are disabled 表示外部配置源特性未启用(配置参数详解篇 5.14 节);
  • -c 的默认值是安装目录内的 dist/proxy.config.json(发布包内置默认配置),不带 --config 启动时读的就是它——这正是部署篇 4.4 节强制 command 里显式传 --config /app/proxy.config.json 的原因;
  • 没有独立的 start 子命令:不带 --help / --version / -v 执行即直接启动服务。

4.2 --version

node dist/index.js --version
Configuration loaded successfully
2.3.0

4.3 配置校验(-v / --validate)

校验模式只做检查、不启动服务。不带 -c 时按默认路径检查:

node dist/index.js -v
Configuration sources are disabled
Configuration loaded successfully
Setting config file to: /app/dist/proxy.config.json
/app/dist/proxy.config.json is valid

校验部署实际加载的配置文件(部署篇挂载进来的 /app/proxy.config.json):

node dist/index.js -v -c /app/proxy.config.json
Configuration sources are disabled
Configuration loaded successfully
Setting config file to: /app/proxy.config.json
/app/proxy.config.json is valid

把合法配置改成非法(比如 serverPort 传字符串)后再校验,会得到明确的键名与类型报错,这一形态的完整实测见配置参数详解篇 3.3 节。运维惯例:改配置 → -v -c 校验 → 重启服务。

五、REST API 详解

5.1 认证与会话(/api/auth)

csrfProtection: true 时,所有写请求(POST/PUT/PATCH/DELETE)都必须先取 CSRF Token 并随 X-CSRF-Token 头提交,同时用 Cookie 维持会话。Token 每次写操作前都要重新获取——复用旧 Token 会得到 401(实测回显):

{"message":"CSRF token mismatch"}

统一约定:会话 Cookie 存入 admin.jar,用 python3 -c 从响应里提取 Token 到变量。完整登录闭环:

CT=$(curl -s -c admin.jar http://gitproxy.local:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -b admin.jar -c admin.jar -X POST http://gitproxy.local:8150/api/auth/login \
  -H 'Content-Type: application/json' -H "X-CSRF-Token: $CT" \
  -d '{"username":"admin","password":"q1w2e3r4"}'
{"message":"success","user":{"username":"admin","displayName":"","email":"admin@place.com","title":"","scmIdentities":{"local-gitea":"admin"},"admin":true}}

scmIdentities 显示该账号已绑定 SCM 供应商身份 local-gitea(推送时网关据此映射凭据,见配置参数详解篇 5.12 节)。当前会话信息与会话终止:

curl -s -b admin.jar http://gitproxy.local:8150/api/auth/profile
{"username":"admin","displayName":"","email":"admin@place.com","title":"","scmIdentities":{"local-gitea":"admin"},"admin":true}
CT=$(curl -s -b admin.jar -c admin.jar http://gitproxy.local:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -b admin.jar -c admin.jar -X POST http://gitproxy.local:8150/api/auth/logout -H "X-CSRF-Token: $CT"
{"isAuth":false,"user":null}

登出后再查 profile 返回 401:

{"message":"Not logged in"}

健康检查无需会话:

curl -s http://gitproxy.local:8150/api/v1/healthcheck
{"message":"ok"}

5.2 用户域(/api/v1/user + 创建/改密)

用户清单(管理员会话)。返回数组,每项含 _id、username、email、admin、scmIdentities 等字段;本环境当前 5 个账号:

curl -s -b admin.jar http://gitproxy.local:8150/api/v1/user | python3 -c "import json,sys;[print(u['username'],'| admin:',u['admin'],'|',u['email']) for u in json.load(sys.stdin)]"
admin | admin: True | admin@place.com
user | admin: False | user@place.com
reviewer | admin: False | reviewer@example.com
dev1 | admin: False | dev1@example.com
democli | admin: False | democli@example.com

单个用户详情(/api/v1/user/<username>):

curl -s -b admin.jar http://gitproxy.local:8150/api/v1/user/reviewer
{"username":"reviewer","displayName":"","email":"reviewer@example.com","title":"","scmIdentities":{},"admin":false}

创建用户(POST /api/auth/create-user,管理员会话)。新建 dev1,先用临时口令创建:

CT=$(curl -s -b admin.jar -c admin.jar http://gitproxy.local:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -w '\nHTTP:%{http_code}\n' -b admin.jar -X POST http://gitproxy.local:8150/api/auth/create-user \
  -H 'Content-Type: application/json' -H "X-CSRF-Token: $CT" \
  -d '{"username":"dev1","password":"dev1temp123","email":"dev1@example.com"}'
{"message":"User created successfully","username":"dev1"}
HTTP:201

修改密码(POST /api/auth/change-password,本人会话)。模拟 dev1 首次登录后把临时口令换成演示口令:

CT=$(curl -s -c dev1.jar http://gitproxy.local:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -b dev1.jar -c dev1.jar -X POST http://gitproxy.local:8150/api/auth/login \
  -H 'Content-Type: application/json' -H "X-CSRF-Token: $CT" \
  -d '{"username":"dev1","password":"dev1temp123"}'
CT=$(curl -s -b dev1.jar -c dev1.jar http://gitproxy.local:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -b dev1.jar -X POST http://gitproxy.local:8150/api/auth/change-password \
  -H 'Content-Type: application/json' -H "X-CSRF-Token: $CT" \
  -d '{"currentPassword":"dev1temp123","newPassword":"q1w2e3r4"}'
{"message":"Password updated successfully"}

5.3 仓库域(/api/v1/repo)

仓库白名单清单:

curl -s -b admin.jar http://gitproxy.local:8150/api/v1/repo/ | python3 -c "import json,sys;[print(r['_id'], r['project']+'/'+r['name']) for r in json.load(sys.stdin)]"
JgcqA5axqbi0LcWa admin/hello-gitproxy
eoe5s1nmyjFFmN71 admin/second-repo

新增白名单仓库(POST /api/v1/repo/,_id 由服务端生成,proxyURL 自动拼装):

CT=$(curl -s -b admin.jar -c admin.jar http://gitproxy.local:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -w '\nHTTP:%{http_code}\n' -b admin.jar -X POST http://gitproxy.local:8150/api/v1/repo/ \
  -H 'Content-Type: application/json' -H "X-CSRF-Token: $CT" \
  -d '{"project":"admin","name":"api-demo","url":"https://gitea.local:3000/admin/api-demo.git"}'
{"project":"admin","name":"api-demo","url":"https://gitea.local:3000/admin/api-demo.git","users":{"canPush":[],"canAuthorise":[]},"dateCreated":"2026-10-26T04:21:31.146Z","lastModified":"2026-10-26T04:21:31.146Z","_id":"TvJVq0VVeXOPxsKQ","proxyURL":"http://gitproxy.local:8149","message":"created"}
HTTP:201

_id 为 TvJVq0VVeXOPxsKQ。给 admin 授推送权、给 reviewer 授审批权(PATCH,{"username": …}):

CT=$(curl -s -b admin.jar -c admin.jar http://gitproxy.local:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -b admin.jar -X PATCH http://gitproxy.local:8150/api/v1/repo/TvJVq0VVeXOPxsKQ/user/push \
  -H 'Content-Type: application/json' -H "X-CSRF-Token: $CT" -d '{"username":"admin"}'
CT=$(curl -s -b admin.jar -c admin.jar http://gitproxy.local:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -b admin.jar -X PATCH http://gitproxy.local:8150/api/v1/repo/TvJVq0VVeXOPxsKQ/user/authorise \
  -H 'Content-Type: application/json' -H "X-CSRF-Token: $CT" -d '{"username":"reviewer"}'
{"message":"created"}
{"message":"created"}

复核授权矩阵(GET /api/v1/repo/<_id>):

curl -s -b admin.jar http://gitproxy.local:8150/api/v1/repo/TvJVq0VVeXOPxsKQ
{"project":"admin","name":"api-demo","url":"https://gitea.local:3000/admin/api-demo.git","users":{"canPush":["admin"],"canAuthorise":["reviewer"]},"dateCreated":"2026-10-26T04:21:31.146Z","lastModified":"2026-10-26T04:21:47.904Z","_id":"TvJVq0VVeXOPxsKQ","proxyURL":"http://gitproxy.local:8149"}

回收与删除:DELETE /api/v1/repo/<_id>/user/authorise/reviewer 回收审批权,DELETE /api/v1/repo/<_id>/delete 删除整条白名单记录(两者回显均为 {"message":"created"});删除后清单复核,api-demo 已消失,仅剩 hello-gitproxy 与 second-repo。

5.4 推送域(/api/v1/push)

推送记录清单(返回数组,按时间倒序,含历史记录)。用脚本打印最新两条的状态四元组:

curl -s -b admin.jar http://gitproxy.local:8150/api/v1/push/ | python3 -c "
import json,sys
for p in json.load(sys.stdin)[:2]:
    print(p['id'][:16], p['repoName'], '| authorised:',p['authorised'],'rejected:',p['rejected'],'canceled:',p['canceled'],'blocked:',p['blocked'])"
27243868d87ba1cb admin/hello-gitproxy | authorised: False rejected: False canceled: True blocked: True
2d13fe11cb713cad admin/hello-gitproxy | authorised: False rejected: True canceled: False blocked: True

单条详情 GET /api/v1/push/<id>,完整记录含提交明细(commitData)、diff 全文与检查步骤流水(steps)。步骤多、diff 长,用脚本把 steps 折叠为步骤名序列、剔除 diff,字段结构一目了然:

curl -s -b admin.jar http://gitproxy.local:8150/api/v1/push/e9bbecdd76b07e8e7759f33ee3d3efe3829adaf5d1a52d9886e85a58c7faf521 \
  | python3 -c "import json,sys; d=json.load(sys.stdin); d['steps']=[s['stepName'] for s in d['steps']]; d.pop('diff',None); d['commitData']=[{k:c.get(k) for k in ('message','author')} for c in d['commitData']]; print(json.dumps(d,ensure_ascii=False,indent=1))" | head -40
{
 "id": "e9bbecdd76b07e8e7759f33ee3d3efe3829adaf5d1a52d9886e85a58c7faf521",
 "project": "admin",
 "repoName": "hello-gitproxy",
 "branch": "main",
 "type": "commit",
 "authorised": true,
 "rejected": false,
 "canceled": false,
 "blocked": false,
 "error": false,
 "user": "admin",
 "userEmail": "admin@example.com",
 "commitData": [
  {
   "message": "docs: d1 api closed-loop test",
   "author": "admin"
  }
 ],
 "steps": [
  "parsePackFile",
  "resolveUserFromToken",
  "checkRepoInAuthorisedList",
  "checkMessages",
  "checkAuthorEmails",
  "checkUserPushPermission",
  "pullRemote",
  "writePack",
  "checkHiddenCommits",
  "checkIfWaitingAuth",
  "executeExternalPreReceiveHook",
  "diff",
  "gitleaks",
  "scanDiff",
  "authBlock",
  "clearBareClone"
 ],
 ...

steps 就是六节回显里那些 remote: Checking … 的内部对应物:从解析包、身份解析、白名单/消息/邮箱/权限检查,到 diff 与密钥扫描,最后 authBlock 决定放行还是挂起审批。状态字段含义:

组合含义
authorised: false, blocked: true, canceled: false, rejected: false待审批(默认态)
authorised: true, blocked: false已批准,复推后落地
rejected: true, blocked: true已驳回(rejection.reason 带驳回理由与审批人)
canceled: true, blocked: true推送人已自行取消

5.5 推送审批三个写端点

三个写端点的请求体与回显(均在六节闭环中实测):

端点请求体成功回显
POST /api/v1/push/<id>/authorise{"params":{"attestation":[{"label":"…","checked":true},…]}}{"message":"authorised <id>"}
POST /api/v1/push/<id>/reject{"reason":"…"}(缺 reason 返回 400){"message":"reject <id>"}
POST /api/v1/push/<id>/cancel无{"message":"cancel <id>"}

三条硬约束(实测):attestation 数组的 label 必须与 attestationConfig.questions 逐字一致,否则 400 Attestation is not complete;推送人审批自己发起的推送返回 User admin not authorised to approve pushes on this project(四眼原则);cancel 只能由推送人本人对未决推送调用。

六、git push 触发审批实测闭环

以下命令全部在演示工作机执行(工作机同样按部署篇配置了 /etc/hosts 与对 gitproxy.local:8149 的访问)。仓库 /root/work/hello-gitproxy 克隆自上游 Gitea(部署篇 4.8 节),GITEA_TOKEN 已按三节交互式录入。

6.1 推送入队(待审批)

cd /root/work/hello-gitproxy
echo "d1-api-loop-test $(date +%s)" > d1note.txt
git add d1note.txt && git commit -qm "docs: d1 api closed-loop test"
git push "http://admin:${GITEA_TOKEN}@gitproxy.local:8149/gitea.local:3000/admin/hello-gitproxy.git" main

实测回显(输出节选,行尾多空格略):

remote: Resolving user from token...
remote: Checking for empty branch...
remote: Checking repository is authorised...
remote: Checking commit messages...
remote: Checking author emails...
remote: Checking push permissions...
remote: Fetching remote repository...
remote: Writing pack data...
remote: Checking for hidden commits...
remote: Checking approval status...
remote: Running pre-receive hook...
remote: Computing diff...
remote: Scanning for secrets...
remote: Scanning diff contents...
remote: Requesting approval...
remote:
remote: GitProxy has received your push ✅
remote: 🔗 Shareable Link
remote: http://gitproxy.local:8150/dashboard/push/e9bbecdd76b07e8e7759f33ee3d3efe3829adaf5d1a52d9886e85a58c7faf521
To http://gitproxy.local:8149/gitea.local:3000/admin/hello-gitproxy.git
 ! [remote rejected] main -> main (approval required)
error: failed to push some refs to 'http://gitproxy.local:8149/gitea.local:3000/admin/hello-gitproxy.git'

提交被网关完整接收并进入审批队列(分享链接末段即推送 _id),上游尚未收到该提交。随后用五.1 节的登录流程建立 reviewer 会话。

6.2 批准与落地

CT=$(curl -s -b reviewer.jar -c reviewer.jar http://gitproxy.local:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -w ' HTTP:%{http_code}\n' -b reviewer.jar -X POST http://gitproxy.local:8150/api/v1/push/e9bbecdd76b07e8e7759f33ee3d3efe3829adaf5d1a52d9886e85a58c7faf521/authorise \
  -H 'Content-Type: application/json' -H "X-CSRF-Token: $CT" \
  -d '{"params":{"attestation":[{"label":"I have checked every commit in this push","checked":true},{"label":"I am authorised to make this push","checked":true}]}}'
{"message":"authorised e9bbecdd76b07e8e7759f33ee3d3efe3829adaf5d1a52d9886e85a58c7faf521"} HTTP:200

复核详情:authorised: true,attestation 记录了两项确认、审批人 reviewer 与审批时间戳。批准后复推同一分支:

git push "http://admin:${GITEA_TOKEN}@gitproxy.local:8149/gitea.local:3000/admin/hello-gitproxy.git" main
remote: Checking approval status...
remote: . Processing 1 references
remote: Processed 1 references in total
To http://gitproxy.local:8149/gitea.local:3000/admin/hello-gitproxy.git
   757f8c7..e705760  main -> main

提交真正落到上游 Gitea(远端引用从 757f8c7 前进到 e705760)。

6.3 驳回(reject)

再推一个用于驳回的提交,然后 reviewer 带理由驳回:

echo "d1-reject-test $(date +%s)" > d1rej.txt
git add d1rej.txt && git commit -qm "docs: d1 reject loop test"
git push "http://admin:${GITEA_TOKEN}@gitproxy.local:8149/gitea.local:3000/admin/hello-gitproxy.git" main
# 新推送 _id:2d13fe11cb713cada5c8f3b2390b812a42437188f7420de6582eb4be79fdbec9

CT=$(curl -s -b reviewer.jar -c reviewer.jar http://gitproxy.local:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -w ' HTTP:%{http_code}\n' -b reviewer.jar -X POST http://gitproxy.local:8150/api/v1/push/2d13fe11cb713cada5c8f3b2390b812a42437188f7420de6582eb4be79fdbec9/reject \
  -H 'Content-Type: application/json' -H "X-CSRF-Token: $CT" \
  -d '{"reason":"变更未包含关联工单编号,请补充后再推送。"}'
{"message":"reject 2d13fe11cb713cada5c8f3b2390b812a42437188f7420de6582eb4be79fdbec9"} HTTP:200

复核详情(状态字段与驳回记录):

curl -s -b admin.jar http://gitproxy.local:8150/api/v1/push/2d13fe11cb713cada5c8f3b2390b812a42437188f7420de6582eb4be79fdbec9 \
  | python3 -c "import json,sys; d=json.load(sys.stdin); print('rejected:',d['rejected'],'| blocked:',d['blocked']); print(json.dumps(d['rejection'],ensure_ascii=False))"
rejected: True | blocked: True
{"reason": "变更未包含关联工单编号,请补充后再推送。", "timestamp": "2026-10-26T04:24:26.321Z", "reviewer": {"username": "reviewer", "email": "reviewer@example.com"}}

6.4 驳回后重推(仍被拦截)

带着被驳回的同一段历史再次推送,网关仍指向同一条审批记录、继续拦截:

remote: Checking approval status...
remote: . Processing 1 references
remote: Processed 1 references in total
remote: http://gitproxy.local:8150/dashboard/push/2d13fe11cb713cada5c8f3b2390b812a42437188f7420de6582eb4be79fdbec9
 ! [remote rejected] main -> main (approval required)
error: failed to push some refs to 'http://gitproxy.local:8149/gitea.local:3000/admin/hello-gitproxy.git'

结论:驳回不靠「重试」翻案,必须按驳回意见修改、形成新提交后再推(6.5 节的新提交即产生全新推送记录)。

6.5 取消(cancel,推送人本人)

推送人可以撤回自己的待审批推送。新推一个提交(产生 27243868d87ba1cb… 记录),然后 admin 本人取消:

CT=$(curl -s -b admin.jar -c admin.jar http://gitproxy.local:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -w ' HTTP:%{http_code}\n' -b admin.jar -X POST http://gitproxy.local:8150/api/v1/push/27243868d87ba1cbb1857d1e8441fbbe9f6555571e28e549dd4af93ee2719384/cancel \
  -H "X-CSRF-Token: $CT"
{"message":"cancel 27243868d87ba1cbb1857d1e8441fbbe9f6555571e28e549dd4af93ee2719384"} HTTP:200

复核:canceled: true。至此「待审批 → 批准落地 / 驳回拦截 / 自行取消」三种走向全部闭环。

七、推送人 CLI(@finos/git-proxy-cli)

7.1 定位与安装

@finos/git-proxy-cli v2.3.0 是推送人侧的命令行工具(npm install -g @finos/git-proxy-cli 后获得 git-proxy-cli 命令),把六节的 REST 调用封装成「登录 → 列表 → 审批」的固定动作,会话 Cookie 落在当前目录 git-proxy-cookie 文件里。

7.2 命令清单

git-proxy-cli --help
git-proxy-cli <command>

Commands:
  git-proxy-cli authorise    authorise a push
  git-proxy-cli cancel       cancel a push
  git-proxy-cli config       check config
  git-proxy-cli login        log in to GitProxy
  git-proxy-cli logout       log out of GitProxy
  git-proxy-cli ls           list pushes awaiting approval
  git-proxy-cli reject       reject a push
  git-proxy-cli reload-config  reload the GitProxy config
  git-proxy-cli createuser   create a user

Options:
  --help  Show help  [boolean]

7.3 网关地址

CLI 从两个环境变量拼装基地址:GIT_PROXY_UI_HOST(默认 http://localhost)与 GIT_PROXY_UI_PORT(默认 8080)。指向本环境:

export GIT_PROXY_UI_HOST=http://gitproxy.local GIT_PROXY_UI_PORT=8150

7.4 CSRF 拦截实测

csrfProtection: true 时,CLI 的 login 直接被 403 拒绝(CLI 未实现 CSRF Token 获取流程):

git-proxy-cli login --username admin --password q1w2e3r4
Error: Login 'admin': '403'

这与部署篇 FAQ Q6、配置参数详解篇 5.2 节的结论一致。要实际使用 CLI 审批,需要临时关闭 csrfProtection 并重启,且 CLI 的 authorise 固定提交内置问卷 label Authorising via GitProxy CLI——attestationConfig.questions 的第一项必须逐字改为该值(改法与恢复见配置参数详解篇 5.7 节)。

7.5 选型建议

交互式审批日常可用管理台网页完成;自动化与脚本统一走 REST API(5 节):CSRF、会话、问卷 label 全部可控,不依赖 CLI 的硬编码行为。CLI 适合给推送人提供最小化的命令行体验。

八、注意事项

  1. CSRF Token 一次性:每次写操作前重新 GET /api/auth/csrf-token,复用旧 Token 必得 401 CSRF token mismatch(5.1 节实测);
  2. 会话有效期:sessionMaxAgeHours: 12(本环境取值),过期后所有请求重新走登录流程;登出后 Cookie 立即失效(5.1 节 401 实测);
  3. 限流:管理台 API 60 秒 1000 次(rateLimit),超限返回 429 并携带 X-RateLimit-* 与 Retry-After 头(压测形态见配置参数详解篇 5.3 节);
  4. 401 与 403 的语义:缺 Token/Token 失配是 401(JSON 报文);带 Token 但会话未建立(如 CLI 直连 login)是 403;权限不足有专属报文(如推送人审批自己发起的推送返回 User admin not authorised to approve pushes on this project,5.5 节);
  5. Gitea Token 作用域:推送凭据建议 read:user + write:repository——网关要从 Token 反查用户身份(Resolving user from token...),只给 write:repository 可能卡在身份解析;Token 失效时推送表现为认证失败,重签后恢复;
  6. 生产自动化别用 Cookie 会话:给脚本长期用 POST /api/auth/login 维持会话不如启用 apiAuthentication(JWT)方案,键位见配置参数详解篇 5.14 节;
  7. 不要用本篇演示口令:q1w2e3r4 仅为系列演示值,生产环境按部署篇的安全清单替换。

九、常见问题(FAQ)

Q1:所有 POST 都报 CSRF token mismatch 或 CSRF token missing? 没带 X-CSRF-Token 头或复用了旧 Token。按 5.1 节模式「先取 Token → 立即随写请求提交」,每个写请求都取新的。

Q2:POST /api/auth/login 返回 403 还是 401? 403 且响应是 HTML 报错页:请求根本没带 X-CSRF-Token 头(lusca 中间件直接拒绝,5.1 节);401 且响应是纯文本 Unauthorized:CSRF 头正确但口令不对(实测),核对部署篇 4.7 节的账号初始化步骤。

Q3:GET /api/v1/config 返回 HTML 而不是配置 JSON? /api/v1/config 只有五个只读子端点(attestation/scmProviders/contactEmail/urlShortener/uiRouteAuth),访问其他路径会落到管理台页面兜底路由。完整配置以配置文件与 node dist/index.js -v -c 校验为准(4.3 节)。

Q4:CLI 全家桶都报 403? csrfProtection: true 拦的,见 7.4 节;临时关闭并改完问卷 label 才能走通 CLI 审批,用完记得恢复。

Q5:git push 报 Authentication failed? 推送凭据是「GitProxy 用户名 + Gitea Token」,检查 Token 是否失效、作用域是否含 read:user(三节/注意事项 5)。

Q6:批准后复推还是 approval required? 先确认调的是正确的推送 _id(分享链接末段),再用 GET /api/v1/push/<id> 看 authorised 字段。若仍是 false,检查审批人是否在仓库 canAuthorise 列表、attestation label 是否逐字一致。

Q7:审批人能看到别人的推送明细吗? 推送域读取端点要求登录会话;审批人通过管理台或 GET /api/v1/push/<id>(reviewer 会话)都能查看待审批明细,但只有 canAuthorise 名单内的人能执行 authorise/reject。

十、参考链接

  1. GitProxy 官方文档(API 与 CLI):https://finos.github.io/git-proxy/
  2. GitProxy 源码(服务路由与 CLI):https://github.com/finos/git-proxy
  3. GitProxy v2.3.0 Release:https://github.com/finos/git-proxy/releases/tag/v2.3.0
  4. 本系列姊妹篇:《GitProxy 2.3 介绍与选型指南》、《GitProxy 2.3 Docker Compose 部署指南》、《GitProxy 2.3 配置参数详解》