Per-repo documentation — each repo's docs/ and README, ingested and associated with the repo. Rendered through the <<<RepoDocs>>> tag.
← gitlab__testmonkeyalpha-group__jazz-forge__netboot docs · README.md
A stand-alone demo that turns the Mac into a Raspberry Pi 5 (naszilla) network-boot server and proves, from a packet capture, whether the Pi truly netbooted.
It runs six stages end-to-end and ends with a one-word verdict:
pin -> serve -> capture -> boot -> decode -> verdict
| Stage | What it does | |---------|--------------| | pin | Give the Mac's USB-ethernet port en8 the fixed address 10.10.0.10/24 — its end of the direct cable to the Pi. | | serve | Start dnsmasq on en8 (DHCP :67 + TFTP :69) and prove both are bound (not merely "running"). | | capture | Run tcpdump on en8 into a timestamped pcap — the evidence. | | boot | Reboot the Pi over SSH and watch her down→up cycle. (Skipped in dry-run.) | | decode | Parse the pcap once, via scapy, into {discovers, offers, acks, tftp_rrqs}. | | verdict | From that one decode: NETBOOT / LEASE_ONLY / NO_OFFER. |
Mac ──en8 (USB-ethernet, Realtek RTL8153)── direct cable ── Pi 5 (naszilla) eth0
10.10.0.10/24 DHCPs from .50-.150
dnsmasq: DHCP + TFTP, tcpdump capturing
An isolated point-to-point segment. The Pi's bootloader DHCPs (before Linux), gets an OFFER, then pulls her boot files over TFTP from /Users/steve/pi-netboot/tftp (serial dir 18cc271b/, already staged with all 56 boot files). The capture is the proof.
cd ~/code/netboot
./netboot.sh build # check prerequisites + assets (no network changes)
./netboot.sh dry-run # full cycle on the EMPTY wire, no Pi reboot -> expect NO_OFFER
./netboot.sh live-test # full cycle WITH a Pi reboot -> expect NETBOOT
./netboot.sh stop # kill dnsmasq + tcpdump, release the en8 pin
The pin / serve / capture stages need sudo (set a manual IP, bind ports, sniff the wire). Run from a terminal that can authenticate; macOS will prompt.
./netboot.sh build && ./netboot.sh dry-run
- ./netboot.sh build — all-green prerequisite report (python>=3.11, dnsmasq, tcpdump, uv for scapy, en8 present, TFTP serial dir staged), ending in BUILD OK. Changes nothing on the wire.
- ./netboot.sh dry-run (Pi OFF or uncabled) — pins en8, starts the server, captures ~60s of an empty wire, skips the reboot, decodes, and prints:
==> VERDICT: NO_OFFER — no DHCP traffic on the wire (empty wire / Pi off / no boot attempt)
This validates the whole plumbing without the Pi. NO_OFFER is the correct dry-run result.
- ./netboot.sh live-test (Pi ON + en8 cabled) — same, but reboots the Pi over SSH and watches her cycle. Expected:
==> VERDICT: NETBOOT — leased an address AND pulled boot files over TFTP
- LEASE_ONLY = she got an address but no TFTP → fix the /<serial>/ path. - NO_OFFER with DISCOVERs > 0 = she asked but the server didn't answer →
`en8` subnet/binding (the failure mode of the last live run: 64 DISCOVERs / 0 OFFERs).
./netboot.sh verdict /Users/steve/pi-netboot/netboot-YYYYmmdd-HHMMSS.pcap
Decode + verdict are pure functions of the pcap, so any capture can be re-judged offline. (Verified against the prior netboot-20260628-225119.pcap: 315 packets, 64 DISCOVERs, 0 OFFERs → NO_OFFER.)
- Trace log /Users/steve/pi-netboot/netboot.trace — one tab-separated line per stage, greppable, no emoji:
2026-06-29T14:23:45Z pin done en8=10.10.0.10/255.255.255.0 link=up
2026-06-29T14:23:46Z serve done dnsmasq SERVING DHCP:67=1 TFTP:69=1 ...
2026-06-29T14:23:47Z capture done pcap=netboot-20260629-142347.pcap
2026-06-29T14:23:50Z boot skipped dry-run: idle capture 60s (Pi not rebooted)
2026-06-29T14:24:50Z decode done pkts=0 discovers=0 offers=0 acks=0 tftp_rrqs=0
2026-06-29T14:24:50Z verdict done NO_OFFER (...)
- pcap in /Users/steve/pi-netboot/ (latest path also in .last-pcap). - Telemetry into ~/code/forge/telemetry.db (component netboot) — fire-and-forget, off if jazz_telemetry is unavailable.
To power/cable the Pi by hand between stages:
./netboot.sh serve # pin + serve + capture, then leaves them running
# ... power on + cable the Pi, let her try to boot ...
./netboot.sh stop # tear down
./netboot.sh verdict /Users/steve/pi-netboot/netboot-<stamp>.pcap
netboot/
├── netboot.py driver — six stages, centralized decode, structured trace
├── __main__.py CLI (run/serve/verdict/decode/stop/status; --dry/--step/--watch)
├── __init__.py
├── netboot.toml all addresses, paths, Pi SSH contact, telemetry
├── netboot.sh walkthrough CLI: build / dry-run / live-test / serve / stop / verdict
├── bin/netboot thin wrapper (run `netboot` from anywhere)
└── README.md this file
vm/netboot/ EXPERIMENTAL alternate topology (server inside a VM) — see below
├── netboot.xml libvirt domain: SLIRP eth0 (ssh :2223) + USB-NIC passthrough eth1
├── user-data cloud-init (uid 501 for 9p RW, scapy/dnsmasq/tcpdump, eth1=10.10.0.10)
├── meta-data
├── netboot_ed25519 SSH keypair for the VM
├── netboot_ed25519.pub
└── build-vm.sh builds seed.iso + disk, then virsh define/start
python3 -m netboot run --dry --watch # dry-run with a live trace tail
python3 -m netboot run --step # pause after each stage
python3 -m netboot serve # server up, left running
python3 -m netboot verdict <pcap> # re-judge a capture
python3 -m netboot status # interface + process + trace state
python3 -m netboot stop # tear down
- macOS (Apple Silicon), dnsmasq (Homebrew), tcpdump, python3 >= 3.11. - uv (the decode runs uv run --with scapy, so no global scapy install is needed). Without uv, install scapy into the calling python and clear decode.scapy_runner in netboot.toml. - The Realtek RTL8153 USB-ethernet adapter on en8, cabled to the Pi. - TFTP assets already staged at /Users/steve/pi-netboot/tftp/18cc271b/. - SSH to the Pi via the naszilla alias (~/.ssh/config → 10.0.0.156, key pi-fleet). Only used by live-test to trigger the reboot; skipped gracefully if unreachable.
The design also called for an isolated topology where the server runs inside a libvirt VM with the USB-NIC passed through to the guest (the Mac goes blind on en8; the guest owns the wire). It's fully manifested in vm/netboot/:
vm/netboot/build-vm.sh # build + start the VM
ssh -i vm/netboot/netboot_ed25519 -p 2223 [email protected]
# in the guest:
ip -br link # expect eth1 (the RTL8153)
cd /Users/steve/code/netboot && python3 -m netboot run --dry
Caveat: USB passthrough on macOS HVF is fragile. If eth1 doesn't appear in the guest, passthrough failed — fall back to the host path (./netboot.sh), which is the proven one. The VM path is provided for completeness, not relied upon.
pin sets a manual IP on en8; stop (and ./netboot.sh stop) releases it back to DHCP and kills both dnsmasq and tcpdump. While the demo runs, the Mac uses en8 as the Pi's isolated boot segment — it is not your normal LAN uplink.
Do not revert the Pi's EEPROM — naszilla stays at BOOT_ORDER=0xf612 (net → SD → NVMe). This demo serves that net-boot attempt; it does not change Pi firmware.