0
0

Hermes Terminal Docker 配置步骤

2026-07-07
2026-07-07
Hermes Terminal Docker 配置步骤

本文整理 Hermes profile 使用 Docker 作为 terminal backend 的完整配置流程。

目标:

  • Hermes 本体仍运行在宿主机 hermes 用户下。
  • 命令执行、代码执行、文件工具等进入 Docker sandbox。
  • 每个 profile 使用自己的 workspace,不直接暴露真实项目目录。
  • Telegram / gateway 中的 terminal 工具可用。

适用环境:

  • Ubuntu 24.04 LTS
  • Hermes 运行用户:hermes
  • Hermes 配置目录:/home/hermes/.hermes
  • 已使用 loginctl enable-linger hermes 让 gateway 常驻
  • Docker 已安装在宿主机

1. 核心概念

Hermes 的 Docker backend 不是把 Hermes 本体放进 Docker。

正确结构是:

宿主机:
  hermes gateway / Hermes 本体
        |
        | 调用 terminal / execute_code / file tools
        v
  Docker sandbox container
        |
        v
  /workspace

也就是说:

  • Hermes gateway 仍是普通 Linux 进程。
  • terminal 工具执行时才进入 Docker。
  • Docker 镜像在第一次使用或手动 docker pull 时下载。
  • config.yaml 里的 terminal.docker_image 必须是服务器能拉到的镜像。

2. 把 hermes 加入 docker 组

用有 sudo 权限的管理用户执行:

sudo usermod -aG docker hermes
id hermes
getent group docker

预期能看到类似:

uid=1000(hermes) gid=1000(hermes) groups=1000(hermes),100(users),987(docker)
docker:x:987:hermes

注意:Linux 组权限不会注入到已经运行的进程里。加入 docker 组后,必须重启 hermes 的 user manager / gateway。

3. 重启 hermes user manager

如果 gateway 是由 linger / systemd user service 拉起的,只重启一个 gateway 服务可能仍然继承旧组。

用 root 或 sudo 用户执行:

sudo loginctl enable-linger hermes
sudo loginctl terminate-user hermes || true
sleep 2
sudo systemctl start "user@$(id -u hermes).service"

这一步会中断 hermes 用户下正在运行的 gateway。

4. 选择可拉取的 Docker 镜像

最初建议的镜像可能是:

nikolaik/python-nodejs:python3.11-nodejs20

但如果服务器使用的 Docker Hub 镜像源返回 403 Forbidden,这个镜像可能拉不下来。

先用 hermes 用户测试镜像:

sudo -u hermes docker pull python:3.11-slim
sudo -u hermes docker run --rm python:3.11-slim python --version

如果 python:3.11-slim 也拉不动,再测试其他镜像源:

sudo -u hermes docker pull docker.m.daocloud.io/library/python:3.11-slim
sudo -u hermes docker pull ccr.ccs.tencentyun.com/library/python:3.11-slim

选一个能成功 docker pulldocker run 的镜像。

5. 写入 companion 的 terminal 配置

使用 Hermes 命令写配置:

sudo -u hermes /home/hermes/.local/bin/hermes -p companion config set terminal.backend docker
sudo -u hermes /home/hermes/.local/bin/hermes -p companion config set terminal.docker_image python:3.11-slim
sudo -u hermes /home/hermes/.local/bin/hermes -p companion config set terminal.cwd /workspace
sudo -u hermes /home/hermes/.local/bin/hermes -p companion config set terminal.docker_run_as_host_user true
sudo -u hermes /home/hermes/.local/bin/hermes -p companion config set terminal.docker_mount_cwd_to_workspace false

docker_volumes 是列表,建议手动用 vim 检查和合并,避免重复 key:

sudo -u hermes vim /home/hermes/.hermes/profiles/companion/config.yaml

确认 terminal: 类似如下:

terminal:
  backend: docker
  cwd: /workspace
  timeout: 120
  lifetime_seconds: 300
  docker_image: python:3.11-slim
  docker_mount_cwd_to_workspace: false
  docker_forward_env: []
  docker_volumes:
    - "/home/hermes/.hermes/profiles/companion/workspace:/workspace"
  docker_run_as_host_user: true
  container_cpu: 1
  container_memory: 768
  container_persistent: true
  docker_persist_across_processes: true
  docker_orphan_reaper: true

关键点:

  • docker_mount_cwd_to_workspace: false:不要自动挂载启动目录。
  • docker_forward_env: []:不要把宿主机密钥转发进容器。
  • docker_volumes:只挂 profile 自己的 workspace。
  • docker_run_as_host_user: true:避免容器写出的文件变成 root-owned。

6. 创建 companion gateway user service

如果已经有服务文件,可以跳过创建,只检查内容。

用 root 执行:

sudo install -d -o hermes -g hermes /home/hermes/.config/systemd/user

sudo tee /home/hermes/.config/systemd/user/hermes-gateway-companion.service >/dev/null <<'EOF'
[Unit]
Description=Hermes Gateway - companion
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=/home/hermes
Environment=PATH=/home/hermes/.local/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/home/hermes/.local/bin/hermes -p companion gateway run
Restart=always
RestartSec=5

[Install]
WantedBy=default.target
EOF

sudo chown hermes:hermes /home/hermes/.config/systemd/user/hermes-gateway-companion.service

7. 启动 / 重启 companion gateway

因为普通 hermes shell 可能没有 user bus,直接 systemctl --user 会报:

Failed to connect to bus: No medium found

用 root 指定 XDG_RUNTIME_DIRDBUS_SESSION_BUS_ADDRESS

uid=$(id -u hermes)
runtime="/run/user/$uid"
bus="unix:path=$runtime/bus"

sudo -u hermes XDG_RUNTIME_DIR="$runtime" DBUS_SESSION_BUS_ADDRESS="$bus" systemctl --user daemon-reload
sudo -u hermes XDG_RUNTIME_DIR="$runtime" DBUS_SESSION_BUS_ADDRESS="$bus" systemctl --user enable hermes-gateway-companion.service
sudo -u hermes XDG_RUNTIME_DIR="$runtime" DBUS_SESSION_BUS_ADDRESS="$bus" systemctl --user restart hermes-gateway-companion.service

8. 验证 gateway 是否继承 docker 组

查 docker 组 ID:

getent group docker

例如:

docker:x:987:hermes

查 companion gateway 进程:

pid=$(pgrep -u hermes -f "hermes.*companion.*gateway run|hermes.*-p companion.*gateway run|hermes.*--profile companion.*gateway run" | head -1)
echo "$pid"
grep -E "^(Uid|Gid|Groups):" "/proc/$pid/status"

Groups: 里必须包含 docker 组 ID,例如 987

如果没有,说明 user manager 仍是旧组,需要重新执行:

sudo loginctl terminate-user hermes
sleep 2
sudo systemctl start "user@$(id -u hermes).service"

然后再重启 companion gateway。

9. 验证 Docker backend

宿主机上先测:

sudo -u hermes docker version
sudo -u hermes docker run --rm python:3.11-slim python --version

如果镜像不是 python:3.11-slim,替换成 config.yaml 里的 terminal.docker_image

再去 companion 的 Telegram 里发:

用 terminal 执行:python --version,然后执行:pwd

预期:

  • terminal 工具可用。
  • python --version 正常输出。
  • pwd 输出 /workspace

10. 一键配置脚本

以下脚本会:

  • 确认 linger
  • 重启 hermes user manager
  • 创建 workspace
  • 测试候选 Docker 镜像
  • 写入 companion 的 Docker backend 配置
  • 创建 / 重启 companion gateway service
  • 验证进程组

用 root 执行:

sudo bash -lc '
set -euo pipefail

USER_NAME=hermes
PROFILE=companion
UID_HERMES=$(id -u "$USER_NAME")
RUNTIME_DIR="/run/user/$UID_HERMES"
BUS="unix:path=$RUNTIME_DIR/bus"

echo "== 1. 确认 docker 组 =="
id "$USER_NAME"
getent group docker

echo "== 2. 启用 linger 并重启 user manager =="
loginctl enable-linger "$USER_NAME"
loginctl terminate-user "$USER_NAME" || true
sleep 2
systemctl start "user@$UID_HERMES.service"

echo "== 3. 准备 workspace =="
install -d -o "$USER_NAME" -g "$USER_NAME" "/home/$USER_NAME/.hermes/profiles/$PROFILE/workspace/logs"
install -d -o "$USER_NAME" -g "$USER_NAME" "/home/$USER_NAME/.hermes/profiles/$PROFILE/workspace/notes"
install -d -o "$USER_NAME" -g "$USER_NAME" "/home/$USER_NAME/.config/systemd/user"

echo "== 4. 选择可用 Docker 镜像 =="
CANDIDATES=(
  "python:3.11-slim"
  "python:3.11-bookworm"
  "docker.m.daocloud.io/library/python:3.11-slim"
  "ccr.ccs.tencentyun.com/library/python:3.11-slim"
)

IMG=""
for candidate in "${CANDIDATES[@]}"; do
  echo "Trying: $candidate"
  if sudo -u "$USER_NAME" docker pull "$candidate"; then
    IMG="$candidate"
    break
  fi
done

if [ -z "$IMG" ]; then
  echo "没有候选镜像拉取成功,请先修 Docker registry / 网络。"
  exit 1
fi

echo "Selected image: $IMG"

echo "== 5. 写 Hermes 配置 =="
sudo -u "$USER_NAME" /home/hermes/.local/bin/hermes -p "$PROFILE" config set terminal.backend docker
sudo -u "$USER_NAME" /home/hermes/.local/bin/hermes -p "$PROFILE" config set terminal.docker_image "$IMG"
sudo -u "$USER_NAME" /home/hermes/.local/bin/hermes -p "$PROFILE" config set terminal.cwd /workspace
sudo -u "$USER_NAME" /home/hermes/.local/bin/hermes -p "$PROFILE" config set terminal.docker_run_as_host_user true
sudo -u "$USER_NAME" /home/hermes/.local/bin/hermes -p "$PROFILE" config set terminal.docker_mount_cwd_to_workspace false

echo "== 6. 确保 docker_volumes 存在 =="
python3 - <<PY
from pathlib import Path
import yaml

p = Path("/home/hermes/.hermes/profiles/companion/config.yaml")
data = yaml.safe_load(p.read_text()) or {}
terminal = data.setdefault("terminal", {})
terminal["docker_forward_env"] = []
terminal["docker_volumes"] = ["/home/hermes/.hermes/profiles/companion/workspace:/workspace"]
p.write_text(yaml.safe_dump(data, sort_keys=False, allow_unicode=True))
PY
chown "$USER_NAME:$USER_NAME" "/home/$USER_NAME/.hermes/profiles/$PROFILE/config.yaml"

echo "== 7. 写 companion user service =="
cat > "/home/$USER_NAME/.config/systemd/user/hermes-gateway-companion.service" <<EOF
[Unit]
Description=Hermes Gateway - companion
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=/home/hermes
Environment=PATH=/home/hermes/.local/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/home/hermes/.local/bin/hermes -p companion gateway run
Restart=always
RestartSec=5

[Install]
WantedBy=default.target
EOF
chown "$USER_NAME:$USER_NAME" "/home/$USER_NAME/.config/systemd/user/hermes-gateway-companion.service"

echo "== 8. 启动 companion gateway =="
sudo -u "$USER_NAME" XDG_RUNTIME_DIR="$RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="$BUS" systemctl --user daemon-reload
sudo -u "$USER_NAME" XDG_RUNTIME_DIR="$RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="$BUS" systemctl --user enable hermes-gateway-companion.service
sudo -u "$USER_NAME" XDG_RUNTIME_DIR="$RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="$BUS" systemctl --user restart hermes-gateway-companion.service

echo "== 9. 验证 Docker =="
sudo -u "$USER_NAME" docker run --rm "$IMG" python --version

echo "== 10. 验证 gateway 进程组 =="
sleep 2
pid=$(pgrep -u "$USER_NAME" -f "hermes.*companion.*gateway run|hermes.*-p companion.*gateway run|hermes.*--profile companion.*gateway run" | head -1 || true)
if [ -z "$pid" ]; then
  echo "没找到 companion gateway 进程。"
  sudo -u "$USER_NAME" XDG_RUNTIME_DIR="$RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="$BUS" systemctl --user status hermes-gateway-companion.service --no-pager -l
  exit 1
fi

echo "PID=$pid"
grep -E "^(Uid|Gid|Groups):" "/proc/$pid/status"

echo "== 11. 服务状态 =="
sudo -u "$USER_NAME" XDG_RUNTIME_DIR="$RUNTIME_DIR" DBUS_SESSION_BUS_ADDRESS="$BUS" systemctl --user status hermes-gateway-companion.service --no-pager -l
'

评论