Unify Omarchy migrations

This commit is contained in:
Ryan Hughes
2026-06-12 13:49:46 -04:00
parent 04b0fab64d
commit c582e7133a
27 changed files with 259 additions and 681 deletions
+18 -31
View File
@@ -63,12 +63,11 @@ default/libalpm/hooks/*.hook
──► omarchy /usr/share/libalpm/hooks/*.hook
install/** ──► omarchy /usr/share/omarchy/install/
migrations/system/** ──► omarchy /usr/share/omarchy/migrations/system/
migrations/user/** ──► omarchy /usr/share/omarchy/migrations/user/
migrations/** ──► omarchy /usr/share/omarchy/migrations/
themes/** ──► omarchy /usr/share/omarchy/themes/
shell/** ──► omarchy /usr/share/omarchy/shell/
version ──► omarchy /usr/share/omarchy/version
+ /etc/skel/.local/state/omarchy/migrations/user/*
+ /etc/skel/.local/state/omarchy/migrations/*
config/** ──► omarchy-settings /etc/skel/.config/** (seeds new users)
/usr/share/omarchy/config/** (resync source)
@@ -185,37 +184,26 @@ the root-side work.
See [`migrations.md`](migrations.md) for the full migration model, authoring
guidelines, and troubleshooting notes.
Omarchy migrations are split by scope:
- `migrations/system/*.sh` — root, noninteractive, safe to run from pacman.
The `omarchy` package `post_upgrade()` runs `omarchy-migrate-system` after an
`omarchy` package upgrade, so even explicit direct-pacman bypasses still get
system migrations applied. Completion state lives in
`/var/lib/omarchy/migrations/system/`.
- `migrations/user/*.sh` — current user/session, may be interactive, and may
touch `~/.config`, `~/.local`, user systemd, DBus, browser prefs, etc.
Completion state lives in `~/.local/state/omarchy/migrations/user/`.
Package upgrades happen as root, but user migrations must run as each user and
may be interactive. There is no root-owned "user migration required" marker. A
user migration is pending only when a script exists in `migrations/user/` and
that user's matching state file is missing.
Omarchy migrations live in `migrations/*.sh` and run per-user through
`omarchy-migrate`. Completion state lives in
`~/.local/state/omarchy/migrations/`, so every user gets a chance to run every
migration. Migrations run as the user; privileged work should invoke the
appropriate helper or privilege prompt. Migrations must be idempotent;
machine-wide repairs should no-op when another user already applied them.
Each graphical user has `omarchy-update-user-notify.path` watching the packaged
user migration directory. When that directory changes, or when the path unit is
migration directory. When that directory changes, or when the path unit is
started on login, `omarchy-update-user-notify.service` runs
`omarchy-migrate-notify` as that user. The notifier checks
`omarchy-migrate --pending user`. If this user has missing migration state, it
shows a notification that opens a terminal for `omarchy-migrate`. The notifier
never runs user migrations in the background.
`omarchy-migrate --pending`. If this user has missing migration state, it shows a
notification that opens a terminal for `omarchy-migrate`. The notifier never runs
migrations in the background.
`omarchy-migrate` waits for any active pacman transaction to finish, runs
pending system migrations, then runs pending user migrations. It does not need
`--force`; migrations happen when state files are missing. For watchers and
diagnostics, `omarchy-migrate --pending [all|system|user]` prints
scope-prefixed pending migration filenames and exits `0` when any are pending.
`omarchy update` runs `omarchy-migrate` after the package transaction in the
already-visible update terminal, then runs `omarchy-hook post-update`.
`omarchy-migrate` waits for any active pacman transaction to finish, then runs
pending migrations. It does not need `--force`; migrations happen when state
files are missing. `omarchy update` runs `omarchy-migrate` after the package
transaction in the already-visible update terminal, then runs
`omarchy-hook post-update`.
## First-run (`omarchy-first-run`)
@@ -286,8 +274,7 @@ return to the packaged default.
| Per-user file that's static but lives outside `~/.config` | `default/`, then add `install -Dm644 ... $pkgdir/etc/skel/...` in `omarchy-settings` PKGBUILD |
| Runtime tweak that needs `$HOME` or live system state | extend `omarchy-finalize-user`, or add a per-user leaf under `install/user/` and wire into `install/user/all.sh` |
| One-time root-side setup step | `install/config/*.sh` or `install/hardware/*.sh`, wire into `omarchy-setup-system` or `install/hardware/all.sh` |
| Noninteractive root fix for existing installs | `migrations/system/<unix-timestamp>.sh` |
| User/session fix for existing installs | `migrations/user/<unix-timestamp>.sh` |
| One-time fix for existing installs | `migrations/<unix-timestamp>.sh` |
| User-facing `omarchy-*` command | `bin/omarchy-<group>-<verb>` — see `GROUP_DESCRIPTIONS` in `bin/omarchy` |
| New stock theme | `themes/<name>/` (+ matching templates under `default/themed/` if they need theme colors) |
| User-installed theme | `~/.config/omarchy/themes/<name>/` |
+58 -208
View File
@@ -4,59 +4,30 @@ 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.
## The two migration scopes
## Migration model
Omarchy migrations are split by execution context:
Migrations live in:
```text
migrations/system/*.sh
migrations/user/*.sh
migrations/*.sh
```
### System migrations
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.
System migrations run as root and must be noninteractive.
Use system migrations for machine-wide state, for example:
- `/etc` drop-ins or legacy config cleanup
- `/boot` / bootloader cleanup
- systemd system units or service state
- hardware quirks
- package-owned layout transitions
Note that it's not necessary to write a migration for any file that will be package-owned.
State lives in:
Completion state is per-user:
```text
/var/lib/omarchy/migrations/system/<migration filename>
~/.local/state/omarchy/migrations/<migration filename>
```
### User migrations
User migrations run as the current graphical/login user and may be interactive.
Use user migrations for per-user or session-aware state, for example:
- `~/.config` changes
- `~/.local` state
- user systemd units
- browser/editor preferences
- DBus/session-dependent updates
- prompts that need to happen in a visible terminal
State lives in:
```text
~/.local/state/omarchy/migrations/user/<migration filename>
```
A user migration is pending for a given user only when the script exists under
`/usr/share/omarchy/migrations/user/` and that user's matching state file is
missing.
There is no global root-owned “user migrations required” queue.
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
@@ -69,15 +40,8 @@ omarchy-migrate
omarchy-hook post-update
```
`omarchy-migrate`:
1. waits for any active pacman transaction to finish
2. runs pending system migrations
3. runs pending user migrations for the current user
System migrations usually already ran during the pacman transaction via the
`omarchy` package `post_upgrade()`, but the second check is intentional and
idempotent.
`omarchy-migrate` waits for any active pacman transaction to finish, then runs
all pending migrations for the current user in the visible update terminal.
### During direct pacman updates
@@ -87,28 +51,21 @@ Raw `sudo pacman -Syu` is guarded. Users should normally run:
omarchy update
```
If a user explicitly bypasses the guard, the `omarchy` package still runs system
migrations from `post_upgrade()`:
If a user explicitly bypasses the guard, user sessions watch the packaged
migration directory and run a notifier. The notifier checks:
```bash
omarchy-migrate-system
omarchy-migrate --pending
```
User migrations are never run by pacman. Instead, the user session watches the
packaged user migration directory and runs a notifier. The notifier checks:
```bash
omarchy-migrate --pending user
```
If that user has pending user migrations, it shows a notification that opens a
If that user has pending migrations, it shows a notification that opens a
terminal for:
```bash
omarchy-migrate
```
The notifier never runs user migrations silently in the background.
The notifier never runs migrations silently in the background.
### Manually
@@ -126,179 +83,72 @@ Use:
```bash
omarchy-migrate --pending
omarchy-migrate --pending system
omarchy-migrate --pending user
```
Exit behavior:
- `0` — one or more matching migrations are pending
- non-zero — no matching migrations are pending
- `0` — one or more migrations are pending
- non-zero — no migrations are pending
Output is scope-prefixed:
Output is one pending migration per line:
```text
system/100-system.sh
user/200-user.sh
1781158082.sh
```
The lower-level runners also support `--pending` and print filenames without the
scope prefix:
## Creating a migration
Use the helper:
```bash
omarchy-migrate-system --pending
omarchy-migrate-user --pending
omarchy-dev-add-migration --no-edit
```
Prefer `omarchy-migrate --pending ...` in user-facing tools and watchers.
This creates:
## Writing migrations
Create a migration with:
```bash
omarchy-dev-add-migration system --no-edit
omarchy-dev-add-migration user --no-edit
```text
migrations/<unix timestamp>.sh
```
Choose the scope based on the state being changed, not on convenience.
New migration format:
### File 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.
Migrations are plain shell files:
- filename: unix timestamp, generated by `omarchy-dev-add-migration`
- mode: `0644`
- no shebang
- start with an `echo` describing the migration
- use `$OMARCHY_PATH` for Omarchy-owned files
- should be idempotent
- should be as simple as possible to accomplish the task
Example system migration:
Example:
```bash
echo "Remove legacy Limine config"
echo "Relink Neovim theme to Omarchy current state"
rm -f /boot/EFI/limine/limine.conf
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"
```
Example user migration:
## Testing migrations
Run a migration against a temporary home when possible:
```bash
echo "Refresh user app launchers"
omarchy-refresh-applications
HOME=$(mktemp -d) bash -euo pipefail migrations/<timestamp>.sh
```
### Execution model
Migration runners execute scripts with strict shell settings:
```bash
bash -euo pipefail <migration>
```
`$OMARCHY_PATH` is exported into the migration process.
A migration is marked complete only after the script exits successfully. If it
fails, the marker is not written and the migration will be retried later.
Because failed migrations can be retried after doing partial work, migrations
must be idempotent. Prefer operations that are safe to run more than once:
```bash
mkdir -p ~/.config/example
rm -f ~/.config/example/legacy.conf
install -Dm644 "$OMARCHY_PATH/default/example.conf" ~/.config/example/example.conf
systemctl --user enable --now some-unit.service || true
```
Use `|| true` only when a failure is genuinely acceptable.
## What belongs in a migration?
Good migration uses:
- deleting or moving legacy files that block the packaged layout
- marking or enabling new service state
- migrating old config paths to new config paths
- repairing known bad state from an earlier Omarchy release
- one-time compatibility for existing installs
Bad migration uses:
- fresh-install setup that belongs in `install/` or `/etc/skel`
- package installation that belongs in package dependencies or package lists
- recurring maintenance
- arbitrary cleanup that should be a user command
- pre-4 upgrade work that belongs in `omarchy-upgrade-to-4`
- hidden user/session work from pacman
## Fresh installs and new users
Fresh users created from the packaged layout should not have to run historical
user migrations. The package seeds user migration markers into `/etc/skel` so
new users start with shipped user migrations marked complete.
The ISO/finalization path also marks shipped user migrations complete for the
freshly-created install user when running:
```bash
omarchy-finalize-user --first-install
```
Existing users keep their own migration state and run only migrations whose
state files are missing.
## Troubleshooting
### See what is pending
```bash
omarchy-migrate --pending
```
### Retry failed migrations
Fix the underlying problem, then run:
To rerun a migration locally, remove its marker and run the migrator:
```bash
rm ~/.local/state/omarchy/migrations/<migration>.sh
omarchy-migrate
```
A failed migration is not marked complete, so it will retry.
### Re-run a completed migration manually
Remove its marker, then run the migration command again:
```bash
rm ~/.local/state/omarchy/migrations/user/<migration>.sh
omarchy-migrate
```
For system migrations:
```bash
sudo rm /var/lib/omarchy/migrations/system/<migration>.sh
omarchy-migrate
```
Be careful: migrations should be idempotent, but manually removing markers is an
advanced troubleshooting step.
## Agent checklist
Before adding a migration:
1. Is this a one-time repair for existing installs?
2. Is it system state or user/session state?
3. Can it run safely more than once?
4. Will it fail loudly if the important work fails?
5. Does it avoid hidden interactive work from pacman?
6. Does it belong in `omarchy-upgrade-to-4` instead?
7. Did you add or update tests if behavior changed?
If the answer to any of these is unclear, stop and ask before adding the
migration.
Omarchy 4.0 is upgraded through `bin/omarchy-upgrade-to-4`, 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.
+40 -104
View File
@@ -10,13 +10,12 @@ bypass it:
The design goal is:
- System/root migrations should run automatically from package transactions when
safe.
- User/session migrations should never run invisibly from pacman because they may
need `$HOME`, DBus/session state, a graphical session, or user interaction.
- Users who bypass `omarchy update` should still get system migrations from
pacman and should be notified only when their own user migration state is
missing a shipped user migration.
- `omarchy update` owns the visible update pipeline: package transaction,
migrations, post-update hooks, update-state refresh, and restart checks.
- Migrations run per-user after pacman finishes, because they may need `$HOME`,
DBus/session state, a graphical session, sudo, or user interaction.
- Users who bypass `omarchy update` are nudged back by the pacman guard; if they
explicitly bypass it, their session is notified when migrations are pending.
## State and coordination files
@@ -26,8 +25,7 @@ The design goal is:
| `/tmp/omarchy-update.log` | user | Transcript of `omarchy update`, used by `omarchy-update-analyze-logs`. |
| `~/.local/state/omarchy/updates/` | user | Update-check state for the shell widget (`packages`, `aur`, `available`, `checked-at`, `error`). |
| `~/.local/state/omarchy/current/` | user | Generated active theme, selected theme name, and current background symlink. |
| `/var/lib/omarchy/migrations/system/` | root | System migration markers. |
| `~/.local/state/omarchy/migrations/user/` | user | User migration markers. |
| `~/.local/state/omarchy/migrations/` | user | Per-user migration markers. |
| `~/.local/state/omarchy/reboot-required` | user | Optional reboot marker checked by `omarchy-update-restart`. |
| `~/.local/state/omarchy/restart-*-required` | user | Optional service/app restart markers checked by `omarchy-update-restart`. |
@@ -36,83 +34,32 @@ The design goal is:
See [`migrations.md`](migrations.md) for the full migration model, authoring
guidelines, and troubleshooting notes.
Migrations are scoped by directory:
Migrations live in:
```text
migrations/system/*.sh
migrations/user/*.sh
migrations/*.sh
```
### System migrations
System migrations are root-owned, noninteractive fixes for existing installs.
They may touch `/etc`, `/usr`, `/boot`, system services, hardware configuration,
and other machine-wide state.
Runner:
```bash
omarchy-migrate-system
```
Completion state:
```text
/var/lib/omarchy/migrations/system/<migration filename>
```
Pacman integration:
```text
pkgbuilds/omarchy/omarchy.install post_upgrade()
```
The `omarchy` package calls `omarchy-migrate-system` from `post_upgrade()`.
The runner is idempotent, so repeated calls only check the state files.
### User migrations
User migrations are current-user/session fixes. They may touch `~/.config`,
`~/.local`, user systemd units, browser/editor preferences, DBus/session state,
or ask questions.
Runner:
```bash
omarchy-migrate-user
```
Public command:
They run as the current user through:
```bash
omarchy-migrate
```
Completion state:
Completion state is per-user:
```text
~/.local/state/omarchy/migrations/user/<migration filename>
~/.local/state/omarchy/migrations/<migration filename>
```
A user migration is pending only when a script exists in `migrations/user/` and
that user's matching state file is missing. There is no global root-owned queue
for user migrations.
Every user gets a chance to run every migration. Migrations run as the user;
privileged work should invoke the appropriate helper or privilege prompt.
Migrations must be idempotent; if one user already applied a machine-wide repair,
the migration should no-op for other users.
`omarchy-migrate` is the public command. It waits for any active pacman
transaction to finish, runs pending system migrations, then runs pending user
migrations. It does not need `--force`; migrations should happen when they are
pending.
For watchers and diagnostics, `omarchy-migrate --pending [all|system|user]`
prints pending migration names and exits `0` when any are pending. Output is
scope-prefixed, for example:
```text
system/100-system.sh
user/200-user.sh
```
When no matching migrations are pending, it prints nothing and exits non-zero.
For watchers and diagnostics, `omarchy-migrate --pending` prints pending
migration names and exits `0` when any are pending. When no migrations are
pending, it prints nothing and exits non-zero.
## Raw pacman guard
@@ -187,10 +134,8 @@ omarchy-update
Important behavior:
- `omarchy update` checks/runs system and user migrations in the same visible
terminal via `omarchy-migrate`.
- `omarchy update` still benefits from pacman-triggered system migrations
because the system migration hook runs as part of the pacman transaction.
- `omarchy update` checks/runs migrations in the same visible terminal via
`omarchy-migrate` after pacman finishes.
- A failure should leave enough output in `/tmp/omarchy-update.log` and the
terminal transcript to debug.
@@ -202,10 +147,9 @@ High-level flow:
sudo pacman -Syu
├─ pre-transaction guard aborts and tells the user to run omarchy update
└─ if explicitly bypassed, upgrades omarchy and related packages
├─ omarchy post_upgrade runs omarchy-migrate-system as root
└─ user session notices migration directory changes
├─ omarchy-update-user-notify.path triggers, if enabled
├─ omarchy-migrate-notify checks omarchy-migrate --pending user
├─ omarchy-migrate-notify checks omarchy-migrate --pending
├─ if this user has missing migration state, show notification
└─ click opens terminal: omarchy-migrate
```
@@ -215,12 +159,11 @@ Fallbacks:
- `omarchy-first-run` enables the user notification path unit.
- `omarchy-first-run` also invokes `omarchy-migrate-notify` on graphical
startup, so users who updated before the path unit existed still get prompted
if they have missing user migration state.
- The notifier is only a prompt. It does not run user migrations in the
background.
if they have missing migration state.
- The notifier is only a prompt. It does not run migrations in the background.
- Direct pacman updates do not run `omarchy-hook post-update` unless the user
explicitly runs that hook; without a package-update marker, the only user-side
pending state we can derive is missing user migration markers.
explicitly runs that hook; without a package-update marker, the only pending
state we can derive is missing per-user migration markers.
## Shell update indicator
@@ -265,11 +208,9 @@ scripts.
| `omarchy-update-confirm` | Gum confirmation copy for `omarchy update`. | **Question.** Could be inlined into `omarchy-update`; separate file only helps keep copy isolated. |
| `omarchy-update-keyring` | Ensures Omarchy keyring and Arch keyring are current before the main transaction. | **Keep, but review.** It uses targeted `pacman -Sy` for keyring bootstrapping; acceptable for this special case but should remain tightly scoped. |
| `omarchy-update-system-pkgs` | Runs `sudo env OMARCHY_UPDATE_PACMAN=1 pacman -Syu --noconfirm` with targeted transition `--overwrite` entries so the ALPM guard allows the transaction and early package-layout conflicts are handled. | **Keep for now.** Small leaf command, clear/testable. |
| `omarchy-migrate-system` | Runs root/system migrations from `migrations/system`. Called by `omarchy` package `post_upgrade()` and by `omarchy-migrate` when system migration state is missing. | **Keep.** This is the important direct-`pacman -Syu` integration. |
| `omarchy-migrate-user` | Checks `migrations/user` against this user's state and runs only missing user migrations. Supports `--pending` and prints pending filenames. | **Keep internal.** Public users should generally run `omarchy-migrate`. |
| `omarchy-migrate` | Public migration command. Waits for pacman, then runs pending system and user migrations. Supports `--pending [all|system|user]` and prints scope-prefixed pending filenames. | **Keep.** This replaces the discarded `omarchy-update-user-finalize` name and no longer needs `--force`. |
| `omarchy-migrate` | Public migration command. Waits for pacman, then runs all pending migrations for the current user. Supports `--pending`. | **Keep.** This replaces the discarded `omarchy-update-user-finalize` name and no longer needs `--force`. |
| `omarchy-update-pacman-guard` | ALPM pre-transaction guard that aborts direct `pacman -Syu` style upgrades unless Omarchy set `OMARCHY_UPDATE_PACMAN=1` or the user explicitly set `OMARCHY_ALLOW_DIRECT_PACMAN=1`. | **Keep internal/hidden.** This is what nudges users back to `omarchy update`. |
| `omarchy-migrate-notify` | Internal notification helper for direct pacman updates. Uses `omarchy-migrate --pending user` and shows notification only when this user has pending migrations. | **Keep internal/hidden.** Clear name now that the public command is `omarchy-migrate`. |
| `omarchy-migrate-notify` | Internal notification helper for direct pacman updates. Uses `omarchy-migrate --pending` and shows notification only when this user has pending migrations. | **Keep internal/hidden.** Clear name now that the public command is `omarchy-migrate`. |
| `omarchy-update-user-notify` | Hidden compatibility wrapper for `omarchy-migrate-notify`. | **Temporary.** Keep only for old callers. |
| `omarchy-update-available` | Update checker for shell widget and post-update refresh. Writes update state. | **Keep.** Could eventually be renamed `omarchy-update-check`, but current name matches widget semantics. |
| `omarchy-update-aur-pkgs` | Updates AUR packages with `yay -Sua` if foreign packages exist and AUR is reachable. | **Question.** Omarchy is package-backed now, but users may still install AUR packages. Keep for now. |
@@ -282,36 +223,31 @@ scripts.
## Closed decisions
1. **System migrations run from the `omarchy` package**
- `omarchy.install post_upgrade()` calls `omarchy-migrate-system`.
- We do not need a separate system-migration ALPM hook from
`omarchy-settings`.
1. **Migrations run per-user from the update pipeline**
- `omarchy update` runs `omarchy-migrate` after pacman finishes.
- Package-time migration runners do not apply migrations inside pacman.
- Every user has per-user migration markers, and migrations must be
idempotent when they repair machine-wide state.
2. **Root vs user migrations stay separate**
- Do not use `su {user}` from pacman to run user migrations.
- User migrations need the user's real session and should be visible.
- Pending user work is determined only by comparing `migrations/user/*.sh`
against `~/.local/state/omarchy/migrations/user/`.
3. **Migration notification naming**
2. **Migration notification naming**
- The real helper is `omarchy-migrate-notify`.
- `omarchy-update-user-notify` remains only as a hidden compatibility wrapper.
4. **Update pipeline ownership**
3. **Update pipeline ownership**
- `omarchy-update` owns the full update pipeline now.
- `omarchy-update-perform` is only a hidden compatibility wrapper for
`omarchy-update -y`.
5. **Mise remains in the blessed update path**
4. **Mise remains in the blessed update path**
- `omarchy-update-mise` intentionally runs as part of `omarchy update`.
6. **Orphan cleanup stays in the update path for now**
5. **Orphan cleanup stays in the update path for now**
- It is prompt-only and never removes packages noninteractively.
7. **Direct pacman user follow-up is based on actual migration state**
6. **Direct pacman user follow-up is based on actual migration state**
- Direct `sudo pacman -Syu` no longer uses a fake user-update marker.
- User notifications are shown only when `omarchy-migrate --pending user`
finds missing per-user migration state.
- User notifications are shown only when `omarchy-migrate --pending` finds
missing per-user migration state.
## Remaining concerns