Skip to content

feat: Wi-Fi setup hotspot (MAD-808) - #7

Merged
madeofpendletonwool merged 1 commit into
mainfrom
feat/wifi-setup-hotspot
Oct 15, 2026
Merged

madeofpendletonwool merged 1 commit into
mainfrom
feat/wifi-setup-hotspot

Conversation

@madeofpendletonwool

Copy link
Copy Markdown
Owner

Closes MAD-808, the last stage-3 task under MAD-797.

A box flashed with no Wi-Fi settings and no cable can now be set up from a phone alone. If there is no connection, the box becomes its own open Wi-Fi network, Syncphony-Box-XXXX. The TV shows a QR code to join it, the phone's captive-portal page opens, and the user picks the house Wi-Fi (and the server URL if the box doesn't have one yet). Design: ADR 0010.

When it starts

  • After 60 s for a box that has never been online.
  • After 5 min for one that has been online (a marker in /data/boxd/network-seen). The offline screen shows first.
  • It stops when a join succeeds or Ethernet comes up.
  • While it is up, a saved network is rechecked every 5 min, but only when nobody has used the portal for 2 min.
  • The policy is one pure function, decideNet, covered by a table test.

How it works

  • New boxd/internal/wifi package that calls nmcli.
  • The access point is an in-memory connection (save no) in NetworkManager shared mode at 10.42.0.1, so it never reaches /data.
  • The dnsmasq-shared.d drop-in resolves every DNS name to the box and sends DHCP option 114. boxd's :80 listener redirects every request (including connectivity probes) to the form.
  • On save, boxd answers the phone first, then joins. On success it runs data-persist save right away. On failure it brings the hotspot back and shows the reason on both the TV and the form.
  • New /hotspot TV screen. /setup and /start hand off to it, and /status gains wifiSetup and wifi.
  • New image stage stage-syncphony/09-wifi-setup (network-manager, dnsmasq-base, the captive drop-in, boxd ordered after NetworkManager). The smoke test checks it.

Testing

  • make lint and go test -race ./... pass.
  • The new tests cover the policy, the watch loop, the probe redirects, form validation, a successful join including the server save, an unreachable server, and the failed-join path back to the hotspot.
  • The arm64 cross-build succeeds.
  • Not yet run on hardware: real phones' captive-portal behavior and the Pi's AP. The procedure is in docs/testing.md under "The Wi-Fi setup run".

🤖 Generated with Claude Code

- boxd starts an open access point, Syncphony-Box-XXXX (last 4 of the MAC), when there is no Ethernet or Wi-Fi: after 60 s for a box that has never been online, after 5 min for one that has (the offline screen shows first, so a router reboot never means setup)
- NetworkManager runs it in shared mode at 10.42.0.1 (an in-memory connection that never reaches /data); a dnsmasq-shared.d drop-in resolves every name to the box and sends DHCP option 114, making boxd's :80 listener the captive portal
- the portal form lists the scanned networks, plus a hidden-network field, a password, and the server URL when none is set; saving answers the phone, joins with nmcli, and persists the connection to /data right away
- a failed join brings the hotspot back and shows the reason on the TV and the form; Ethernet coming up stops it; a saved network is rechecked every 5 min while the portal is idle
- new /hotspot TV screen with the Wi-Fi join QR; /setup and /start hand off to it, and /status reports the wifi state
- stage-syncphony/09-wifi-setup (network-manager, dnsmasq-base, captive drop-in, boxd after NetworkManager); smoke test covers it
- docs: ADR 0010, README, persistent paths, the Wi-Fi setup run in docs/testing.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@madeofpendletonwool
madeofpendletonwool merged commit 3d24587 into main Oct 15, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant