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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cjz8a58eB5P9sUweqd3NHm
This commit is contained in:
Claude
2026-10-09 15:39:24 +00:00
parent b660ba89db
commit b479b68219
3 changed files with 23 additions and 13 deletions
+7 -8
View File
@@ -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"