Commit Graph
24 Commits
Author SHA1 Message Date
beatzaplentyandClaude Sonnet 5 a5990ccf7d Build Proxmox images directly on the node instead of transferring them
Check NixOS configurations / eval-hosts (pull_request) Failing after 11m39s
create-proxmox-resource.sh no longer builds locally and scp's a
multi-gigabyte image over -- it now clones/pulls this repo onto the
Proxmox node itself (bootstrapping build tooling via the existing
codex-setup.sh on first use) and runs the nix build / disko image
script there, staging the result straight into the node's own import
directory. host-keys/ (gitignored) is copied over separately since a
git pull doesn't carry it. --image still uploads an explicit local
file for the case where you don't want a build at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 13:29:45 +00:00
rootandClaude Sonnet 5 5ec7033439 Fix stale documentation: outdated counts, missing build type, spec status
Check NixOS configurations / eval-hosts (pull_request) Failing after 11m26s
Same class of problem as the deployedTargets/README fixes: hand-maintained
prose that drifted from reality and nobody was obligated to update.

- CLAUDE.md: "18 hosts" was a stale hardcoded count (actually 20); reworded
  to not need updating as hosts are added. Also added the missing
  tailscale-exit-node build type to a list that had it everywhere else in
  the file except one bullet.
- AGENTS.md: same missing tailscale-exit-node build type.
- docs/auto-installer.md: the hand-enumerated lxc-* list was missing
  lxc-tailscale-exit-node.
- flake-target-refactor-spec.md: added a "Status: implemented" note so this
  completed historical spec (referenced elsewhere purely for rationale)
  can't be mistaken for an open plan with unresolved Open Questions.
- remove-sensetive-info-refactor.md: the "Definition of done" checklist was
  entirely unchecked despite most of the work being done. Checked off what's
  actually done (sops-nix migration, history scrub just performed, the
  pre-commit gitleaks hook), and left rotation of the GitHub PAT found in
  history explicitly flagged as the one still-open item -- an operator
  action against GitHub, not something this repo can attest to itself.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 11:06:05 +00:00
beatzaplentyandClaude Sonnet 5 75f1342339 Declaratively trust nix-cache's SSH host key on remote-builder clients
Distributed builds failed with "Host key verification failed" on any
client that had never manually SSH'd to nix-cache before, since
nothing populated root's known_hosts for it. Wire nix-cache's host
public key into programs.ssh.knownHosts via a new vars.nixCacheHostKey
so every client picks it up automatically on rebuild.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 17:21:48 +10:00
beatzaplentyandClaude Sonnet 5 d973da487c Fix lxc-* hosts having no host-key pre-seeding mechanism at all
The real root cause behind the original nix-cache 502, traced all the way
through: modules/installer/host-keys.nix (which NIXOS_HOST_KEYS_DIR=...
--impure actually wires up) is only ever imported by the installer's own
modules/installer/common.nix -- modules/platforms/lxc.nix, which every
real lxc-* host build actually uses, never imported anything like it.
docs/auto-installer.md previously claimed NIXOS_HOST_KEYS_DIR bakes a key
into lxc-* tarballs "the same way it does for the ISO/PXE installer
images" -- that was never actually true; I wrote it without verifying the
mechanism existed for lxc.nix specifically.

In practice this meant every lxc-* container booted with a freshly
self-generated SSH host key that could never match whatever .sops.yaml
actually trusts for that target, so *every* secret -- not just
cache-priv-key -- silently failed to decrypt. No error surfaces in the
boot log for this: the activation step that installs secrets only runs
on a genuinely fresh first activation and silently no-ops once
/run/current-system already exists, so by the time anyone looks the
window has closed. Found by manually invoking sops-install-secrets
directly: "Error getting data key: 0 successful groups required, got 0".

Fixed by giving modules/platforms/lxc.nix the same key-baking mechanism
the installer has, but keyed to its own exact flake target and placing
the key directly at /etc/ssh/ssh_host_ed25519_key (no copy step to stage
for, unlike the installer's /etc/host-keys/ staging area -- an lxc-*
tarball has no install step). The target name comes in via
specialArgs.flakeTarget (new, set by flake.nix's mkTarget) rather than
being read back from config.environment.etc."flake-target" -- reading
that back from within a module that also contributes to
environment.etc is circular (confirmed: "infinite recursion
encountered").

Verified live end-to-end against the real test container (lxc-nix-cache,
VMID 100 on pve.sweet.home): destroyed it, rebuilt the tarball fresh with
the fix, recreated it, and confirmed /run/secrets/ now has all three
secrets this host needs (beszel-token, cache-priv-key, nix-github-token),
nix-serve is active (running), and curl http://localhost/nix-cache-info
succeeds both directly and through nginx.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01La55Nsss8jZ7ZuzUV9mfot
2026-07-20 12:29:01 +10:00
beatzaplentyandClaude Sonnet 5 274d54a774 Fix LXC container creation: unprivileged, nesting/keyctl, swap sizing
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
2026-07-20 11:33:53 +10:00
beatzaplentyandClaude Sonnet 5 ad274d99fb Fix nix-cache retry storms and lxc creation in create-proxmox-resource.sh
Two independent problems found while actually running the script:

1. nix build/nix-shell retry each unreachable substituter/builder up to
   5x with backoff, per store path -- with nix-cache down this compounds
   into minutes of noise. scripts/env.sh gains nix_extra_opts(), which
   probes http://nix-cache and nixremote@nix-cache:22 once via plain
   curl/TCP (bypassing Nix's own retry logic entirely -- confirmed
   nix store ping still retries 5x even with a short connect-timeout)
   and exports the decision so create-proxmox-resource.sh and the
   sync-host-keys.sh subprocess it shells out to both reuse it instead
   of probing independently.

2. The actual failure: "archive contains no configuration file". pct
   restore expects a vzdump backup archive with embedded config;
   config.system.build.tarball is a plain CT template tarball -- wrong
   Proxmox mechanism entirely. Fixed to pct create against it as a vztmpl
   template instead, uploaded to /var/lib/vz/template/cache/ rather than
   /var/lib/vz/dump/. This same wrong claim had propagated into
   docs/auto-installer.md, README.md, and CLAUDE.md from when the script
   was first written -- corrected everywhere.

Also: checks for an already-uploaded image on the node (fixed
<flake_target>.tar.xz/.raw naming) before building, skipping build+upload
entirely if found (--force-rebuild to always rebuild).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01La55Nsss8jZ7ZuzUV9mfot
2026-07-20 11:05:26 +10:00
beatzaplentyandClaude Sonnet 5 bd8d93d890 Add sync-host-keys.sh and create-proxmox-resource.sh
sync-host-keys.sh: generates/registers SSH host keys and their
.sops.yaml/secrets/*.yaml recipients for flake targets, idempotently.
--all, <target>, --remove, --regenerate-all-keys, all with --dry-run
(verified zero-side-effect via a sandboxed git-status check across every
mode). Only ever touches anchors with a corresponding host-keys/ file --
&admin and any hand-registered real-host anchor are never listed,
removed, or regenerated. Supersedes running prepare-host-key.sh one host
at a time for any target that already has a flake entry.

create-proxmox-resource.sh: builds a lxc-*/proxmox-* target's
tarball/disk image and creates it on a real Proxmox node, or reconfigures
an existing resource's cores/memory/disk (--modify, always requires
typing the VMID back to confirm). Refuses to create a new resource for a
VMID that already exists, and refuses to duplicate a host identity that
already has a real deployment elsewhere (variables.nix's new
deployedTargets, checked by hostName so it also catches cross-platform
duplicates) unless --allow-duplicate-host is passed. --dry-run throughout.

scripts/env.sh centralizes the Proxmox connection config both scripts
(and future ones) share. Also fixes an unrelated gap found along the way:
proxmox-* Disko image builds write their .raw file straight into the
repo root, and .gitignore never covered it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01La55Nsss8jZ7ZuzUV9mfot
2026-07-20 10:31:06 +10:00
beatzaplentyandClaude Sonnet 5 53b9a64826 Move nix-cache's binary cache signing key into sops
nix-serve's secretKeyFile was a manual, undocumented-outside-a-comment
`nix-store --generate-binary-cache-key` step per host -- easy to miss on
a fresh nix-cache instance (as lxc-nix-cache testing just found: systemd
fails the unit with EXIT_CREDENTIALS when LoadCredential can't find the
source file, which nginx then reports as a 502 from clients). It also
can't be regenerated per-host safely: modules/nix-cache/client.nix
hardcodes every client's trust in one specific public key, so every
nix-cache instance has to share the exact same keypair.

Sourced from secrets/nix-cache.yaml's new cache-priv-key entry instead,
via the same sops-nix pattern every other secret in this repo already
uses. Verified the added value derives to the exact public key
modules/nix-cache/client.nix already trusts before committing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01La55Nsss8jZ7ZuzUV9mfot
2026-07-20 09:49:30 +10:00
beatzaplentyandClaude Sonnet 5 a351cbcf80 Give linode-* hosts a real Disko config, simplify auto-install.sh
Linode provisions and sizes /dev/sda (root) and /dev/sdb (swap) itself
as whole, unpartitioned block devices before the OS ever boots.
modules/disko/linode.nix declares them with destroy = false (skips
Disko's wipe stage for these disks entirely) and a bare filesystem/swap
content type matching that existing layout, so re-running it against an
already-provisioned disk only mkfs/mkswaps if blkid shows it isn't
formatted yet -- never repartitions or destroys data.

With every host reachable through the installer menu now carrying a
Disko config, auto-install.sh no longer needs to probe the flake and
branch between `disko --mode destroy,format,mount` and a bind-mount
fallback -- it just always runs Disko.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01La55Nsss8jZ7ZuzUV9mfot
2026-07-20 08:04:34 +10:00
beatzaplentyandClaude Sonnet 5 2aa625d566 Remove packages.all bundle and the installer's own proxmox-lxc target
Both existed only so the installer could boot as an LXC container and
nixos-install some other host from within it, but lxc-* targets are
already excluded from the install menu (nixos-install can't touch its
own running root filesystem), and now have their own direct tarball
path anyway. That left the installer's own LXC form with no real use
case, and packages.all with only two members worth bundling.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01La55Nsss8jZ7ZuzUV9mfot
2026-07-20 08:04:08 +10:00
beatzaplentyandClaude Sonnet 5 288835db29 Consolidate CI on codex-maintenance.sh; clean up dead scripts and docs
Both check-nixos.yml workflows (GitHub + Gitea) now call
scripts/codex-maintenance.sh instead of a hand-rolled eval-only loop,
closing a real gap: CI previously enforced none of the secret grep,
nixpkgs-fmt, or statix checks that codex-maintenance.sh already runs
locally — nothing was stopping that from regressing. One script now
backs both, instead of two copies that can drift from each other.

codex-maintenance.sh itself is extended to cover buildable surface
that wasn't validated anywhere before: packages.x86_64-linux.*, plus
config.system.build.tarball (lxc-* hosts) and
config.system.build.diskoImagesScript (proxmox-*, excluding the
installer's own proxmox-lxc target, which has no disko config).

Also:
- scripts/prepare-host-key.sh: dropped the redundant
  [path-to-nixos-repo] parameter — it always defaults to the repo the
  script itself lives in now, so a second argument never made sense
  after the nix-auto-installer migration.
- Removed prepare.sh (dead pre-disko manual parted/mkfs/mkswap
  partitioning, fully superseded) and
  scripts/create-linode-installer-disk.sh (incomplete draft for an
  abandoned dd-via-rescue-mode approach; Linode hosts already deploy
  fine through the normal auto-installer flow).
- docs/pxe-boot.md: fixed a stale `nixosConfigurations.pxe-boot` eval
  command (pre-refactor flat name, not a real flake attribute
  anymore) and added a cross-reference to docs/auto-installer.md.
- CLAUDE.md/README.md: full documentation pass reconciling this
  session's changes — modules/installer/, modules/pxe-boot/, the
  LXC/Proxmox image-building deployment paths, corrected the
  password-hash/SSH-key locations in the safety-rules section (both
  had drifted to reference files/paths that no longer exist), and
  added session-workflow guidance to prefer targeted host evals over
  full-repo sweeps for incremental changes (explicitly scoped to
  interactive sessions, not CI).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01La55Nsss8jZ7ZuzUV9mfot
2026-07-20 07:02:23 +10:00
beatzaplentyandClaude Sonnet 5 feee2f1679 Add Proxmox VM disk-image building; fix disko confirmation bypass
modules/disko/proxmox.nix gains imageSize (20G default) and a
per-host imageName (networking.hostName, so every proxmox-* host
produces a distinctly named image instead of an identical main.raw).
This is the same disko.devices config already used to format a real
disk on install, so it's available for every proxmox-* target with no
per-host changes needed:

  nix build .#nixosConfigurations.<host>.config.system.build.diskoImagesScript
  sudo ./result --build-memory 2048

docs/proxmox-images.md covers building, host-key pre-seeding via
disko's --pre-format-files (same host-keys/ workflow as the installer
and LXC tarball paths), and the qm import/attach sequence for
deploying the result to Proxmox.

Also fixes a real bug in auto-install.sh found while testing: the
disko confirmation bypass used --yes, which disko's CLI doesn't
recognize at all (the actual flag is --yes-wipe-all-disks) — so the
"skip confirmation" flag was silently a no-op and the interactive
prompt kept appearing regardless.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01La55Nsss8jZ7ZuzUV9mfot
2026-07-20 07:01:33 +10:00
beatzaplentyandClaude Sonnet 5 120240f14a Fix LXC deployment path and clean up remaining eval warnings
LXC hosts (device busy fix):

modules/platforms/lxc.nix now imports nixpkgs' own
virtualisation/proxmox-lxc.nix, giving every lxc-* host a real
config.system.build.tarball output — a directly `pct restore`-able
Proxmox container image. This is the actual bug fix behind the
"cannot remove real root directory: device busy or in use" error:
lxc-* targets were only reachable through nixos-install, which
bind-mounts / onto /mnt for containers (no raw disk to partition)
and then correctly refuses to modify the filesystem it's currently
running on. auto-install.sh's menu now excludes lxc-* targets
entirely (they deploy via nix build + pct restore instead, see
docs/auto-installer.md) — and, on the same reasoning, also excludes
`installer`/`proxmox-lxc`, which are the installer image's own flake
targets, not deployable hosts.

manageHostName = true keeps host.nix's declared hostnames (upstream's
default would let Proxmox's ambient container config win instead);
privileged = false matches how these containers are actually created.

Eval warnings, now zero across all 19 nixosConfigurations + 4 packages:

- Multiple password options (root/nixos in the installer): nixpkgs'
  own installer profile sets initialHashedPassword = "" for
  passwordless login, conflicting with our explicit hashedPassword.
  Force-nulled the upstream option rather than adopting passwordless
  login, since this image now also boots over LAN PXE with
  PasswordAuthentication enabled.
- boot.zfs.forceImportRoot default value: set explicitly to false
  (matching the two places that already did) in
  modules/common/configuration.nix and modules/installer/common.nix,
  covering every host and the installer alike.
- Deprecated pkgs.system in modules/build-types/gui.nix: switched to
  pkgs.stdenv.hostPlatform.system.

All confirmed non-behavioral where it matters: unrelated hosts'
drvPaths are byte-identical to their pre-existing baselines throughout.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01La55Nsss8jZ7ZuzUV9mfot
2026-07-20 05:58:56 +10:00
beatzaplentyandClaude Sonnet 5 b0ccbb1162 Migrate host-key tooling from nix-auto-installer, bake keys into images
Finishes the nix-auto-installer migration: scripts/prepare-host-key.sh
and the local host-keys/ directory (gitignored, private key material,
never committed — moved as plain files, not through git history)
weren't carried over in the initial migration.

Also implements automatic key staging, replacing the manual
scp-after-boot step:

- modules/installer/host-keys.nix reads host-keys/ via
  builtins.getEnv, which Nix silently returns as "" under normal
  (non---impure) evaluation — the module is a no-op by default, safe
  for CI, until explicitly opted into:

    NIXOS_HOST_KEYS_DIR=$(pwd)/host-keys nix build .#iso --impure

  When built this way every key present gets baked into the image at
  /etc/host-keys/, and auto-install.sh installs whichever one matches
  the flake target selected at install time — no manual per-host scp.

- This deliberately includes the PXE netboot variant, even though
  pxe-boot serves it unauthenticated over LAN HTTP: accepted
  explicitly as a reasonable trade-off for a network that sits behind
  LAN-only infrastructure, not the open internet. auto-install.sh
  still falls back to /root/host-keys (manual scp) if a key isn't
  baked in, so images built without --impure keep working exactly as
  before.

- docs/auto-installer.md replaces nix-auto-installer's README,
  updated for in-repo paths and the new build flow.

Verified: normal `nix eval` (no --impure) evaluates identically across
all 19 nixosConfigurations + 4 packages with zero host-keys/* entries
(CI-unaffected); with --impure + the env var set, all three installer
variants (installer/ISO, proxmox-lxc, pxe) correctly embed every key
in host-keys/.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01La55Nsss8jZ7ZuzUV9mfot
2026-07-20 04:41:34 +10:00
beatzaplentyandClaude Sonnet 5 95d4db5609 Authorize the WSL workstation as a nix-cache remote-builder client
Adds debian@surface's existing SSH key (already used as its admin
key elsewhere in this repo) to vars.remoteBuilderAuthorizedKeys so
nix-cache will accept it as a distributed-build client once deployed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01La55Nsss8jZ7ZuzUV9mfot
2026-07-20 04:08:19 +10:00
beatzaplentyandClaude Sonnet 5 745f4d6fb4 Refresh stale architecture docs
CLAUDE.md's "Composition pattern" section still described the
pre-refactor layout (hosts/<host>/configuration.nix as a thin imports
list, hardware-configuration wired in from flake.nix) from before the
platform x build-type matrix landed. Rewrite it to match the current
mkTarget/host.nix architecture and the module moves from the prior
commit. Also fixes docs/nix-cache.md, which referenced a
modules/nix/ path that never existed in this repo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01La55Nsss8jZ7ZuzUV9mfot
2026-07-20 02:58:25 +10:00
beatzaplentyandClaude Sonnet 5 e76486efbe Refactor flake targets into platform x build-type matrix
Generates all nixosConfigurations from mkTarget(platform, buildType,
hostPath) instead of hand-written per-host blocks, so adding a new
platform or build type is a one-line addition. Per-machine identity
(hostname, hostId, secrets, stateVersion) moves into hosts/<name>/host.nix;
platform-specific config (hardware, boot, networking) into
modules/platforms/*.nix; build-type config (minimal/server/docker/gui/
nix-cache/pxe-boot) into modules/build-types/*.nix.

Old flat targets (nixos, docker, server, nix-cache, nix-minimal, pxe-boot)
are replaced by the 17-target <platform>-<buildtype> matrix; each new
target was verified to evaluate before its old counterpart was removed.
CI workflows and docs/aliases now discover hosts dynamically via
nixosConfigurations attrNames and /etc/flake-target instead of hardcoded
lists, so they can't drift from flake.nix again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 12:03:33 +10:00
beatzaplentyandClaude Sonnet 5 302c3b671f Sync host lists with flake.nix and add CLAUDE.md
Remove the nonexistent kuma host and add the missing linode-minimal
host across README, AGENTS.md, docs, and CI eval workflows so they
match flake.nix's nixosConfigurations. Also add CLAUDE.md with
architecture/safety guidance for future Claude Code sessions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 09:48:05 +10:00
beatzaplenty 1fd0adee54 updated pxe config 2026-06-02 19:00:30 +10:00
beatzaplenty b0d480adb8 updated server root folders 2026-06-02 18:33:13 +10:00
beatzaplenty e78dd516a6 updated ipxe configuration 2026-06-02 18:05:39 +10:00
beatzaplenty 3259391451 updated documentation 2026-06-01 18:22:05 +10:00
beatz174-bit 5f63a48731 ci: automate flake lock updates and host evaluation 2026-05-12 12:28:37 +10:00
beatz174-bit 26eec71b8c Fix pure eval and harden nix script bootstrap 2026-05-12 11:09:23 +10:00