New installs put the Web UI on port 8888 instead of 8090 (existing installs
keep their port; change it under Addons -> Web UI, which now also closes the
old port in the firewall). The power menu gets a Lock now button, shown only
when password protection is on.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013wi3ZQveYdjfcrEsyazq32
Offline bundle now carries every addon that works on a network without
internet: LMS (Lyrion's own .deb, from the addon's LMS_DEB_URL) +
Squeezelite, Asterisk Intercom (baresip), VNC, WireGuard and the
Emergency Hotspot packages - each list in a variable next to the code
that installs it (SQUEEZELITE_APT_PACKAGES, ASTERISK_INTERCOM_APT_PACKAGES,
VNC_APT_PACKAGES, WIREGUARD_APT_PACKAGES, EMERGENCY_HOTSPOT_APT_PACKAGES),
read by build-bundle.sh. Each install goes through
run_with_offline_fallback like CUPS, so they install offline from the
normal menus / Web UI. LMS installs as a bundle package when offline
(lms_offline_install_wanted); its online repo/direct-download paths are
unchanged. Tailscale/Netbird deliberately not bundled (vendor scripts,
vendor servers). Bundle: 524 MB (+87 MB).
Emergency hotspot: the boot check pinged only 8.8.8.8, so on a LAN with
no internet the hotspot started on every boot. Now chosen at setup:
"network" (default - router or internet must answer; a router that
ignores ping still counts if it answered ARP), "internet" (old check),
or "host" (a specific address). Three tries 10s apart. Existing scripts
keep the old check until Reconfigure.
New Core Settings -> Time Server (NTP) (menus/time_sync.sh): point
systemd-timesyncd at LAN time server(s) via a timesyncd.conf.d drop-in,
back to Ubuntu's defaults, or set the clock by hand (and the RTC) for a
network with no time server. Complete Uninstall resets it.
Also: lms_service_name used `systemctl list-unit-files | grep -q`, the
same pipefail/SIGPIPE pattern as the earlier cups_is_installed bug; now
`systemctl cat`.
Tested in a network-less chroot of the 26.04.1 server filesystem: kiosk
base, then action_wireguard_install and action_install_lms (real
functions) and the Squeezelite/Intercom/VNC/hotspot package sets all
installed from the bundle, apt left on normal sources after each.
Hotspot script generated for all three modes (plus an invalid address,
which falls back to "network") and its check run against simulated
networks: LAN-only with router answering ping or only ARP -> no hotspot
in "network" mode; nothing reachable -> hotspot. Time Server actions
driven with timedatectl stubbed: drop-in contents, input validation,
manual set-time sequence, reset removes the drop-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
`iso/build-iso.sh --offline` (and the workflow's new `offline` box, on by
default) adds ~420 MB to the ISO and copies it to /opt/kiosk-offline:
- apt/: local repository with every package install.sh's provisioning
and the CUPS addon install ask for, NodeSource's nodejs, and all
dependencies a fresh server install doesn't already have. Resolved
against the source ISO's own package manifest via a synthesized dpkg
status (iso/offline/fake-dpkg-status.py), so apt downloads only what's
missing and makes the same virtual/alternative choices as the target.
- npm/: prebuilt kiosk-app (Electron 42, binary included - it no longer
downloads at npm install time) and Web UI node_modules.
On first boot, kiosk-firstboot installs online as before when there's
internet; with none it waits KIOSK_OFFLINE_WAIT (90s), then runs
`kiosk-offline-apt on` (apt.conf.d drop-in: bundle is the ONLY source)
and turns it off again afterwards. install.sh notices offline mode via
offline_mode_active (lib/config.sh) and installs nodejs from the bundle
and unpacks prebuilt node_modules instead of npm install.
Package lists moved into PROVISION_APT_PACKAGES /
PROVISION_INTEL_APT_PACKAGES / CUPS_APT_PACKAGES so the bundler reads
the same lists install.sh installs from.
Tested: built against the 26.04.1 ISO, then in a network-less chroot of
the ISO's own server filesystem installed every package set, ran
provision_create_kiosk_user/install_nodejs/install_app and
webui_install_app_files in offline mode (Electron runs, no missing
libraries, chrome-sandbox setuid), and ran kiosk-firstboot through the
offline switch with a stub install.sh.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
Web UI now installs by default during first-time provisioning (fixed
port 8090, no prompt) instead of being opt-in, and can install/
reconfigure CUPS Printing, LMS Server, Squeezelite Player, and Asterisk
Intercom, and check for updates - the addons and Update action asked
for by name.
Privilege model: the web service itself still runs as $KIOSK_USER with
zero ambient sudo. A new narrow, allow-listed root helper
(menus/addon_webui.sh's webui_write_helper_script) is the only way it
ever gains privilege, reachable only via a single-path passwordless
sudo rule generated and validated with `visudo -c -f` before being
installed, and it re-checks its own fixed action allow-list before
dispatching anything. Each allow-listed action is the exact same
interactive action_* function the terminal menu already uses, driven
by piping the right answers on stdin - the same technique this
project's own bash tests already use, so no prompt/mutation refactor
of any addon file was needed. webui/lib/actions.js's stdin sequences
were cross-validated against the real bash functions (not just read),
which caught two real bugs (Squeezelite and Asterisk Intercom both
silently lost their "decline reconfigure" path).
Long-running installs stream live output via Server-Sent Events
(webui/lib/jobs.js), one action at a time.
Full visual redesign: a sidebar shell (Sites/Display/Lockout/Addons/
Update) replacing the single scrolling page, light+dark themes via
prefers-color-scheme, no external font/CDN dependency. Actually driving
the redesigned UI in a headless browser (not just reading the code)
caught a real bug: refreshing an addon's pill/button after a successful
install used to rebuild the whole card, racing (and usually losing to)
the success status/log that job had just written. Fixed to update
pill/buttons in place.
Uninstall-via-web is deliberately still not offered, for any addon.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VfsFSoRqfbRG7XAg5RoE7e
New Addons -> Web UI: a small Node/Express app (webui/) installed as a
systemd service running as $KIOSK_USER, giving a browser-based editor
for Sites & Page Timing, Display & Interaction, and Password Protection
& Lockout - the three Core Settings menus that are pure config.json
read/write with no privileged system mutation involved. Runs with no
sudo at all, since config.json is already owned by $KIOSK_USER.
webui/lib/config.js re-implements lib/config.sh's exact schema and
merge-on-save contract in JS (kiosk-app/main.js already reads the same
config.json directly in JS, so this isn't a new pattern), so it can
never silently clobber fields it doesn't track - the same bug
previously fixed in lib/config.sh's own history.
No login of its own by design: Authelia runs elsewhere, and the
expectation is a reverse proxy (e.g. Caddy) with Authelia forward-auth
in front of it, the same way other self-hosted apps get protected -
Authelia integration is explicitly out of scope for this repo.
Deliberately narrow scope for this first pass: WiFi, Timezone,
Power/Display/Quiet Hours, Complete Uninstall, every other addon, and
everything in Advanced remain terminal-only, since a network-facing
process shouldn't be handed sudo-level system mutation without a lot
more thought than this pass gives it. Wired into Complete Uninstall
(webui_do_uninstall) and Clone Settings (addon-presence detection) the
same way every other addon is.
This is the single-kiosk piece of the web-based GUI this repo's
"Modular Management" notes have mentioned for a while - a central
multi-kiosk fleet dashboard is an intentional follow-up.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VfsFSoRqfbRG7XAg5RoE7e