Hermes One 桌面客户端远程连接 Hermes Agent:SSH 隧道与 HTTP 直连完整配置

桌面客户端连远程 Hermes Agent 的两种方式完整步骤:服务端 API server 配置、SSH 免密与权限坑、HTTP 直连 token,含真实报错与排查清单。

Hermes Agent 跑在一台内网 Linux 虚拟机上,人在 MacBook Air 前面。想用桌面客户端 Hermes One 连过去聊天,客户端启动就问你要 SSH 地址或者 HTTP 地址加 token。

服务端必须先配,不配连不上。这篇把两种连法的完整步骤写出来,照着做就能通。

💡 两种方式选哪个

  • SSH 隧道:客户端在本地开个隧道钻进去,Hermes 端口只监听 127.0.0.1,外面扫不到。推荐。
  • HTTP 直连:直接填 http://<内网IP>:8642 加 token。少一层,但端口对整个内网敞开,靠 token 挡。

两种可以同时开着,互不影响。本文先把 SSH 走通(第 1~5 步),HTTP 是在此基础上改一个值(第 6 步)。

一、要准备什么

东西说明怎么查
服务端内网 IP跑 Hermes 那台机器的地址在服务端运行 ip -4 addr show | grep inet
服务端登录用户名Hermes 是用哪个账号跑的在服务端运行 whoami
Hermes 端口API server 端口,默认 8642本文一律用 8642
客户端公钥Mac 上的 SSH 公钥在 Mac 运行 cat ~/.ssh/id_rsa.pub

本文示例用 203.0.113.19 代表服务端 IP,用户名 hermesuser照做时换成你自己的值。

⚠️ 客户端和服务端必须能互通。内网地址意味着 Mac 要在同一网段,或者已经连上 VPN。人在外面用 4G 是连不上的,那需要另配 Tailscale 或反向代理,不在本文范围。

二、服务端配置(在跑 Hermes 的机器上操作)

第 1 步:开启 API server

Hermes 默认不开 API server,得在 ~/.hermes/.env 里加四行。

先看看有没有配过:

grep -n "API_SERVER" ~/.hermes/.env

没有输出就是没配。用编辑器打开 ~/.hermes/.env,在末尾追加:

API_SERVER_ENABLED=true
API_SERVER_KEY=先随便填个占位符
API_SERVER_PORT=8642
API_SERVER_HOST=127.0.0.1

四个值的含义:

变量作用本步填什么
API_SERVER_ENABLED总开关,不写就不启动true
API_SERVER_KEY客户端认证用的 Bearer token占位符即可,见下面说明
API_SERVER_PORT监听端口8642
API_SERVER_HOST监听地址SSH 模式填 127.0.0.1

💡 API_SERVER_KEY 为什么可以先填占位符

客户端第一次通过 SSH 连上来时,会检查这个值:长度不足 16 字符或者长得像占位符,它就自己生成一个 48 位十六进制字符串写回服务端的 .env

这不是 bug,是客户端源码 src/main/ssh-remote.tsisUsableApiServerKey() 的设计(MIN_API_SERVER_KEY_LENGTH = 16)。所以你自己精心生成的 key 可能会被换掉——不用跟它较劲,让它自己生成最省事

后面第 6 步要用 HTTP 模式时,记得重新读一遍 .env 拿最新的值。

第 2 步:确认 SSH 服务端允许端口转发

SSH 隧道靠的是端口转发。检查一下:

sudo sshd -T | grep -i allowtcpforwarding

要看到 allowtcpforwarding yes。如果是 no,编辑 /etc/ssh/sshd_configAllowTcpForwarding 改成 yes,然后 sudo systemctl reload sshd

⚠️ 路径是 /etc/ssh/sshd_configssh 目录下),不是 /etc/sshd_config。另外注意别改成 ssh_config——那个是客户端配置,改了没用。

💡 这项默认就是 yes,绝大多数机器不用动。sudo 是必须的,普通用户跑 sshd -T 会报 Permission denied

没有 sudo 权限的话用这个替代,直接查配置文件里有没有显式关掉:

grep -i allowtcpforwarding /etc/ssh/sshd_config

没有任何输出,或者输出前面带 #(注释掉了),都说明用的是默认值 yes,可以放心往下走。只有明确看到不带 #AllowTcpForwarding no 才需要改。

第 3 步:把客户端公钥加进来

在 Mac 上先看公钥:

cat ~/.ssh/id_rsa.pub

输出是一整行,ssh-rsa AAAAB3... 开头,用户名@主机名 结尾。整行复制。

⚠️ 一定是 .pub 结尾的公钥。公钥公开传没问题;不带 .pub 的那个是私钥,绝对不要拷来拷去、不要发到聊天软件里

如果 No such file or directory,说明 Mac 还没生成密钥,跑一次:

ssh-keygen -t ed25519 -C "macbook-air"

一路回车(passphrase 可以留空),然后 cat ~/.ssh/id_ed25519.pub

回到服务端,把这行追加进去:

echo "把刚复制的整行粘在这里" >> ~/.ssh/authorized_keys

第 4 步:修权限(最容易踩的坑)

这步是本文最重要的一步。权限不对,认证一定失败,而且报错信息完全看不出原因。

chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys

验证:

ls -ld ~/.ssh ~/.ssh/authorized_keys

必须看到 drwx------.ssh 目录)和 -rw-------(authorized_keys 文件):

drwx------ 2 hermesuser hermesuser 4096 Aug  1 19:20 /home/hermesuser/.ssh
-rw------- 1 hermesuser hermesuser  740 Aug  1 19:20 /home/hermesuser/.ssh/authorized_keys

⚠️ 为什么这步能卡住人

sshd 默认 StrictModes yes,只要 ~/.ssh 目录组可写或其他人可写(比如 drwxrwxr-x,也就是 775),它就直接拒绝公钥认证——不给任何提示,日志里也不写具体原因。

客户端这边只会看到一句笼统的 Could not connect via SSH or reach Hermes on the remote,让人以为是 key 错了或者服务没起来,方向全错。

很多人只 chmodauthorized_keys 文件,忘了父目录,就栽在这里。

第 5 步:重启 Hermes gateway

改完 .env 必须重启才生效。用 systemd 管理的话:

systemctl --user restart hermes-gateway.service

⚠️ Hermes gateway 关闭时要等在线会话排空,重启可能要 2~3 分钟。这期间 systemctl --user is-active 会一直显示 deactivating,是正常的,耐心等。

等它变成 active 后,确认端口起来了:

ss -tlnp | grep 8642

应该看到:

LISTEN 0  128  127.0.0.1:8642  0.0.0.0:*  users:(("hermes",pid=892356,fd=21))

127.0.0.1:8642 说明只监听本地回环——这正是 SSH 模式要的效果,外网和内网都扫不到这个端口。

再验证服务真的能应答:

curl -s http://127.0.0.1:8642/health

真实输出长这样,能看到版本号就说明 API server 正常工作:

{"status": "ok", "platform": "hermes-agent", "version": "0.19.0"}

三、客户端连接(在 Mac 上操作)

先自测免密登录

别急着打开客户端。 客户端界面下方那句提示 “Make sure you can already run ssh user@host without a password prompt” 是硬要求,先在 Mac 终端验证:

ssh hermesuser@203.0.113.19 'echo OK'

  • 输出 OK没问密码 → 通过,往下走。
  • 弹出密码提示 → 公钥认证没成功,回头查第 3、4 步。
  • Connection refused / timeout → 网络不通或 sshd 没跑,跟 Hermes 无关。

如果要看具体卡在哪,加 -vvv

ssh -vvv hermesuser@203.0.113.19 'echo OK' 2>&1 | tail -30

关键是找 Offering public key: 那几行——它列出客户端实际尝试了哪些密钥

⚠️ 另一个高频坑:客户端根本没用你以为的那把钥匙

如果 -vvv 输出里全是这样:

debug1: Will attempt key: /Users/mana/.ssh/id_rsa RSA SHA256:wBww...
debug1: Trying private key: /Users/mana/.ssh/id_ecdsa
debug3: no such identity: /Users/mana/.ssh/id_ecdsa: No such file or directory
debug1: Trying private key: /Users/mana/.ssh/id_ed25519
debug3: no such identity: /Users/mana/.ssh/id_ed25519: No such file or directory
debug1: Next authentication method: password
hermesuser@203.0.113.19's password:

说明 Mac 上只有 id_rsa 一把钥匙,而你加到服务端的是另一把的公钥。它试完手头所有钥匙都不匹配,就回落到密码认证。

最省事的解法:别折腾专用密钥,直接用 Mac 现成的 ~/.ssh/id_rsa.pub(第 3 步就是这么写的)。客户端的 Private Key Path 留空,默认就走 ~/.ssh/id_rsa

填写客户端

自测过了,打开 Hermes One,选 Connect via SSH

字段填什么
SSH Host203.0.113.19
SSH Port22
Usernamehermesuser
Private Key Path留空(默认 ~/.ssh/id_rsa
Remote Hermes Port8642

Connect via SSH →

连上后可能弹一条警告

界面顶部出现琥珀色横幅:

API Server Key not set — chat will fail.    SET NOW  Show details  ✕

这是客户端的误报,服务端没问题。

原因在客户端源码:getApiServerKeyStatus()getApiServerKey()readEnv(profile)profilePaths(profile).envFile。这条链读的是 Mac 本地~/.hermes/.env,整个过程没有任何 SSH 感知。Mac 上没装 Hermes、没这个文件,就判定 hasKey: false,横幅就弹了——它查错了机器。

这条文案属于 “Configuration health”(本地配置体检)模块,不是连接状态检测。客户端自己的 i18n 文案里都写了「if you keep secrets in a vault… this warning can be ignored」。

怎么处理

  • 先直接发一条消息试试。 能正常回复就是纯误报,点 关掉。
  • 想彻底消掉:点 SET NOW,再点里面的 Auto(自动生成)。它只写 Mac 本地的 ~/.hermes/.env服务端完全不受影响

💡 之后 Mac 本地和服务端会有两个不同API_SERVER_KEY,这不冲突:SSH 模式下客户端从服务端读真 token,本地那个只是让体检闭嘴用的。

四、可选:加开 HTTP 直连模式

不想每次走 SSH 隧道,可以让客户端直接 HTTP 连。

第 6 步:把监听地址改成 0.0.0.0

编辑服务端 ~/.hermes/.env,把第 1 步那行改掉:

API_SERVER_HOST=0.0.0.0

⚠️ 必须是 0.0.0.0,不要填具体内网 IP。

填成 203.0.113.19 这种具体地址后,127.0.0.1 就不再监听了——SSH 隧道转发的目标正是 127.0.0.1:8642SSH 模式会直接失效0.0.0.0 表示所有网卡都听,两种模式才能共存。

重启:

systemctl --user restart hermes-gateway.service

确认监听地址变了:

ss -tlnp | grep 8642

LISTEN 0  128  0.0.0.0:8642  0.0.0.0:*  users:(("hermes",pid=900967,fd=21))

第 7 步:取出真正的 token

这步不能跳。 前面说过,客户端第一次 SSH 连接时可能已经把 API_SERVER_KEY 换掉了。重新读一遍:

grep "^API_SERVER_KEY" ~/.hermes/.env

输出形如:

API_SERVER_KEY=3f9a1c74-2b58-4e0d-9a61-7c4e8d05b2af

用这个值,不是你第 1 步填的占位符。

⚠️ 上面这串是示例,你的值一定不一样,照抄必然认证失败。UUID 形式(客户端 randomUUID() 生成)和 48 位十六进制(客户端 randomBytes(24) 生成)都是正常的。

第 8 步:服务端自测

在服务端用内网 IP(不是 127.0.0.1)验证三件事:

带 token 应该返回模型列表:

curl -s -H "Authorization: Bearer 你的token" http://203.0.113.19:8642/v1/models

不带 token 应该返回 401:

curl -s -o /dev/null -w "%{http_code}\n" http://203.0.113.19:8642/v1/models

真发一条消息,确认 agent 能回:

curl -s -m 120 -X POST http://203.0.113.19:8642/v1/chat/completions \
  -H "Authorization: Bearer 你的token" \
  -H "Content-Type: application/json" \
  -d '{"model":"hermes-agent","messages":[{"role":"user","content":"只回复两个字:正常"}],"stream":false}'

三项都对了再去填客户端。

客户端填写

Settings → Connection → 选 Remote

字段填什么
Remote URLhttp://203.0.113.19:8642
API Key第 7 步取到的值

⚠️ 安全提醒:绑 0.0.0.0 后,8642 会暴露在这台机器的所有网络接口上,包括 docker 网桥(172.17.x 那些)。唯一的防线就是 Bearer token。内网可信的话风险不大;不放心就改回 127.0.0.1 只用 SSH 模式。

五、排查清单

连不上时按顺序查,每一步都有明确的判定标准。

#检查命令期望
1网络通不通Mac: nc -vz 203.0.113.19 22succeeded
2免密登录Mac: ssh user@ip 'echo OK'输出 OK,不问密码
3.ssh 权限服务端: ls -ld ~/.sshdrwx------
4公钥在不在服务端: ssh-keygen -l -f ~/.ssh/authorized_keys指纹与 Mac 的 ssh-keygen -l -f ~/.ssh/id_rsa.pub 一致
5端口转发服务端: grep -i allowtcpforwarding /etc/ssh/sshd_config无输出或带 # = 默认 yes
6gateway 活着服务端: systemctl --user is-active hermes-gateway.serviceactive
7端口在听服务端: ss -tlnp | grep 8642有 LISTEN 行
8服务能应答服务端: curl -s http://127.0.0.1:8642/health有响应

💡 第 4 步的指纹对比是最直接的判据。两边跑同样的命令,输出的 SHA256:... 必须一样。不一样就是加错了公钥。

自己搭个隧道验证

想绕开客户端确认隧道链路本身没问题,在 Mac 上手动开一个:

ssh -N -L 18642:127.0.0.1:8642 hermesuser@203.0.113.19

这条命令不会返回,让它挂着。另开一个终端

curl -s http://127.0.0.1:18642/health

有响应就说明隧道链路完全正常,问题在客户端配置。测完回第一个终端按 Ctrl+C

💡 -L 18642:127.0.0.1:8642 的意思是:Mac 本地的 18642 端口,转发到服务端的 127.0.0.1:8642。本地用 18642 是为了避免和其它服务撞车,随便换个空闲端口都行。

六、实战验证

本文所有命令都在真实环境跑过。

环境

服务端Linux 6.14.0,Hermes Agent,systemd user service 托管
客户端MacBook Air,Hermes One (fathah/hermes-desktop)
网络同一内网

验证结果

步骤结果
开启 API server,重启 gatewayss 显示 127.0.0.1:8642 LISTEN
/health 探活✅ 有响应
手动 SSH 隧道 + 隧道内 /health✅ 通
带 token 请求 /v1/models✅ 200,返回模型列表
不带 token 请求✅ 401
真实 chat completion✅ 返回预期内容
0.0.0.0 后内网 IP 直连✅ 三项检查全通过
客户端 SSH 模式连接✅ 连通,可正常对话

踩到的两个坑,都是真实报错

  1. ~/.ssh 权限 775 → sshd StrictModes 静默拒绝公钥认证。客户端只报笼统的 Could not connect via SSH,服务端日志也不写原因。修成 700 后立刻通。

  2. 私钥没在客户端机器上ssh -vvv 显示只尝试了 id_rsa,其余 4 个候选全是 No such identity,最后回落到密码认证。改用客户端现成的 id_rsa.pub 解决,顺带避免了传私钥。

一个误报:连接成功后弹 API Server Key not set — chat will fail.,实测聊天功能完全正常。查客户端源码确认它读的是本地 ~/.hermes/.env,与服务端无关。点 SET NOWAuto 后横幅消失,且核对服务端 .env 的 mtime 和 key 值均未改变——确认只写了本地文件。

七、小结

  • SSH 模式的服务端配置就四行 .env 加一次重启,API_SERVER_HOST127.0.0.1
  • chmod 700 ~/.ssh 是最容易漏又最致命的一步,权限不对时所有报错都会指向错误的方向。
  • 打开客户端之前,先在终端跑通 ssh user@host 'echo OK'。这一步能过,客户端基本就能连。
  • 客户端会覆写服务端的 API_SERVER_KEY,要 HTTP 模式的话记得重新读 .env 拿最新值。
  • “API Server Key not set” 横幅查的是客户端本地文件,属于误报,别去服务端找问题。
  • HTTP 模式改 0.0.0.0 即可与 SSH 模式共存,但不要填具体 IP,那样会让 SSH 隧道失效。