结论
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 解密+并发天花板」实测放宽,依据写在文件里) |
| TUN | compose 映射 /dev/net/tun + CAP_NET_ADMIN,但 data/config.yaml 里 tun.enable: false(注释:容器环境建议关闭) |
| 防火墙 | modules/system/service/firewall.nix:trustedInterfaces = [ "lo" "wlp2s0" ],allowedTCPPorts 刻意留空(策略=按接口信任,不逐个开端口) |
| proxy.nix | modules/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 上 |
原笔记逐条裁决
| # | 原笔记说法 | 裁决 | 依据 |
|---|---|---|---|
| 1 | services.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 要联网重下 |
| 4 | tunMode = true;(称「官方自动配置内核参数」) | ⚠️ 不符实况 | 本机 tun.enable: false,走纯 7890 HTTP/SOCKS;tunMode 只给 CAP_NET_ADMIN 并放开 PrivateDevices/PrivateUsers,不替你开内核转发 |
| 5 | MemoryMax=256M / CPUQuota=50% | ⚠️ 抄的旧值 | 会把已实测放宽的 512M/2.0 退回半核瓶颈 |
| 6 | networking.firewall.allowedTCPPorts = [7890 9091 9090] + enable = true | ⚠️ 与既定策略冲突 | 本机按接口信任(含 wlp2s0)已覆盖同一效果;多模块叠写 firewall 配置易互相打架 |
| 7 | 用 nginx 托管 pkgs.metacubexd(9090) | ⚠️ 多余 | services.mihomo.webui = pkgs.metacubexd 已内置 UI(-ext-ui,走 API 端口 /ui),不必新增守护进程 |
| 8 | external-controller: 0.0.0.0:9091 | ✅ 对 | 容器内原为 9090、靠 compose 映射到宿主 9091;原生须直接改配置。注意改后需 systemctl restart mihomo(配置文件不走热加载) |
| 9 | metacubexd 首访手填 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,局域网可达性与现状一致。
切换顺序(顺序错了会绑不上端口)
- 容器仍在服务时先构建:
nixos-rebuild build(此时下载还走 7890)。 - 停掉容器栈:
docker compose -f /s500data/docker/mihomo/docker-compose.yml down(否则 7890/9091/9090 被 rootlesskit 占住)。 - 激活:
nixos-rebuild switch(已构建,无需新下载)。 - 验证出网与端口归属,失败即回滚:
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