v0.5.8: installer UX polish — pro design

User-locked design changes for serious/pro feel:

Banner:
- Full VEILOR OS wordmark (figlet ANSI Regular block)
- Version + date + live indicator: "veilor-os 0.5.8 · 2026-05-03 · live"
- No tagline, no credit
- Rounded gum border, dim grey accent

Menu:
- Drop "Welcome" header (banner = welcome enough)
- Reorder + simplify:
    Install
    live · KDE
    live · shell
    ──────
    Reboot
    Power off
- Cursor: ❯ (sharp angle, matches box-drawing weight)
- Middle-dot · separators (cleaner than en-dash)
- Visual separator line between primary/session actions

Install flow:
- Step indicators on each prompt: [1/4] [2/4] [3/4] [4/4]
- Disk: "[1/4] Select install disk · WILL BE ERASED"
- LUKS: "[2/4] Encryption · LUKS2 passphrase (min 8)"
- Admin: "[3/4] Admin user · password for 'admin'"
- Locale: "[4/4] Locale"

Confirm screen:
- Boxed (gum style --border rounded)
- "WILL BE ERASED" colored red (FG=1)
- "This action is irreversible" colored amber (FG=3)
- gum confirm with --affirmative "Yes, install" / --negative "Cancel"

Install progress:
- gum spin with --show-output during anaconda run
- Title: "Installing veilor-os to /dev/X · 10-30min · logs on tty2"
- Success: green-bordered "✓ Install complete" box, 5s reboot countdown

os-release: VERSION_ID 0.1 → 0.5.8
This commit is contained in:
veilor-org 2026-05-03 03:46:36 +01:00
parent 53949b0899
commit f8fc89e399
3 changed files with 106 additions and 45 deletions

View file

@ -28,7 +28,7 @@ export GUM_CHOOSE_HEADER_FOREGROUND="$VEILOR_FG"
export GUM_CHOOSE_ITEM_FOREGROUND="$VEILOR_FG" export GUM_CHOOSE_ITEM_FOREGROUND="$VEILOR_FG"
export GUM_CHOOSE_SELECTED_FOREGROUND="$VEILOR_FG" export GUM_CHOOSE_SELECTED_FOREGROUND="$VEILOR_FG"
export GUM_CHOOSE_SELECTED_BACKGROUND="$VEILOR_DIM" export GUM_CHOOSE_SELECTED_BACKGROUND="$VEILOR_DIM"
export GUM_CHOOSE_CURSOR=" " export GUM_CHOOSE_CURSOR=" "
# ── gum input ────────────────────────────────────────── # ── gum input ──────────────────────────────────────────
# Single-line text entry (hostname). # Single-line text entry (hostname).
@ -36,7 +36,7 @@ export GUM_INPUT_PROMPT_FOREGROUND="$VEILOR_DIM"
export GUM_INPUT_CURSOR_FOREGROUND="$VEILOR_FG" export GUM_INPUT_CURSOR_FOREGROUND="$VEILOR_FG"
export GUM_INPUT_PLACEHOLDER_FOREGROUND="$VEILOR_MUTE" export GUM_INPUT_PLACEHOLDER_FOREGROUND="$VEILOR_MUTE"
export GUM_INPUT_HEADER_FOREGROUND="$VEILOR_FG" export GUM_INPUT_HEADER_FOREGROUND="$VEILOR_FG"
export GUM_INPUT_PROMPT=" " export GUM_INPUT_PROMPT=" "
# ── gum write (multi-line) ───────────────────────────── # ── gum write (multi-line) ─────────────────────────────
# Reserved for any longer-form prompts; not used in v0.5.1 yet. # Reserved for any longer-form prompts; not used in v0.5.1 yet.

View file

@ -2,8 +2,8 @@ NAME="veilor-os"
PRETTY_NAME="veilor-os 0.1" PRETTY_NAME="veilor-os 0.1"
ID=veilor ID=veilor
ID_LIKE=fedora ID_LIKE=fedora
VERSION="0.1" VERSION="0.5.8"
VERSION_ID="0.1" VERSION_ID="0.5.8"
HOME_URL="https://github.com/veilor-org/veilor-os" HOME_URL="https://github.com/veilor-org/veilor-os"
DOCUMENTATION_URL="https://github.com/veilor-org/veilor-os/tree/main/docs" DOCUMENTATION_URL="https://github.com/veilor-org/veilor-os/tree/main/docs"
BUG_REPORT_URL="https://github.com/veilor-org/veilor-os/issues" BUG_REPORT_URL="https://github.com/veilor-org/veilor-os/issues"

View file

@ -54,23 +54,34 @@ fi
banner() { banner() {
clear clear
# Read version + date for version line. /etc/os-release has VERSION_ID.
local ver=""
[[ -r /etc/os-release ]] && ver=$(. /etc/os-release; echo "${VERSION_ID:-0.0}")
local d
d=$(date +%Y-%m-%d)
local vline="veilor-os ${ver} · ${d} · live"
if [[ -r $BANNER_FILE ]]; then if [[ -r $BANNER_FILE ]]; then
if [[ $TUI == gum ]]; then if [[ $TUI == gum ]]; then
# gum style draws a rounded border + padding around the banner. # gum style: rounded border, banner + blank line + version line.
gum style --border rounded --margin "1" --padding "1 2" \ gum style --border rounded --margin "0 2" --padding "1 3" \
--border-foreground "${VEILOR_DIM:-240}" \ --border-foreground "${VEILOR_DIM:-240}" \
--foreground "${VEILOR_FG:-15}" \ --foreground "${VEILOR_FG:-15}" \
"$(cat "$BANNER_FILE")" "$(cat "$BANNER_FILE")" \
"" \
"$vline"
else else
cat "$BANNER_FILE" cat "$BANNER_FILE"
echo echo
echo " $vline"
echo
fi fi
else else
# Fallback ASCII if banner.txt missing (older overlay). # Fallback ASCII if banner.txt missing (older overlay).
cat << 'EOF' cat << EOF
veilor-os installer veilor-os installer
hardened. branded. yours. $vline
EOF EOF
fi fi
@ -180,10 +191,14 @@ prompt_error() {
} }
main_menu() { main_menu() {
prompt_choose "Welcome" \ # Empty header — banner already provides context. `──────` line splits
# primary actions (top) from session controls (bottom). Cursor `` set
# via GUM_CHOOSE_CURSOR in colors.gum.
prompt_choose "" \
"Install" \ "Install" \
"live - (KDE)" \ "live · KDE" \
"live - shell" \ "live · shell" \
"──────" \
"Reboot" \ "Reboot" \
"Power off" "Power off"
} }
@ -202,7 +217,7 @@ collect_answers() {
prompt_error "No installable disks found." prompt_error "No installable disks found."
return 1 return 1
fi fi
disk=$(prompt_choose_pairs "Select install disk (will be ERASED)" "${disks_pairs[@]}") || return 1 disk=$(prompt_choose_pairs "[1/4] Select install disk · WILL BE ERASED" "${disks_pairs[@]}") || return 1
# ── Hostname ── # ── Hostname ──
# Hardcoded for branded consistency. Post-install: `hostnamectl set-hostname`. # Hardcoded for branded consistency. Post-install: `hostnamectl set-hostname`.
@ -231,27 +246,48 @@ collect_answers() {
} }
# ── LUKS passphrase ── # ── LUKS passphrase ──
luks_pw=$(prompt_password "LUKS passphrase (full-disk encryption)") || return 1 luks_pw=$(prompt_password "[2/4] Encryption · LUKS2 passphrase (min 8)") || return 1
validate_pw "$luks_pw" "passphrase" || return 1 validate_pw "$luks_pw" "passphrase" || return 1
# ── Admin password ── # ── Admin password ──
admin_pw=$(prompt_password "Admin user password (login after install)") || return 1 admin_pw=$(prompt_password "[3/4] Admin user · password for 'admin'") || return 1
validate_pw "$admin_pw" "password" || return 1 validate_pw "$admin_pw" "password" || return 1
# ── Locale ── # ── Locale ──
locale=$(prompt_choose "Choose locale" \ locale=$(prompt_choose "[4/4] Locale" \
"en_GB.UTF-8" \ "en_GB.UTF-8" \
"en_US.UTF-8") || return 1 "en_US.UTF-8") || return 1
# ── Confirmation ── # ── Confirmation ──
prompt_confirm "About to install veilor-os: # Render summary box + danger lines via gum style, then gum confirm.
# Whiptail fallback: simple yesno.
if [[ $TUI == gum ]]; then
clear
gum style --border rounded --margin "1 2" --padding "1 3" \
--border-foreground "${VEILOR_DIM:-240}" \
--foreground "${VEILOR_FG:-15}" \
"Confirm install" \
"" \
" Disk $disk $(gum style --foreground 1 'WILL BE ERASED')" \
" Locale $locale" \
" LUKS ✓ set" \
" Admin ✓ set" \
"" \
"$(gum style --foreground 3 'This action is irreversible.')"
gum confirm --affirmative "Yes, install" --negative "Cancel" "Proceed?" || return 1
else
whiptail --title "Confirm install" --yesno \
"About to install veilor-os:
Disk: $disk (will be ERASED) Disk: $disk (WILL BE ERASED)
Locale: $locale Locale: $locale
LUKS: set LUKS: set
Admin pw: set Admin pw: set
Proceed?" || return 1 This action is irreversible.
Proceed?" 16 60 || return 1
fi
# Export to caller via globals # Export to caller via globals
SEL_DISK=$disk SEL_DISK=$disk
@ -524,26 +560,48 @@ KSEOF
} }
run_install() { run_install() {
# Anaconda env setup (see comments below).
export LANG="${SEL_LOCALE:-en_GB.UTF-8}"
export LC_ALL="$LANG"
# LANG / LC_ALL: anaconda's keyboard.activate_keyboard() raises
# AnacondaError: 'LANG' if missing. tty1 inherits no locale by default.
# XDG_RUNTIME_DIR: anaconda's display.setup_display() probes
# os.getenv("XDG_RUNTIME_DIR") + Wayland socket and crashes with
# TypeError on None. We're running unattended so no display needed,
# but env var still has to exist.
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
mkdir -p "$XDG_RUNTIME_DIR"
chmod 0700 "$XDG_RUNTIME_DIR"
if [[ $TUI == gum ]]; then
clear
# gum spin runs anaconda as subprocess. --show-output forwards
# stdout/stderr to /tmp/anaconda-cmdline.log so we can debug.
# --title shows live-updating header. --spinner dot is minimal.
local rc=0
gum spin --spinner dot \
--title "Installing veilor-os to $SEL_DISK · this takes 10-30 min · logs on tty2" \
--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
systemctl reboot
else
prompt_error "Anaconda exited non-zero (status $rc).
Logs at /tmp/anaconda.log + /var/log/veilor-installer.log.
Press OK to drop to shell."
return 1
fi
else
prompt_message "Installing veilor-os to $SEL_DISK ... prompt_message "Installing veilor-os to $SEL_DISK ...
This will take 10-30 minutes. This will take 10-30 minutes.
Logs: /var/log/veilor-installer.log + /tmp/anaconda.log" Logs: /var/log/veilor-installer.log + /tmp/anaconda.log"
sleep 1 sleep 1
# Hand off to anaconda. --kickstart runs unattended.
# LANG / LC_ALL must be set — anaconda's keyboard.activate_keyboard()
# raises AnacondaError: 'LANG' if missing. tty1 inherits no locale by
# default. Use the locale the user picked (or fall back to en_GB).
export LANG="${SEL_LOCALE:-en_GB.UTF-8}"
export LC_ALL="$LANG"
# XDG_RUNTIME_DIR must exist — anaconda's display.setup_display()
# tries os.getenv("XDG_RUNTIME_DIR") + constants.WAYLAND_SOCKET_NAME
# and crashes with TypeError on None. We're running unattended so
# no graphical display is needed, but the env var still has to exist.
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
mkdir -p "$XDG_RUNTIME_DIR"
chmod 0700 "$XDG_RUNTIME_DIR"
# --cmdline: fully unattended text-only mode. No Wayland setup, no
# graphical/text TUI. Reads kickstart, executes, reboots. Right mode
# for our gum-driven flow where ks is fully self-contained.
if anaconda --cmdline --kickstart=/run/install/veilor-generated.ks; then if anaconda --cmdline --kickstart=/run/install/veilor-generated.ks; then
prompt_message "Install complete. System will reboot. prompt_message "Install complete. System will reboot.
Remove the install media after shutdown." Remove the install media after shutdown."
@ -555,6 +613,7 @@ Logs at /tmp/anaconda.log + /var/log/veilor-installer.log.
Press OK to drop to shell." Press OK to drop to shell."
return 1 return 1
fi fi
fi
} }
drop_to_shell() { drop_to_shell() {
@ -595,10 +654,12 @@ while true; do
run_install || continue run_install || continue
fi fi
;; ;;
"live - (KDE)") launch_desktop ;; "live · KDE") launch_desktop ;;
"live - shell") drop_to_shell ;; "live · shell") drop_to_shell ;;
"──────") banner ;; # separator clicked: redraw, no-op
"Reboot") systemctl reboot ;; "Reboot") systemctl reboot ;;
"Power off") systemctl poweroff ;; "Power off") systemctl poweroff ;;
*) drop_to_shell ;; "") banner ;; # ESC/cancel from menu: redraw
*) banner ;;
esac esac
done done