diff --git a/overlay/usr/local/bin/veilor-installer b/overlay/usr/local/bin/veilor-installer index 999766a..ccae33c 100644 --- a/overlay/usr/local/bin/veilor-installer +++ b/overlay/usr/local/bin/veilor-installer @@ -69,6 +69,21 @@ banner() { local vline="veilor-os ${ver} · ${d} · live" if [[ -r $BANNER_FILE ]]; then + # v0.6: staged line-by-line reveal of the banner before the + # gum-style border draws around it. 40ms/line gives a subtle + # "typewriter" feel — 5 lines × 40ms = 200ms total, fast enough + # not to feel laggy but slow enough to land an aesthetic on the + # very first frame the user sees. Once the reveal finishes we + # clear and re-draw with the bordered gum-style version so the + # operator never sees both stacked on top of each other. + local line + while IFS= read -r line; do + printf ' %s\n' "$line" + sleep 0.04 + done < "$BANNER_FILE" + sleep 0.08 + clear + if [[ $TUI == gum ]]; then # gum style: rounded border, banner + blank line + version line. gum style --border rounded --margin "0 2" --padding "1 3" \ @@ -150,10 +165,30 @@ prompt_input() { } # prompt_password
+# +# v0.6: gum-path replaced with bash `read -srp` because `gum input +# --password` rendered as a duplicate-"Install" + stray-T artefact on +# the linux fbcon since v0.5.27 (Agent 7 of the v0.6 polish research +# wave traced this to gum's bubbletea screen-restore writing back the +# previous menu buffer when the framebuffer terminfo lacked +# `civis/cnorm` cursor-hide sequences). bash `read -srp` is a single +# write to stdout + termios echo-off — no redraw, no glitch. Header +# rendered separately via gum style for visual parity with the rest +# of the installer. prompt_password() { local header=$1 if [[ $TUI == gum ]]; then - gum input --password --header "$header" + # Render the prompt header as a styled box so it looks at home + # next to the other gum prompts, then collect the password via + # plain bash read on the next line. `read -s` disables echo, + # `read -p` writes the prompt to stderr (so command-substitution + # callers still get the password on stdout cleanly). + gum style --foreground "${VEILOR_FG:-15}" --border rounded \ + --border-foreground "${VEILOR_DIM:-240}" --padding "0 2" -- "$header" + local pw + read -srp " password: " pw + echo >&2 # newline after silent read so next prompt isn't on same line + printf '%s' "$pw" else whiptail --title "veilor-os" --passwordbox "$header" 10 60 \ 3>&1 1>&2 2>&3 @@ -253,12 +288,36 @@ collect_answers() { } # ── LUKS passphrase ── - luks_pw=$(prompt_password "[2/3] Encryption · LUKS2 passphrase (min 8)") || return 1 - validate_pw "$luks_pw" "passphrase" || return 1 + # v0.6: prompt twice + string-compare. A typo in the LUKS passphrase + # is unrecoverable — the disk is unmountable without it and we + # don't escrow the key. Re-prompting until the two reads match + # catches keyboard-layout surprises (US vs UK quote position is + # the most common one) before they brick the install. + local luks_pw_confirm + while true; do + luks_pw=$(prompt_password "[2/3] Encryption · LUKS2 passphrase (min 8)") || return 1 + validate_pw "$luks_pw" "passphrase" || continue + luks_pw_confirm=$(prompt_password "[2/3] Confirm LUKS2 passphrase") || return 1 + if [[ $luks_pw == "$luks_pw_confirm" ]]; then + break + fi + prompt_error "Passphrases do not match — try again." + done # ── Admin password ── - admin_pw=$(prompt_password "[3/3] Admin user · password for 'admin'") || return 1 - validate_pw "$admin_pw" "password" || return 1 + # Same confirm-twice pattern. Less catastrophic than LUKS (admin + # password can be reset from a recovery shell) but a mismatch here + # still locks the user out of their fresh install on first boot. + local admin_pw_confirm + while true; do + admin_pw=$(prompt_password "[3/3] Admin user · password for 'admin'") || return 1 + validate_pw "$admin_pw" "password" || continue + admin_pw_confirm=$(prompt_password "[3/3] Confirm admin password") || return 1 + if [[ $admin_pw == "$admin_pw_confirm" ]]; then + break + fi + prompt_error "Passwords do not match — try again." + done # ── Locale ── # Hardcoded en_US.UTF-8 for branded consistency. The picker that @@ -978,12 +1037,39 @@ run_install() { --show-output \ -- bash -c 'anaconda --cmdline --kickstart=/run/install/veilor-generated.ks 2>&1 | tee /tmp/anaconda-cmdline.log' || rc=$? if [[ $rc -eq 0 ]]; then - gum style --foreground 2 --border rounded --margin "1 2" --padding "1 3" \ - "✓ Install complete" \ - "" \ - "System will reboot in 5 seconds." \ - "Remove the install media." - sleep 5 + # v0.6: split the success screen into THREE stacked boxes. + # + # 1. Green success box — quiet confirmation. + # 2. Yellow eject box — promoted out of the buried + # one-liner the v0.5 success box used. Operators on + # both onyx and the friend's RTX 4080 rig missed the + # reminder and rebooted into the live ISO instead of + # the install. Now it's its own loud thick-bordered + # box that sits BELOW the success box and is + # impossible to miss. + # 3. Reboot countdown — embedded inside the green + # success box so the operator can see "complete + + # Xs to reboot" at a glance. + # + # Each tick clears + redraws all three, so the eject-media + # box stays in front of the operator for the full 10-second + # window and isn't scrolled off by a banner refresh. + local secs + for secs in 10 9 8 7 6 5 4 3 2 1; do + clear + gum style --foreground 2 --border rounded --margin "1 2" --padding "1 3" \ + "✓ Install complete" \ + "" \ + "Rebooting in ${secs}s..." + gum style --foreground 3 --border thick --margin "0 2" --padding "1 3" \ + --border-foreground 3 \ + " Remove the install media NOW " \ + "" \ + " Unplug the USB stick / eject the DVD before " \ + " reboot, otherwise the system will boot back " \ + " into the live ISO instead of your fresh install. " + sleep 1 + done systemctl reboot else prompt_error "Anaconda exited non-zero (status $rc).