Files
omarchycn/docs/migrations.md
T
David Heinemeier HanssonandClaude Opus 5 425c3ff84d Only check for pending migrations at login
omarchy-update-user-notify.path watched /usr/share/omarchy/migrations, but
pacman writes that directory during every update, including the blessed
omarchy update, which runs omarchy-migrate a step later. The watcher fired a
critical notification for the migrations the update was already applying in
the visible terminal. A watcher cannot tell that apart from a bypassed
pacman -Syu, so the only trigger that never collides with a running update is
a once-per-login check.

The service that already ran at graphical-session.target is now the whole
mechanism, renamed after the command it runs. That is also all the second-user
case needs: markers are per-user, so anyone who did not run the update finds
them missing at their next login.

Login timing means the toast can be sent before the shell has claimed
org.freedesktop.Notifications, so the notifier waits for a live server first.
The wait is omarchy-first-run's, lifted into omarchy-notification-wait rather
than duplicated.

The package keeps omarchy-update-user-notify.service as a symlink onto the new
unit. Existing users hold an absolute wants symlink to the old path, and the
migration that repoints it only runs for users who run an update, which is the
opposite of who the notifier is for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 14:53:29 -07:00

4.0 KiB

Omarchy migrations

Omarchy migrations are one-time repair scripts for existing installs. They are used when a package update needs to change state that pacman cannot safely own by itself.

Migration model

Migrations live in:

migrations/*.sh

They run as the current Omarchy user through omarchy-migrate, normally during omarchy update. A migration may touch user/session state (~/.config, ~/.local, user systemd, browser/editor prefs, DBus/session state), and may also perform machine-wide repairs when needed.

Completion state is per-user:

~/.local/state/omarchy/migrations/<migration filename>

That means every user gets a chance to run every migration. Migrations run as the user; privileged operations should invoke the appropriate helper or privilege prompt themselves. Migrations must be idempotent: if one user already applied a machine-wide repair, the same migration running for another user should detect that and no-op.

When migrations run

During omarchy update

omarchy update is the normal update path. It runs package updates, then:

omarchy-migrate
omarchy-hook post-update

omarchy-migrate waits for any active pacman transaction to finish, then runs all pending migrations for the current user in the visible update terminal.

At login

Every graphical login starts omarchy-migrate-notify.service, which checks:

omarchy-migrate --pending

If that user has pending migrations, it shows a notification that opens a terminal for:

omarchy-migrate

The notifier never runs migrations silently in the background.

This is what covers users who did not run the update themselves: someone who bypassed the pacman guard with sudo env OMARCHY_ALLOW_DIRECT_PACMAN=1 pacman -Syu, and any second user on the machine, whose migration markers are per-user and therefore still missing after another user updated.

Login is the only trigger on purpose. Watching the packaged migration directory also fires during a normal omarchy update, which prompts for migrations that omarchy-migrate is about to run in the visible update terminal.

Manually

Users can safely run:

omarchy-migrate

at any time. Already-completed migrations are skipped.

Inspecting pending migrations

Use:

omarchy-migrate --pending

Exit behavior:

  • 0 — one or more migrations are pending
  • non-zero — no migrations are pending

Output is one pending migration per line:

1781158082.sh

Creating a migration

Use the helper:

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

This creates:

migrations/<unix timestamp>.sh

New migration format:

  • File permissions must be 0644 (-rw-r--r--). Migration runners execute them with bash -euo pipefail, not through executable bits.
  • No shebang line.
  • Start with an echo describing what the migration does.
  • Use $OMARCHY_PATH to reference the Omarchy directory.
  • Be idempotent. Check existing state before changing it.
  • Use helper commands such as omarchy-cmd-present, omarchy-cmd-missing, omarchy-pkg-add, omarchy-pkg-drop, omarchy-pkg-present, and omarchy-pkg-missing when appropriate.

Example:

echo "Relink Neovim theme to Omarchy current state"

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

[[ -L $theme_link ]] || exit 0
ln -sfn "$current_relative_target" "$theme_link"

Testing migrations

Run a migration against a temporary home when possible:

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

To rerun a migration locally, remove its marker and run the migrator:

rm ~/.local/state/omarchy/migrations/<migration>.sh
omarchy-migrate

Omarchy 4.0 is upgraded through bin/omarchy-upgrade-to-quattro, not through the normal migration runner. Do not add compatibility migrations for old installer layouts; put pre-4 package-layout transition work in the upgrade command instead.