From 3164e864755deae240c97cefc4db449e7bb24aae Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 15:39:11 +0000 Subject: [PATCH] Fix first-boot service never starting on Ubuntu 26.04 Reported: installed from the ISO, files present, but the kiosk setup never appeared on first boot. Cause: kiosk-firstboot.service had After=cloud-final.service, and on 26.04 cloud-final.service is After=multi-user.target - while multi-user.target, which wants kiosk-firstboot, is implicitly After= it. An ordering cycle; systemd breaks it at boot by deleting a job, and kiosk-firstboot's was the one to go. systemd-analyze verify against the 26.04.1 ISO's own units reproduces it: Found ordering cycle: kiosk-firstboot.service/start after cloud-final.service/start after multi-user.target/start - after kiosk-firstboot.service - Drop After=cloud-final.service. The script's `cloud-init status --wait` goes too: cloud-final waits for multi-user.target, which waits for this service - it would deadlock. DPkg::Lock::Timeout already covers what it was guarding against (dpkg lock contention). - Drop Conflicts=getty@tty1.service: getty.target pulls getty@tty1 into the same boot transaction, and a conflict between two wanted jobs is also resolved by dropping one. Before=getty@tty1 alone keeps tty1's login prompt waiting until this oneshot finishes. Verified: systemd-analyze verify of kiosk-firstboot + multi-user.target + cloud-final + cloud-init.target + getty@tty1 + graphical.target in the 26.04.1 server filesystem - old unit reports the cycle, fixed unit is clean. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm --- Readme.md | 1 + iso/firstboot/kiosk-firstboot | 15 +++++++-------- iso/firstboot/kiosk-firstboot.service | 20 +++++++++++++++----- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Readme.md b/Readme.md index cacf298..e19ed4b 100644 --- a/Readme.md +++ b/Readme.md @@ -1489,6 +1489,7 @@ full migration pass. - **All-in-one install ISO** (`iso/build-iso.sh`, or Actions → Build kiosk ISO): Ubuntu Server 26.04 plus the kiosk in one USB stick — the Ubuntu install, then `install.sh` automatically on first boot. Interactive, unattended, or fully automatic. See "All-in-One Install ISO" above. - **Offline installs** (`--offline`): the ISO carries every package (including CUPS printing and all printer drivers), Node.js 22, and prebuilt Electron/Web UI modules, so a kiosk installs with no internet. With internet it still installs current versions online. Addons → CUPS Printing (menu or Web UI) installs from the bundle by itself when offline. - **Local files as sites:** an absolute path (`/home/kiosk/docs/menu.pdf`) shows a local page, PDF or image; a folder (`/home/kiosk/photos/`) plays as a full-screen **image slideshow** that picks up added/removed images within a minute. Works in both the Sites menu and the Web UI. +- **Fix: the first-boot kiosk setup never started on 26.04.** Its service was ordered after cloud-init's final stage, which itself runs after `multi-user.target` - an ordering cycle that systemd broke by dropping the kiosk setup. It also "conflicted" with the tty1 login prompt, which systemd can resolve the same way. Both removed; checked with `systemd-analyze verify` against 26.04's own units. Rebuild the ISO to pick this up. - **Offline addons:** the `--offline` bundle now also carries LMS (Lyrion) + Squeezelite, Asterisk Intercom, VNC, WireGuard and the Emergency Hotspot; their normal installs use it automatically when there's no internet. - **Emergency Hotspot fix:** by default it now starts only when the local network is unreachable (router *and* internet), not merely when there's no internet - on a LAN without internet it used to start on every boot. Existing setups keep the old check until Advanced → Emergency Hotspot → Reconfigure. - **New: Core Settings → Time Server (NTP)** - use a time server on your network, go back to Ubuntu's, or set the clock by hand; for networks without internet. diff --git a/iso/firstboot/kiosk-firstboot b/iso/firstboot/kiosk-firstboot index 81d2e11..66fa5f7 100755 --- a/iso/firstboot/kiosk-firstboot +++ b/iso/firstboot/kiosk-firstboot @@ -61,8 +61,9 @@ if [[ "${1:-}" == "--cleanup" ]]; then exit 0 fi -# Hand tty1 back to a normal login prompt (the unit Conflicts= with -# getty@tty1 so it can own the console; systemd won't restart it alone). +# Make sure tty1 gets its login prompt back. getty@tty1 is ordered after +# this unit and normally starts by itself once we exit; this just covers +# a getty that was stopped or never queued (e.g. started by hand later). release_tty() { systemctl --no-block start getty@tty1.service 2>/dev/null || true } @@ -167,12 +168,10 @@ echo "Mode : $([[ "$KIOSK_UNATTENDED" == "1" ]] && echo "unattended (defau echo "Log : $LOG" echo -# cloud-init and the first apt/unattended-upgrades run on a fresh system -# both want the dpkg lock; let them finish rather than racing them. -if command -v cloud-init &>/dev/null; then - echo "Waiting for cloud-init to finish..." - cloud-init status --wait >/dev/null 2>&1 || true -fi +# Anything else on a fresh system that wants the dpkg lock (e.g. the +# first unattended-upgrades run): wait for it rather than failing. Not +# `cloud-init status --wait` - cloud-init's final stage runs after +# multi-user.target, which waits for this service: a deadlock. mkdir -p "$STATE_DIR" printf 'DPkg::Lock::Timeout "900";\n' > "$APT_DROPIN" diff --git a/iso/firstboot/kiosk-firstboot.service b/iso/firstboot/kiosk-firstboot.service index fc2cdd5..f9c1226 100644 --- a/iso/firstboot/kiosk-firstboot.service +++ b/iso/firstboot/kiosk-firstboot.service @@ -1,16 +1,26 @@ # Installed by the custom ISO (iso/build-iso.sh) and enabled from its # autoinstall late-commands. Runs install.sh once, on tty1, the first -# time the freshly installed system boots - takes tty1 over from getty -# so an interactive install has a keyboard and screen, then hands tty1 -# back. /var/lib/kiosk-firstboot/pending is removed once the install +# time the freshly installed system boots - ordered before getty@tty1, +# so the login prompt only appears on tty1 once this has finished and +# an interactive install has the keyboard and screen to itself. /var/lib/kiosk-firstboot/pending is removed once the install # finishes, so this is a no-op on every boot after that; an interrupted # or failed install leaves it in place and simply runs again next boot. +# +# Ordering, learned the hard way on 26.04: +# - NOT After=cloud-final.service: cloud-final is After=multi-user.target, +# and multi-user.target (which wants this unit) is After= it - an +# ordering cycle, which systemd "fixes" at boot by dropping a job: this +# one, so the install silently never ran. (The script doesn't wait for +# cloud-init either, for the same reason - it would deadlock.) +# - NOT Conflicts=getty@tty1: getty.target pulls getty@tty1 into the same +# boot transaction, and systemd resolves a conflict between two wanted +# jobs by dropping one. Before= alone is enough - getty@tty1 waits for +# this oneshot to finish. [Unit] Description=Ubuntu Based Kiosk first-boot install -After=network-online.target systemd-user-sessions.service cloud-final.service plymouth-quit-wait.service +After=network-online.target systemd-user-sessions.service plymouth-quit-wait.service Wants=network-online.target Before=getty@tty1.service -Conflicts=getty@tty1.service ConditionPathExists=/var/lib/kiosk-firstboot/pending [Service]