diff --git a/docs/pxe-boot.md b/docs/pxe-boot.md index 5d1fe18..5b8cc5f 100644 --- a/docs/pxe-boot.md +++ b/docs/pxe-boot.md @@ -15,6 +15,7 @@ rescue/inspection use. - TFTP root for first-stage bootloaders: `/srv/pxe/tftp` - iPXE entry script: `/srv/pxe/http/boot.ipxe` - Generated iPXE menu: `/srv/pxe/http/menu.ipxe` +- Debian Minimal iPXE script: `/srv/pxe/http/debian.ipxe` - SystemRescue iPXE script: `/srv/pxe/http/systemrescue.ipxe` - TFTP fallback script: `/srv/pxe/tftp/autoexec.ipxe` - Boot binaries copied from the Nix `ipxe` package: @@ -28,18 +29,23 @@ The host creates these directories with systemd tmpfiles: ```text /srv/pxe /srv/pxe/http -/srv/pxe/http/images +/srv/pxe/http/images -> /mnt/pxe-images (symlink to NFS share) /srv/pxe/http/auto-installer /srv/pxe/http/nixos-minimal +/srv/pxe/http/debian /srv/pxe/http/systemrescue /srv/pxe/http/ubuntu /srv/pxe/http/rescue /srv/pxe/tftp ``` -Mount shared image storage under `/srv/pxe/http`, preferably -`/srv/pxe/http/images` unless a menu entry expects files in a specific -directory such as `/srv/pxe/http/auto-installer`. +`/srv/pxe/http/images` is a symlink to `/mnt/pxe-images`, which is an NFSv4.2 +mount of `server.sweet.home:/tank/proxmox/pxe-images` +(`modules/pxe-boot/mount-pxe-images.nix`). Place large images there (ISOs, +disk images) rather than on the pxe-boot host's own root disk. For an LXC +pxe-boot container the mount uses `nofail` (eager, non-blocking on server +unavailability); for a Proxmox VM it uses `x-systemd.automount` (lazy, +triggered on first access). The HTTP iPXE chain is: @@ -54,6 +60,7 @@ The generated menu currently exposes entries for: - NixOS Auto-Installer - NixOS Minimal +- Debian Minimal - SystemRescue environment - iPXE shell - Reboot @@ -84,12 +91,38 @@ directory name (`auto-installer` / `nixos-minimal`), so each one's generated system name (`nixos-system--*`) is self-describing rather than the nixpkgs default of `nixos-system-nixos-*` for both. +The Debian Minimal entry chains `http:///debian.ipxe`, which loads +the Debian bookworm netboot kernel and initrd from `/srv/pxe/http/debian/`. The +`fetch-debian-netboot.service` oneshot downloads these files from +`deb.debian.org` on first boot (idempotent — skips if files are already +present): + +```text +/srv/pxe/http/debian/linux (Debian bookworm netboot kernel) +/srv/pxe/http/debian/initrd.gz (Debian bookworm netboot initrd) +``` + +The service requires outbound internet access on the pxe-boot host. To +re-download (e.g. after a Debian point release), delete the files and restart +the service: + +```bash +rm /srv/pxe/http/debian/linux /srv/pxe/http/debian/initrd.gz +systemctl restart fetch-debian-netboot.service +``` + +To update to a different Debian release, change `debianRelease` in +`modules/build-types/pxe-boot.nix` and redeploy. + The SystemRescue entry expects the source ISO at: ```text /srv/pxe/http/images/systemrescue.iso ``` +Since `/srv/pxe/http/images` is the NFS-backed symlink, place the ISO on the +NFS share at `server.sweet.home:/tank/proxmox/pxe-images/systemrescue.iso`. + The `stage-systemrescue.service` oneshot extracts that ISO into: ```text @@ -113,6 +146,9 @@ After deployment by an operator, basic service checks are: ```bash curl http://pxe-boot/boot.ipxe curl http://pxe-boot/menu.ipxe +curl http://pxe-boot/debian.ipxe +curl -I http://pxe-boot/debian/linux +curl -I http://pxe-boot/debian/initrd.gz curl http://pxe-boot/systemrescue.ipxe curl -I http://pxe-boot/systemrescue/sysresccd/boot/x86_64/vmlinuz curl -I http://pxe-boot/systemrescue/sysresccd/boot/x86_64/sysresccd.img diff --git a/modules/build-types/pxe-boot.nix b/modules/build-types/pxe-boot.nix index 4dcd88b..8179adb 100644 --- a/modules/build-types/pxe-boot.nix +++ b/modules/build-types/pxe-boot.nix @@ -21,6 +21,39 @@ let chain ${pxeBaseUrl}/boot.ipxe ''; + debianRelease = "bookworm"; + debianMirror = "https://deb.debian.org/debian"; + debianNetbootBase = "${debianMirror}/dists/${debianRelease}/main/installer-amd64/current/images/netboot/debian-installer/amd64"; + + debianIpxe = pkgs.writeText "debian.ipxe" '' + #!ipxe + + set base ${pxeBaseUrl} + + kernel ''${base}/debian/linux + initrd ''${base}/debian/initrd.gz + boot + ''; + + fetchDebianNetboot = pkgs.writeShellScript "fetch-debian-netboot" '' + set -eu + + dir="${httpRoot}/debian" + mirror="${debianNetbootBase}" + + if [ -f "$dir/linux" ] && [ -f "$dir/initrd.gz" ]; then + echo "Debian ${debianRelease} netboot files already present; skipping download." + exit 0 + fi + + echo "Downloading Debian ${debianRelease} netboot kernel and initrd from $mirror ..." + ${pkgs.curl}/bin/curl -fsSL -o "$dir/linux.tmp" "$mirror/linux" + ${pkgs.curl}/bin/curl -fsSL -o "$dir/initrd.gz.tmp" "$mirror/initrd.gz" + mv "$dir/linux.tmp" "$dir/linux" + mv "$dir/initrd.gz.tmp" "$dir/initrd.gz" + echo "Debian ${debianRelease} netboot files staged." + ''; + systemRescueIpxe = pkgs.writeText "systemrescue.ipxe" '' #!ipxe @@ -70,6 +103,7 @@ let menu PXE Boot Menu item auto-installer NixOS Auto-Installer item nixos-minimal NixOS Minimal + item debian Debian Minimal item rescue Rescue Environment item shell iPXE Shell item reboot Reboot @@ -82,6 +116,9 @@ let :nixos-minimal chain ''${base}/nixos-minimal/netboot.ipxe + :debian + chain ''${base}/debian.ipxe + :rescue chain ''${base}/systemrescue.ipxe @@ -95,6 +132,7 @@ in { imports = [ ../pxe-boot/stage-installer-artifacts.nix + ../pxe-boot/mount-pxe-images.nix ]; environment.systemPackages = with pkgs; [ @@ -129,34 +167,56 @@ in openssh.settings.PermitRootLogin = "yes"; }; - systemd.tmpfiles.rules = [ - "d ${pxeRoot} 0755 root root -" - "d ${httpRoot} 0755 root root -" - "d ${httpRoot}/images 0755 root root -" - "d ${httpRoot}/auto-installer 0755 root root -" - "d ${httpRoot}/nixos-minimal 0755 root root -" - "d ${httpRoot}/systemrescue 0755 root root -" - "d ${httpRoot}/ubuntu 0755 root root -" - "d ${httpRoot}/rescue 0755 root root -" - "d ${tftpRoot} 0755 root root -" - "C+ ${httpRoot}/boot.ipxe 0644 root root - ${bootIpxe}" - "C+ ${httpRoot}/menu.ipxe 0644 root root - ${menuIpxe}" - "C+ ${httpRoot}/systemrescue.ipxe 0644 root root - ${systemRescueIpxe}" - "C+ ${tftpRoot}/autoexec.ipxe 0644 root root - ${autoexecIpxe}" - "C+ ${tftpRoot}/ipxe.efi 0644 root root - ${pkgs.ipxe}/ipxe.efi" - "C+ ${tftpRoot}/undionly.kpxe 0644 root root - ${pkgs.ipxe}/undionly.kpxe" - ]; - - systemd.services.stage-systemrescue = { - description = "Stage SystemRescue ISO contents for HTTP PXE boot"; - after = [ - "local-fs.target" - "systemd-tmpfiles-setup.service" + systemd = { + tmpfiles.rules = [ + "d ${pxeRoot} 0755 root root -" + "d ${httpRoot} 0755 root root -" + "L+ ${httpRoot}/images - - - - ${vars.nfsShares.proxmoxPxeImages.mountpoint}" + "d ${httpRoot}/auto-installer 0755 root root -" + "d ${httpRoot}/nixos-minimal 0755 root root -" + "d ${httpRoot}/systemrescue 0755 root root -" + "d ${httpRoot}/debian 0755 root root -" + "d ${httpRoot}/ubuntu 0755 root root -" + "d ${httpRoot}/rescue 0755 root root -" + "d ${tftpRoot} 0755 root root -" + "C+ ${httpRoot}/boot.ipxe 0644 root root - ${bootIpxe}" + "C+ ${httpRoot}/menu.ipxe 0644 root root - ${menuIpxe}" + "C+ ${httpRoot}/debian.ipxe 0644 root root - ${debianIpxe}" + "C+ ${httpRoot}/systemrescue.ipxe 0644 root root - ${systemRescueIpxe}" + "C+ ${tftpRoot}/autoexec.ipxe 0644 root root - ${autoexecIpxe}" + "C+ ${tftpRoot}/ipxe.efi 0644 root root - ${pkgs.ipxe}/ipxe.efi" + "C+ ${tftpRoot}/undionly.kpxe 0644 root root - ${pkgs.ipxe}/undionly.kpxe" ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - Type = "oneshot"; - ExecStart = stageSystemRescue; + + services = { + fetch-debian-netboot = { + description = "Download Debian ${debianRelease} netboot kernel and initrd for HTTP PXE boot"; + after = [ + "local-fs.target" + "systemd-tmpfiles-setup.service" + "network-online.target" + ]; + wants = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = fetchDebianNetboot; + RemainAfterExit = true; + }; + }; + + stage-systemrescue = { + description = "Stage SystemRescue ISO contents for HTTP PXE boot"; + after = [ + "local-fs.target" + "systemd-tmpfiles-setup.service" + ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = stageSystemRescue; + }; + }; }; }; diff --git a/modules/pxe-boot/mount-pxe-images.nix b/modules/pxe-boot/mount-pxe-images.nix new file mode 100644 index 0000000..853ab08 --- /dev/null +++ b/modules/pxe-boot/mount-pxe-images.nix @@ -0,0 +1,24 @@ +{ config, vars, ... }: + +let + # Use the same FQDN approach as docker/mount-data.nix — a bare hostname is + # unreliable: systemd-resolved only tries LLMNR for single-label names, and + # a global search domain causes it to skip the interface-scoped LAN DNS. + nfsServer = "${vars.nfsServerHost}.${vars.homeDomain}"; + + # x-systemd.automount is unsupported inside LXC containers (systemd logs + # "Starting of .automount unsupported" and never mounts). Use nofail + # there so a boot with the NFS server unreachable doesn't hang instead. + automountOpts = if config.boot.isContainer then [ "nofail" ] else [ "x-systemd.automount" ]; +in +{ + fileSystems.${vars.nfsShares.proxmoxPxeImages.mountpoint} = { + device = "${nfsServer}:${vars.storageRoot}/${vars.nfsShares.proxmoxPxeImages.subpath}"; + fsType = "nfs"; + options = [ + "nfsvers=4.2" + "_netdev" + "noatime" + ] ++ automountOpts; + }; +}