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 pull 和 docker 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_DIR 和 DBUS_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
'