From 48255ba1da99759d0e0b288228690210d17a373a Mon Sep 17 00:00:00 2001 From: beatzaplenty Date: Mon, 20 Jul 2026 08:04:34 +1000 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01La55Nsss8jZ7ZuzUV9mfot --- README.md | 5 ++- docs/auto-installer.md | 24 +++++++++++--- modules/disko/linode.nix | 35 ++++++++++++++++++++ modules/hardware-configuration/vm/linode.nix | 13 +++----- modules/installer/common.nix | 21 ++++-------- modules/platforms/linode.nix | 1 + 6 files changed, 70 insertions(+), 29 deletions(-) create mode 100644 modules/disko/linode.nix diff --git a/README.md b/README.md index c1fb673..016901b 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,10 @@ 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`. + `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 for `linode-*`, whose disks + Linode itself provisions ahead of time). - `lxc-*` targets: not installed at all — build a ready-to-run container tarball and `pct restore` it directly. `docs/auto-installer.md` covers why (and the installer's menu excludes them for the same reason). diff --git a/docs/auto-installer.md b/docs/auto-installer.md index 8cad6a8..8a30608 100644 --- a/docs/auto-installer.md +++ b/docs/auto-installer.md @@ -126,10 +126,24 @@ next, where you can `scp` a key in after boot, same as before this migration. ## Storage Disk partitioning is handled by Disko — the installer has no hardcoded -`parted`/`mkfs`/`mkswap`/`mount` commands. Each host defines its own layout -via `disko.devices`; a host without one is installed in place via bind-mount -(this is how every LXC container target works, since they have no raw block -device to partition). +`parted`/`mkfs`/`mkswap`/`mount` commands, and `auto-install.sh` runs +`disko --mode destroy,format,mount` unconditionally, no branching on whether +the target has a Disko config. Every host reachable through this menu has +one: + +- `proxmox-*` (`modules/disko/proxmox.nix`): a real GPT partition table + (ESP + swap + root) on `/dev/sda`. +- `linode-*` (`modules/disko/linode.nix`): Linode provisions and sizes + `/dev/sda`/`/dev/sdb` itself as whole, unpartitioned block devices before + the OS ever boots, so this declares them with `destroy = false` (skips + disko's wipe stage for these disks entirely — see the option's own docs) + and a bare `filesystem`/`swap` content type with no partition table, and + the format step it does run only calls `mkfs`/`mkswap` if `blkid` shows + the device isn't already formatted — a re-run against an + already-provisioned Linode disk is a no-op, not a wipe. + +`lxc-*` is the only category without one — it's excluded from this menu +entirely (see "LXC hosts" above), so it never reaches this code path. ## Installer process @@ -138,7 +152,7 @@ device to partition). 1. Queries `nixosConfigurations` from this flake over the network (`git+https:///beatzaplenty/nixos.git`) — this happens at *install* time, not build time, so a generic installer image always sees whatever hosts are currently committed, without needing a rebuild. 2. Presents them as a menu; confirms the choice. 3. Skips the `nix-cache` substituter when installing a `nix-cache` host itself (consistent with that host's own runtime config). -4. Runs `disko --mode destroy,format,mount` if the target has a Disko config, otherwise bind-mounts `/` onto `/mnt`. +4. Runs `disko --mode destroy,format,mount` (see "Storage" above — every host reachable through this menu has a Disko config, so this is unconditional). 5. Installs the target's SSH host key from `/etc/host-keys` or `/root/host-keys` (see "Host keys" above). 6. Runs `nixos-install --flake # --no-root-password`. 7. Cleans up and reboots. diff --git a/modules/disko/linode.nix b/modules/disko/linode.nix new file mode 100644 index 0000000..92b278f --- /dev/null +++ b/modules/disko/linode.nix @@ -0,0 +1,35 @@ +_: + +{ + # Linode provisions and sizes these disks itself (via the Linode + # dashboard/API) before the OS ever boots, and presents them as whole, + # unpartitioned block devices — /dev/sda is the root filesystem directly, + # /dev/sdb is swap directly, no partition table on either. Nothing here + # should ever repartition or resize them: + # - `destroy = false` skips each disk entirely during disko's destroy + # stage (see disko's disk.destroy option) — no wipefs, ever. + # - the filesystem content type's own create step only runs mkfs if the + # device isn't already formatted (checked via `blkid`), so re-running + # this against an already-provisioned Linode disk is a no-op. + disko.devices.disk = { + main = { + device = "/dev/sda"; + destroy = false; + + content = { + type = "filesystem"; + format = "ext4"; + mountpoint = "/"; + }; + }; + + swap = { + device = "/dev/sdb"; + destroy = false; + + content = { + type = "swap"; + }; + }; + }; +} diff --git a/modules/hardware-configuration/vm/linode.nix b/modules/hardware-configuration/vm/linode.nix index 663f777..0276b70 100644 --- a/modules/hardware-configuration/vm/linode.nix +++ b/modules/hardware-configuration/vm/linode.nix @@ -33,14 +33,11 @@ }; }; - fileSystems."/" = - { - device = "/dev/sda"; - fsType = "ext4"; - }; - - swapDevices = - [{ device = "/dev/sdb"; }]; + # fileSystems."/" and swapDevices are now owned by disko + # (../disko/linode.nix, imported from ../platforms/linode.nix) — same + # /dev/sda root + /dev/sdb swap layout, declared there instead so disko's + # (idempotent, non-destructive — see that file) format/mount scripts stay + # in sync with what NixOS actually mounts. nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; } diff --git a/modules/installer/common.nix b/modules/installer/common.nix index fde1ee0..2951068 100644 --- a/modules/installer/common.nix +++ b/modules/installer/common.nix @@ -119,21 +119,12 @@ nix_extra_opts+=(--option substituters "https://cache.nixos.org/") fi - if nix eval --refresh --json --extra-experimental-features "flakes nix-command" "''${nix_extra_opts[@]}" "''${FLAKE_BASE_URL}#nixosConfigurations.''${choice}.config.disko.devices.disk.main.device" >/dev/null 2>&1; then - disko --mode destroy,format,mount \ - --flake "''${FLAKE_BASE_URL}#''${choice}" "''${nix_extra_opts[@]}" --yes-wipe-all-disks - else - # No raw disk to partition — true for every LXC container, which has - # no block device visible from inside it, only its already-mounted - # root filesystem. nixos-install defaults to installing into /mnt; if - # nothing points /mnt at the real root, the new system gets built and - # written to a disconnected empty directory, never actually becomes - # bootable, and the container silently keeps running this installer - # environment forever. Bind-mount / onto /mnt so the install actually - # lands on the filesystem this container boots from. - echo "Selected host has no Disko configuration — installing in place via bind mount." - mount --bind / /mnt - fi + # Every host reachable through this menu has a Disko config (lxc-* + # is filtered out above, and is the only category that doesn't — + # see docs/auto-installer.md), so this can run unconditionally: no + # need to probe the flake first and branch on whether Disko applies. + disko --mode destroy,format,mount \ + --flake "''${FLAKE_BASE_URL}#''${choice}" "''${nix_extra_opts[@]}" --yes-wipe-all-disks # sops-nix derives this host's decryption key from its own SSH host key # at *activation* time, which runs before systemd would otherwise diff --git a/modules/platforms/linode.nix b/modules/platforms/linode.nix index 08ede8d..4c6feb9 100644 --- a/modules/platforms/linode.nix +++ b/modules/platforms/linode.nix @@ -3,6 +3,7 @@ { imports = [ ../hardware-configuration/vm/linode.nix + ../disko/linode.nix ]; networking = {