Reported on a 26.04 offline install, first-boot step 2:
useradd: group 'netdev' does not exist
useradd refuses to create the user at all when any -G group is missing,
which stopped provisioning. Ubuntu 26.04 Server's base system has audio,
video, input and plugdev but no netdev (checked against the 26.04.1
ISO's /etc/group).
provision_create_kiosk_user now `groupadd -f -r`s each supplementary
group first (a no-op when it exists), then passes the same list to
useradd.
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
PDFs in a slideshow folder now play alongside images and Word documents:
a one-page PDF is shown whole, a longer one fits the width and scrolls
through its pages during docInterval (first 30 pages - rendering a huge
PDF for a few seconds on screen isn't worth the time and memory).
- kiosk-app/pdf-render.mjs renders pages to canvases with pdf.js
(pdfjs-dist ^6.3.289, new kiosk-app dependency): no viewer toolbar, and
the slideshow's existing auto-scroll works on it. pdf.js >= 4.2.67 is
required (CVE-2024-4367: older versions could run script embedded in a
crafted PDF); isEvalSupported:false too. It's a module because pdf.js
only ships as one - confirmed to load (with its worker) from file:// in
Electron 44.
- Hands pdf.js a copy of the bytes: it transfers the buffer it's given,
which would break the cached copy the next time round.
- slideshow.js lists .pdf as a document type; the title-request handler
serves any listed non-image item (still only from that folder).
- provision_install_app now copies kiosk-app/*.mjs as well - without it
pdf-render.mjs would never reach an installed kiosk.
Tested with the real main.js on Electron 44.5.1 under Xvfb + Openbox,
app files copied with provision_install_app's own glob: a photo, a
one-page PDF, a three-page PDF and a .docx played in order, twice round;
the one-page PDF shown whole, the three-page one scrolled through all
pages. Offline bundle rebuilt with pdfjs-dist (437 MB total, +22 MB).
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
iso/build-iso.sh remasters the official 26.04.x live-server ISO (downloaded
and checksum-verified, or --iso PATH) with xorriso, replaying its BIOS/UEFI
boot records unchanged. It adds:
- /autoinstall.yaml: Subiquity autoinstall that answers locale/keyboard/SSH
and by default leaves only network, storage and identity interactive
(--username/--auto-storage/--auto-network/--no-confirm/--fully-automatic
make those automatic too). late-commands copy the repo (a clone of the
current HEAD) to /opt/ubuntu-based-kiosk and enable a first-boot service.
- iso/firstboot/kiosk-firstboot(.service): on first boot, takes tty1, waits
for cloud-init and internet, copies the repo to the admin user's home and
runs ./install.sh as that user with temporary passwordless sudo (removed
on every exit path). Failed/interrupted installs re-run next boot.
install.sh hooks for this:
- KIOSK_UNATTENDED=1 makes ask_yes_no take each prompt's default and skips
the Core Settings menu (--unattended).
- KIOSK_PROVISION_DONE_MARKER is touched by provision_finish before the
reboot prompt, so the caller can tell a finished install from an
interrupted one.
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
Until now ./install.sh only managed an already-installed kiosk;
ubuntu-based-kiosk.sh was still the only path from a bare Ubuntu Server
box to a running one. install.sh now provisions from scratch too:
packages, kiosk user, Node.js/Electron, LightDM+Openbox autologin,
audio/video/HDMI/power-button hardware setup, and the firewall, then
hands off to the already-migrated Core Settings/Advanced menus for
initial configuration instead of reimplementing that logic again.
- lib/provision.sh: the new provisioning flow, built mostly by calling
existing menus (core_settings_menu, emergency hotspot, virtual
consoles) - cuts it to ~300 lines against the legacy script's
~4,000-line first_time_install().
- lib/electron.sh: electron_install_binary() extracted out of
menus/advanced_electron.sh so provisioning and the existing "Fix
blank screen" action share one implementation.
- kiosk-app/ and provision/files/: the Electron app source and every
system template file, extracted byte-for-byte out of
ubuntu-based-kiosk.sh's heredocs into real files.
- Found and fixed a bash set -e gotcha along the way: testing a
multi-statement function as an if-condition (`if ! some_func; then`)
silently exempts everything inside that function from set -e for the
duration of the call. Fixed in the new provisioning code and in
menus/advanced_electron.sh's pre-existing repair action, which had
the same shape.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VfsFSoRqfbRG7XAg5RoE7e