#!/usr/bin/env bash # Foreground Omarchy ISO install UI supervisor. # # This process is the only owner of the visible install UI. It launches the # actual installer as a non-interactive child, sends child output to the support # log, renders progress while the child runs, then renders the final installed # time + reboot prompt itself. There is deliberately no background dashboard. set -euo pipefail usage() { echo "Usage: $0 -- " >&2 exit 2 } (( $# >= 4 )) || usage LOG_FILE="$1" STATE_FILE="$2" shift 2 [[ ${1:-} == "--" ]] || usage shift (( $# > 0 )) || usage INSTALL_CMD=("$@") TTY_PATH="${OMARCHY_DASHBOARD_TTY:-/dev/tty}" LOGO_PATH="${OMARCHY_PATH:-/usr/share/omarchy}/logo.txt" 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" GREEN="${CSI}32m" DARK="${CSI}90m" child_pid="" child_pgid="" 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 } stop_child() { [[ -n ${child_pid:-} ]] || return 0 if kill -0 "$child_pid" 2>/dev/null; then if [[ -n ${child_pgid:-} ]]; then kill -- -"$child_pgid" 2>/dev/null || true fi kill "$child_pid" 2>/dev/null || true for _ in {1..20}; do kill -0 "$child_pid" 2>/dev/null || break sleep 0.05 done if kill -0 "$child_pid" 2>/dev/null; then [[ -n ${child_pgid:-} ]] && kill -9 -- -"$child_pgid" 2>/dev/null || true kill -9 "$child_pid" 2>/dev/null || true fi fi wait "$child_pid" 2>/dev/null || true } interrupt() { local code="$1" stop_child cleanup exit "$code" } trap cleanup EXIT trap 'interrupt 130' INT trap 'interrupt 143' 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 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 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), ] 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 } 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 >"$TTY_PATH" 2>/dev/null; then blank_line return fi 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" } install_duration() { local path line duration for path in /mnt/var/log/omarchy-install.log "$LOG_FILE"; do [[ -f $path ]] || continue for prefix in Total Omarchy; do line="$(grep "^$prefix:" "$path" 2>/dev/null | tail -n 1 || true)" [[ -n $line ]] || continue duration="${line#*:}" duration="${duration#${duration%%[![:space:]]*}}" [[ -n $duration ]] && { printf '%s' "$duration"; return 0; } done done return 0 } render_finish() { local duration message cols cols="$(term_cols)" printf '%s%s' "$SHOW_CURSOR" "$CLEAR" >"$TTY_PATH" if command -v tte >/dev/null 2>&1 && command -v timeout >/dev/null 2>&1 && [[ -f $LOGO_PATH ]]; then timeout 8s tte -i "$LOGO_PATH" --canvas-width 0 --anchor-text c --frame-rate 920 laseretch >"$TTY_PATH" 2>/dev/null || true else render_logo >"$TTY_PATH" fi duration="$(install_duration || true)" message="Finished installing" [[ -n $duration ]] && message="Installed in $duration" { blank_line center "$message" "$cols" blank_line } >"$TTY_PATH" } reboot_prompt() { local prompt_pad prompt_pad=$(( ( $(term_cols) - 18 ) / 2 )) (( prompt_pad < 0 )) && prompt_pad=0 gum confirm \ --padding "0 0 0 $prompt_pad" \ --show-help=false \ --default \ --affirmative "Reboot Now" \ --negative "" \ "" <"$TTY_PATH" >"$TTY_PATH" 2>&1 } launch_child() { mkdir -p "$(dirname "$LOG_FILE")" # Child is non-interactive. All visible UI belongs to this supervisor. setsid "${INSTALL_CMD[@]}" >(sed -u 's/\x1b\[[0-9;]*[a-zA-Z]//g' >>"$LOG_FILE") 2>&1 & child_pid=$! child_pgid=$child_pid } [[ -e $TTY_PATH ]] || exit 2 printf '%s' "$HIDE_CURSOR" >"$TTY_PATH" render_static >"$TTY_PATH" launch_child frame=0 while jobs -pr | grep -qx "$child_pid"; do render_dynamic "$frame" >"$TTY_PATH" frame=$((frame + 1)) sleep 0.5 done set +e wait "$child_pid" status=$? set -e child_pid="" child_pgid="" if (( status != 0 )); then printf '%s%s' "$SHOW_CURSOR" "$CLEAR" >"$TTY_PATH" exit "$status" fi render_finish if [[ ${OMARCHY_UI_AUTO_REBOOT:-} == "no" ]]; then exit 0 fi if reboot_prompt; then printf '%s' "$CLEAR" >"$TTY_PATH" reboot 2>/dev/null || systemctl reboot 2>/dev/null || true fi exit 0