Commit Graph
6 Commits
Author SHA1 Message Date
Claude 1ba9e0a8aa Install WiFi tools (wpasupplicant, iw) in the base kiosk install
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
2026-10-09 15:54:59 +00:00
Claude fb4ee6fa62 Slideshow folders can include PDFs
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
2026-10-03 15:43:05 +00:00
Claude a170dc6228 ISO: add --offline bundle so the kiosk installs with no internet
`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
2026-10-03 04:34:32 +00:00
Claude aee6b779eb Add all-in-one Ubuntu Server 26.04 + kiosk install ISO builder
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
2026-10-03 01:18:54 +00:00
Claude e607e8cea0 Web UI: install/reconfigure addons, default-on install, visual redesign (v2.17.0)
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
2026-08-19 17:51:07 +00:00
Claude 037a62e008 Add real first-time provisioning to install.sh (v2.14.0)
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
2026-08-19 05:24:32 +00:00