- Menu asks which adapter when several exist (shows USB/PCI); the web UI
helper prefers the adapter already configured.
- Stale-entry cleanup also marks remaining installer wifis optional so a
dead WiFi entry can't hold up boot.
- After a failed connect, read wpa_supplicant's log to say wrong password /
network not found / rejected.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A97KUggSE3rBL8veDM5Mr4
netplan apply waits ~90s for every wifis: interface in every file and then
fails if one doesn't exist (e.g. installer config naming wlan0 while the
adapter is wlp1s0). Remove such entries (backup kept as *.kiosk-bak) before
applying, in both the menu and kiosk-wifi-helper.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A97KUggSE3rBL8veDM5Mr4
Reported: Core Settings -> WiFi "just returns to the core settings menu".
run_menu clears the screen the moment a handler returns, and every early
exit in wifi_menu returned without a pause - so whatever went wrong
(no adapter found, interface wouldn't come up, ...) was wiped before it
could be read.
- wifi_menu now runs the wizard (renamed wifi_configure) and always
pauses afterwards, success or failure.
- Adapter detection by type (/sys/class/net/*/wireless or phy80211),
falling back to wl* - not every adapter is named wl*. When none is
found, lists the interfaces that do exist.
- Clears a software rfkill block via sysfs before `ip link set up`
(rfkill isn't installed on a server); warns on a hard block; shows
ip's actual error if bringing the link up still fails.
- Writes its own $NETPLAN_DIR/60-kiosk-wifi.yaml instead of overwriting
the first *.yaml found - on a fresh install that's the installer's
50-cloud-init.yaml with the wired config, which it silently dropped.
- SSID/password written as YAML single-quoted scalars: a password with
" or \ produced invalid YAML before.
- Success = the WiFi interface itself has an IPv4 address (waits up to
30s), not "the machine has some IP". The SSH revert watchdog uses the
same test instead of pinging 8.8.8.8, which on a network without
internet reverted every working connection; with no previous WiFi file
it reverts by removing ours.
Tested with ip/netplan/sudo stubbed and netplan files in a scratch dir:
installer's wired file untouched, own file written, SSID
`Bob's "Café" #2` and password `p@ss"wo'rd\\:#x` round-trip through a
YAML parser exactly, no-address case reports instead of claiming
success, early errors stay on screen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
A default Ubuntu Server 26.04 install only gets wpasupplicant/iw when
WiFi is configured in the installer's network screen (they're not in the
server manifest otherwise). Without them netplan can't join a WiFi
network and Core Settings -> WiFi stops at "No WiFi tools found" - which
is exactly the offline-first case: install with no network, then use the
kiosk's WiFi menu to connect.
- Add wpasupplicant and iw to PROVISION_APT_PACKAGES, so first boot
installs them in step 1 (from the offline bundle when there's no
internet) - before Core Settings -> WiFi is offered. +1.6 MB in the
bundle.
- wifi_menu installs them itself (via run_with_offline_fallback) if
they're missing, instead of erroring with an apt command to run.
Bundle rebuilt: contains wpasupplicant and iw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
By far the riskiest menus migrated so far. Both can affect real system
state outside config.json in ways that are hard to reverse: WiFi
rewrites live netplan config and, over SSH, can disconnect the very
session configuring it; power scheduling can shut the physical machine
down and wake it via RTC.
- lib/config.sh: new $SYSTEMD_DIR/$CRON_D_DIR/$BIN_DIR/$NETPLAN_DIR,
same `: "${VAR:=default}"` pattern as $KIOSK_DIR. Nothing under
menus/ hardcodes /etc/systemd/system, /etc/cron.d, /usr/local/bin, or
/etc/netplan directly, so every test in this change points them at
scratch space instead of ever touching this sandbox's real systemd
units, cron, or network config.
- lib/menu.sh: ported get_ip_address (also fixing its "No IP" fallback,
which never actually fired before - `hostname -I | awk` always exits
0 even on empty output).
- menus/wifi.sh: apply_wifi_config split out from wifi_menu specifically
so tests can drive the netplan-writing logic without needing real
scan hardware. Preserves the legacy netplan backup, 60s SSH watchdog,
and restore-on-failure behavior exactly.
- menus/power_schedule.sh: power schedule (+ RTC wake), display
schedule, quiet hours, and an Electron reload timer (with its own
nested run_menu, mirroring the legacy configured/not-configured
dispatch), plus remove-all. Deliberately excludes the legacy
dispatcher's "Test schedules & system" - a shared diagnostics submenu
(audio/network/keyboard tests) that isn't specific to scheduling and
belongs with a future Advanced/Diagnostics migration instead.
Bugs found and fixed along the way, none papered over:
- The legacy dispatcher refused to open "Configure power schedule" at
all without RTC hardware, even though shutdown-only mode never needed
RTC. Now always available.
- None of the six HH:MM prompts across these menus (shutdown, wake,
display off/on, quiet start/end, custom Electron reload time) were
validated before - plain `read`, no format check. All now go through
ask_time.
- set -e safety (same class as the v2.1.0 run_menu fix), three more
instances: `ls *.yaml` when no netplan file exists still fails under
pipefail even with stderr silenced (masked in practice by cloud-init
usually leaving a file behind); the restore-and-reapply `netplan
apply` after an initial failure was a bare unguarded statement; and
`systemctl enable`/`start` after writing each of the four timer pairs
was unguarded too - caught only by testing in an environment without
a live systemd, but a real enable/start failure on actual hardware
(bad unit, daemon-reload skipped, ...) would hit the exact same crash.
Added a shared enable_and_start_timers() helper used at all four call
sites; all now report a clear warning and return to the menu instead
of taking the session down.
Testing discipline for this round, given the risk:
- No automated test calls the real netplan/nmcli/iw/wpa_cli/systemctl -
confirmed no WiFi tools or `wl*` interface exist in this sandbox, so
wifi_menu's own tools-check safely short-circuits before touching
anything; apply_wifi_config's actual YAML/backup/failure-recovery
logic is tested with sudo/netplan/get_ip_address stubbed instead.
- One stubbing pitfall caught and fixed in the test itself: `nohup sudo
bash "$watchdog" ... &` execs nohup as a real external binary, which
then execs the real sudo - a bash function stub named `sudo` does NOT
intercept that, only stubbing `nohup` itself does. Verified via pgrep
that no real watchdog process or `sleep 60` was ever spawned.
- power_schedule.sh tested with SYSTEMD_DIR/CRON_D_DIR/BIN_DIR pointed
at scratch dirs and only `sudo systemctl` stubbed (tee/rm/chmod/cp
left real, since they only ever touch scratch paths): full lifecycle
for all four schedule types plus remove-all, the RTC-available branch
(including the wake-time-before-shutdown-time hour/day wraparound
arithmetic) via a stubbed rtc_wake_available, and the new
enable_and_start_timers failure path via a stub that fails `enable`
specifically.
- End-to-end: ran the real install.sh as a genuine non-root, non-
"kiosk" user for both menus. WiFi correctly short-circuits on missing
tools without crashing. Power/Display/Quiet Hours (SYSTEMD_DIR/
CRON_D_DIR/BIN_DIR redirected to scratch space) configured all four
schedule types in sequence including the nested Electron Reload menu,
survived four consecutive real "systemctl enable/start failed"
warnings (this container has no live systemd) without the session
dying, then removed everything - confirmed the scratch dirs ended up
empty and config.json was never touched (correctly out of scope for
this menu).