对,先做 1+2 一起,而且要改变一个原则:

以后不要再让 healthcheck 或手动命令直接跑 hermes gateway run --replace。
统一交给 systemd --user 管,healthcheck 只负责 systemctl --user restart hermes-gateway。

否则你现在这个崩溃链会一直复现:

手动 gateway --replace
↓
杀掉 healthcheck 拉起的 gateway
↓
healthcheck 又发现挂了
↓
再拉一个
↓
多个 --replace 互相杀
↓
Discord 偶发 timeout
↓
gateway 退出
↓
循环

最推荐顺序

1. 先停掉所有 Hermes gateway
2. 建 systemd user service
3. healthcheck 改成只操作 systemd,不直接启动 gateway
4. 再做 Discord 连接状态检测

也就是:

先 1 + 2
再 3

一、先清理所有旧进程

在 WSL 里执行:

pkill -f "hermes gateway" 2>/dev/null || true
ps aux | grep "hermes gateway" | grep -v grep || echo "no gateway"

二、确认 systemd 是否可用

ps -p 1 -o comm=

如果输出是:

systemd

就可以继续。

如果不是,编辑:

sudo nano /etc/wsl.conf

写入:

[boot]
systemd=true

然后在 Windows PowerShell 执行:

wsl --shutdown

重新打开 WSL,再检查:

ps -p 1 -o comm=

三、创建 Hermes gateway 的 systemd user service

先找 Hermes 路径:

command -v hermes

假设输出是 ${HOME}/.local/bin/hermes 或类似路径。

然后创建 service:

mkdir -p ~/.config/systemd/user
 
cat > ~/.config/systemd/user/hermes-gateway.service <<'EOF'
[Unit]
Description=Hermes Gateway
After=network-online.target
Wants=network-online.target
 
[Service]
Type=simple
WorkingDirectory=%h/.hermes/hermes-agent
EnvironmentFile=%h/.hermes/.env
ExecStart=/usr/bin/env bash -lc 'exec hermes gateway run'
Restart=always
RestartSec=10
KillSignal=SIGTERM
TimeoutStopSec=20
 
[Install]
WantedBy=default.target
EOF

重点是这里:

ExecStart=/usr/bin/env bash -lc 'exec hermes gateway run'

不要用:

hermes gateway run --replace

因为现在 service 本身就是唯一控制者,不需要 --replace。


四、启动并设置自启

systemctl --user daemon-reload
systemctl --user enable --now hermes-gateway.service
systemctl --user status hermes-gateway.service

看日志:

journalctl --user -u hermes-gateway.service -f

五、把 healthcheck 改成带锁,并且只重启 service

新建:

nano ~/.hermes/healthcheck_gateway.sh

写入:

#!/usr/bin/env bash
set -euo pipefail
 
LOCK_FILE="/tmp/hermes-gateway-healthcheck.lock"
LOG_FILE="$HOME/.hermes/healthcheck_gateway.log"
 
exec 9>"$LOCK_FILE"
 
if ! flock -n 9; then
  echo "$(date '+%F %T') another healthcheck is running, exit" >> "$LOG_FILE"
  exit 0
fi
 
echo "$(date '+%F %T') healthcheck start" >> "$LOG_FILE"
 
# 确保 env 加载
if [ -f "$HOME/.hermes/.env" ]; then
  set -a
  source "$HOME/.hermes/.env"
  set +a
fi
 
# 只检查 systemd 里的 gateway,不再自己启动 gateway
if ! systemctl --user is-active --quiet hermes-gateway.service; then
  echo "$(date '+%F %T') gateway inactive, restarting service" >> "$LOG_FILE"
  systemctl --user restart hermes-gateway.service
  exit 0
fi
 
echo "$(date '+%F %T') gateway service active" >> "$LOG_FILE"

赋权:

chmod +x ~/.hermes/healthcheck_gateway.sh

手动跑一次:

~/.hermes/healthcheck_gateway.sh
cat ~/.hermes/healthcheck_gateway.log

六、检查是否还存在多实例

pgrep -af "hermes gateway"

正常只应该看到一个 gateway 主进程。

如果看到两个以上,先杀干净再交给 systemd:

pkill -f "hermes gateway" 2>/dev/null || true
systemctl --user restart hermes-gateway.service
pgrep -af "hermes gateway"

七、暂时不要做的事

先不要让 Hermes 自己执行这种命令:

hermes gateway run --replace

也不要让 healthcheck 里写:

nohup hermes gateway run --replace &

这是你这次多实例互杀的核心原因。


八、下一步再加 Discord 智能检测

等 systemd 稳定后,再让 healthcheck 检测日志里的 Discord 状态,例如:

journalctl --user -u hermes-gateway.service -n 100 --no-pager | grep -Ei "connected|ready|discord|gateway"

如果日志里长期没有 ready / connected,再重启 service。

但第一阶段先别搞复杂,先保证:

gateway 只有一个
不会跟终端生命周期绑定
不会被 --replace 互相杀

你现在最该做的是:systemd user service 接管 gateway,healthcheck 只能 restart service,不能直接 run gateway。

2026-10-09 Codex 审阅说明

这是单 profile 阶段的部署方案。当前可能有多个独立 profile 的 gateway,不能只因多个进程就判断重复;广泛 pkill -f 会影响所有匹配的 gateway。user network-online.target 不保证系统网络已可达;EnvironmentFile 采用 systemd 环境文件语法,与 source .env 的 shell 语法并不完全相同。user service 注销后能否持续取决于 user manager 生命周期/lingering 等条件,不能无条件承诺。本次未改 unit、reload 或重启。

核验依据:systemd KillMode 说明。