Found and fixed live against a real test container (VMID 100, lxc-nix-cache on pve.sweet.home) after the previous pct-restore-to-pct-create fix still produced a container that booted into garbled console output: 1. pct create's own CLI default for --unprivileged is privileged (unlike the web UI, whose checkbox defaults the other way), but modules/platforms/lxc.nix sets proxmoxLXC.privileged = false, so the image assumes it's running unprivileged. Real mismatch -- now passes --unprivileged 1 explicitly. 2. The actual root cause of the garbled console: modern (v247+) systemd routinely uses nested user namespaces and credential mounts (even plain getty units, via LoadCredential=-style mechanisms), which AppArmor's default LXC confinement denies without --features nesting=1,keyctl=1. Confirmed via the host's kernel audit log: every getty unit was crash-looping on a denied /run/credentials/* mount every ~3s, and core services like nsncd failed userns_create the same way -- the system never finished activating. Fixed live (pct set + restart on the running test container) before committing the script change: systemctl is-system-running went from never completing to "running" with zero failed units. 3. --memory doesn't touch swap -- confirmed live it silently stayed at Proxmox's own 512M default with --memory 2048. Now defaults --swap to whatever --memory resolves to. docs/auto-installer.md's manual pct create walkthrough gets the same fixes, with the "why" for each flag, since a human following it by hand would hit the identical bugs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01La55Nsss8jZ7ZuzUV9mfot
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 - Build types (what it's for):
minimal,nix-cache,server,docker,gui,pxe-boot
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. The full
list:
| Target | Purpose |
|---|---|
linode-minimal |
Minimal NixOS host profile on a Linode VPS (real, deployed) |
proxmox-minimal |
Minimal NixOS host profile on Proxmox (real, deployed — 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 (proxmox-nix-cache is the real, deployed one — previously the flat nix-cache target) |
linode-server / proxmox-server / lxc-server |
Storage, NFS, backup, and monitoring exporter host (proxmox-server is the real, deployed one — previously the flat server target) |
linode-docker / proxmox-docker / lxc-docker |
Docker host for the main container stack (proxmox-docker is the real, deployed one — previously the flat docker target) |
linode-gui / proxmox-gui / lxc-gui |
Cinnamon desktop workstation (proxmox-gui is the real, deployed one — previously the flat nixos target) |
proxmox-pxe-boot / lxc-pxe-boot |
HTTP/iPXE boot asset host (proxmox-pxe-boot is the real, deployed one — previously the flat pxe-boot target) |
The "(real, deployed)" targets above are also tracked machine-readably in
variables.nix's deployedTargets — keep both in sync when a deployment
changes. scripts/create-proxmox-resource.sh reads that list to refuse
creating a same-identity duplicate of an already-deployed host by accident.
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) |
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 dry-run
bash scripts/codex-maintenance.sh
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/create-proxmox-resource.sh --type lxc|vm --host <name> automates
either of the last two end to end (build, host-key registration, upload,
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.