The orchestrator only runs on the live ISO, so it lives in omarchy-iso
where the rest of the ISO-specific code (configurator, .automated_script
.sh, archinstall pacman config) lives. omarchy-installer stays the
monorepo of Omarchy source content (configs, themes, default files,
install scripts) without a Python install driver mixed in.
bin/omarchy-install simplifies to a one-liner: it's the online entry
point now, no --config dispatcher. The orchestrator never invokes it;
it runs finalize.sh directly via arch-chroot.
Chunk 1 of the C3 refactor: replace the two-pass install model (archinstall
CLI + custom shell dance + chroot bash installer) with a single Python
orchestrator that owns phase ordering, using archinstall as a library
subsystem.
Splits the bash entry point into two:
- install.sh: online entry point. Ensures omarchy runtime + base.packages
are installed/up to date, then exec's finalize.sh.
- finalize.sh: the in-target portion (preflight + packaging + config +
login + post-install). Called by install.sh (online) AND by the
orchestrator after arch-chroot -u $USER (offline).
Adds the orchestrator skeleton under install/orchestrator/:
- main.py: entry point, builds + runs the phase list
- context.py: InstallContext (parsed configurator JSON + invocation paths)
- phases.py: phase state machine (logging + state.json + error wrapping)
- phases_impl.py: stubbed phase implementations (filled in by chunks 2-6)
- archinstall_adapter.py: thin compat wall around archinstall lib imports
(only this module imports from archinstall.*)
- ui.py: gum subprocess wrappers so the orchestrator keeps the same
styled-terminal UX as the bash installer
Updates bin/omarchy-install to dispatch:
- --config <json> in args → python -m orchestrator.main (ISO install)
- anything else → bash install.sh (online rerun on installed system)
Concrete phase logic lands in subsequent chunks. All phases currently
raise NotImplementedError; the orchestrator imports cleanly and --help
works as a smoke check.
The omarchy install runs inside an arch-chroot to /mnt, so its log file
(/var/log/omarchy-install.log from the installer's perspective) is at
/mnt/var/log/omarchy-install.log from the live ISO. The previous script
only read /var/log/omarchy-install.log, and 'cat $ARCHINSTALL_LOG
$OMARCHY_LOG 2>/dev/null' swallowed the resulting 'No such file' error,
so uploaded logs from the ISO contained only the archinstall portion.
Prefer the /mnt path when it exists (live ISO post-install), fall back
to /var/log when running on the installed system. Also print a visible
separator between the two log sections and warn on stderr if the omarchy
log is missing entirely (instead of silently producing an incomplete
upload).
The lua-based monitors config (~/.config/hypr/monitors.lua) ships from
skel with sensible defaults (omarchy_gdk_scale = 2, omarchy_monitor_scale
= "auto"). Hyprland handles the dynamic monitor scale at runtime via
"auto"; 'omarchy hyprland monitor scaling <N>' already updates both
omarchy_monitor_scale and omarchy_gdk_scale in monitors.lua when the
user wants to adjust.
The first-run 'detect what hyprland picked and write it back' dance
was leftover from the .conf-based config; it's redundant on the lua
flow and was the script that aborted omarchy-first-run under set -e
when it sed'd a missing monitors.conf.
Two bugs caused fresh users to land without branding files / AI skill
symlinks / nautilus extensions / xdg dirs / etc.:
1. install/first-run/gdk-scale.sh sed-edited ~/.config/hypr/monitors.conf,
but the lua-based config flow uses ~/.config/hypr/monitors.lua. sed -i
on the missing .conf returned non-zero, set -e fired, omarchy-first-run
aborted mid-block AFTER deleting ~/.local/state/omarchy/first-run.mode.
omarchy-setup-user (called at the bottom of the script, outside the
if-block) never ran, so setup-user.done never got written and the
per-user defaults (~/.config/omarchy/branding/*, ~/.agents/skills/omarchy,
xdg dirs, etc.) never got created.
Replace the .conf sed with a call to omarchy-hyprland-monitor-scaling
which already knows how to persist into monitors.lua.
2. Move 'omarchy-setup-user || true' to the TOP of omarchy-first-run, before
the privileged first-run block. setup-user is idempotent and gated on
its own marker, so running it first is safe and means a failure in any
of the privileged steps can't prevent user setup from completing.
The edge mirror temporarily lacked a current 1password build (315e60d
hadn't been rebuilt), so install/omarchy-base.packages was pointing at
1password-beta as a workaround. Reverting now that the stable package
is available, and matching the entry in omarchy-remove-preinstalls so
that bin actually drops the package we installed.
fonts.sh: obsolete. omarchy-settings installs omarchy.ttf to
/usr/share/fonts/omarchy/ system-wide and the fontconfig package's
post-install hook updates the cache, so copying it to ~/.local/share/
fonts and re-running fc-cache is redundant. The script was also failing
because omarchy-settings explicitly removes the source path
/usr/share/omarchy/config/omarchy.ttf during package install.
omarchy-setup-lock: the final line was 'omarchy-shell lock status
>/dev/null && echo "..."' which inherits the failed exit code when
omarchy-shell can't reach a running shell (always the case during chroot
install). set -e exempts cmd1 of && from triggering, but the script
still returns that non-zero as its own exit code, which the install
wrapper treated as a script failure. Wrap in an if/then so a failed
status check doesn't poison the script's exit code.
0x0.st has been intermittently down. logs.omarchy.org accepts the same
curl -F 'file=@-' POST and returns the resulting URL in the response
body, so the swap is purely the endpoint. Dropping the -Fexpires=24
parameter since logs.omarchy.org doesn't honour it (and doesn't expire
uploads by default).
User-level files (~/.config, ~/.local/share, ~/.agents, etc.) shouldn't
be written by install.sh — it runs as root or with system-level sudo,
and in offline (ISO chroot) mode the user doesn't exist yet. Moved into
a per-user idempotent command, called from omarchy-first-run.
Moved into bin/omarchy-setup-user (deleted from install/config/):
- omarchy-ai-skill.sh AI skill symlinks
- omarchy-toggles.sh hypr toggle flags
- nautilus-python.sh nautilus extensions
- branding.sh ~/.config/omarchy/branding/{about,screensaver}.txt
- user-dirs.sh xdg-user-dirs-update + gtk bookmarks
- detect-keyboard-layout.sh layout/variant sed into ~/.config/hypr/input.lua
- toggles.sh ~/.local/state/omarchy/toggles mkdir
- mimetypes.sh omarchy-refresh-applications + xdg-mime/xdg-settings
Kept in install/config/ (rely on OMARCHY_USER_NAME / OMARCHY_USER_EMAIL
that only exist during install):
- git.sh git config --global user.{name,email}
- xcompose.sh ~/.XCompose with name/email substituted
omarchy-setup-user:
- Refuses to run as root
- Idempotent via ~/.local/state/omarchy/setup-user.done marker
- --force escape hatch for re-running after dev-link or upstream changes
- Defaults OMARCHY_PATH to /usr/share/omarchy so it works outside install
bin/omarchy-first-run now invokes omarchy-setup-user unconditionally
(it self-gates), so first user login picks up the user setup.
install/config/all.sh: dropped the eight deleted entries.
All three packages (omarchy, omarchy-settings, omarchy-installer) still
build clean.
Brings back the omarchy-install command as a 5-line shell wrapper that
just exec's /usr/share/omarchy/install.sh. Not the 258-line Python
refactor-poc version — that one stays dropped.
The omarchy package keeps excluding bin/omarchy-install exactly so it
doesn't conflict with the omarchy-installer package's copy at /usr/bin.
All 25+ user-facing omarchy-install-* commands (gaming, browser, etc.)
still ship via omarchy.
- install.sh: use realpath to follow symlinks when deriving OMARCHY_PATH
from script location. Logical pwd left dev-link symlink chains pointing
at the wrong dir; realpath resolves to the underlying checkout.
- install/helpers/mode.sh: validate explicit OMARCHY_INSTALL_MODE values
and exit on typos. Also export_legacy_mode_flags now UNSETS the
contradictory legacy flag for each mode so unmigrated callers can't
see both OMARCHY_CHROOT_INSTALL=1 and OMARCHY_ONLINE_INSTALL=true.
- install/helpers/errors.sh: retry uses "$OMARCHY_PATH/install.sh"
and preserves OMARCHY_INSTALL_MODE explicitly; the previous hardcoded
~/.local/share/omarchy/install.sh broke package-mode retries.
- bin/omarchy-install-mode: soften the summary; the helper only knows
the current env, not any persisted post-install state.
Three modes the installer now branches on:
iso-chroot Running inside the new system's chroot from the ISO build.
pacman is configured by archinstall; systemd isn't running,
so service enables skip --now.
online-package Running on an existing Arch where the omarchy-* packages
are already installed. Skip base.sh and the bootstrap
preflight steps; only the per-host runtime ops in
config/login/post-install need to run.
online-git boot.sh path: clone into $HOME/.local/share/omarchy and
bootstrap everything from scratch.
install.sh derives OMARCHY_PATH from its own location, so the same script
works whether it's at /usr/share/omarchy/install.sh (package mode) or
$HOME/.local/share/omarchy/install.sh (git mode). install_mode_is helper
replaces direct ${OMARCHY_CHROOT_INSTALL:-} / ${OMARCHY_ONLINE_INSTALL:-}
checks in chroot.sh, finished.sh, errors.sh, mise-work.sh, preflight/pacman.sh.
Legacy vars still work through export_legacy_mode_flags, which sets
OMARCHY_CHROOT_INSTALL=1 / OMARCHY_ONLINE_INSTALL=true based on the
canonical mode for any caller that hasn't been updated.
bin/omarchy-install-mode prints the current mode for scripts/users that
need to introspect.
Smoke-tested all four entry paths (explicit mode, both legacy shims,
auto-detection from OMARCHY_PATH); each resolves correctly and the
legacy flags are exported in matching modes.
Sequential 'dev-link A' then 'dev-link B' previously left A/bin in
PATH because the prepend didn't strip the old one. unlink had the
same issue (it was trying to strip $(dirname $0), which only worked
if you invoked unlink via an absolute path that happened to match
the dev-link bin).
Both now read the previous OMARCHY_PATH from /etc/omarchy.conf before
overwriting/deleting it, and strip that path's bin/ from PATH before
prepending the new one (or nothing, for unlink).
A pass over the install scripts, dev-tools commands, and Hyprland Lua
files that I had stuffed with explain-everything preambles. Most of
those rationales (which files ship where, why hyprctl setenv doesn't
suffice, etc.) belong in commit messages or PR descriptions, not in
code people have to read forever. Kept the few comments that document
genuinely non-obvious behaviour: the keybind-env reason for hl.env in
envs.lua, why the runtime PAM seds stay scripted in
increase-lockout-limit, the chroot/--now distinction in chroot.sh, and
the dev-pkg-test split-install reason.
Two related fixes for installing package builds onto a system that has
the legacy script-installed Omarchy files already in place:
bin/omarchy-dev-pkg-test: split makepkg -s (build) from sudo pacman -U
(install) so we can pass --overwrite='*' to pacman. makepkg -i forwards
a fixed flag set to pacman and doesn't expose --overwrite, so dev pkg
tests on an existing script-install fail on every conflicting file
(plymouth themes, /etc drop-ins, sudoers). --overwrite='*' is correct
for the dev use case: the build IS the new authoritative state.
bin/omarchy-update-system-pkgs: add /usr/share/plymouth/themes/omarchy/*
to the transition --overwrite list. The old install/login/plymouth.sh
cp -r'd the theme files into that directory, so existing installs
conflict on the omarchy-settings package's plymouth payload.
The previous format wrote 'export OMARCHY_PATH="${OMARCHY_PATH:-<path>}"'
to /etc/omarchy.conf, intended so install.sh's script-mode OMARCHY_PATH
could win over the dev-link value. But install.sh doesn't source
/etc/omarchy.conf (only profile.d/uwsm/bash do, all post-install), so the
guard was protecting a non-scenario.
It also caused a real bug: paths.lua parses /etc/omarchy.conf as text
and returned the literal '${OMARCHY_PATH:-<path>}' string as the
omarchy_path, breaking dev-link in Hyprland.
Switch to plain 'export OMARCHY_PATH="<path>"'. dev-link is the only
writer, and when it's active, its value should win unconditionally.
When the script is run under outer sudo, the environment loses
HYPRLAND_INSTANCE_SIGNATURE, XDG_RUNTIME_DIR, and DBUS_SESSION_BUS_ADDRESS,
so the 'hyprctl version' check silently fails and the live-session
refresh (setenv / import-environment / restart shell / hyprctl reload)
gets skipped. The user is left with /etc/omarchy.conf written but no
session updates — confusing because the env in the current shell still
shows the old value.
Reject EUID=0 invocations with a clear message pointing at the right
pattern: invoke as your user, the script prompts for sudo only when it
needs to write /etc/omarchy.conf.
The four dev-tools commands that complete the developer-ergonomics
story for the package refactor:
- omarchy-dev-link <path>: writes /etc/omarchy.conf so OMARCHY_PATH
resolves to <path> in all new shells, the Hyprland session env, and
systemd --user. Updates the live session via hyprctl setenv +
systemctl --user import-environment, restarts omarchy-shell, and
reloads Hyprland so the changes are visible immediately. Affects
every tree resolved via $OMARCHY_PATH: bin/, default/, shell/,
themes/, applications/, config/.
- omarchy-dev-unlink: removes /etc/omarchy.conf and reverses the live
session updates back to /usr/share/omarchy.
- omarchy-dev-status: reports current dev-link state (configured path,
current shell, hyprland session env).
- omarchy-dev-pkg-test [pkg] [checkout]: builds and installs a package
from the checkout via OMARCHY_SRC + makepkg -si --skipchecksums.
Used for changes that land at fixed system paths (/etc/, /usr/lib/,
udev rule bodies, plymouth themes, /etc/skel) that dev-link can't
shadow. The built package's pkgver is tagged 'dev.<short-sha>[.dirty]'
so 'pacman -Q' makes its source obvious. Defaults: package
omarchy-settings, checkout ~/Work/omarchy/omarchy-installer,
PKGBUILDs read from ~/Work/omarchy/omarchy-pkgs/pkgbuilds/.
All four ship via omarchy-dev-tools (existing PKGBUILD wildcard).
Verified package build picks them up at /usr/bin/omarchy-dev-*.
The /etc/profile.d/omarchy.sh default for OMARCHY_PATH was being clobbered
by shipped config files that re-export the variable to the old script-mode
path. In package mode, every login shell, UWSM session, and Hyprland
autostart was effectively reversing the migration. Fix:
- default/bashrc: source from $OMARCHY_PATH/default/bash/rc; default
OMARCHY_PATH to /usr/share/omarchy if not set (so the source line
works in any boot order, including non-login interactive shells).
- default/bash/envs: change 'export OMARCHY_PATH=$HOME/...' to
'export OMARCHY_PATH=${OMARCHY_PATH:-/usr/share/omarchy}' so the
profile.d default and install.sh script-mode override both win.
- config/uwsm/env: same defaulting pattern. UWSM may not source
profile.d, so the inline default still matters.
- default/hypr/autostart.lua: quickshell launched from $OMARCHY_PATH/shell
(not literal $HOME path). The bash that runs hl.exec_cmd sees
OMARCHY_PATH propagated from UWSM env.
- config/chromium-flags.conf: --load-extension hard-codes
/usr/share/omarchy/... because chromium doesn't expand env vars or
'~'. The omarchy-dev-link workflow symlinks /usr/share/omarchy to a
checkout.
- bin/omarchy-install-browser: same chromium-flag path correction for
brave-origin-beta-flags.conf.
Missed in the previous pass:
- bin/omarchy-launch-screensaver: alacritty/ghostty config paths
- bin/omarchy-refresh-applications: source paths for icons and .desktop
- bin/omarchy-refresh-config: docstring/comment references
Still NOT touched (intentional):
- bin/omarchy-reinstall-git: stages '~/.local/share/omarchy-{old,new}' for
the script-mode upgrade dance; needs redesign for package mode.
- bin/omarchy-dev-add-migration: uses 'cd ~/.local/share/omarchy' to read
a git log; only meaningful in dev mode.
- bin/omarchy-install-browser:69: writes a string into a chromium config file.
- install/helpers/errors.sh:125: boot.sh relaunch (separate redesign).
The package-installed paths live at /usr/share/omarchy; the script-install
paths live at ~/.local/share/omarchy. $OMARCHY_PATH is set by install.sh
during the install flow and by /etc/profile.d/omarchy.sh in package mode.
Scripts that hard-code the script-install path don't honour that and will
misbehave once shipped to /usr/bin.
16 files migrated via sed s|~/\.local/share/omarchy|$OMARCHY_PATH|g
and s|$HOME/\.local/share/omarchy|$OMARCHY_PATH|g:
bin/:
- omarchy-plymouth-{preview,reset,set}
- omarchy-refresh-{limine,plymouth}
- omarchy-reinstall-configs
- omarchy-show-logo
- omarchy-games-retro-install
- omarchy-install-gaming-battlenet
install/:
- config/branding.sh
- packaging/{fonts,icons}.sh
- post-install/{finished,pacman}.sh
- preflight/{migrations,pacman}.sh
Deliberately NOT migrated:
- bin/omarchy-dev-add-migration: uses 'cd ~/.local/share/omarchy' to
resolve a git repo for 'git log'. The packaged binary doesn't have a
git repo to read; this command only makes sense in dev mode where
the home path is the right one.
- bin/omarchy-install-browser:69: writes a string into a chromium
config file that chromium itself interprets; not a bash-resolved path.
- install/helpers/errors.sh:125: the boot.sh-style relaunch behaviour
needs a separate redesign.
- bin/omarchy-wifi-powersave: shopt -s nullglob. Previously, machines
with no wireless interfaces ran the loop once with the literal glob
('iface=*'), failed iw, and exited 237. With the new always-installed
udev rule that invokes this on every AC transition, the failure
would surface as failed transient omarchy-wifi-powersave-* units on
desktops. Now it cleanly no-ops.
- migrations/1779307845.sh: add 'systemctl daemon-reload' so the
plocate-updatedb.service.d/ac-only.conf drop-in is picked up on
upgrade. Arch's systemd pacman hook only triggers on
/usr/lib/systemd/system/*, not /etc/systemd/system/*.
- install/config/powerprofilesctl-rules.sh and wifi-powersave-rules.sh:
rm -f the pre-rename legacy paths before udevadm reload, so the
cleanup is idempotent outside the one-shot migration (matters for
re-install/downgrade testing).
Four scripts that wrote static /etc or /usr files become package-shipped
files. The other config-script audit candidates (omarchy-ai-skill,
nautilus-python, omarchy-toggles, input-group) correctly stay scripts —
they're user-level operations, not /etc writes.
New package-owned files (omarchy-installer/etc/, shipped by
omarchy-settings):
- etc/systemd/system/plocate-updatedb.service.d/ac-only.conf
- etc/udev/rules.d/99-omarchy-power-profile.rules
- etc/udev/rules.d/99-omarchy-wifi-powersave.rules
The two udev rule files are renamed for namespacing (99-power-profile
-> 99-omarchy-power-profile; 99-wifi-powersave -> 99-omarchy-wifi-powersave)
and rewritten to invoke /usr/bin/omarchy-powerprofiles-set and
/usr/bin/omarchy-wifi-powersave instead of $HOME/.local/share/omarchy/bin/...
(both binaries ship in the omarchy package at /usr/bin/).
The battery-present gate from the original scripts is dropped — the rules
trigger on power_supply udev events, which are benign on desktops without
batteries. (The runtime commands no-op on AC-only systems.)
Script changes:
- install/config/plocate-ac-only.sh: DELETED (file ships, daemon-reload
happens via pacman hook).
- install/config/unmount-fuse.sh: DELETED (file ships at
/usr/lib/systemd/system-sleep/unmount-fuse via omarchy-settings).
- install/config/powerprofilesctl-rules.sh: SHRINK to runtime ops
(enable power-profiles-daemon, udevadm reload+trigger).
- install/config/wifi-powersave-rules.sh: SHRINK to udevadm reload+trigger.
- install/config/all.sh: drop the two deleted entries.
bin/omarchy-update-system-pkgs: add --overwrite for plocate-updatedb
drop-in and unmount-fuse paths (the udev rule renames need no overwrite
since the new paths are virgin).
migrations/1779307845.sh: remove the legacy 99-power-profile.rules and
99-wifi-powersave.rules paths on existing installs, then reload udev.
Four corrections to the omarchy-settings transition:
- bin/omarchy-update-system-pkgs: pass --overwrite for the 13 paths
omarchy-settings now owns. Existing Omarchy installs would otherwise
fail pacman conflict checks on the upgrade that ships the package
(the files exist as unowned filesystem entries from the previous
install scripts). The flags are no-ops once the transition release
is everyone's baseline; remove then.
- config/mimeapps.list: drop the HEY.desktop mailto mapping. HEY.desktop
is generated by install/packaging/webapps.sh at install time, not
shipped under applications/, so it isn't valid for a fresh /etc/skel
user before the installer runs.
- install/config/mimetypes.sh: add 'xdg-mime default HEY.desktop
x-scheme-handler/mailto' as a runtime op since the mapping no longer
lives in mimeapps.list.
- install/config/increase-lockout-limit.sh: delete existing
pam_faillock.so authsucc lines before re-adding, so re-running the
installer doesn't duplicate the authsucc entry in
/etc/pam.d/sddm-autologin.
Download the current page's video with yt-dlp via Alt+Shift+D or a click
on the toolbar icon. A native-messaging host runs the download, shows live
progress on the Quickshell OSD, and posts a clickable "Download complete"
toast that opens the file in mpv.
- Extension: pinned key for a stable id, green download-video icon,
keyboard command + toolbar action (reads the active tab URL).
- Native host (omarchy-chromium-ytdlp-host): verifies the URL with
yt-dlp --simulate (else "No video found"), streams progress to the OSD
(time-throttled to ~4/s), saves to ~/Videos, opens mpv on click.
- Installer (omarchy-install-chromium-ytdlp) writes the native-messaging
manifest into installed Chromium/Chrome/Brave/Edge profiles; wired into
browser install and chromium refresh, with a migration for existing users.
- omarchy-osd: add -d/--duration so the OSD can persist during a download.
- Add yt-dlp to base packages, load the extension via --load-extension,
and document the Alt+Shift+D binding.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Let users add trusted plugin source repos and add/update/remove plugins
from them, alongside the existing in-shell plugin commands:
omarchy plugin source <add|list|remove|refresh>
omarchy plugin available
omarchy plugin add | update | remove | validate
Each command is interactive (gum/fzf pickers, confirmation, an update
diff) in a TTY and fully flag-driven with --yes for scripts and agents.
Sources live in ~/.config/omarchy/plugins/sources.json and clone into
~/.cache/omarchy/plugin-sources/. The installer only copies files,
validates manifests against the shell's schema, and toggles enabled
state over IPC -- it never runs plugin code, hooks, or sudo.
Also guard 'plugin bar add' so only known bar-widget ids enter the
layout (rejecting typos/non-widgets like a stray '--help').
Restore the clipboard watcher behavior that drops CLIPBOARD_STATE=sensitive events, which password managers use for copied secrets.
Use the same sensitive foreground clipboard path for emoji insertion instead of a custom history marker, and keep the password-manager MIME hint ignored as a fallback.
Keep the first emoji result selected whenever results exist, including after search filtering, so Enter inserts immediately.
Route emoji insertion through a hidden helper that serves a transient sensitive clipboard entry long enough for Shift+Insert, then stops that clipboard owner.
Mark transient emoji inserts so clipboard history skips only that event while continuing to record normal terminal copies.
Clipboard history selections passed the full selected text as a process argument to omarchy-clipboard-paste-text. Large entries can exceed Linux's per-argument exec limit, so the helper never starts reliably for big copied blocks.
Pass the original history index instead and have the helper read that entry from clipboard-history.json before wl-copy and Shift+Insert. Also focus the first row when opening the manager, keep filtered rows mapped to their original history indexes, and drop whitespace-only text entries.