diff --git a/docs/nixos-modernization-options.md b/docs/nixos-modernization-options.md index e64d1660..9f82d5c8 100644 --- a/docs/nixos-modernization-options.md +++ b/docs/nixos-modernization-options.md @@ -199,8 +199,7 @@ defaults become less transparent than a file you can read in the diff; your deli ## A7. attic self-hosted binary cache -**Current:** substituters are the two Chinese mirrors plus numtide's cache; a cachix endpoint exists -in the config but is unused. +**Current:** substituters are the two Chinese mirrors plus numtide's cache. **Proposed:** `services.networking.atticd` on youko. diff --git a/hosts/12kingdoms-youko/homelab-services/nix-cache-proxy.nix b/hosts/12kingdoms-youko/homelab-services/nix-cache-proxy.nix new file mode 100644 index 00000000..8a4c46f7 --- /dev/null +++ b/hosts/12kingdoms-youko/homelab-services/nix-cache-proxy.nix @@ -0,0 +1,86 @@ +{ myvars, ... }: +let + cacheDir = "/data/apps/nix-cache-proxy"; + inherit (myvars.ports) youkoNixCacheProxy; + + # Nix clients verify narinfo signatures and NAR hashes, so this only needs to + # be a transparent HTTP cache, not a Nix-aware store. Keep it on the btrfs + # data volume, never on the /data tmpfs. + cacheConfig = '' + proxy_ssl_server_name on; + proxy_ssl_verify on; + proxy_ssl_verify_depth 3; + proxy_ssl_trusted_certificate /etc/ssl/certs/ca-bundle.crt; + + proxy_cache nix_cache; + proxy_cache_key "$scheme$request_method$host$request_uri"; + proxy_cache_valid 200 206 30d; + proxy_cache_valid 404 1m; + proxy_cache_lock on; + proxy_cache_lock_timeout 60s; + proxy_cache_use_stale + error timeout updating + http_500 http_502 http_503 http_504; + + add_header X-Cache-Status $upstream_cache_status always; + ''; + + cacheLocation = upstream: { + proxyPass = "https://${upstream}/"; + extraConfig = '' + proxy_set_header Host ${upstream}; + ${cacheConfig} + ''; + }; +in +{ + services.nginx = { + enable = true; + + appendHttpConfig = '' + proxy_cache_path ${cacheDir} + levels=1:2 + keys_zone=nix_cache:256m + max_size=300g + inactive=30d + use_temp_path=off; + ''; + + virtualHosts."nix-cache-proxy" = { + # Caddy owns the public listener and terminates HTTPS; nginx is internal. + listen = [ + { + addr = "127.0.0.1"; + port = youkoNixCacheProxy; + } + ]; + + locations = { + "/cache.nixos.org/" = cacheLocation "cache.nixos.org"; + "/cache.numtide.com/" = cacheLocation "cache.numtide.com"; + }; + }; + }; + + # `proxy_cache_path` is created at runtime; nginx owns it. + systemd.tmpfiles.rules = [ + "d ${cacheDir} 0755 nginx nginx - -" + ]; + systemd.services.nginx = { + unitConfig.RequiresMountsFor = [ "/data/apps" ]; + # The NixOS nginx unit runs with ProtectSystem=strict; the custom cache + # path must be explicitly writable. + serviceConfig.ReadWritePaths = [ cacheDir ]; + }; + + # Public Let's Encrypt wildcard, already managed by the Caddy module. This + # avoids distributing the private homelab CA to clients that only consume the + # cache (including macOS). + services.caddy.virtualHosts."nix-cache.writefor.fun".extraConfig = '' + tls /var/lib/acme/writefor.fun/fullchain.pem /var/lib/acme/writefor.fun/key.pem { + protocols tls1.3 tls1.3 + curves x25519 secp384r1 secp521r1 + } + reverse_proxy http://127.0.0.1:${toString youkoNixCacheProxy} + ''; +} diff --git a/hosts/idols-aquamarine/asr.nix b/hosts/idols-aquamarine/asr.nix index 99d9e393..1eaeae9e 100644 --- a/hosts/idols-aquamarine/asr.nix +++ b/hosts/idols-aquamarine/asr.nix @@ -8,7 +8,8 @@ }: let inherit (myvars.networking.hostsAddr.aquamarine) ipv4; - port = 18080; + inherit (myvars.ports) aquamarineAsr; + port = aquamarineAsr; # OpenVINO publishes static IRs for Whisper only, and the NPU compiler accepts # nothing less: any other ASR family keeps a dynamic time axis and aborts it diff --git a/modules/base/nix.nix b/modules/base/nix.nix index 6318b016..d93435de 100644 --- a/modules/base/nix.nix +++ b/modules/base/nix.nix @@ -28,7 +28,7 @@ # The official https://cache.nixos.org is always trusted and is appended # automatically, so it does not need to be listed here. substituters = [ - # cache mirror located in China + # Domestic mirrors are fast from the LAN; query them directly first. # status: https://mirrors.ustc.edu.cn/status/ "https://mirrors.ustc.edu.cn/nix-channels/store" # status: https://mirror.sjtu.edu.cn/ @@ -37,11 +37,14 @@ # "https://mirrors.sustech.edu.cn/nix-channels/store" "https://mirrors.tuna.tsinghua.edu.cn/nix-channels/store" + # Pull-through cache for upstreams that are slow from the LAN. Nix + # appends the official cache.nixos.org last, so list its proxy here. + "https://nix-cache.writefor.fun/cache.nixos.org" # numtide's cache (niks3), which serves the prebuilt packages of the # numtide/llm-agents.nix flake input used for the AI agent CLIs. + "https://nix-cache.writefor.fun/cache.numtide.com" + # Direct fallback if the LAN proxy is unavailable. "https://cache.numtide.com" - # my own cache server, currently not used. - # "https://ryan4yin.cachix.org" ]; # WARNING: every key here trusts *everything* that key signs. Add one only @@ -49,7 +52,6 @@ # a third party the ability to ship you arbitrary store paths. trusted-public-keys = [ "niks3.numtide.com-1:DTx8wZduET09hRmMtKdQDxNNthLQETkc/yaX7M4qK0g=" - "ryan4yin.cachix.org-1:Gbk27ZU5AYpGS9i3ssoLlwdvMIh0NxG0w8it/cv9kbU=" ]; builders-use-substitutes = true; }; diff --git a/outputs/x86_64-linux/tests/youko-nix-cache-proxy/expected.nix b/outputs/x86_64-linux/tests/youko-nix-cache-proxy/expected.nix new file mode 100644 index 00000000..c1b3682c --- /dev/null +++ b/outputs/x86_64-linux/tests/youko-nix-cache-proxy/expected.nix @@ -0,0 +1,11 @@ +{ + nginxEnabled = true; + caddyVhostExists = true; + ustcRemainsFirst = true; + nixosProxyConfigured = true; + numtideProxyConfigured = true; + numtideDirectFallback = true; + cacheLivesOnDataVolume = true; + nixosUpstream = "https://cache.nixos.org/"; + numtideUpstream = "https://cache.numtide.com/"; +} diff --git a/outputs/x86_64-linux/tests/youko-nix-cache-proxy/expr.nix b/outputs/x86_64-linux/tests/youko-nix-cache-proxy/expr.nix new file mode 100644 index 00000000..9a8c16b7 --- /dev/null +++ b/outputs/x86_64-linux/tests/youko-nix-cache-proxy/expr.nix @@ -0,0 +1,21 @@ +{ + lib, + outputs, + ... +}: +let + cfg = outputs.nixosConfigurations.youko.config; + nginx = cfg.services.nginx; + substituters = cfg.nix.settings.substituters; +in +{ + nginxEnabled = nginx.enable; + caddyVhostExists = cfg.services.caddy.virtualHosts ? "nix-cache.writefor.fun"; + ustcRemainsFirst = builtins.head substituters == "https://mirrors.ustc.edu.cn/nix-channels/store"; + nixosProxyConfigured = builtins.elem "https://nix-cache.writefor.fun/cache.nixos.org" substituters; + numtideProxyConfigured = builtins.elem "https://nix-cache.writefor.fun/cache.numtide.com" substituters; + numtideDirectFallback = builtins.elem "https://cache.numtide.com" substituters; + cacheLivesOnDataVolume = lib.hasInfix "/data/apps/nix-cache-proxy" nginx.appendHttpConfig; + nixosUpstream = nginx.virtualHosts."nix-cache-proxy".locations."/cache.nixos.org/".proxyPass; + numtideUpstream = nginx.virtualHosts."nix-cache-proxy".locations."/cache.numtide.com/".proxyPass; +} diff --git a/vars/README.md b/vars/README.md index 157b9c3f..bb28e9b7 100644 --- a/vars/README.md +++ b/vars/README.md @@ -8,7 +8,8 @@ Common variables and configuration used across my NixOS and nix-darwin configura vars/ ├── README.md ├── default.nix # Main variables entry point -└── networking.nix # Network configuration and host definitions +├── networking.nix # Network configuration and host definitions +└── ports.nix # Loopback HTTP backend port registry ``` ## Components diff --git a/vars/default.nix b/vars/default.nix index 491ea505..90f81d4b 100644 --- a/vars/default.nix +++ b/vars/default.nix @@ -4,6 +4,7 @@ userfullname = "Ryan Yin"; useremail = "xiaoyin_c@qq.com"; networking = import ./networking.nix { inherit lib; }; + ports = import ./ports.nix; # Generated using: mkpasswd -m yescrypt --rounds=11 # Password: long, strong random string (full charset) # Rotation policy: changed annually diff --git a/vars/ports.nix b/vars/ports.nix new file mode 100644 index 00000000..8aebcc1e --- /dev/null +++ b/vars/ports.nix @@ -0,0 +1,6 @@ +{ + # Loopback-only HTTP backends behind Caddy. Allocate services sequentially + # from 18080; keep this list sorted by port. + aquamarineAsr = 18080; + youkoNixCacheProxy = 18081; +}