fix(installer): pre-flight resume polish (4 gaps)
Four resume-flow papercuts in `installer/install.sh` that hurt the "interrupted install" path the most. 1. `--resume` with no state file is no longer silent. The most common operator confusion: reboot the live ISO, forget /tmp/ is tmpfs, re-run with --resume, watch the installer start over from scratch without saying anything. Now: loud error, tmpfs explanation, exit 1. 2. Validate the saved TARGET_DRIVE still exists on resume. Live ISO USB sticks get unplugged between sessions, dev hosts sometimes have non-deterministic /dev/sdX numbering. Without the guard the install proceeds and fails with cryptic disko / mount errors deep in execute_installation. Now we fail at load_state with the actual reason and a clean recovery path. 3. Resume now shows what's being resumed. `save_state` stamps an ISO-8601 timestamp; `load_state` prints "Resumed from <path> (saved Xm ago)" plus a "Target: /dev/X → user @ host" summary line. Lets the user Ctrl-C before any destructive prompt fires if they're resuming onto the wrong machine. 4. `--help` documents the tmpfs limitation. Saved state lives in /tmp/ which is tmpfs on the live ISO; --resume only works within the same boot. The man-page now says so instead of letting users discover it the hard way. `format_age` is the one new helper — pretty-prints "Xs/Xm/Xh Ym/Xd" relative to now, falls back to the raw timestamp if `date -d` can't parse the input. shellcheck --severity=error passes. Out of scope (potential future work): - Persistent state across reboots (would need a writable USB / external drive — chicken/egg with the installer setting up the only persistent storage in the first place). - `--show-state` flag to inspect a saved file without running. - State-file schema versioning. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -74,6 +74,9 @@ Usage: install.sh [--dry-run] [--resume] [-h|--help]
|
||||
Doesn't touch the disk, doesn't run nixos-install.
|
||||
--resume Reuse answers from a previous interrupted run
|
||||
(saved at $STATE_FILE — passwords excluded).
|
||||
NOTE: the live ISO uses tmpfs, so the state file is lost
|
||||
on reboot. --resume only works within the same live-ISO
|
||||
session as the original interrupted run.
|
||||
-h, --help Print this message.
|
||||
USAGE
|
||||
}
|
||||
@@ -102,19 +105,74 @@ parse_args() {
|
||||
# will silently install a system with an empty password hash and lock the
|
||||
# user out. Keep the guard checking the hash.
|
||||
save_state() {
|
||||
declare -p \
|
||||
TARGET_DRIVE USERNAME HOSTNAME TIMEZONE \
|
||||
KEYMAP_LAYOUT KEYMAP_VARIANT LOCALE FORM_FACTOR \
|
||||
ENABLE_IMPERMANENCE HARDWARE_MODULES NOMARCHY_HW_OPTS \
|
||||
SELECTED_PROFILES NOMARCHY_REV \
|
||||
> "$STATE_FILE"
|
||||
# The leading timestamp lets --resume surface "how old is this state?"
|
||||
# and is parsed back via NOMARCHY_INSTALL_STATE_SAVED_AT.
|
||||
{
|
||||
echo "NOMARCHY_INSTALL_STATE_SAVED_AT=\"$(date -Iseconds)\""
|
||||
declare -p \
|
||||
TARGET_DRIVE USERNAME HOSTNAME TIMEZONE \
|
||||
KEYMAP_LAYOUT KEYMAP_VARIANT LOCALE FORM_FACTOR \
|
||||
ENABLE_IMPERMANENCE HARDWARE_MODULES NOMARCHY_HW_OPTS \
|
||||
SELECTED_PROFILES NOMARCHY_REV
|
||||
} > "$STATE_FILE"
|
||||
}
|
||||
|
||||
# Pretty-print "X minutes/hours/days ago" from an ISO-8601 timestamp.
|
||||
# Falls back to the raw string if `date -d` can't parse it (defensive —
|
||||
# the timestamp is always produced by `date -Iseconds` above, but we
|
||||
# don't want a stale state file to crash --resume).
|
||||
format_age() {
|
||||
local saved="$1" saved_epoch now_epoch diff
|
||||
saved_epoch=$(date -d "$saved" +%s 2>/dev/null) || { echo "$saved"; return; }
|
||||
now_epoch=$(date +%s)
|
||||
diff=$(( now_epoch - saved_epoch ))
|
||||
if (( diff < 60 )); then echo "${diff}s ago"
|
||||
elif (( diff < 3600 )); then echo "$((diff / 60))m ago"
|
||||
elif (( diff < 86400 )); then echo "$((diff / 3600))h $((diff % 3600 / 60))m ago"
|
||||
else echo "$((diff / 86400))d ago"
|
||||
fi
|
||||
}
|
||||
|
||||
load_state() {
|
||||
if [[ "$RESUME" == "true" ]] && [[ -f "$STATE_FILE" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$STATE_FILE"
|
||||
info "Resumed from $STATE_FILE"
|
||||
if [[ "$RESUME" != "true" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
# --resume with no state file is almost always operator error — the
|
||||
# most common cause is "rebooted the live ISO and forgot tmpfs eats
|
||||
# /tmp/". Fail loudly so the user doesn't sit through a fresh prompt
|
||||
# cycle thinking it was resumed.
|
||||
if [[ ! -f "$STATE_FILE" ]]; then
|
||||
error "--resume was passed but no saved state exists at $STATE_FILE."
|
||||
info "The live ISO uses tmpfs — saved state doesn't survive a reboot."
|
||||
info "Re-run install.sh without --resume to start fresh."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
source "$STATE_FILE"
|
||||
|
||||
# If the saved target drive isn't visible right now, every later
|
||||
# disk-phase step will fail with cryptic errors. Catch it here.
|
||||
# Live ISOs frequently get their non-boot USB sticks unplugged
|
||||
# between sessions, and dev hosts sometimes have non-deterministic
|
||||
# /dev/sdX numbering.
|
||||
if [[ -n "${TARGET_DRIVE:-}" ]] && [[ ! -b "$TARGET_DRIVE" ]]; then
|
||||
error "Saved target drive $TARGET_DRIVE is no longer a block device."
|
||||
info "The drive may have been unplugged or renamed since the saved run."
|
||||
info "Delete $STATE_FILE and re-run without --resume."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Show what we're resuming into so the user can Ctrl-C if they're on
|
||||
# the wrong host before any password / disk-wipe prompts fire.
|
||||
local age="unknown age"
|
||||
if [[ -n "${NOMARCHY_INSTALL_STATE_SAVED_AT:-}" ]]; then
|
||||
age=$(format_age "$NOMARCHY_INSTALL_STATE_SAVED_AT")
|
||||
fi
|
||||
info "Resumed from $STATE_FILE (saved $age)"
|
||||
if [[ -n "${USERNAME:-}" || -n "${HOSTNAME:-}" || -n "${TARGET_DRIVE:-}" ]]; then
|
||||
info " Target: ${TARGET_DRIVE:-?} → ${USERNAME:-?} @ ${HOSTNAME:-?}"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user