From 623262080622fa87b1c53814a0cdb155cf028324 Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Sat, 23 May 2026 10:21:38 -0400 Subject: [PATCH] installer: unify finalizer logging --- bin/test-installer-ui-flow | 15 ++++++-- configs/airootfs/root/.automated_script.sh | 2 +- .../usr/local/bin/omarchy-install-dashboard | 30 +++++++++++----- .../omarchy-iso/orchestrator/phases_impl.py | 34 +++++++++++++++++-- 4 files changed, 67 insertions(+), 14 deletions(-) diff --git a/bin/test-installer-ui-flow b/bin/test-installer-ui-flow index 5bf419f..592bb9a 100755 --- a/bin/test-installer-ui-flow +++ b/bin/test-installer-ui-flow @@ -94,9 +94,13 @@ cat >"$fail_worker" <&2 exit 1 fi +assert_contains "$fail_log" "Installation completed without any errors." assert_contains "$fail_log" "[dashboard] installer child exited with status 7" -assert_contains "$fail_log" "[dashboard] last installer phase: Installing Arch + Omarchy" +assert_contains "$fail_log" "[dashboard] last installer phase: Finalizing in chroot" +python - "$fail_log" <<'PY' +from pathlib import Path +import sys +if "\x1b[?25h" in Path(sys.argv[1]).read_text(errors="ignore"): + raise SystemExit("CSI cursor sequence leaked into log") +PY assert_not_contains "$fail_capture" "Installed in" assert_not_contains "$fail_capture" "Finished installing" diff --git a/configs/airootfs/root/.automated_script.sh b/configs/airootfs/root/.automated_script.sh index d669161..ebb2d55 100644 --- a/configs/airootfs/root/.automated_script.sh +++ b/configs/airootfs/root/.automated_script.sh @@ -36,7 +36,7 @@ touch "$OMARCHY_INSTALL_LOG_FILE" export COLUMNS=$(tput cols) export LINES=$(tput lines) -exec > >(tee >(sed -u 's/\x1b\[[0-9;]*[a-zA-Z]//g' >>"$OMARCHY_INSTALL_LOG_FILE") 2>/dev/null) 2>/dev/tty +exec > >(tee >(sed -u 's/\x1b\[[0-9;?]*[A-Za-z]//g' >>"$OMARCHY_INSTALL_LOG_FILE") 2>/dev/null) 2>/dev/tty export CLICOLOR_FORCE=1 export FORCE_COLOR=1 diff --git a/configs/airootfs/usr/local/bin/omarchy-install-dashboard b/configs/airootfs/usr/local/bin/omarchy-install-dashboard index 22a96fb..288e84c 100755 --- a/configs/airootfs/usr/local/bin/omarchy-install-dashboard +++ b/configs/airootfs/usr/local/bin/omarchy-install-dashboard @@ -60,6 +60,10 @@ cleanup() { printf '%s%s' "$RESET" "$SHOW_CURSOR" >"$TTY_PATH" 2>/dev/null || true } +dashboard_log() { + printf '[dashboard] %s\n' "$*" >>"$LOG_FILE" 2>/dev/null || true +} + stop_child() { [[ -n ${child_pid:-} ]] || return 0 if kill -0 "$child_pid" 2>/dev/null; then @@ -308,7 +312,7 @@ reboot_prompt() { 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 & + setsid "${INSTALL_CMD[@]}" >(sed -u 's/\x1b\[[0-9;?]*[A-Za-z]//g' >>"$LOG_FILE") 2>&1 & child_pid=$! child_pgid=$child_pid } @@ -320,7 +324,7 @@ launch_child frame=0 while jobs -pr | grep -qx "$child_pid"; do - render_dynamic "$frame" >"$TTY_PATH" + render_dynamic "$frame" >"$TTY_PATH" 2>/dev/null || true frame=$((frame + 1)) sleep 0.5 done @@ -332,11 +336,11 @@ set -e child_pid="" child_pgid="" -echo "[dashboard] installer child exited with status $status" >>"$LOG_FILE" +dashboard_log "installer child exited with status $status" if (( status != 0 )); then if [[ -f $STATE_FILE ]]; then - python - "$STATE_FILE" >>"$LOG_FILE" <<'PY' 2>/dev/null || true + state_summary="$(python - "$STATE_FILE" <<'PY' 2>/dev/null || true import json, sys try: state = json.load(open(sys.argv[1])) @@ -344,10 +348,20 @@ except Exception: raise SystemExit(0) phase = state.get("current_phase") if phase: - print(f"[dashboard] last installer phase: {phase}") + print(f"last installer phase: {phase}") +for item in reversed(state.get("phases", [])): + if item.get("status") == "failed": + print(f"failed phase: {item.get('name')}: {item.get('error')}") + break PY +)" + if [[ -n ${state_summary:-} ]]; then + while IFS= read -r line; do + [[ -n $line ]] && dashboard_log "$line" + done <<<"$state_summary" + fi fi - printf '%s%s' "$SHOW_CURSOR" "$CLEAR" >"$TTY_PATH" + printf '%s%s' "$SHOW_CURSOR" "$CLEAR" >"$TTY_PATH" 2>/dev/null || true exit "$status" fi @@ -356,7 +370,7 @@ render_finish finish_render_status=$? set -e if (( finish_render_status != 0 )); then - echo "[dashboard] finish screen render failed with status $finish_render_status; using fallback" >>"$LOG_FILE" + dashboard_log "finish screen render failed with status $finish_render_status; using fallback" { printf '%s%s' "$SHOW_CURSOR" "$CLEAR" printf '\nFinished installing\n\n' @@ -368,7 +382,7 @@ if [[ ${OMARCHY_UI_AUTO_REBOOT:-} == "no" ]]; then fi if reboot_prompt; then - printf '%s' "$CLEAR" >"$TTY_PATH" + printf '%s' "$CLEAR" >"$TTY_PATH" 2>/dev/null || true reboot 2>/dev/null || systemctl reboot 2>/dev/null || true fi diff --git a/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py b/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py index b1c20d7..668887f 100644 --- a/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py +++ b/configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py @@ -827,14 +827,28 @@ def run_chroot_finalizer(ctx: InstallContext) -> None: ) sudoers.chmod(0o440) - # run_logged appends as the install user. Create the target log before - # arch-chroot so the first redirection cannot fail on /var/log perms. + # run_logged appends as the install user. Bind the target's + # /var/log/omarchy-install.log to the live ISO log so the error UI, upload, + # and "view full log" all show the finalizer details instead of a separate + # hidden target-only log. target_log = ctx.target / "var" / "log" / "omarchy-install.log" omarchy_start_epoch = int(time.time()) omarchy_start_time = time.strftime("%Y-%m-%d %H:%M:%S") + ctx.log_path.parent.mkdir(parents=True, exist_ok=True) + ctx.log_path.touch(exist_ok=True) + ctx.log_path.chmod(0o666) target_log.parent.mkdir(parents=True, exist_ok=True) - target_log.write_text(f"=== Omarchy Installation Started: {omarchy_start_time} ===\n") + target_log.touch(exist_ok=True) target_log.chmod(0o666) + with ctx.log_path.open("a", encoding="utf-8") as log: + log.write(f"=== Omarchy Finalizer Started: {omarchy_start_time} ===\n") + log_bind_mounted = False + try: + subprocess.run(["mount", "--bind", str(ctx.log_path), str(target_log)], check=True) + log_bind_mounted = True + except subprocess.CalledProcessError as exc: + with ctx.log_path.open("a", encoding="utf-8") as log: + log.write(f"[orchestrator] WARNING: failed to bind unified finalizer log: {exc}\n") # 4: copy install tooling somewhere arch-chroot will not mask. /tmp is not # safe here: arch-chroot mounts a fresh tmpfs over the target's /tmp before @@ -892,6 +906,20 @@ def run_chroot_finalizer(ctx: InstallContext) -> None: subprocess.run(cmd, check=True) finally: sudoers.unlink(missing_ok=True) + if log_bind_mounted: + subprocess.run(["umount", str(target_log)], check=False, capture_output=True) + try: + shutil.copy2(ctx.log_path, target_log) + target_log.chmod(0o644) + except OSError: + pass + else: + try: + with ctx.log_path.open("a", encoding="utf-8") as live_log: + live_log.write("\n=== Target finalizer log ===\n") + live_log.write(target_log.read_text(errors="ignore")) + except OSError: + pass def _read_omarchy_mirror() -> str: