nix was installed as root on pve1 (the codex-setup.sh root path, meant for container/Codex environments), making nix build require sudo there. After cleaning up the root install and reinstalling as the SSH user (wayne), nix is owned by that user and runs directly without sudo. create-proxmox-resource.sh: drop sudo_pfx from nix build in both remote scripts. The SSH user owns the store after reinstall; nix build goes through the nix daemon-or-store directly. sudo stays on pct/qm/pvesh (cluster IPC) and the disko image-writer script (writes to disk). codex-setup.sh: add build-users-group = (empty) to the user nix.conf written by the non-root install path. Guards against a stale /etc/nix/nix.conf from a prior root install (which sets build-users-group = nixbld) silently breaking single-user builds. Manual cleanup required once on each Proxmox node that had root's nix: sudo rm -rf /nix /etc/nix sudo rm -f /etc/profile.d/nix.sh /etc/profile.d/nix-daemon.sh for i in $(seq 1 10); do sudo userdel nixbld$i 2>/dev/null||true; done sudo groupdel nixbld 2>/dev/null || true After that, the next create-proxmox-resource.sh run auto-reinstalls nix as the SSH user via codex-setup.sh. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NixOS LAN Configurations
Flake-based NixOS configuration repository for Wayne's LAN servers and workstation.
Hosts
Targets are named <platform>-<buildtype>, generated from two orthogonal
pieces composed in flake.nix:
- Platforms (what it runs on):
linode,proxmox,lxc,baremetal - Build types (what it's for):
minimal,nix-cache,server,docker,gui,pxe-boot,tailscale-exit-node,tor-relay
Not every combination exists — pxe-boot has no linode variant, since
PXE/DHCP/TFTP need LAN L2 adjacency that a Linode VPS doesn't have,
tor-relay currently only exists as lxc-tor-relay, and baremetal
currently only exists as baremetal-gui (the real gui-host hardware). The
full list:
| Target | Purpose |
|---|---|
linode-minimal |
Minimal NixOS host profile on a Linode VPS |
proxmox-minimal |
Minimal NixOS host profile on Proxmox — previously the flat nix-minimal target |
lxc-minimal |
Minimal NixOS host profile in a Proxmox LXC container |
linode-nix-cache / proxmox-nix-cache / lxc-nix-cache |
Local Nix binary cache and remote builder — previously the flat nix-cache target |
linode-server / proxmox-server / lxc-server |
Storage, NFS, backup, and monitoring exporter host — previously the flat server target |
linode-docker / proxmox-docker / lxc-docker |
Docker host for the main container stack — previously the flat docker target |
linode-gui / proxmox-gui / lxc-gui |
Cinnamon desktop workstation — previously the flat nixos target |
baremetal-gui |
Same Cinnamon desktop workstation, on the real gui-host hardware — ZFS RAID0 root, systemd-boot |
proxmox-pxe-boot / lxc-pxe-boot |
HTTP/iPXE boot asset host — previously the flat pxe-boot target |
linode-tailscale-exit-node / proxmox-tailscale-exit-node / lxc-tailscale-exit-node |
Tailscale exit node |
lxc-tor-relay |
Tor middle relay |
Which variant of a given buildtype is actually deployed isn't tracked
anywhere in this repo — that's live infrastructure state, not something a
committed file can keep accurate, and it changes independently of the code.
Check the Proxmox node itself, or /etc/flake-target on a running host (see
below), if you need to know what's really out there right now.
scripts/proxmox/create-proxmox-resource.sh's duplicate-host guard works the same
way: it checks the Proxmox node directly rather than any file here.
Real, production deployments live on pve1.sweet.home; there's a second
node, pve-test.sweet.home, set aside purely for scratch/test resources —
see scripts/env.sh (PVE1_HOST / PVE_TEST_HOST, and the
--node/PROXMOX_HOST targeting they feed into) and CLAUDE.md's Proxmox
section for which is which.
Each buildtype's hosts/<name>/host.nix carries the per-machine identity
(hostname, hostId, per-machine secrets, system.stateVersion) that must stay
fixed regardless of which platform it's built for — see
flake-target-refactor-spec.md for the full rationale. Every deployed host
stamps its own active target name into /etc/flake-target at build time, so
nixos-rebuild switch --flake .#$(cat /etc/flake-target) always picks up the
right one even after a platform migration changes the flake attribute name.
List hosts with:
nix eval --json .#nixosConfigurations --apply builtins.attrNames | jq -r '.[]'
Layout
| Path | Purpose |
|---|---|
flake.nix |
Flake inputs, the mkTarget platform × build-type generator, and nixosConfigurations outputs |
variables.nix |
Single source of truth for shared values (LAN domain/CIDR, hostnames, timezone, primary username, storage root, NFS share subpaths/mountpoints, service ports, ...) — passed to every module and Home Manager config as the vars argument via specialArgs/extraSpecialArgs |
hosts/<name>/host.nix |
Per-machine identity: hostname, hostId, per-machine secrets, system.stateVersion |
hosts/nixos/home.nix |
Workstation-specific Home Manager config (used by the gui build type) |
modules/platforms/ |
Platform-specific config: virtualisation guest tools, boot method, hardware config (linode.nix, proxmox.nix, lxc.nix, baremetal.nix) |
modules/build-types/ |
Build-type-specific config: what makes a system minimal/server/docker/gui/pxe-boot/nix-cache |
modules/common/ |
Shared NixOS config, Home Manager, aliases imported by every host |
modules/nix-cache/ |
Binary cache and remote builder client/server modules |
modules/installer/ |
Auto-installer environment (ISO, also served as PXE netboot) — see docs/auto-installer.md |
host-keys/ |
Gitignored, locally-generated SSH host keys for the auto-installer — see docs/auto-installer.md |
docs/ |
Operational notes for cache, builders, lock updates, boot services, the auto-installer, and Proxmox image builds |
scripts/ |
Codex setup, validation, host-key, release-bump, and Proxmox resource helpers |
Validation
Safe validation commands for Codex and local review:
bash scripts/codex-setup.sh
bash scripts/codex-maintenance.sh
codex-maintenance.sh with no flags (what CI runs on every push/PR) scopes
fmt-check/statix/eval to files changed against a base ref — fast, but only
as thorough as the diff. For the full sweep (every host, every package,
fmt-check and statix over the whole tree — slow, CI never runs this):
bash scripts/codex-maintenance.sh --full-check
bash scripts/codex-maintenance.sh --full-check --dry-run
For individual host evaluation:
nix eval .#nixosConfigurations.<host>.config.system.build.toplevel.drvPath --raw
Use nix build --dry-run --no-link when build planning is needed. Do not run
deployment, install, disk formatting, mount, or reboot commands from automated
review sessions.
Operations
- Host rebuilds should consume the committed
flake.lock. - Routine dependency updates should happen through the flake lock automation
described in
docs/flake-lock-automation.md. nix-cacheserves substitutes over HTTP and can act as a remote builder for client hosts.pxe-bootserves iPXE boot files over HTTP from/srv/pxe.
Deploying a new host
Three different paths depending on target, none of them involving a manual
nixos-rebuild switch from this repo:
- Most hosts: boot the auto-installer, pick the target from its menu — see
docs/auto-installer.md. Every menu target has a Disko config the installer formats unconditionally (docs/auto-installer.md's "Storage" section covers how this stays non-destructive forlinode-*, whose disks Linode itself provisions ahead of time). lxc-*targets: not installed at all — build a ready-to-run container tarball andpct createit as a CT template directly.docs/auto-installer.mdcovers why (and the installer's menu excludes them for the same reason).proxmox-*targets: can alternatively be built as a standalone.rawdisk image and attached to a new VM with no install step — seedocs/proxmox-images.md.
scripts/proxmox/create-proxmox-resource.sh --type lxc|vm --host <name> automates
either of the last two end to end (host-key registration, building the
image directly on the Proxmox node itself, pct create/qm create), with
--dry-run and a guard against duplicating an already-deployed host's
identity. See its --help.
Security Notes
Do not commit tokens, private keys, live credentials, or new password hashes
as plaintext. Secrets are managed with sops-nix:
encrypted files live under secrets/, recipients (per-host age keys derived
from each host's existing SSH host key, plus an admin key) are declared in
.sops.yaml. To add or edit a secret:
nix-shell -p sops --run "sops secrets/<file>.yaml"
then reference it from a module via config.sops.secrets."<name>".path
(or sops.templates for values that need to be embedded in a rendered
config file, e.g. nix.conf's access-tokens). Never write a secret value
directly into a tracked .nix file. A pre-commit hook (.githooks/,
enabled via git config core.hooksPath .githooks, done automatically by
scripts/codex-setup.sh) runs gitleaks protect --staged to catch mistakes
before they're committed.
The auto-installer environment is the one deliberate exception to
sops-nix-everywhere: it has a hardcoded login password instead (no stable
per-boot host key for sops-nix to derive from on ephemeral media) — see
"Host keys" in docs/auto-installer.md for why, and how the private keys it
does pre-seed for target hosts stay out of git via the gitignored
host-keys/ directory.
This repository's git history still contains secrets committed before this
migration (see remove-sensetive-info-refactor.md) — those are being
scrubbed and rotated separately; don't treat the repo as safe to make public
until that's finished.