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
This commit is contained in:
Claude
2026-10-03 04:34:32 +00:00
parent 125622cd74
commit a170dc6228
11 changed files with 586 additions and 38 deletions
+28 -2
View File
@@ -26,6 +26,8 @@
#
# Requirements (on the build machine, any recent Ubuntu/Debian):
# sudo apt install xorriso curl git openssl
# plus, for --offline (amd64 build machine only):
# sudo apt install apt-utils python3 gnupg ubuntu-keyring
#
# Usage:
# iso/build-iso.sh [options]
@@ -65,6 +67,15 @@
# --repo-url URL git remote recorded in the copied repo, used by
# Advanced -> Upgrade (default: this checkout's
# origin, else the upstream GitHub repo).
# --offline Also bundle everything install.sh downloads (~420 MB:
# every apt package incl. the CUPS printing addon,
# Node.js, prebuilt Electron/Web UI node_modules), so
# first boot can install the kiosk with NO internet.
# With internet it still installs online as usual;
# offline it waits 90s, then uses the bundle. The
# bundle stays on the kiosk at /opt/kiosk-offline
# (see iso/firstboot/kiosk-offline-apt for using it
# later, e.g. adding CUPS while offline).
#
# The repo copied onto the ISO is a clone of this checkout's current HEAD
# (committed changes only - uncommitted edits are NOT included).
@@ -91,6 +102,7 @@ AUTO_STORAGE=0
AUTO_NETWORK=0
NO_CONFIRM=0
UNATTENDED=0
OFFLINE=0
REPO_URL=""
# Usernames the Ubuntu installer rejects (system users/groups) - copied
@@ -136,6 +148,7 @@ while [[ $# -gt 0 ]]; do
--unattended) UNATTENDED=1; shift ;;
--fully-automatic) AUTO_STORAGE=1; AUTO_NETWORK=1; NO_CONFIRM=1; UNATTENDED=1; shift ;;
--repo-url) REPO_URL="$2"; shift 2 ;;
--offline) OFFLINE=1; shift ;;
-h|--help) usage 0 ;;
*) echo "Unknown option: $1" >&2; usage 1 ;;
esac
@@ -212,8 +225,9 @@ fi
if [[ -z "$OUTPUT" ]]; then
src_base="$(basename "$SRC_ISO" .iso)"
OUTPUT="$BUILD_DIR/${src_base/-live-server/-kiosk}.iso"
[[ "$OUTPUT" != "$BUILD_DIR/$src_base.iso" ]] || OUTPUT="$BUILD_DIR/${src_base}-kiosk.iso"
kind="kiosk"; (( OFFLINE )) && kind="kiosk-offline"
OUTPUT="$BUILD_DIR/${src_base/-live-server/-$kind}.iso"
[[ "$OUTPUT" != "$BUILD_DIR/$src_base.iso" ]] || OUTPUT="$BUILD_DIR/${src_base}-$kind.iso"
fi
################################################################################
@@ -244,12 +258,18 @@ git -C "$stage_repo" config "branch.$BRANCH.merge" "refs/heads/$BRANCH"
install -m 755 "$SCRIPT_DIR/firstboot/kiosk-firstboot" "$WORK/kiosk/firstboot/"
install -m 644 "$SCRIPT_DIR/firstboot/kiosk-firstboot.service" "$WORK/kiosk/firstboot/"
install -m 755 "$SCRIPT_DIR/firstboot/kiosk-offline-apt" "$WORK/kiosk/firstboot/"
cat > "$WORK/kiosk/firstboot/kiosk-firstboot.conf" <<EOF
# Read by /usr/local/sbin/kiosk-firstboot. Written by iso/build-iso.sh.
# 1 = take every install.sh default and reboot into the kiosk; 0 = interactive.
KIOSK_UNATTENDED=$UNATTENDED
EOF
if (( OFFLINE )); then
info "Building offline bundle (packages, Node.js, Electron)..."
"$SCRIPT_DIR/offline/build-bundle.sh" "$WORK/kiosk/offline" "$SRC_ISO" "$stage_repo" "$BUILD_DIR/offline-cache"
fi
################################################################################
# autoinstall.yaml
################################################################################
@@ -305,9 +325,14 @@ interactive=()
- install -m 755 /cdrom/kiosk/firstboot/kiosk-firstboot /target/usr/local/sbin/kiosk-firstboot
- install -m 644 /cdrom/kiosk/firstboot/kiosk-firstboot.service /target/etc/systemd/system/kiosk-firstboot.service
- install -m 644 /cdrom/kiosk/firstboot/kiosk-firstboot.conf /target/etc/kiosk-firstboot.conf
- install -m 755 /cdrom/kiosk/firstboot/kiosk-offline-apt /target/usr/local/sbin/kiosk-offline-apt
- touch /target/var/lib/kiosk-firstboot/pending
- curtin in-target --target=/target -- systemctl enable kiosk-firstboot.service
EOF
if (( OFFLINE )); then
echo " - cp -a /cdrom/kiosk/offline /target/opt/kiosk-offline"
echo " - chmod -R u+w,a+rX /target/opt/kiosk-offline"
fi
} > "$WORK/autoinstall.yaml"
################################################################################
@@ -345,6 +370,7 @@ echo
echo "Installer asks for : ${interactive[*]:-nothing}$( (( NO_CONFIRM )) || echo " (plus a 'Continue with autoinstall?' confirmation)")"
echo "Admin user : ${ADMIN_USER:-chosen during install}"
echo "First boot : $( (( UNATTENDED )) && echo "unattended install.sh, then reboots into the kiosk" || echo "interactive install.sh on the console")"
echo "Offline bundle : $( (( OFFLINE )) && echo "yes - installs with no internet (used after 90s offline)" || echo "no - first boot needs internet (add --offline to bundle everything)")"
echo
echo "Write it to a USB stick (replace /dev/sdX - this erases it):"
echo " sudo dd if='$OUTPUT' of=/dev/sdX bs=4M status=progress oflag=sync"