installer: show dashboard during ISO install
This commit is contained in:
5 files changed
+628
-6
No files matched your search
Executable
+346
@@ -0,0 +1,346 @@
|
||||
#!/usr/bin/env bash
|
||||
# Preview Omarchy installer UI concepts without building or booting the ISO.
|
||||
# Usage: bin/omarchy-install-ui-preview [dashboard|matrix|forge|logs]
|
||||
set -euo pipefail
|
||||
|
||||
concept="${1:-dashboard}"
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
LOGO_PATH="${OMARCHY_PREVIEW_LOGO:-}"
|
||||
if [[ -z $LOGO_PATH ]]; then
|
||||
for candidate in \
|
||||
"$SCRIPT_DIR/../../omarchy-installer/logo.txt" \
|
||||
"$SCRIPT_DIR/../configs/airootfs/usr/share/omarchy/logo.txt" \
|
||||
"/usr/share/omarchy/logo.txt"; do
|
||||
if [[ -f $candidate ]]; then
|
||||
LOGO_PATH="$candidate"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
CSI=$'\033['
|
||||
RESET="${CSI}0m"
|
||||
BOLD="${CSI}1m"
|
||||
DIM="${CSI}2m"
|
||||
HIDE_CURSOR="${CSI}?25l"
|
||||
SHOW_CURSOR="${CSI}?25h"
|
||||
CLEAR="${CSI}2J${CSI}H"
|
||||
HOME="${CSI}H"
|
||||
CLEAR_LINE="${CSI}2K"
|
||||
CLEAR_TO_END="${CSI}J"
|
||||
GREEN="${CSI}38;5;150m"
|
||||
BLUE="${CSI}38;5;111m"
|
||||
PURPLE="${CSI}38;5;141m"
|
||||
YELLOW="${CSI}38;5;221m"
|
||||
RED="${CSI}38;5;203m"
|
||||
GRAY="${CSI}38;5;245m"
|
||||
DARK="${CSI}38;5;238m"
|
||||
|
||||
logo_width() {
|
||||
if [[ -n ${LOGO_PATH:-} && -f $LOGO_PATH ]]; then
|
||||
awk '{ if (length > max) max = length } END { print max+0 }' "$LOGO_PATH"
|
||||
else
|
||||
printf '78\n'
|
||||
fi
|
||||
}
|
||||
|
||||
LOGO_WIDTH="$(logo_width)"
|
||||
(( LOGO_WIDTH > 0 )) || LOGO_WIDTH=78
|
||||
CONTENT_WIDTH="${OMARCHY_PREVIEW_WIDTH:-$LOGO_WIDTH}"
|
||||
(( CONTENT_WIDTH > 0 )) || CONTENT_WIDTH="$LOGO_WIDTH"
|
||||
|
||||
phases=(
|
||||
"Preparing live environment"
|
||||
"Partitioning target disk"
|
||||
"Installing base system"
|
||||
"Installing Omarchy packages"
|
||||
"Building Limine UKI"
|
||||
"Configuring Snapper"
|
||||
"Validating boot setup"
|
||||
"Finishing"
|
||||
)
|
||||
|
||||
logs=(
|
||||
"pacman-key: populating archlinux keyring"
|
||||
"mount: /mnt ready"
|
||||
"pacstrap: installing linux btrfs-progs limine"
|
||||
"pacman: installing omarchy-installer"
|
||||
"useradd: creating ryan with /etc/skel defaults"
|
||||
"limine-update: generating omarchy_linux.efi"
|
||||
"snapper: creating root config"
|
||||
"efibootmgr: registered Limine"
|
||||
"validation: UKI present, fstab present, boot entry present"
|
||||
)
|
||||
|
||||
whimsy=(
|
||||
"making Linux feel like home"
|
||||
"installing the good defaults"
|
||||
"putting the system in order"
|
||||
"setting up a civilized desktop"
|
||||
"clearing the path to Hyprland"
|
||||
"preparing the command line sanctuary"
|
||||
"teaching pacman the house rules"
|
||||
"laying down the Tokyo Night carpet"
|
||||
"arranging the sharp tools safely"
|
||||
"keeping the boot path clean"
|
||||
"giving Limine the keys"
|
||||
"making snapshots worth having"
|
||||
"saving future you a few hours"
|
||||
"choosing boring where boring wins"
|
||||
"leaving room for your dotfiles"
|
||||
"tightening the last few screws"
|
||||
)
|
||||
|
||||
cleanup() {
|
||||
printf '%s%s' "$RESET" "$SHOW_CURSOR"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
term_cols() {
|
||||
# Match install/helpers/presentation.sh: read the real tty dimensions even
|
||||
# if stdout is redirected through a pipe/process substitution.
|
||||
if [[ -e /dev/tty ]]; then
|
||||
local size
|
||||
size=$(stty size 2>/dev/null </dev/tty || true)
|
||||
if [[ -n $size ]]; then
|
||||
printf '%s\n' "${size#* }"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
tput cols 2>/dev/null || echo 100
|
||||
}
|
||||
|
||||
term_rows() {
|
||||
if [[ -e /dev/tty ]]; then
|
||||
local size
|
||||
size=$(stty size 2>/dev/null </dev/tty || true)
|
||||
if [[ -n $size ]]; then
|
||||
printf '%s\n' "${size%% *}"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
tput lines 2>/dev/null || echo 32
|
||||
}
|
||||
|
||||
repeat() {
|
||||
local char="$1" count="$2"
|
||||
local out=""
|
||||
for ((i=0; i<count; i++)); do out+="$char"; done
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
left_padding() {
|
||||
local width="${1:-$CONTENT_WIDTH}"
|
||||
local cols
|
||||
cols=$(term_cols)
|
||||
local pad=$(( (cols - width) / 2 ))
|
||||
(( pad < 0 )) && pad=0
|
||||
printf '%*s' "$pad" ''
|
||||
}
|
||||
|
||||
logo_padding() {
|
||||
# Exact equivalent of the installer's:
|
||||
# PADDING_LEFT=$(((TERM_WIDTH - LOGO_WIDTH) / 2))
|
||||
left_padding "$LOGO_WIDTH"
|
||||
}
|
||||
|
||||
visible_len() {
|
||||
# ANSI color/control codes should not affect centering.
|
||||
local text="$1"
|
||||
text="$(printf '%b' "$text" | sed -E $'s/\x1b\\[[0-9;?]*[A-Za-z]//g')"
|
||||
printf '%s' "${#text}"
|
||||
}
|
||||
|
||||
center() {
|
||||
# Match the real installer's gum-style layout: fixed content width shifted by
|
||||
# a computed left gutter, rather than trying to center against the full tty.
|
||||
local text="$1" width="${2:-$CONTENT_WIDTH}"
|
||||
local len
|
||||
len="$(visible_len "$text")"
|
||||
local inner_pad=$(( (width - len) / 2 ))
|
||||
(( inner_pad < 0 )) && inner_pad=0
|
||||
printf '\r%s%s%*s%b%s\n' "$CLEAR_LINE" "$(left_padding "$width")" "$inner_pad" '' "$text" "$RESET"
|
||||
}
|
||||
|
||||
line_at() {
|
||||
local width="${1:-$CONTENT_WIDTH}" indent="${2:-0}"
|
||||
shift 2
|
||||
printf '\r%s%s%*s' "$CLEAR_LINE" "$(left_padding "$width")" "$indent" ''
|
||||
printf '%b' "$*"
|
||||
}
|
||||
|
||||
blank_line() {
|
||||
printf '\r%s\n' "$CLEAR_LINE"
|
||||
}
|
||||
|
||||
progress_bar() {
|
||||
local pct="$1" width="${2:-36}"
|
||||
local filled=$(( pct * width / 100 ))
|
||||
local empty=$(( width - filled ))
|
||||
printf '%s%s%s%s%s %3d%%' "$GREEN" "$(repeat █ "$filled")" "$DARK" "$(repeat ░ "$empty")" "$RESET" "$pct"
|
||||
}
|
||||
|
||||
logo() {
|
||||
if [[ -n ${LOGO_PATH:-} && -f $LOGO_PATH ]]; then
|
||||
cat "$LOGO_PATH"
|
||||
else
|
||||
printf 'Omarchy\n'
|
||||
fi
|
||||
}
|
||||
|
||||
render_logo() {
|
||||
# Draw the logo like clear_logo does: one left gutter derived from the max
|
||||
# logo width, then the logo text exactly as stored in logo.txt.
|
||||
local pad
|
||||
pad="$(logo_padding)"
|
||||
while IFS= read -r line; do
|
||||
printf '\r%s%s%b%s%b\n' "$CLEAR_LINE" "$pad" "$GREEN" "$line" "$RESET"
|
||||
done < <(logo)
|
||||
}
|
||||
|
||||
current_log() {
|
||||
local i="$1"
|
||||
printf '%s' "${logs[$(( i % ${#logs[@]} ))]}"
|
||||
}
|
||||
|
||||
current_whimsy() {
|
||||
local frame="$1" phase_idx="${2:-0}"
|
||||
# Change slower than the spinner so it feels intentional, not jittery.
|
||||
printf '%s' "${whimsy[$(( (phase_idx * 3 + frame / 16) % ${#whimsy[@]} ))]}"
|
||||
}
|
||||
|
||||
render_dashboard() {
|
||||
local frame="$1" phase_idx=$(( frame / 12 ))
|
||||
(( phase_idx >= ${#phases[@]} )) && phase_idx=$((${#phases[@]} - 1))
|
||||
local pct=$(( frame * 100 / 95 ))
|
||||
(( pct > 100 )) && pct=100
|
||||
|
||||
printf '%s' "$HOME"
|
||||
blank_line
|
||||
render_logo
|
||||
blank_line
|
||||
center "Phase $((phase_idx + 1))/${#phases[@]} ${phases[$phase_idx]}" "$CONTENT_WIDTH"
|
||||
blank_line
|
||||
line_at "$CONTENT_WIDTH" $(( (CONTENT_WIDTH - 40) / 2 )) ""
|
||||
progress_bar "$pct" 34
|
||||
printf '\n'
|
||||
blank_line
|
||||
center "Now: $(current_log "$frame")" "$CONTENT_WIDTH"
|
||||
blank_line
|
||||
local spin=("◐" "◓" "◑" "◒")
|
||||
center "${GREEN}${spin[$((frame % 4))]} $(current_whimsy "$frame" "$phase_idx") ${RESET}" "$CONTENT_WIDTH"
|
||||
blank_line
|
||||
center "${DIM}Press Ctrl-C to quit${RESET}" "$CONTENT_WIDTH"
|
||||
}
|
||||
|
||||
render_matrix() {
|
||||
local frame="$1" rows cols
|
||||
rows=$(term_rows); cols=$(term_cols)
|
||||
(( rows > 34 )) && rows=34
|
||||
(( cols > 110 )) && cols=110
|
||||
local words=(pacstrap limine btrfs snapper uki kernel hyprland omarchy archlinux)
|
||||
|
||||
printf '%s' "$HOME"
|
||||
printf '%s%s' "$DIM" "$PURPLE"
|
||||
center "TOKYO NIGHT PACKAGE RAIN" "$cols"
|
||||
printf '%s' "$RESET"
|
||||
echo
|
||||
|
||||
for ((r=2; r<rows-7; r++)); do
|
||||
line=""
|
||||
for ((c=0; c<cols; c+=10)); do
|
||||
if (( (r + c + frame) % 7 == 0 )); then
|
||||
word="${words[$(((r + c + frame) % ${#words[@]}))]}"
|
||||
line+="${GREEN}${word:0:9}${RESET}"
|
||||
elif (( (r * c + frame) % 19 == 0 )); then
|
||||
line+="${BLUE}│${RESET} "
|
||||
else
|
||||
line+=" "
|
||||
fi
|
||||
done
|
||||
printf '%b\n' "${line:0:cols}"
|
||||
done
|
||||
|
||||
local phase_idx=$(( frame / 10 ))
|
||||
(( phase_idx >= ${#phases[@]} )) && phase_idx=$((${#phases[@]} - 1))
|
||||
echo
|
||||
center "${BOLD}${GREEN}${phases[$phase_idx]}${RESET}" "$cols"
|
||||
center "${GRAY}$(current_log "$frame")${RESET}" "$cols"
|
||||
}
|
||||
|
||||
render_forge() {
|
||||
local frame="$1" phase_idx=$(( frame / 12 ))
|
||||
(( phase_idx >= ${#phases[@]} )) && phase_idx=$((${#phases[@]} - 1))
|
||||
local sparks=("·" "∙" "*" "✦" "✧" "⋆")
|
||||
local hammer=("⚒" " " "⚒" " ")
|
||||
local pct=$(( frame * 100 / 95 ))
|
||||
(( pct > 100 )) && pct=100
|
||||
|
||||
printf '%s' "$HOME"
|
||||
printf '%s' "$YELLOW"
|
||||
center "THE OMARCHY FORGE" 80
|
||||
printf '%s\n\n' "$RESET"
|
||||
|
||||
center " ${sparks[$((frame % 6))]} ${sparks[$(((frame+2) % 6))]} ${sparks[$(((frame+4) % 6))]}" 80
|
||||
center " ${YELLOW}${hammer[$((frame % 4))]}${RESET}" 80
|
||||
center " _____________" 80
|
||||
center " / \\" 80
|
||||
center " ____/ ARCH ROOTFS \____" 80
|
||||
center " / \_______________/ \\" 80
|
||||
center " / BTRFS │ LIMINE │ UKI \\" 80
|
||||
center " \_________│__________│______/" 80
|
||||
echo
|
||||
center "${GREEN}${phases[$phase_idx]}${RESET}" 80
|
||||
echo
|
||||
line_at 80 22 ""
|
||||
progress_bar "$pct" 36
|
||||
echo -e "\n"
|
||||
center "${GRAY}$(current_log "$frame")${RESET}" 80
|
||||
}
|
||||
|
||||
render_logs() {
|
||||
local frame="$1" phase_idx=$(( frame / 12 ))
|
||||
(( phase_idx >= ${#phases[@]} )) && phase_idx=$((${#phases[@]} - 1))
|
||||
local pct=$(( frame * 100 / 95 ))
|
||||
(( pct > 100 )) && pct=100
|
||||
|
||||
printf '%s' "$HOME"
|
||||
render_logo
|
||||
blank_line
|
||||
line_at "$CONTENT_WIDTH" 0 "${BOLD}Omarchy Install${RESET}\n"
|
||||
line_at "$CONTENT_WIDTH" 0 ""
|
||||
progress_bar "$pct" 50
|
||||
printf '\n'
|
||||
blank_line
|
||||
line_at "$CONTENT_WIDTH" 0 "${GREEN}${phases[$phase_idx]}${RESET}\n"
|
||||
blank_line
|
||||
|
||||
local start=$(( frame - 12 ))
|
||||
(( start < 0 )) && start=0
|
||||
for ((i=start; i<=frame; i++)); do
|
||||
line_at "$CONTENT_WIDTH" 2 "${GRAY}→${RESET} [$(printf '%02d:%02d' "$((i / 3))" "$(((i * 7) % 60))")] $(current_log "$i")\n"
|
||||
done
|
||||
blank_line
|
||||
line_at "$CONTENT_WIDTH" 0 "${DIM}This is the boring/debuggable mode: curated progress plus log tail.${RESET}\n"
|
||||
}
|
||||
|
||||
case "$concept" in
|
||||
dashboard|matrix|forge|logs) ;;
|
||||
-h|--help|help)
|
||||
echo "Usage: $0 [dashboard|matrix|forge|logs]"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown concept: $concept" >&2
|
||||
echo "Usage: $0 [dashboard|matrix|forge|logs]" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
printf '%s%s' "$HIDE_CURSOR" "$CLEAR"
|
||||
for ((frame=0; frame<=96; frame++)); do
|
||||
"render_$concept" "$frame"
|
||||
printf '%s' "$CLEAR_TO_END"
|
||||
sleep 0.08
|
||||
done
|
||||
@@ -43,11 +43,33 @@ export FORCE_COLOR=1
|
||||
cd /root
|
||||
./configurator
|
||||
|
||||
# Keep the actual install screen calm: the dashboard owns /dev/tty while the
|
||||
# noisy installer stream is captured to the support log.
|
||||
/usr/local/bin/omarchy-install-dashboard "$OMARCHY_INSTALL_LOG_FILE" /run/omarchy-install/state.json /mnt/var/log/omarchy-install.log >/dev/tty 2>&1 &
|
||||
dashboard_pid=$!
|
||||
export OMARCHY_INSTALL_DASHBOARD_PID="$dashboard_pid"
|
||||
|
||||
stop_install_dashboard() {
|
||||
if [[ -n ${dashboard_pid:-} ]]; then
|
||||
kill "$dashboard_pid" 2>/dev/null || true
|
||||
wait "$dashboard_pid" 2>/dev/null || true
|
||||
unset dashboard_pid
|
||||
fi
|
||||
printf "\033[?25h" >/dev/tty
|
||||
}
|
||||
|
||||
# Absolute paths because omarchy-iso-install cd's into /usr/share/omarchy-iso
|
||||
# before exec'ing python; relative paths would resolve against the wrong dir.
|
||||
exec /usr/local/bin/omarchy-iso-install \
|
||||
set +e
|
||||
/usr/local/bin/omarchy-iso-install \
|
||||
--config /root/user_configuration.json \
|
||||
--creds /root/user_credentials.json \
|
||||
--full-name-file /root/user_full_name.txt \
|
||||
--email-file /root/user_email_address.txt \
|
||||
--encrypt-file /root/user_encrypt_installation.txt
|
||||
--encrypt-file /root/user_encrypt_installation.txt \
|
||||
> >(sed -u 's/\x1b\[[0-9;]*[a-zA-Z]//g' >>"$OMARCHY_INSTALL_LOG_FILE") 2>&1
|
||||
install_status=$?
|
||||
set -e
|
||||
|
||||
stop_install_dashboard
|
||||
exit "$install_status"
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
#!/usr/bin/env bash
|
||||
# Render the live Omarchy install dashboard on tty1 while installer output is
|
||||
# captured to logs. Intentionally has no external UI dependencies.
|
||||
set -euo pipefail
|
||||
|
||||
LOG_FILE="${1:-/var/log/omarchy-install.log}"
|
||||
STATE_FILE="${2:-/run/omarchy-install/state.json}"
|
||||
TARGET_LOG_FILE="${3:-/mnt/var/log/omarchy-install.log}"
|
||||
LOGO_PATH="${OMARCHY_PATH:-/usr/share/omarchy}/logo.txt"
|
||||
|
||||
CSI=$'\033['
|
||||
RESET="${CSI}0m"
|
||||
BOLD="${CSI}1m"
|
||||
DIM="${CSI}2m"
|
||||
HIDE_CURSOR="${CSI}?25l"
|
||||
SHOW_CURSOR="${CSI}?25h"
|
||||
CLEAR="${CSI}2J${CSI}H"
|
||||
HOME="${CSI}H"
|
||||
CLEAR_LINE="${CSI}2K"
|
||||
CLEAR_TO_END="${CSI}J"
|
||||
GREEN="${CSI}38;5;150m"
|
||||
GRAY="${CSI}38;5;245m"
|
||||
DARK="${CSI}38;5;238m"
|
||||
|
||||
whimsy=(
|
||||
"making Linux feel like home"
|
||||
"installing the good defaults"
|
||||
"putting the system in order"
|
||||
"setting up a civilized desktop"
|
||||
"clearing the path to Hyprland"
|
||||
"preparing the command line sanctuary"
|
||||
"teaching pacman the house rules"
|
||||
"laying down the Tokyo Night carpet"
|
||||
"arranging the sharp tools safely"
|
||||
"keeping the boot path clean"
|
||||
"giving Limine the keys"
|
||||
"making snapshots worth having"
|
||||
"saving future you a few hours"
|
||||
"choosing boring where boring wins"
|
||||
"leaving room for your dotfiles"
|
||||
"tightening the last few screws"
|
||||
)
|
||||
|
||||
cleanup() {
|
||||
printf '%s%s' "$RESET" "$SHOW_CURSOR" >/dev/tty 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
term_size() {
|
||||
stty size 2>/dev/null </dev/tty || printf '24 80\n'
|
||||
}
|
||||
|
||||
term_cols() {
|
||||
local size
|
||||
size="$(term_size)"
|
||||
printf '%s\n' "${size#* }"
|
||||
}
|
||||
|
||||
repeat() {
|
||||
local char="$1" count="$2" out=""
|
||||
for ((i=0; i<count; i++)); do out+="$char"; done
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
logo_width() {
|
||||
if [[ -f $LOGO_PATH ]]; then
|
||||
awk '{ if (length > max) max = length } END { print max+0 }' "$LOGO_PATH"
|
||||
else
|
||||
printf '81\n'
|
||||
fi
|
||||
}
|
||||
|
||||
LOGO_WIDTH="$(logo_width)"
|
||||
(( LOGO_WIDTH > 0 )) || LOGO_WIDTH=81
|
||||
CONTENT_WIDTH="$LOGO_WIDTH"
|
||||
|
||||
left_padding() {
|
||||
local width="${1:-$CONTENT_WIDTH}" cols pad
|
||||
cols="$(term_cols)"
|
||||
pad=$(( (cols - width) / 2 ))
|
||||
(( pad < 0 )) && pad=0
|
||||
printf '%*s' "$pad" ''
|
||||
}
|
||||
|
||||
visible_len() {
|
||||
local text="$1"
|
||||
text="$(printf '%b' "$text" | sed -E $'s/\x1b\\[[0-9;?]*[A-Za-z]//g')"
|
||||
printf '%s' "${#text}"
|
||||
}
|
||||
|
||||
blank_line() {
|
||||
printf '\r%s\n' "$CLEAR_LINE"
|
||||
}
|
||||
|
||||
line_at() {
|
||||
local width="${1:-$CONTENT_WIDTH}" indent="${2:-0}"
|
||||
shift 2
|
||||
printf '\r%s%s%*s' "$CLEAR_LINE" "$(left_padding "$width")" "$indent" ''
|
||||
printf '%b' "$*"
|
||||
}
|
||||
|
||||
center() {
|
||||
local text="$1" width="${2:-$CONTENT_WIDTH}" len inner_pad
|
||||
len="$(visible_len "$text")"
|
||||
inner_pad=$(( (width - len) / 2 ))
|
||||
(( inner_pad < 0 )) && inner_pad=0
|
||||
printf '\r%s%s%*s%b%s\n' "$CLEAR_LINE" "$(left_padding "$width")" "$inner_pad" '' "$text" "$RESET"
|
||||
}
|
||||
|
||||
render_logo() {
|
||||
local pad
|
||||
pad="$(left_padding "$LOGO_WIDTH")"
|
||||
if [[ -f $LOGO_PATH ]]; then
|
||||
while IFS= read -r line; do
|
||||
printf '\r%s%s%b%s%b\n' "$CLEAR_LINE" "$pad" "$GREEN" "$line" "$RESET"
|
||||
done <"$LOGO_PATH"
|
||||
else
|
||||
center "${GREEN}Omarchy${RESET}" "$CONTENT_WIDTH"
|
||||
fi
|
||||
}
|
||||
|
||||
progress_bar() {
|
||||
local pct="$1" width="${2:-34}" filled empty
|
||||
(( pct < 0 )) && pct=0
|
||||
(( pct > 100 )) && pct=100
|
||||
filled=$(( pct * width / 100 ))
|
||||
empty=$(( width - filled ))
|
||||
printf '%s%s%s%s%s %3d%%' "$GREEN" "$(repeat █ "$filled")" "$DARK" "$(repeat ░ "$empty")" "$RESET" "$pct"
|
||||
}
|
||||
|
||||
state_value() {
|
||||
local key="$1" fallback="$2"
|
||||
[[ -f $STATE_FILE ]] || { printf '%s' "$fallback"; return; }
|
||||
python - "$STATE_FILE" "$key" "$fallback" <<'PY' 2>/dev/null || true
|
||||
import json, sys
|
||||
path, key, fallback = sys.argv[1:4]
|
||||
try:
|
||||
with open(path) as f:
|
||||
state = json.load(f)
|
||||
except Exception:
|
||||
print(fallback, end='')
|
||||
raise SystemExit
|
||||
if key == 'phase':
|
||||
print(state.get('current_phase') or 'Starting installation', end='')
|
||||
elif key == 'index':
|
||||
print(state.get('current_index') or 0, end='')
|
||||
elif key == 'total':
|
||||
print(state.get('total_phases') or 1, end='')
|
||||
elif key == 'done':
|
||||
print(len([p for p in state.get('phases', []) if p.get('status') == 'ok']), end='')
|
||||
else:
|
||||
print(fallback, end='')
|
||||
PY
|
||||
}
|
||||
|
||||
last_log_line() {
|
||||
local file line files
|
||||
if [[ -s $TARGET_LOG_FILE && $TARGET_LOG_FILE -nt $LOG_FILE ]]; then
|
||||
files=("$TARGET_LOG_FILE" "$LOG_FILE")
|
||||
else
|
||||
files=("$LOG_FILE" "$TARGET_LOG_FILE")
|
||||
fi
|
||||
|
||||
for file in "${files[@]}"; do
|
||||
[[ -s $file ]] || continue
|
||||
line="$(grep -avE '^(\s*|===|\[.*\] (Starting|Completed):)' "$file" 2>/dev/null | tail -n 1 || true)"
|
||||
if [[ -n $line ]]; then
|
||||
line="${line//$'\r'/}"
|
||||
line="$(printf '%s' "$line" | sed -E 's/\x1b\[[0-9;?]*[A-Za-z]//g')"
|
||||
printf '%s' "${line:0:$((CONTENT_WIDTH - 8))}"
|
||||
return
|
||||
fi
|
||||
done
|
||||
printf 'starting installer'
|
||||
}
|
||||
|
||||
current_whimsy() {
|
||||
local frame="$1" index="$2"
|
||||
printf '%s' "${whimsy[$(( (index * 3 + frame / 8) % ${#whimsy[@]} ))]}"
|
||||
}
|
||||
|
||||
render() {
|
||||
local frame="$1" phase index total done pct spin log_line
|
||||
phase="$(state_value phase 'Starting installation')"
|
||||
index="$(state_value index 0)"
|
||||
total="$(state_value total 1)"
|
||||
done="$(state_value done 0)"
|
||||
(( total < 1 )) && total=1
|
||||
pct=$(( (done * 100) / total ))
|
||||
# Give the active phase a little movement without pretending exact progress.
|
||||
if (( done < total )); then
|
||||
pct=$(( pct + (frame % 8) * (100 / total) / 8 ))
|
||||
fi
|
||||
(( pct > 100 )) && pct=100
|
||||
spin=("◐" "◓" "◑" "◒")
|
||||
log_line="$(last_log_line)"
|
||||
|
||||
printf '%s' "$HOME"
|
||||
blank_line
|
||||
render_logo
|
||||
blank_line
|
||||
center "Phase $((index + 1))/$total $phase" "$CONTENT_WIDTH"
|
||||
blank_line
|
||||
line_at "$CONTENT_WIDTH" $(( (CONTENT_WIDTH - 40) / 2 )) ""
|
||||
progress_bar "$pct" 34
|
||||
printf '\n'
|
||||
blank_line
|
||||
center "Now: $log_line" "$CONTENT_WIDTH"
|
||||
blank_line
|
||||
center "${GREEN}${spin[$((frame % 4))]} $(current_whimsy "$frame" "$index") ${RESET}" "$CONTENT_WIDTH"
|
||||
blank_line
|
||||
center "${DIM}Logs are being saved for support${RESET}" "$CONTENT_WIDTH"
|
||||
printf '%s' "$CLEAR_TO_END"
|
||||
}
|
||||
|
||||
printf '%s%s' "$HIDE_CURSOR" "$CLEAR" >/dev/tty
|
||||
frame=0
|
||||
while true; do
|
||||
render "$frame" >/dev/tty
|
||||
frame=$((frame + 1))
|
||||
sleep 0.5
|
||||
done
|
||||
@@ -23,10 +23,21 @@ class PhaseError(Exception):
|
||||
def run(ctx: InstallContext, phases: list[tuple[str, PhaseFn]]) -> None:
|
||||
ctx.state_dir.mkdir(parents=True, exist_ok=True)
|
||||
state_path = ctx.state_dir / "state.json"
|
||||
state = {"started_at": time.time(), "phases": []}
|
||||
state = {
|
||||
"started_at": time.time(),
|
||||
"total_phases": len(phases),
|
||||
"current_index": 0,
|
||||
"current_phase": "Starting installation",
|
||||
"phases": [],
|
||||
}
|
||||
_write_state(state_path, state)
|
||||
|
||||
for name, fn in phases:
|
||||
for index, (name, fn) in enumerate(phases):
|
||||
state["current_index"] = index
|
||||
state["current_phase"] = name
|
||||
state["phase_started_at"] = time.time()
|
||||
_write_state(state_path, state)
|
||||
|
||||
info(f"› {name}")
|
||||
started = time.time()
|
||||
try:
|
||||
@@ -49,6 +60,8 @@ def run(ctx: InstallContext, phases: list[tuple[str, PhaseFn]]) -> None:
|
||||
state["phases"].append({"name": name, "status": "ok", "elapsed": elapsed})
|
||||
_write_state(state_path, state)
|
||||
|
||||
state["current_index"] = max(len(phases) - 1, 0)
|
||||
state["current_phase"] = "Installation complete"
|
||||
state["finished_at"] = time.time()
|
||||
_write_state(state_path, state)
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ Phase ordering (protected / pre-mounted):
|
||||
arch_install_base → archinstall used as pacstrap + users +
|
||||
packages driver only; no bootloader,
|
||||
no fstab, no mkinitcpio
|
||||
configure_protected_boot → Omarchy-owned fstab/crypttab/mkinitcpio/
|
||||
bootloader — implemented in Step 8
|
||||
configure_protected_boot → protected fstab/crypttab + Limine EFI handoff;
|
||||
final UKI build still happens in finalizer
|
||||
run_chroot_finalizer → same
|
||||
configure_login → same
|
||||
validate_boot_protected → implemented in Step 8
|
||||
@@ -1047,7 +1047,26 @@ def cleanup_protected_state(ctx: InstallContext) -> None:
|
||||
# finish: prompt for reboot. Bind mounts are unwound in main()'s finally.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def _stop_install_dashboard() -> None:
|
||||
pid = os.environ.get("OMARCHY_INSTALL_DASHBOARD_PID")
|
||||
if not pid:
|
||||
return
|
||||
try:
|
||||
os.kill(int(pid), 15)
|
||||
except (OSError, ValueError):
|
||||
pass
|
||||
# Give the shell dashboard a moment to restore the cursor before gum draws.
|
||||
time.sleep(0.1)
|
||||
try:
|
||||
with open("/dev/tty", "w", encoding="utf-8") as tty:
|
||||
tty.write("\033[?25h\033[H\033[2J")
|
||||
tty.flush()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def finish(ctx: InstallContext) -> None:
|
||||
_stop_install_dashboard()
|
||||
info("Installation finished. Reboot when ready.")
|
||||
if confirm("Reboot now?", default=True):
|
||||
os.system("reboot")
|
||||
Reference in new issue
Block a user