Running sops --decrypt/edit_secrets --show and displaying the result
puts every plaintext secret in the file wherever that output lands,
not just the one value being checked. Point at `sops --set` instead
for adding/changing a single value non-interactively.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
terra booted the staged installer this time but dropped to an emergency
shell: stage 1 mounted /sysroot, then timed out on /sysroot/nix/.ro-store
waiting for /dev/disk/by-label/nixos-minimal-26.05-x86_64.
findiso= is handled only in the scripted stage-1-init.sh, which loop-mounts
the file the param points at and symlinks it to /dev/root. The systemd initrd
— the default since 26.05 — has no findiso handling at all: iso-image.nix
mounts /iso directly from /dev/disk/by-label/<volumeID>, a label that only
exists when the ISO is the physical boot medium. Booted as kernel + initrd
off the ESP with the iso as a plain file on another partition, that label
never appears, so the store squashfs never mounts.
The entire `install <config> localhost` path is built on findiso, so pin the
installer to script stage 1. Verified: /iso device flips to /dev/root,
root=LABEL=... is added to the params, the rebuilt initrd's /init is
stage-1-init.sh and carries the findiso logic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`install <config> localhost` assumed systemd-boot. terra's CachyOS boots
Limine, so it stopped at "/boot/loader/entries doesn't exist" — the check
added in e538788 doing its job, but with nowhere to go.
Limine cannot help here at all: `bootctl status` lists it as
`✗ One-shot entry control`, and CachyOS's pacman hooks regenerate
limine.conf regardless. So drop below the bootloader entirely and use the
firmware's own BootNext, pointing at a temporary UEFI entry that
EFI-stub-boots the installer kernel straight off the ESP. That keeps the
property which makes this safe to attempt: BootNext is spent by that one
boot, so a failed try still comes back on the normal bootloader.
- picked at runtime: systemd-boot loader entry when $BOOT/loader/entries
exists, else arm_efi_bootnext(). jupiter/neptun and terra-after-install
keep the systemd-boot path.
- `efibootmgr --create-only`, NOT `--create`: the latter pushes the entry
to the front of BootOrder, which would make a wiped installer the
permanent default if the install died halfway.
- the EFI stub loads initrd= from the volume it was loaded from, so this
mode stages on --print-esp-path rather than --print-boot-path.
- stale entries from an earlier attempt are removed before adding one, and
homelab-auto-install.service deletes the entry as soon as it boots, so
nothing lingers in NVRAM pointing at a reformatted partition.
- label matching is EXACT ("Homelab Installer"); a prefix match would have
deleted this box's Windows or Limine entry.
Verified against terra's real NVRAM (read-only): the label parser picks out
Limine/UEFI OS/Windows by exact name and rejects prefixes, and both branches
run end-to-end under stubs — BootNext mode emits the right --disk/--part,
loader path and initrd= cmdline, systemd-boot mode still writes its entry and
never calls efibootmgr.
README/CLAUDE.md corrected: terra runs Limine, not systemd-boot.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The `install <config> localhost` auto-path added in 0ea9020 could not have
completed. Fixed three blockers plus the guard that was silently not guarding.
Inside installer-iso the run died before doing anything:
- systemd sets no $HOME for a service without User= (SetLoginEnvironment=
defaults to false), and this script runs under `set -u`, so it aborted on
the bare $HOME with "unbound variable". Added $KEYDIR + Environment=HOME.
- the host key it needs to seed /etc/ssh isn't on the ISO at all — that is
built from a public repo and carries no credentials on purpose. It now
travels on the boot partition, located via homelab.keypart=<PARTUUID> on
the kernel cmdline, and dies with the disko wipe minutes later. Without
it sops can't decrypt on boot #1 and mutableUsers locks darman for good.
- installation-cd-minimal leaves experimental-features unset, so both
`nix run` and `nixos-install --flake` failed. (The nixos-images kexec
installer sets them itself, which is why the same branch worked after
kexec-local but not from the ISO.)
The staging-dir guard passed everything on btrfs: findmnt prints the
subvolume as /dev/sdb2[/@], lsblk can't open that, and an empty parent was
treated as "different disk" — so it allowed staging the iso on the very disk
disko then wiped. terra's current CachyOS root is exactly that layout. Now
uses --nofsroot, resolves EVERY whole-disk ancestor (LVM/RAID span several:
/mnt/ssd_01 -> sdd+sde), and treats "can't tell" as a hard error. btrfs
staging is refused outright — stage-1 mounts a btrfs volume's top level, so
an iso inside a subvolume is unreachable.
findiso= lost its leading slash whenever the staging mountpoint was /,
giving /findisovar/tmp/x.iso and an emergency shell after the reboot.
Also:
- confirm before rebooting, like flash/kexec-local already do; --yes skips
it and is what the ISO passes itself
- $BOOT from `bootctl --print-boot-path`, not a hardcoded /boot
- free-space checks on both target partitions before the ~1GB copy
- `nix run .#disko` / `.#nixos-anywhere` from locked inputs instead of
github:... master-of-the-day, resolved while a disk is being wiped
- one_match warns instead of silently taking [0]; require_tracked covers
every hosts/<config>/*.nix; flash traps its mount
- drop nixos-images' `inputs.nixpkgs.follows` — that input doesn't exist,
it only printed a warning on every nix command
Verified: the prepare path exercised under stubs against this box's real
disks (btrfs-on-OS-disk, tmpfs, LVM, subdirectory), shellcheck clean, all
six configs evaluate, checks.kexec-local still passes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
./scripts/deploy install <config> localhost now branches on is_live_installer()
(checks uname -n): outside a live installer it builds installer-iso, stages
its kernel/initrd on the ESP and the iso file on a disk the caller picks
(never auto-picked — the wrong disk here is destroyed mid-install), writes a
systemd-boot one-shot findiso= entry with homelab.install=<config> on the
kernel cmdline, and does a real systemctl reboot (not kexec — terra's
kexec-local hang is specifically in kexec's device-shutdown pass, a real ACPI
reboot never runs that code at all).
installer-iso gains homelab-auto-install.service: once homelab-checkout.service
clones the repo, it reads homelab.install= back off /proc/cmdline and re-runs
the identical deploy command itself, now genuinely inside the installer, so
it takes the disko+nixos-install branch instead of preparing again. The whole
reinstall is one command and unattended after the first reboot.
Also: every root-requiring path (kexec-local, the new prepare-and-reboot
branch, the disko+nixos-install branch) self-elevates via a require_root()
helper that re-execs the original invocation under sudo -E, instead of dying
and asking the caller to prefix sudo themselves. Uses an absolute script path
captured before the script's own cd, so the re-exec is correct regardless of
how it was invoked.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed on real hardware: kexec's device_shutdown() pass runs (SCSI disks
sync fine in the log) then the machine goes dark for good — journalctl
--list-boots showed a ~15min gap before the next boot, a genuine hang needing
a manual power cycle, not a slow jump. Near-certainly amdgpu (RX 6800 XT):
discrete AMD GPUs are known to hang during kexec's device-shutdown pass with
no clean handoff before the jump, same class of issue as jupiter's
reboot=pci warm-reboot workaround, just fatal here instead of slow.
README's terra install section now leads with the USB installer path instead
(build ISO, dd to USB, rsync the repo over, disko + nixos-install locally).
CLAUDE.md's gotchas list gets the same warning. installer-iso is renamed from
jupiter-installer to homelab-installer since it's genuinely host-agnostic,
and now ships git.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kexec-local could never actually jump. nixos-images' kexec-run.sh ends with
`nohup sh -c "sleep 6 && $SCRIPT_DIR/kexec -e" &` and returns immediately, so
the EXIT trap's `rm -rf "$stage"` deleted the kexec binary out from under the
sleeping shell. The box stayed on the old kernel and it looked like a slow boot.
Clear the trap before jumping, verify /sys/kernel/kexec_loaded, then sleep past
the timer.
Preflight everything before the point of no return, since this jumps the machine
you are typing at: CONFIG_KEXEC, kernel lockdown, exec-capable staging dir, free
space, RAM vs image size, and that the tarball holds all five expected files.
Stage on /var/tmp rather than /tmp because kexec-run.sh appends to initrd in
place and execs from that directory. sync before jumping (kexec -e skips
unmount). Confirmation prompt naming the host, since run in the wrong terminal
this kexecs the laptop; --yes skips it.
Drop the ssh-keygen -R added to the remote kexec path: kexec-run.sh copies
/etc/ssh/ssh_host_* into the appended initrd and restore-remote-access.nix
installs them back, so the host key survives the jump.
Proton Pass items are now keyed by <config> instead of <host>, since the address
is incidental and the config name is stable. kexec therefore takes <config>
<host>. Resolve titles among --filter-state active items first: a trashed item
with the same title shadowed the active one and returned an empty password,
which is indistinguishable from "no entry" and silently fell back to prompting
(hit on darman@neptun).
Other fixes: replace `ls glob | head -1` (returns empty with exit 0 on no match)
with a helper that dies; guard against untracked hosts/<config> since flakes
ignore untracked files; feed the sudo password more than once under setsid;
handle empty arrays under set -u; tolerate empty FSTYPE in the SD-card root
partition lookup; preflight zstdcat/dd/lsblk before the destructive dd; list
image and flash in the usage strings.
Add checks.x86_64-linux.kexec-local, a VM test driving the real script. It is
the only way to exercise kexec-local, which cannot be rehearsed on hardware. It
asserts the box left the old kernel, returned as nixos-installer, lost its old
/run, and kept its ssh host key. HOMELAB_KEXEC_TARBALL lets it reuse a prebuilt
installer instead of building ~500MB inside the guest.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Group service modules by category (media, network, vpn, identity,
dev, desktop) to make the growing services/ dir easier to navigate.
containers.nix stays at the top level since it's a shared backend,
not a single-category service.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>