#!/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"