Hermes-WebUI 在 WSL2 下的部署与局域网访问实践

本文要回答的核心问题

如何在 WSL2 环境中正确部署 Hermes-WebUI,并实现稳定的局域网访问能力?

这不是一个“能不能跑起来”的问题,而是一个典型的三层结构问题:

  • 应用层:Hermes-WebUI 是否正常启动
  • 运行层:WSL2 是否正确暴露服务
  • 网络层:局域网设备是否可达

很多失败都发生在第三层,而不是第一层。


第一部分:跨界参照 —— “后台舞台上的戏剧调度系统”

本节要回答的问题

为什么 Hermes-WebUI 在 WSL2 下的部署,本质像一个复杂的后台舞台调度系统?

如果用一个非技术领域的参照来理解 WSL2 + WebUI 的组合,可以把它类比成:

一场戏剧演出,但舞台分成三层:后台(WSL)、控制室(Windows)、观众席(局域网设备)

三层结构对应关系

系统层级 类比角色 实际职责
WSL2 后台舞台 Hermes-WebUI 运行环境
Windows 控制室 网络转发与端口管理
局域网设备 观众席 访问 WebUI 的终端

问题就出在一个经典误解:

“后台演员已经在演出 = 观众就一定看得到”

现实是:

  • 后台在演(WSL 内服务启动)
  • 控制室没开门(Windows 未转发端口)
  • 观众依然看不到(局域网无法访问)

第二部分:部署路径拆解 —— Hermes-WebUI 在 WSL2 的真实运行结构

本节要回答的问题

Hermes-WebUI 在 WSL2 中到底是怎么启动和运行的?

从你提供的日志可以还原完整结构:

repo root   : /mnt/f/WSL/hermes-webui
agent dir   : /root/.hermes/hermes-agent
python      : /root/.hermes/hermes-agent/venv/bin/python
state dir   : /root/.hermes/webui
workspace   : /root/.hermes/webui/workspace
host:port   : 0.0.0.0:8787
config file : /root/.hermes/config.yaml

这说明系统被拆成四个核心模块:


1. 代码目录(Repo Root)

/mnt/f/WSL/hermes-webui

这是你的“项目本体”,位于 Windows 挂载盘。

特点

  • 属于 WSL 与 Windows 共享文件系统
  • IO 性能略低,但方便管理

2. Agent 环境(隔离 Python)

/root/.hermes/hermes-agent/venv/bin/python

本质

这是 Hermes 自动创建的虚拟运行环境。

作用

  • 隔离依赖
  • 避免污染系统 Python
  • 固定运行版本

3. 状态目录(运行核心)

/root/.hermes/webui

内容可能包括:

  • session 状态
  • cache
  • workspace
  • runtime metadata

这是“WebUI 的大脑状态区”。


4. 网络入口(最关键)

0.0.0.0:8787

这是局域网访问的唯一前提。


第三部分:启动行为分析 —— 为什么“看似失败但其实成功”

本节要回答的问题

为什么你会看到 bootstrap failed,但服务其实正常运行?

关键日志:

ERROR: Web UI did not become healthy

但后续又出现:

/health status 200
/api/... status 200
Hermes Web UI listening on http://0.0.0.0:8787

结论:

这是一个“启动探测机制误判”。


启动流程真实逻辑:

Step 1:启动 Web 服务

✔ 进程已启动

Step 2:bootstrap 等待 health check

❗ 默认等待窗口较短

Step 3:health endpoint 实际返回正常

✔ /health = 200

Step 4:bootstrap 判定超时(误判)

❗ 输出 error


作者视角反思

这个问题本质不是“系统坏了”,而是“观测者太急”。

很多 WebUI 框架都会出现类似问题:

  • 服务已经活着
  • 但监控脚本还没反应过来

这在分布式系统里属于典型的“启动一致性错觉”。


第四部分:WSL2 网络问题的本质

本节要回答的问题

为什么 WSL2 的 Web 服务默认无法被局域网访问?

WSL2 默认是:

NAT 网络 + 虚拟子网隔离

结构如下:

局域网设备
     ↓
Windows IP(可访问)
     ↓
WSL2 NAT(默认不可直连)
     ↓
Hermes-WebUI

核心问题不是 WebUI,而是“网络边界”

即使你绑定:

0.0.0.0:8787

也只解决:

  • ✔ WSL 内部访问
  • ✔ Windows localhost 转发(部分情况)

但不保证:

  • ❌ 手机访问
  • ❌ 局域网设备访问

第五部分:完整局域网访问方案(工程级实践)

本节要回答的问题

如何让 Hermes-WebUI 真正被局域网设备访问?


Step 1:启动服务(必须绑定 0.0.0.0)

HERMES_WEBUI_HOST=0.0.0.0 HERMES_WEBUI_PORT=8787 ./start.sh

Step 2:确认监听

lsof -i :8787

目标结果:

LISTEN 0.0.0.0:8787

Step 3:获取 WSL IP(用于调试)

ip addr | grep inet

示例:

172.29.xx.xx

Step 4:Windows 端口转发(关键)

netsh interface portproxy add v4tov4 listenport=8787 listenaddress=0.0.0.0 connectport=8787 connectaddress=127.0.0.1

Step 5:防火墙放行

netsh advfirewall firewall add rule name="hermes-webui" dir=in action=allow protocol=TCP localport=8787

Step 6:访问路径

http://<Windows-IP>:8787

第六部分:安全机制与风险提示

本节要回答的问题

为什么 Hermes-WebUI 会提示 NO PASSWORD SET?

日志:

WARNING: Binding to 0.0.0.0 with NO PASSWORD SET

本质风险:

当绑定 0.0.0.0 时:

  • 同一局域网所有设备可访问
  • 包含文件系统能力
  • 包含 agent 执行能力

安全建议:

export HERMES_WEBUI_PASSWORD=your_password

或在 config.yaml 设置。


第七部分:三番式问题复盘(关键理解模型)

第一番:你以为的问题

“为什么我打不开局域网?”

第二番:真实问题

“WSL2 网络隔离 + 未做端口转发”

第三番:本质问题

“服务运行层与网络暴露层被混淆”


实用摘要(可操作清单)

  • Hermes-WebUI 必须绑定 0.0.0.0
  • WSL2 默认不等于局域网可达
  • Windows 需要 portproxy 转发
  • 防火墙必须放行端口
  • bootstrap error 可忽略(只要 health=200)

一页速览(One-page Summary)

  • 启动:HERMES_WEBUI_HOST=0.0.0.0 ./start.sh
  • 访问:http://Windows-IP:8787
  • WSL2:NAT 网络隔离
  • 必做:portproxy + firewall
  • 常见误判:bootstrap health timeout

FAQ(5–8条)

Q1:为什么 bootstrap 显示失败但还能访问?

因为 health check 超时误判,但服务已运行。

Q2:WSL2 可以直接局域网访问吗?

默认不可以,需要 Windows 转发。

Q3:必须用 0.0.0.0 吗?

是,否则只能本机访问。

Q4:为什么手机访问不到?

因为没有做 portproxy 或防火墙未放行。

Q5:8787 可以改吗?

可以,但需同步修改启动参数与转发规则。

Q6:是否安全?

不安全,必须设置 password。

Q7:为什么 Windows localhost 可以访问?

因为 WSL2 自动做了部分 NAT 映射。

Q8:推荐稳定方案?

Docker 或 Tailscale(避免 NAT 复杂性)。