summaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorAlejandro Soto <alejandro@34project.org>2026-03-08 21:19:07 -0600
committerAlejandro Soto <alejandro@34project.org>2026-03-08 21:19:12 -0600
commit709df598565115413d00cffa9a0798882583a8ad (patch)
tree38d1d260333cf55ceb056646a1de3edcece75d07 /sys
parent49d5c6716c24cebf9dd9fa06d7f5145345026c3e (diff)
sys/web: pxe: implement PXE boot server
Diffstat (limited to '')
-rw-r--r--sys/net/interfaces.nix2
-rw-r--r--sys/web/sites/default.nix1
-rw-r--r--sys/web/sites/pxe.nix180
3 files changed, 182 insertions, 1 deletions
diff --git a/sys/net/interfaces.nix b/sys/net/interfaces.nix
index 3150b02..764973c 100644
--- a/sys/net/interfaces.nix
+++ b/sys/net/interfaces.nix
@@ -103,7 +103,7 @@ in {
};
systemd.network.networks = mkIf (cfg.dhcpInterface != null) {
- "40-${cfg.dhcpInterface}" = {
+ ${cfg.dhcpInterface} = {
matchConfig.Name = cfg.dhcpInterface;
networkConfig = {
diff --git a/sys/web/sites/default.nix b/sys/web/sites/default.nix
index ba2835c..85b6020 100644
--- a/sys/web/sites/default.nix
+++ b/sys/web/sites/default.nix
@@ -3,5 +3,6 @@
./home.nix
./host.nix
./portal.nix
+ ./pxe.nix
];
}
diff --git a/sys/web/sites/pxe.nix b/sys/web/sites/pxe.nix
new file mode 100644
index 0000000..54f3e56
--- /dev/null
+++ b/sys/web/sites/pxe.nix
@@ -0,0 +1,180 @@
+{
+ config,
+ flakes,
+ lib,
+ pkgs,
+ ...
+}:
+with lib; let
+ cfg = config.local.web.sites.pxe;
+
+ pxeEnabled = machine: machine.config.local.boot.pxe.enable;
+ machines = listToAttrs (map (name: {
+ inherit name;
+ value = flakes.self.nixosConfigurations.${name};
+ })
+ cfg.machines);
+
+ stage2For = name: machine:
+ # https://purpleidea.com/blog/2025/01/23/working-around-an-ipxe-issue/
+ # "The answer:
+ # They told me that one of their engineers had struggled with the same issue for a while. The solution:
+ # Add the imgfree iPXE command before you boot!
+ # I wish the docs would hint that this is often required, but now I know, and now you know too!
+ # You can see that line here."
+ pkgs.writeText "stage2-${name}.ipxe" ''
+ #!ipxe
+ echo stage2: booting ${name}...
+ imgfree
+ kernel kernel init=${machine.config.system.build.toplevel}/init ${concatStringsSep " " machine.config.boot.kernelParams} || goto fail
+ initrd initrd || goto fail
+ boot ||
+ :fail
+ echo stage2: failed, rebooting in 5 seconds...
+ sleep 5
+ reboot --warm
+ '';
+
+ machineLocations = name: machine: let
+ mac = machine.config.local.boot.pxe.mac;
+ build = machine.config.system.build;
+ in {
+ "= /${mac}/initrd".alias = "${build.initialRamdisk}/initrd";
+ "= /${mac}/kernel".alias = "${build.kernel}/${pkgs.stdenv.hostPlatform.linux-kernel.target}";
+ "= /${mac}/stage2.ipxe".alias = "${stage2For name machine}";
+ };
+in {
+ options.local.web.sites.pxe = {
+ enable = mkEnableOption "PXE netboot image server";
+
+ linkLocal6 = mkOption {
+ type = types.str;
+ };
+
+ network = mkOption {
+ type = types.str;
+ };
+
+ machines = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions =
+ mapAttrsToList (name: machine: {
+ message = "PXE boot is not enabled in config '${name}'";
+ assertion = pxeEnabled machine;
+ })
+ machines;
+
+ local.web.enable = mkDefault true;
+
+ networking = {
+ firewall.extraCommands = ''
+ ip6tables -t filter -A local-input -p udp -i ${cfg.network} --dport 1792 -d ${cfg.linkLocal6} -j ACCEPT
+ '';
+ };
+
+ services = {
+ nginx = {
+ virtualHosts = {
+ "[${cfg.linkLocal6}]" = {
+ listenAddresses = ["[${cfg.linkLocal6}]"];
+
+ addSSL = false;
+ forceSSL = false;
+
+ locations = mergeAttrsList (mapAttrsToList machineLocations (filterAttrs (_: pxeEnabled) machines));
+ };
+ };
+ };
+ };
+
+ systemd = {
+ network = {
+ networks.${cfg.network}.addresses = [
+ {
+ Address = "${cfg.linkLocal6}/128";
+ AddPrefixRoute = "no";
+ PreferredLifetime = "0";
+ }
+ ];
+ };
+
+ services = {
+ pxe-store-upload = {
+ path = [config.nix.package pkgs.sshfs pkgs.squashfsTools];
+
+ script = let
+ host = lib.throw "TODO: pxe host";
+ machine = machines.${host};
+ in ''
+ set -e
+
+ pxe_ip='${machine.config.local.boot.pxe.linkLocal6}'
+ pxe_host='${host}'
+ pxe_system='${machine.config.system.build.toplevel}'
+
+ mountpoint="./pxe-initrd.$pxe_host"
+ store_img="$mountpoint/nix-store.squashfs"
+
+ mkdir -p "$mountpoint"
+
+ export PATH="/run/wrappers/bin:$PATH"
+ sshfs \
+ "root@[$pxe_ip%${cfg.network}]:/nix/.ro-store-img/" \
+ "$mountpoint" \
+ -o idmap=user
+
+ nix-store --query --requisites "$pxe_system" | \
+ xargs -I{} mksquashfs {} - \
+ -stream -comp zstd -one-file-system -no-xattrs \
+ >"$store_img.tmp"
+
+ mksquashfs -fix "$store_img.tmp"
+
+ sync "$store_img.tmp"
+ mv -- "$store_img.tmp" "$store_img"
+ fusermount3 -u "$mountpoint"
+ '';
+
+ serviceConfig = {
+ #AmbientCapabilities = ["CAP_SYS_ADMIN"];
+ #CapabilityBoundingSet = ["CAP_SYS_ADMIN"];
+ #BindPaths = ["/dev/fuse"];
+ #BindReadOnlyPaths = ["/nix/store"];
+ #DeviceAllow = ["/dev/fuse rwm"];
+ #DevicePolicy = "closed";
+ #MountAPIVFS = true;
+ ProtectSystem = "strict";
+ ProtectControlGroups = true;
+ ProtectKernelLogs = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+ #PrivateDevices = true;
+ #PrivateTmp = true;
+ #PrivateUsers = true;
+ #TemporaryFileSystem = ["/"];
+ #DynamicUser = true;
+ RuntimeDirectory = "pxe-store-upload";
+ WorkingDirectory = "/run/pxe-store-upload";
+ };
+ };
+ };
+
+ sockets = {
+ pxe-store-upload = {
+ after = ["network-online.target"];
+ wants = ["network-online.target"];
+ wantedBy = ["sockets.target"];
+
+ socketConfig = {
+ ListenDatagram = "[${cfg.linkLocal6}]:1792%%${cfg.network}";
+ };
+ };
+ };
+ };
+ };
+}