Merge pull request 'feat(installer): v0.6 ergonomics + polish — 5 quick wins' (#3) from feat/ux-installer-v06-polish into main
Some checks failed
Build veilor-os ISO / Build live ISO (push) Failing after 0s
Lint / Kickstart syntax (push) Failing after 0s
Lint / Shell scripts (push) Failing after 0s
Lint / No personal/onyx leaks (push) Failing after 0s

This commit is contained in:
s8n-ru 2026-05-06 13:47:35 +01:00
commit 8b1b49b5fc

View file

@ -69,6 +69,21 @@ banner() {
local vline="veilor-os ${ver} · ${d} · live" local vline="veilor-os ${ver} · ${d} · live"
if [[ -r $BANNER_FILE ]]; then 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 if [[ $TUI == gum ]]; then
# gum style: rounded border, banner + blank line + version line. # gum style: rounded border, banner + blank line + version line.
gum style --border rounded --margin "0 2" --padding "1 3" \ gum style --border rounded --margin "0 2" --padding "1 3" \
@ -150,10 +165,30 @@ prompt_input() {
} }
# prompt_password <header> # prompt_password <header>
#
# 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() { prompt_password() {
local header=$1 local header=$1
if [[ $TUI == gum ]]; then 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 else
whiptail --title "veilor-os" --passwordbox "$header" 10 60 \ whiptail --title "veilor-os" --passwordbox "$header" 10 60 \
3>&1 1>&2 2>&3 3>&1 1>&2 2>&3
@ -253,12 +288,36 @@ collect_answers() {
} }
# ── LUKS passphrase ── # ── LUKS passphrase ──
# 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 luks_pw=$(prompt_password "[2/3] Encryption · LUKS2 passphrase (min 8)") || return 1
validate_pw "$luks_pw" "passphrase" || 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 password ──
# 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 admin_pw=$(prompt_password "[3/3] Admin user · password for 'admin'") || return 1
validate_pw "$admin_pw" "password" || 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 ── # ── Locale ──
# Hardcoded en_US.UTF-8 for branded consistency. The picker that # Hardcoded en_US.UTF-8 for branded consistency. The picker that
@ -984,12 +1043,39 @@ run_install() {
--show-output \ --show-output \
-- bash -c 'anaconda --cmdline --kickstart=/run/install/veilor-generated.ks 2>&1 | tee /tmp/anaconda-cmdline.log' || rc=$? -- bash -c 'anaconda --cmdline --kickstart=/run/install/veilor-generated.ks 2>&1 | tee /tmp/anaconda-cmdline.log' || rc=$?
if [[ $rc -eq 0 ]]; then if [[ $rc -eq 0 ]]; then
# 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" \ gum style --foreground 2 --border rounded --margin "1 2" --padding "1 3" \
"✓ Install complete" \ "✓ Install complete" \
"" \ "" \
"System will reboot in 5 seconds." \ "Rebooting in ${secs}s..."
"Remove the install media." gum style --foreground 3 --border thick --margin "0 2" --padding "1 3" \
sleep 5 --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 systemctl reboot
else else
prompt_error "Anaconda exited non-zero (status $rc). prompt_error "Anaconda exited non-zero (status $rc).