How Omarchy's Per-User Migration System Handles Version Upgrades
Omarchy handles version upgrades by running timestamped, incremental migration scripts through a dedicated omarchy-migrate command that tracks execution state and applies only pending changes per user.
When upgrading from one major version of Omarchy to another, user configurations and runtime state must evolve safely. The system in basecamp/omarchy solves this with a formal per-user migration system that applies ordered, idempotent transformations without requiring manual intervention. This article breaks down the architecture, key source files, and safety mechanisms that make Omarchy upgrades reliable.
Timestamped Migration Scripts Establish Total Ordering
All migrations live in the migrations/ directory and follow a strict naming convention: Unix timestamp plus .sh extension. For example, migrations/1785189600.sh represents a specific point in time.
This design guarantees chronological execution order. Since Unix timestamps increase monotonically, lexicographic sorting produces the correct sequence. The system never needs complex dependency management—earlier migrations naturally precede later ones.
The 1785189600.sh migration demonstrates a typical transformation: it removes legacy tmux alert hooks and cleans up the Omarchy widget configuration. Each script is a standalone Bash file that can be inspected, tested, and reviewed independently.
The omarchy-migrate Command: Core Discovery and Execution
The bin/omarchy-migrate script serves as the migration engine. Its responsibilities include:
- Scanning
migrations/for scripts newer than the last-run marker - Executing pending scripts with
bash -euo pipefailfor strict error handling - Updating
~/.local/share/omarchy/migration-stateafter each successful run
The strict mode flags (-e exit on error, -u treat unset variables as errors, -o pipefail propagate pipeline failures) ensure that any migration failure halts execution. This prevents partial state corruption.
The state marker prevents re-execution. Once 1785189600.sh completes successfully, its timestamp is recorded. Subsequent runs skip it automatically.
Integration with the Upgrade Flow
The upgrade script bin/omarchy-upgrade-to-quattro orchestrates the full version transition. After installing new packages, it invokes migrations via the run_post_upgrade_migrations function:
run_post_upgrade_migrations() {
PATH="$package_path" command -v omarchy-migrate >/dev/null 2>&1 || return 0
log "Running Omarchy migrations"
if ! run_as_user_omarchy OMARCHY_UPGRADE_TO_QUATTRO_LIVE=1 omarchy-migrate; then
warn "Could not run Omarchy migrations; the user may be prompted to run them after login."
fi
}
Key integration points visible in this source:
- Conditional execution: Returns silently if
omarchy-migrateis unavailable - User context switching:
run_as_user_omarchyensures migrations run as the target user, not root - Environment signaling:
OMARCHY_UPGRADE_TO_QUATTRO_LIVE=1informs the migration system that modifying live state is safe - Graceful degradation: Warns but continues if migrations cannot run immediately
Idempotent Design Patterns in Migration Scripts
Each migration script is engineered for safe re-execution. Common patterns found in migrations/1785189600.sh and similar files include:
- Existence checks before file modification:
[[ -f "$file" ]]guards against operating on missing paths - Content comparison with
cmp -sto avoid unnecessary writes - Surgical removal of only Omarchy-specific entries, preserving user customizations
The tmux migration specifically:
- Edits
~/.config/tmux/tmux.confonly when the file exists - Uses
awkto preserve non-Omarchy configuration lines - Targets only
omarchy-tmux-alerthook entries in the running tmux server
This precision ensures that re-running the migration produces no changes after the first successful application.
Testing Infrastructure for Migration Correctness
Every migration ships with dedicated tests under test/shell.d/. The harness:
- Sets up isolated, temporary user environments
- Executes migrations directly with the same strict flags (
bash -euo pipefail) - Asserts expected file system changes
- Verifies idempotency by running the migration twice
The test/shell.d/tmux-alert-removal-migration-test.sh validates 1785189600.sh behavior:
#!/usr/bin/env bash
migration=$(grep -rl 'Remove the tmux alert hooks' "$ROOT/migrations" | head -n1)
HOME="$test_home" bash "$migration" >/dev/null
# Verify that the tmux config no longer contains Omarchy hook lines
grep -q 'omarchy-tmux-alert' "$test_home/.config/tmux/tmux.conf" && fail "hook still present"
pass "tmux hooks removed"
The test/shell.d/upgrade-to-quattro-test.sh file verifies end-to-end integration: that post-upgrade migrations trigger correctly during version jumps.
Manual Migration Operations
While the upgrade flow runs migrations automatically, administrators can invoke omarchy-migrate directly for debugging or forced re-application:
# List pending migrations without executing
omarchy-migrate --list
# Apply all pending migrations
omarchy-migrate
# Run a specific migration by timestamp
omarchy-migrate --run 1785189600
Migration Script Skeleton
New migrations follow this structure:
#!/usr/bin/env bash
set -euo pipefail
# Example: remove a legacy config key
legacy_conf="$HOME/.config/example.conf"
if [[ -f $legacy_conf ]]; then
tmp=$(mktemp)
grep -v '^legacy_key=' "$legacy_conf" >"$tmp"
mv "$tmp" "$legacy_conf"
fi
The set -euo pipefail header is mandatory per the project's strict error-handling standards.
Summary
- Timestamped filenames in
migrations/provide automatic chronological ordering without dependency management bin/omarchy-migratehandles discovery, execution, and state tracking via~/.local/share/omarchy/migration-statebin/omarchy-upgrade-to-quattrointegrates migrations into the upgrade flow with user context switching and environment signaling- Strict execution mode (
bash -euo pipefail) and idempotent design patterns prevent partial state corruption - Test coverage under
test/shell.d/validates each migration's correctness and re-runnability
Frequently Asked Questions
How does Omarchy prevent the same migration from running twice?
The migration system writes a state marker to ~/.local/share/omarchy/migration-state after each successful script execution. On subsequent runs, omarchy-migrate compares migration timestamps against this marker and skips any that have already been applied. This state is stored per-user, so different users on the same system migrate independently.
What happens if a migration script fails?
The bash -euo pipefail strict mode causes immediate termination on any error. The run_post_upgrade_migrations wrapper in bin/omarchy-upgrade-to-quattro catches this, logs a warning, and continues the upgrade process. The failed migration remains unmarked, so it will be retried on the next invocation—either automatically during a future upgrade or manually by the user.
Can migrations be run outside of the automatic upgrade flow?
Yes. The omarchy-migrate command supports manual invocation with flags like --list and --run <timestamp>. Administrators can also execute individual migration scripts directly with bash, though this bypasses state tracking and should be used only for debugging or recovery scenarios.
Why use Unix timestamps instead of sequential numbers for migration names?
Unix timestamps provide conflict-free distributed creation. Multiple developers can write migrations concurrently without coordinating to avoid sequence number collisions. Timestamps also encode when each migration was authored, which aids debugging and audit trails. The lexicographic sort of timestamps matches chronological order, so no complex parsing is required.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →