258 lines
7.0 KiB
Bash
Executable File
258 lines
7.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Render the live Omarchy install dashboard on tty1 while installer output is
|
|
# captured to logs. This is presentation only: it must never read input or gate
|
|
# install completion.
|
|
set -euo pipefail
|
|
|
|
LOG_FILE="${1:-/var/log/omarchy-install.log}"
|
|
STATE_FILE="${2:-/run/omarchy-install/state.json}"
|
|
STATE_DIR="$(dirname "$STATE_FILE")"
|
|
STOP_FILE="$STATE_DIR/dashboard.stop"
|
|
PID_FILE="$STATE_DIR/dashboard.pid"
|
|
LOGO_PATH="${OMARCHY_PATH:-/usr/share/omarchy}/logo.txt"
|
|
TTY_PATH="${OMARCHY_DASHBOARD_TTY:-/dev/tty}"
|
|
|
|
CSI=$'\033['
|
|
RESET="${CSI}0m"
|
|
DIM="${CSI}2m"
|
|
HIDE_CURSOR="${CSI}?25l"
|
|
SHOW_CURSOR="${CSI}?25h"
|
|
CLEAR="${CSI}2J${CSI}H"
|
|
CLEAR_LINE="${CSI}2K"
|
|
CLEAR_TO_END="${CSI}J"
|
|
# Use the 16-color ANSI palette configured by .automated_script.sh. The Linux
|
|
# VT does not reliably map 256-color indexes the same way terminal emulators do.
|
|
GREEN="${CSI}32m"
|
|
DARK="${CSI}90m"
|
|
|
|
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" >"$TTY_PATH" 2>/dev/null || true
|
|
rm -f "$PID_FILE" 2>/dev/null || true
|
|
}
|
|
trap cleanup EXIT
|
|
trap 'cleanup; exit 0' INT TERM
|
|
|
|
term_size() { stty size 2>/dev/null <"$TTY_PATH" || printf '24 80\n'; }
|
|
term_cols() { local size; size="$(term_size)"; printf '%s\n' "${size#* }"; }
|
|
|
|
repeat() {
|
|
local char="$1" count="$2" out="" i
|
|
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_height() {
|
|
if [[ -f $LOGO_PATH ]]; then
|
|
wc -l <"$LOGO_PATH"
|
|
else
|
|
printf '1\n'
|
|
fi
|
|
}
|
|
|
|
LOGO_WIDTH="$(logo_width)"
|
|
LOGO_HEIGHT="$(logo_height)"
|
|
(( LOGO_WIDTH > 0 )) || LOGO_WIDTH=81
|
|
(( LOGO_HEIGHT > 0 )) || LOGO_HEIGHT=1
|
|
CONTENT_WIDTH="$LOGO_WIDTH"
|
|
DYNAMIC_ROW=$((LOGO_HEIGHT + 3))
|
|
LAST_PCT=1
|
|
|
|
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"
|
|
}
|
|
|
|
install_progress() {
|
|
[[ -f $STATE_FILE ]] || { printf '1'; return; }
|
|
python - "$STATE_FILE" <<'PY' 2>/dev/null || printf '1'
|
|
import json, sys, time
|
|
path = sys.argv[1]
|
|
try:
|
|
state = json.load(open(path))
|
|
except Exception:
|
|
print(1, end='')
|
|
raise SystemExit
|
|
phase = state.get('current_phase') or 'Starting installation'
|
|
if state.get('finished_at') or phase == 'Installation complete':
|
|
print(100, end='')
|
|
raise SystemExit
|
|
# Internal weighting only. Keep estimates conservative but close to observed
|
|
# ISO installs so the bar does not sit at 12% then jump to 90%.
|
|
weights = [
|
|
('Starting installation', 1, 2, 2),
|
|
('Preparing live environment', 2, 6, 5),
|
|
('Verifying protected mounts', 6, 8, 3),
|
|
('Installing Arch + Omarchy', 8, 82, 60),
|
|
('Installing base system', 8, 82, 60),
|
|
('Configuring protected boot', 82, 86, 8),
|
|
('Finalizing in chroot', 86, 98, 90),
|
|
('Configuring login', 98, 99, 3),
|
|
('Validating protected boot setup', 99, 100, 5),
|
|
('Validating boot setup', 99, 100, 5),
|
|
('Finishing', 99, 100, 3),
|
|
]
|
|
start, end, expected = 1, 99, 90
|
|
for name, s, e, seconds in weights:
|
|
if phase == name:
|
|
start, end, expected = s, e, seconds
|
|
break
|
|
phase_started = state.get('phase_started_at') or state.get('started_at') or time.time()
|
|
elapsed = max(0, time.time() - float(phase_started))
|
|
span = max(0, end - start)
|
|
active = min(max(span - 1, 0), int((elapsed / max(expected, 1)) * span))
|
|
print(min(100, start + active), end='')
|
|
PY
|
|
}
|
|
|
|
install_finished() {
|
|
[[ -f $STATE_FILE ]] || return 1
|
|
python - "$STATE_FILE" <<'PY' 2>/dev/null
|
|
import json, sys
|
|
try:
|
|
state = json.load(open(sys.argv[1]))
|
|
except Exception:
|
|
raise SystemExit(1)
|
|
raise SystemExit(0 if state.get('finished_at') else 1)
|
|
PY
|
|
}
|
|
|
|
current_whimsy() {
|
|
local frame="$1" pct="$2"
|
|
printf '%s' "${whimsy[$(( (pct / 8 + frame / 8) % ${#whimsy[@]} ))]}"
|
|
}
|
|
|
|
render_static() {
|
|
printf '%s' "$CLEAR"
|
|
blank_line
|
|
|
|
if command -v tte >/dev/null 2>&1 && command -v timeout >/dev/null 2>&1 && [[ -f $LOGO_PATH ]]; then
|
|
if timeout 8s tte -i "$LOGO_PATH" --canvas-width 0 --anchor-text c --frame-rate 920 laseretch 2>/dev/null; then
|
|
blank_line
|
|
return
|
|
fi
|
|
# If animation fails or times out, clear any partial frame before fallback.
|
|
printf '%s' "$CLEAR"
|
|
blank_line
|
|
fi
|
|
|
|
render_logo
|
|
blank_line
|
|
}
|
|
|
|
render_dynamic() {
|
|
local frame="$1" pct spin
|
|
pct="$(install_progress)"
|
|
[[ $pct =~ ^[0-9]+$ ]] || pct=$LAST_PCT
|
|
(( pct > 100 )) && pct=100
|
|
(( pct < LAST_PCT )) && pct=$LAST_PCT
|
|
LAST_PCT=$pct
|
|
spin=("◐" "◓" "◑" "◒")
|
|
|
|
printf '%s%d;1H' "$CSI" "$DYNAMIC_ROW"
|
|
center "Installing Omarchy" "$CONTENT_WIDTH"
|
|
blank_line
|
|
line_at "$CONTENT_WIDTH" $(( (CONTENT_WIDTH - 40) / 2 )) ""
|
|
progress_bar "$pct" 34
|
|
printf '\n'
|
|
blank_line
|
|
center "${GREEN}${spin[$((frame % 4))]} $(current_whimsy "$frame" "$pct") ${RESET}" "$CONTENT_WIDTH"
|
|
blank_line
|
|
center "${DIM}Logs are being saved for support${RESET}" "$CONTENT_WIDTH"
|
|
printf '%s' "$CLEAR_TO_END"
|
|
}
|
|
|
|
if [[ ! -e $TTY_PATH ]]; then
|
|
exit 0
|
|
fi
|
|
|
|
mkdir -p "$STATE_DIR"
|
|
echo "$$" >"$PID_FILE"
|
|
|
|
printf '%s' "$HIDE_CURSOR" >"$TTY_PATH"
|
|
render_static >"$TTY_PATH"
|
|
frame=0
|
|
while true; do
|
|
[[ -e $STOP_FILE ]] && break
|
|
render_dynamic "$frame" >"$TTY_PATH"
|
|
if install_finished || [[ -e $STOP_FILE ]]; then
|
|
sleep 1
|
|
break
|
|
fi
|
|
frame=$((frame + 1))
|
|
sleep 0.5
|
|
done
|