How to Write a Migration Script in the Migrations/ Directory with the Timestamp Naming Convention

To write a migration script in Omarchy's migrations/ directory, create a file named with a Unix timestamp followed by .sh (e.g., 1788862626.sh), start with a descriptive echo line, write idempotent bash logic without a shebang, and ensure the file permissions are set to 0644.

Omarchy uses migration scripts to perform one-off repairs when package updates need to modify state that pacman cannot handle automatically. When you write a migration script in the migrations/ directory according to the omac/omarchy source code, you must follow a strict timestamp naming convention and formatting requirements so the omarchy-migrate runner can discover and execute it reliably.

Timestamp Naming Convention

Every migration file must reside in the repository's migrations/ directory and use a Unix timestamp (seconds since epoch) as its filename with the .sh extension.

For example:


migrations/1788862626.sh

This timestamp guarantees total ordering and prevents name collisions across concurrent development. The omarchy-dev-add-migration helper automates this naming by extracting the current commit date in Unix format and creating the file for you, as implemented in /bin/omarchy-dev-add-migration lines 30-36.

Required File Format and Structure

The migration runner executes scripts with bash -euo pipefail, imposing specific formatting constraints documented in agents/skills/migrations.md.

Permissions and Shebang

Migration files must have permissions set to 0644 (-rw-r--r--). The runner does not rely on executable bits. Crucially, do not include a shebang line—the migration runner supplies the interpreter explicitly.

Header and Variable Usage

Begin each script with a single echo line that briefly describes the migration's purpose. For any repository-relative paths, use the $OMARCHY_PATH variable instead of hardcoded paths.

Idempotence Requirements

All migration actions must be safe to run repeatedly. Check existing system state before modifying it so the script can be re-executed without side effects. For example, verify a symlink exists before recreating it, or check if a package is present before installing.

Preferred Helper Commands

When possible, use Omarchy helper utilities such as omarchy-cmd-present, omarchy-pkg-add, and other standardized tools for common operations rather than raw shell commands.

Practical Example

Here is a real-world migration from migrations/1788862626.sh that relinks a Neovim theme configuration:


# Example migration demonstrating idempotent symlink handling

echo "Relink Neovim theme to Omarchy current state"

theme_link="$HOME/.config/nvim/lua/plugins/theme.lua"
current_target="../../../../.local/state/omarchy/current/theme/neovim.lua"

# Exit early if the symlink does not exist (idempotent check)

[[ -L $theme_link ]] || exit 0

# Re-create the symlink atomically

ln -sfn "$current_target" "$theme_link"

This pattern ensures the script exits successfully if no action is needed and safely updates the link when required.

Step-by-Step Creation Process

While you can manually create files, the recommended workflow uses the provided helper to ensure correct naming and location.

  1. Generate the file using the helper:

    omarchy-dev-add-migration --no-edit

    This creates migrations/<unix-timestamp>.sh with the correct permissions and prints the absolute path. Omit --no-edit to open the file immediately in nvim.

  2. Add the descriptive header:

    Insert an echo line describing the migration's purpose as the first executable statement.

  3. Implement idempotent logic:

    Write bash code that checks state before modifying, using $OMARCHY_PATH for repository references and preferring Omarchy helper commands.

  4. Verify permissions:

    Ensure the file mode is 0644 (though the helper sets this by default).

  5. Test locally:

    Validate the migration against a temporary home directory to mimic the runner environment:

    HOME=$(mktemp -d) bash -euo pipefail migrations/<timestamp>.sh

    This mimics the execution environment used by omarchy-migrate as documented in agents/skills/migrations.md lines 51-58.

How Migration Execution Works

Understanding the runtime behavior helps ensure your scripts handle edge cases correctly.

  • During package updates: After omarchy update completes, omarchy-migrate runs all pending migrations for the current user.
  • At graphical login: A systemd service triggers omarchy-migrate --pending and presents a terminal if any migrations remain unexecuted.
  • Manual invocation: Users can run omarchy-migrate directly at any time; completed migrations are skipped automatically.

Completion tracking occurs per-user under ~/.local/state/omarchy/migrations/<migration-filename>, ensuring each user on a machine executes the script independently.

Summary

  • Timestamp naming: Use Unix epoch seconds with .sh extension (e.g., 1788862626.sh) in the migrations/ directory.
  • No shebang required: The runner supplies bash -euo pipefail; omit the interpreter declaration.
  • Start with echo: Begin with a descriptive echo line explaining the migration's purpose.
  • Idempotence is mandatory: Check state before modifying to allow safe re-execution.
  • Use helpers: Leverage omarchy-dev-add-migration for creation and Omarchy utility commands for operations.
  • Permissions: Set files to 0644 (readable, not executable).

Frequently Asked Questions

Why does Omarchy use Unix timestamps instead of sequential numbers for migration naming?

Unix timestamps provide a decentralized naming scheme that prevents collisions when multiple developers create migrations simultaneously without coordinating sequence numbers. The numeric value ensures chronological ordering while avoiding merge conflicts that sequential numbering (001, 002) typically creates in distributed development.

What happens if my migration script fails during execution?

The omarchy-migrate runner executes scripts with bash -euo pipefail, meaning it exits immediately on errors or undefined variables. If a migration fails, it will not be marked as complete in ~/.local/state/omarchy/migrations/, causing it to retry on the next update or login until it succeeds or is manually resolved.

Can I use languages other than Bash for migration scripts?

No. The migration runner specifically expects Bourne-again shell (bash) scripts and invokes them with explicit bash arguments. While you could theoretically call other interpreters from within the bash script, the migration file itself must be valid bash to satisfy the runner's execution model.

How do I test a migration script without affecting my live system?

Create a temporary home directory and execute the script manually with the same flags the runner uses: HOME=$(mktemp -d) bash -euo pipefail migrations/<timestamp>.sh. This isolates the migration's effects while accurately simulating the environment that omarchy-migrate provides during actual execution.

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 →