Advance forge rollout, Ethereum rails, and NBC sources
This commit is contained in:
parent
be26313225
commit
7d84510eac
88 changed files with 11230 additions and 302 deletions
338
nix/modules/ec-netboot.nix
Normal file
338
nix/modules/ec-netboot.nix
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
{ lib, config, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.every-channel.netboot;
|
||||
scriptsRoot = ../..;
|
||||
|
||||
boolString = v: if v then "true" else "false";
|
||||
|
||||
mkExport = name: value: "export ${name}=${lib.escapeShellArg value}";
|
||||
|
||||
optionalExport = name: value:
|
||||
if value == null then "" else mkExport name value;
|
||||
|
||||
optionalFileExport = name: path:
|
||||
if path == null then ""
|
||||
else ''
|
||||
if [[ ! -r ${lib.escapeShellArg path} ]]; then
|
||||
echo "error: required file is not readable: ${path}" >&2
|
||||
exit 2
|
||||
fi
|
||||
export ${name}="$(tr -d '\\r\\n' < ${lib.escapeShellArg path})"
|
||||
'';
|
||||
|
||||
stageToolchain = with pkgs; [
|
||||
bash
|
||||
coreutils
|
||||
curl
|
||||
gawk
|
||||
gnugrep
|
||||
gnused
|
||||
gnutar
|
||||
gzip
|
||||
python3
|
||||
];
|
||||
|
||||
ipxeToolchain = with pkgs; [
|
||||
git
|
||||
gnumake
|
||||
gcc
|
||||
binutils
|
||||
perl
|
||||
mtools
|
||||
docker-client
|
||||
];
|
||||
in
|
||||
{
|
||||
options.services.every-channel.netboot = {
|
||||
enable = lib.mkEnableOption "every.channel persistent netboot stage/serve services";
|
||||
|
||||
listenIP = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "10.20.30.2";
|
||||
description = "IP address bound for TFTP/HTTP on the provisioning VLAN.";
|
||||
};
|
||||
|
||||
interface = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "enp3s0";
|
||||
description = "Network interface name on the provisioning VLAN.";
|
||||
};
|
||||
|
||||
hostname = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "boot.every.channel";
|
||||
description = "Boot server hostname advertised to DHCP/iPXE clients.";
|
||||
};
|
||||
|
||||
rootDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/var/lib/every-channel-netboot";
|
||||
description = "Persistent root directory containing staged HTTP and TFTP artifacts.";
|
||||
};
|
||||
|
||||
httpPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 8080;
|
||||
description = "HTTP port used for kernel/initrd/netboot script serving.";
|
||||
};
|
||||
|
||||
tftpBootFilename = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "ec-ipxe.efi";
|
||||
description = "Filename served via TFTP (DHCP option 67 in UniFi-only mode).";
|
||||
};
|
||||
|
||||
chainTokenFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "/run/agenix/every-channel-netboot-chain-token";
|
||||
description = "Optional file containing netboot chain token passed to stage/serve scripts.";
|
||||
};
|
||||
|
||||
httpAllowedCIDRs = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "10.20.30.0/24" ];
|
||||
description = "Optional CIDR allowlist for HTTP artifact serving.";
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Open firewall ports for TFTP/HTTP (and ProxyDHCP ports when enabled).";
|
||||
};
|
||||
|
||||
stageOnBoot = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Start the stage oneshot service during boot before serving.";
|
||||
};
|
||||
|
||||
release = {
|
||||
host = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "https://git.every.channel";
|
||||
description = "Forge host used to fetch netboot release assets.";
|
||||
};
|
||||
|
||||
repo = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "every-channel/every.channel";
|
||||
description = "Forge repository containing netboot release assets.";
|
||||
};
|
||||
|
||||
tag = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional release tag to stage; defaults to latest release.";
|
||||
};
|
||||
|
||||
localTarball = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional local netboot tarball path; bypasses release API download when set.";
|
||||
};
|
||||
|
||||
tokenFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional file containing Forge API token for private release access.";
|
||||
};
|
||||
|
||||
verifyChecksums = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Verify staged release tarball using SHA256SUMS.txt when available.";
|
||||
};
|
||||
};
|
||||
|
||||
ipxe = {
|
||||
buildEmbedded = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Build embedded iPXE (ec-ipxe.efi) before staging artifacts.";
|
||||
};
|
||||
|
||||
path = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional prebuilt iPXE EFI binary path; used when buildEmbedded is false.";
|
||||
};
|
||||
|
||||
allowRemoteDownload = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Allow remote iPXE URL download during staging. Disabled by default.";
|
||||
};
|
||||
|
||||
sha256 = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional SHA256 digest expected for selected iPXE EFI binary.";
|
||||
};
|
||||
|
||||
repo = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "https://github.com/ipxe/ipxe.git";
|
||||
description = "iPXE source repository used for embedded EFI builds.";
|
||||
};
|
||||
|
||||
ref = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional git ref/commit for iPXE build reproducibility.";
|
||||
};
|
||||
|
||||
useDocker = lib.mkOption {
|
||||
type = lib.types.enum [ "auto" "true" "false" ];
|
||||
default = "auto";
|
||||
description = "Container fallback mode for iPXE builds (auto/true/false).";
|
||||
};
|
||||
|
||||
dockerImage = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "ubuntu:24.04";
|
||||
description = "Docker image used when iPXE build runs in container mode.";
|
||||
};
|
||||
|
||||
dockerPlatform = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "linux/amd64";
|
||||
description = "Docker platform used for containerized iPXE build.";
|
||||
};
|
||||
};
|
||||
|
||||
proxyDhcp = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Enable dnsmasq ProxyDHCP chainloading mode.";
|
||||
};
|
||||
|
||||
subnet = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "10.20.30.0/24";
|
||||
description = "ProxyDHCP subnet (required when proxyDhcp.enable is true).";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.proxyDhcp.enable -> cfg.proxyDhcp.subnet != null;
|
||||
message = "services.every-channel.netboot.proxyDhcp.subnet must be set when proxyDhcp.enable is true";
|
||||
}
|
||||
{
|
||||
assertion = cfg.ipxe.buildEmbedded || cfg.ipxe.path != null || cfg.ipxe.allowRemoteDownload;
|
||||
message = "Set ipxe.buildEmbedded=true, or provide ipxe.path, or allow ipxe.allowRemoteDownload=true";
|
||||
}
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.rootDir} 0750 root root -"
|
||||
"d ${cfg.rootDir}/http 0750 root root -"
|
||||
"d ${cfg.rootDir}/tftp 0750 root root -"
|
||||
];
|
||||
|
||||
systemd.services.every-channel-netboot-ipxe = lib.mkIf cfg.ipxe.buildEmbedded {
|
||||
description = "every.channel netboot embedded iPXE build";
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
wantedBy = lib.mkIf cfg.stageOnBoot [ "multi-user.target" ];
|
||||
path = stageToolchain ++ ipxeToolchain;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_ROOT" cfg.rootDir}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_HOSTNAME" cfg.hostname}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_HTTP_PORT" (toString cfg.httpPort)}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_IPXE_FILENAME" cfg.tftpBootFilename}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_IPXE_REPO" cfg.ipxe.repo}
|
||||
${optionalExport "EVERY_CHANNEL_NETBOOT_IPXE_REF" cfg.ipxe.ref}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_IPXE_USE_DOCKER" cfg.ipxe.useDocker}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_IPXE_DOCKER_IMAGE" cfg.ipxe.dockerImage}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_IPXE_DOCKER_PLATFORM" cfg.ipxe.dockerPlatform}
|
||||
${optionalFileExport "EVERY_CHANNEL_NETBOOT_CHAIN_TOKEN" cfg.chainTokenFile}
|
||||
|
||||
${scriptsRoot}/scripts/netboot-build-ipxe.sh
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.every-channel-netboot-stage = {
|
||||
description = "every.channel netboot artifact stage";
|
||||
requires = lib.optionals cfg.ipxe.buildEmbedded [ "every-channel-netboot-ipxe.service" ];
|
||||
after = [ "network-online.target" ] ++ lib.optionals cfg.ipxe.buildEmbedded [ "every-channel-netboot-ipxe.service" ];
|
||||
wants = [ "network-online.target" ] ++ lib.optionals cfg.ipxe.buildEmbedded [ "every-channel-netboot-ipxe.service" ];
|
||||
wantedBy = lib.mkIf cfg.stageOnBoot [ "multi-user.target" ];
|
||||
path = stageToolchain;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_ROOT" cfg.rootDir}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_HOSTNAME" cfg.hostname}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_HTTP_PORT" (toString cfg.httpPort)}
|
||||
${mkExport "EVERY_CHANNEL_FORGE_HOST" cfg.release.host}
|
||||
${mkExport "EVERY_CHANNEL_FORGE_REPO" cfg.release.repo}
|
||||
${optionalExport "EVERY_CHANNEL_NETBOOT_RELEASE_TAG" cfg.release.tag}
|
||||
${optionalExport "EVERY_CHANNEL_NETBOOT_TARBALL" cfg.release.localTarball}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_VERIFY_RELEASE_CHECKSUMS" (boolString cfg.release.verifyChecksums)}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_ALLOW_REMOTE_IPXE" (boolString cfg.ipxe.allowRemoteDownload)}
|
||||
${mkExport "EVERY_CHANNEL_IPXE_EFI_FILENAME" cfg.tftpBootFilename}
|
||||
${optionalExport "EVERY_CHANNEL_IPXE_EFI_SHA256" cfg.ipxe.sha256}
|
||||
${optionalFileExport "EVERY_CHANNEL_FORGE_TOKEN" cfg.release.tokenFile}
|
||||
${optionalFileExport "EVERY_CHANNEL_NETBOOT_CHAIN_TOKEN" cfg.chainTokenFile}
|
||||
|
||||
${lib.optionalString cfg.ipxe.buildEmbedded (mkExport "EVERY_CHANNEL_IPXE_EFI_PATH" "${cfg.rootDir}/tftp/${cfg.tftpBootFilename}")}
|
||||
${lib.optionalString (!cfg.ipxe.buildEmbedded && cfg.ipxe.path != null) (mkExport "EVERY_CHANNEL_IPXE_EFI_PATH" cfg.ipxe.path)}
|
||||
|
||||
${scriptsRoot}/scripts/netboot-stage.sh
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.every-channel-netboot = {
|
||||
description = "every.channel netboot HTTP + TFTP service";
|
||||
requires = [ "every-channel-netboot-stage.service" ];
|
||||
after = [ "network-online.target" "every-channel-netboot-stage.service" ];
|
||||
wants = [ "network-online.target" "every-channel-netboot-stage.service" ];
|
||||
wantedBy = lib.mkIf cfg.stageOnBoot [ "multi-user.target" ];
|
||||
path = stageToolchain ++ [ pkgs.dnsmasq ];
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
};
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_ROOT" cfg.rootDir}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_LISTEN_IP" cfg.listenIP}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_INTERFACE" cfg.interface}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_HOSTNAME" cfg.hostname}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_HTTP_PORT" (toString cfg.httpPort)}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_PROXY_DHCP" (boolString cfg.proxyDhcp.enable)}
|
||||
${optionalExport "EVERY_CHANNEL_NETBOOT_PROXY_SUBNET" cfg.proxyDhcp.subnet}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_TFTP_BOOT_FILENAME" cfg.tftpBootFilename}
|
||||
${mkExport "EVERY_CHANNEL_NETBOOT_HTTP_ALLOWED_CIDRS" (lib.concatStringsSep "," cfg.httpAllowedCIDRs)}
|
||||
${optionalFileExport "EVERY_CHANNEL_NETBOOT_CHAIN_TOKEN" cfg.chainTokenFile}
|
||||
|
||||
${scriptsRoot}/scripts/netboot-serve.sh
|
||||
'';
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.httpPort ];
|
||||
networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall (
|
||||
[ 69 ] ++ lib.optionals cfg.proxyDhcp.enable [ 67 4011 ]
|
||||
);
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue