installer: unify finalizer logging

This commit is contained in:
Ryan Hughes committed 2026-05-23 10:21:38 -04:00
1 parent 8d1d3c906d
commit 6232620806
4 files changed
+67 -14

No files matched your search

+13 -2
View File
@@ -94,9 +94,13 @@ cat >"$fail_worker" <<EOF
set -euo pipefail
state="\$1"
mkdir -p "\$(dirname "\$state")"
python - "\$state" "Installing Arch + Omarchy" <<'PY'
python - "\$state" "Finalizing in chroot" <<'PY'
$write_state_py
PY
echo "Syncing the system..."
echo "Installation completed without any errors."
printf '\033[?25h'
echo "You may reboot when ready."
sleep 0.2
exit 7
EOF
@@ -132,8 +136,15 @@ if [[ $fail_status -ne 7 ]]; then
echo "Expected failure worker status 7, got $fail_status" >&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"
+1 -1
View File
@@ -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
@@ -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[@]}" </dev/null > >(sed -u 's/\x1b\[[0-9;]*[a-zA-Z]//g' >>"$LOG_FILE") 2>&1 &
setsid "${INSTALL_CMD[@]}" </dev/null > >(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
@@ -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: