Self-hosted Tangled spindle

S0: Executor NixOS configuration (nixery engine, LAN firewall) (ENG-464) #1

Merged demi.dev
targeting main from stoopkidddd/eng-464-s0-executor-nixos-configuration-nixery-engine-lan-firewall
Merged opened by and assigned to demi.dev
targeting main from stoopkidddd/eng-464-s0-executor-nixos-configuration-nixery-engine-lan-firewall

Linear: ENG-464

Summary #

A NixOS config that turns the VM spindle-exec-1 into a spindle executor that dials out to the mill.

  • executor/flake.nix: nixpkgs nixos-26.05 (fa13731, the same as the bootstrap) and Tangled v1.20.0 (locked at 2096d06d). Output nixosConfigurations.executor.
  • services.tangled.spindle:
    • role executor, connecting to wss://spindle.demi.dev/mill;
    • the token is read from /var/lib/secrets/spindle-executor.token (LoadCredential), and the S3 credentials from /var/lib/secrets/spindle-executor.env;
    • nixery engine: workflow timeout 15m, 4 workflows at once, 6144 MiB per job;
    • seats 4, labels linux and x86_64;
    • artifact store s3. The bucket name is a placeholder until ENG-465.
  • Docker logs rotate at 10 MiB × 3. Each week a timer prunes unused Docker data older than 7 days and collects Nix garbage older than 14 days.
  • nftables blocks CI jobs on docker0 and br-* from reaching RFC 1918, link-local and ULA addresses. DNS to 1.1.1.1 and the internet stay reachable.
  • SSH is key-only, uses the lead key, and is only reachable from RFC 1918 sources.
  • hardware-configuration.nix keeps the bootstrap's boot setup: GRUB on /dev/vda, the ext4 root labelled nixos with growPartition, the qemu guest agent and the ttyS0 console.

Verify #

$ mise run check
hadolint: no files yet
all checks passed!
/nix/store/cw7cjgqzmbgja8lndhsbvnza1mwzm3cq-nixos-system-spindle-exec-1-26.05.20261031.fa13731.drv
check_exit=0

$ nix eval (selected fields)
{"docker":true,"mill":"wss://spindle.demi.dev/mill","nft":true,"role":"executor","ssh22open":false,"timeout":"15m"}

The secret-pattern grep over the diff has 0 hits. The lane is local-only until the S3 smoke issue, and there is no CI yet.

Deviations #

  • DNS is pinned to 1.1.1.1 instead of the LAN resolver, so the firewall needs no hole into the LAN. Follow-up: ENG-474.
  • SSH is allowed from all RFC 1918 ranges, because the exact LAN subnet wasn't specified.
  • ICMPv6 neighbor discovery is allowed on Docker bridges; without it, public IPv6 egress breaks.

Review #

Two rounds of pneumo-reviewer, both done on the local branch before this PR could open:

  • round 1: REQUEST_CHANGES, for an out-of-scope README section;
  • round 2: APPROVE.

Both are posted as comments. The deploy checks are tracked in ENG-468.

🤖 Generated with Claude Code

Commits 0