482 lines
13 KiB
Nix
482 lines
13 KiB
Nix
# NixOS Module for PXE Boot Server
|
|
#
|
|
# This module provides a complete PXE boot infrastructure for bare-metal
|
|
# provisioning of Centra Cloud nodes.
|
|
#
|
|
# Services provided:
|
|
# - DHCP server (ISC DHCP)
|
|
# - TFTP server (for iPXE bootloaders)
|
|
# - HTTP server (nginx, for iPXE scripts and NixOS images)
|
|
#
|
|
# Usage:
|
|
# 1. Import this module in your NixOS configuration
|
|
# 2. Enable and configure the PXE server
|
|
# 3. Deploy to your provisioning server
|
|
#
|
|
# Example:
|
|
# imports = [ ./baremetal/pxe-server/nixos-module.nix ];
|
|
#
|
|
# services.centra-pxe-server = {
|
|
# enable = true;
|
|
# interface = "eth0";
|
|
# serverAddress = "10.0.100.10";
|
|
# subnet = "10.0.100.0/24";
|
|
# dhcpRange = {
|
|
# start = "10.0.100.100";
|
|
# end = "10.0.100.200";
|
|
# };
|
|
# };
|
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.services.centra-pxe-server;
|
|
|
|
# DHCP configuration file
|
|
dhcpdConf = pkgs.writeText "dhcpd.conf" ''
|
|
# ISC DHCP Server Configuration for PXE Boot
|
|
# Auto-generated by NixOS module
|
|
|
|
option space pxelinux;
|
|
option architecture-type code 93 = unsigned integer 16;
|
|
|
|
default-lease-time ${toString cfg.dhcp.defaultLeaseTime};
|
|
max-lease-time ${toString cfg.dhcp.maxLeaseTime};
|
|
|
|
authoritative;
|
|
log-facility local7;
|
|
|
|
subnet ${cfg.dhcp.subnet} netmask ${cfg.dhcp.netmask} {
|
|
range ${cfg.dhcp.range.start} ${cfg.dhcp.range.end};
|
|
|
|
option routers ${cfg.dhcp.router};
|
|
option subnet-mask ${cfg.dhcp.netmask};
|
|
option broadcast-address ${cfg.dhcp.broadcast};
|
|
option domain-name-servers ${concatStringsSep ", " cfg.dhcp.nameservers};
|
|
option domain-name "${cfg.dhcp.domainName}";
|
|
|
|
next-server ${cfg.serverAddress};
|
|
|
|
if exists user-class and option user-class = "iPXE" {
|
|
filename "http://${cfg.serverAddress}:${toString cfg.http.port}/boot/ipxe/boot.ipxe";
|
|
} elsif option architecture-type = 00:00 {
|
|
filename "undionly.kpxe";
|
|
} elsif option architecture-type = 00:06 {
|
|
filename "ipxe-i386.efi";
|
|
} elsif option architecture-type = 00:07 {
|
|
filename "ipxe.efi";
|
|
} elsif option architecture-type = 00:09 {
|
|
filename "ipxe.efi";
|
|
} else {
|
|
filename "undionly.kpxe";
|
|
}
|
|
}
|
|
|
|
${cfg.dhcp.extraConfig}
|
|
'';
|
|
|
|
# iPXE boot script
|
|
bootIpxeScript = pkgs.writeText "boot.ipxe" ''
|
|
#!ipxe
|
|
|
|
set boot-server ${cfg.serverAddress}
|
|
set boot-url http://''${boot-server}:${toString cfg.http.port}/boot
|
|
set nixos-url ''${boot-url}/nixos
|
|
set provisioning-server http://''${boot-server}:${toString cfg.http.port}
|
|
set deployer-url ${if cfg.bootstrap.deployerUrl != null then cfg.bootstrap.deployerUrl else "http://${cfg.serverAddress}:8080"}
|
|
|
|
echo Network Configuration:
|
|
echo IP Address: ''${ip}
|
|
echo MAC Address: ''${mac}
|
|
echo
|
|
|
|
isset ''${profile} || set profile unknown
|
|
|
|
${concatStringsSep "\n" (mapAttrsToList (mac: node:
|
|
"iseq ''${mac} ${mac} && set profile ${node.profile} && set hostname ${node.hostname} && goto boot ||"
|
|
) cfg.nodes)}
|
|
|
|
goto menu
|
|
|
|
:menu
|
|
clear menu
|
|
menu Centra Cloud - Bare-Metal Provisioning
|
|
item --gap -- ------------------------- Boot Profiles -------------------------
|
|
item control-plane 1. Control Plane Node (All Services)
|
|
item worker 2. Worker Node (Compute Services)
|
|
item all-in-one 3. All-in-One Node (Testing/Homelab)
|
|
item --gap -- ------------------------- Advanced Options -------------------------
|
|
item shell iPXE Shell (for debugging)
|
|
item reboot Reboot System
|
|
item exit Exit to BIOS
|
|
choose --timeout 30000 --default control-plane selected || goto cancel
|
|
goto ''${selected}
|
|
|
|
:cancel
|
|
echo Boot cancelled, rebooting in 5 seconds...
|
|
sleep 5
|
|
reboot
|
|
|
|
:control-plane
|
|
set profile control-plane
|
|
echo Booting: Control Plane Node
|
|
goto boot
|
|
|
|
:worker
|
|
set profile worker
|
|
echo Booting: Worker Node
|
|
goto boot
|
|
|
|
:all-in-one
|
|
set profile all-in-one
|
|
echo Booting: All-in-One Node
|
|
goto boot
|
|
|
|
:boot
|
|
isset ''${hostname} || set hostname centra-node-''${mac:hexhyp}
|
|
|
|
echo Profile: ''${profile}
|
|
echo Hostname: ''${hostname}
|
|
echo MAC Address: ''${mac}
|
|
|
|
set kernel-params initrd=initrd ip=dhcp
|
|
set kernel-params ''${kernel-params} centra.profile=''${profile}
|
|
set kernel-params ''${kernel-params} centra.hostname=''${hostname}
|
|
set kernel-params ''${kernel-params} centra.mac=''${mac}
|
|
set kernel-params ''${kernel-params} centra.provisioning-server=''${provisioning-server}
|
|
set kernel-params ''${kernel-params} plasmacloud.deployer_url=''${deployer-url}
|
|
${optionalString (cfg.bootstrap.bootstrapToken != null) "set kernel-params ''${kernel-params} plasmacloud.bootstrap_token=${cfg.bootstrap.bootstrapToken}"}
|
|
${optionalString (cfg.bootstrap.caCertUrl != null) "set kernel-params ''${kernel-params} plasmacloud.ca_cert_url=${cfg.bootstrap.caCertUrl}"}
|
|
set kernel-params ''${kernel-params} console=tty0 console=ttyS0,115200n8
|
|
|
|
kernel ''${nixos-url}/bzImage ''${kernel-params} || goto failed
|
|
initrd ''${nixos-url}/initrd || goto failed
|
|
boot || goto failed
|
|
|
|
:failed
|
|
echo Boot Failed!
|
|
echo Failed to load kernel or initrd from ''${nixos-url}
|
|
goto shell
|
|
|
|
:shell
|
|
echo Entering iPXE shell...
|
|
shell
|
|
|
|
:reboot
|
|
reboot
|
|
|
|
:exit
|
|
exit
|
|
'';
|
|
|
|
# Nginx configuration
|
|
nginxConf = pkgs.writeText "nginx.conf" ''
|
|
user nginx;
|
|
worker_processes auto;
|
|
error_log /var/log/nginx/error.log warn;
|
|
pid /var/run/nginx.pid;
|
|
|
|
events {
|
|
worker_connections 1024;
|
|
}
|
|
|
|
http {
|
|
include ${pkgs.nginx}/conf/mime.types;
|
|
default_type application/octet-stream;
|
|
|
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
|
'$status $body_bytes_sent "$http_referer" '
|
|
'"$http_user_agent"';
|
|
|
|
access_log /var/log/nginx/access.log main;
|
|
|
|
sendfile on;
|
|
tcp_nopush on;
|
|
keepalive_timeout 65;
|
|
|
|
types {
|
|
application/octet-stream kpxe;
|
|
application/octet-stream efi;
|
|
text/plain ipxe;
|
|
}
|
|
|
|
server {
|
|
listen ${toString cfg.http.port};
|
|
server_name _;
|
|
|
|
root ${cfg.bootAssetsPath};
|
|
|
|
location /boot/ {
|
|
alias ${cfg.bootAssetsPath}/;
|
|
autoindex on;
|
|
autoindex_exact_size off;
|
|
autoindex_localtime on;
|
|
}
|
|
|
|
location ~ \.ipxe$ {
|
|
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
|
expires -1;
|
|
}
|
|
|
|
location /health {
|
|
access_log off;
|
|
return 200 "OK\n";
|
|
add_header Content-Type text/plain;
|
|
}
|
|
|
|
location = / {
|
|
return 200 "Centra Cloud PXE Boot Server\n";
|
|
add_header Content-Type text/plain;
|
|
}
|
|
}
|
|
}
|
|
'';
|
|
|
|
in {
|
|
options.services.centra-pxe-server = {
|
|
enable = mkEnableOption "Centra Cloud PXE Boot Server";
|
|
|
|
interface = mkOption {
|
|
type = types.str;
|
|
default = "eth0";
|
|
description = "Network interface to listen on for DHCP requests";
|
|
};
|
|
|
|
serverAddress = mkOption {
|
|
type = types.str;
|
|
example = "10.0.100.10";
|
|
description = "IP address of the PXE boot server";
|
|
};
|
|
|
|
bootAssetsPath = mkOption {
|
|
type = types.path;
|
|
default = "/var/lib/pxe-boot";
|
|
description = "Path to boot assets directory";
|
|
};
|
|
|
|
dhcp = {
|
|
subnet = mkOption {
|
|
type = types.str;
|
|
example = "10.0.100.0";
|
|
description = "Network subnet for DHCP";
|
|
};
|
|
|
|
netmask = mkOption {
|
|
type = types.str;
|
|
default = "255.255.255.0";
|
|
description = "Network netmask";
|
|
};
|
|
|
|
broadcast = mkOption {
|
|
type = types.str;
|
|
example = "10.0.100.255";
|
|
description = "Broadcast address";
|
|
};
|
|
|
|
range = {
|
|
start = mkOption {
|
|
type = types.str;
|
|
example = "10.0.100.100";
|
|
description = "Start of DHCP range";
|
|
};
|
|
|
|
end = mkOption {
|
|
type = types.str;
|
|
example = "10.0.100.200";
|
|
description = "End of DHCP range";
|
|
};
|
|
};
|
|
|
|
router = mkOption {
|
|
type = types.str;
|
|
example = "10.0.100.1";
|
|
description = "Default gateway";
|
|
};
|
|
|
|
nameservers = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [ "8.8.8.8" "8.8.4.4" ];
|
|
description = "DNS nameservers";
|
|
};
|
|
|
|
domainName = mkOption {
|
|
type = types.str;
|
|
default = "centra.local";
|
|
description = "Domain name";
|
|
};
|
|
|
|
defaultLeaseTime = mkOption {
|
|
type = types.int;
|
|
default = 600;
|
|
description = "Default DHCP lease time in seconds";
|
|
};
|
|
|
|
maxLeaseTime = mkOption {
|
|
type = types.int;
|
|
default = 7200;
|
|
description = "Maximum DHCP lease time in seconds";
|
|
};
|
|
|
|
extraConfig = mkOption {
|
|
type = types.lines;
|
|
default = "";
|
|
description = "Additional DHCP configuration";
|
|
};
|
|
};
|
|
|
|
http = {
|
|
port = mkOption {
|
|
type = types.int;
|
|
default = 80;
|
|
description = "HTTP server port";
|
|
};
|
|
};
|
|
|
|
tftp = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Enable TFTP server for bootloader files";
|
|
};
|
|
};
|
|
|
|
bootstrap = {
|
|
deployerUrl = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Deployer endpoint passed to the bootstrap ISO/netboot environment";
|
|
example = "https://deployer.example.com:8443";
|
|
};
|
|
|
|
bootstrapToken = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Optional shared bootstrap token embedded in iPXE kernel arguments";
|
|
};
|
|
|
|
caCertUrl = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Optional CA certificate URL fetched by the bootstrap environment before phone-home";
|
|
example = "https://deployer.example.com/bootstrap-ca.crt";
|
|
};
|
|
};
|
|
|
|
nodes = mkOption {
|
|
type = types.attrsOf (types.submodule {
|
|
options = {
|
|
profile = mkOption {
|
|
type = types.enum [ "control-plane" "worker" "all-in-one" ];
|
|
description = "Node profile";
|
|
};
|
|
|
|
hostname = mkOption {
|
|
type = types.str;
|
|
description = "Node hostname";
|
|
};
|
|
|
|
ipAddress = mkOption {
|
|
type = types.str;
|
|
description = "Fixed IP address (optional)";
|
|
default = "";
|
|
};
|
|
};
|
|
});
|
|
default = {};
|
|
example = literalExpression ''
|
|
{
|
|
"52:54:00:12:34:56" = {
|
|
profile = "control-plane";
|
|
hostname = "control-plane-01";
|
|
ipAddress = "10.0.100.50";
|
|
};
|
|
}
|
|
'';
|
|
description = "MAC address to node configuration mapping";
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
# DHCP Server
|
|
services.dhcpd4 = {
|
|
enable = true;
|
|
interfaces = [ cfg.interface ];
|
|
configFile = dhcpdConf;
|
|
};
|
|
|
|
# TFTP Server
|
|
services.atftpd = mkIf cfg.tftp.enable {
|
|
enable = true;
|
|
root = "${cfg.bootAssetsPath}/ipxe";
|
|
};
|
|
|
|
# HTTP Server (Nginx)
|
|
services.nginx = {
|
|
enable = true;
|
|
package = pkgs.nginx;
|
|
appendHttpConfig = ''
|
|
server {
|
|
listen ${toString cfg.http.port};
|
|
server_name _;
|
|
root ${cfg.bootAssetsPath};
|
|
|
|
location /boot/ {
|
|
alias ${cfg.bootAssetsPath}/;
|
|
autoindex on;
|
|
autoindex_exact_size off;
|
|
autoindex_localtime on;
|
|
}
|
|
|
|
location ~ \.ipxe$ {
|
|
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
|
expires -1;
|
|
}
|
|
|
|
location /health {
|
|
access_log off;
|
|
return 200 "OK\n";
|
|
add_header Content-Type text/plain;
|
|
}
|
|
}
|
|
'';
|
|
};
|
|
|
|
# Firewall Rules
|
|
networking.firewall = {
|
|
allowedUDPPorts = [
|
|
67 # DHCP server
|
|
68 # DHCP client
|
|
69 # TFTP
|
|
];
|
|
allowedTCPPorts = [
|
|
cfg.http.port # HTTP
|
|
];
|
|
};
|
|
|
|
# Create boot assets directory structure
|
|
systemd.tmpfiles.rules = [
|
|
"d ${cfg.bootAssetsPath} 0755 nginx nginx -"
|
|
"d ${cfg.bootAssetsPath}/ipxe 0755 nginx nginx -"
|
|
"d ${cfg.bootAssetsPath}/nixos 0755 nginx nginx -"
|
|
"L+ ${cfg.bootAssetsPath}/ipxe/boot.ipxe - - - - ${bootIpxeScript}"
|
|
];
|
|
|
|
# Systemd service dependencies
|
|
systemd.services.dhcpd4.after = [ "network-online.target" ];
|
|
systemd.services.dhcpd4.wants = [ "network-online.target" ];
|
|
|
|
systemd.services.atftpd.after = [ "network-online.target" ];
|
|
systemd.services.atftpd.wants = [ "network-online.target" ];
|
|
|
|
# Environment packages for management
|
|
environment.systemPackages = with pkgs; [
|
|
dhcp
|
|
tftp-hpa
|
|
curl
|
|
wget
|
|
ipxe
|
|
];
|
|
};
|
|
}
|