-
Notifications
You must be signed in to change notification settings - Fork 0
ZH Linux systemd
推荐使用 systemd --user。它在登录时启动,进程退出时自动拉起,并且以你自己的身份运行
—— 因此使用的是你的 ~/.copilot-relay 令牌缓存,而不是另一份。
npm install -g copilot-relay@latest
copilot-relay auth
command -v copilot-relaymkdir -p ~/.config/systemd/user
RELAY_BIN="$(command -v copilot-relay)"
cat > ~/.config/systemd/user/copilot-relay.service <<EOF
[Unit]
Description=copilot-relay for Claude Code
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=simple
ExecStart=${RELAY_BIN} start
WorkingDirectory=%h
Restart=on-failure
RestartSec=30
[Install]
WantedBy=default.target
EOFcopilot-relay start 在前台运行并处理 SIGTERM,所以 Type=simple 是正确的。
不要加 & 或 nohup —— systemd 会把立刻退出当成失败,从而无限重启。
这里有三项设置是关键,而大多数教程使用的默认值对这个服务来说都是错的。
Restart=on-failure 只在非零退出时重启。用 Restart=always 的话,systemd 在正常
关闭后也会重启 —— 于是 copilot-relay stop 看起来毫无作用,因为 systemd 立刻又把它
拉了起来。参见停止。
RestartSec=30 限制重试频率。relay 启动时会校验上游 Copilot 访问,失败则 exit(1)。
开机时网络通常还没就绪,所以第一次尝试失败是正常的。RestartSec=5 意味着对着一个还不
存在的网络每分钟重试十二次。
StartLimitIntervalSec=300 + StartLimitBurst=5 直接终止这种循环:5 分钟内失败超过
5 次,unit 进入 failed,systemd 不再重试。这把一个看不见的循环变成了一个能被发现、能
被诊断的状态。
After=network-online.target 只保证启动顺序,不保证网络真的可达,所以重试策略仍然有用。
systemctl --user daemon-reload
systemctl --user enable --now copilot-relay.service修改 unit 文件后要先 daemon-reload 再 restart,否则 systemd 用的还是旧定义。
--user 服务默认在登录时启动、注销时停止。要让 relay 在无人登录的机器上持续运行,或
跨注销存活:
sudo loginctl enable-linger "$USER"copilot-relay statuscopilot-relay 0.2.5
process running (pid 93744, up 1h 16m)
version 0.2.5
listening http://127.0.0.1:4142
health ok (9ms)
models gpt-5.6-sol[1m], claude-opus-5
upstream not checked (use --deep)
log ~/.copilot-relay/logs/copilot-relay.2026-07-25.log
config ~/.copilot-relay/config.yaml
host 127.0.0.1
port 4142
…
gptModel gpt-5.6-sol
opusModel claude-opus-5
host, port and claudeSetup take effect on restart; the rest hot-reload.
上面的 config 块是节选;status 会打印全部 11 个解析后的配置项。参见
配置说明。
version 这一行是正在运行的守护进程自己报告的版本,和第一行不是一回事 —— 第一行
是你刚刚调用的那个 CLI。执行 npm i -g copilot-relay@latest 之后,在服务真正重启之前,
两者会不一致:
version 0.2.6 — MISMATCH, 0.3.0 is installed
在 systemd 下,这通常意味着 unit 从未被重新加载。执行 systemctl --user restart copilot-relay 后再查一次。版本不一致不会改变退出码 —— relay 仍然可用,只是它不是你
刚装上的那个版本。
加上 --deep 会额外发一个真实请求经由 Copilot 走一遍 —— 这是唯一能证明 relay 真的可以
为 Claude Code 服务的检查。它会消耗少量 token,所以默认不做:
copilot-relay status --deep upstream ok (1191ms) — end-to-end Copilot round trip
退出码便于脚本使用:0 运行中且可达,1 未运行,2 运行中但不可用(健康检查失败,或 --deep 失败)。--json 输出
机器可读格式。因此它可以直接写进 unit 文件:
ExecStartPost=/usr/bin/env copilot-relay statusstatus 已经替你跑了这三层。理解它们仍然有价值,因为前两层在一个根本无法处理任何请求
的 relay 上也会通过 —— 在没有装 CLI 的机器上排查时也用得上。
systemctl --user status copilot-relay.service
curl -s http://127.0.0.1:4142/healthz期望 active (running) 和 {"ok":true}。这个接口是静态处理器:它只能证明有个端口在
监听,别的什么都证明不了,完全不会访问 GitHub Copilot。
curl -s http://127.0.0.1:4142/v1/models期望看到你配置的模型,例如 gpt-5.6-sol[1m] 和 claude-opus-5。结果直接来自配置,
同样不访问上游。一个 Copilot 令牌一小时前就过期的 relay,前两层照样通过。
curl -s -X POST http://127.0.0.1:4142/v1/messages \
-H "content-type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{"model":"gpt-5.6-sol","max_tokens":16,
"messages":[{"role":"user","content":"Reply with the single word: ok"}]}'返回 200,带 content 和非零 usage,说明整条链路都是通的:配置、令牌刷新、Copilot
调用,以及转换回 Claude 格式。只有这一层能证明 relay 真的可以为 Claude Code 服务。
它会消耗少量 token。
如果前两层通过而第三层失败,问题在鉴权或上游,不在 unit 文件 —— 执行
copilot-relay auth 并查看当天日志。
systemd 把标准输出收进 journal,relay 自己也会写轮转日志文件。启动失败看 journal,请求 历史看日志文件。
journalctl --user -u copilot-relay.service -n 100 --no-pager
journalctl --user -u copilot-relay.service -f
tail -f ~/.copilot-relay/logs/copilot-relay.$(date +%F).log日志按天轮转,文件名带本地日期。要搜索所有保留的日期,用通配符:
grep -n "Startup preflight failed" ~/.copilot-relay/logs/copilot-relay.*.log现在停止,下次登录时再启动:
systemctl --user stop copilot-relay.service现在停止,并且以后登录也不再启动:
systemctl --user disable --now copilot-relay.service重启:
systemctl --user restart copilot-relay.servicecopilot-relay stop 会直接找到并终止 relay 进程。它是正常退出,所以在
Restart=on-failure 下 systemd 不会再拉起 —— 命令行和守护进程的行为是一致的。
如果用 Restart=always,systemd 会在 RestartSec 之后重启它,看起来就像命令行没生效。
出现这种情况说明你的 unit 还是旧的。
不论用哪种守护方式,确认 relay 确实没了:
copilot-relay stop
ss -ltnp 'sport = :4142' # 应该没有 LISTEN 行systemctl --user disable --now copilot-relay.service
rm ~/.config/systemd/user/copilot-relay.service
systemctl --user daemon-reloadfailed 且提示 start-limit-hit。 5 分钟内失败超过 5 次,systemd 主动放弃了 ——
这是设计如此。先修掉根因,再清除这个状态:
systemctl --user reset-failed copilot-relay.service
systemctl --user start copilot-relay.service在你手动执行之前,它不会自己重试。
反复出现 status=1。 启动前校验在失败:鉴权过期或没有网络。看 journalctl 和当天
日志,然后执行 copilot-relay auth。
注销后服务就没了。 需要开启 linger:sudo loginctl enable-linger "$USER"。
unit 里 command -v copilot-relay 找不到。 --user unit 拿不到你交互式 shell 的
PATH。上面的 ExecStart 在创建时就写入了绝对路径,正是为了避免这个问题 —— 如果你把它
改成了裸命令,请把完整路径改回去。
relay 在跑但 Claude Code 不走它。 服务注册没问题;检查 ~/.claude/settings.json
里的 ANTHROPIC_BASE_URL。当 claudeSetup: true 时,relay 启动时会自己管理这个值。