feat: custom kexec installer for headless install on read-only-root (ZimaOS)

- add nixos-images input; nixosConfigurations.kexec bakes in the ssh login key
- build via config.system.build.kexecInstallerTarball
- deploy: ./deploy kexec <host> streams the installer to /tmp and kexecs
- works around ZimaOS RO root where nixos-anywhere ssh-copy-id fails

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
erik
2026-07-12 22:14:35 +02:00
co-authored by Claude Opus 4.8
parent 95e945fa6f
commit f581203b57
3 changed files with 102 additions and 4 deletions
+26 -3
View File
@@ -1,8 +1,12 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Deploy the jupiter NixOS config. # Deploy the jupiter NixOS config.
# #
# ./deploy install <ip> first install onto a fresh box (nixos-anywhere). # ./deploy kexec <host> headless-only: for a read-only-root box (ZimaOS)
# Wipes the OS disk. Ships the sops host key. # where nixos-anywhere can't ssh-copy-id. Uploads a
# kexec installer (our SSH key baked in) to /tmp and
# boots into it. Then run `install`.
# ./deploy install <ip> first install onto a fresh box / running installer
# (nixos-anywhere). Wipes the OS disk. Ships host key.
# ./deploy [switch] [host] rebuild + activate on a running jupiter (default). # ./deploy [switch] [host] rebuild + activate on a running jupiter (default).
# ./deploy boot [host] stage for next boot, don't activate now. # ./deploy boot [host] stage for next boot, don't activate now.
# ./deploy test [host] activate without adding a boot entry. # ./deploy test [host] activate without adding a boot entry.
@@ -18,11 +22,30 @@ HOSTKEY="$HOME/.config/homelab/jupiter/ssh_host_ed25519_key"
cmd="${1:-switch}" cmd="${1:-switch}"
case "$cmd" in case "$cmd" in
switch|boot|test|install) shift || true ;; switch|boot|test|install|kexec) shift || true ;;
*) cmd="switch" ;; *) cmd="switch" ;;
esac esac
case "$cmd" in case "$cmd" in
kexec)
host="${1:-}"
[ -n "$host" ] || { echo "usage: ./deploy kexec <ip-or-host>" >&2; exit 1; }
echo ">> building kexec installer (SSH key baked in)"
nix build .#nixosConfigurations.kexec.config.system.build.kexecInstallerTarball \
-o result-kexec
tb="$(ls result-kexec/*.tar.gz | head -1)"
echo ">> streaming installer to root@$host and kexec-ing (enter root password)"
echo " Extracted to /tmp (tmpfs); no compressed copy stored. SSH drops as"
echo " the box jumps into the RAM installer. Disks are untouched."
# Stream the tarball straight into remote tar to avoid a second 522MB copy.
ssh "root@$host" 'mkdir -p /tmp/k && tar -C /tmp/k -xzf - && /tmp/k/kexec/run' < "$tb" || true
echo ">> box is kexec-ing. Wait ~1-2 min for the installer + network, then:"
echo " ./deploy install $host"
;;
install) install)
host="${1:-}" host="${1:-}"
[ -n "$host" ] || { echo "usage: ./deploy install <ip-or-host>" >&2; exit 1; } [ -n "$host" ] || { echo "usage: ./deploy install <ip-or-host>" >&2; exit 1; }
Generated
+54
View File
@@ -20,6 +20,59 @@
"type": "github" "type": "github"
} }
}, },
"nixos-images": {
"inputs": {
"nixos-stable": "nixos-stable",
"nixos-unstable": "nixos-unstable"
},
"locked": {
"lastModified": 1783593136,
"narHash": "sha256-zy5an02BdZ65OgVKdRkz2TpbdBrsW+uQD7AA2wLuiTM=",
"owner": "nix-community",
"repo": "nixos-images",
"rev": "803f28511c7d5f39f2537c342122fd94b8e1d519",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixos-images",
"type": "github"
}
},
"nixos-stable": {
"locked": {
"lastModified": 1783389287,
"narHash": "sha256-0xIy4dVLqq47rA+mRy0hXDfjhQd4E5PoIns/RmB7nR4=",
"ref": "nixos-26.05",
"rev": "0ad6f47ea4fe188f4bc8f0380f93ae8523337c6c",
"shallow": true,
"type": "git",
"url": "https://github.com/NixOS/nixpkgs"
},
"original": {
"ref": "nixos-26.05",
"shallow": true,
"type": "git",
"url": "https://github.com/NixOS/nixpkgs"
}
},
"nixos-unstable": {
"locked": {
"lastModified": 1782175435,
"narHash": "sha256-EMzXKmnOtBQ2MnvpiNOm7E+kOMvdPrIKaeg52Tip2Uk=",
"ref": "nixpkgs-unstable",
"rev": "89570f24e97e614aa34aa9ab1c927b6578a43775",
"shallow": true,
"type": "git",
"url": "https://github.com/NixOS/nixpkgs"
},
"original": {
"ref": "nixpkgs-unstable",
"shallow": true,
"type": "git",
"url": "https://github.com/NixOS/nixpkgs"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1783703440, "lastModified": 1783703440,
@@ -39,6 +92,7 @@
"root": { "root": {
"inputs": { "inputs": {
"disko": "disko", "disko": "disko",
"nixos-images": "nixos-images",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"sops-nix": "sops-nix" "sops-nix": "sops-nix"
} }
+22 -1
View File
@@ -11,9 +11,13 @@
url = "github:Mic92/sops-nix"; url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
nixos-images = {
url = "github:nix-community/nixos-images";
inputs.nixpkgs.follows = "nixpkgs";
};
}; };
outputs = { self, nixpkgs, disko, sops-nix, ... }@inputs: outputs = { self, nixpkgs, disko, sops-nix, nixos-images, ... }@inputs:
let let
system = "x86_64-linux"; system = "x86_64-linux";
in in
@@ -39,6 +43,23 @@
specialArgs = { inherit inputs; }; specialArgs = { inherit inputs; };
modules = [ ./jupiter/vm.nix ]; modules = [ ./jupiter/vm.nix ];
}; };
# Custom kexec installer with our SSH key baked in, for headless install
# onto a box with a read-only root (ZimaOS) where nixos-anywhere can't
# ssh-copy-id. Build the tarball:
# nix build .#nixosConfigurations.kexec.config.system.build.kexecInstallerTarball
# then scp it to the target's writable /tmp and run kexec/run (see README).
kexec = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
nixos-images.nixosModules.kexec-installer
({ ... }: {
users.users.root.openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGZpkPVhzi1zG5JI9hWyUgdyvNIQbp4ts4jw3idpMhhN erik@laptop"
];
})
];
};
}; };
}; };
} }