summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlejandro Soto <alejandro@34project.org>2026-03-08 21:10:11 -0600
committerAlejandro Soto <alejandro@34project.org>2026-03-08 21:10:11 -0600
commitdb61b9e84dad7cc234f489c13f0491a12625613e (patch)
tree523e36bfa44d91b9b3c89649b70294337d2dceb0
parent8ce8eed4869436819b5cad84750e486572033dae (diff)
sys/boot/pxe: initial support
-rw-r--r--sys/boot/chain.nix14
-rw-r--r--sys/boot/default.nix1
-rw-r--r--sys/boot/pxe.nix187
-rw-r--r--sys/boot/tpm.nix4
4 files changed, 200 insertions, 6 deletions
diff --git a/sys/boot/chain.nix b/sys/boot/chain.nix
index 43edcb4..b6a9ab2 100644
--- a/sys/boot/chain.nix
+++ b/sys/boot/chain.nix
@@ -9,7 +9,7 @@ with lib; let
in {
options.local.boot = {
loader = mkOption {
- type = types.enum ["none" "grub" "systemd-boot"];
+ type = types.enum ["none" "out-of-band" "grub" "systemd-boot"];
};
kernel = mkOption {
@@ -22,7 +22,11 @@ in {
kernelPackages = cfg.kernel;
loader =
- if cfg.loader == "grub"
+ if cfg.loader == "none" || cfg.loader == "out-of-band"
+ then {
+ grub.enable = false;
+ }
+ else if cfg.loader == "grub"
then {
grub = {
enable = true;
@@ -30,12 +34,14 @@ in {
efiSupport = true;
};
}
- else {
+ else if cfg.loader == "systemd-boot"
+ then {
systemd-boot = {
enable = true;
editor = true;
};
- };
+ }
+ else throw "unexpected config.local.boot.loader setting: ${cfg.loader}";
};
};
}
diff --git a/sys/boot/default.nix b/sys/boot/default.nix
index 4580cba..3cbbb6f 100644
--- a/sys/boot/default.nix
+++ b/sys/boot/default.nix
@@ -7,6 +7,7 @@
./fscrypt.nix
./impermanence.nix
./namespaced.nix
+ ./pxe.nix
./secure-boot.nix
./stack
./tpm.nix
diff --git a/sys/boot/pxe.nix b/sys/boot/pxe.nix
new file mode 100644
index 0000000..e25ba9d
--- /dev/null
+++ b/sys/boot/pxe.nix
@@ -0,0 +1,187 @@
+{
+ config,
+ lib,
+ ...
+}:
+with lib; let
+ cfg = config.local.boot.pxe;
+in {
+ options.local.boot.pxe = {
+ enable = mkEnableOption "PXE netboot";
+
+ setupMode = mkOption {
+ type = types.bool;
+ default = false;
+ };
+
+ initrdInterface = mkOption {
+ type = types.str;
+ };
+
+ linkLocal6 = mkOption {
+ type = types.str;
+ };
+
+ networkDriver = mkOption {
+ type = types.str;
+ };
+
+ mac = mkOption {
+ type = types.str;
+ };
+
+ panicTimeout = mkOption {
+ type = types.nullOr types.ints.u16;
+ default = 30;
+ };
+ };
+
+ # Based on nixpkgs/nixos/modules/installer/netboot/netboot.nix
+ config = mkIf cfg.enable {
+ boot = {
+ initrd = {
+ kernelModules = [cfg.networkDriver];
+
+ network = {
+ enable = true;
+ flushBeforeStage2 = true;
+ ssh = {
+ enable = true;
+ ignoreEmptyHostKeys = true;
+ };
+ };
+
+ preFailCommands = mkIf (!cfg.setupMode) ''
+ echo "init stage1 failed in unattended mode, rebooting"
+ reboot -f
+ '';
+
+ postResumeCommands = ''
+ store_img_dir="/nix/.ro-store-img"
+ store_img="$store_img_dir/nix-store.squashfs"
+
+ mkdir -p "$store_img_dir"
+ mount -t tmpfs -o mode=0700,nr_inodes=2,huge=within_size,nodev,nosuid tmpfs "$store_img_dir"
+
+ ip link set up ${cfg.initrdInterface}
+ ip addr add ${cfg.linkLocal6}/64 dev ${cfg.initrdInterface}
+
+ recv_init_timeout=120
+ echo -n "Waiting up to ''${recv_init_timeout}s for $store_img"
+
+ t="$recv_init_timeout"
+ while true; do
+ echo -n . | nc -u -w1 -s ${cfg.linkLocal6}%${cfg.initrdInterface} fe80::b007:b007%${cfg.initrdInterface} 1792 >/dev/null 2>&1 &
+
+ if [ -e "$store_img.tmp" ]; then
+ kill -x nc
+ break
+ elif [ "$t" -le 0 ]; then
+ echo "timed out"
+ fail
+ fi
+
+ # sleep builtin de ash retorna cuando nc termina (lo cual puede pasar muchas veces debido a IPv6 DAD)
+ /bin/sleep 1
+ echo -n .
+ t=$((t - 1))
+ done
+
+ recv_poll=60
+ recv_timeout=1800
+
+ t=0
+ last_mtime=""
+ poll_time="$recv_poll"
+ while true; do
+ if [ -e "$store_img" ]; then
+ break
+ elif [ "$t" -ge "$recv_timeout" ]; then
+ echo "Store image upload timed out"
+ fail
+ fi
+
+ sleep 1
+ t=$((t + 1))
+ poll_time=$((poll_time - 1))
+
+ if [ "$poll_time" -le 0 ]; then
+ current_size=$(stat -c '%s' "$store_img.tmp" || echo -)
+ current_mtime=$(stat -c '%y' "$store_img.tmp" || echo final)
+ echo "Store upload: $current_mtime: $current_size bytes"
+
+ if [ "$current_mtime" = "$last_mtime" ]; then
+ echo "Store image upload stalled, image not modified in ''${recv_poll}s"
+ fail
+ fi
+
+ last_mtime="$current_mtime"
+ poll_time="$recv_poll"
+ fi
+ done
+
+ sleep 1
+ kill -x sshd
+
+ chmod 0500 "$store_img_dir"
+ chmod 0400 "$store_img"
+ chattr +i "$store_img"
+ mount -o remount,ro "$store_img_dir"
+
+ store_size=$(stat -c '%s' "$store_img")
+ echo "Store image upload complete, final size is $store_size bytes"
+
+ ln -sf "$store_img" /dev/nix-store
+ '';
+
+ postMountCommands = ''
+ mkdir -p "/mnt-root$store_img_dir"
+ mount --move "$store_img_dir" "/mnt-root$store_img_dir"
+ '';
+ };
+
+ kernelParams =
+ optional (cfg.panicTimeout != null) "panic=${toString cfg.panicTimeout}"
+ ++ optional cfg.setupMode "boot.shell_on_fail";
+ };
+
+ fileSystems = {
+ "/" = {
+ fsType = "tmpfs";
+ neededForBoot = true;
+
+ options = ["mode=0755"];
+ };
+
+ "/nix/store" = {
+ fsType = "overlay";
+ neededForBoot = true;
+
+ overlay = {
+ lowerdir = ["/nix/.ro-store"];
+ upperdir = "/nix/.rw-store/store";
+ workdir = "/nix/.rw-store/work";
+ };
+ };
+
+ "/nix/.ro-store" = {
+ fsType = "squashfs";
+ neededForBoot = true;
+
+ device = "/dev/nix-store";
+ options = ["loop" "threads=multi"];
+ };
+
+ "/nix/.rw-store" = {
+ fsType = "tmpfs";
+ neededForBoot = true;
+
+ options = ["mode=0755"];
+ };
+ };
+
+ local = {
+ boot.loader = "out-of-band";
+ };
+ };
+}
diff --git a/sys/boot/tpm.nix b/sys/boot/tpm.nix
index ecc115b..da6f73a 100644
--- a/sys/boot/tpm.nix
+++ b/sys/boot/tpm.nix
@@ -93,8 +93,8 @@ in {
config = mkIf cfg.enable {
assertions = [
{
- assertion = config.local.boot.efi.enable;
- message = "TPM2 requires EFI";
+ assertion = cfg.initrd.enable -> config.local.boot.efi.enable;
+ message = "TPM2 in initrd requires EFI";
}
{
assertion = cfg.initrd.enable -> cfg.enable;