Files
host-git-cred-proxy/IMPLEMENTATION_PLAN.md

18 KiB
Raw Permalink Blame History

host-git-cred-proxy 下一阶段实施文档

1. 目标

这一轮改造的目标不是继续优化“仓库里的几个脚本”,而是把项目升级成一个安装后可直接使用的本地产品:

  • 宿主机安装后可直接启动,不要求先 git clone 本仓库才能使用
  • 宿主机提供本地 Web 面板,用来查看状态、配置代理、查看日志和接入说明
  • 代理接口和 Web 面板共用一个端口,但必须做严格的路由和权限隔离
  • 容器侧不再依赖本仓库目录,也不再依赖 node / bun
  • 首发目标平台为 macOS重点兼容 Docker Desktop / OrbStack

2. 已拍板的技术决策

以下内容作为实现基线,不再在实现阶段反复讨论:

  • 宿主机服务端:Bun + Elysia + TypeScript
  • Web UIReact + Vite + TypeScript
  • 端口策略:单端口,默认 127.0.0.1:18765
  • 进程形态:一个宿主机服务,同时提供 UI、管理 API、代理 API、容器 helper 下载入口
  • 发布形态:Bun 编译的 macOS 二进制 + GitHub Releases + Homebrew tap
  • 容器 helperPOSIX sh + curl,不依赖 node / bun
  • 开发方式:按全新产品开发,不保留旧脚本兼容层;现有实现仅作为行为参考

3. 当前实现的主要问题

当前版本能工作,但它的使用方式仍然是“源码仓库型工具”,主要问题如下:

  • host/start.sh 把运行状态写到仓库里的 host/state/
  • container/configure-git.sh 把 Git helper 直接指向仓库内脚本路径
  • container/helper.mjs 默认从仓库相对路径读取 token
  • 容器 helper 仍然依赖 nodebun
  • 用户没有宿主机 UI只能通过脚本和日志排查问题

这导致当前体验更像“开发者自己维护脚本”,而不是“安装后直接用的本地工具”。

4. 目标产品形态

目标使用流程如下:

宿主机

brew install host-git-cred-proxy
host-git-cred-proxy start
host-git-cred-proxy open

用户打开面板后可以:

  • 查看当前运行状态、监听地址、容器接入地址
  • 修改协议白名单、host 白名单、端口等配置
  • 轮换 proxy token
  • 查看最近请求和运行日志
  • 直接复制 docker-compose / devcontainer 接入片段

容器

curl -fsSL http://host.docker.internal:18765/container/install.sh | sh
git-credential-hostproxy configure --global
git clone https://...

注:最终 helper 是否保留 configure 子命令,还是继续提供独立 configure-git.sh,实现阶段可选其一;但容器侧必须做到“无 Bun/Node 依赖”。

5. MVP 范围

这一轮只做最关键的可用能力:

  • 可启动、可停止、可查看状态的宿主机二进制 CLI
  • 基于同一端口的 Web 面板
  • 完整的 Git credential 代理能力
  • 容器侧 shell helper 与安装脚本
  • 从源码仓库状态目录迁移到稳定的用户态目录
  • GitHub Releases 二进制产物
  • Homebrew tap 安装方式

以下能力明确不属于 MVP

  • Windows 支持
  • Linux 作为一等发布目标
  • 远程管理面板
  • 多用户 / 多租户隔离
  • 自动开机启动(可作为后续增强)
  • UI 实时推送MVP 先用轮询,不先上 SSE / WebSocket

6. 总体架构

6.1 单端口策略

单端口可以做,而且这一轮就按单端口实现。

默认监听:

  • host = 127.0.0.1
  • port = 18765

同一个 Elysia 服务负责四类内容:

  1. 代理 API
  2. 管理 API
  3. Web UI 静态资源
  4. 容器 helper 下载入口

但“同端口”不等于“同权限”。必须按路由分层。

6.2 路由分层

A. 代理 API容器可访问

  • GET /healthz
  • POST /fill
  • POST /approve
  • POST /reject

规则:

  • 所有 POST 代理路由都必须校验 Authorization: Bearer <proxy-token>
  • 保留当前请求体格式,继续直接对接 git credential fill/approve/reject
  • 保留当前语义:缺失凭证时,fill 返回 200 空 body而不是直接报错

B. 容器 helper 下载入口(容器可访问)

  • GET /container/install.sh
  • GET /container/configure-git.sh
  • GET /container/git-credential-hostproxy

这些路由用于让容器直接从宿主机服务下载 helper不要求预先挂载本仓库也不要求容器能访问 GitHub Releases。

C. 本地 UI仅 loopback 可访问)

  • GET /
  • GET /assets/*

D. 本地管理 API仅 loopback 可访问)

  • GET /api/admin/bootstrap
  • GET /api/admin/status
  • GET /api/admin/config
  • POST /api/admin/config
  • POST /api/admin/restart
  • POST /api/admin/token/rotate
  • GET /api/admin/requests
  • GET /api/admin/logs

6.3 本地路由访问规则

UI 和管理 API 必须同时满足以下条件:

  • 请求来源是 loopback127.0.0.1::1,以及规范化后的 loopback 地址)
  • 非 GET 的管理 API 必须校验 Origin
  • 非 GET 的管理 API 必须带 X-Admin-Nonce

其中:

  • proxy-token 专门给容器代理 API 用
  • admin-nonce 专门给浏览器管理操作用
  • 两者绝不能复用

admin-nonce 可以在服务启动时生成,仅保存在内存中;前端通过 GET /api/admin/bootstrap 获取。

7. 进程模型与 CLI 设计

最终 CLI 名称:

host-git-cred-proxy

建议支持以下命令:

  • host-git-cred-proxy start:后台启动服务,打印 panel URL 和 state dir
  • host-git-cred-proxy serve:前台运行服务,供开发和内部启动使用
  • host-git-cred-proxy stop:停止后台服务
  • host-git-cred-proxy status显示运行状态、pid、URL、健康检查结果
  • host-git-cred-proxy open:在默认浏览器打开面板
  • host-git-cred-proxy rotate-token:轮换 proxy token

实现要求:

  • start 通过后台拉起 serve
  • 需要 server.pid
  • 需要基础健康检查,避免启动假成功
  • 保留当前 start/stop/status 的使用心智

8. 状态目录设计

必须把状态目录从仓库移走。

默认状态目录:

  • macOS~/Library/Application Support/host-git-cred-proxy
  • 其他系统的开发环境回退:~/.local/state/host-git-cred-proxy

允许覆盖:

  • 环境变量:GIT_CRED_PROXY_STATE_DIR

状态目录内文件:

  • config.json
  • token
  • server.pid
  • server.log
  • requests.ndjson
  • runtime.json

文件职责:

  • config.json:持久配置
  • token:代理 API 的 bearer token
  • server.pid:后台进程 pid
  • server.log:服务运行日志
  • requests.ndjson:脱敏后的请求历史
  • runtime.json:当前运行实例的时间、版本、面板地址等运行态信息

9. 配置模型

config.json 建议采用如下结构:

{
  "host": "127.0.0.1",
  "port": 18765,
  "publicUrl": "http://host.docker.internal:18765",
  "protocols": ["https"],
  "allowedHosts": [],
  "requestHistoryLimit": 200,
  "openBrowserOnStart": false
}

规则:

  • host 默认仍为 127.0.0.1
  • publicUrl 允许手动覆盖,不从 host 自动推导
  • protocols 默认只允许 https
  • allowedHosts 为空表示不限制 host
  • requestHistoryLimit 控制 UI 展示和清理上限,不要求无限累积

如需支持环境变量覆盖,建议使用以下命名:

  • GIT_CRED_PROXY_HOST
  • GIT_CRED_PROXY_PORT
  • GIT_CRED_PROXY_PUBLIC_URL
  • GIT_CRED_PROXY_PROTOCOLS
  • GIT_CRED_PROXY_ALLOWED_HOSTS
  • GIT_CRED_PROXY_STATE_DIR

优先级建议:

  1. CLI flag如果后续加入
  2. 环境变量
  3. config.json
  4. 内置默认值

10. 代理核心实现要求

代理核心继续复用当前项目已经验证过的 Git credential 行为,但要迁移到新的 Bun/Elysia 服务内。

必须保留的行为:

  • 请求体仍然是 Git credential 的 key=value 文本格式
  • body 限制保留 64 KiB
  • fill -> git credential fill
  • approve -> git credential approve
  • reject -> git credential reject
  • GIT_TERMINAL_PROMPT=0
  • 对缺失凭证的 fill 做空返回兼容,而不是把 stderr 原样返回给容器

新增要求:

  • 每次代理请求都记录脱敏后的审计事件
  • 审计事件不得包含 usernamepasswordoauth tokenAuthorization header
  • 记录项只保留最小必要信息时间、action、protocol、host、path、结果、状态码、耗时

推荐的事件结构:

{
  "time": "2026-03-09T10:00:00.000Z",
  "action": "fill",
  "protocol": "https",
  "host": "github.com",
  "path": "owner/repo.git",
  "statusCode": 200,
  "outcome": "ok",
  "durationMs": 12
}

outcome 约定值建议包括:

  • ok
  • empty
  • denied
  • bad_request
  • error

11. Web 面板设计

Web 面板使用 React + Vite,构建产物由宿主机服务直接托管。

不做 SSR不额外引入第二个 Web 服务。

MVP 页面建议分为五个区块:

11.1 Overview

展示:

  • 当前服务状态
  • 监听 URL
  • 容器访问 URL
  • 当前协议白名单
  • 当前 host 白名单
  • token 文件路径
  • state dir 路径
  • 最近一次启动时间

11.2 Setup

展示并可复制:

  • curl .../container/install.sh | sh
  • 全局配置 Git helper 的命令
  • docker-compose 片段
  • devcontainer 片段
  • token 文件挂载说明

要求:

  • Setup 页面必须根据当前 publicUrl 和当前 state dir 动态生成示例
  • 不要把 token 明文直接显示在页面上
  • 允许显示 token 文件路径

11.3 Requests

展示最近请求表格:

  • time
  • action
  • protocol
  • host
  • path
  • outcome
  • duration

11.4 Logs

展示 server.log 的最近内容。

MVP 直接轮询 GET /api/admin/logs 即可,不先做实时流。

11.5 Settings

允许:

  • 修改 host / port / publicUrl
  • 修改协议白名单
  • 修改 host 白名单
  • 保存配置
  • 重启服务
  • 轮换 token

配置保存后的行为建议:

  • 先写入 config.json
  • 再触发服务重启
  • 页面显示“正在重启 / 已重启”的明确反馈

12. 容器 helper 设计

12.1 基本原则

容器 helper 必须改成不依赖 node / bun 的实现。

目标是:

  • sh
  • curl
  • 就能工作

12.2 helper 文件

保留文件名:

  • git-credential-hostproxy

建议行为:

  • 读取 Git 传入的 stdin
  • 根据命令参数识别 get / store / erase
  • 映射到 /fill / /approve / /reject
  • 通过 curl 发到宿主机服务

环境变量:

  • GIT_CRED_PROXY_URL:默认 http://host.docker.internal:18765
  • GIT_CRED_PROXY_TOKEN:可选,直接传 token
  • GIT_CRED_PROXY_TOKEN_FILE:默认 /run/host-git-cred-proxy/token

优先级:

  1. GIT_CRED_PROXY_TOKEN
  2. GIT_CRED_PROXY_TOKEN_FILE

12.3 安装脚本

新增:

  • container/install.sh

功能:

  • git-credential-hostproxy 安装到 /usr/local/bin
  • 如有需要,同时安装 configure-git.sh
  • 尽量不做复杂依赖检查,只校验 sh / curl / 写权限

12.4 Git 配置方式

MVP 推荐继续保留 configure-git.sh,但它不再依赖仓库路径。

预期行为:

  • 优先使用 PATH 中的 git-credential-hostproxy
  • 支持 --global
  • 支持 --local
  • 支持 --repo PATH

换句话说,配置脚本可以保留,但必须从“仓库路径绑定模式”改成“已安装命令模式”。

13. Web 服务向容器分发 helper

为实现“直接用”,宿主机服务必须暴露 helper 下载入口。

这是本轮设计里很关键的一点,因为它能把容器接入改成:

curl -fsSL http://host.docker.internal:18765/container/install.sh | sh

而不再要求:

  • 挂载整个源码仓库
  • 预装 node
  • 预装 bun
  • 容器必须能访问 GitHub

要求:

  • install.shgit-credential-hostproxy 作为静态内容由宿主机服务返回
  • 返回内容允许根据当前端口和 URL 进行简单模板替换
  • 这些下载路由不需要 admin 权限,也不需要 proxy token

14. 发布与分发策略

14.1 主发布渠道

主发布渠道确定为:

  • GitHub Releases
  • Homebrew tap

MVP 不把 npm 包作为主发布渠道。

14.2 二进制产物

发布以下两个目标:

  • darwin-arm64
  • darwin-x64

建议产物名:

  • host-git-cred-proxy-darwin-arm64.tar.gz
  • host-git-cred-proxy-darwin-x64.tar.gz

每个 tarball 中至少包含:

  • bin/host-git-cred-proxy
  • share/host-git-cred-proxy/ui/*
  • share/host-git-cred-proxy/container/install.sh
  • share/host-git-cred-proxy/container/configure-git.sh
  • share/host-git-cred-proxy/container/git-credential-hostproxy

这里明确采用“二进制 + 静态资源目录”的打包方式,而不是强行追求所有内容都塞进单一可执行文件。

这样做的理由:

  • 避免 Bun 编译时静态资源嵌入带来的额外不确定性
  • 方便 Homebrew 安装 share/ 目录资源
  • 方便本地开发与打包产物共用同一套 UI 资源读取逻辑

14.3 Homebrew

Homebrew 作为默认安装入口。

期望用户体验:

brew install <tap>/host-git-cred-proxy
host-git-cred-proxy start

Formula 负责:

  • 安装二进制到 bin/
  • 安装 UI 和 helper 资源到 share/host-git-cred-proxy/

14.4 npm 的定位

可以保留 package.json 作为源码开发入口,但 MVP 不做“主打 npm 安装”。

如果后续要发 npm定位也应是

  • 源码开发入口
  • CI / 本地开发辅助
  • 非主支持平台的备用渠道

而不是主要用户安装方式。

15. 建议的仓库结构

建议重构为如下结构:

host-git-cred-proxy/
├── host/
│   ├── src/
│   │   ├── cli.ts
│   │   ├── server.ts
│   │   ├── routes/
│   │   │   ├── admin.ts
│   │   │   ├── proxy.ts
│   │   │   └── container.ts
│   │   ├── services/
│   │   │   ├── config.ts
│   │   │   ├── git-credential.ts
│   │   │   ├── process-manager.ts
│   │   │   ├── request-log.ts
│   │   │   ├── state-dir.ts
│   │   │   ├── token.ts
│   │   │   └── ui-assets.ts
│   │   └── utils/
│   │       ├── loopback.ts
│   │       └── sanitize.ts
│   ├── ui/
│   │   ├── index.html
│   │   └── src/
│   │       ├── main.tsx
│   │       ├── App.tsx
│   │       ├── api.ts
│   │       ├── pages/
│   │       └── components/
├── container/
│   ├── install.sh
│   ├── configure-git.sh
│   └── git-credential-hostproxy
├── examples/
├── package.json
└── IMPLEMENTATION_PLAN.md

说明:

  • 现有 host/*.shhost/server.mjscontainer/helper.mjs 仅作行为参考,不作为新产品结构约束
  • 新实现可以直接删除旧脚本链路,不需要包袱式迁移

16. 实施阶段

阶段 1宿主机服务基础迁移

目标:先把代理核心迁到 Bun/Elysia并建立新的状态目录与 CLI。

任务:

  • 建立 Bun + TypeScript 运行结构
  • 实现 cli.tsstart/serve/stop/status/open
  • 实现状态目录解析与 config.json 读写
  • 把当前代理逻辑迁到新的 /fill/approve/reject
  • 增加请求脱敏日志

完成标准:

  • 不启动 UI 也能通过 CLI 运行代理
  • 容器可按规定协议访问 /fill/approve/reject
  • 状态不再写入仓库目录

阶段 2Web 面板

目标:补齐本地可视化管理能力。

任务:

  • 建立 React + Vite 前端工程
  • 实现 Overview / Setup / Requests / Logs / Settings 页面
  • 实现 /api/admin/* 路由
  • 实现 loopback 校验、Origin 校验、X-Admin-Nonce
  • 完成静态资源托管

完成标准:

  • 本地浏览器可以打开面板
  • 非 loopback 客户端访问 UI / admin API 会被拒绝
  • 可以在页面里修改配置并重启服务

阶段 3容器 helper 去运行时依赖

目标让容器接入不再依赖源码仓库、Node、Bun。

任务:

  • 用 shell 重写 git-credential-hostproxy
  • 新增 container/install.sh
  • 重写 container/configure-git.sh 为已安装命令模式
  • 在宿主机服务内暴露 /container/* 下载路由
  • 更新 examples/ 为 token 文件挂载模式

完成标准:

  • 容器内只有 sh + curl + git 也能接入
  • 不挂载源码仓库也能完成配置

阶段 4打包与发布

目标:形成真正可安装的产品分发链路。

任务:

  • 构建 UI 产物
  • 编译 macOS arm64 / x64 二进制
  • 组装 tarball
  • 编写 Homebrew formula / tap
  • 做最小 smoke test

完成标准:

  • 从 GitHub Release 下载 tarball 后可直接运行
  • brew install 后可直接 host-git-cred-proxy start

17. 验收标准

以下条件全部满足,才算这一轮完成:

  • 用户不需要先 git clone 本仓库,也能在宿主机安装和启动服务
  • 用户可以在本地浏览器打开 Web 面板
  • UI、管理 API、代理 API 共用一个端口
  • 容器 helper 不再依赖 node / bun
  • 容器不挂载源码仓库,也能安装 helper 并使用代理
  • 代理仍然支持协议白名单和 host 白名单
  • token 和请求日志不泄露敏感凭证内容
  • 提供 darwin-arm64darwin-x64 二进制发布产物
  • 提供 Homebrew 安装路径

18. 测试建议

实现阶段至少覆盖以下测试:

  • 配置读写测试
  • loopback 判断测试
  • admin nonce / origin 校验测试
  • 代理请求 body 解析与脱敏测试
  • git credential fill 缺失凭证时的兼容行为测试
  • shell helper 的 basic smoke test
  • 编译后二进制的启动与 /healthz smoke test

19. 额外约束

实现时请遵守以下约束:

  • 不要把 proxy token 明文显示在 UI 上
  • 不要把凭证请求体完整落盘
  • 不要让管理 API 复用 proxy token
  • 不要让 UI 依赖额外 dev server 才能运行产品版
  • 不要把容器 helper 再次实现成 Node/Bun 脚本

20. 一句话结论

这一轮不是“小修小补”,而是一次明确的产品化改造:

  • 宿主机:Bun + Elysia + React,单端口,本地面板
  • 容器:sh + curl helper零运行时依赖
  • 发布:macOS 二进制 + GitHub Releases + Homebrew

后续 agent 按这个文档分阶段推进即可,不需要再回到“是否继续绑定源码仓库路径”这类问题上反复摇摆。