The flow
- No saved network → the box starts an AP named
Naive-XXXX(balena wifi-connect, which drives NetworkManager over DBus and runs adnsmasqcaptive portal). - Your phone joins
Naive-XXXX; the OS captive-portal detection opens the portal (served by wifi-connect, or the box’s own page atGET /v1/device/portal). - The portal scans nearby WiFi (
GET /v1/device/network/scan) and you pick your home network + enter the password. - The box joins it (
POST /v1/device/network/join→ NetworkManagernmcli dev wifi connect), the AP tears down, and the box comes up on your LAN. - You continue in the normal onboarding wizard at
http://tower.local— this AP step runs before Step 1.
Endpoints (self-hosted only, inert in cloud)
All three are gated to
NAIVE_BUILD=tower and 404/return empty in cloud.
Install
The AP stack is opt-in (it needs AP-capable WiFi hardware and would disrupt a box already on the LAN):network-manager + dnsmasq-base and fetches the balena
wifi-connect binary for the box’s arch (aarch64 or x86_64). wifi-connect
needs NET_ADMIN and an AP-capable WiFi adapter.
What is VM-testable vs. hardware-gated
The web flow + join endpoint are fully testable in a VM with no radio: set
NAIVE_WIFI_MODE=mock (or just run a non-Tower build) and the scan returns a
synthetic network list and join returns a synthetic success — so the captive
portal page and /network/join can be exercised end-to-end in CI.The hardware leg is genuinely not VM-testable — bringing up a real AP,
serving the captive portal over it, and joining a real WiFi radio require
AP-capable WiFi hardware. That leg is validated on a real device.Real-device checklist
Run these on real AP-capable hardware (marked BLOCKED until validated on a device — do not assume they pass from the VM subset):- Boot headless with no saved WiFi → AP
Naive-XXXXappears. - Phone joins the AP → captive portal auto-opens (iOS + Android).
- Portal scan lists real nearby SSIDs (
nmcli dev wifi listparity). - Selecting a network + password joins it (
nmcli dev wifi connect). - The AP tears down and the box is reachable at
http://tower.local. - A wrong password surfaces a clear error and the AP stays up for a retry.
- Reboot after a successful join reconnects automatically (no AP).