一句话结论
依你 2026-10-11 的定案:proxy.nix 只解决 system 面(环境变量 / mihomo 国内直连清单 / 给非 nix 脚本的 env 文件),不再把各应用的代理往 proxy.nix 里塞;各应用在自己模块里接线,值统一从 config.networking.proxy.* 取。已于 2026-10-11 实施完成并验收,三处改动见下。
职责划分(你定案 2026-10-11)
| 层 | 文件 | 管什么 |
|---|---|---|
| system 面(唯一值源) | modules/system/service/proxy.nix | 顶部 let 里的 proxyDefault / cnDirectDomains / baseNoProxy ⇒ 写出 networking.proxy.default 与 networking.proxy.noProxy;生成 /etc/mihomo/cn-direct.yaml;生成 /etc/proxy/env.sh |
| 应用面(各自接线,值引上面) | modules/system/service/docker.nix | docker daemon 拉镜像的代理(本机生效的是 rootless.daemon.settings) |
| 应用面(各自接线,值引上面) | modules/system/service/git.nix | /etc/gitconfig 的 http(s).proxy |
| 故意重复(不改) | modules/desktop/browser/google-chrome.nix | Chrome 自带策略 + 自带绕过清单(模块注释写明“故意不复用,避免模块间依赖”) |
| 其它消费者(只读 env) | curl / uv / nix / hermes / KDE GUI | 统统跟随 networking.proxy 写出的环境变量,无需单独接线 |
★ 一个必须记住的坑:rootless 的落点是子选项
本机跑的是 rootless docker,而 nixpkgs 里 rootless 的错误/正确落点差别如下:
✗ virtualisation.docker.daemon.settings ← 只喂给【rootful】单元(本机不生效,实测 daemon.json 仍是 { })
✓ virtualisation.docker.rootless.daemon.settings ← 本机真正生效的那一个(docker-rootless.nix 里独立的推出项)验证方法(酸试):看单元实际引用的那个文件里有没有 proxies。实施后实测输出:
单元引用: /nix/store/pv52fmdali9m8w8apxd38v90a3dymizk-daemon.json
{ "proxies": { "http-proxy": "http://127.0.0.1:7890",
"https-proxy": "http://127.0.0.1:7890",
"no-proxy": "<103 条国内直连清单>" } }落地方案(已实施)
① docker.nix(modules/system/service/docker.nix)—— 两段都写上(rootful 那份留作备用,同一个值源):
virtualisation.docker.daemon.settings = # 只对 rootful 生效,留着备用
lib.mkIf (config.networking.proxy.default != null) {
proxies = {
http-proxy = config.networking.proxy.default;
https-proxy = config.networking.proxy.default;
no-proxy = config.networking.proxy.noProxy;
};
};
virtualisation.docker.rootless.daemon.settings = # ★ 本机生效的就是这一段
lib.mkIf (config.networking.proxy.default != null) {
proxies = {
http-proxy = config.networking.proxy.default;
https-proxy = config.networking.proxy.default;
no-proxy = config.networking.proxy.noProxy;
};
};⚠ 为什么要 mkIf (… != null):nixpkgs 的 networking.proxy.default 默认就是 null(没引 proxy.nix 时),不加守卫会把 null 写进 daemon.json。这是“引用数据源 + 不造假值”的最小写法,不是防御式编程。
② proxy.nix 补一份给非 nix 脚本 source 的文件(声明式,不用 tmpfiles):
environment.etc."proxy/env.sh".text = ''
export http_proxy=${proxyDefault}
export https_proxy=${proxyDefault}
export no_proxy=${config.networking.proxy.noProxy}
export all_proxy=${proxyDefault}
'';③ git.nix 两行改为引用数据源(去掉硬编码):
http.proxy = config.networking.proxy.default;
https.proxy = config.networking.proxy.default;不做的事:不加任何“重启服务”的 hook;不改 Chrome 那份自带清单;不新增自定义 options;不把 docker 的接线搬进 proxy.nix。
实施记录(2026-10-11)
| 步骤 | 结果 |
|---|---|
| 改动文件的备份 | /etc/nixos/backup/proxy-converge-20261011-181803/(三个文件原样) |
| rebuild | 两次均成功:proxy-docker-git-converge / proxy-rootless-setting |
| docker daemon 代理 | docker info ⇒ http://127.0.0.1:7890 ✓(daemon.json 里也确有 proxies ✓) |
| git 系统级代理 | git config --system --get http.proxy ⇒ http://127.0.0.1:7890 ✓ |
/etc/proxy/env.sh | 生成✓,四行导出(含 no_proxy 103 条)实际值均正确 ✓ |
| 重启 docker 的代价 | clash 断约 10~30 秒后自恢复;mariadb/clash/clash-ui 均回 Up ✓;127.0.0.1:3306 握手仍正常 ✓ |
本机代理链路现状(实测 2026-10-11)
| 编号 | 消费者 | 实际落点 | 数据来源 | 是否单一数据源 |
|---|---|---|---|---|
| C01 | 主机侧环境变量 | /etc/set-environment | networking.proxy.{default,noProxy}(proxy.nix 顶部 let) | ✓ 是 |
| C02 | GUI 应用(KDE 系) | ~/.config/kioslaverc(ProxyType=4 = 读环境变量) | 跟随 C01 | ✓ 是 |
| C03 | git | /etc/gitconfig | programs.git.config 引用 config.networking.proxy.default | ✓ 是(已去硬编码) |
| C04 | docker daemon(镜像拉取) | rootless.daemon.settings ⇒ daemon.json | 引用 config.networking.proxy.* | ✓ 是(已声明式化) |
| C05 | Chrome | 自带策略 + 自带绕过清单 | google-chrome.nix(刻意重复) | ✓ 故意重复,不改 |
| C06 | mihomo 容器(真正出网的代理本体) | 内联 data/config.yaml 国内直连段 + 生成 /etc/mihomo/cn-direct.yaml | proxy.nix | ✓ 是 |
| C07a | curl | 只读环境变量 | 跟随 C01 | ✓ 是(已实测) |
| C07b | uv | 只读环境变量 | 跟随 C01 | ✓ 是(已实测) |
| C07c | nix / nix-daemon | 只读环境变量(daemon 环境里也有) | 跟随 C01 | ✓ 是(已实测) |
| C07d | hermes | 读环境变量(config_defaults.py 里写死 trust_env: True) | 跟随 C01 | ✓ 是(已实测+源码核对);本地端点例外 |
证据(毒代理 http://127.0.0.1:9 与真代理对照):curl 毒⇒000/真⇒204;uv 毒⇒Connection refused/真⇒正常;nix 用户侧 毒⇒Could not connect(7)/真⇒下载成功;nix-daemon 自身环境里就写着 127.0.0.1:7890;hermes 进程环境有代理 ✓ 且源码 hermes_cli/config_defaults.py:2212-2217 明确 “Let adapters read HTTP_PROXY/HTTPS_PROXY/NO_PROXY…”,web_routers/config_env.py:962 对本地端点 trust_env=False(刻意绕开代理,不是漏洞)。
代理本体 = 容器 clash,定义在 /s500data/docker/mihomo/docker-compose.yml(restart: always),对外服务 127.0.0.1:7890。
逐条裁决(针对“另一个 AI”的提案)
| 编号 | 它的提案 | 裁决 | 理由 |
|---|---|---|---|
| R01 | 收敛到单一模块(proxy.nix) | 部分采信 | system 面已汇在 proxy.nix;应用面按你的定案留在各自模块内接线 |
| R02 | 用 options.services.unified-proxy 自定义选项 + apps 开关 | 驳回 | 与本机契约冲突:已全线消灭 options.local.*,改为一律“模块顶部 let 参数 + import 即生效” |
| R03 | HTTP_PROXY = mkIf cfg.apps.nixos proxyUrl 写在 sessionVariables 里 | 修正 | 类型非法;且本机不需要 —— networking.proxy 已覆盖这批环境变量 |
| R04 | docker daemon 用 daemon.settings.proxies 声明 | 采信但修正落点 | 方向对;但本机 rootless,必须用 rootless.daemon.settings(实测 daemon.settings 对 rootless 不生效) |
| R05 | git 代理走 programs.git.config | 采信(已是),但修正 | 本机 git.nix 已这么写,但值是硬编码 ⇒ 已改为引用数据源 |
| R06 | Chrome 用 programs.chromium.extraOpts 强制代理 | 驳回 | 本机 Chrome 走 google-chrome 包 + policies.json;自带绕过清单是刻意设计 |
| R07 | 用 systemd.tmpfiles.rules 写 /system/service/proxy-data/env.sh | 驳回(意图采信) | ① 路径臆造 ② f+ 每次激活重写 = 可变状态。正确手段 = environment.etc |
| R08 | hook 里无条件 systemctl reload-or-restart docker | 驳回 | 正是 2026-10-11 事故的做法:重启 daemon 会连带给 clash 断网 |
| R09 | “三级生效机制”(含必须 reboot) | 修正 | 不需要 reboot;真实生效点见下表 |
生效点表(真实版,替代“三级机制”)
| 改了什么 | 要跑什么 | 何时生效 |
|---|---|---|
| proxy.nix / git.nix 的数据 | rebuild <描述> | 环境变量、/etc/gitconfig、/etc/proxy/env.sh 立即写入;已打开的终端要重开或 source /etc/set-environment |
| docker 的 daemon.json | rebuild 后再做一次 systemctl --user restart docker.service | ⚠ 连带给 clash 断约 10~30 秒 |
| GUI / Chrome 行为 | 注销重登 | 不必整机重启 ✗ |
| 代理本体(clash 容器) | docker start clash clash-ui 或 docker compose -f /s500data/docker/mihomo/docker-compose.yml up -d | 立即 |
代理故障 30 秒定位手册 ★
认址规则:本机代理唯一合法值 = http://127.0.0.1:7890;出现任何别的 IP(尤其 192.168.*)就是错的。
第①问 代理本体在吗:
ss -lntp | grep 7890 # 没输出 ⇒ 起容器(命令见上一节)
第②问 报错里的地址是谁:
proxyconnect tcp: dial tcp 192.168.1.100:7890: connection refused
⇒ 说明【那个消费者自己的代理值】被写错了,不是网络问题(2026-10-11 就是这案)
第③问 各消费者当前值(一条命令全查):
printf 'env=%s\ngit=%s\ndocker=%s\nkde=%s\n' \
"${http_proxy:-未设}" \
"$(git config --system --get http.proxy)" \
"$(docker info 2>/dev/null | awk -F': ' '/HTTP Proxy/{print $2}')" \
"$(grep -h ProxyType ~/.config/kioslaverc)"
判读:env / git / docker 都应该是 127.0.0.1:7890;kde 的 ProxyType=4 = 用环境变量
第④问 修哪一处:
system 面(环境变量) → 只改 modules/system/service/proxy.nix 顶部一处 → rebuild
docker 那条不对 → 改 modules/system/service/docker.nix 里【rootless.daemon.settings】→ rebuild → 再 restart docker.service
git 那条不对 → 改 modules/system/service/git.nix → rebuild
用户级残留遮盖 → 删掉 ~/.config/systemd/user/*docker* 里的单元文件(它会盖掉 nix 单元)
环境变量没刷新 → 重开终端 / source /etc/set-environment2026-10-11 真实案例:mariadb 容器起不来,报错正是第②条;根因 = 一个用户级 docker.service 单元(文件已不在,systemd 里残留加载定义)把 daemon 代理指向 192.168.1.100:7890(那是手机的地址),盖掉了 nix 侧正确单元;修法 = 清残留 + 重启 daemon;代价 = clash 断约 1 分钟。这次收敛之后,这类故障只需改对应模块一处 + rebuild。