Cleanup and improvements

This commit is contained in:
2025-11-07 21:15:29 +01:00
parent 6cb1c7d48b
commit 8c53b59671
22 changed files with 263 additions and 151 deletions

12
.editorconfig Normal file
View File

@@ -0,0 +1,12 @@
root = true
[*]
indent_style = tab
[*.yaml]
indent_style = space
indent_size = 4
[{*.py,tools/merge}]
indent_style = space
indent_size = 4

3
.gitignore vendored
View File

@@ -1,3 +1,2 @@
ipxe/
.ipxe/
rendered/
tftp/

View File

@@ -1,40 +0,0 @@
FROM docker.io/library/debian:stable AS builder-ipxe
RUN apt-get update \
&& apt-get install -y \
build-essential \
curl \
liblzma-dev \
genisoimage
ARG IPXE_VERSION=b41bda4413bf286d7b7a449bc05e1531da1eec2e
RUN curl -L https://github.com/ipxe/ipxe/archive/${IPXE_VERSION}.tar.gz | tar -xz
WORKDIR /ipxe-${IPXE_VERSION}/src
# Enable HTTPS
RUN sed -i 's/^#undef[\t ]DOWNLOAD_PROTO_HTTPS.*$/#define DOWNLOAD_PROTO_HTTPS/g' config/general.h
RUN mkdir /build
RUN make -j$(nproc) bin/ipxe.pxe && cp bin/ipxe.pxe /build
RUN make -j$(nproc) bin-x86_64-efi/ipxe.efi && cp bin-x86_64-efi/ipxe.efi /build
FROM docker.io/library/python:3.13-slim AS config-renderer
COPY --from=docker.io/hairyhenderson/gomplate:v4.3 /gomplate /bin/gomplate
COPY ./requirements.txt /requirements.txt
RUN pip install -r /requirements.txt
COPY ./generate.sh /generate.sh
COPY ./tools /tools
COPY ./nodes /nodes
COPY ./templates /templates
RUN ./generate.sh
FROM docker.io/library/alpine:3.22.2 AS runtime
RUN apk add dnsmasq
COPY --from=builder-ipxe /build/ipxe.pxe /tftproot/
COPY --from=builder-ipxe /build/ipxe.efi /tftproot/
COPY --from=config-renderer /rendered/boot.ipxe /tftproot/
COPY --from=config-renderer /rendered/dnsmasq.conf /dnsmasq.conf
EXPOSE 67/udp
EXPOSE 69/udp
CMD ["dnsmasq", "--conf-file=/dnsmasq.conf", "--keep-in-foreground", "--user=root", "--log-facility=-", "--port=0"]

2
config.yaml Normal file
View File

@@ -0,0 +1,2 @@
dhcp:
tftpIp: 10.0.0.3

View File

@@ -1 +0,0 @@
tftpIp: 10.0.0.3

View File

@@ -1,5 +0,0 @@
#!/usr/bin/env bash
set -euxo pipefail
SCRIPT_DIR=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
${SCRIPT_DIR}/tools/merge.py ./nodes | gomplate -d nodes=stdin://nodes.json -d dhcp=${SCRIPT_DIR}/dhcp.yaml --input-dir ${SCRIPT_DIR}/templates --output-dir ${SCRIPT_DIR}/rendered

View File

@@ -6,4 +6,3 @@ dns0: 1.1.1.1
dns1: 8.8.8.8
ntp: nl.pool.ntp.org
install: false
upgradeIPXE: false

View File

@@ -1,5 +1,6 @@
customization:
systemExtensions:
officialExtensions:
- siderolabs/iscsi-tools
- siderolabs/util-linux-tools
systemExtensions:
officialExtensions:
- siderolabs/iscsi-tools
- siderolabs/util-linux-tools
- siderolabs/intel-ucode

View File

@@ -1,3 +1,2 @@
netmask: 255.255.255.0
gateway: 192.168.1.1
upgradeIPXE: ipxe.pxe

View File

@@ -1,4 +1,4 @@
serial: vm
serial: talos-vm
interface: enp1s0
ip: 192.168.1.2
install: true

View File

@@ -2,35 +2,24 @@
dhcp
echo Starting ${serial}
:start
# Is a known serial is set, execute that
# If an unknown serial is set, exit
# If no serial is set, ask the user
goto node_${serial} || goto manual
goto node_${serial} || shell
# Default behavior (non install mode) is to exit iPXE script
{{ range (datasource "nodes" | jsonArray) }}
{{ range datasource "nodes" }}
{{- if .install }}
# {{ .filename }}
:node_{{ .serial }}
{{- $ipArg := printf "ip=%s::%s:%s:%s:%s::%s:%s:%s" .ip .gateway .netmask .hostname .interface .dns0 .dns1 .ntp }}
{{- $kernelArgs := printf "%s %s" $ipArg .kernelArgs }}
imgfree
kernel https://pxe.factory.talos.dev/image/{{ .schematicID }}/{{ .talosVersion }}/kernel-{{ .arch }} {{ $kernelArgs }} {{- if .upgradeIPXE }} || boot {{ .upgradeIPXE }} {{- end }}
kernel https://pxe.factory.talos.dev/image/{{ .schematicID }}/{{ .talosVersion }}/kernel-{{ .arch }} {{ $kernelArgs }}
initrd https://pxe.factory.talos.dev/image/{{ .schematicID }}/{{ .talosVersion }}/initramfs-{{ .arch }}.xz
boot
{{- end }}
{{ end }}
:manual
menu Select node
{{ range (datasource "nodes" | jsonArray) }}
item {{ .serial }} {{ .hostname }}
{{ end }}
choose selected || goto cancel
goto node_${selected}
:cancel
echo Type exit to restart script
shell
goto start

View File

@@ -1,4 +1,4 @@
{{ $tftpIp := (ds "dhcp").tftpIp -}}
{{ $tftpIp := (ds "config").dhcp.tftpIp -}}
enable-tftp
tftp-root=/tftproot

11
tools/render Executable file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT=$(git rev-parse --show-toplevel)
RENDERED=${ROOT}/rendered
TEMPLATES=${ROOT}/templates
${ROOT}/tools/merge ./nodes > ${RENDERED}/nodes.json
gomplate --input-dir ${TEMPLATES} --output-dir ${RENDERED} \
-d nodes=file://${RENDERED}/nodes.json \
-d config=${ROOT}/config.yaml \

71
tools/tftpd Executable file
View File

@@ -0,0 +1,71 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT=$(git rev-parse --show-toplevel)
IPXE_VERSION=b41bda4413bf286d7b7a449bc05e1531da1eec2e
IPXE_BIN=(bin/ipxe.pxe bin-x86_64-efi/ipxe.efi)
IPXE_DIR=${ROOT}/.ipxe/ipxe-${IPXE_VERSION}
function download_ipxe() {
base_dir=$(dirname ${IPXE_DIR})
# Download the iPXE source if needed
if [ ! -d "${IPXE_DIR}" ]; then
mkdir -p "${base_dir}"
curl -L https://github.com/ipxe/ipxe/archive/${IPXE_VERSION}.tar.gz | tar -xz -C "${base_dir}"
fi
}
function patch_ipxe() {
# Apply patches to iPXE source
cd "${IPXE_DIR}/src"
sed -i 's/^#undef[\t ]DOWNLOAD_PROTO_HTTPS.*$/#define DOWNLOAD_PROTO_HTTPS/g' config/general.h
cat > embed.ipxe << EOF
#!ipxe
dhcp
chain boot.ipxe || shell
EOF
cd - > /dev/null
}
function build_ipxe() {
cd "${IPXE_DIR}/src"
for bin in "${IPXE_BIN[@]}"; do
path=${IPXE_DIR}/src/${bin}
if [ ! -f "${path}" ]; then
make -j$(nproc) ${bin} EMBED=embed.ipxe
fi
done
cd - > /dev/null
}
function render() {
${ROOT}/tools/render
}
function host_tftp() {
TFTP_DIR=$(mktemp --tmpdir -d tftp.XXX)
chmod 755 ${TFTP_DIR}
function cleanup() {
rm -rf ${TFTP_DIR}
}
trap cleanup EXIT
cp ${ROOT}/rendered/boot.ipxe ${TFTP_DIR}
for bin in "${IPXE_BIN[@]}"; do
path=${IPXE_DIR}/src/${bin}
cp ${path} ${TFTP_DIR}
done
echo "Starting tftpd"
sudo in.tftpd --verbosity 100 --permissive -L --secure ${TFTP_DIR}
}
download_ipxe
patch_ipxe
build_ipxe
render
host_tftp

154
tools/vm Executable file
View File

@@ -0,0 +1,154 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT=$(git rev-parse --show-toplevel)
VM_NAME="talos-vm"
VCPUS="2"
RAM_MB="2048"
DISK_GB="10"
NETWORK=talos
CONNECTION="qemu:///system"
function define_network() {
config_file=$(mktemp)
cat > ${config_file} << EOF
<network>
<name>${NETWORK}</name>
<bridge name="talos0" stp="on" delay="0"/>
<forward mode='nat'>
<nat/>
</forward>
<ip address="192.168.1.1" netmask="255.255.255.0">
<dhcp>
<range start="192.168.1.2" end="192.168.1.254"/>
<bootp file='ipxe.pxe'/>
</dhcp>
</ip>
</network>
EOF
function cleanup() {
rm ${config_file}
}
trap cleanup EXIT
if [[ $(virsh --connect="${CONNECTION}" net-list --all | grep -c "${NETWORK}") == "0" ]]; then
virsh --connect="${CONNECTION}" net-define "${config_file}"
virsh --connect="${CONNECTION}" net-start "${NETWORK}"
virsh --connect="${CONNECTION}" net-autostart "${NETWORK}"
fi
trap - EXIT
cleanup
}
function create() {
define_network
if [[ $(virsh --connect="${CONNECTION}" list --all | grep -c "${VM_NAME}") == "0" ]]; then
virt-install --connect="${CONNECTION}" --name="${VM_NAME}" --vcpus="${VCPUS}" --memory="${RAM_MB}" \
--os-variant="linux2022" \
--disk="size=${DISK_GB}" \
--pxe \
--sysinfo system.serial=${VM_NAME} \
--network network="${NETWORK}"
else
echo -n "VM already exists, start it with:
${0} start
"
exit -1
fi
}
function start() {
if [[ $(virsh --connect="${CONNECTION}" list --all | grep -c "${VM_NAME}") > "0" ]]; then
virsh --connect="${CONNECTION}" start ${VM_NAME}
virt-viewer --connect="${CONNECTION}" ${VM_NAME}
else
echo -n "VM doest not exists yet, create it with:
${0} create
"
exit -1
fi
}
function connect() {
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") > "0" ]]; then
virt-viewer --connect="${CONNECTION}" ${VM_NAME}
else
echo "VM is not running"
exit -1
fi
}
function stop() {
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") > "0" ]]; then
virsh --connect="${CONNECTION}" shutdown ${VM_NAME}
WAIT=240
for i in $(seq 0 1 ${WAIT}); do
echo -en "\rWaiting for VM to shutdown... (${i}/${WAIT})"
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") == "0" ]]; then
echo -e "\nVM successfully shutdown"
exit
fi
sleep 1
done
echo -e "\nDestroying VM"
virsh --connect="${CONNECTION}" destroy ${VM_NAME}
else
echo "VM is not running"
exit -1
fi
}
function delete() {
if [[ $(virsh --connect="${CONNECTION}" list --all | grep -c "${VM_NAME}") > "0" ]]; then
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") > "0" ]]; then
virsh --connect="${CONNECTION}" destroy "${VM_NAME}"
fi
virsh --connect="${CONNECTION}" undefine "${VM_NAME}" --remove-all-storage
else
echo "VM doest not exists"
exit -1
fi
if [[ $(virsh --connect="${CONNECTION}" net-list --all | grep -c "${NETWORK}") > "0" ]]; then
virsh --connect="${CONNECTION}" net-destroy "${NETWORK}"
virsh --connect="${CONNECTION}" net-undefine "${NETWORK}"
fi
}
function help() {
echo -n "Available commands:
start
stop
remove
connect
"
}
COMMAND=${1:-}
case ${COMMAND} in
create)
create Create the vm and perform first install
;;
start)
start Start the vm
;;
stop)
stop Stop the vm
;;
delete)
delete Delete the vm
;;
connect)
connect Connect to an already running vm
;;
*)
help
;;
esac

View File

@@ -1,13 +0,0 @@
<network xmlns:dnsmasq='http://libvirt.org/schemas/network/dnsmasq/1.0'>
<name>cluster-vm</name>
<bridge name="cluster0" stp="on" delay="0"/>
<forward mode='nat'>
<nat/>
</forward>
<ip address="192.168.1.1" netmask="255.255.255.0">
<dhcp>
<range start="192.168.1.2" end="192.168.1.254"/>
<bootp file='boot.ipxe'/>
</dhcp>
</ip>
</network>

View File

@@ -1,15 +0,0 @@
#!/usr/bin/env bash
SCRIPT_DIR=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
source ${SCRIPT_DIR}/helper.sh
if [[ $(virsh --connect="${CONNECTION}" net-list --all | grep -c "${NETWORK}") == "0" ]]; then
virsh --connect="${CONNECTION}" net-define "${SCRIPT_DIR}/${NETWORK}.xml"
virsh --connect="${CONNECTION}" net-start "${NETWORK}"
virsh --connect="${CONNECTION}" net-autostart "${NETWORK}"
fi
virt-install --connect="${CONNECTION}" --name="${VM_NAME}" --vcpus="${VCPUS}" --memory="${RAM_MB}" \
--os-variant="linux2022" \
--disk="size=${DISK_GB}" \
--pxe \
--network network="${NETWORK}"

View File

@@ -1,8 +0,0 @@
#!/usr/bin/env bash
SCRIPT_DIR=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
source ${SCRIPT_DIR}/helper.sh
virsh --connect="${CONNECTION}" destroy "${VM_NAME}"
virsh --connect="${CONNECTION}" undefine "${VM_NAME}" --remove-all-storage
virsh --connect="${CONNECTION}" net-destroy "${NETWORK}"
virsh --connect="${CONNECTION}" net-undefine "${NETWORK}"

View File

@@ -1,10 +0,0 @@
set -euxo pipefail
VM_NAME="test"
VCPUS="2"
RAM_MB="2048"
DISK_GB="10"
NETWORK=cluster-vm
CONNECTION="qemu:///system"
IPXE_VERSION=b41bda4413bf286d7b7a449bc05e1531da1eec2e
IPXE_BIN=bin/ipxe.pxe

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env bash
SCRIPT_DIR=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
source ${SCRIPT_DIR}/helper.sh
virsh --connect="${CONNECTION}" start ${VM_NAME}
virt-viewer --connect="${CONNECTION}" ${VM_NAME}
virsh --connect="${CONNECTION}" shutdown ${VM_NAME}

View File

@@ -1,26 +0,0 @@
#!/usr/bin/env bash
SCRIPT_DIR=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
source ${SCRIPT_DIR}/helper.sh
TFTP_DIR=${SCRIPT_DIR}/../tftp
rm -rf "${TFTP_DIR}"
mkdir -p "${TFTP_DIR}"
IPXE_DIR=${SCRIPT_DIR}/../ipxe
IPXE_FILE=${IPXE_DIR}/ipxe-${IPXE_VERSION}/src/${IPXE_BIN}
if [ ! -f "${IPXE_FILE}" ]; then
mkdir -p "${IPXE_DIR}"
rm -rf "${IPXE_DIR}/ipxe-${IPXE_VERSION}"
curl -L https://github.com/ipxe/ipxe/archive/${IPXE_VERSION}.tar.gz | tar -xz -C "${IPXE_DIR}"
cd "${IPXE_DIR}/ipxe-${IPXE_VERSION}/src"
sed -i 's/^#undef[\t ]DOWNLOAD_PROTO_HTTPS.*$/#define DOWNLOAD_PROTO_HTTPS/g' config/general.h
make -j$(nproc) ${IPXE_BIN}
cd -
fi
${SCRIPT_DIR}/../generate.sh
cp ${SCRIPT_DIR}/../rendered/boot.ipxe ${TFTP_DIR}
cp ${IPXE_FILE} ${TFTP_DIR}
sudo in.tftpd -L --secure ./tftp