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
377
nix/modules/ec-ipxe-qemu.nix
Normal file
377
nix/modules/ec-ipxe-qemu.nix
Normal file
|
|
@ -0,0 +1,377 @@
|
|||
{ lib, config, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.every-channel.ipxe-qemu;
|
||||
scriptsRoot = ../..;
|
||||
|
||||
firstDns = lib.head cfg.boot.userNet.dnsServers;
|
||||
qemuBin = "${cfg.package}/bin/qemu-system-x86_64";
|
||||
qemuImgBin = "${cfg.package}/bin/qemu-img";
|
||||
bootNetdevArg = lib.escapeShellArg "user,id=boot0,dns=${firstDns},hostname=${cfg.boot.userNet.hostname},tftp=${cfg.boot.userNet.tftpDir},bootfile=${cfg.boot.userNet.bootFilename}";
|
||||
bootDeviceArg = lib.escapeShellArg "${cfg.boot.userNet.model},netdev=boot0";
|
||||
lanNetdevArg = lib.escapeShellArg "tap,id=lan0,fd=3";
|
||||
lanDeviceArg = lib.escapeShellArg "${cfg.lan.model},netdev=lan0";
|
||||
driveArg = lib.escapeShellArg "if=virtio,format=qcow2,file=${cfg.disk.path}";
|
||||
effectiveChainUrl =
|
||||
if cfg.boot.chainUrl != null
|
||||
then cfg.boot.chainUrl
|
||||
else "http://10.0.2.2:${toString cfg.boot.http.port}/netboot.ipxe";
|
||||
|
||||
bootScript = pkgs.writeText "every-channel-qemu.ipxe" ''
|
||||
#!ipxe
|
||||
dhcp
|
||||
set dns ${firstDns}
|
||||
chain ${effectiveChainUrl} || shell
|
||||
'';
|
||||
bootAsset = pkgs.ipxe.override {
|
||||
embedScript = bootScript;
|
||||
additionalTargets = {
|
||||
"bin/undionly.kpxe" = null;
|
||||
};
|
||||
firmwareBinary = "undionly.kpxe";
|
||||
};
|
||||
|
||||
bootExtraArgs = lib.concatMapStringsSep "\n" (arg: " cmd+=(${lib.escapeShellArg arg})") cfg.extraArgs;
|
||||
|
||||
runner = pkgs.writeShellApplication {
|
||||
name = "every-channel-ipxe-qemu";
|
||||
runtimeInputs = [
|
||||
pkgs.coreutils
|
||||
pkgs.iproute2
|
||||
cfg.package
|
||||
];
|
||||
text = ''
|
||||
set -euo pipefail
|
||||
|
||||
state_dir=${lib.escapeShellArg cfg.stateDir}
|
||||
tftp_dir=${lib.escapeShellArg cfg.boot.userNet.tftpDir}
|
||||
boot_file=${lib.escapeShellArg cfg.boot.userNet.bootFilename}
|
||||
disk_path=${lib.escapeShellArg cfg.disk.path}
|
||||
disk_size=${lib.escapeShellArg cfg.disk.size}
|
||||
lan_ifname=${lib.escapeShellArg cfg.lan.macvtap.name}
|
||||
enable_kvm=${lib.escapeShellArg (lib.boolToString cfg.enableKvm)}
|
||||
enable_lan=${lib.escapeShellArg (lib.boolToString cfg.lan.enable)}
|
||||
serve_http=${lib.escapeShellArg (lib.boolToString cfg.boot.http.enable)}
|
||||
http_bind_ip=${lib.escapeShellArg cfg.boot.http.bindIp}
|
||||
http_port=${toString cfg.boot.http.port}
|
||||
http_root=${lib.escapeShellArg cfg.boot.http.root}
|
||||
|
||||
cleanup() {
|
||||
if [[ "$enable_lan" == true ]]; then
|
||||
ip link del "$lan_ifname" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
terminate() {
|
||||
if [[ -n "''${http_pid:-}" ]]; then
|
||||
kill "$http_pid" 2>/dev/null || true
|
||||
wait "$http_pid" 2>/dev/null || true
|
||||
fi
|
||||
if [[ -n "''${qemu_pid:-}" ]]; then
|
||||
kill "$qemu_pid" 2>/dev/null || true
|
||||
wait "$qemu_pid" 2>/dev/null || true
|
||||
fi
|
||||
exit 0
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
trap terminate TERM INT
|
||||
|
||||
install -d -m 0755 "$state_dir" "$tftp_dir" "$(dirname "$disk_path")"
|
||||
install -m 0644 ${bootAsset}/undionly.kpxe "$tftp_dir/$boot_file"
|
||||
|
||||
if [[ "$serve_http" == true ]]; then
|
||||
if [[ ! -d "$http_root" ]]; then
|
||||
echo "error: boot HTTP root not found: $http_root" >&2
|
||||
exit 1
|
||||
fi
|
||||
${pkgs.python3}/bin/python3 ${scriptsRoot}/scripts/netboot-http-server.py \
|
||||
--bind-ip "$http_bind_ip" \
|
||||
--port "$http_port" \
|
||||
--root "$http_root" &
|
||||
http_pid=$!
|
||||
fi
|
||||
|
||||
if [[ ! -f "$disk_path" ]]; then
|
||||
${qemuImgBin} create -f qcow2 "$disk_path" "$disk_size" >/dev/null
|
||||
fi
|
||||
|
||||
cmd=(
|
||||
${lib.escapeShellArg qemuBin}
|
||||
-name ${lib.escapeShellArg cfg.name}
|
||||
-machine ${lib.escapeShellArg cfg.machine}
|
||||
-cpu ${lib.escapeShellArg cfg.cpu}
|
||||
-smp ${toString cfg.vcpus}
|
||||
-m ${toString cfg.memoryMiB}
|
||||
-nographic
|
||||
-boot ${lib.escapeShellArg cfg.boot.order}
|
||||
-serial ${lib.escapeShellArg cfg.serial}
|
||||
-device virtio-rng-pci
|
||||
)
|
||||
|
||||
if [[ "$enable_kvm" == true ]]; then
|
||||
cmd+=(-enable-kvm)
|
||||
fi
|
||||
|
||||
cmd+=(
|
||||
-netdev ${bootNetdevArg}
|
||||
-device ${bootDeviceArg}
|
||||
)
|
||||
|
||||
if [[ "$enable_lan" == true ]]; then
|
||||
ip link del "$lan_ifname" 2>/dev/null || true
|
||||
ip link add link ${lib.escapeShellArg cfg.lan.macvtap.interface} name "$lan_ifname" type macvtap mode ${lib.escapeShellArg cfg.lan.macvtap.mode}
|
||||
ip link set "$lan_ifname" up
|
||||
tap_index="$(< /sys/class/net/$lan_ifname/ifindex)"
|
||||
exec 3<>"/dev/tap''${tap_index}"
|
||||
cmd+=(
|
||||
-netdev ${lanNetdevArg}
|
||||
-device ${lanDeviceArg}
|
||||
)
|
||||
fi
|
||||
|
||||
cmd+=(
|
||||
-drive ${driveArg}
|
||||
)
|
||||
${bootExtraArgs}
|
||||
|
||||
"''${cmd[@]}" &
|
||||
qemu_pid=$!
|
||||
wait "$qemu_pid"
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
options.services.every-channel.ipxe-qemu = {
|
||||
enable = lib.mkEnableOption "every.channel iPXE/QEMU boot VM";
|
||||
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default = pkgs.qemu_kvm;
|
||||
description = "QEMU package providing qemu-system-x86_64 and qemu-img.";
|
||||
};
|
||||
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "every-channel-ipxe";
|
||||
description = "QEMU guest name.";
|
||||
};
|
||||
|
||||
machine = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "q35,accel=kvm";
|
||||
description = "QEMU machine string.";
|
||||
};
|
||||
|
||||
cpu = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "host";
|
||||
description = "QEMU CPU model.";
|
||||
};
|
||||
|
||||
enableKvm = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Use KVM acceleration when available.";
|
||||
};
|
||||
|
||||
vcpus = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 2;
|
||||
description = "Virtual CPU count.";
|
||||
};
|
||||
|
||||
memoryMiB = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 4096;
|
||||
description = "Guest memory in MiB.";
|
||||
};
|
||||
|
||||
serial = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "mon:stdio";
|
||||
description = "Serial/monitor wiring passed to QEMU.";
|
||||
};
|
||||
|
||||
stateDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/var/lib/every-channel/ipxe-qemu";
|
||||
description = "Persistent state directory for qcow2 disk and boot assets.";
|
||||
};
|
||||
|
||||
disk = {
|
||||
path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "${cfg.stateDir}/disk.qcow2";
|
||||
description = "Persistent qcow2 disk path.";
|
||||
};
|
||||
|
||||
size = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "16G";
|
||||
description = "Size used when initializing the qcow2 disk.";
|
||||
};
|
||||
};
|
||||
|
||||
boot = {
|
||||
order = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "order=n";
|
||||
description = "QEMU boot order, defaulting to network first.";
|
||||
};
|
||||
|
||||
chainUrl = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Optional explicit iPXE chain target fetched by the guest; defaults to the local boot.http server.";
|
||||
};
|
||||
|
||||
http = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Serve a host-local HTTP root for the guest netboot flow.";
|
||||
};
|
||||
|
||||
bindIp = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "127.0.0.1";
|
||||
description = "Bind address used for the host-local boot HTTP server.";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 8080;
|
||||
description = "Port used for the host-local boot HTTP server.";
|
||||
};
|
||||
|
||||
root = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Directory containing kernel/initrd/netboot.ipxe served to the guest.";
|
||||
};
|
||||
};
|
||||
|
||||
userNet = {
|
||||
hostname = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "every-channel-ipxe";
|
||||
description = "Hostname advertised on the user-mode boot network.";
|
||||
};
|
||||
|
||||
dnsServers = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "1.1.1.1" ];
|
||||
description = "DNS servers used by the user-mode boot NIC; the first entry is applied.";
|
||||
};
|
||||
|
||||
tftpDir = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "${cfg.stateDir}/tftp";
|
||||
description = "TFTP directory used by the user-mode boot NIC.";
|
||||
};
|
||||
|
||||
bootFilename = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "undionly.kpxe";
|
||||
description = "Boot asset filename exposed on the user-mode boot NIC.";
|
||||
};
|
||||
|
||||
model = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "e1000";
|
||||
description = "NIC model used for the boot network.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
lan = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Attach a second NIC to a real LAN using macvtap.";
|
||||
};
|
||||
|
||||
model = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "virtio-net-pci";
|
||||
description = "NIC model used for the LAN attachment.";
|
||||
};
|
||||
|
||||
macvtap = {
|
||||
interface = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Host interface used as the lower device for macvtap.";
|
||||
};
|
||||
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "ecqemu0";
|
||||
description = "macvtap link name created while the guest runs.";
|
||||
};
|
||||
|
||||
mode = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"private"
|
||||
"vepa"
|
||||
"bridge"
|
||||
"passthru"
|
||||
"source"
|
||||
];
|
||||
default = "bridge";
|
||||
description = "macvtap mode used for the LAN attachment.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
extraArgs = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Additional arguments appended to the QEMU command.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.boot.userNet.dnsServers != [ ];
|
||||
message = "services.every-channel.ipxe-qemu.boot.userNet.dnsServers must not be empty";
|
||||
}
|
||||
{
|
||||
assertion = (!cfg.lan.enable) || (cfg.lan.macvtap.interface != null);
|
||||
message = "services.every-channel.ipxe-qemu.lan.macvtap.interface must be set when lan.enable is true";
|
||||
}
|
||||
{
|
||||
assertion = (!cfg.boot.http.enable) || (cfg.boot.http.root != null);
|
||||
message = "services.every-channel.ipxe-qemu.boot.http.root must be set when boot.http.enable is true";
|
||||
}
|
||||
];
|
||||
|
||||
environment.systemPackages = [ runner ];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${cfg.stateDir} 0755 root root -"
|
||||
"d ${cfg.boot.userNet.tftpDir} 0755 root root -"
|
||||
];
|
||||
|
||||
systemd.services.every-channel-ipxe-qemu = {
|
||||
description = "every.channel iPXE QEMU VM";
|
||||
after = [ "local-fs.target" "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [
|
||||
pkgs.coreutils
|
||||
pkgs.iproute2
|
||||
cfg.package
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${runner}/bin/every-channel-ipxe-qemu";
|
||||
Restart = "always";
|
||||
RestartSec = 5;
|
||||
WorkingDirectory = cfg.stateDir;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue