#!/bin/sh
# bty-web PXE activation helper.
#
# Invoked by the ``bty`` service user via the entry in
# ``/etc/sudoers.d/bty-web``. Writes a dnsmasq proxy-DHCP config to
# ``/etc/dnsmasq.d/bty-pxe-active.conf`` and restarts
# ``dnsmasq.service`` so the new config takes effect.
#
# Proxy-DHCP only: bty assumes there is already a DHCP server on
# the segment handing out IPs. dnsmasq just answers PXE-specific
# queries layered on top. We deliberately do NOT support full DHCP
# - "I'm the only DHCP server on this LAN" is the easiest way for
# an operator to accidentally rogue-DHCP a production network and
# wreck other clients' leases. If you genuinely need DHCP-as-a-
# service, run a dedicated DHCP daemon next to bty.
#
# The shipped ``/etc/dnsmasq.d/bty-pxe.conf`` keeps everything
# commented as documentation; this active config goes in a separate
# file so the original stays as the canonical example. Removing
# the active file + ``systemctl restart dnsmasq`` deactivates PXE
# again.
#
# Args:
#   $1  network interface (e.g. ``eth0``, ``ens18``)
#   $2  subnet network address (e.g. ``192.168.1.0``)

set -eu

if [ $# -ne 2 ]; then
    printf 'usage: %s <interface> <subnet>\n' "$0" >&2
    exit 64
fi

IFACE=$1
SUBNET=$2

# Validate inputs (defence in depth - the caller bty-web also
# validates, but this script runs as root).
case "$IFACE" in
    "" | *[!a-zA-Z0-9_-]*) printf 'bad interface name: %s\n' "$IFACE" >&2; exit 64 ;;
esac
case "$SUBNET" in
    "" | *[!0-9.]*) printf 'bad subnet: %s\n' "$SUBNET" >&2; exit 64 ;;
esac

ACTIVE=/etc/dnsmasq.d/bty-pxe-active.conf
# Same-filesystem tempfile so the final ``mv`` is an atomic
# rename, not a cross-device copy-then-unlink. With $TMPDIR on a
# different filesystem (typical /tmp on tmpfs vs /etc on rootfs),
# coreutils' mv falls back to copy-then-unlink-source-then-unlink-
# target; any failure in that sequence -- "Read-only file system"
# on the target dir is the historical reproducer -- aborts the
# activation mid-write. Same-dir tempfile sidesteps that path.
TMP=$(mktemp -p /etc/dnsmasq.d "bty-pxe-active.conf.XXXXXX")
trap 'rm -f "$TMP"' EXIT

# ``bind-dynamic`` (instead of bind-interfaces) tolerates the
# interface coming up after dnsmasq starts - relevant when a
# systemd-networkd unit assigns the static IP after dnsmasq.service
# has already started its bind sequence.
cat > "$TMP" <<EOF
# Generated by bty-web-activate-pxe.
# Removing this file and restarting dnsmasq deactivates the chain.

# log-dhcp: log every DHCP transaction (DISCOVER, OFFER, REQUEST,
# ACK, NAK) plus TFTP file fetches to journalctl. Without this,
# ``journalctl -u dnsmasq`` only shows startup banner + "bound to
# interface X" lines, which gives the operator zero signal when
# diagnosing "the target machine isn't picking up bty" -- the
# question "did the client ever talk to dnsmasq?" becomes
# answerable from logs alone, no tcpdump needed. Cheap: PXE
# transactions are rare events (a handful per machine boot), so
# log volume is trivial compared to the diagnostic value.
log-dhcp

bind-dynamic
interface=$IFACE
dhcp-range=$SUBNET,proxy

dhcp-match=set:bios,option:client-arch,0
dhcp-match=set:efi,option:client-arch,7
dhcp-match=set:efi,option:client-arch,9
dhcp-userclass=set:ipxe,iPXE

dhcp-boot=tag:!ipxe,tag:bios,undionly.kpxe
dhcp-boot=tag:!ipxe,tag:efi,ipxe.efi
dhcp-boot=tag:ipxe,http://\${next-server}:8080/pxe-bootstrap.ipxe
EOF

chown root:root "$TMP"
chmod 0644 "$TMP"
mv "$TMP" "$ACTIVE"
trap - EXIT

systemctl restart dnsmasq.service

printf 'PXE activated on %s (proxy-DHCP on %s)\n' "$IFACE" "$SUBNET"
