ops: add CI boot-image releases and Unifi PXE rollout
Some checks failed
ci-gates / checks (push) Has been cancelled
deploy-cloudflare / checks (push) Has been cancelled
deploy-cloudflare / deploy (push) Has been cancelled

This commit is contained in:
every.channel 2026-02-28 22:53:59 -08:00
parent 043b1730dc
commit be26313225
No known key found for this signature in database
9 changed files with 720 additions and 0 deletions

99
scripts/netboot-serve.sh Executable file
View file

@ -0,0 +1,99 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "${root}"
netboot_root="${EVERY_CHANNEL_NETBOOT_ROOT:-tmp/netboot}"
http_dir="${netboot_root}/http"
tftp_dir="${netboot_root}/tftp"
listen_ip="${EVERY_CHANNEL_NETBOOT_LISTEN_IP:-}"
interface_name="${EVERY_CHANNEL_NETBOOT_INTERFACE:-}"
proxy_subnet="${EVERY_CHANNEL_NETBOOT_PROXY_SUBNET:-}"
netboot_hostname="${EVERY_CHANNEL_NETBOOT_HOSTNAME:-}"
http_port="${EVERY_CHANNEL_NETBOOT_HTTP_PORT:-8080}"
dnsmasq_port="${EVERY_CHANNEL_NETBOOT_DNS_PORT:-0}"
need_cmd() {
local name="$1"
if ! command -v "${name}" >/dev/null 2>&1; then
echo "error: required command not found: ${name}" >&2
exit 2
fi
}
need_cmd dnsmasq
need_cmd python3
if [[ "$(id -u)" -ne 0 ]]; then
echo "error: netboot-serve requires root (TFTP + ProxyDHCP ports)." >&2
echo "hint: run with sudo and pass env vars, for example:" >&2
echo " sudo EVERY_CHANNEL_NETBOOT_LISTEN_IP=10.20.30.2 EVERY_CHANNEL_NETBOOT_INTERFACE=eth0 EVERY_CHANNEL_NETBOOT_PROXY_SUBNET=10.20.30.0/24 EVERY_CHANNEL_NETBOOT_HOSTNAME=boot.every.channel ./scripts/netboot-serve.sh" >&2
exit 2
fi
if [[ -z "${listen_ip}" ]]; then
echo "error: set EVERY_CHANNEL_NETBOOT_LISTEN_IP (boot server IP on NUC VLAN)" >&2
exit 2
fi
if [[ -z "${interface_name}" ]]; then
echo "error: set EVERY_CHANNEL_NETBOOT_INTERFACE (interface on NUC VLAN)" >&2
exit 2
fi
if [[ -z "${proxy_subnet}" ]]; then
echo "error: set EVERY_CHANNEL_NETBOOT_PROXY_SUBNET (for example 10.20.30.0/24)" >&2
exit 2
fi
if [[ -z "${netboot_hostname}" ]]; then
netboot_hostname="${listen_ip}"
fi
for required in "${http_dir}/kernel" "${http_dir}/initrd" "${http_dir}/netboot.ipxe" "${tftp_dir}/ipxe.efi"; do
if [[ ! -f "${required}" ]]; then
echo "error: missing required staged file: ${required}" >&2
echo "hint: run ./scripts/netboot-stage.sh first" >&2
exit 2
fi
done
run_dir="$(mktemp -d)"
cleanup() {
if [[ -n "${http_pid:-}" ]] && kill -0 "${http_pid}" >/dev/null 2>&1; then
kill "${http_pid}" >/dev/null 2>&1 || true
wait "${http_pid}" 2>/dev/null || true
fi
rm -rf "${run_dir}"
}
trap cleanup EXIT INT TERM
cat > "${run_dir}/dnsmasq.conf" <<EOF
port=${dnsmasq_port}
bind-interfaces
interface=${interface_name}
listen-address=${listen_ip}
log-dhcp
enable-tftp
tftp-root=${tftp_dir}
dhcp-range=${proxy_subnet},proxy
dhcp-userclass=set:ipxe,iPXE
dhcp-match=set:efi64,option:client-arch,7
dhcp-match=set:efi64,option:client-arch,9
dhcp-option=66,${netboot_hostname}
dhcp-boot=tag:!ipxe,tag:efi64,ipxe.efi
dhcp-boot=tag:ipxe,tag:efi64,http://${netboot_hostname}:${http_port}/netboot.ipxe
dhcp-boot=tag:!ipxe,ipxe.efi
dhcp-boot=tag:ipxe,http://${netboot_hostname}:${http_port}/netboot.ipxe
EOF
python3 -m http.server "${http_port}" --bind "${listen_ip}" --directory "${http_dir}" >/tmp/every-channel-netboot-http.log 2>&1 &
http_pid="$!"
echo "ok: HTTP serving ${http_dir} on http://${listen_ip}:${http_port}/"
echo "ok: advertised netboot host: ${netboot_hostname}"
echo "ok: TFTP serving ${tftp_dir} on ${listen_ip}:69"
echo "ok: ProxyDHCP active for ${proxy_subnet} on interface ${interface_name}"
echo "ok: Use normal Unifi DHCP for IP assignment; do not configure Unifi DHCP bootfile while proxy mode is active."
echo
echo "Press Ctrl+C to stop."
dnsmasq --no-daemon --conf-file="${run_dir}/dnsmasq.conf"

130
scripts/netboot-stage.sh Executable file
View file

@ -0,0 +1,130 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "${root}"
forge_host="${EVERY_CHANNEL_FORGE_HOST:-https://forge.every.channel}"
forge_repo="${EVERY_CHANNEL_FORGE_REPO:-every-channel/every.channel}"
release_tag="${EVERY_CHANNEL_NETBOOT_RELEASE_TAG:-}"
local_tarball="${EVERY_CHANNEL_NETBOOT_TARBALL:-}"
out_root="${EVERY_CHANNEL_NETBOOT_ROOT:-tmp/netboot}"
ipxe_efi_url="${EVERY_CHANNEL_IPXE_EFI_URL:-https://boot.ipxe.org/snponly.efi}"
netboot_hostname="${EVERY_CHANNEL_NETBOOT_HOSTNAME:-boot.every.channel}"
http_port="${EVERY_CHANNEL_NETBOOT_HTTP_PORT:-8080}"
token="${EVERY_CHANNEL_FORGE_TOKEN:-${FORGE_TOKEN:-${CODEBERG_TOKEN:-}}}"
need_cmd() {
local name="$1"
if ! command -v "${name}" >/dev/null 2>&1; then
echo "error: required command not found: ${name}" >&2
exit 2
fi
}
need_cmd curl
need_cmd tar
need_cmd python3
tmp_dir="$(mktemp -d)"
cleanup() {
rm -rf "${tmp_dir}"
}
trap cleanup EXIT
archive_path="${tmp_dir}/netboot.tar.gz"
release_asset_url=""
if [[ -n "${local_tarball}" ]]; then
if [[ ! -f "${local_tarball}" ]]; then
echo "error: netboot tarball not found: ${local_tarball}" >&2
exit 2
fi
cp -f "${local_tarball}" "${archive_path}"
else
api_base="${forge_host%/}/api/v1/repos/${forge_repo}"
release_endpoint="${api_base}/releases/latest"
if [[ -n "${release_tag}" ]]; then
release_endpoint="${api_base}/releases/tags/${release_tag}"
fi
auth_args=()
if [[ -n "${token}" ]]; then
auth_args=(-H "Authorization: token ${token}")
fi
release_json="${tmp_dir}/release.json"
curl -fsSL "${auth_args[@]}" "${release_endpoint}" -o "${release_json}"
release_asset_url="$(
python3 - "${release_json}" <<'PY'
import json
import sys
path = sys.argv[1]
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
assets = data.get("assets", [])
candidates = []
for asset in assets:
name = asset.get("name", "")
if name.startswith("ec-runner-x86_64-netboot-") and name.endswith(".tar.gz"):
candidates.append(asset)
if not candidates:
sys.exit(1)
# Pick newest by release ordering if API already sorted; otherwise prefer largest id.
chosen = sorted(candidates, key=lambda x: x.get("id", 0))[-1]
print(chosen.get("browser_download_url", ""))
PY
)"
if [[ -z "${release_asset_url}" ]]; then
echo "error: unable to find x86_64 netboot asset in release" >&2
exit 2
fi
curl -fsSL "${auth_args[@]}" -o "${archive_path}" "${release_asset_url}"
fi
http_dir="${out_root}/http"
tftp_dir="${out_root}/tftp"
rm -rf "${http_dir}"
mkdir -p "${http_dir}" "${tftp_dir}"
tar -xzf "${archive_path}" -C "${http_dir}"
for required in kernel initrd netboot.ipxe; do
if [[ ! -f "${http_dir}/${required}" ]]; then
echo "error: extracted netboot bundle is missing ${required}" >&2
exit 2
fi
done
curl -fsSL -o "${tftp_dir}/ipxe.efi" "${ipxe_efi_url}"
cp -f "${http_dir}/netboot.ipxe" "${tftp_dir}/netboot.ipxe"
cat > "${tftp_dir}/bootstrap.ipxe" <<'EOF'
#!ipxe
dhcp
chain http://__NETBOOT_HOST__:__HTTP_PORT__/netboot.ipxe
EOF
sed -i.bak \
-e "s#__NETBOOT_HOST__#${netboot_hostname}#g" \
-e "s#__HTTP_PORT__#${http_port}#g" \
"${tftp_dir}/bootstrap.ipxe"
rm -f "${tftp_dir}/bootstrap.ipxe.bak"
echo "ok: staged netboot content"
echo "ok: http root: ${http_dir}"
echo "ok: tftp root: ${tftp_dir}"
echo "ok: netboot hostname: ${netboot_hostname}"
echo "ok: netboot http port: ${http_port}"
if [[ -n "${release_asset_url}" ]]; then
echo "ok: source asset: ${release_asset_url}"
else
echo "ok: source asset: ${local_tarball}"
fi
echo "hint: run sudo ./scripts/netboot-serve.sh to expose HTTP+TFTP+ProxyDHCP"