nixos: Initial netbooting installer
All checks were successful
CI / Check, build and cache Nix flake (push) Successful in 34m36s
Installer / Build installer (push) Successful in 4m34s

This commit is contained in:
2024-06-24 00:08:55 +01:00
parent 9f2651e352
commit 84ca556c47
9 changed files with 390 additions and 16 deletions

View File

@@ -0,0 +1,165 @@
{ lib, pkgs, config, systems, ... }:
let
inherit (lib) mkMerge mkIf mkForce mkOption;
inherit (lib.my) mkOpt' mkBoolOpt';
cfg = config.my.netboot;
tftpRoot = pkgs.linkFarm "tftp-root" [
{
name = "ipxe-x86_64.efi";
path = "${pkgs.ipxe}/ipxe.efi";
}
];
menuFile = pkgs.runCommand "menu.ipxe" {
bootHost = cfg.server.host;
} ''
substituteAll ${./menu.ipxe} "$out"
'';
in
{
options.my.netboot = with lib.types; {
client = {
enable = mkBoolOpt' false "Whether network booting should be enabled.";
};
server = {
enable = mkBoolOpt' false "Whether a netboot server should be enabled.";
ip = mkOpt' str null "IP clients should connect to via TFTP.";
host = mkOpt' str config.networking.fqdn "Hostname clients should connect to over HTTP.";
installer = {
storeSize = mkOpt' str "16GiB" "Total allowed writable size of store.";
};
instances = mkOpt' (listOf str) [ ] "Systems to hold boot files for.";
keaClientClasses = mkOption {
type = listOf (attrsOf str);
description = "Kea client classes for PXE boot.";
readOnly = true;
};
};
};
config = mkMerge [
(mkIf cfg.client.enable {
# TODO: Implement!
})
(mkIf cfg.server.enable {
environment = {
etc = {
"netboot/menu.ipxe".source = menuFile;
"netboot/shell.efi".source = "${pkgs.edk2-uefi-shell}/shell.efi";
};
};
systemd = {
services = {
netboot-update = {
description = "Update netboot images";
after = [ "systemd-networkd-wait-online.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
path = with pkgs; [
coreutils curl jq zstd gnutar
];
script = ''
update_nixos() {
latestShort="$(curl -s https://git.nul.ie/api/v1/repos/dev/nixfiles/tags/installer \
| jq -r .commit.sha | cut -c -7)"
if [ -f nixos-installer/tag.txt ] && [ "$(< nixos-installer/tag.txt)" = "$latestShort" ]; then
echo "NixOS installer is up to date"
return
fi
echo "Updating NixOS installer to $latestShort"
mkdir -p nixos-installer
fname="jackos-installer-netboot-$latestShort.tar.zst"
downloadUrl="$(curl -s https://git.nul.ie/api/v1/repos/dev/nixfiles/releases/tags/installer | \
jq -r ".assets[] | select(.name == \"$fname\").browser_download_url")"
curl -Lo /tmp/nixos-installer-netboot.tar.zst "$downloadUrl"
tar -C nixos-installer --zstd -xf /tmp/nixos-installer-netboot.tar.zst
truncate -s "${cfg.server.installer.storeSize}" nixos-installer/rootfs.ext4
rm /tmp/nixos-installer-netboot.tar.zst
echo "$latestShort" > nixos-installer/tag.txt
}
mkdir -p /srv/netboot
cd /srv/netboot
ln -sf ${menuFile} boot.ipxe
ln -sf "${pkgs.edk2-uefi-shell}/shell.efi" "efi-shell-${config.nixpkgs.localSystem.linuxArch}.efi"
update_nixos
'';
startAt = "06:00";
wantedBy = [ "network-online.target" ];
};
nbd-server = {
serviceConfig = {
PrivateUsers = mkForce false;
CacheDirectory = "netboot";
};
};
};
};
services = {
atftpd = {
enable = true;
root = tftpRoot;
};
nginx = {
virtualHosts."${cfg.server.host}" = {
locations."/" = {
root = "/srv/netboot";
extraConfig = ''
autoindex on;
'';
};
};
};
nbd.server = {
enable = true;
extraOptions = {
allowlist = true;
};
exports = {
nixos-installer = {
path = "/srv/netboot/nixos-installer/rootfs.ext4";
extraOptions = {
copyonwrite = true;
cowdir = "/var/cache/netboot";
sparse_cow = true;
};
};
};
};
};
my = {
tmproot.persistence.config.directories = [
"/srv/netboot"
{ directory = "/var/cache/netboot"; mode = "0700"; }
];
netboot.server.keaClientClasses = [
{
name = "ipxe";
test = "substring(option[user-class].hex, 0, 4) == 'iPXE'";
next-server = cfg.server.ip;
server-hostname = cfg.server.host;
boot-file-name = "http://${cfg.server.host}/boot.ipxe";
}
{
name = "efi-x86_64";
test = "option[client-system].hex == 0x0007";
next-server = cfg.server.ip;
server-hostname = cfg.server.host;
boot-file-name = "ipxe-x86_64.efi";
}
];
};
})
];
}

View File

@@ -0,0 +1,68 @@
#!ipxe
set server http://@bootHost@
# Figure out if client is 64-bit capable
cpuid --ext 29 && set arch x86_64 || set arch i386
isset ${menu-default} || set menu-default exit
:start
menu Welcome to /dev/player0's humble iPXE boot menu
item --gap -- Operating Systems
iseq ${arch} x86_64 &&
item --key n nixos NixOS installer
# iseq ${arch} x86_64 &&
# item --key a archlinux Arch Linux (archiso x86_64)
# iseq ${arch} x86_64 &&
# item --key p alpine Alpine Linux
item --gap -- Other Options
item --key e efi_shell UEFI Shell
item --key x xyz netboot.xyz
item --key c config iPXE settings
item --key s shell Drop to iPXE shell
item --key r reboot Reboot
item --key q exit Exit (and continue to next boot device)
choose --timeout 0 --default ${menu-default} selected || goto cancel
goto ${selected}
:cancel
echo You cancelled the menu, dropping you to an iPXE shell
:shell
echo Type 'exit' to go back to the menu
shell
set menu-default nixos
goto start
:failed
echo Booting failed, dropping to shell
goto shell
:reboot
reboot
:exit
exit
:config
config
set menu-default config
goto start
:efi_shell
chain ${server}/efi-shell-${arch}.efi || goto failed
:xyz
chain --autofree https://boot.netboot.xyz || goto failed
:nixos
set cmdline nbd_export=nixos-installer
chain ${server}/nixos-installer/boot.ipxe || goto failed
:archlinux
# set mirrorurl https://arch.nul.ie/
chain ${server}/arch.ipxe || goto failed
:alpine
chain ${server}/alpine.ipxe || goto failed