diff --git a/iso/firstboot/kiosk-firstboot b/iso/firstboot/kiosk-firstboot index 66fa5f7..dca388d 100755 --- a/iso/firstboot/kiosk-firstboot +++ b/iso/firstboot/kiosk-firstboot @@ -22,6 +22,10 @@ # KIOSK_OFFLINE_WAIT=N on an ISO built with --offline: seconds to wait # for internet before installing from the bundled # packages instead (default 90). +# KIOSK_STALL_TIMEOUT=N on an --offline ISO, unattended: if the install +# prints nothing for N seconds (a stalled download on +# a flaky link), stop it and finish from the bundled +# packages instead (default 600). ################################################################################ set -uo pipefail @@ -40,6 +44,7 @@ OFFLINE_FLAG="$STATE_DIR/offline-mode" KIOSK_UNATTENDED=0 KIOSK_OFFLINE_WAIT=90 +KIOSK_STALL_TIMEOUT=600 # shellcheck source=/dev/null [[ -f "$CONF" ]] && source "$CONF" @@ -61,6 +66,21 @@ if [[ "${1:-}" == "--cleanup" ]]; then exit 0 fi +have_bundle() { [[ -f "$OFFLINE_BUNDLE/apt/Packages" ]]; } + +# Finish this install from the bundled packages instead of the internet +# (cleanup() turns it back off). install.sh notices via lib/config.sh's +# offline_mode_active. +switch_to_offline() { + touch "$OFFLINE_FLAG" + if ! "$OFFLINE_APT" on; then + echo "ERROR: couldn't switch apt to the offline bundle." + rm -f "$OFFLINE_FLAG" + return 1 + fi + return 0 +} + # 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). @@ -83,13 +103,15 @@ banner() { # drop to a shell as the admin user to fix it, or give up until next boot. # Returns 0 to retry, 1 to give up. offer_retry() { - local choice + local choice offline_opt="" + have_bundle && [[ ! -f "$OFFLINE_FLAG" ]] && offline_opt=" [O] Install from the offline bundle on this disk" while true; do echo - echo " [R] Retry [S] Shell (as $ADMIN_USER, exit to come back) [Q] Quit to login prompt" + echo " [R] Retry [S] Shell (as $ADMIN_USER, exit to come back) [Q] Quit to login prompt$offline_opt" read -r -p "Choice [R]: " choice case "${choice,,}" in ""|r) return 0 ;; + o) [[ -n "$offline_opt" ]] && switch_to_offline && return 0 ;; s) runuser -l "$ADMIN_USER" ;; q) echo "Setup will run again on next boot, or run it now with:" @@ -227,6 +249,39 @@ run_install() { # runuser rather than sudo -u: sudo would set SUDO_USER=root, which # config.sh reads as "the admin running this tool". + # A stalled download on a flaky connection would otherwise hang an + # unattended install forever. With the offline bundle as a safety net, + # watch the log: no new output for KIOSK_STALL_TIMEOUT seconds means + # stuck, so the whole process group is stopped (the caller then + # finishes from the bundle). + if [[ "$KIOSK_UNATTENDED" == "1" ]] && have_bundle && [[ ! -f "$OFFLINE_FLAG" ]]; then + touch "$LOG" + setsid bash -c ' + runuser -u "$1" -- env -i "${@:4}" bash -c "cd \"\$1\" && exec ./install.sh" _ "$2" 2>&1 | tee -a "$3" + exit "${PIPESTATUS[0]}" + ' _ "$ADMIN_USER" "$REPO_DEST" "$LOG" "${envs[@]}" & + local pid=$! + ( + while kill -0 "$pid" 2>/dev/null; do + sleep 20 + age=$(( $(date +%s) - $(stat -c %Y "$LOG") )) + if (( age > KIOSK_STALL_TIMEOUT )); then + echo + echo "No output for ${age}s - the online install looks stuck; stopping it." + kill -TERM -- "-$pid" 2>/dev/null + sleep 30 + kill -KILL -- "-$pid" 2>/dev/null + break + fi + done + ) & + local watcher=$! + wait "$pid" + local rc=$? + kill "$watcher" 2>/dev/null + wait "$watcher" 2>/dev/null + return "$rc" + fi runuser -u "$ADMIN_USER" -- env -i "${envs[@]}" \ bash -c 'cd "$1" && exec ./install.sh' _ "$REPO_DEST" \ 2>&1 | tee -a "$LOG" @@ -249,6 +304,15 @@ while true; do echo echo "install.sh did not finish (exit code $rc). Full output: $LOG" if [[ "$KIOSK_UNATTENDED" == "1" ]]; then + # Online attempt failed or stalled: the bundle on this disk is + # always going to work, so use it rather than retry the same thing. + if have_bundle && [[ ! -f "$OFFLINE_FLAG" ]]; then + echo "Online install didn't finish - continuing from the offline bundle on this disk." + if switch_to_offline; then + attempt=$((attempt + 1)) + continue + fi + fi if (( attempt >= 3 )); then echo "Giving up after $attempt attempts; will try again next boot." release_tty diff --git a/lib/provision.sh b/lib/provision.sh index 307a613..cf33cb8 100644 --- a/lib/provision.sh +++ b/lib/provision.sh @@ -127,7 +127,7 @@ provision_install_nodejs() { # The bundle carries NodeSource's own nodejs package. sudo apt install -y nodejs else - curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - + curl -fsSL --connect-timeout 20 --max-time 300 https://deb.nodesource.com/setup_22.x | sudo -E bash - sudo apt install -y nodejs fi fi