#!/usr/bin/env bash
# Real demos for P118 "How PXE Network Boot Works". Made on the Mac (Docker Desktop), like P100.
# One container, "pxe-server": a stock Ubuntu 24.04 with systemd as PID 1 (lab/Dockerfile.server), running the
# archive's dnsmasq (DHCP + TFTP) and nginx (HTTP), configured from the files in lab/ (as if Ana wrote them in nano).
# Its "classroom" network is a Linux bridge inside the container (10.20.0.1/24); the classroom PCs are QEMU virtual
# PCs with no disk at all, plugged into that bridge: lab-pc-07 has BIOS firmware (SeaBIOS + the network card's iPXE
# boot ROM), lab-pc-08 has UEFI firmware (OVMF). QEMU emulates their x86 CPU in software (TCG), so they run slower
# than real PCs. Each PC's screen is its serial console in a pty; Ana's terminal is a real bash in a pty.
# The boot files are Ubuntu's own: pxelinux.0 / lpxelinux.0 / ldlinux.c32 from the pxelinux package, the kernel
# (linux) and the signed shim + GRUB (bootx64.efi, grubx64.efi) from Ubuntu 24.04.5's netboot tarball. The initrd is
# ours: busybox plus lab/rootfs/init (an operating system small enough to show it ran from memory).
# Everything stays inside the container; nothing on the Mac's network is touched. demo_driver.py writes
# demo_output.json; build_script.py reads every line and number from there.
set -euo pipefail
cd "$(dirname "$0")"
CACHE=${PXE_CACHE:-$HOME/pxelab-cache}
C=ce-p118-server
mkdir -p "$CACHE"
[ -f "$CACHE/netboot.tar.gz" ] || curl -sSL -o "$CACHE/netboot.tar.gz" \
  https://releases.ubuntu.com/noble/ubuntu-24.04.5-netboot-amd64.tar.gz
if ! ls "$CACHE"/busybox-static_*_amd64.deb >/dev/null 2>&1; then
  docker run --rm --platform linux/amd64 -v "$CACHE:/out" ubuntu:24.04 \
    sh -c 'cd /out && apt-get update -qq >/dev/null && apt-get download busybox-static >/dev/null'
fi
PY=${PY:-/opt/homebrew/bin/python3.14}
[ -d "$CACHE/py/pyte" ] || $PY -m pip install -q --target "$CACHE/py" pyte
B=$(mktemp -d)
cleanup() { docker rm -f $C >/dev/null 2>&1 || true; rm -rf "$B"; }
cleanup; B=$(mktemp -d)
[ -n "${KEEP:-}" ] || trap cleanup EXIT
tar xzf "$CACHE/netboot.tar.gz" -C "$B" ./amd64/linux ./amd64/bootx64.efi ./amd64/grubx64.efi
(cd "$B" && ar x "$CACHE"/busybox-static_*_amd64.deb && mkdir bb && tar xf data.tar.* -C bb ./usr/bin/busybox)
mkdir -p "$B/rootfs"/{bin,proc,sys,dev,root} && cp "$B/bb/usr/bin/busybox" "$B/rootfs/bin/" && cp lab/rootfs/init "$B/rootfs/"
(cd "$B/rootfs" && find . | sort | cpio -o -H newc --quiet | gzip -9n > "$B/initrd.img")
docker build -q -t $C -f lab/Dockerfile.server lab >/dev/null
docker run -d --name $C --hostname pxe-server \
  --privileged --cgroupns=private --tmpfs /run --tmpfs /run/lock $C >/dev/null
for i in $(seq 60); do s=$(docker exec $C systemctl is-system-running 2>/dev/null || true)
  [ "$s" = running ] || [ "$s" = degraded ] && break; sleep 1; done
# off-screen setup, as root: the classroom network, the boot files, the configs Ana wrote
docker exec $C sh -c 'ip link add classroom type bridge && ip addr add 10.20.0.1/24 dev classroom && ip link set classroom up
  for p in pc07 pc08; do ip tuntap add $p mode tap && ip link set $p master classroom && ip link set $p up; done
  mkdir -p /srv/tftp/pxelinux.cfg /srv/tftp/grub
  cp /usr/lib/PXELINUX/pxelinux.0 /usr/lib/PXELINUX/lpxelinux.0 /usr/lib/syslinux/modules/bios/ldlinux.c32 /srv/tftp/'
docker cp "$B/amd64/linux" $C:/srv/tftp/linux
docker cp "$B/amd64/bootx64.efi" $C:/srv/tftp/bootx64.efi
docker cp "$B/amd64/grubx64.efi" $C:/srv/tftp/grubx64.efi
docker cp "$B/initrd.img" $C:/srv/tftp/initrd.img
docker cp lab/tftp/pxelinux.cfg/default $C:/srv/tftp/pxelinux.cfg/default
docker cp lab/tftp/grub/grub.cfg $C:/srv/tftp/grub/grub.cfg
docker cp lab/pxe.conf $C:/etc/dnsmasq.d/pxe.conf
docker cp lab/nginx-site $C:/etc/nginx/sites-available/default
for f in pxe-uefi.conf pxe-http.conf teacher.cfg default-http; do docker cp lab/$f $C:/home/ana/$f; done
docker exec $C sh -c 'chown -R root:root /srv/tftp /etc/dnsmasq.d && chmod -R a+rX /srv/tftp && chown ana:ana /home/ana/* &&
  systemctl restart nginx && systemctl stop dnsmasq'
PYTHONPATH="$CACHE/py" $PY demo_driver.py $C
