For shells outside the install flow (login shells, cron jobs, dev work)
the install.sh-set OMARCHY_PATH isn't in scope. Without a system-wide
default, omarchy-* commands shipped at /usr/bin can't find runtime data
under /usr/share/omarchy/.
profile.d sets OMARCHY_PATH=/usr/share/omarchy if not already set, so
install.sh's override (to $HOME/.local/share/omarchy during script-install)
still wins. The omarchy-dev-link workflow manages a symlink at
/usr/share/omarchy that points at a local checkout when active.
Also prepends $OMARCHY_PATH/bin to PATH if the directory exists (mainly
matters for script-install mode; /usr/bin is on PATH for package mode).
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.
The previous setup scattered systemctl enables across docker.sh,
printer.sh, bluetooth.sh, network.sh, powerprofilesctl-rules.sh, and
kernel-modules-hook.sh. Some used chrootable_systemctl_enable; others
used bare 'sudo systemctl enable'. Centralizing makes the install-time
service surface auditable in one place and consistent in chroot/non-chroot
behavior.
install/config/enable-services.sh enables:
- bluetooth.service
- cups.service, cups-browsed.service, avahi-daemon.service
- docker.socket
- iwd.service
- linux-modules-cleanup.service
- power-profiles-daemon.service
Each via chrootable_systemctl_enable, which uses 'enable --now' on a live
system and bare 'enable' in chroot (set OMARCHY_CHROOT_INSTALL=1).
Stays in original script (deliberately):
- sddm.sh: bare 'systemctl enable sddm.service' (no --now during install
would log the user out; lives in install/login/)
- limine-snapper.sh: limine-snapper-sync (bootloader-specific path)
- first-run/firewall.sh: ufw (deferred to first-run for UX)
- hardware/{t2,intel/lpmd,intel/thermald,apple/fix-suspend-nvme}: gated
on hardware detection
Side cleanups:
- install/config/kernel-modules-hook.sh: DELETED (sole line was the enable
for linux-modules-cleanup, now in enable-services.sh).
- install/config/hardware/printer.sh: DELETED (sole purpose was the
three cups/avahi enables; nothing else to do).
- install/config/hardware/network.sh: keep the systemd-networkd-wait-online
disable+mask; drop the iwd enable.
- install/config/hardware/bluetooth.sh: keep the AutoEnable=false sed and
the wireplumber/bt-agent user-session setup; drop the system enable.
- install/config/docker.sh: drop the docker.socket enable.
- install/config/powerprofilesctl-rules.sh: drop the
power-profiles-daemon enable.
- install/config/all.sh: register enable-services.sh; remove the two
deleted entries.
- 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.
Existing users upgrading from script-installed Omarchy still have files
the install scripts wrote at the OLD paths. The new package can't own
those old paths (they were either renamed or replaced by drop-ins), so
this migration tidies them up:
- /etc/sysctl.d/99-sysctl.conf: strip the appended net.ipv4.tcp_mtu_probing
line (new file is /etc/sysctl.d/99-omarchy-sysctl.conf)
- /etc/modprobe.d/disable-usb-autosuspend.conf: removed
(new file is /etc/modprobe.d/omarchy-usb-autosuspend.conf)
- /etc/sudoers.d/{passwd-tries,asdcontrol}: removed
(new files are /etc/sudoers.d/omarchy-{passwd-tries,asdcontrol})
- /etc/systemd/system/user@.service.d/faster-shutdown.conf: removed
(new file is 10-faster-shutdown.conf)
- /etc/systemd/logind.conf HandlePowerKey=ignore line: reset to its
commented default so the logind.conf.d/ drop-in is the only source
After cleanup, systemctl daemon-reload + sysctl --system to pick up the
new state immediately.
config/mimeapps.list ships at /etc/skel/.config/mimeapps.list (via
omarchy-settings's existing config/** -> /etc/skel/.config copy).
New users get the full Omarchy MIME defaults on first login without
running install scripts.
install/config/mimetypes.sh shrinks to two runtime ops that a static
file can't replace:
- omarchy-refresh-applications: copies .desktop entries and icons into
the user home and refreshes the desktop database
- xdg-settings set default-web-browser chromium.desktop: sets the
system-default browser; omarchy-install-browser overrides this if
the user later picks a different browser.
The three /etc writes (resolved.conf.d drop-in, nsswitch.conf override,
cups-browsed.conf override) now ship via omarchy-settings. The service
enables stay scripted because they need chrootable_systemctl_enable to
work both inside the ISO chroot and on a live system.
Each script's static-file write moves to omarchy-settings. The scripts
keep only the runtime side: reload systemd, restart dirmngr, sysctl --system,
or (for lockout-limit) the PAM seds that have to stay scripted because
/etc/pam.d/system-auth and /etc/pam.d/sddm-autologin are upstream-owned and
need targeted line edits rather than a full-file override.
faillock.conf's deny=10 (formerly inside increase-sudo-tries.sh sed) now
rides through the etc-overrides dance in omarchy-settings.
The three Docker config files (etc/docker/daemon.json,
etc/systemd/system/docker.service.d/no-block-boot.conf,
etc/systemd/resolved.conf.d/20-docker-dns.conf) now ship via the
omarchy-settings package. The script keeps only the actions a static
file can't do:
- restart systemd-resolved so the new drop-in takes effect
- usermod -aG docker $USER (user-specific, can't be packaged)
- systemctl enable docker.socket and daemon-reload
Each of these scripts only wrote a single /etc file (or copied
config/bashrc into the user home). The corresponding files now ship
from the omarchy-settings package — either directly into /etc (drop-ins
the package fully owns) or via the /usr/share/omarchy/etc-overrides
post_install dance (for upstream-owned paths).
Deleted:
- install/config/timezones.sh -> etc/sudoers.d/omarchy-tzupdate
- install/config/sudoless-asdcontrol.sh -> etc/sudoers.d/omarchy-asdcontrol
($USER rewritten to %wheel)
- install/config/increase-sudo-tries.sh -> etc/sudoers.d/omarchy-passwd-tries
(faillock.conf side now in etc-overrides)
- install/config/hardware/ignore-power-button.sh
-> etc/systemd/logind.conf.d/10-ignore-power-button.conf
(replaces sed-edit of logind.conf)
- install/config/hardware/usb-autosuspend.sh
-> etc/modprobe.d/omarchy-usb-autosuspend.conf
- install/config/ssh-flakiness.sh -> etc/sysctl.d/99-omarchy-sysctl.conf
- install/config/config.sh -> /etc/skel seeds ~/.config from config/**;
~/.bashrc seeded from etc-overrides
- install/login/plymouth.sh -> theme files already shipped by
omarchy-settings; etc/plymouth/plymouthd.conf
handled via etc-overrides
A separate migration tidies up the old paths on existing installs.
all.sh entries updated to drop these scripts.
This tree is the source for omarchy-settings to install into /etc
(for paths Omarchy fully owns) or stage at /usr/share/omarchy/etc-overrides
(for paths upstream packages own, which the post_install copies into place).
Package-owned drop-ins (no upstream conflict):
- etc/docker/daemon.json
- etc/systemd/system/docker.service.d/no-block-boot.conf
- etc/systemd/resolved.conf.d/20-docker-dns.conf
- etc/systemd/resolved.conf.d/10-disable-multicast.conf
- etc/systemd/system.conf.d/10-faster-shutdown.conf
- etc/systemd/system/user@.service.d/10-faster-shutdown.conf
(renamed from .../faster-shutdown.conf for consistency)
- etc/systemd/logind.conf.d/10-ignore-power-button.conf
(replaces the previous in-place sed-edit of logind.conf)
- etc/gnupg/dirmngr.conf
- etc/sysctl.d/99-omarchy-sysctl.conf (renamed from the 99-sysctl.conf
appendage; a migration cleans up the old path on existing installs)
- etc/sysctl.d/90-omarchy-file-watchers.conf
- etc/modprobe.d/omarchy-usb-autosuspend.conf
(renamed from disable-usb-autosuspend.conf)
- etc/sudoers.d/omarchy-asdcontrol
(renamed from asdcontrol; $USER -> %wheel for packaging)
- etc/sudoers.d/omarchy-tzupdate
- etc/sudoers.d/omarchy-passwd-tries
(renamed from passwd-tries for namespacing)
Etc-overrides (upstream-owned paths; staged by the PKGBUILD at
/usr/share/omarchy/etc-overrides/ and cp'd in by post_install):
- etc/security/faillock.conf (owned by pam)
- etc/nsswitch.conf (owned by filesystem)
- etc/cups/cups-browsed.conf (owned by cups-browsed)
- etc/plymouth/plymouthd.conf (owned by plymouth)
All sudoers files validate with visudo -cf.
The install scripts that previously wrote these files are updated or
removed in follow-up commits.
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').
Based entirely on the work of @robzolkos in #5711. That attempt was
adapted to better fit the standard templating system while also adding
color mixing for use in other themes.
The custom PositionButtonGroup component wrapped Repeater + Button
directly without setting bordered: true on the delegates. After the
recent Button changes (idle non-bordered Buttons have zero border
width), the non-selected position chips lost their visible chip
structure entirely \u2014 they rendered as floating text labels and read
as unclickable.
Replace the bespoke component with qs.Ui.ButtonGroup (which sets
bordered: true on each chip) inside a Column with a 'Position' label,
and drop the now-unused PositionButtonGroup definition. All four
options now read as clickable chips and the chosen value still paints
the accent fill+border.
Pass a section name to open the gallery with the cursor already on
that component instead of scrolling from the top. Useful when
iterating on a single widget.
omarchy dev ui-preview # default position
omarchy dev ui-preview button # land on the Button section
omarchy dev ui-preview button-group # land on ButtonGroup
omarchy dev ui-preview slider # land on Slider, etc.
The bin script forwards the argument as {"section": "..."} in the
shell summon payload. The gallery's open(payloadJson) parses it,
clamps against visibleSections (unknown names fall back to default),
and sets focusSection + selectedIndex inside the existing Qt.callLater
so the cursor's hasCursor bindings fire ensureCursorVisible
automatically.
Three small fixes around the new Button + ButtonGroup so the cursor is
always visible and the section reads as a single Button showing flag
combinations.
ButtonGroup now exposes cursorIndex + hovered(index, isHovered) so
panels can drive a single Button's hasCursor without reaching past
ButtonGroup's API. Every chip gets bordered: true so the row reads as
a real picker — distinct outlines, not just a tinted fill that vanishes
into the background.
Button's border treatment grows two paths so the cursor is always
visible regardless of other state: bordered + hot recolors the border
to the accent and thickens to the focus-ring width, and selected + hot
thickens the existing accent border to the focus-ring width (so the
cursor landing on the chosen option still reads as cursor presence,
instead of disappearing into the selected fill).
Gallery's Button section pairs each demo button with a small caption
('idle', 'active', 'icon only', 'icon + active', 'bordered +
focusable') so the row reads as one Button with its state flags side
by side rather than five unrelated buttons. The ButtonGroup section
gets the cursor wiring (cursorIndex + onHovered) plus an
onFocusedChanged auto-scroll so the cursor visual is reachable via
j/k h/l from the panel cursor model.
Wide content rows (slider rows in audio / monitor panels) opted out of
the cursor fill by setting fill: "transparent" on their CursorSurface,
which left them with no cursor visual at all — j/k landed there
silently with no way to know what was selected.
Add an `outline: bool` flag to CursorSurface. When true, hasCursor
paints an accent border (Style.focusBorderColor at
Style.focusBorderWidth) instead of a fill, leaving the row's chrome
fully visible underneath. Use for slider rows where a fill would
obscure the track.
Audio output / input slider rows and the monitor brightness row opt
in. The dev gallery's slider section replaces its bespoke Rectangle
wrapper with the same CursorSurface { outline: true }, so what the
gallery documents is what the panels ship.
The three components were three takes on the same shape \u2014 a clickable
rectangle with text/icon, a hot/hover state, an optional persistent
border, and an optional 'selected' or 'active' highlight. CursorPill
was a 30-line PillButton wrapper that added a hovered() signal;
ChoiceButton was effectively PillButton with selected: bool painting
an accent fill+border.
Collapse them into a single qs.Ui.Button. State flags compose
independently:
hasCursor / hover hot fill
active persistent foreground-tint fill
selected accent fill + accent border
bordered: true persistent 1px idle border (form primaries)
focusable: true Tab focus paints the accent ring
pressed pressed fill
The hovered(bool) signal is now built-in, so CursorPill's wrapper is
unnecessary. ButtonGroup wraps a Row+Repeater for the form-style
'pick one of N' pattern; panel-cursor-driven cases still compose
Buttons directly in a Row with per-instance hasCursor wiring.
Theme tokens move into a new [style] section in shell.toml:
border-width = 1
focus-border-width = 3
idle-border-alpha = 0.4
hot-fill-alpha = 0.08
selected-fill-alpha = 0.18
pressed-fill-alpha = 0.22
focus-fill-alpha = 0.22
Style.qml parses these out of the same shell.toml [font] / [bar]
already reads, and exposes pre-computed Style.hotFill / selectedFill /
pressedFill / idleBorderColor / selectedAccentFill / borderWidth +
the existing focusBorder* tokens. Themes that don't ship a [style]
section get the previous defaults unchanged.
Dev gallery consolidates three sections (PillButton, CursorPill,
ChoiceButton) into Button + ButtonGroup, with the cursor model
sections renamed accordingly.
CursorSurface defines the canonical cursor chrome: a tinted fill, no
border. Three components had inconsistent treatments that the recent
standardization pass left half-finished:
- PillButton kept a 1px foreground border on hot (cursor or hover)
via a cursorBordered: true default.
- CursorPill specifically had to override cursorBordered: false to
match the rest of the kit.
- ChoiceButton thickened its border to 2px on hasCursor, which mixed
the cursor visual with the Tab-focus visual.
Drop the cursorBordered escape hatch from PillButton (no caller other
than CursorPill ever used it), drop CursorPill's now-redundant
override, and remove hasCursor from ChoiceButton's border.width
thickening. Update Toggle's class comment (it still claimed cursor
mirrored Tab focus, which f3ba5055 broke without updating the doc).
Now: hot/hasCursor = fill only, activeFocus = accent border ring,
selected/active = stronger fill or accent border per component
semantic. One canonical pattern across the kit.
Themes now drive typography the same way they drive colors: one [font]
base-size in shell.toml is the rem root, and every Style.font.<token>
(caption, bodySmall, body, subtitle, title, heading, display,
displayLarge, iconSmall, icon, iconLarge) derives from it via a fixed
multiplier. Themes can also pin individual tokens for stylistic
emphasis. base-size is clamped 11..13 until row-height tokens exist.
Bar dimensions move to the same singleton: [bar] size-horizontal /
size-vertical replace the hardcoded 26/28 in Bar.qml, exposed as
Style.bar.sizeHorizontal / sizeVertical.
Style.qml also resolves the fontconfig 'monospace' alias via fc-match
and exposes Style.font.resolvedFamily so panels can display the
concrete family. Watches ~/.config/fontconfig/fonts.conf so it tracks
'omarchy font set <name>'.
The qs.Ui kit (PillButton, Dropdown, Toggle, TextField, etc.) and
every first-party plugin (bar widgets, settings, menu, clipboard,
emoji, polkit, notifications, osd, image-picker, dev-gallery) now
bind to Style.font.* instead of pixel literals. Only three deliberate
display-scale outliers remain: the notification empty-state glyph and
the weather flyout's hero temperature pair, all commented.
Background plugin's applyTheme IPC fast-path also pushes shell.toml to
Style so theme swaps update typography and bar size without waiting
for inotify debounce.
Dev gallery (omarchy dev ui-preview) now ships a Typography section
that renders the full scale and theme tokens live, and its summon
command is fixed (omarchy-shell-ipc -> omarchy-shell).