feat(youko): add pull-through cache for two Nix upstreams (#499)

Run nginx on youko as a loopback pull-through HTTP cache for
cache.nixos.org and cache.numtide.com, exposed by Caddy at
https://nix-cache.writefor.fun.

Assisted-by: DeepSeek Harness (deepseek-flash)
This commit is contained in:
ryan4yin | 二花 authored and GitHub committed 2026-11-01 18:27:43 +08:00
1 parent 9fe86de51d
commit 58d1af8f9d
9 files changed
+136 -8

No files matched your search

+1 -2
View File
@@ -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.
@@ -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}
'';
}
+2 -1
View File
@@ -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
+6 -4
View File
@@ -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;
};
@@ -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/";
}
@@ -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;
}
+2 -1
View File
@@ -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
+1
View File
@@ -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
+6
View File
@@ -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;
}