Offline CUPS from the normal menu, local-file sites, build-dep install

- CUPS (Addons -> CUPS Printing, from ./install.sh or the Web UI) now
  installs from the offline bundle by itself when the kiosk can't reach
  the Ubuntu archive: lib/config.sh's run_with_offline_fallback switches
  apt to the bundle (kiosk-offline-apt on) around the install and back
  afterwards. No-op on manual installs or when online.
- Fix cups_is_installed reporting "not installed" for an installed CUPS:
  `dpkg -l | grep -q` under install.sh's pipefail fails when grep exits
  early and dpkg takes SIGPIPE. Uses dpkg-query on the one package now.
- Sites: an absolute path (/home/kiosk/docs/menu.pdf) is accepted and
  turned into a percent-encoded file:// URL, identically in the Sites
  menu (sites_parse_url) and the Web UI (parseUrl, with tests). Electron
  42 shows local HTML, PDFs (built-in viewer) and images as-is.
- iso/build-iso.sh checks its build tools (plus the --offline ones) and
  offers to `sudo apt install` what's missing; --install-deps does it
  without asking. --offline now refuses non-amd64 hosts up front.

Tested: offline chroot of the 26.04.1 server filesystem - action_install_cups
installed CUPS + printer-driver-all/hplip/gutenprint from the bundle with
no network and left apt back on normal sources; webui test suite passes;
Electron 42 rendered a file:// PDF and PNG with spaces/# in the path;
build-iso.sh stopped with the install command when apt-utils was missing
and no TTY, and installed it and built the ISO with --install-deps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
This commit is contained in:
Claude
2026-10-03 13:29:04 +00:00
parent 0b5d958fcc
commit 5792e201b6
9 changed files with 119 additions and 31 deletions
+4 -2
View File
@@ -35,9 +35,11 @@ const NAV_MODES = ['restricted', 'same-origin', 'open'];
const TIME_RE = /^([01]\d|2[0-3]):[0-5]\d$/;
// Same normalization rule as menus/sites.sh's sites_parse_url(): bare
// host -> https://, bare IPv4 -> http://, else passed through as-is.
// host -> https://, bare IPv4 -> http://, absolute path -> file:// (local
// pages/PDFs/images on the kiosk itself), else passed through as-is.
function parseUrl(raw) {
if (/^https?:\/\//.test(raw)) return raw;
if (/^(https?|file):\/\//.test(raw)) return raw;
if (raw.startsWith('/')) return `file://${raw.split('/').map(encodeURIComponent).join('/')}`;
if (/^\d+\.\d+\.\d+\.\d+/.test(raw)) return `http://${raw}`;
return `https://${raw}`;
}