#!/bin/sh
# bty-web PXE activation helper.
#
# Invoked by the ``bty`` service user via the entry in
# ``/etc/sudoers.d/bty-web``. Writes the bty-pxe-proxy env file at
# ``/etc/default/bty-pxe-proxy`` and starts (or restarts) the
# ``bty-pxe-proxy.service`` so the daemon picks up the new
# interface binding.
#
# Proxy-DHCP only: bty assumes there is already a DHCP server on
# the segment handing out IPs. The daemon just answers PXE-
# specific queries layered on top. Full-DHCP-mode is intentionally
# not offered -- "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.
#
# An older flow wrote dnsmasq config at /etc/dnsmasq.d/bty-pxe-
# active.conf and restarted dnsmasq. That path is gone --
# dnsmasq's own proxy-DHCP in trixie (2.91) doesn't put the
# bootfile in the offer for modern UEFI firmware; we replaced
# it with our own daemon (``bty-pxe-proxy``).
#
# Args:
#   $1  network interface (e.g. ``eth0``, ``ens18``)
#   $2  subnet network address (e.g. ``192.168.1.0``); informational
#       only -- the daemon binds the interface and observes broadcast
#       PXE discovers; it doesn't need the subnet to function. Kept
#       in the env file so the /ui/settings page can display it as
#       "active on <iface> for <subnet>".

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/default/bty-pxe-proxy
# 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/default "bty-pxe-proxy.XXXXXX")
trap 'rm -f "$TMP"' EXIT

cat > "$TMP" <<EOF
# Generated by bty-web-activate-pxe.
# Read by bty-pxe-proxy.service via EnvironmentFile=.
# Removing this file (and stopping bty-pxe-proxy.service) deactivates PXE.

BTY_PXE_INTERFACE=$IFACE
BTY_PXE_SUBNET=$SUBNET
EOF

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

# Cleanup of any historical dnsmasq-proxy-DHCP file from an
# appliance upgraded in place. Safe no-op on fresh bakes.
if [ -f /etc/dnsmasq.d/bty-pxe-active.conf ]; then
    rm -f /etc/dnsmasq.d/bty-pxe-active.conf
fi

# Always restart dnsmasq, not just when a legacy file was cleaned
# up. The bty-pxe.conf shipped in /etc/dnsmasq.d/ may have changed
# across an in-place upgrade (e.g. dropping ``enable-tftp`` so
# bty-tftp.service can bind UDP 69); the running dnsmasq is still
# on the OLD config until restart. Unconditional restart costs a
# tiny dnsmasq blip but guarantees we end in a known good state
# regardless of upgrade path.
systemctl restart dnsmasq.service

# daemon-reload ensures systemd picks up any updated unit file on
# an in-place upgrade where the appliance's bty-lab was pip-
# updated since the last activate -- without this, systemctl
# restart would use the stale cached unit.
systemctl daemon-reload
# Restart picks up new EnvironmentFile contents. The units were
# enabled at bake time (cloud-init runcmd) -- doing it here would
# need /etc/systemd/system writable, which bty-web's
# ProtectSystem=full denies. Both halves of the PXE stack come up
# together: bty-pxe-proxy answers DHCP discovers + tells clients
# which file to fetch, bty-tftp serves the actual file. Either
# alone is useless.
systemctl restart bty-pxe-proxy.service bty-tftp.service

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