NAS 代理网关部署(mihomo)+ Hermes 容器接入

写于 2026-10-20

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 '' 是机场没提供流量头,无害。


八、坑清单(本次踩到的)

  1. Hiddify Manager 不能装 DSM:无 systemd/apt;network_mode: host 抢 80/443(DSM nginx 已占);官方要求整机专用。
  2. DSM 没有 /dev/net/tun:TUN/透明代理方案一律不可行,别浪费时间。
  3. Docker Hub 直连超时:用 docker.1ms.run / docker.xuanyuan.me / dockerproxy.net;docker.m.daocloud.io 有白名单限制拉不到任意镜像。
  4. 订阅夹带假节点:剩余流量:xxx、套餐到期:xxx 会被当成节点参与测速,必须 exclude-filter 排除。
  5. 别用 ifconfig.me 验出口 IP:不在白名单会走直连,误判成「代理没生效」。
  6. NO_PROXY 必须写全:漏了容器名/内网网段,容器间调用会被绕到代理并解析失败。
  7. /volume2/docker/hermes/compose.yaml 是 root:root 644,fei 无权覆写。正确姿势两步:cat 本地 > /tmp/x → docker cp /tmp/x mihomo:/tmp/x → docker cp mihomo:/tmp/x 宿主机路径。
  8. docker run -i ... sh -c 'cat > file' 经 ssh 管道会挂住不退出(本次超时两次),不要用这条写文件,改用上面的 docker cp 两步法。
  9. 改 Hermes compose 后必须 -p herms up -d(项目名是历史 typo)。
  10. 重建 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

要接通需改三处(本次未执行,等确认):

  1. /opt/data/.env 增加 OPENCODE_GO_API_KEY
  2. config.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 }
  3. 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 官方客户端内可用

十、遗留问题(本次发现,未处理)

  1. 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=<强随机串> 后重建。

  2. 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,未写入任何笔记、记忆或云端文档。