Understanding the Omarchy Migration Structure: A Per-User Shell-Based Framework

Omarchy's migration system uses timestamp-prefixed shell scripts stored in migrations/ that execute in ascending order per user, tracking completion via zero-byte marker files in ~/.local/state/omarchy/migrations/ to ensure idempotent, safe environment evolution across updates.

The omacom/omarchy project implements a robust, file-based migration framework designed to evolve user environments safely without requiring privileged access. Unlike traditional database migration systems, Omarchy's approach treats shell scripts as versioned artifacts that run per-user, ensuring consistent configuration states across package updates and system changes.

Migration Directory Layout and Naming Conventions

All migration scripts live in the repository under the migrations/ directory and follow a strict naming convention that determines execution order.

Source Location and Installation

The source files reside in migrations/**/*.sh within the repository. At runtime, these scripts install to /usr/share/omarchy/migrations/, making them available system-wide while keeping the execution model user-centric. This separation allows administrators to ship updates via the repository while maintaining per-user state tracking.

Timestamp-Based Ordering

Scripts must use a Unix-timestamp prefix to define global execution order. For example, migrations/1784960000.sh runs before migrations/1784960001.sh. The omarchy-migrate command scans the installed directory and sorts scripts by filename to determine the sequence, ensuring migrations run strictly in ascending chronological order regardless of installation timing.

How the omarchy-migrate Command Executes

The core migration logic resides in bin/omarchy-migrate, which orchestrates discovery, execution, and state management automatically.

Execution Flow

The command performs three critical steps:

  1. Discovery: Scans /usr/share/omarchy/migrations/ for all *.sh files
  2. Comparison: Checks each script's basename against the user's state directory at ~/.local/state/omarchy/migrations/
  3. Execution: Runs pending scripts with bash -euo pipefail to enable strict error handling

This process triggers automatically after package updates and on user login via the omarchy-migrate-notify.service systemd unit, ensuring environments stay current without manual intervention.

Marker File Creation

Upon successful completion, the system creates a zero-byte marker file in the state directory matching the script's basename. For example, completing 1784960000.sh creates ~/.local/state/omarchy/migrations/1784960000.sh. These markers persist across sessions, preventing re-execution of already-applied migrations while allowing new users to run the full sequence on first login.

Per-User State Tracking and Idempotency

The migration model explicitly requires every script to be idempotent, as documented in agents/skills/migrations.md. This design ensures safety when re-running partially completed sequences or when users manually trigger migrations multiple times.

Writing Idempotent Scripts

Each migration must verify state before modifying the system. The recommended pattern checks for the existence of the marker file or the desired end state before executing privileged operations:

#!/usr/bin/env bash

# Guard against re-running

[[ -e "$HOME/.local/state/omarchy/migrations/$(basename "$0")" ]] && exit 0

# Example: Install package only if missing

if ! pacman -Qs lsp-plugins-lv2 > /dev/null; then
  omarchy-pkg-add lsp-plugins-lv2
fi

Privilege Delegation

Migrations run as the regular user account, never as root. Any operation requiring elevated privileges must delegate to helper commands such as omarchy-pkg-add, sudo, or omarchy-cmd-present. This constraint prevents accidental system-wide damage while allowing necessary configuration changes through controlled interfaces.

Error Handling and Execution Safety

The omarchy-migrate command implements strict failure semantics to prevent partial migration states.

Queue Stopping on Failure

If any migration exits with a non-zero status, the entire queue halts immediately. According to the implementation in bin/omarchy-migrate at line 129, the process aborts without marking subsequent migrations as completed. This guarantees that a broken migration never leaves the system in a partially-migrated state where later scripts depend on earlier, failed changes.

Strict Shell Options

All migrations execute with bash -euo pipefail, which causes immediate termination on:

  • Non-zero exit codes (-e)
  • Unset variables (-u)
  • Pipeline failures (-o pipefail)

These options enforce defensive programming practices, catching errors early before they can corrupt user configuration.

Creating Custom Migrations

Developers and advanced users can extend Omarchy by adding new scripts to the migrations/ directory following the established conventions.

Template Structure

Save new migrations with the current timestamp to ensure proper ordering:

#!/usr/bin/env bash

# Template for migrations/$(date +%s).sh

# All migrations must be idempotent.

# Check if already applied

state_dir="$HOME/.local/state/omarchy/migrations"
[[ -e "$state_dir/$(basename "$0")" ]] && exit 0

# Perform user-land configuration

conf="/etc/vconsole.conf"
if [[ -f "$conf" && "$(grep -E '^KEYMAP=' "$conf")" != 'KEYMAP=us' ]]; then
  sudo sed -i 's/^KEYMAP=.*/KEYMAP=us/' "$conf"
fi

# The migration runner creates the marker file automatically on success

Testing Migrations Locally

Use the omarchy-migrate command with flags to preview and test changes:


# Show pending migrations without executing

omarchy-migrate --pending

# Execute all pending migrations

omarchy-migrate

Summary

  • Timestamp Naming: Use Unix timestamps (e.g., 1784960000.sh) in migrations/ to define execution order
  • Per-User State: Marker files in ~/.local/state/omarchy/migrations/ track completion per account
  • Automatic Execution: The omarchy-migrate command runs via systemd service after updates and login
  • Idempotency Required: Scripts must verify state before changing; privileged work delegates to helpers
  • Strict Failure Handling: Non-zero exits abort the queue immediately, preventing partial states

Frequently Asked Questions

How do I create a new migration in Omarchy?

Create a shell script in the migrations/ directory with a Unix timestamp prefix (e.g., migrations/$(date +%s).sh). The script must be idempotent, check for existing marker files, and delegate privileged operations to commands like omarchy-pkg-add or sudo. Follow the guidelines in agents/skills/migrations.md to ensure compatibility with the execution framework.

Where does Omarchy store migration state?

Omarchy stores state in the user-specific directory ~/.local/state/omarchy/migrations/. Each completed migration creates a zero-byte marker file matching the script's basename (e.g., 1784960000.sh). The omarchy-migrate command compares these markers against scripts in /usr/share/omarchy/migrations/ to determine which migrations remain pending.

What happens if a migration fails?

If a migration exits with a non-zero status, omarchy-migrate immediately aborts the entire queue and does not mark subsequent migrations as completed. This prevents the system from entering a partially-migrated state where later scripts might depend on failed earlier operations. Fix the failing script and re-run omarchy-migrate to resume from the failure point.

Do migrations run as root or regular user?

Migrations run as the regular user account, not root. This security model requires that any privileged system changes use delegation commands such as omarchy-pkg-add, sudo, or omarchy-cmd-present rather than direct root execution. The design ensures that user-specific configurations remain isolated while still allowing necessary system modifications through controlled interfaces.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →