N

Norlcayn的博客

A text-focused Halo theme

  • 首页
  • 文章
  • 默认分类
  • 关于
主页 把 magpie 装进无头服务器:一次部署、三次判断被推翻与双号池接入
文章

把 magpie 装进无头服务器:一次部署、三次判断被推翻与双号池接入

发表于 3天前 更新于 3天前
作者 Administrator
38~48 分钟 阅读

把 magpie 装进无头服务器:一次部署、三次判断被推翻与双号池接入

这篇文章记录在同一天里做完的一件事:把 magpie 装进一台没有图形界面的 Ubuntu 服务器,用 Web UI 管理它,最后把服务器上那套模型号池接进本地的 DSH。中间有三次判断被自己的实测推翻,都记下来。

提前说明:这里的 magpie 不是 Windows 上那个窗口放大工具,而是 yetone/magpie —— 一个 agent 模型统一入口。


先搞清楚 magpie 是什么

它做两件事:

  1. 统一改写各家 agent 的模型配置(Claude Code、Codex、OpenCode、Pi……),一处切换全部生效;
  2. 内置一个 OpenAI 兼容网关,默认监听 127.0.0.1:3425。

对我们要做的事来说,第二点才是关键:只要网关能访问,任何 OpenAI 兼容客户端都能借到它背后的账号池。

它在 Linux 上发两个构建,这个分叉后来决定了整个部署路径:

构建 文件 依赖
桌面版 magpie-linux-amd64 需要 GTK 3 + WebKitGTK 4.1
终端版 magpie-cli-linux-amd64 静态链接,零系统依赖

终端版包含 TUI + Web UI + CLI 三套界面,Web UI 不需要图形环境。


第一次推翻:桌面版装不上

服务器是 Ubuntu 24.04 / x86_64 / glibc 2.39,纯 headless,一个 GUI 库都没有。我按直觉先把桌面版拷过去试:

$ ldd /tmp/magpie-probe | grep 'not found'
libwayland-client.so.0      => not found
libgtk-3.so.0               => not found
libgdk-3.so.0               => not found
libgdk_pixbuf-2.0.so.0      => not found
libwebkit2gtk-4.1.so.0      => not found
libsoup-3.0.so.0            => not found
libjavascriptcoregtk-4.1.so.0 => not found

$ /tmp/magpie-probe --version
error while loading shared libraries: libwayland-client.so.0: cannot open shared object file

7 个缺失库,启动阶段就挂 —— 哪怕我只想跑 web 模式。动态链接器在 main() 之前就检查 DT_NEEDED,不看你用不用得到那个功能。

补库要多少?

$ apt-get -s install --no-install-recommends libgtk-3-0t64 libwebkit2gtk-4.1-0 ...
134 newly installed

而服务器磁盘只剩 4.8G(88% 已用)。为了跑一个工具装上百个包,不划算。

正解藏在官方 install.sh 的注释里:

Linux: puts magpie in ~/.local/bin — the desktop app when GTK 3 and WebKitGTK 4.1 are installed, the terminal build otherwise.

脚本里有 has_gui_libs(),用 ldconfig -p 查那两个库,没有就自动选终端版。而终端版是静态的:

$ file magpie-cli
ELF 64-bit LSB executable, x86-64, statically linked

$ ldd magpie-cli
not a dynamic executable

结论:headless 机器上不要装桌面版,直接用 terminal build。


第二个坑:GitHub 只有 17 KB/s

终端版 64 MB,服务器直连 GitHub:

$ curl -o magpie-cli .../magpie-cli-linux-amd64
# 5 分钟后:5,251,072 / 64,368,802 字节

约 17 KB/s。本机(有代理)同一文件是 8.5 MB/s。

所以改成"本机下载 + scp":

步骤 耗时
本机下载 64 MB 约 8 秒
scp 到服务器 1 分 24 秒(约 760 KB/s)

校验后才装:SHA-256 ddcc5ddb…eb6f9eb、尺寸 64,368,802 字节,与官方 release 一致。

这条经验后面又救了一次场(见 Bun 那节)。在国内云上装 GitHub 分发的东西,"本机下好再传"几乎总是比直连快一个数量级。


让它活下来:systemd + linger

最初用 setsid 裸起:

setsid -f env MAGPIE_WEB_KEY=... magpie web --addr 127.0.0.1:3500 --no-open

能用,但服务器一重启就没了。

改用 systemd user service 时踩到一个隐蔽的坑:user manager 默认会跟着最后一个 SSH 会话一起退出。

$ loginctl show-user admin -p Linger
Linger=no

于是:

sudo loginctl enable-linger admin

验证方式不是看命令返回值,而是看进程树:

$ ps -o pid,ppid,cmd -p $(pgrep -u admin -f 'systemd --user')
   PID    PPID CMD
2679568       1 /usr/lib/systemd/systemd --user

PPID=1 才说明它真的独立于 SSH 会话了。

服务单元:

[Unit]
Description=magpie web UI and gateway (headless)

[Service]
Type=simple
EnvironmentFile=/home/admin/.config/magpie/web.env
ExecStart=/home/admin/.local/bin/magpie web --addr 127.0.0.1:3500 --no-open
Restart=always
RestartSec=5

自愈实测:kill -9 主进程后 10 秒内换新 PID,端口恢复监听,页面依旧 200。


对外访问的三次演进

第一版:SSH 隧道

gateway 和 Web UI 都绑在 127.0.0.1,本机要访问只能建隧道:

ssh -N -L 127.0.0.1:3500:127.0.0.1:3500 ztb-zeb-server

做成 systemd user service 后能自愈。但缺陷很明显:每台想用的电脑都得自己建隧道,手机根本没法用。

顺带纠正一个常见误解:nohup / setsid 只解决"终端关了进程还活着",不解决重连。实测关掉 shell 后 ssh 进程确实被 init 收养(PPID 变成 847)、端口继续响应,但网络抖一次它就成僵尸——进程还在、端口还占着、连接已死。所以最后还是落到 systemd 的 Restart=always。

第二版:绑 Tailscale IP

服务器上本来就装了 Tailscale(1.102.2),tailscale0 = 100.64.x.x。把监听地址从 127.0.0.1 换成这个 IP:

ExecStart=... magpie web --addr 100.64.x.x:3500 --no-open

本机、Windows 桌面、手机,只要在同一个 tailnet 里,浏览器直接开就能用。隧道退役。

我也试过看起来更优雅的 tailscale serve(自带 HTTPS 反代),结果卡在证书获取:国内访问 Tailscale 的证书服务太慢,命令挂住,serve status 始终是 No serve config。清理掉,放弃这条路。

第三版:magpie 原生的「局域网共享」(最终)

后来才发现,magpie 的「设置 › 网络与共享」里本来就有一项「局域网共享」:

  • 开了它,gateway 从 127.0.0.1:3425 变成 *:3425(所有网卡)
  • 自动生成网关密钥 lanKey
  • 配套「允许的地址」CIDR 白名单

开启后实测:

# 带密钥
$ curl -H "Authorization: Bearer sk-magpie-key-…" \
       http://100.64.x.x:3425/v1/models
200  → 服务器号池的 3 个模型

# 不带密钥
$ curl http://100.64.x.x:3425/v1/models
401

到这一步,SSH 隧道完全多余了 —— agent 直接连 tailnet 地址即可。这才是它设计好的用法,我前面绕了远路。

安全上补一刀:在「允许的地址」里填 100.64.0.0/10(CGNAT 段,即 tailnet 范围),非 tailnet 来源一律拒绝,即使带着密钥。


插件宿主需要 Bun,又撞上 GitHub

装 WorkBuddy 插件时,Web UI 直接红字报错:

downloading Bun 1.4.2 to run plugins:
https://github.com/oven-sh/bun/releases/download/bun-v1.4.2/bun-linux-x64-baseline.zip:
http: client connection lost

magpie 的插件宿主(plugin host)用 Bun 运行,第一次加载插件时去 GitHub 拉 36.6 MB 的 zip。又是那条 17 KB/s 的链路。

解法还是"本机下载 + 传送",但得知道它放哪。本机已有的缓存可以做参照:

~/.cache/magpie/bun/
├── 1.4.3/magpie-bun
├── 1.4.4/magpie-bun
└── state.json      {"current":"1.4.4","previous":"1.4.3","checked":"…"}

于是照抄这个布局:

# 本机(36,646,949 字节)
curl -O .../bun-v1.4.2/bun-linux-x64-baseline.zip
unzip → bun-linux-x64-baseline/bun        # 79,500,640 字节
scp bun ztb-zeb-server:/tmp/magpie-bun-1.4.2

# 服务器
install -m 755 /tmp/magpie-bun-1.4.2 ~/.cache/magpie/bun/1.4.2/magpie-bun
cat > ~/.cache/magpie/bun/state.json <<'EOF'
{"current": "1.4.2", "previous": "", "checked": "2026-10-31T10:10:18Z"}
EOF
systemctl --user restart magpie-web

验证成功的标志不是"没有报错",而是这两样:

$ ~/.cache/magpie/bun/1.4.2/magpie-bun --version
1.4.2

$ ls ~/.cache/magpie/plugin-host/
host-57e4026712e7.js  pi-2694d2cfa370.js

插件宿主脚本被生成出来,说明 Bun 真的被调起来了。接着:

$ magpie plugin add workbuddy
✓ added @magpie-community/opencode-workbuddy-auth@latest
  workbuddy-ai (16 models)  not signed in
  workbuddy (16 models)     not signed in

值得注意的是:插件本体走 npm,服务器访问 npm 完全正常(npmmirror / npmjs 都 200)。坏的只有 GitHub 这一条链路。


双号池:为什么故意不合并

本机 magpie 上挂着 9 个以上 WorkBuddy 账号:

workbuddy · Free plan · .            Credits 55% (2200.75 / 4000)
workbuddy · Free plan · 137****4011  Credits 17%
workbuddy · Free plan · 13962297849  Credits 50%
...

服务器上是另一套,2 个账号:

workbuddy · Free plan · 192****5634  Credits 96% (2893.16 / 3000)
workbuddy · Free plan · 192****7516  Credits 57%

我的选择是不合并:两套各自轮询、各自限速,互不影响。于是本地 DSH 需要两个 provider:

DSH
├─ provider magpie         → 127.0.0.1:3425        → 本机 magpie   → 本机号池
└─ provider magpie-server  → 100.64.x.x:3425       → 服务器 magpie → 服务器号池

两者确实不是同一套,模型列表都不一样:

端点 模型
127.0.0.1:3425 22 个(auto/river/codex/qoder/workbuddy…)
100.64.x.x:3425 3 个(workbuddy/deepseek-v4.1-flash、glm-5.3、glm-5.3-flash)

接进 DSH:判断错了两次

错一:以为协议必须改

在「设置 › 模型 › 自定义模型 API」里新建 provider 时,「API 协议」默认是 OpenAI Responses。我第一反应是"这不对,magpie gateway 是 Chat Completions 兼容",于是断言必须改成 openai-completions。

一测就打脸:

$ curl -X POST http://100.64.x.x:3425/v1/responses \
    -d '{"model":"workbuddy/glm-5.3-flash","input":"say OK"}'

{"object":"response","model":"glm-5.3-flash",
 "output":[{"type":"reasoning",…},{"text":"OK"}]}

两种协议都支持。 更直接的反证其实一直摆在眼前:配置文件里早就有一个 magpie-responses provider,用的就是 api: openai-responses 指向同一个 gateway。我看到了,却没当回事。

错二:以为必须重启 DSH

DSH 的 cordis.patch.yml 只在启动时加载,没有热重载,所以我断言改完必须 systemctl --user restart。

但实际上,在 UI 里补上密钥之后直接就好了,进程根本没重启:

$ systemctl --user show dsh-web.service -p MainPID -p ActiveEnterTimestamp --value
Sat 2026-10-31 12:13:17 CST
886

原因在源码里:llm-pi-ai 适配器是 "dormant settings-driven" 的,settings 一到就动态注册 routes。UI 里的改动即时生效;外部改文件才需要重启。

真正的原因只有一个

本轮运行失败  No API key for provider: magpie-server

表单里密钥留空,provider 就没有 apiKeyEnv,DSH 在发请求前直接拒掉。跟协议、跟重启都无关。

最终配置:

magpie-server:
  displayName: Magpie 服务器号池
  apiKeyEnv: MAGPIE_GATEWAY_KEY
  api: openai-responses
  baseURL: http://100.64.x.x:3425/v1

这里有个小技巧:本机 magpie 对任意 key 都返回 200(loopback 免校验),所以可以直接把凭证里的占位值换成服务器的 lanKey —— 一个变量同时喂两个 provider:

  • 本机 provider(loopback)→ 不校验,随便过
  • 服务器 provider(LAN)→ lanKey 正确,401 变 200

最终架构

本机 DSH
 ├─ provider magpie         → 127.0.0.1:3425 ─────────┐
 │                                                    ├→ 本机 magpie  → 本机号池(9 账号)
 └─ provider magpie-server  → 100.64.x.x:3425         │
                              ├ tailnet(WireGuard)  │
                              ├ lanKey 认证           │
                              ├ CIDR 100.64.0.0/10    │
                              └→ 服务器 magpie ───────┴→ 服务器号池(2 账号)

访问面
 ├─ Web UI   http://100.64.x.x:3500/?k=…    仅 tailnet
 └─ Gateway  http://100.64.x.x:3425/v1      仅 tailnet + lanKey

服务器侧组件:

组件 形态 自愈
magpie-web.service systemd user service + Linger=yes Restart=always,5 秒
运行时 静态二进制 ~/.local/bin/magpie 无动态依赖
插件宿主 Bun 1.4.2(离线预置在 ~/.cache/magpie/bun/) 随插件加载

公网零新增端口,云安全组保持原样。


值得记下来的坑

  1. headless 上别装 GUI 版。 动态链接器在 main() 之前就校验 DT_NEEDED,缺库直接拒绝启动,跟你跑不跑 GUI 无关。看 install.sh 的注释比看 README 快。
  2. 国内云装 GitHub 分发物,先本机下再传。 17 KB/s 对 8.5 MB/s,差两个数量级;scp 走 SSH 链路通常也有 700 KB/s 以上。
  3. user service ≠ 常驻。 Linger=no 时会话断完就退出,验证要看 systemd --user 的 PPID 是否为 1。
  4. nohup 不解决重连。 活着的僵尸隧道比死了的更麻烦——端口还占着,你还得先找 pid 杀掉才能重开。
  5. tailscale serve 在国内可能卡证书。 别把它当首选反代,直连 tailnet IP + 应用自带鉴权更省事。
  6. 先翻一遍 UI 的「网络/共享」页。 magpie 本来就有局域网共享 + 密钥 + CIDR 白名单,我一开始完全没注意到,多建了一层隧道。
  7. 插件宿主是独立运行时。 它不是内嵌执行的,要 Bun;缓存路径固定为 ~/.cache/magpie/bun/<version>/magpie-bun,还配套一个 state.json。
  8. UI 建的自定义 provider,创建后只有密钥可改。 协议等字段锁死,建错只能删重建或直接改配置文件。
  9. "配置文件只读一次"≠"改了必须重启"。 要看改的是文件,还是通过 UI/settings API —— 后者是运行时生效的。
  10. 说"能不能用"之前先实测。 我两次断言(协议必须改、必须重启)都被一条 curl 和一个进程时间戳推翻了。

最后

这次真正耗时的不是部署,而是三次判断被自己的实测推翻:

  • 以为 headless 只能装 GUI 版 → 官方早就分好了两个构建
  • 以为 gateway 只支持一种协议 → 两种都通
  • 以为改配置必须重启 → UI 改的是运行时

每一次都是因为多跑了一条命令才发现前面想错了。结论还是那句老话:判断靠推理,结论靠实测。

运维
magpie Tailscale systemd
许可协议:  CC BY 4.0
分享

相关文章

10月 31, 2026

把 magpie 装进无头服务器:一次部署、三次判断被推翻与双号池接入

把 magpie 装进无头服务器:一次部署、三次判断被推翻与双号池接入 这篇文章记录在同一天里做完的一件事:把 magpie 装进一台没有图形界面的 Ubuntu 服务器,用 Web UI 管理它,最后把服务器上那套模型号池接进本地的 DSH。中间有三次判断被自己的实测推翻,都记下来。 提前说明:这

1月 16, 2026

Jmeter快速入门

1.安装Jmeter Jmeter依赖于JDK,所以必须确保当前计算机上已经安装了JDK,并且配置了环境变量。 1.1.下载 可以Apache Jmeter官网下载,地址:http://jmeter.apache.org/download_jmeter.cgi 当然,我们课前资料也提供了下载好的安装

1月 15, 2026

Linux项目部署

认识Linux Linux是一套免费使用和自由传播的操作系统 分类 系统 特点 桌面操作系统 Windows 用户数量最多 Mac OS 操作体验好,偏向办公 Linux 用户数量少 移动设备操作系统 Android 基于Linux、开源,主要用于智能手机、平板、智能电视 IOS 苹果公司开发、不开

下一篇

上一篇

Hello Halo

最近更新

  • 把 magpie 装进无头服务器:一次部署、三次判断被推翻与双号池接入
  • Hello Halo
  • 状态机
  • 数据表设计规范
  • 缓存优化

热门标签

Halo magpie 异常处理 Redisson 并行 Vue 并发 递归 请求限流 SpringAMQP

目录

©2026 Norlcayn的博客. 保留部分权利。

使用 Halo 主题 Chirpy