目标是在自己的电脑打开 http://127.0.0.1:9119,访问临时 Linux Docker 服务器上的 Hermes 控制台。跨越公网的只有已有的 SSH 连接,不需要域名、公网控制台端口或反向代理。
执行状态:尚未在 VPS 上运行。 独立的恢复 API 实验未测试本篇控制台配置;尚无控制台、隧道、公网探测、重启成功记录或部署截图。本文给出可执行步骤和验收条件,不能当作实测教程。
本阶段只对全新的合成数据目录启动控制台,不启动 gateway、调度器、模型对话或消息平台。本阶段不要打开对话会话、启动 gateway 或添加定时任务。页面能打开,不代表 Agent 能完成任务。接入真实数据或工具前,请继续做隔离恢复演练。
1. 先确定边界
使用已经授权的临时 Linux 主机、现有非 root SSH 账户(UID/GID 为 1–65534)、Docker Engine 和 Compose v2。不要挂载生产 Hermes 目录。为镜像预留磁盘空间,并准备好停止实验的方法。安装软件、加入 Docker 组、修改防火墙、配置凭据和重启都需要明确决定,本文不会代为执行。
三个地址不能混淆:
- 容器内:Hermes 监听
0.0.0.0:9119,Docker 才能通过桥接网络转发过去 - 服务器上:Docker 只发布
127.0.0.1:9119 - 自己的电脑上:SSH 只监听
127.0.0.1:9119,目标也是服务器的回环地址
Docker 的回环映射不会把应用自身的监听地址变成回环。 当前 Hermes 对非回环监听要求身份验证,旧的 --insecure 已不能绕过它。反过来,把桥接容器内的监听改成 127.0.0.1,普通端口转发就无法连接。不要用 9119:9119 全接口发布或关闭认证来“修复”。
原生安装、直接监听主机回环是另一种方案,不是本篇 Docker 方案。host 网络模式同样改变隔离边界,不能悄悄替换。
2. 新建状态目录,记录准确镜像
以下命令在获授权的服务器执行。目录必须不存在,以免覆盖真实配置;后续服务器命令继续使用这个 shell 和目录。
set -eu
umask 077
export LAB="$HOME/deploymanual-private-dashboard"
test ! -e "$LAB"
mkdir -p "$LAB/state" "$LAB/evidence"
cd "$LAB"
docker version > evidence/docker-version.txt
docker compose version > evidence/compose-version.txt
uname -sm > evidence/host.txt
cat /etc/os-release >> evidence/host.txt
docker pull nousresearch/hermes-agent:stable
export HERMES_IMAGE="$(docker image inspect nousresearch/hermes-agent:stable --format '{{index .RepoDigests 0}}')"
case "$HERMES_IMAGE" in
nousresearch/hermes-agent@sha256:*) ;;
*) echo 'Unexpected image identity; stop' >&2; exit 1 ;;
esac
printf '%s\n' "$HERMES_IMAGE" > evidence/image-digest.txt
export HERMES_UID="$(id -u)" HERMES_GID="$(id -g)"
test "$HERMES_UID" -ge 1 && test "$HERMES_UID" -le 65534
test "$HERMES_GID" -ge 1 && test "$HERMES_GID" -le 65534
docker run --rm "$HERMES_IMAGE" --version > evidence/hermes-version.txt
docker run --rm "$HERMES_IMAGE" dashboard --help > evidence/dashboard-help.txt
先检查所选版本的帮助。stable 只是发现渠道,实际固定的是解析出的 digest;不能把猜测的 digest 写成实测版本。本阶段没有模型 API 请求,但拉取镜像仍消耗磁盘和流量。
3. 私下完成认证配置,再启动服务
操作者先明确同意并亲自完成控制台认证配置。不要把密码、密码散列或签名密钥放进聊天、命令参数、截图或 Git。关闭录屏,在私密终端使用该版本的交互式设置:
docker run -it --rm \
-e HERMES_UID -e HERMES_GID \
--mount "type=bind,src=$LAB/state,dst=/opt/data" \
"$HERMES_IMAGE" dashboard --host 0.0.0.0 --port 9119 --no-open
设置容器不发布任何主机端口。前面的 umask 限制新实验目录的权限,保存的认证配置也应保持私密。当前上游在没有认证提供者时会提示交互式用户名/密码设置;由操作者完成,然后 Ctrl+C 停止临时进程。如果固定版本没有此提示,先停止并阅读对应的认证文档,不要把明文密码填进示例。确认配置保存在新状态目录中。当前交互流程会将签名密钥与密码散列一起持久保存,因此应能保留重启后的会话;若采用其他配置且没有固定签名密钥,进程临时密钥可能导致重新登录。控制台凭据不是模型服务商 API 密钥。
在 $LAB 中保存以下 compose.yaml。它只运行控制台,不运行 gateway run:
services:
dashboard:
image: ${HERMES_IMAGE:?Set the recorded image digest}
command: ["dashboard", "--host", "0.0.0.0", "--port", "9119", "--no-open"]
restart: unless-stopped
environment:
HERMES_UID: ${HERMES_UID:?Set the operator UID}
HERMES_GID: ${HERMES_GID:?Set the operator GID}
volumes:
- ./state:/opt/data
ports:
- "127.0.0.1:9119:9119"
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
docker compose -p deploymanual-private config --quiet
docker compose -p deploymanual-private up -d
docker compose -p deploymanual-private ps
CID="$(docker compose -p deploymanual-private ps -q dashboard)"
test -n "$CID"
docker inspect --format '{{json .HostConfig.PortBindings}}' "$CID" \
> evidence/port-bindings.json
ss -ltn > evidence/listeners.txt
curl --noproxy '*' --fail --silent --show-error \
--retry 10 --retry-connrefused --retry-delay 2 --retry-max-time 45 \
--connect-timeout 2 --max-time 5 \
http://127.0.0.1:9119/api/status > evidence/dashboard-status.json
HTTP 命令会在有上限的时间内重试启动期间的连接拒绝;持续失败就停止检查。以下是待验证条件,不是实际输出:只有一个运行中的控制台服务,映射是 127.0.0.1:9119,状态显示 auth_required: true 和已配置的认证提供者。需要时在本地查看启动日志,保存或分享前去除敏感内容。容器运行但控制台失败仍算失败。ss 可能看不到 Docker NAT 发布,必须同时检查 Docker 映射。
4. 从自己的电脑建立隧道
使用已核验的 SSH 主机别名,不关闭主机密钥检查。先确认本机端口空闲,在独立终端保持此命令运行:
ssh -a -N -T \
-o ExitOnForwardFailure=yes \
-o ServerAliveInterval=30 -o ServerAliveCountMax=3 \
-L 127.0.0.1:9119:127.0.0.1:9119 YOUR_EXISTING_SSH_ALIAS
将 YOUR_EXISTING_SSH_ALIAS 替换成获授权的主机别名。在自己电脑的浏览器打开 http://127.0.0.1:9119,私下登录,确认显示的是实验用空白配置。另开无痕窗口,检查未登录时无法读取受保护的会话数据。/api/status 可能是公开状态接口,能访问它不能证明敏感接口泄露。
ExitOnForwardFailure 能发现转发监听创建失败,不能证明远端控制台健康。本机 9119 已占用时,关闭冲突程序,或明确选择另一个本地转发端口并记录。不要随意修改公共 URL 或代理设置来绕过来源检查。
5. 从外部证明公网入口关闭
关闭隧道后,从 VPS 之外的另一台机器,只测试自己获授权的主机。填入实际公网 IPv4;如果分配了公网 IPv6,也必须测试:
curl --noproxy '*' -4 --connect-timeout 5 --max-time 8 \
-o /dev/null -w 'HTTP %{http_code}\n' http://YOUR_SERVER_IPV4:9119/
curl --noproxy '*' -6 --connect-timeout 5 --max-time 8 \
-o /dev/null -w 'HTTP %{http_code}\n' 'http://[YOUR_SERVER_IPV6]:9119/'
分别执行,记录时间、退出码和结果。预期是拒绝连接或超时,而不是 HTTP 响应。即使只返回登录页或 401,也说明端口可达,私密性验收失败;先停止实验控制台再排查,不要修改防火墙来伪造通过结果。
单独一个超时并不充分:还要通过获授权的 SSH 路径确认同一主机在线、服务器回环请求成功,并核对 Docker 映射。在服务器自身执行不算外部测试。没有分配 IPv6 时标记“不适用”;有 IPv6 却没有可用的外部探测时标记“未测”。Docker 28 之前有文档说明的同二层网络回环发布例外,应使用维护中的版本,并测试相关私网路径。
6. 分别核验容器重启和主机重启
在状态目录写一个无害文件并记录散列,本步骤不创建定时任务或接入工具。
printf 'deploymanual dashboard persistence marker\n' > state/reboot-marker.txt
sha256sum state/reboot-marker.txt > evidence/reboot-marker.sha256
cat /proc/sys/kernel/random/boot_id > evidence/boot-id-before.txt
docker compose -p deploymanual-private restart dashboard
sha256sum -c evidence/reboot-marker.sha256
curl --noproxy '*' --fail --silent --show-error \
--retry 10 --retry-connrefused --retry-delay 2 --retry-max-time 45 \
--connect-timeout 2 --max-time 5 \
http://127.0.0.1:9119/api/status > evidence/status-after-container-restart.json
容器重启不等于主机重启。安排获批准的维护窗口,由操作者按正常管理流程重启临时服务器。SSH 恢复后,先不要手动运行 compose up,否则无法证明自动恢复。回到 $LAB;需要使用 Compose 时重新加载非秘密的镜像、UID/GID 值,然后记录:
cd "$HOME/deploymanual-private-dashboard"
cat /proc/sys/kernel/random/boot_id > evidence/boot-id-after.txt
if cmp -s evidence/boot-id-before.txt evidence/boot-id-after.txt; then
echo 'Boot ID unchanged: host reboot not demonstrated' >&2
exit 1
fi
docker ps --filter label=com.docker.compose.project=deploymanual-private \
> evidence/containers-after-reboot.txt
sha256sum -c evidence/reboot-marker.sha256
curl --noproxy '*' --fail --silent --show-error \
--retry 10 --retry-connrefused --retry-delay 2 --retry-max-time 45 \
--connect-timeout 2 --max-time 5 \
http://127.0.0.1:9119/api/status > evidence/status-after-host-reboot.json
要求 Docker 自动启动、控制台响应且仍要求认证、文件散列一致。重新建立 SSH 隧道,必要时重新登录,并重复外部 IPv4/IPv6 探测。分别记录 SSH 恢复时间和控制台就绪时间。unless-stopped 容器被手动停止后保持停止是预期行为,不应误报为崩溃恢复测试。
证据和停止条件
保留真实 digest、版本、去敏后的端口映射、状态、外部探测、前后 boot ID 和文件散列。截图只能来自真正执行过的控制台及命令,隐藏凭据、Cookie 和账户信息;示意终端图片不能替代证据。
任何一项失败都先保留失败记录,不继续加功能。结束本阶段时,用已记录的配置停止这个 Compose 项目,关闭 SSH 隧道。保留合成状态供恢复检查,不自动执行 down -v 或删除目录。停止 VPS 不一定停止计费。
升级为“实测操作教程”仍需: 干净主机完整执行、真实登录与隧道交互、独立外部探测、主机重启证据,以及中英文结果一致。完整 Agent 执行、调度恢复、模型费用、备份加密和生产安全加固仍是独立验收项。