{ 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"; }; }; }