Runbook — secrets
Everything in the repository’s secrets/ directory is sops ciphertext (safe to commit). Plaintext never enters git or the
Nix store; sops-nix decrypts at activation into /run/secrets. Recipients are listed in .sops.yaml at the repository root.
Host key (once, on the workstation, BEFORE building the installer image)
Section titled “Host key (once, on the workstation, BEFORE building the installer image)”age-keygen -o host-age.txt # prints "Public key: age1…"; keep the file off the repo and off the imagechmod 600 host-age.txtAdd the printed public key to .sops.yaml as &cobalt, then (re-)encrypt the files so the host can read
them: sops updatekeys secrets/*.yaml. The image is built from the final configuration,
so the secrets must exist — encrypted to this key — before nix build path:.#images.installer. On the
laptop, install.sh asks for the path of the prepared key (another medium) and installs it as
/persist/var/lib/sops-nix/key.txt; sops-nix decrypts into /run/secrets at every activation.
users.yaml (login password hashes)
Section titled “users.yaml (login password hashes)”Accounts are declarative (users.mutableUsers = false), so the hashes must exist before the first boot
or nobody can log in. Keys are fixed names, independent of the actual usernames:
# One hash per account: mkpasswd (yescrypt) on Linux; on macOS `openssl passwd -6` (sha512-crypt), which# NixOS accepts as well. Both prompt for the password without echoing it.h1=$(mkpasswd -m yescrypt) # the operator's passwordh2=$(mkpasswd -m yescrypt) # the second account's password# Encrypted straight from stdin, so the hashes never land in a plaintext file. --filename-override: sops# matches the creation rule against the DESTINATION path (sops ≥ 3.9); stdin alone matches no rule.printf 'password-operator: "%s"\npassword-secondary: "%s"\n' "$h1" "$h2" | sops --encrypt --filename-override secrets/users.yaml --input-type yaml --output-type yaml /dev/stdin > secrets/users.yamlunset h1 h2mihomo.yaml (proxy configuration)
Section titled “mihomo.yaml (proxy configuration)”Source: your own Stash/Clash Meta profile, or example.CM.min.yaml from the repository root, a skeleton that
already contains every setting in the table below. Work on a copy named CM.min.yaml, which git ignores, and do
not commit the plaintext. A Stash profile is already in mihomo dialect; apply these Linux/NixOS adjustments:
| Key | Change | Why |
|---|---|---|
tun.device | add device: Meta | the firewall exemption in network.nix is keyed on this name |
tun.stack | gvisor → system (or mixed) | better throughput on Linux; the firewall trusts the TUN for it |
tun.auto-redir | remove | Linux-only nftables redirect; redundant with auto-route and fights our nftables firewall |
dns.listen | 127.0.0.1:53 → 127.0.0.1:1053 (or remove) | the service has no CAP_NET_BIND_SERVICE; resolution still works via dns-hijack |
allow-lan | true → false (recommended) | the laptop is not a relay; inbound 7890/7891/7894 are firewalled anyway |
external-controller, secret | keep (loopback + secret) | dashboard comes from services.mihomo.webui, drop any external-ui key |
everything else (geodata, rule-providers, profile.store-*, proxies, groups, rules) | keep | state lands in /var/lib/private/mihomo, which is persisted |
Notes
DOMAIN-KEYWORD,nixos,DIRECTalready sendscache.nixos.orgdirect — keep it, the binary cache should not ride the proxy.- First start needs connectivity to download GeoIP/GeoSite (
geox-url); bootstrap resolvers are literal IPs, so no deadlock. - The relative paths in
rule-providers.*.pathresolve inside the state directory.
Encrypt:
cp example.CM.min.yaml CM.min.yaml # or copy your own profile here; CM.min.yaml is git-ignored$EDITOR CM.min.yaml # servers, credentials, rules; keep the settings from the table abovesops --encrypt --filename-override secrets/mihomo.yaml --input-type yaml --output-type yaml CM.min.yaml > secrets/mihomo.yamlKeep the plaintext CM.min.yaml only as long as you need it: it holds the proxy credentials, and on an SSD
overwriting a file does not reliably erase it, so the workstation’s disk encryption is what protects it.
modules/nixos/hardening/network.nix enables services.mihomo automatically once secrets/mihomo.yaml
exists; until then the module is inert and the flake still evaluates.