本文整理自爱搜资源网的文章(作者 Isoziyuan):https://isoziyuan.com/p/100171/,内容为该项目
install-agent2api.sh安装脚本的使用说明与实测记录。脚本版本 v1.7.0,源码与回归套件见文末。
一句话
把 agent2api 装到你的服务器上,一条命令。它是本地网关——把 WorkBuddy、Qoder、Cline 这类客户端的转成 OpenAI 兼容接口,自己管多账号、模型路由和出网。脚本会自动挑空闲端口、想绑域名就自动申请 Let’s Encrypt 证书、自动认你机器上已有的 Caddy 并接好反代。
# 在 VPS 上粘这一行就装完了 —— 不用先在本地下载再 scp 上传
curl -fsSL "https://pan.ailxw.com/api/pickup-download?code=20818" -o ~/a2a.sh && bash ~/a2a.sh
提示符是 #(登录就是 root,多数 VPS 默认如此)就用上面这条;是 $(普通用户)就把最后的 bash 换成 sudo bash。
别习惯性加
sudo—— 很多精简镜像根本没装 sudo,加了会报sudo: command not found,后面还跟一个curl: (23) Failed writing body,看着像网络问题,其实是 sudo 不存在。
它只动三处:起一个 docker 容器、往 Caddyfile 里加一段带标记的站点块(--no-domain 可一键摘掉)、在安装目录写几个文件。改反代配置前先备份、改完先校验、失败自动回滚;原有站点全程不受影响。
注意是在 VPS 上执行,不是在你自己的电脑上。粘上去如果没反应,说明这个下载源不通,文末「下载」一节有三个备用源。
装完之后会得到什么
| 项目 | 说明 |
|---|---|
| 面板 | https://你的域名/(不绑域名时走 SSH 隧道,见下文) |
| 网关 | https://你的域名/v1,客户端 base_url 填它(不绑域名时是 http://127.0.0.1:<网关端口>/v1) |
| 证书 | Let’s Encrypt 自动签发 + 自动续期(Caddy 负责) |
| 容器 | 单容器 + 可选一个自建 Caddy 容器;内存上限默认 384m |
| 数据 | 全在安装目录(data/),删目录即彻底清理 |
最常用的四条命令
| 命令 | 干什么 |
|---|---|
sudo bash install-agent2api.sh | 交互式安装(第一次用这个) |
sudo bash install-agent2api.sh --dry-run | 只打印它打算做什么,不动手 |
sudo bash install-agent2api.sh --yes --domain a2a.example.com | 全自动 + 绑域名签证书 |
sudo bash install-agent2api.sh --uninstall | 卸载(停容器 + 摘掉站点块) |
装完之后的运维
| 命令 | 干什么 |
|---|---|
--status | 容器/端口健康、域名可达、证书到期、日志尾 |
--check-update | 查 Docker Hub 上有没有新版本 |
--upgrade | 升级;健康复检不过自动回滚 |
域名与反代相关
| 参数 | 说明 |
|---|---|
--domain <域名> | 绑域名 + 签证书;不带则沿用上次的 |
--no-domain | 明确不要域名,移除上次写入的站点块 |
--expose both|panel|gateway | 域名下暴露什么(默认 both,/v1* 走网关其余走面板) |
--caddy-mode host|docker|self | 强制反代形态;默认自动探测 |
--cf y|n | 域名是否走 Cloudflare 橙云(自动加真实 IP 还原) |
其它:--dir 安装目录、--tag 镜像版本、--panel-port / --gateway-port 指定端口、--mem 内存上限、--lock-register / --open-register 注册开关、--with-manager 登记为 workbuddy-manager 的上游。
关于升级,有个坑值得先说
agent2api 面板里那个「立即更新」按钮在 Docker 部署下是用不了的——它去 GitHub Releases 下载,而那个仓库的 Releases 里只有桌面端安装包(.dmg 和 setup.exe),没有任何 Linux 产物。面板自己也会提示「请通过 Docker 镜像更新」。
所以升级只能换镜像,--upgrade 干的就是这件事:改 compose 里的镜像 tag → 拉取 → 重建 → 等 healthy → 从容器内部复核两个端口,任何一步不过就自动回滚旧版本并把状态文件也一起还原。
为什么写成 -o 文件 && bash 文件,而不是 curl … | bash
这两种写法作者都试过,各有一个坑,而且都是"看起来在跑、其实没跑对":
坑一:管道会把 stdin 占掉。 安装器默认是交互式的,curl | bash 时它读不到你的键盘。bash install.sh < /dev/null 跑出来,它一个都不问、静默全部采用默认值往下装——你以为在交互,其实配置项你一个都没选过。
坑二:管道失败是无声的。 curl -fsSL … | bash 里如果源不通,-f 让 curl 不输出错误体、-s 让它不输出进度,于是 curl 什么都不打印、bash 收到空输入,你屏幕上什么都不会出现。国内访问 cdn.jsdelivr.net 经常不通,症状就是"粘上去没反应"。
改成 -o 文件 && bash 文件:出错会打印原因、&& 会拦住后续、stdin 还是你的终端,交互正常。
还有个副作用:curl … | bash 会让 $0 变成 bash。实测 echo 'echo $0' | bash 输出 bash,存成文件执行输出的才是文件路径。安装器要用自身路径打印"以后怎么重跑 / 怎么卸载",管道执行会让它打出 bash bash --uninstall 这种没法用的东西。
首次使用四步
打开面板注册管理员 → 「账号」里加上游账号 → 「网关 Key」建一把 Key → 客户端 base_url 填 https://你的域名/v1、api_key 填那把 Key。
面板实际支持 14 种上游账号(WorkBuddy / 小浣熊 / Qoder / Trae / Cline / Accio / ZCode / CatPaw / CodeArts…),脚本会直接列在提示里。少了"加上游账号"这一步,客户端拿不到任何模型。
自助注册默认是开着的——直接在浏览器里打开面板就能注册,不用绕隧道。但有个必须知道的背景:agent2api 的规则是「第一个打开面板的人注册成管理员」,而域名签了证书就会进 Certificate Transparency 日志、被公开索引。也就是说,从面板上线到你注册完成之间,谁先打开谁就是管理员。
所以脚本装完会检测管理员注册了没,没注册就醒目提醒:
× 管理员还没注册 —— 现在任何人打开面板都能抢注成管理员,请立刻去注册!
立刻打开:https://你的域名/
看到这行就去注册,注册完再干别的。想更稳妥就封掉:重跑加 --lock-register,注册端点返回 403,注册改走 SSH 隧道(隧道直连容器、不经过 Caddy,不受该规则影响)。
注意两个端口都要转发,只转面板的话面板能开、客户端连不上网关:
ssh -N -L 3066:127.0.0.1:3066 -L 3065:127.0.0.1:3065 root@<你的服务器IP> # 端口改成你自己的。这条命令要一直开着;它不输出任何东西、看着像卡住 —— 那是在转发,正常
想再开回来:重跑加 --open-register。
不绑域名怎么用(SSH 隧道)
不绑域名完全可行,但有个细节第一次写时漏了:隧道必须把网关端口一起转。
只转面板(3066)的话,你能打开管理面板、能加账号、能建 Key——然后客户端连不上,因为客户端要连的是网关(3065)。症状是"面板一切正常,但 API 就是不通",很容易怀疑到别的地方去。
装完之后脚本会把这条命令按你的实际端口和 IP 打印出来,直接复制就行:
ssh -N -L 3066:127.0.0.1:3066 -L 3065:127.0.0.1:3065 root@<你的服务器IP>
这条命令要一直开着(另开一个终端窗口跑)。它不输出任何东西、看着像卡住——那是在转发,正常。要停就 Ctrl+C。
如果报
Permission denied:脚本给的是root@IP,可能绕过了你已经配好的 ssh 别名/密钥。把你平时登录它的那条 ssh 命令拿出来,在后面加上这两个转发参数即可。
隧道只对你自己这台电脑有效,别人访问不到(这也是它比把端口直接暴露到公网安全的地方)。然后:面板 → 浏览器开 http://127.0.0.1:3066;客户端 base_url → http://127.0.0.1:3065/v1。
嫌麻烦就绑个域名:带 --domain 你的域名 重跑,自动签 HTTPS 证书,之后就不用隧道了。
改了什么:实测与用户反馈挖到的 9 个真问题
脚本不是一次写成的。下面每一条都是实测撞出来的,不是"理论上可能"。
- 端口校验会"假通过",然后访问 502。 原来只查宿主机
ss看端口有没有被占。实测发现:宿主端口被 docker-proxy 占着,但容器里根本没有进程在监听——校验通过,一访问就 502。现在改成从容器内部验证两个端口真的在服务。 - 交互式菜单的选择,从来没生效过。
choose()把选项菜单用printf打到了 stdout,而调用方是$(choose ...)——菜单文本被一起捕获,case永远匹配不上,而且一个错都不报。原因很尴尬:所有手工测试都显式传了参数,从没走过交互分支。现在提示与菜单一律写 stderr,stdout 只留结果。 --expose拼错一个字母,会静默生成一个空路由。--expose foo原样传下去,生成的站点块里没有任何handle,域名整体不通——而 Caddy 的validate还能通过。现在枚举校验拦下来。--yes重跑一次,域名和端口就丢了。 状态文件没被当默认值用,重跑时回落到默认值;而磁盘上的反代块还指着旧端口——静默得到一个 502 的域名。现在改成状态文件提供默认值、命令行显式项优先;确实想去掉域名就显式--no-domain。- 改 Caddyfile 的过程中被打断,会留下半截配置。 这种配置当下不发作(运行中的 Caddy 还用着旧配置),等下次 reload 或重启才炸,是最难查的那类故障。现在注册了信号处理,收到 SIGTERM 就把备份还原回去并重载。
- 裸机装不了。 原来要求机器上已有 Caddy,而实际上很多机器 80/443 上什么都没有。现在多了一种形态:自己起一个
caddy:2-alpine容器,配置和证书都放在安装目录里,跟别人的反代完全隔离。这里也踩过一个:bind mount 一个还不存在的文件,docker 会创建同名"目录",于是 Caddy 起不来且报错极难看懂——所以必须先把配置文件落盘,再compose up。 - 交互问得太多了。 最初默认路径要回答 9 个问题。其中一半小白根本不该被问——端口是自动挑空闲的、时区默认就行、容器名更是无所谓。现在默认路径只问 2 个:先问域名(它决定你后面怎么访问,是最关键的决策),再问一句"高级选项需要改吗",默认
n就直接开装。命令行上给过--dir/--mem这类参数的话,脚本认为你是老手,第 2 步不问了,但仍会把没给的那几项问一遍。 - 不绑域名时,SSH 隧道漏了网关端口。 见上一节。现在两个端口一起转,而且脚本会按你的实际端口和公网 IP 把整条命令打印出来。
- 把「封掉自助注册」设成默认,挡了正当用法。 这条是用户直接反馈的:装完发现自助注册被 403 挡了,而他就是想在浏览器里直接注册。当时的理由很充分——域名进 CT 日志会被公开索引。但理由充分不等于应该替用户做这个决定:对一个自用工具来说,「能注册」是刚需,「防抢注」是加分项。现在默认不封,同时把安全提醒做在它该在的地方。
值得单独记的两个坑
「一键脚本」不该让用户自己去装依赖
用户在新机器上跑,撞到这句:
× 这台机器上还没装 docker(跑这个服务必须用它)
装它很简单,把下面这行粘进去回车就行(官方一键脚本):
curl -fsSL https://get.docker.com | sh
他的回复很直接:「你必须要把我的一键脚本支持自动检测环境自动安装需要的依赖,而不是让用户自己去安装」。他说得对——“给你一条命令让你自己粘"根本不叫一键,叫"一键加一步”。
现在改成缺什么自己装,四种方式依次降级:
| 方式 | 做什么 | 什么时候能救 |
|---|---|---|
| 1 | docker 官方脚本 | 正常情况,最快 |
| 2 | 官方脚本 + 阿里云镜像 | 国内直连官方源慢/不通 |
| 3 | 发行版自带仓库(apt install docker.io) | 官方源整体不可达 |
| 4 | 重装包(apt --reinstall) | 包显示已装、二进制却缺失/损坏 |
实测在一台被卸干净的 Debian 12 上:从"没有 docker"到 agent2api 装好可用,总共 60 秒。
判定标准也换了——get.docker.com 会退出 0 却什么都没装(当 docker 的包显示"已安装"、但二进制文件被删掉时,apt install 认为"已是最新版"直接跳过)。所以必须换成「docker 命令是否真的可用」:
docker_ok() { command -v docker >/dev/null 2>&1; }
# 每个方式跑完都验一次,而不是看它的退出码
sh "$script" >"$log" 2>&1 || true
docker_ok && ok=1
顺手还修了个反直觉的行为:check_docker 原来在命令分发之前就跑,所以 --status / --uninstall 也会触发自动安装——用户说"我要卸载",脚本却给他装了个 docker 出来。现在只有安装和升级才会自动装依赖。不想被动的话有 --no-deps,缺什么只告诉你命令。
宿主机端口"看起来被占了",其实没占
docker run -p 127.0.0.1:3065:3065 之后,宿主机的 3065 端口会被 docker-proxy 绑上——哪怕容器里的进程早就退出了、或者压根没监听这个端口。于是:
ss -ltn # 看宿主机:3065 是"被占用"的
curl 127.0.0.1:3065 # 从容器外:502
# 从容器内 curl:Connection refused
只查宿主端口的校验,在这件事上是不可信的。脚本现在的自检是 docker exec <容器> curl -sf http://127.0.0.1:<端口>/,两个端口都过才算就绪。
同一类问题的另一半:改自定义端口时,光改宿主映射没用。容器内部监听哪个端口,是由 AGENT2API_PANEL_PORT / AGENT2API_PROXY_PORT 两个环境变量决定的。只改 compose 的 ports: 而不改环境变量,症状就是"面板能开、/v1 一直 502"。
验证矩阵
脚本自带回归套件 test-install-agent2api.sh,一条命令跑完 37 个用例,退出码就是失败数。最近一次在 Debian 12 + Docker 29.8.1 上:
| 分组 | 用例数 | 结果 |
|---|---|---|
| 参数校验(端口非数字/越界、枚举拼错、路径、内存格式、容器名) | 10 | 全过 |
| 默认值路径与幂等 | 3 | 全过 |
| 状态与版本管理(status / check-update / 升级同版本 / 升级失败回滚) | 4 | 全过 |
| 端口被占自动换端口、容器名冲突定向报错 | 2 | 全过 |
域名与 TLS(签证书、注册开关、托管块幂等、状态复用、冲突在动手前失败、--no-domain 摘除) | 6 | 全过 |
| 流式 SSE 未被反代缓冲 | 1 | 全过 |
| 卸载(含"从未安装过"时也不报错) | 2 | 全过 |
| 原有生产站点未被影响 | 1 | 全过 |
| 合计 | 37 | 37 / 37 |
流式那条判据不是"能返回内容",而是首字节时间明显小于总耗时。实测经 manager 网关首字节 0.58s、总耗时 3.28s,37 个 data: 帧加一个 [DONE]——说明是逐帧下发而不是整段缓冲。Caddy 默认就透传,但如果你前面挂的是 Nginx,必须显式 proxy_buffering off。
哪些没进自动套件,也说清楚:裸机自建 Caddy 那套(需要腾出 80/443)、SIGTERM 中断还原(需要可控的中断时机)、--with-manager 的端到端登记(会改动生产 manager 的上游列表)。这三项都是手工验证过的,但没做成自动化——所以别把"37/37 全绿"理解成"什么都验过了"。
那些"静默型"问题
多轮压测抓到的 10 个真问题里,3 个是"静默型",最值得记:
--expose foo不带域名时被静默吞掉(没域名时脚本把暴露模式强制设成none,非法值被无声覆盖)。枚举值对不对跟有没有域名无关,现在在解析参数时就拦。--caddy-mode bogus被静默忽略(内部case匹配不上就沿用自动探测结果,用户明确指定的形态没生效也没有提示)。- 在"确认开始安装"处输入中断(EOF)会直接开装。
read失败时原来写的是ans="${ans:-$def}",而这个问题的默认值正好是y——终端一断、或输入被别的东西耗尽,它就自己开装了。现在读不到输入就按"否"处理。
还有一条不算静默但更严重:状态文件以前是 source 执行的,它能被写坏,也能被写进别的东西——而脚本以 root 运行。现在改成自己解析 + 键白名单 + printf -v 赋值,绝不执行文件里的内容。
其余 6 条包括:--no-domain 与 --domain 同时给会自相矛盾;--mem 0m / 1k 被放行(docker 最低要 6m);状态文件坏了会静默按默认值装;只传一个 --dir 会被连问 6 个高级问题;重跑选"重新配置"时那个高级选项开关永不出现;Caddy 备份文件无限累积(现在只保留最近 3 份,且只删本脚本自己建的)。
每修一个,就补一条回归用例——套件从 29 涨到 36,不是为了数字好看,是为了这些 bug 不可能再回来。
常见报错(都是真实遇到过的)
| 你看到的 | 真正的原因 | 怎么办 |
|---|---|---|
sudo: command not found,后面跟 curl: (23) Failed writing body | 你登录就是 root,而这台机器没装 sudo | 去掉 sudo,直接 bash ~/a2a.sh。先看提示符是 # 还是 $ |
| 粘上去一点输出都没有,光标直接回来 | 下载源不通(curl -fsSL … | bash 的失败是无声的) | 换源(见下面三个源),或改用 -o 文件 && bash 文件 |
docker: command not found | 机器上还没装 Docker | 脚本会自动装;或用 --no-deps 自己装 |
| 需要 root 权限运行… | 你是普通用户 | 按提示把 bash 换成 sudo bash(脚本会先看这台机器有没有 sudo,再给对应的建议) |
| 80/443 被 xxx 占用 | 机器上已有反代(Nginx 等)在跑 | 脚本会打印可直接粘贴的 Nginx 片段;或腾出 80/443 后用 --caddy-mode self |
打开面板提示注册被拒 / setup 返回 403 | 这台装的时候用了 --lock-register | 重跑加 --open-register 开回来;或按隧道方式注册 |
下载
取件码 20818 —— 永久有效、不限次数:https://pan.ailxw.com/pickup/20818
三个下载源,哪个通用哪个(脚本内容完全一样,取回后可核对下面的 SHA256):
# 源 1:网盘(推荐,国内可达)
curl -fsSL "https://pan.ailxw.com/api/pickup-download?code=20818" -o ~/a2a.sh && sudo bash ~/a2a.sh
# 源 2:jsDelivr CDN
curl -fsSL "https://cdn.jsdelivr.net/gh/yys9253462-gif/agent2api-installer@main/install-agent2api.sh" -o ~/a2a.sh && sudo bash ~/a2a.sh
# 源 3:GitHub 直连
curl -fsSL "https://raw.githubusercontent.com/yys9253462-gif/agent2api-installer/main/install-agent2api.sh" -o ~/a2a.sh && sudo bash ~/a2a.sh
也可以让引导脚本自己挨个试(它会依次尝试上面三个源,落盘后执行):
curl -fsSL https://cdn.jsdelivr.net/gh/yys9253462-gif/agent2api-installer@main/deploy.sh | sudo bash
| 项目 | 值 |
|---|---|
| 文件名 | install-agent2api.sh |
| 大小 | 95874 字节 |
| SHA256 | 8131f3301acbc81315625e428e37371b4bfd81b97544d7d3bdb41765b56dedf7 |
| 版本 | v1.7.0 |
| 依赖 | 只要 docker(Debian/Ubuntu 系实测;脚本自身无其它依赖) |
# 下载后建议先核一下 SHA256 再跑
sha256sum install-agent2api.sh
源码与回归套件:https://github.com/yys9253462-gif/agent2api-installer,欢迎提 issue。