Files
ubuntu-based-kiosk/.github/workflows/build-iso.yml
T
Claude fcbb890dbf Offline addons, LAN-aware emergency hotspot, LAN time server
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
2026-10-03 16:08:46 +00:00

134 lines
5.5 KiB
YAML

# Builds the all-in-one Ubuntu Server 26.04 + kiosk install ISO
# (iso/build-iso.sh) on demand: Actions tab -> "Build kiosk ISO" ->
# Run workflow. The ISO (~2.8 GB) is attached to the run as a downloadable
# artifact for a few days - see the run's Summary page.
#
# Pre-setting the admin account (needed for "fully-automatic") uses the
# repository secret KIOSK_ADMIN_PASSWORD (Settings -> Secrets and
# variables -> Actions) - never a workflow input, since inputs are shown
# in the run log. Only a SHA-512 hash of it goes on the ISO, but anyone
# who downloads the ISO can read that hash, so treat the artifact as
# private and use a password you'll change after install.
name: Build kiosk ISO
on:
workflow_dispatch:
inputs:
mode:
description: >-
interactive: installer asks network/disk/user, kiosk install.sh
runs with prompts on first boot. unattended-kiosk: same installer,
kiosk installs with defaults and reboots into the kiosk.
fully-automatic: no questions at all - WIPES THE LARGEST DISK
(needs username + KIOSK_ADMIN_PASSWORD secret).
type: choice
default: interactive
options:
- interactive
- unattended-kiosk
- fully-automatic
username:
description: Admin username to pre-set (blank = asked during install). Needs the KIOSK_ADMIN_PASSWORD secret. Not "kiosk"/"admin".
type: string
default: ""
hostname:
description: Hostname (only used when username is set)
type: string
default: kiosk
ssh_public_key:
description: Optional SSH public key for the admin user (ssh-ed25519 AAAA...)
type: string
default: ""
offline:
description: >-
Bundle everything install.sh downloads (~520 MB: all packages incl.
the CUPS, LMS/Squeezelite, Intercom, VNC, WireGuard and hotspot
addons, Node.js, Electron) so the kiosk and those addons install
with no internet.
type: boolean
default: true
retention_days:
description: Days to keep the ISO artifact
type: number
default: 3
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Install build tools
run: |
sudo apt-get update -q
sudo apt-get install -y -q xorriso apt-utils ubuntu-keyring
- name: Build ISO
env:
MODE: ${{ inputs.mode }}
OFFLINE: ${{ inputs.offline }}
ADMIN_USERNAME: ${{ inputs.username }}
ADMIN_HOSTNAME: ${{ inputs.hostname }}
SSH_PUBLIC_KEY: ${{ inputs.ssh_public_key }}
KIOSK_ADMIN_PASSWORD: ${{ secrets.KIOSK_ADMIN_PASSWORD }}
run: |
mkdir -p "$RUNNER_TEMP/iso"
args=(--output "$RUNNER_TEMP/iso/ubuntu-kiosk-amd64.iso")
case "$MODE" in
unattended-kiosk) args+=(--unattended) ;;
fully-automatic) args+=(--fully-automatic) ;;
esac
if [[ -n "$ADMIN_USERNAME" ]]; then
if [[ -z "$KIOSK_ADMIN_PASSWORD" ]]; then
echo "::error::username given but the KIOSK_ADMIN_PASSWORD repository secret isn't set"
exit 1
fi
args+=(--username "$ADMIN_USERNAME" --hostname "$ADMIN_HOSTNAME")
fi
if [[ -n "$SSH_PUBLIC_KEY" ]]; then
printf '%s\n' "$SSH_PUBLIC_KEY" > "$RUNNER_TEMP/ssh_key.pub"
args+=(--ssh-key "$RUNNER_TEMP/ssh_key.pub")
fi
[[ "$OFFLINE" == "true" ]] && args+=(--offline)
iso/build-iso.sh "${args[@]}"
# Name the file after the Ubuntu point release the script picked.
src="$(find iso/build/cache -name "*.iso" -print -quit)"
kind="kiosk"; [[ "$OFFLINE" == "true" ]] && kind="kiosk-offline"
name="$(basename "$src" .iso | sed "s/-live-server/-$kind/")-${MODE}-${GITHUB_SHA::7}.iso"
mv "$RUNNER_TEMP/iso/ubuntu-kiosk-amd64.iso" "$RUNNER_TEMP/iso/$name"
# Free the ~3 GB source ISO before the upload step needs scratch space.
rm -rf iso/build/cache iso/build/offline-cache
(cd "$RUNNER_TEMP/iso" && sha256sum "$name" > "$name.sha256")
echo "ISO_NAME=$name" >> "$GITHUB_ENV"
{
echo "### $name"
echo
echo "- Mode: \`$MODE\`"
echo "- Offline bundle: $([[ "$OFFLINE" == "true" ]] && echo "yes - installs with no internet" || echo "no - kiosk needs internet on first boot")"
echo "- Admin user: ${ADMIN_USERNAME:-chosen during install}"
echo "- Built from: \`${GITHUB_REF_NAME}\` @ \`${GITHUB_SHA::7}\`"
echo "- SHA-256: \`$(cut -d' ' -f1 "$RUNNER_TEMP/iso/$name.sha256")\`"
echo
echo "Download it from **Artifacts** below (GitHub zips it; unzip, then write the .iso to a USB stick)."
if [[ "$MODE" == "fully-automatic" ]]; then
echo
echo "> **Warning:** this ISO wipes the target machine's largest disk without asking."
fi
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload ISO
uses: actions/upload-artifact@v4
with:
name: ${{ env.ISO_NAME }}
path: |
${{ runner.temp }}/iso/${{ env.ISO_NAME }}
${{ runner.temp }}/iso/${{ env.ISO_NAME }}.sha256
# An ISO is already compressed - zipping it again only costs time.
compression-level: 0
retention-days: ${{ inputs.retention_days }}
if-no-files-found: error