NAS 代理网关部署(mihomo)+ Hermes 容器接入
NAS 代理网关部署(mihomo)+ Hermes 容器接入
- 日期:2026-10-20
- 目标机:群晖 DS920+(DSM 7.2.2,Docker 24.0.2 / Compose v2.20.1)
- 目的:让 NAS 及其容器、局域网设备能通过境外 VPS 访问 Google / GitHub / OpenAI / Gemini 等
- 关键词:群晖、mihomo、Clash.Meta、Hiddify、代理网关、白名单分流、HTTP_PROXY
一、结论先行:NAS 上该装什么
最初的疑问是「Hiddify 如何在 NAS 中安装」。查证后的结论分两半:
| 方案 | 判断 | 原因 |
|---|---|---|
| Hiddify Manager(服务端面板)装 NAS | ❌ 不可行 | ①官方一键脚本依赖 Ubuntu/Debian + systemd + apt,DSM 两者都没有 ②手工 compose 要求 network_mode: host + privileged + 绑定 80/443,与 DSM nginx 硬冲突 ③它是出口节点,装在境内 NAS 上出海毫无意义 ④官方文档明确要求整机专用 |
| mihomo(客户端内核)装 NAS | ✅ 采用 | 只开 7890 端口,不动 DSM 网络栈,不冲突任何既有服务 |
Hiddify Manager 的正确落点是境外 VPS(Ubuntu 22.04 + 公网独立 IP + 整机专用),与 NAS 无关。 NAS 侧是「客户端」角色,与 Windows 上的 Hiddify 客户端是两条完全独立的链路——Windows 关机不影响 NAS 上网。
二、最终架构
境外 VPS(订阅来源 liangxin.xyz)
│
│ ① 每小时拉一次订阅(base64/Clash 格式,mihomo 原生解析,不经第三方转换)
▼
NAS: mihomo 容器 ── 7890 (HTTP+SOCKS5 mixed) ── 9090 (控制 API)
│
├──► Hermes 容器(Gemini / OpenAI / Anthropic / GitHub 出海)
├──► 局域网设备(手机 / 平板 / 电视,Windows 关机也可用)
└──► DSM 系统 / 套件中心(控制面板 → 网络 → 代理服务器)
分流决策在 mihomo 侧完成:白名单域名走 VPS,其余(含全部内网)直连
关键设计:白名单制分流。默认 MATCH,DIRECT,只有 google / github / ghcr.io / huggingface / openai / anthropic / udemy 等明确需要出海的域名进 PROXY 组。好处是 VPS 抖动、换节点、订阅失效时,immich / mysite / open-webui / hermes / searxng 等内网服务完全无感。不要用全局模式,那是 NAS 装代理最常见的自毁姿势。
三、环境事实(本次实测,可复用于后续排障)
| 项目 | 实测结果 |
|---|---|
/dev/net/tun |
不存在 → DSM 内核未开 tun 模块,TUN/透明代理不可用,只能用 mixed-port |
| Docker Hub 直连 | 超时(registry-1.docker.io Client.Timeout exceeded) |
| 可用镜像源 | docker.1ms.run ✅、docker.xuanyuan.me ✅、dockerproxy.net ✅ |
| 不可用镜像源 | docker.m.daocloud.io ❌ 有白名单限制(this image is not in the allowlist) |
| NAS 直连出口 IP | 183.6.x.x / 广东电信 |
| 代理出口 IP | 211.20.x.x / 台湾(url-test 自动选最快) |
| 磁盘 | /volume2 3.5T,用 973G,剩余 2.6T |
镜像拉取的正确姿势(先拉镜像源、再打回标准名):
sudo /usr/local/bin/docker pull docker.1ms.run/metacubex/mihomo:latest
sudo /usr/local/bin/docker tag docker.1ms.run/metacubex/mihomo:latest metacubex/mihomo:latest
四、部署步骤(可复现)
1. 目录与文件
NAS: /volume2/docker/mihomo/
├── docker-compose.yml
├── config.yaml
└── providers/hiddify.yaml ← 由 mihomo 自动生成,不要手工改
本地: D:\Code\20261020_mihomo-nas\ ← 留底(docker-compose.yml / config.yaml)
2. docker-compose.yml
name: mihomo
services:
mihomo:
image: metacubex/mihomo:latest
container_name: mihomo
restart: always
ports:
- "7890:7890" # mixed-port: HTTP + SOCKS5
- "9090:9090" # external-controller(面板/API)
volumes:
- /volume2/docker/mihomo:/root/.config/mihomo
environment:
- TZ=Asia/Shanghai
dns:
- 223.5.x.x
- 119.29.x.x
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
3. config.yaml(核心片段)
mixed-port: 7890 # HTTP + SOCKS5 共用端口
allow-lan: true
bind-address: '*'
mode: rule
external-controller: 0.0.0.0:9090
unified-delay: true
tcp-concurrent: true
profile:
store-selected: true
proxy-providers:
hiddify:
type: http
url: "https://liangxin.xyz/api/v1/liangxin?OwO=<TOKEN>" # 订阅链接,token 见 NAS 配置文件
interval: 3600
path: ./providers/hiddify.yaml
# 订阅里夹带的信息节点(剩余流量/套餐到期)不是真节点,排除掉避免污染测速组
exclude-filter: "(剩余流量|套餐到期|官网|网址|群组|公告|过期|Expire|Traffic)"
health-check:
enable: true
url: https://www.gstatic.com/generate_204
interval: 300
proxy-groups:
- name: PROXY
type: url-test
use: [hiddify]
url: https://www.gstatic.com/generate_204
interval: 300
tolerance: 50
- name: MANUAL
type: select
use: [hiddify]
dns:
enable: true
enhanced-mode: normal
nameserver: [223.5.x.x, 119.29.x.x]
fallback: [8.8.x.x, 1.1.x.x]
rules:
- IP-CIDR,127.0.0.0/8,DIRECT,no-resolve
- IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
- IP-CIDR,172.16.0.0/12,DIRECT,no-resolve
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
- DOMAIN-SUFFIX,google.com,PROXY
- DOMAIN-SUFFIX,googleapis.com,PROXY # Gemini
- DOMAIN-SUFFIX,gstatic.com,PROXY
- DOMAIN-SUFFIX,googlevideo.com,PROXY
- DOMAIN-SUFFIX,youtube.com,PROXY
- DOMAIN-SUFFIX,github.com,PROXY
- DOMAIN-SUFFIX,githubusercontent.com,PROXY
- DOMAIN-SUFFIX,ghcr.io,PROXY
- DOMAIN-SUFFIX,docker.io,PROXY
- DOMAIN-SUFFIX,huggingface.co,PROXY
- DOMAIN-SUFFIX,hf.co,PROXY
- DOMAIN-SUFFIX,openai.com,PROXY
- DOMAIN-SUFFIX,anthropic.com,PROXY
- DOMAIN-SUFFIX,gemini.google.com,PROXY
- DOMAIN-SUFFIX,cloudflare.com,PROXY
- DOMAIN-SUFFIX,telegram.org,PROXY
- DOMAIN-SUFFIX,x.com,PROXY
- DOMAIN-SUFFIX,wikipedia.org,PROXY
- DOMAIN-SUFFIX,udemy.com,PROXY
- MATCH,DIRECT # 想临时全局,把这里改成 MATCH,PROXY
4. 启动
ssh nas "cd /volume2/docker/mihomo && sudo -n /usr/local/bin/docker compose -p mihomo up -d"
五、验证记录(实测数据)
| 验证项 | 命令 | 结果 |
|---|---|---|
| 容器状态 | docker ps |
mihomo Up ... 0.0.0.0:7890->7890, 0.0.0.0:9090->9090 ✅ |
| Google 连通 | curl -x http://127.0.0.1:7890 https://www.google.com/generate_204 |
204 / 0.13s ✅ |
| GitHub | curl -x ... https://github.com |
200 ✅ |
| YouTube | curl -x ... https://www.youtube.com |
200 ✅ |
| 出口 IP(经代理) | curl -x ... https://cloudflare.com/cdn-cgi/trace |
ip=211.20.x.x loc=TW colo=SIN ✅ |
| 出口 IP(直连) | curl https://cloudflare.com/cdn-cgi/trace |
ip=183.6.x.x loc=CN ✅ 两者不同即分流生效 |
| 节点数 | curl -s http://127.0.0.1:9090/proxies/PROXY |
53 个(已排除 2 个假节点) ✅ |
| 局域网可用 | Windows: curl -x http://192.170.x.x:7890 ... |
204 / 0.69s ✅ |
⚠ 踩过的坑:别用
ifconfig.me验证代理出口 IP。它不在白名单里,会走MATCH,DIRECT,看到「代理 IP == 直连 IP」其实是规则在正常工作,不是代理失败。验证出口要用白名单内的域名(cloudflare.com/cdn-cgi/trace最方便)。
六、消费端接入
1. 局域网设备(手机 / 平板 / 电视)
代理服务器 192.170.x.x,端口 7890,HTTP 与 SOCKS5 都支持(依赖 allow-lan: true)。Windows 关机后依然可用。
2. 单个 Docker 容器
environment:
- HTTP_PROXY=http://192.170.x.x:7890
- HTTPS_PROXY=http://192.170.x.x:7890
- http_proxy=http://192.170.x.x:7890
- https_proxy=http://192.170.x.x:7890
- NO_PROXY=localhost,127.0.0.1,::1,192.168.0.0/16,192.170.x.x/24,<其他容器名>,host.docker.internal
- no_proxy=<同 NO_PROXY>
必须成对写大写+小写(不同 HTTP 库读的变量名不同),NO_PROXY 一个都不能漏:容器名经代理无法解析,内网 IP 会白白绕一圈。
3. Hermes 容器(本次已接入)
改 /volume2/docker/hermes/compose.yaml,在 environment: 下增加上面那 6 行(NO_PROXY 里额外包含 ollama,mihomo,hermes,searxng,immich_server,mysite-web-1,vanblog,crm-django-app-1),然后:
cd /volume2/docker/hermes && sudo -n /usr/local/bin/docker compose -p herms up -d
⚠ 项目名必须是 herms(历史拼写错误),否则报 container name already in use。
为什么不是「让 Hermes 全走代理」:环境变量把所有请求交给 mihomo,再由 mihomo 的白名单决定谁出海。所以 Gemini / OpenAI / Anthropic / GitHub 出 VPS,而 OpenCode Go、DeepSeek、Tavily、国内资源仍直连——默认模型链路零改动。
Hermes 接入后的验证结果:
| 验证项 | 结果 |
|---|---|
| 容器内出口 | ip=211.20.x.x loc=TW ✅ |
| Gemini API | 404(真实响应,非超时)✅ |
| OpenAI API | 401(真实响应,缺 key 而已)✅ |
| Tavily | 200 直连未受影响 ✅ |
| 内网 mysite 8084 | 200 ✅ |
| 内网 Immich 2283 | 200 ✅ |
| cron 调度器 | 已加载(monthly-work-summary 可见)✅ |
回滚(备份 /volume2/docker/hermes/compose.yaml.bak-20261020):
ssh nas "cat /volume2/docker/hermes/compose.yaml.bak-20261020 > /tmp/c.yaml && \
sudo -n /usr/local/bin/docker cp /tmp/c.yaml mihomo:/tmp/c.yaml && \
sudo -n /usr/local/bin/docker cp mihomo:/tmp/c.yaml /volume2/docker/hermes/compose.yaml && \
cd /volume2/docker/hermes && sudo -n /usr/local/bin/docker compose -p herms up -d"
4. DSM 系统 / 套件中心
控制面板 → 网络 → 常规 → 代理服务器 → 手动 → 192.170.x.x:7890(HTTP)。
- 对套件中心、部分套件生效
- 对 Docker 拉镜像不生效(Docker 是独立守护进程,需改
dockerd.json并重启 Container Manager,有中断风险,不建议动;用镜像源即可) - 不要把所有流量都塞给它,QuickConnect / 远程访问走代理反而更慢
七、运维命令
## 状态
sudo -n /usr/local/bin/docker ps --filter name=mihomo
## 改配置后生效
sudo -n /usr/local/bin/docker restart mihomo
## 看日志(含节点拉取情况)
sudo -n /usr/local/bin/docker logs --tail 30 mihomo
## 当前选中节点 / 全部节点
curl -s http://127.0.0.1:9090/proxies/PROXY
curl -s http://127.0.0.1:9090/providers/proxies/hiddify
## 订阅落盘位置(mihomo 自动生成)
sudo -n /usr/local/bin/docker exec mihomo ls -l /root/.config/mihomo/providers/
订阅每小时自动更新一次;机场按 UA 返回不同格式(curl 拿到 base64、mihomo 拿到 Clash YAML),两种 mihomo 都能吃。日志里 get subscription-userinfo: failed to parse value '' 是机场没提供流量头,无害。
八、坑清单(本次踩到的)
- Hiddify Manager 不能装 DSM:无 systemd/apt;
network_mode: host抢 80/443(DSM nginx 已占);官方要求整机专用。 - DSM 没有
/dev/net/tun:TUN/透明代理方案一律不可行,别浪费时间。 - Docker Hub 直连超时:用
docker.1ms.run/docker.xuanyuan.me/dockerproxy.net;docker.m.daocloud.io有白名单限制拉不到任意镜像。 - 订阅夹带假节点:
剩余流量:xxx、套餐到期:xxx会被当成节点参与测速,必须exclude-filter排除。 - 别用 ifconfig.me 验出口 IP:不在白名单会走直连,误判成「代理没生效」。
NO_PROXY必须写全:漏了容器名/内网网段,容器间调用会被绕到代理并解析失败。/volume2/docker/hermes/compose.yaml是 root:root 644,fei 无权覆写。正确姿势两步:cat 本地 > /tmp/x→docker cp /tmp/x mihomo:/tmp/x→docker cp mihomo:/tmp/x 宿主机路径。docker run -i ... sh -c 'cat > file'经 ssh 管道会挂住不退出(本次超时两次),不要用这条写文件,改用上面的docker cp两步法。- 改 Hermes compose 后必须
-p herms up -d(项目名是历史 typo)。 - 重建 Hermes 容器会清空
/root临时层(数据在/opt/data卷里,无影响);会短暂中断网关与 cron,避开整点任务时间窗。
九、NAS Hermes 接 OpenCode Go 的可行性实测(2026-10-20)
问题:「目前 NAS 系统能不能用 Hermes 连接 OpenCode 的 AI 模型?」 结论:网络与账号层面完全可行,但 NAS 上的 Hermes 目前没配 OpenCode——它用的是 DeepSeek 官方 API。
当前 NAS 容器配置(/opt/data/config.yaml):
model:
default: deepseek-v4-flash
provider: deepseek
base_url: https://api.deepseek.com/v1
/opt/data/.env 里只有 DEEPSEEK_API_KEY,没有任何 OpenCode 凭据。容器内 Hermes 版本 v0.17.0(与 Windows 同版本)。
实测矩阵(在 hermes 容器内用 Python 发起真实请求):
| 测试 | 结果 |
|---|---|
NAS 宿主直连 opencode.ai/zen/go/v1/models |
200,1.01s,DNS 9ms → 未被墙,不需要代理 |
| 同上经 mihomo 代理 | 200,1.01s |
容器内 POST /chat/completions + key + x-opencode-session |
200,deepseek-v4.1-flash 真实回复 "OK" ✅ |
容器内同上但去掉 x-opencode-session |
400 MissingSessionID(与 Windows 同一坑) |
要点:
- v0.17.0 不内建发送 x-opencode-session,缺了直接 400,必须靠 model.default_headers 补(和 Windows 上的做法一致)。
- 容器内 python-urllib 裸访问 /models 会 403——那是 Cloudflare 拦默认 User-Agent,不是网络问题;换成 hermes-agent/0.17.0 后正常。
- GET /models 不校验 key(空 key 也返回列表),所以不能用它判断凭据是否有效,必须打 /chat/completions。
OpenCode Go 订阅当前暴露 37 个模型:
deepseek-v4.1-flash / deepseek-v4-pro / glm-5.1 / 5.2 / 5.3 / 5.3-flash
grok-4.6 / 4.7 / gpt-5.6-luna / gpt-6-luna / claude-haiku-5-5
kimi-k2.6 / kimi-k2.7-code / kimi-k3 / minimax-m2.5 / m2.7 / m3 / m3.1-flash-free
qwen3.6-plus / qwen3.7-plus / qwen3.8-max / qwen3.8-flash
mimo-v2.5 / v2.5-pro / v2.6-flash / v2.6-pro / hy3 / hy4-preview
longcat-2.0 / longcat-2.5-preview-free / step-5-preview / beam-free
gemini-4-argon-contributor / muse-spark-1.2-contributor / 1.3-contributor / omen-alpha
要接通需改三处(本次未执行,等确认):
/opt/data/.env增加OPENCODE_GO_API_KEYconfig.yaml的model:段改为provider: opencode-go、base_url: https://opencode.ai/zen/go/v1、api_mode: chat_completions、default_headers: { x-opencode-session: hermes-nas-opencode-go }docker restart hermes
风险提示:
- key 与 Windows 共用,订阅制下两边同时跑会互相挤额度/并发
- NAS 三个 cron(AI资讯日报 7:00 / 周报周日 20:00 / 月报每月1号9:00)会跟着换模型,换完要盯一次实跑
- *-free 系列(beam-free、m3.1-flash-free、longcat-2.5-preview-free)在 Hermes 内大概率 403,只在 OpenCode 官方客户端内可用
十、遗留问题(本次发现,未处理)
-
Hermes API Server 起不来
ERROR [Api_Server] Refusing to start: API_SERVER_KEY is required for the API server, including loopback-only binds on 0.0.0.0.compose 里API_SERVER_ENABLED=true但没配API_SERVER_KEY,8642 端口只是 docker 转发在监听,里面没有服务。 修法:compose 里加- API_SERVER_KEY=<强随机串>后重建。 -
cron 任务 monthly-work-summary 空跑一个月
Script not found: /opt/data/scripts/run-monthly-summary.sh要么补脚本,要么删任务。
十一、变更清单
| 位置 | 变更 |
|---|---|
/volume2/docker/mihomo/ |
新建:docker-compose.yml、config.yaml、providers/ |
metacubex/mihomo:latest |
从 docker.1ms.run 拉取(103MB),已打标准标签 |
/volume2/docker/hermes/compose.yaml |
增加 6 个代理环境变量;原文件备份为 compose.yaml.bak-20261020 |
/volume2/docker/hermes/compose.yaml 属主 |
root:root 644(保持原样,如需编辑走 docker cp 中转) |
D:\Code\20261020_mihomo-nas\ |
本地留底:docker-compose.yml、config.yaml、hermes-compose.yaml、README.md |
凭据说明:订阅 token 仅存在于 NAS /volume2/docker/mihomo/config.yaml(运行必需)与本地 D:\Code\20261020_mihomo-nas\config.yaml,未写入任何笔记、记忆或云端文档。