服务:mihomo 迁移评估(Docker → NixOS 原生,未实施)

结论

NixOS 有现成的 services.mihomo(nixpkgs 包版本与在跑的镜像同为 1.19.24),迁到原生 service 在技术上成立;但本机没有必须迁的理由,故仅留设计方案备查,不实施。原笔记(另一 AI 产出)的转换代码不能照抄:其中 dataDir 是凭空的选项、数据目录凭空、资源限额抄的是已废弃旧值,并会覆盖本机在用的 proxy.nix。

本机现状(实测)

维度本机实测值
代理栈rootless docker compose /s500data/docker/mihomo/docker-compose.yml;容器 clash(metacubex/mihomo v1.19.24) + clash-ui(metacubexd v1.247.0),docker compose ls 显示 running(2)
端口占用7890→7890(代理)、9091→9090(API)、9090→80(UI),均绑 0.0.0.0,持有者进程 rootlesskit
docker 形态docker info Root Dir = /home/cat/.local/share/docker;单位挂在 user systemd(systemctl --user 的 docker.service),没有 rootful docker.service
资源限制compose 现为 mem_limit 512M / cpus 2.0(原 256M/0.5 已按「半核是 TLS 解密+并发天花板」实测放宽,依据写在文件里)
TUNcompose 映射 /dev/net/tun + CAP_NET_ADMIN,但 data/config.yaml 里 tun.enable: false(注释:容器环境建议关闭)
防火墙modules/system/service/firewall.nix:trustedInterfaces = [ "lo" "wlp2s0" ],allowedTCPPorts 刻意留空(策略=按接口信任,不逐个开端口)
proxy.nixmodules/system/service/proxy.nix 已被本机占用:管 networking.proxy.noProxy/default(103 条国内直连域名的单一数据源)+ 生成 /etc/mihomo/cn-direct.yaml
nixpkgs 侧services.mihomo 可用(mihomo 包 1.19.24,与容器镜像同版);pkgs.metacubexd 1.245.1 存在(纯静态 pnpm generate 产物)
services.mihomo 选项enable / package / configFile / webui / extraOpts / tunMode / processesInfo —— 没有 dataDir
docker 上的其它栈mariadb(127.0.0.1:3306)也在同一 rootless docker 上

原笔记逐条裁决

#原笔记说法裁决依据
1services.mihomo.dataDir = dataDir;❌ 直接 eval 报错该选项不存在;模块实际用 -d /var/lib/private/mihomo(StateDirectory + DynamicUser)
2「把配置直接写入你的 modules/proxy.nix」❌ 会覆盖在用的模块本机该文件是代理分流单一数据源(noProxy + cn-direct.yaml),照抄即删掉该机制;应另开 mihomo.nix
3数据目录 /system/service/proxy-data❌ 路径不存在本机数据在 /s500data/docker/mihomo/data;且原生服务的 -d 是 /var/lib/private/mihomo,geo 文件必须放那里,否则 GEOSITE,cn / GEOIP,cn 要联网重下
4tunMode = true;(称「官方自动配置内核参数」)⚠️ 不符实况本机 tun.enable: false,走纯 7890 HTTP/SOCKS;tunMode 只给 CAP_NET_ADMIN 并放开 PrivateDevices/PrivateUsers,不替你开内核转发
5MemoryMax=256M / CPUQuota=50%⚠️ 抄的旧值会把已实测放宽的 512M/2.0 退回半核瓶颈
6networking.firewall.allowedTCPPorts = [7890 9091 9090] + enable = true⚠️ 与既定策略冲突本机按接口信任(含 wlp2s0)已覆盖同一效果;多模块叠写 firewall 配置易互相打架
7用 nginx 托管 pkgs.metacubexd(9090)⚠️ 多余services.mihomo.webui = pkgs.metacubexd 已内置 UI(-ext-ui,走 API 端口 /ui),不必新增守护进程
8external-controller: 0.0.0.0:9091✅ 对容器内原为 9090、靠 compose 映射到宿主 9091;原生须直接改配置。注意改后需 systemctl restart mihomo(配置文件不走热加载)
9metacubexd 首访手填 API 地址、存 localStorage✅ 对nixpkgs 的 metacubexd 无 docker 镜像那套启动时注入 DEFAULT_BACKEND_URL 的入口脚本,只能手填
10「Docker Daemon 挂掉全网崩溃」「毫秒级启动」「~20-40MB」⚠️ 夸大且对本机不成立本机 docker 是 rootless(user slice),故障域限于 user.slice;且 mariadb 也在同一 docker 上,只搬 mihomo 去不掉 dockerd,省内存的账算不出来
11标题「移植为内置 container 机制」⚠️ 术语错位services.mihomo 是原生 systemd 服务,不是容器;若图「内置容器」,现成的 rootless docker 就是那条路

设计方案(未实施 · 备查)

新增模块文件

新开 modules/system/service/mihomo.nix,不动现有 proxy.nix(后者继续管 noProxy 与 cn-direct 清单,两者职责不重叠)。

{ config, lib, pkgs, ... }:
{
  # 代理内核:原生 systemd 服务(非容器)
  services.mihomo = {
    enable = true;
    configFile = "/s500data/mihomo/config.yaml";  # 运行时读取,不进 /nix/store(含订阅凭据)
    webui = pkgs.metacubexd;                      # 模块内置 -ext-ui:UI 在 <API端口>/ui,无需 nginx
    tunMode = false;                              # 与 config.yaml 的 tun.enable: false 一致
  };
 
  systemd.services.mihomo.serviceConfig = {
    MemoryMax = "512M";   # 沿用现 compose 口径(256M 是已废弃旧值)
    CPUQuota = "200%";    # 二核;半核曾是 TLS 并发天花板
    Restart = "always";
  };
}

配置文件与数据落位

  • config.yaml 迁到 /s500data/mihomo/config.yaml;把 external-controller 从 0.0.0.0:9090 改为 0.0.0.0:9091(9090 留给原 UI 入口),UI 则为 http://<本机IP>:9091/ui。
  • 运行状态目录由模块给出:/var/lib/private/mihomo(DynamicUser + StateDirectory,UMask 0077)。现网 GeoSite.dat、geoip.metadb 需放进该目录,避免首启联网重下;sub.yaml/cache.db 会在这里自动生成。
  • 订阅链接写在 config.yaml 内、属凭据:该文件只在运行时读取(模块以 LoadCredential 注入,不进 world-readable 的 /nix/store),也不要提交到 git。
  • 防火墙模块不改:wlp2s0 已进 trustedInterfaces,局域网可达性与现状一致。

切换顺序(顺序错了会绑不上端口)

  1. 容器仍在服务时先构建:nixos-rebuild build(此时下载还走 7890)。
  2. 停掉容器栈:docker compose -f /s500data/docker/mihomo/docker-compose.yml down(否则 7890/9091/9090 被 rootlesskit 占住)。
  3. 激活:nixos-rebuild switch(已构建,无需新下载)。
  4. 验证出网与端口归属,失败即回滚:docker compose … up -d。

落地时的关键判据

  • 迁移的收益只有一项可量化:省掉依赖 docker 守护进程这一层(rootless dockerd 实测约 97MB);启动/调度「更快」这类说法不作判据。
  • 只要 mariadb 仍在 docker 上,dockerd 就不会消失,「完全摆脱 Docker」不成立。
  • 原生服务的沙盒更严(ProtectSystem=strict、PrivateUsers、DynamicUser)——代价是配置改动一律要 restart,且改文件路径时注意 RestrictAddressFamilies 在 tunMode 下才放开 AF_NETLINK。

不实施的理由(留档)

  • 现状可用:rootless docker 已是「容器化且免 root」的形态,故障域限于 user.slice,不是原笔记假设的「一挂全崩」。
  • 收益不闭环:mariadb 仍在 docker 上,搬 mihomo 省不掉 dockerd,「降低连锁故障」的前提在本机不成立。
  • 有真实代价:端口迁移要停一次代理(本机出网唯一通路),配置与订阅路径变更引入失败面,收益却不落地。
  • 替代触发条件:若将来 mariadb 也移出 docker(或 docker 本身被停用),再启动本方案才有净收益——届时按上面的模块文件与切换顺序执行。

复核命令

# 端口归属:原生服务 vs 容器
ss -tlnp | grep -E '7890|9090|9091'
 
# 容器栈状态
docker compose ls
docker ps --format '{{.Names}} {{.Image}} {{.Ports}}'
 
# 原生服务(迁移后才有)
systemctl status mihomo
systemctl cat mihomo | grep -E 'ExecStart|StateDirectory|Capability'
 
# 版本一致性(容器镜像=1.19.24,nixpkgs 也是 1.19.24)
nix-instantiate --eval -E 'with import <nixpkgs> {}; mihomo.version'
 
# 出网探针(换代理前后都该过)
curl -s -o /dev/null -w '%{http_code} %{time_total}s\n' -x http://127.0.0.1:7890 https://cache.nixos.org/nix-cache-info
 
# 回滚
docker compose -f /s500data/docker/mihomo/docker-compose.yml up -d
systemctl stop mihomo