{ flake, config, pkgs, lib, sources, ... }: with lib; let nfsrootBaseUrl = "http://nfsroot.vidhar.yggdrasil"; tftpIp = "10.141.0.1"; nfsIp = tftpIp; ipxe = pkgs.ipxe.override { additionalTargets = { "bin-i386-efi/ipxe.efi" = "i386-ipxe.efi"; }; additionalOptions = [ "NSLOOKUP_CMD" "PING_CMD" "CONSOLE_CMD" ]; embedScript = pkgs.writeText "yggdrasil.ipxe" '' #!ipxe cpair --background 9 1 cpair --background 9 3 cpair --background 9 6 set user-class iPXE-yggdrasil autoboot ''; }; in { config = { services.kea = { dhcp4 = { enable = true; settings = { valid-lifetime = 4000; rebind-timer = 2000; renew-timer = 1000; interfaces-config = { interfaces = [ "lan" "mgmt" ]; }; lease-database = { name = "/var/lib/kea/dhcp4.leases"; persist = true; type = "memfile"; }; client-classes = [ { name = "ipxe-eostre"; test = "hexstring(pkt4.mac, ':') == '00:d8:61:79:c5:40' and option[77].hex == 'iPXE-yggdrasil'"; next-server = tftpIp; boot-file-name = "${nfsrootBaseUrl}/eostre.menu.ipxe"; only-if-required = true; } { name = "ipxe-yggdrasil"; test = "option[77].hex == 'iPXE-yggdrasil'"; next-server = tftpIp; boot-file-name = "${nfsrootBaseUrl}/installer-x86_64-linux.menu.ipxe"; only-if-required = true; } { name = "uefi-http"; test = "option[client-system].hex == 0x0010"; option-data = [ { name = "vendor-class-identifier"; data = "HTTPClient"; } ]; boot-file-name = "${nfsrootBaseUrl}/ipxe.efi"; only-if-required = true; } { name = "ipxe-uefi-64"; test = "option[77].hex == 'iPXE' and (substring(option[60].hex,0,20) == 'PXEClient:Arch:00007' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00008' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00009')"; boot-file-name = "${nfsrootBaseUrl}/ipxe.efi"; only-if-required = true; } { name = "ipxe-uefi-32"; test = "option[77].hex == 'iPXE' and (substring(option[60].hex,0,20) == 'PXEClient:Arch:00002' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00006')"; boot-file-name = "${nfsrootBaseUrl}/i386-ipxe.efi"; only-if-required = true; } { name = "ipxe-legacy"; test = "option[77].hex == 'iPXE' and substring(option[60].hex,0,20) == 'PXEClient:Arch:00000'"; boot-file-name = "${nfsrootBaseUrl}/ipxe.lkrn"; only-if-required = true; } { name = "uefi-64"; test = "substring(option[60].hex,0,20) == 'PXEClient:Arch:00007' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00008' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00009'"; option-data = [ { name = "tftp-server-name"; data = tftpIp; } ]; boot-file-name = "ipxe.efi"; only-if-required = true; } { name = "uefi-32"; test = "substring(option[60].hex,0,20) == 'PXEClient:Arch:00002' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00006'"; option-data = [ { name = "tftp-server-name"; data = tftpIp; } ]; boot-file-name = "i386-ipxe.efi"; only-if-required = true; } { name = "legacy"; test = "substring(option[60].hex,0,20) == 'PXEClient:Arch:00000'"; option-data = [ { name = "tftp-server-name"; data = tftpIp; } ]; boot-file-name = "ipxe.lkrn"; only-if-required = true; } ]; dhcp-ddns.enable-updates = true; ddns-send-updates = false; ddns-override-client-update = true; ddns-override-no-update = true; ddns-replace-client-name = "when-not-present"; ddns-generated-prefix = "noname"; ddns-update-on-renew = true; subnet4 = [ { id = 3; subnet = "10.141.0.0/24"; option-data = [ { name = "domain-name-servers"; data = "10.141.0.1"; } { name = "broadcast-address"; data = "10.141.0.255"; } { name = "routers"; data = "10.141.0.1"; } { name = "domain-name"; data = "yggdrasil"; } { name = "domain-search"; data = "lan.yggdrasil, yggdrasil"; } ]; ddns-send-updates = true; ddns-qualifying-suffix = "lan.yggdrasil"; pools = [ { pool = "10.141.0.128 - 10.141.0.254"; } ]; require-client-classes = map (cc: cc.name) config.services.kea.dhcp4.settings.client-classes; reservations = [ { hostname = "sif"; hw-address = "3c:e1:a1:52:24:35"; } { hostname = "sif"; hw-address = "ee:32:68:76:83:ac"; } { hostname = "sif"; hw-address = "48:2a:e3:64:62:97"; } { hostname = "eos"; hw-address = "00:d8:61:79:c5:40"; } { hostname = "geri"; hw-address = "0e:e6:43:5e:37:7b"; } ]; } { id = 2; subnet = "10.141.1.0/24"; option-data = [ { name = "domain-name-servers"; data = "10.141.1.1"; } { name = "broadcast-address"; data = "10.141.1.255"; } { name = "ntp-servers"; data = "10.141.1.1"; } { name = "domain-name"; data = "yggdrasil"; } { name = "domain-search"; data = "mgmt.yggdrasil, yggdrasil"; } ]; ddns-send-updates = true; ddns-qualifying-suffix = "mgmt.yggdrasil"; pools = [ { pool = "10.141.1.128 - 10.141.1.254"; } ]; reservations = [ { hostname = "switch01"; hw-address = "60:a4:b7:53:94:b5"; ip-address = "10.141.1.2"; } { hostname = "ap01"; hw-address = "74:ac:b9:29:ad:9a"; ip-address = "10.141.1.4"; } ]; } { id = 4; subnet = "10.141.2.0/24"; option-data = [ { name = "domain-name-servers"; data = "10.141.2.1"; } { name = "broadcast-address"; data = "10.141.2.255"; } { name = "routers"; data = "10.141.2.1"; } ]; ddns-send-updates = false; pools = [ { pool = "10.141.2.128 - 10.141.2.254"; } ]; reservations = []; } ]; }; }; # dhcp6 = { # enable = true; # settings = { # interfaces-config = { # interfaces = [ "lan" ]; # }; # lease-database = { # name = "/var/lib/kea/dhcp6.leases"; # persist = true; # type = "memfile"; # }; # }; # }; dhcp-ddns = { enable = true; settings = { forward-ddns = { ddns-domains = [ { name = "yggdrasil."; dns-servers = [ { ip-address = "127.0.0.1"; port = 5353; key-name = "local_key"; } ]; } ]; }; reverse-ddns = { ddns-domains = [ { name = "141.10.in-addr.arpa."; dns-servers = [ { ip-address = "127.0.0.1"; port = 5353; key-name = "local_key"; } ]; } ]; }; }; }; }; systemd.services.kea-dhcp-ddns-server = { preStart = let configLines = [ "" ] ++ mapAttrsToList (k: v: "\"${k}\": ${builtins.toJSON v}" ) config.services.kea.dhcp-ddns.settings; config-template = pkgs.writeText "dhcp-ddns.conf" '' {"DhcpDdns": { ${concatStringsSep ",\n " configLines} }} ''; in '' ${pkgs.envsubst}/bin/envsubst -i "${config-template}" -o "''${RUNTIME_DIRECTORY}/dhcp-ddns.conf" ''; serviceConfig = { ExecStart = mkForce '' ${pkgs.kea}/bin/kea-dhcp-ddns -c "''${RUNTIME_DIRECTORY}/dhcp-ddns.conf" ${escapeShellArgs config.services.kea.dhcp-ddns.extraArgs} ''; LoadCredential = [ "knot-tsig.json.frag:${config.sops.secrets."kea-knot-tsig.json.frag".path}" ]; }; }; sops.secrets."kea-knot-tsig.json.frag" = { format = "binary"; sopsFile = ./knot-tsig.json.frag; }; services.nginx.virtualHosts."nfsroot.vidhar.yggdrasil" = { addSSL = false; forceSSL = false; locations."/" = { extraConfig = '' autoindex on; ''; root = pkgs.symlinkJoin { name = "nfsroot.vidhar.yggdrasil"; paths = (map (system: pkgs.symlinkJoin { name = "installer-${system}"; paths = [ (let installerBuild = (flake.nixosConfigurations.${"installer-${system}-nfsroot"}.extendModules { modules = [ ({ ... }: { config.nfsroot.storeDevice = "${nfsIp}:nix-store"; config.nfsroot.registrationUrl = "${nfsrootBaseUrl}/installer-${system}/registration"; }) ]; }).config.system.build; in builtins.toPath (pkgs.runCommandLocal "install-${system}" {} '' mkdir -p $out/installer-${system} install -m 0444 -t $out/installer-${system} \ ${installerBuild.initialRamdisk}/initrd \ ${installerBuild.kernel}/bzImage \ ${installerBuild.netbootIpxeScript}/netboot.ipxe \ ${pkgs.closureInfo { rootPaths = installerBuild.storeContents; }}/registration '')) (pkgs.writeTextFile { name = "installer-${system}.menu.ipxe"; destination = "/installer-${system}.menu.ipxe"; text = '' #!ipxe :start menu iPXE boot menu for installer-${system} item installer installer-${system} item memtest memtest86plus item netboot netboot.xyz item shell iPXE shell choose --timeout 0 --default installer selected || goto shell goto ''${selected} :shell shell goto start :installer chain installer-${system}/netboot.ipxe goto start :netboot iseq ''${platform} efi && chain --autofree netboot.xyz.efi || chain --autofree netboot.xyz.lkrn goto start :memtest iseq ''${platform} efi && chain --autofree memtest.efi || chain --autofree memtest.bin goto start ''; }) ]; }) ["x86_64-linux"] ) ++ [ (pkgs.runCommandLocal "utils" {} '' mkdir $out install -m 0444 -t $out \ ${ipxe}/{ipxe.efi,i386-ipxe.efi,ipxe.lkrn} \ ${pkgs.memtest86plus}/{memtest.efi,memtest.bin} install -m 0444 ${sources.netbootxyz-efi.src} $out/netboot.xyz.efi install -m 0444 ${sources.netbootxyz-lkrn.src} $out/netboot.xyz.lkrn '') (let eostreBuild = (flake.nixosConfigurations.eostre.extendModules { modules = [ ({ ... }: { config.nfsroot.storeDevice = "${nfsIp}:nix-store"; config.nfsroot.registrationUrl = "${nfsrootBaseUrl}/eostre/registration"; }) ]; }).config.system.build; in builtins.toPath (pkgs.runCommandLocal "eostre" {} '' mkdir -p $out/eostre install -m 0444 -t $out/eostre \ ${eostreBuild.initialRamdisk}/initrd \ ${eostreBuild.kernel}/bzImage \ ${eostreBuild.netbootIpxeScript}/netboot.ipxe \ ${pkgs.closureInfo { rootPaths = eostreBuild.storeContents; }}/registration '')) (pkgs.writeTextFile { name = "eostre.menu.ipxe"; destination = "/eostre.menu.ipxe"; text = '' #!ipxe set menu-timeout 5000 :start menu iPXE boot menu for eostre item eostre eostre item memtest memtest86plus item netboot netboot.xyz item shell iPXE shell choose --timeout ''${menu-timeout} --default eostre selected || goto shell set menu-timeout 0 goto ''${selected} :shell set menu-timeout 0 shell goto start :eostre chain eostre/netboot.ipxe goto start :netboot iseq ''${platform} efi && chain --autofree netboot.xyz.efi || chain --autofree netboot.xyz.lkrn goto start :memtest iseq ''${platform} efi && chain --autofree memtest.efi || chain --autofree memtest.bin goto start ''; }) ]; }; }; }; systemd.services."pxe-atftpd" = { description = "TFTP Server for PXE Booting"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig.ExecStart = let tftpRoot = pkgs.runCommandLocal "netboot" {} '' mkdir -p $out install -m 0444 -t $out \ ${ipxe}/{ipxe.efi,i386-ipxe.efi,ipxe.lkrn} ''; in "${pkgs.atftp}/sbin/atftpd --daemon --no-fork --bind-address=${tftpIp} ${tftpRoot}"; }; }; }