From e82df1d4d070f03b12cbf1a0daaa4934164d786a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 16 Jul 2026 21:53:15 -0700 Subject: [PATCH] Track one-time setup with completion markers --- bin/omarchy-done | 40 +++++++++++++++ bin/omarchy-finalize-user | 7 ++- bin/omarchy-first-run | 6 +-- bin/omarchy-upgrade-to-quattro | 8 ++- docs/file-layout.md | 13 ++++- install/user/first-run/install-voxtype.hook | 15 +++--- test/shell.d/done-test.sh | 30 +++++++++++ test/shell.d/upgrade-to-quattro-test.sh | 6 +++ test/shell.d/voxtype-invitation-test.sh | 55 +++++++++++++++++++++ 9 files changed, 162 insertions(+), 18 deletions(-) create mode 100755 bin/omarchy-done create mode 100644 test/shell.d/done-test.sh create mode 100644 test/shell.d/voxtype-invitation-test.sh diff --git a/bin/omarchy-done b/bin/omarchy-done new file mode 100755 index 00000000..889a0214 --- /dev/null +++ b/bin/omarchy-done @@ -0,0 +1,40 @@ +#!/bin/bash + +# omarchy:summary=Check or mark completed Omarchy setup tasks +# omarchy:args= +# omarchy:hidden=true + +set -e + +if (( $# != 2 )); then + echo "Usage: omarchy-done " >&2 + exit 1 +fi + +action=$1 +name=$2 +done_dir="$HOME/.local/state/omarchy/done" +marker="$done_dir/$name" + +if [[ $name == */* || $name == "." || $name == ".." ]]; then + echo "Invalid done marker name: $name" >&2 + exit 1 +fi + +case "$action" in + check) + [[ -f $marker ]] + ;; + mark) + mkdir -p "$done_dir" + touch "$marker" + ;; + ensure) + mkdir -p "$done_dir" + (set -o noclobber; : >"$marker") 2>/dev/null + ;; + *) + echo "Usage: omarchy-done " >&2 + exit 1 + ;; +esac diff --git a/bin/omarchy-finalize-user b/bin/omarchy-finalize-user index df856d84..dcf72094 100755 --- a/bin/omarchy-finalize-user +++ b/bin/omarchy-finalize-user @@ -20,7 +20,7 @@ For shipped configs see /etc/skel (new users) and omarchy-reinstall-configs --first-install is used by the ISO in the target chroot. It marks shipped migrations complete for the freshly-created user. -Idempotency marker: ~/.local/state/omarchy/finalize-user.done +Idempotency marker: ~/.local/state/omarchy/done/finalize-user USAGE } @@ -55,10 +55,9 @@ while (($#)); do done state_dir="$HOME/.local/state/omarchy" -marker="$state_dir/finalize-user.done" mkdir -p "$state_dir" -if [[ -f $marker && $force -eq 0 ]]; then +if omarchy-done check finalize-user && (( force == 0 )); then echo "User finalization already complete (rerun with --force to refresh)." exit 0 fi @@ -113,5 +112,5 @@ if (( first_install )); then done fi -touch "$marker" +omarchy-done mark finalize-user echo "User finalization complete." diff --git a/bin/omarchy-first-run b/bin/omarchy-first-run index b1d3fd1e..5d8be151 100755 --- a/bin/omarchy-first-run +++ b/bin/omarchy-first-run @@ -103,8 +103,8 @@ fi run_first_run_step "notify about pending migrations" omarchy-migrate-notify -USER_MARKER="$state_dir/first-run-user.done" -if [[ ! -f $USER_MARKER || $force -eq 1 ]]; then +FIRST_RUN_DONE="first-run-user" +if ! omarchy-done check "$FIRST_RUN_DONE" || (( force )); then run_first_run_step "install Voxtype post-update hook" \ omarchy-hook-install post-update "$OMARCHY_PATH/install/user/first-run/install-voxtype.hook" @@ -126,7 +126,7 @@ if [[ ! -f $USER_MARKER || $force -eq 1 ]]; then bash "$OMARCHY_PATH/install/user/first-run/wifi.sh" if (( first_run_failed == 0 )); then - touch "$USER_MARKER" + omarchy-done mark "$FIRST_RUN_DONE" else log_first_run "One or more first-run steps failed; first-run will retry next login" fi diff --git a/bin/omarchy-upgrade-to-quattro b/bin/omarchy-upgrade-to-quattro index 491ef374..1ff13de1 100755 --- a/bin/omarchy-upgrade-to-quattro +++ b/bin/omarchy-upgrade-to-quattro @@ -2201,7 +2201,13 @@ if command -v xdg-mime >/dev/null 2>&1; then xdg-mime default HEY.desktop x-scheme-handler/mailto || true fi -touch "$state_dir/finalize-user.done" +if [[ -f $state_dir/first-run-user.done ]]; then + "$root/bin/omarchy-done" mark first-run-user + rm -f "$state_dir/first-run-user.done" +fi + +"$root/bin/omarchy-done" mark finalize-user +rm -f "$state_dir/finalize-user.done" USER_SETUP } diff --git a/docs/file-layout.md b/docs/file-layout.md index dd103ec3..857696da 100644 --- a/docs/file-layout.md +++ b/docs/file-layout.md @@ -176,7 +176,8 @@ It only does the things `/etc/skel` can't: - On `--first-install`, marks every shipped user migration as already applied for the freshly-created user. -Idempotency marker: `~/.local/state/omarchy/finalize-user.done`. +Idempotency marker: `~/.local/state/omarchy/done/finalize-user`, managed +by `omarchy-done`. The ISO calls it as `omarchy-finalize-user --force --first-install` in the target chroot as the install user, after `omarchy-setup-system` has finished @@ -228,9 +229,17 @@ systemd instance: `install/user/first-run/wifi.sh` — welcome and Wi-Fi/update toasts (waits for a live notification server before firing). -Idempotency marker: `~/.local/state/omarchy/first-run-user.done`. On +Idempotency marker: `~/.local/state/omarchy/done/first-run-user`, managed +by `omarchy-done`. On failure the marker is not written and the failed step retries next login. +Completion markers live under `~/.local/state/omarchy/done/`. Use +`omarchy-done check ` to check one and `omarchy-done mark ` to record it. +Use `omarchy-done ensure ` as a conditional when the guarded work should +run only once; it records completion before returning success. +The Quattro upgrade moves legacy completion markers from +`~/.local/state/omarchy/` into `done/`. + ## Root-side install orchestration `omarchy-setup-system` (root, in chroot) runs target-side setup at ISO diff --git a/install/user/first-run/install-voxtype.hook b/install/user/first-run/install-voxtype.hook index 07f1882a..1f4f30d7 100644 --- a/install/user/first-run/install-voxtype.hook +++ b/install/user/first-run/install-voxtype.hook @@ -2,11 +2,10 @@ set -e -( - if [[ -n $(omarchy-notification-send -u critical -g  "Install Dictation with Voxtype" "Click to install voice dictation for Omarchy." -a) ]]; then - omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install - fi -) >/dev/null 2>&1 & - -# Remove this hook after scheduling the background notification action. -rm -f "$0" +if omarchy-done ensure voxtype-install-invitation; then + ( + if [[ -n $(omarchy-notification-send -u critical -g  "Install Dictation with Voxtype" "Click to install voice dictation for Omarchy." -a) ]]; then + omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install + fi + ) >/dev/null 2>&1 & +fi diff --git a/test/shell.d/done-test.sh b/test/shell.d/done-test.sh new file mode 100644 index 00000000..294f1aec --- /dev/null +++ b/test/shell.d/done-test.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "$0")/base-test.sh" + +test_home=$(mktemp -d) +trap 'rm -rf "$test_home"' EXIT + +done_marker="$test_home/.local/state/omarchy/done/example" + +if HOME="$test_home" "$ROOT/bin/omarchy-done" check example; then + fail "done reports an unmarked task as complete" +fi + +HOME="$test_home" "$ROOT/bin/omarchy-done" mark example +[[ -f $done_marker ]] || fail "done marks a task complete" +HOME="$test_home" "$ROOT/bin/omarchy-done" check example || fail "done reports a marked task as complete" + +HOME="$test_home" "$ROOT/bin/omarchy-done" ensure once || fail "done ensures an unmarked task" +[[ -f $test_home/.local/state/omarchy/done/once ]] || fail "done ensure marks a task complete" +if HOME="$test_home" "$ROOT/bin/omarchy-done" ensure once; then + fail "done ensures a completed task again" +fi + +if HOME="$test_home" "$ROOT/bin/omarchy-done" check ../invalid >/dev/null 2>&1; then + fail "done accepts marker path traversal" +fi + +pass "done manages completion markers" diff --git a/test/shell.d/upgrade-to-quattro-test.sh b/test/shell.d/upgrade-to-quattro-test.sh index 036d3d31..767525a5 100644 --- a/test/shell.d/upgrade-to-quattro-test.sh +++ b/test/shell.d/upgrade-to-quattro-test.sh @@ -32,6 +32,12 @@ grep -F 'skip-first-run-update-notification' "$first_run_wifi" >/dev/null grep -F '(( skip_update_notification )) && return 0' "$first_run_wifi" >/dev/null pass "Omarchy 4 upgrade suppresses the fresh-install update toast" +grep -F '"$root/bin/omarchy-done" mark first-run-user' "$upgrade_to_quattro" >/dev/null +grep -F 'rm -f "$state_dir/first-run-user.done"' "$upgrade_to_quattro" >/dev/null +grep -F '"$root/bin/omarchy-done" mark finalize-user' "$upgrade_to_quattro" >/dev/null +grep -F 'rm -f "$state_dir/finalize-user.done"' "$upgrade_to_quattro" >/dev/null +pass "Omarchy 4 upgrade migrates legacy completion markers" + grep -F 'configure_snapper_policy' "$upgrade_to_quattro" >/dev/null grep -F '/usr/share/omarchy/install/config/snapper.sh' "$upgrade_to_quattro" >/dev/null grep -F 'bash -euo pipefail "$snapper_config_script"' "$upgrade_to_quattro" >/dev/null diff --git a/test/shell.d/voxtype-invitation-test.sh b/test/shell.d/voxtype-invitation-test.sh new file mode 100644 index 00000000..9f5954f8 --- /dev/null +++ b/test/shell.d/voxtype-invitation-test.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +source "$(dirname "$0")/base-test.sh" + +test_home=$(mktemp -d) +test_bin=$(mktemp -d) +log_file=$(mktemp) +hook_path="$test_home/.config/omarchy/hooks/post-update.d/install-voxtype.hook" + +cleanup() { + rm -rf "$test_home" "$test_bin" + rm -f "$log_file" +} +trap cleanup EXIT + +mkdir -p "$(dirname "$hook_path")" + +cat >"$test_bin/omarchy-notification-send" <<'EOF' +#!/bin/bash +echo notification >>"$TEST_LOG" +echo action +EOF +chmod +x "$test_bin/omarchy-notification-send" + +cat >"$test_bin/omarchy-launch-floating-terminal-with-presentation" <<'EOF' +#!/bin/bash +echo launch >>"$TEST_LOG" +EOF +chmod +x "$test_bin/omarchy-launch-floating-terminal-with-presentation" + +run_invitation_hook() { + cp "$ROOT/install/user/first-run/install-voxtype.hook" "$hook_path" + HOME="$test_home" PATH="$test_bin:$ROOT/bin:$PATH" TEST_LOG="$log_file" bash "$hook_path" +} + +run_invitation_hook + +for _ in {1..50}; do + [[ $(wc -l <"$log_file") -eq 2 ]] && break + sleep 0.02 +done + +[[ -f $test_home/.local/state/omarchy/done/voxtype-install-invitation ]] || fail "Voxtype invitation records completion" +[[ -f $hook_path ]] || fail "Voxtype invitation keeps its hook installed" +[[ $(grep -c '^notification$' "$log_file") -eq 1 ]] || fail "Voxtype invitation sends one notification" +[[ $(grep -c '^launch$' "$log_file") -eq 1 ]] || fail "Voxtype invitation handles the notification action" + +HOME="$test_home" PATH="$test_bin:$ROOT/bin:$PATH" TEST_LOG="$log_file" bash "$hook_path" +sleep 0.05 + +[[ -f $hook_path ]] || fail "completed Voxtype invitation keeps its hook installed" +[[ $(grep -c '^notification$' "$log_file") -eq 1 ]] || fail "completed Voxtype invitation hook does not notify again" +[[ $(grep -c '^launch$' "$log_file") -eq 1 ]] || fail "completed Voxtype invitation hook does not launch again" + +pass "Voxtype invitation only runs once"