deploy: make the local reinstall path actually work, and fail closed
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>
This commit is contained in:
@@ -16,9 +16,24 @@
|
||||
url = "github:Mic92/sops-nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
# NB: no `inputs.nixpkgs.follows` here — nixos-images has no `nixpkgs`
|
||||
# input (it takes nixos-stable / nixos-unstable), so declaring one only
|
||||
# printed "override for a non-existent input" on every nix command.
|
||||
nixos-images = {
|
||||
url = "github:nix-community/nixos-images";
|
||||
inputs.nixos-stable.follows = "nixpkgs";
|
||||
};
|
||||
# Pinned as an input rather than `nix run github:...` from scripts/deploy.
|
||||
# Both it and disko run at the exact moment a disk is being wiped, so the
|
||||
# revision has to come from flake.lock — reviewed, reproducible, and
|
||||
# resolvable from the local store — instead of whatever upstream master
|
||||
# happens to be that day (which also fails outright with no network).
|
||||
nixos-anywhere = {
|
||||
url = "github:nix-community/nixos-anywhere";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.nixos-stable.follows = "nixpkgs"; # 26.05 already IS stable
|
||||
inputs.disko.follows = "disko";
|
||||
inputs.nixos-images.follows = "nixos-images";
|
||||
};
|
||||
home-manager = {
|
||||
url = "github:nix-community/home-manager/release-26.05";
|
||||
@@ -48,13 +63,21 @@
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, disko, sops-nix, nixos-images, home-manager, mediamanager-nix, authentik-nix, ... }@inputs:
|
||||
outputs = { self, nixpkgs, disko, nixos-anywhere, sops-nix, nixos-images, home-manager, mediamanager-nix, authentik-nix, ... }@inputs:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
in
|
||||
{
|
||||
packages.${system}.tome = nixpkgs.legacyPackages.${system}.callPackage ./pkgs/tome.nix {
|
||||
src = inputs.tome;
|
||||
packages.${system} = {
|
||||
tome = nixpkgs.legacyPackages.${system}.callPackage ./pkgs/tome.nix {
|
||||
src = inputs.tome;
|
||||
};
|
||||
|
||||
# Re-exported so `./scripts/deploy` can run them as `nix run .#disko` /
|
||||
# `nix run .#nixos-anywhere`, at the revision flake.lock pins. See the
|
||||
# nixos-anywhere input above for why that matters.
|
||||
disko = disko.packages.${system}.disko;
|
||||
nixos-anywhere = nixos-anywhere.packages.${system}.nixos-anywhere;
|
||||
};
|
||||
|
||||
nixosConfigurations = {
|
||||
@@ -185,6 +208,16 @@
|
||||
networking.hostName = "homelab-installer";
|
||||
environment.systemPackages = [ pkgs.git ];
|
||||
|
||||
# installation-cd-minimal leaves experimental-features unset, so
|
||||
# the ISO's nix.conf has no `nix-command`/`flakes` at all (unlike
|
||||
# the nixos-images kexec installer, which sets
|
||||
# extra-experimental-features itself — which is why the same
|
||||
# `install <config> localhost` branch works after kexec-local but
|
||||
# not here). Without this, both `nix run .#disko` and
|
||||
# `nixos-install --flake` die with "experimental Nix feature
|
||||
# 'nix-command' is disabled".
|
||||
nix.settings.experimental-features = [ "nix-command" "flakes" ];
|
||||
|
||||
# Fresh clone of a PUBLIC repo — no credentials baked into the
|
||||
# ISO. require_tracked() in scripts/deploy still works fine here
|
||||
# (this IS a real git checkout, unlike the old baked-`self`
|
||||
@@ -219,17 +252,51 @@
|
||||
after = [ "homelab-checkout.service" ];
|
||||
requires = [ "homelab-checkout.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ pkgs.gnugrep ];
|
||||
path = [ pkgs.gnugrep pkgs.util-linux pkgs.coreutils pkgs.git ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
# systemd does NOT set $HOME for a system service without User=
|
||||
# (systemd.exec(5): SetLoginEnvironment= "defaults to true if
|
||||
# User=, DynamicUser= or PAMName= are set, false otherwise"), and
|
||||
# scripts/deploy runs under `set -u`. Without this the whole
|
||||
# unattended run died on the bare $HOME expansion with an
|
||||
# "unbound variable" that reads like a bug in the script.
|
||||
serviceConfig.Environment = "HOME=/root";
|
||||
script = ''
|
||||
cfg=$(grep -o 'homelab\.install=[^ ]*' /proc/cmdline | cut -d= -f2 || true)
|
||||
if [ -z "$cfg" ]; then
|
||||
echo "no homelab.install= on the kernel cmdline — nothing to auto-install"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# The host key scripts/deploy seeds /etc/ssh with (so sops can
|
||||
# decrypt on boot #1) cannot live in this ISO: it is built from
|
||||
# a PUBLIC repo and the private keys are deliberately off-repo.
|
||||
# local_install_prepare_and_reboot() therefore drops it on the
|
||||
# boot partition and passes that partition's PARTUUID here.
|
||||
# That copy dies with the disko wipe a few minutes later.
|
||||
keypart=$(grep -o 'homelab\.keypart=[^ ]*' /proc/cmdline | cut -d= -f2 || true)
|
||||
if [ -n "$keypart" ]; then
|
||||
mkdir -p /run/homelab-key
|
||||
if mount -o ro "/dev/disk/by-partuuid/$keypart" /run/homelab-key; then
|
||||
src=/run/homelab-key/homelab-installer
|
||||
if [ -f "$src/ssh_host_ed25519_key" ]; then
|
||||
echo "picking up $cfg's host key from PARTUUID=$keypart"
|
||||
install -Dm600 "$src/ssh_host_ed25519_key" \
|
||||
"/root/.config/homelab/$cfg/ssh_host_ed25519_key"
|
||||
install -Dm644 "$src/ssh_host_ed25519_key.pub" \
|
||||
"/root/.config/homelab/$cfg/ssh_host_ed25519_key.pub"
|
||||
else
|
||||
echo "warning: no host key at $src — the install will refuse" >&2
|
||||
fi
|
||||
umount /run/homelab-key
|
||||
else
|
||||
echo "warning: could not mount PARTUUID=$keypart for the host key" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "auto-installing $cfg (homelab.install= on the kernel cmdline)"
|
||||
cd /root/homelab
|
||||
exec ./scripts/deploy install "$cfg" localhost
|
||||
exec ./scripts/deploy install "$cfg" localhost --yes
|
||||
'';
|
||||
};
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user