+12








e1b0e5e9bb
* Add omarchy-hw-platform to tell Apple Silicon and Qualcomm machines apart
aarch64 covers Apple Silicon Macs, Snapdragon laptops, Raspberry Pis and
VMs, so the CPU architecture can't pick platform setup. omarchy-hw-platform
prints apple-silicon, qualcomm, generic-aarch64 or generic from the vendor
prefixes in the device tree's root compatible (/proc/device-tree, then
/sys/firmware/devicetree/base) and the CPU, and fails when they contradict
each other. Its qualcomm answer uses the same evidence as the dragon
branch's omarchy-hw-qualcomm-soc, and the fixtures check the two agree.
omarchy-hw-apple-silicon is the Apple predicate on top of it.
An image built away from its hardware names its target in a root-owned
manifest, /var/lib/omarchy/image/target (format=1, platform=). While the
root is being built rather than booted, the detector answers from it and
never reads the build host's device tree; a booted system always answers
from its hardware. As root it restarts in an empty environment, so no
variable picks the platform for privileged setup.
Co-authored-by: Birk Skyum <74932975+birkskyum@users.noreply.github.com>
Co-authored-by: Jimmy Van Veen <8206925+JimmayVV@users.noreply.github.com>
* Reject platform gates keyed only on the CPU architecture
An architecture check is right for an ABI, a binary or a repository's
$arch, not for picking a platform. The lint lists each shipped check after
review and fails on a new one, so platform setup goes through
omarchy-hw-platform.
* Compose default packages from base, architecture and platform lists
omarchy-reinstall-pkgs installed the base list on every machine. A
platform's default set is now the base list, then install/omarchy-aarch64.packages
on aarch64, then the platform's own list when it has one
(install/omarchy-qualcomm.packages adds linux-firmware-qcom).
omarchy-pkg-defaults prints the set for the detected platform, or for the
one named, and reinstall installs it. x86_64 still gets exactly the base
list, and an image build gets its target's set.
* Early-load thunderbolt only where the kernel builds the module
linux-aarch64 has no thunderbolt module, and mkinitcpio fails the whole
image over a MODULES entry it cannot find. The optional form, thunderbolt?,
is not enough: a systemd initramfs copies MODULES as written into
modules-load.d, where "thunderbolt?" names no module, and a kernel without
the module would still list it there. thunderbolt_module.conf now adds plain
thunderbolt only when modinfo finds it as a module for the kernel being
built.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Compose mkinitcpio HOOKS from an early baseline drop-in
omarchy_hooks.conf assigned HOOKS outright, and mkinitcpio sources drop-ins in
name order, so it ran after any numbered drop-in (90-*.conf) and threw its
hooks away. HOOKS now comes from 00-omarchy-hooks.conf, which sorts first, and
later drop-ins add to it. The NVIDIA kms filter and the vconsole handling stay
in omarchy_hooks.conf. With the drop-ins Omarchy writes, machines keep the
same HOOKS, MODULES and FILES; a user drop-in that sets HOOKS and sorts before
omarchy_hooks.conf now takes effect instead of being overwritten.
Migration 1786605598 now sources the baseline first, since without it the
migration would read empty HOOKS and also rebuild hybrid systems. It skips a
machine that lacks the baseline.
* Resolve the LUKS parent from raw lsblk output
lsblk -s prints the ancestor tree with box-drawing prefixes ("└─/dev/nvme0n1p6"), so the crypto_LUKS parent came back as a path that does not exist and first-boot setup stopped with "Could not locate the LUKS device to re-key". -r prints raw names. Factory reset uses the same lookup.
* Check the factory-reset passphrase against keyslots only
cryptsetup open tries enrolled tokens before the key file, so a TPM2 or
FIDO2 token could authorize the re-key with a wrong passphrase.
* Test LUKS parent lookup during owner setup and factory reset
Exercise raw ancestry output without invoking provisioning or reset. Cover btrfs subvolume roots, absent encryption, failed probes, and explicit cryptdevice specifications.
* Cover LUKS parent lookup through an LVM volume group
* Journal the first-boot LUKS re-key so an interrupted attempt resumes
A first boot killed after the staged install key's slot was retired left the
key file behind, and every retry then stopped at "the staged LUKS key no
longer unlocks". A lost key file with the boot-time auto-unlock still
configured let setup finish with the disk unlocking unattended.
The re-key now runs from install/provisioning/luks-rekey.sh and records its
phase and slot numbers (never key material) in
/var/lib/omarchy/provisioning/luks-rekey.state. It reuses the owner's slot
instead of adding a duplicate, retires LUKS1 slots too, and destroys the
staged key only after the volume proves it opens nothing. Setup refuses to
finish while any part of the provisioning unlock remains. The journal ends at
phase=done and leaves together with pending, so a retry after the staged key
is retired must use the password the disk already holds.
Key checks exclude LUKS2 tokens: cryptsetup open tries an enrolled token
(say a TPM2 slot a previous owner left behind) before the key and reports its
slot whatever key it was given. Slot listings read only the keyslot section,
since a luks2-keyring token looks like a keyslot in luksDump.
* Change the root volume's LUKS key before the login and root passwords
Installation gives the login user, root and the disk holding / one password,
but omarchy-drive-password changed only the LUKS key, so the three drifted
apart. For the disk holding / it now checks the current password opens the
disk, changes the LUKS key, confirms the new key opens the disk and the old
one no longer does, and only then sets the login and root passwords. Any other
encrypted drive changes its LUKS key alone. Every drive now asks for the
current password with gum instead of cryptsetup's prompt.
A journal in ~/.local/state/omarchy/drive-password.state (disk UUID, key slots
and phase, never a password, written with the re-key journal helpers) lets the
next run finish an interrupted change. The rerun accepts only the old key
(removing the slot the interrupted change added) or the new one (retiring the
old slot), so another key such as a recovery passphrase never becomes the login
password. luksChangeKey is judged by whether the new key opens the disk, not
by its exit status, and key checks exclude LUKS2 tokens, which cryptsetup
open would otherwise accept in place of any password.
Runs are serialised, cryptsetup calls hold a disk lock, a / whose LUKS
partition cannot be resolved fails closed, and passwords are never traced. A
new password equal to the current one is refused, and --pbkdf argon2id is
passed only to LUKS2, so LUKS1 drives can change their password again.
* Add omarchy-lifecycle-dispatch for platform boot operations
Provisioning, factory reset and update call a fixed set of operations
through it. A platform whose boot chain the generic Limine path can't drive
registers a directory of root-owned entrypoints in the dispatcher; every
other platform gets a no-op and keeps its generic path. Apple Silicon is
registered to omarchy-mac-boot's /usr/lib/omarchy/mac-boot, with its
required operations failing clearly when the package or an entrypoint is
missing. docs/lifecycle-dispatch.md is the contract, including how a
Qualcomm boot package would plug in.
* Let the platform's boot package own first-boot setup's boot chain and key slots
Owner provisioning asks provision-prepare before the owner form, hands the
staged unlock to provision-commit and provision-verify where the platform
implements both, and records the kept LUKS slot through luks-slots before
the staged key is destroyed; the disk password change records the owner's
new slot the same way. On x86, Snapdragon and generic aarch64 every call is
a no-op and first boot is unchanged.
* Reset through the platform's boot package where it owns the boot chain
Where a platform implements the reset operations, reset-prepare and
reset-verify stage and prove the factory root's boot files, the throwaway
slot is added only after that, and reset-commit gets the key on standard
input once the factory root is active. A failure before the switch rolls the
boot state back and revokes the slot. Elsewhere the Limine UKI path is
unchanged.
* Check the platform's boot chain after an update
omarchy-update-boot runs update-verify after AUR packages, the last
sudo-capable step, so it also covers the initramfs rebuilds they trigger.
It follows third-party build code, so like AUR it authenticates through the
no-update wrapper. A failed verification finishes the update's remaining
steps, then exits non-zero without offering the reboot. Where the platform
does not implement it nothing runs and nothing asks for root.
* Hand a machine to its platform's migration through lifecycle dispatch
migrations/1790347292.sh resolves the new optional migrate operation and,
where the platform's boot package implements it, runs it as root. The
entrypoint moves the machine onto the platform's official package set,
idempotently and resumably; its failure leaves the migration pending. Once
it succeeds, a root-owned marker spares other accounts the migration and its
root prompt. Everywhere else, Macs without the boot package included, the
migration is a no-op, and an undetermined platform keeps it pending.
* Refuse packages tagged for another platform in a pacman hook
A platform package names its platform with groups=('omarchy-platform-<name>'),
<name> being what omarchy-hw-platform prints. omarchy-settings ships a
pre-transaction hook, the guard it runs and a copy of the detector, so the
guard is resident before the runtime and any platform package. The guard
reads tags from the synced databases and refuses a transaction that installs
a package tagged for another platform; a transaction without a tagged
package passes without asking which machine this is. Image builds answer
from the image's root-owned target manifest, as the detector does.
The guard ships on aarch64 only: no platform package is built for x86_64,
so x86 transactions run no hook. Hardware setup starts by proving the guard
is resident and checking the packages an installer placed without it. On
generic aarch64, where no platform packages exist, a missing guard is
logged, not fatal, and on x86 it is expected.
docs/platform-guard.md is the contract.
Co-authored-by: Birk Skyum <74932975+birkskyum@users.noreply.github.com>
Co-authored-by: Jimmy Van Veen <8206925+JimmayVV@users.noreply.github.com>
* Enable Voxtype's Vulkan backend only when it is installed
omarchy-hw-vulkan reports a Vulkan runtime, not whether this voxtype build
has a Vulkan backend. The aarch64 voxtype-bin package ships only the CPU and
ONNX binaries, so on any aarch64 machine with a Vulkan driver setup ran
`voxtype setup gpu --enable` and printed "Vulkan backend not installed" in
the middle of an otherwise working install. Require the backend binary too.
x86_64 voxtype-bin always ships /usr/lib/voxtype/voxtype-vulkan, so x86
installs still switch to the GPU backend as before.
Ported from omacom/omarchy-mac#483.
Co-authored-by: Oliver Lukschander <33756270+oliverlukschander@users.noreply.github.com>
* Link the system Widevine CDM into Brave
The widevine package registers its CDM for Chromium and Firefox only.
Brave looks for the CDM in its own install directory, finds nothing, and
DRM sites (Netflix, Apple TV+, Spotify) fail with a blank player and no
useful error. This hits machines where Widevine comes from that package,
such as aarch64 ones, since no Widevine component is published for them.
Link /opt/WidevineCdm/chromium into /opt/brave-bin and /opt/brave-origin-bin
through as_root when Brave or Brave Origin is installed, and add a migration
that repairs existing installs. Both do nothing when the widevine package is
absent or the browser already has a CDM. A standard x86 install has no
widevine package, so nothing changes there.
Ported from omacom/omarchy-mac#474, extended to Brave Origin.
Co-authored-by: Caleb Marshall <68087341+cajomar@users.noreply.github.com>
* Report failed setup steps on the console
With file logging, run_logged sends a failing leaf's output and its
"Failed:" marker to the install log only, so the caller sees setup stop
with an exit status and nothing else. A missing command (exit 127) was the
case that made this hard to diagnose.
Print the failed script, its exit status and the log path on stderr. Stdout
logging mode already shows the failure marker and is unchanged, and
successful steps stay quiet.
Ported from omacom/omarchy-mac#389 (the logging part).
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Let a migration defer itself without stopping the update
A migration that exits 75 changed nothing and cannot finish yet: omarchy-migrate
leaves it pending with its login notification, runs the migrations after it and
exits 0, so omarchy update carries on. Any other failure still stops the queue.
The platform migration passes a deferred refusal of its entrypoint through, and
defers itself where the dispatcher can't resolve its entrypoint (the platform
can't be told, or the entrypoint isn't trusted): nothing has run then, so the
update goes on to its boot verification, which fails there, as documented.
* Keep Apple Silicon's systemd initramfs on the HOOKS baseline
00-omarchy-hooks.conf set the busybox line on every machine. On a Mac,
omarchy-mac-boot's drop-ins then add the Asahi firmware hooks, but its
encryption drop-in reads busybox encrypt as a Mac unlocked through
cryptdevice= and leaves the line alone. The image came out as
"... block encrypt asahi omarchy-vendorfw filesystems ...", with no
systemd, sd-encrypt or omarchy-mac-encrypt, so an encrypted Mac could not
unlock its disk. Reproduced against an M1 Pro's installed drop-ins.
The baseline now asks omarchy-hw-platform. Apple Silicon starts from the
systemd line its boot package builds on. A Mac whose own mkinitcpio.conf
carries the asahi hook, or busybox encrypt, keeps that line, as a legacy
install needs. x86, Snapdragon and generic aarch64 keep today's busybox
line, as does a machine with no detector. The detector is the runtime's,
or else the copy omarchy-settings ships for its platform guard, so a
settings package newer than the runtime still places a Mac. A detector
that cannot place the machine stops the build.
The HOOKS test now composes the baseline on each platform and on legacy
Mac lines. A new test composes it with omarchy-mac-boot 20260926-1.45's
drop-ins, kept as fixtures, and checks the exact HOOKS the M1 Pro builds.
The NVIDIA test pins its platform.
* Hide optional installs that have no package on this machine
Not every optional install exists on every architecture: some vendors ship no
aarch64 build, and some repositories don't carry a package at all. Each
Install row now carries a `when:` guard naming every package its install
command adds, checked with omarchy-pkg-available, so a row with nothing to
install disappears instead of failing halfway through a pacman transaction.
The rows that build from the AUR (Chrome, Edge, Brave, Brave Origin, Zen,
NordVPN) are guarded on the CPU architectures their vendors publish for.
omarchy-pkg-available asks the sync databases: one `pacman -Slq` per batch,
and `pacman -Sp` for a name that list doesn't hold, so a provided name
(libappindicator-gtk3) and a version constraint resolve the way `pacman -S`
will. The menu's guard prelude sources the same function, so the whole batch
still costs one fork. omarchy-reinstall-pkgs reports and skips a default
that no configured repository offers instead of failing the transaction.
On x86_64 every guarded row resolves against the stable and edge
repositories, so the menu is unchanged there.
Tests derive each row's packages from its install command on x86_64 and
aarch64 and fail when the guard names different ones.
Co-authored-by: Ryan Hughes <1630358+ryanrhughes@users.noreply.github.com>
* Run 1Password with software GL where there is no render GPU
Electron draws a blank window when the machine has no DRM render node: a VM,
a headless or passthrough GPU, or a GPU whose driver isn't up yet (Apple M3
and later on Linux today). omarchy-hw-render-gpu tells whether a renderD node
exists, and omarchy-cmd-electron-gl-args prints --ozone-platform=wayland
--disable-gpu only when it doesn't.
omarchy-cmd-electron-gl-wrap installs a marked PATH wrapper in /usr/local/bin
that execs the real binary with those flags, so it follows the machine rather
than the moment it was installed. It refuses to replace a launcher it didn't
write, or to wrap a binary over itself, and stages the write so a failure
leaves the old launcher in place. omarchy-cmd-desktop-exec-repair points the
user's desktop entry at the wrapper by replacing only the executable token,
keeping arguments, other actions and customizations, with a backup.
The 1Password installer uses them when the machine has no render node. With
one, which is every x86 machine with a working GPU, nothing changes.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Record with wf-recorder when gpu-screen-recorder can't start
gpu-screen-recorder knows NVIDIA, AMD, Intel and Broadcom GPUs and aborts
during GL init on anything else, such as Apple Silicon's, before a recording
starts. Screen recording now falls back to wf-recorder, when it is installed,
if gpu-screen-recorder is missing or exits before the file appears. The
picker, file naming, notifications and post-processing are shared. wf-recorder
records one audio device, so desktop plus microphone go through a temporary
null sink that stop unloads, and its track is pinned to 48 kHz. Portal
capture and --resolution stay gpu-screen-recorder only.
The finalize pass keeps 48 kHz too: loudnorm declares 192 kHz output, so
without -ar the AAC encoder wrote 96 kHz from a 48 kHz recording, which many
editors decode as silence, and which on Apple Silicon makes the speaker
safety daemon lock the amps. #11092 makes the same one-line change; whichever
lands second drops it.
Stop and status matched `pgrep -f ^gpu-screen-recorder`, which misses
wf-recorder and matches any command line that starts with that path. The
script now records the recorder's pid, and the new
omarchy-capture-screenrecording-process checks or signals it by its
executable, through a pidfd opened before the check so a reused pid can't be
signalled. When that pid is no longer a recorder (a recording started before
this change, or one that died), every recorder the user runs counts, as it
does for the bar indicator and the Stop row, which use the same helper.
Where gpu-screen-recorder works, which is every x86 GPU it supports today, it
records as before.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
Co-authored-by: Issac Trotts <15530+ijt@users.noreply.github.com>
* Hold logind's lid switch while a display it doesn't count is in use
systemd-logind treats a closed lid as docked only when an external display
is on a connector type it lists (HDMI, DisplayPort, DVI, VGA...). Displays on
"USB" connectors, which Apple Silicon uses for USB-C displays, aren't on that
list, and a missing comma fuses DVI-A and Composite into one entry. With such
a display in use, closing the lid suspended a laptop that was still being
used on it.
omarchy-system-lid-inhibit holds a handle-lid-switch inhibitor while one of
those connectors is connected and enabled in Hyprland, on a laptop where
HandleLidSwitchDocked is ignore, which is exactly the case logind would have
ignored had it counted the display. It releases the inhibitor when the display
goes. A compositor or logind that doesn't answer changes nothing, since
releasing then would suspend a closed lid. The inhibitor runs as a transient
user unit bound to the monitor watcher's unit, so a watcher that dies can't
leave the lid inhibited until logout.
The monitor watcher runs it alongside the clamshell sync. On a machine whose
external displays are all on connectors logind counts, it does nothing.
* Keep the focused workspace in front when a monitor goes away
When a monitor is unplugged, or the lid turns off the laptop panel, Hyprland
first warps focus to whichever monitor it lists first and only then moves the
removed monitor's workspaces over. The workspace you were on lands hidden
behind that monitor's own (Hyprland 0.56.2, CMonitor::onDisconnect).
monitor.focused fires before Hyprland records the new focus, so the monitor
still reported as active is the one being left: its workspace is remembered
until the event loop next turns, and a monitor.removed in that window focuses
it again once the removal has finished. An open special workspace already
comes along, and any focus or workspace change made before the restore runs
wins.
* Drive the keyboard backlight from the ambient light sensor
Laptops with an ambient light sensor and a keyboard backlight LED now light
the keys in the dark and dim them as the room brightens, inverse to display
auto-brightness. omarchy-brightness-keyboard-auto polls the sensor every five
seconds (preferring an IIO device that names itself ALS, and converting a raw
channel with its offset and scale) and maps lux onto the LED, with a deadband
so it doesn't chase noise. It pauses while the screen is locked or the lid is
closed.
A level set with the keyboard-brightness keys wins until the light changes
enough that it no longer fits. omarchy-brightness-keyboard records each level
it sets in the session's runtime directory (a private state directory without
one), so the loop can tell keys turned off on purpose from keys left off by
the lock screen or a restored zero, which it lights again.
The user unit runs it in the graphical session. Its ExecCondition asks
--available, so it is a no-op on machines without both a sensor and a
keyboard LED. First run enables it, and a migration enables it for existing
installs, writing the enable symlink itself when there is no live user
manager.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
Co-authored-by: Gabe Ohlson <3355909+6abe@users.noreply.github.com>
* Defer an image build's hardware setup to the machine's first boot
An image is built away from the machine it will run on, so its hardware
leaves would probe the builder. While the image manifest the detector
already reads (/var/lib/omarchy/image/target) exists, omarchy-apply-hardware
queues every leaf of install/hardware/all.sh instead of running it and arms
omarchy-provision-hardware.service. On the first boot, before owner setup
and the login screen, omarchy-provision-hardware retires the manifest and
runs the queue on the real hardware: a failed step stays queued for the
next boot (exit 75), and the initramfs is rebuilt once after the last step
when a step changed its inputs or asked for it. Bluetooth starts its
service on that boot, whose target is already up. Root ignores any
environment override of these paths. Without a manifest, as on every
ISO install, hardware setup runs exactly as before. Nothing here fetches
package databases: an image carries its sync databases and every package
its steps install.
* Give an image-built root a UTF-8 locale when LANG is still C
A root built from a distribution tarball instead of the ISO (Arch Linux
ARM's ships LANG=C) never went through the ISO's locale step, so it ran
non-UTF-8. The config phase now selects en_US.UTF-8, generating it if
needed, but only when LANG is unset, C or POSIX: any named locale is
somebody's choice and stays. The ISO writes the locale before this runs,
so an ISO install is unchanged.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Let a platform's packages run their own boot, system and user setup
Three dispatch operations give a platform one place for the setup it
needs beyond Omarchy's, instead of inline platform branches in the setup
leaves. The last hardware leaf runs setup-boot, then setup-system, as
root, so an image build queues them with the others and its first boot
runs them, with image-first-boot (offline-safe, asking for the one boot
rebuild instead of building). setup-boot belongs to the boot package and
is required on Apple Silicon, where hardware setup has no other way to
run the Mac's boot setup; a failed setup-boot stops the leaf before
setup-system. setup-user runs as the user being set up, refuses root,
and gets only the user's home and session variables: from the last user
leaf at finalization (skipped inside an image build, where the detector
would read the build host) and again from first run once the session is
up. On Apple Silicon setup-boot resolves in omarchy-mac-boot's directory
and the other two, both optional, in omarchy-mac's, /usr/lib/omarchy/mac;
x86, generic aarch64 and Qualcomm register none, so all three are no-ops
there.
A failed finalization now keeps first run pending, like any failed
first-run step, instead of letting the other steps mark it complete.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Find batteries, lids and panels that aren't named like a PC's
Laptop detection assumed PC names: a battery called BAT*, a lid in /proc/acpi, a
touchpad or trackpad in the device name, a GPU backlight. Apple Silicon has
none of them (macsmc-battery, the SMC lid switch, apple-mtp-multi-touch,
apple-panel-bl), and some Framework and Chromebook firmware calls its battery
CMB0, so those machines lost the battery panel, laptop-only services, lid
handling, the touchpad toggle and display brightness.
- Battery presence goes by type instead of name, and skips a battery scoped
Device (a wireless mouse or keyboard), so a desktop with one isn't a laptop.
- Battery status takes the first UPower battery that powers the machine, reads
thresholds and cycles from it (BAT* stays the fallback), shows the magnitude
of a signed power_now, and rounds the percentage half-up for display like the
bar, while the charge-hold check keeps the unrounded value.
- A laptop is also one with any input device that reports a lid switch
(SW_LID), whatever its name: Apple's SMC input device has the same name on a
Mac mini, where it reports none. Whether the lid is closed comes from ACPI
where there is an ACPI lid, as before; otherwise logind is asked, with a
short timeout, only when a device reports a lid switch.
- A device named touchpad or trackpad still wins; only when there is none does
the touchpad match take a mouse udev marks as a touchpad (Apple's
multi-touch trackpad), never a touchscreen.
- The platform package can describe its displays in
/usr/share/omarchy-platform/displays.conf: backlights that never drive the
panel (a Touch Bar's), the panel's own where there is no GPU backlight, and
a display driver without DDC channels, where a probe walks unrelated I2C
buses for about 3 s: there only a connector with a ddc node is probed, and
a monitor without one reports no brightness instead of dimming the built-in
panel. Omarchy ships no such file and names no device in it; without it the
backlight choice and DDC probing are unchanged.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
Co-authored-by: Ryan Hughes <1630358+ryanrhughes@users.noreply.github.com>
Co-authored-by: Sparsh Kumar <22640187+skkumarsparsh@users.noreply.github.com>
Co-authored-by: dl-alexandre <166029845+dl-alexandre@users.noreply.github.com>
* Keep the user's XDG config and state directories in platform user setup
setup-user runs with an allowlisted environment, which dropped
XDG_CONFIG_HOME and XDG_STATE_HOME. A platform's user setup reads the user's
configuration and keeps its setup markers there, so for a user who sets
either it looked in, and wrote to, the default directories instead. Both now
pass through with the rest of the session; XDG_DATA_HOME and everything else
still don't.
* Let a platform package add Hyprland defaults of its own
A platform's runtime package may need Hyprland defaults of its own (a Mac's
capture keys and three-finger workspace swipe, say), and Omarchy's config
should carry no branch for them. Three directories under the platform root,
/usr/share/omarchy-platform, now take them. The one installed platform package
owns that root, Omarchy reads it by literal path and ships nothing there, so
without a platform package nothing changes. Each file is loaded by its path,
so it can't shadow a module, and one that fails is logged and shown as a
notification while the rest of the config loads.
- hypr/defaults/*.lua loads before Omarchy's defaults. A
chord bound there replaces Omarchy's default for it, and a decorator it adds
runs before every later bind, so it can bind what has to run first. It sees
the command a bind runs, including the command a shell shortcut such as
{ menu = "root" } stands for when it binds the shell's global shortcut.
- hypr/settings/*.lua loads after Omarchy's defaults and
before the theme, for settings that replace Omarchy's.
- hypr/gestures/*.lua loads after the user's files, only for what must
see them: the gesture registry, which now records on every platform, lets a
default gesture step aside for the user's own.
The user's files load after the first two and can unbind or rebind any of it.
* Let a platform package name keys in the keybindings menu
The keybindings menu shows a key by its keysym, which on some keyboards is not
what the key says: on a MacBook, XF86MonBrightnessUp and Down are the F2 and
F1 keys, so the menu listed rows like "SHIFT + XF86MonBrightnessUp". A
platform package can now list names for such keys in /usr/share/omarchy-
platform/key-names, one "<keysym> <name>" per line, and the menu shows those
instead. The file is part of the menu cache's key, so installing or changing
it refreshes the menu. Without it the menu is as before.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
Co-authored-by: kx0101 <90087463+kx0101@users.noreply.github.com>
* Keep the top bar out of a panel's camera cutout
A laptop panel with a camera notch at its top (Apple Silicon MacBooks, for
one) covered the middle of the top bar, and a bar shorter than the notch left
a sliver of every window beside the camera. Omarchy knows no panel's cutout
itself: the platform's own package describes them in
/usr/share/omarchy-platform/display-cutouts.json, by
connector, physical mode and the rows the cutout covers. On a described panel
a top bar is never shorter than the cutout, its center section sits beside
the right one, and hiding it parks all of it; [bar] notch-height sets that
floor by hand, on that panel only. Without a description, which is every
machine without such a package, the bar is as before.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Keep PC and Intel Mac quirks off Apple Silicon
A few hardware steps and commands written for PCs and Intel Macs do the wrong
thing on Apple Silicon:
- The Intel Mac Wi-Fi quirk turns off the Broadcom firmware supplicant for
every Mac whose DMI vendor is Apple and whose chip is on its list. Apple
Silicon reports Apple Inc. through U-Boot's DMI tables, and the M1 Pro and
Max's BCM4387 is on the list, but there the firmware supplicant is the one
that works: turning it off makes firmware commands time out and breaks
scanning. The install step and its migration now skip Apple Silicon.
- fnmode=2 for Apple-like keyboards is left to the Mac's own platform package
on Apple Silicon, where the built-in keyboard's top row depends on it.
- The SPI keyboard check reads the DMI product name without failing the
hardware pass where there is none.
- The Windows VM, an x86_64 guest under KVM, runs on x86_64 only, and direct
boot (U-Boot finds the bootloader and makes its boot entries itself)
refuses Apple Silicon; the menu hides both there. Hibernation setup skips
it; its kernel has no hibernation today, and resume there is not wired.
Everywhere else each step runs as before.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Let a platform's runtime package adjust the apps it needs to
Some apps need a platform's own touch once they are installed. On Apple
Silicon, Chromium-family browsers must decode video in software (the hardware
decoder renders green frames in them), and Steam only runs through a
FEX launcher, which then depends on Steam and must go before Steam can be
removed. Until now the Mac runtime did this with inline branches in the
installers.
Two optional dispatch operations take their place. post-install <app> runs
as the user once an installer has installed the app and finished its own
setup of it: every browser install (after its flags file, before it says the
browser is installed) and Steam's (after the package, before the 32-bit
drivers and the first launch). pre-remove <app> runs before Steam's removal
drops its packages. Where a platform registers them they refuse root (a
platform with nothing registered is a no-op, so sudo installs still work
there) and get the same home and session variables as setup-user, and a
failure fails the install or stops the removal. On Apple Silicon they
resolve in omarchy-mac's directory; every other platform registers none, so
installs and removal run as before there.
Steam's 32-bit driver step also stops failing the install on a machine with no
Intel, AMD or NVIDIA GPU (Apple Silicon, but also Snapdragon or a VM): it has
nothing to add there and now says so.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Ask the platform's boot package before an update takes over its files
When an Omarchy package's upgrade stops only on files no package owns, the
update moves them aside and retries. On a Mac some of those files belong to its
boot chain or were written by its image (initramfs and pacman configuration,
say), and moving them aside can leave it unable to boot or update. The Mac
runtime refused those paths with an inline list.
update-takeover <path>... asks the platform's boot package instead, before
anything moves: read-only, exit 0 lets the takeover go ahead, anything else
keeps every file and the update fails as it did before the retry existed. The
update resolves it as the user and runs it with sudo only where it resolves,
inside the authorization the move needs anyway. On Apple Silicon it is
required: a Mac without its boot package, or with one too old to answer, keeps
its files. Every other platform registers none, so the takeover goes ahead as
before, asking no one.
* Install the Mac's package set on Apple Silicon
omarchy-pkg-defaults already composes install/omarchy-<platform>.packages
after the base and aarch64 lists, but Apple Silicon had no list, so a Mac got
the generic aarch64 set: no omarchy-mac or omarchy-mac-boot, whose entrypoints
the dispatcher requires there.
install/omarchy-apple-silicon.packages names the Mac's runtime and boot
packages, plus the defaults an owner may remove: the startup-volume tool,
hardware video decode, Widevine, and wf-recorder, since gpu-screen-recorder
can't capture on Apple Silicon and screen recording falls back to it. What a
Mac can't work without (its speaker stack, PipeWire's PulseAudio server and
realtime scheduling, the GPU's Vulkan driver) comes as omarchy-mac's
dependencies, so a new Mac driver needs no Omarchy change. Other platforms
compose exactly what they did.
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
* Give existing aarch64 machines the zram swap Omarchy configures
omarchy-settings configures swap on zram and tunes memory reclaim for it.
x86_64 gets zram-generator from its installer and new aarch64 installs from
their default packages, but an aarch64 machine installed before
omarchy-aarch64.packages named it has no generator, so the configuration asks
for a device nothing creates.
Migration 1790328426 installs zram-generator where the platform's default
packages name it and it is missing, and starts the zram swap wherever it is
down, unless no device is configured (then the generator makes no swap unit)
or the administrator masked it. A swap that won't start, or whose unit can't
be read, only warns: the generator's unit starts with every boot, so a reboot
retries it, and failing the migration would stop every update. A failed
install waits (75) for the next migrate, as it does where the platform can't
be told, changing nothing. It keeps the name it has in omarchy-mac, so Macs
that already ran it skip it. It also retires an /etc copy of the drop-in
identical to the packaged one, which earlier Mac setups made and which would
hide the packaged file's updates; an edited copy stays. On x86_64 the default
packages don't name the generator and there is no copy, so it does nothing.
* Keep an ARM machine's repositories when it changes channel
omarchy-refresh-pacman and install finalization copied x86_64 templates
everywhere, so on an aarch64 machine a channel change or a finished install
wrote Arch Linux's x86 [core]/[extra]/[multilib] and dropped Arch Linux ARM
and the platform's own repositories.
Channel changes now tell the platform apart. x86_64 is unchanged. On aarch64
the machine's configuration is kept, with its repositories, mirrors and
signature policies, and only the [omarchy] server moves to the new channel;
a configuration whose Omarchy server can't be identified is refused
unchanged, and one with no Omarchy repository gets it at the end (not on
Apple Silicon, whose own repository must not come first). Only pacman.conf is
backed up and rewritten, straight from this process through sudo, never
through a file another process could change first.
Before that, a channel must be qualified for the platform: a signed upgrade
and reboot tested there, not only a published repository. x86_64 keeps
stable, rc and edge; Snapdragon and other aarch64 machines take edge, as
dragon does; Apple Silicon takes none until the Mac packages qualify on edge.
The refresh then checks, as the user, that the channel's omarchy.db
publishes the runtime pair, and on Apple Silicon omarchy-mac and
omarchy-mac-boot. omarchy-channel-set refuses an unqualified channel before
anything else, the dev confirmation included.
Install finalization writes a whole template, as before on x86_64. Other
aarch64 platforms get Arch Linux ARM (dragon's pacman-aarch64.conf and
mirrorlist-aarch64, byte for byte) with its keyring, plus Omarchy on a
qualified channel. Apple Silicon gets omarchy-mac's template on a qualified
channel and keeps its image's configuration otherwise. In an image build,
finalization leaves the keyring to the machine's first boot, so machines
flashed from one image don't share a pacman master key. On an unqualified
channel, a machine that gets no Omarchy repository is told why.
Co-authored-by: Birk Skyum <74932975+birkskyum@users.noreply.github.com>
Co-authored-by: Jim Martin <1334144+jdvmi00@users.noreply.github.com>
* Keep Arch Linux ARM's signing keys current on ARM machines
omarchy-update-keyring reinstalls archlinux-keyring before every system
upgrade, since keys change without a version bump. An aarch64 machine's Arch
Linux ARM repositories are signed with keys from archlinuxarm-keyring, which
nothing refreshed, so a rotated ALARM key failed the upgrade that followed.
On an aarch64 platform where archlinuxarm-keyring is installed, it is
reinstalled in the same transaction. On x86_64 the keyring step runs exactly
as before, even where that package happens to be installed.
* Mark the runtime's platform files as deciding at runtime
The omarchy-settings recipe leaves the mkinitcpio, Limine, memory and USB
drop-ins out of its aarch64 package, because they used to assume x86_64. That
leaves an aarch64 install without the HOOKS baseline its initramfs needs, and
without the zram and reclaim settings every other Omarchy machine gets.
Each of those files now decides at runtime whether it applies: the HOOKS
baseline asks the platform, thunderbolt is added only where the kernel has the
module, the Limine files are read only where Limine is installed, the oomd
drop-ins are inert without systemd-oomd, aarch64 installs the zram generator
the zram drop-in needs, and the USB autosuspend option is ignored by a kernel
that builds usbcore in. default/settings-runtime-profile says so, file by
file; a recipe that finds it ships the same files on every architecture
(omacom/omarchy-pkgs#638), and older sources keep today's aarch64 package. A
test checks that every file it names exists and that each runtime decision is
in place.
* Set the wireless regulatory domain from the owner's timezone at first-boot setup
A deferred install (an Apple Silicon image, an OEM ISO install) runs its
hardware steps before the owner picks a timezone, on the UTC placeholder,
which names no country, so install/hardware/set-wireless-regdom.sh leaves the
regulatory domain unset and the radios stay on the world domain. Owner setup
now runs that step again once the owner's timezone is set, and applies it to
the radios through wireless-regdb's helper, since no reboot follows setup. An
up-front install, which picks the timezone before its hardware steps, is
unchanged.
* Ask again for a keyboard layout first-boot setup could not apply
First-boot setup kept going when the chosen layout did not take: loadkeys
errors were ignored, a keymap localectl did not list kept the default, and a
failed or partial write of /etc/vconsole.conf was only logged. The owner then
typed the disk password in one layout while the boot files asked for it in
another, and could only unlock the disk by typing US-style.
The keyboard step now fails closed. On a console loadkeys must succeed, the
keymap must be in localectl's list exactly (a list localectl cannot produce
counts as a failure too, logged apart), and after systemd-firstboot or the
localectl fallback, /etc/vconsole.conf is read back in a clean shell and must
name the chosen keymap with an XKB layout. Any failure is logged, the owner is
told the layout could not be set, and the layout is asked for again, so the
password form is never reached with a layout that did not take.
* Refresh a platform's own signing keyrings before an update
omarchy-update-keyring reinstalls archlinux-keyring (and Arch Linux ARM's on
aarch64) before the system upgrade, so a rotated key never fails that
upgrade's signature checks. A platform's own repository has a keyring of its
own that nothing refreshed: on Apple Silicon, asahi-alarm-keyring for
[asahi-alarm].
A platform package now lists those keyrings in
/usr/share/omarchy-platform/keyrings, one package name per line, and
omarchy-update-keyring reinstalls each one that is installed in the same
transaction. It adds no repository the machine didn't already trust. A line
that isn't one keyring package name is skipped with a warning, so a mistake in
the list can't stop every update. Without the file, as on x86 today, the step
runs as before.
* Drive an untyped default input from the audio panel
Quickshell types only exact media classes, so a virtual source (an
Audio/Source/Virtual: EasyEffects' source, or the microphone mapping a
platform package adds) has no PwNode.audio. With one as the default input the
audio panel showed the input at 0% and its slider changed nothing, the peak
meter stayed flat, the microphone widget read it as muted, and the panel could
not list it unless it was already the default.
Such an input now goes through wpctl for its volume and mute (UntypedInput in
shell/Commons, one reader and one pactl subscribe for the whole shell), the
panel meters it with the new omarchy-audio-source-level, and the input list
takes an untyped node PulseAudio lists as a source.
A platform whose audio runs through its own processing can also name nodes
that are neither devices nor apps in /usr/share/omarchy-platform/audio.json,
which Omarchy ships none of: the panel and the microphone widget leave them
out, and a "replaced" node only while its replacement exists. Without the
file nothing is hidden.
On every machine: the input list leaves out a source whose ports are all
unavailable (an empty headset jack) unless it is the default, as the output
list does for sinks; choosing an input no longer moves a filter's own capture
(a source output with node.link-group), which would feed the filter from the
new input or from itself; the shell's own level meters no longer light the
microphone widget; and omarchy-audio-sink-availability reads pactl in the C
locale for sinks too, since "not available" is translated.
* Give first-boot setup an XKB layout for keymaps systemd cannot map
systemd-firstboot writes an XKB layout only for keymaps listed in
kbd-model-map. Nine the setup form offers are not (Colemak, Azerbaijani,
Bulgarian, Czech, Swiss German, Kyrgyz, Norwegian, Polish, Ukrainian), so it
wrote KEYMAP= alone, the readback refused it, and owner setup could never get
past the keyboard step for them.
Setup now adds the XKB settings itself when firstboot left none, matching the
console map so the shared password types the same on the desktop: kbd's cz is
QWERTY, bg-cp1251 phonetic and azerty French AZERTY, and non-Latin layouts keep
a US layout to switch to, as kbd-model-map's own rows do. A test checks every
layout the form offers ends with an XKB layout.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Leave keys a firmware key turned off dark
The ambient keyboard backlight treated any 0 the keyboard-brightness keys had
not recorded as an accident and relit it within five seconds. Firmware Fn keys
(Fn+Space on ThinkPads and Framework laptops, Fn+F10 on Dells) write the LED
directly, so on those laptops turning the keys off never stuck.
The lock screen's off now records a blank, and only that blank or a 1%
leftover is taken back; any other off is a choice that pauses auto like the
keys do. A wake clears the blank only once the restore succeeded, the command
runs one at a time so a blank racing a wake can't leave the LED and record
disagreeing, and the loop drops a blank once it has taken the keys back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Drop the optional-install availability guards
Each menu open checked every Install row against the sync databases, which
costs most of a second of pacman work on every machine to hide rows only an
ARM machine is missing. The fix for those is publishing the ARM packages, not
hiding the rows, so this removes omarchy-pkg-available, the Install rows'
availability and architecture guards, and reinstall's skipping of defaults no
repository offers.
The Windows VM row keeps its x86_64 guard, since no package fixes a guest that
needs an x86_64 host, and reinstall keeps its aarch64 channel handling.
This reverts commit aea001cbde4d0852b311ef42b5e8c380c719f833.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Put the current root back when the factory root cannot take its place
Factory reset moved the running root aside, marked the reset committed, then
renamed the staged factory root to @. If that second rename failed, cleanup
saw a committed reset: it skipped the boot package's rollback and kept the
throwaway LUKS slot, and since the swap had not happened it deleted the staged
root as well, leaving nothing at @.
The reset now commits only once the factory root is at @. A failed activation
renames the old root back first, then cleanup rolls the platform's boot state
back and revokes the throwaway slot. If that rename fails too, both roots are
kept, cleanup still rolls back and revokes, and the error names the rename
that recovers the machine. The generic Limine path has already rebuilt its
boot files from the factory root by then, as it had before this change.
* Leave other recorders alone when the saved recorder pid is stale
A saved pid that no longer named a recorder made status and stop select every
gpu-screen-recorder and wf-recorder the user ran, so stopping could interrupt,
and after five seconds kill, a recording started somewhere else.
A stale pid now means no recording of ours is running: the toggle starts a new
one, and --stop-recording finds nothing to stop. Stop acts on the recorder the
status check selected rather than selecting again. Without a saved pid, as for
a recording started before the pid was saved, every recorder the user runs
still counts.
* Name the audio a wf-recorder recording could not capture
With desktop and microphone audio both requested, wf-recorder records a null
sink fed by two loopbacks, and a loopback that failed to load was ignored: the
recording went ahead without that source, or silent when both failed. A
missing null sink or default device dropped audio just as quietly.
A source that cannot be captured is now left out with a notification naming
it, the loopbacks that did load are unloaded, and the recording carries the
source that remains, or no audio track at all.
* Show the Edge, Spotify and Dropbox installers on x86_64 only
These vendors currently publish Linux builds for x86_64 alone (the AUR's
microsoft-edge-stable-bin and spotify are arch=('x86_64'), and Dropbox lists
no Linux ARM client), so no native aarch64 build is available and choosing
one on an ARM machine only fails. Like the Windows VM row they keep a uname
check, which the menu answers once per open without asking pacman. The other
Install rows are unchanged.
* Revoke the generic reset's throwaway slot when the reset fails
The generic Limine path added its throwaway LUKS slot without recording it,
so a reset that failed before activation never revoked it. Once the factory
root's UKI was on the ESP, a failed activation that put the old root back left
a machine that booted the factory kernel and unlocked unattended with a key
readable on the ESP, while the screen said the current root was back.
The generic slot is now recorded like the platform's and revoked by cleanup
whenever the reset fails before the factory root is active. A failed
activation on that path says the boot files were already rebuilt from the
factory system and to run limine-update before rebooting.
* Clear a stale recorder pid when a recording starts
The recorder's pid is saved only once its file exists. Until then a pid left
by a recording that ended without a stop still counted as stale, so pressing
the toggle again while the new recorder started found nothing of ours and
could start a second recording. Starting a recording now clears that pid,
and during startup the toggle falls back to the user's recorders as before.
* Word the dropped-audio notice for every reason a source is left out
The notice said the dropped source "could not be captured", which was wrong
when the null sink itself failed to load: the microphone was never tried, and
desktop audio recorded alone as it always has. It now says the source could
not be added to the recording, true for a missing device, a failed loopback
and a failed null sink alike.
* Show the Heroic and Minecraft installers on x86_64 only
Heroic publishes Linux builds for x86_64 alone (its arm64 builds are for
macOS and Windows), and Mojang's Linux launcher is a single x86_64 tarball.
They join Edge, Spotify and Dropbox under the same uname check, so every row
whose vendor ships no native Linux aarch64 build is treated alike.
* Ask the recorder script whether its stop has a recording to end
The bar indicator and the menu's Stop Screenrecording row asked the process
helper whether any recorder the user runs was alive, while the toggle's stop
acts only on the recording it saved. With a stale saved pid alongside an
unrelated recorder, both offered a stop that did nothing.
omarchy-capture-screenrecording --status answers with the selection the stop
uses, and both now ask it. Status needs no recordings directory and never
notifies, so the recordings-directory check moved below argument parsing and
skips it.
* Copy every platform's pacman templates into place, as x86_64 does
On aarch64 a channel change edited the machine's pacman.conf in place: an awk
pass found the [omarchy] server and rewrote its channel, appended a section
where there was none, checked the channel's omarchy.db first, and gated it all
on a per-platform table of qualified channels. Install finalization appended
Omarchy to a template, or on a Mac copied one bundled inside omarchy-mac. What
a machine ended up with was never in the repository.
Each platform now has a pacman.conf and mirrorlist per channel, copied whole:
x86_64's stay in default/pacman, Snapdragon and other aarch64 machines use
default/pacman/aarch64, and Apple Silicon default/pacman/apple-silicon, with
Omarchy and Asahi ALARM ahead of Arch Linux ARM. Omarchy publishes aarch64
packages on edge alone so far, so every ARM channel points at edge for now and
channel changes work there; moving a channel is a template edit. A channel
without both files is refused before anything changes, including a dev
checkout's own templates. Reinstall goes back to resetting to stable, and the
update confirmation loses the missing-repository notice it no longer needs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Revert "Copy every platform's pacman templates into place, as x86_64 does"
This reverts commit 9a07e138cc6c3a19b58d2f3d7eb536dd3042ccc5.
* Copy every platform's pacman templates into place, as x86_64 does
On aarch64 a channel change edited the machine's pacman.conf in place: an awk
pass found the [omarchy] server and rewrote its channel, appended a section
where there was none, checked the channel's omarchy.db first, and gated it all
on a per-platform table of qualified channels. Install finalization appended
Omarchy to a template, or on a Mac copied one bundled inside omarchy-mac. What
a machine ended up with was never in the repository.
Each platform now has a pacman.conf and mirrorlist per channel, copied whole:
x86_64's stay in default/pacman, Snapdragon and other aarch64 machines use
default/pacman/aarch64, and Apple Silicon default/pacman/apple-silicon, with
Omarchy and Asahi ALARM ahead of Arch Linux ARM. Omarchy publishes aarch64
packages on edge alone so far, so every ARM channel points at edge for now and
channel changes work there; moving a channel is a template edit. A channel
without both files is refused before anything changes, including a dev
checkout's own templates. Reinstall goes back to resetting to stable, and the
update confirmation loses the missing-repository notice it no longer needs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Drop the migrations no installed machine needs
Fresh installs mark every migration done, and every Apple Silicon machine is a
fresh install, so the Mac handoff to omarchy-mac-boot (and the migrate
operation it called), zram for aarch64 installs that predate the package list,
the Brave Widevine link and the Broadcom migration's Apple Silicon guard never
run anywhere. The exit-75 deferral in omarchy-migrate existed only for two of
them. Keyboard backlight auto stays on new installs through first run rather
than switching on for existing ones, and migration 1786605598 goes back to
quattro: the mkinitcpio drop-ins arrive by package.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop the generic changes Mac support doesn't need
The pacman platform guard (and the detector copy omarchy-settings shipped for
it, which the HOOKS baseline fell back to), the lint against uname gates, first
run staying pending after a failed finalization, run_logged echoing failures to
stderr, half-up battery rounding, hiding empty-jack inputs and leaving a
filter's capture alone when choosing an input all rode along with the Mac work
without serving it. Each goes back to quattro's behaviour and can come back on
its own merits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop the platform Hyprland slots
A platform package could load its own Hyprland binds, settings and gestures:
binds that replaced Omarchy's chords and decorated every later bind, settings
ahead of the theme, and gestures after the user's files, which needed every
hl.gesture call wrapped so a default could step aside. On a Mac those carried
macOS habits (a three-finger workspace swipe, tap-to-click off, menus opening
on the built-in screen, other screenshot and keyboard-backlight chords), not
anything the hardware needs, and Omarchy should behave the same on every
machine. The one bind a Mac does need, its lid switch under the SMC's name,
joins Omarchy's own lid binds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Keep aarch64 machines off the release channels until a release supports them
Every aarch64 channel's template pointed at the edge repository, but a switch
to stable or rc installs the release-named omarchy and omarchy-settings. On
edge/aarch64 those are the 4.0.4 release line, which has no omarchy-hw-platform,
lifecycle dispatch or omarchy-pkg-defaults, so `omarchy-channel-set stable` on
a Mac replaced the quattro runtime it runs with one that cannot run it, and
succeeded.
aarch64 platforms now have edge templates alone, so a switch to stable or rc is
refused before anything changes; adding those templates once a release
supports aarch64 opens them. Refresh with no channel named, as reinstall runs
it, and install finalization for a channel the platform has no templates for
take the platform's default channel: stable on x86_64, edge on aarch64.
* Leave a filter's own capture alone when choosing an input again
d0b94d98c returned omarchy-audio-input-set-default to moving every source
output, as not serving Mac support. It does: asahi-audio's microphone DSP on
Apple Silicon records the raw array through a filter-chain capture stream
(audio_effect.jNNN-mic, node.link-group "filter-chain-..."). On an M1 Pro,
choosing the Mac's microphone in the audio panel moved that stream onto the
DSP's own output, and the microphone recorded digital silence (-91 dB, from
-55 dB of room noise) until PipeWire restarted. With the skip it keeps
recording (-54 dB).
This restores the skip of source outputs carrying node.link-group, and its
test, as they were before that commit.
* Say aarch64 offers edge alone where the templates are described
The aarch64 edge templates, the template helper and the update-process docs
still said every platform has a template per channel and every ARM channel
installs from edge, which the previous commit made untrue.
* Point the Mac packages at omacom/omarchy-mac-pkgs
omarchy-mac and omarchy-mac-boot now live in omacom/omarchy-mac-pkgs, with
each package at the top level. The dispatcher, its documentation and the
Apple Silicon package list name that repository, and the drop-in test's
fixtures, byte for byte the same as omarchy-mac-boot/files/etc/mkinitcpio.conf.d
at 4399105, cite it there.
* Stop naming a platform migration among the dispatcher's callers
The migrate operation went with the Mac migrations, and no caller hands a
machine to a platform migration any more, so the header lists only the
flows that still dispatch. The optional-operations comment is rewrapped.
* Keep the Intel Mac Broadcom migration off Apple Silicon again
A fresh install marks every migration done only for the user it creates, so
an account added later runs them all on its first update. On an Apple
Silicon Mac this one matches: an M1 Pro reports sys_vendor "Apple Inc." and
a BCM4387 (14e4:4433), so it would append feature_disable=0x82000, which
stops that chip scanning once brcmfmac reloads. Restore the guard and its
test case.
* Say when Steam's post-install hook runs now that the drivers go first
Steam's installer now adds the 32-bit drivers before Steam itself, so the hook runs after both and before the first launch.
* Release fallback audio after the recorder exits
When wf-recorder ends outside the normal Stop path, status rejects the saved PID before unloading the combined-audio loopbacks. Release those owned modules when the saved recorder is gone, so the next status or stop call ends microphone capture without signaling another recorder. Leave a mix without a saved PID alone: its recorder can still be starting.
Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Codex Medium <noreply@openai.com>
* Serialize recording state changes and stale audio cleanup
Status can finish checking an exited recorder after another command has replaced its audio modules, and stop finalization can outlive the recorder while a new recording starts. Share a lock across start, stop and cleanup so each command finishes updating its resources before another changes them. Status uses a read-only check when the lock is busy, keeping the bar responsive during the picker. Close the lock descriptor in persistent children so recording and preview cleanup cannot retain it.
Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com>
* Keep automatic and manual keyboard brightness updates together
A brightness key can update the LED and its chosen-level record after the automatic loop checks for a manual change but before it applies a new target. The automatic write then overwrites the user's choice and deletes the record. Take the manual command's lock for the whole tick, skipping a busy tick and releasing the descriptor on every return. Create the private fallback directory before opening the lock outside a session runtime directory.
The normal lock screen waits five seconds before blanking, so an ordinary tick should already see the session locked. The regression exercises a brightness key arriving within a tick instead of relying on an implausibly delayed lock-state check.
Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 High via Pi <noreply@anthropic.com>
* Read the split hook baseline in the NVIDIA migration
The NVIDIA firmware migration decides whether to rebuild by sourcing the hardware and filter drop-ins. With HOOKS now defined in the earlier baseline, that partial read treats every hybrid machine as if kms had been removed. Source the new baseline first when it exists, keeping the legacy two-file layout working and skipping rebuilds when the effective hooks still include kms.
Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 High via Pi <noreply@anthropic.com>
* Use a hardware helper for x86-only menu entries
Keep the x86_64 availability checks for Windows and vendor-specific installers in a hardware predicate, so menu conditions use the same command style as other hardware checks.
Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
* Keep restored keyboard levels from pausing automatic brightness
Idle wake, quick unlock and hibernate can restore a saved level older than the ambient loop current level without the lock blanking first. Treat that restoration as a one-tick event, preserving existing manual pauses and the normal blank-and-restore path. Consume unmatched and unchanged restore records too, so a later firmware key cannot look like the earlier restore.
Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 High via Pi <noreply@anthropic.com>
* Preserve restored keyboard levels across a later lock blank
The ambient loop skips a locked session, so a restored level may still be unconsumed when the next blank saves it. Carry that origin through blanking and restore failures so fingerprint unlock cannot make the old saved level look like a manual choice. A brightness key replaces the marker with its chosen level and retains the ordinary blank behavior.
Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 High via Pi <noreply@anthropic.com>
* Preserve firmware brightness choices before a lock blank
Firmware keys change the LED without updating the record. Carry a pending restoration through the next blank only if the observed LED still matches that restoration, or is still zero from its previous blank. A firmware change otherwise follows the ordinary blank-and-restore path so automatic lighting pauses on the chosen level.
Co-Authored-By: GPT-6 in Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 High via Pi <noreply@anthropic.com>
* Change only the disk password in omarchy-drive-password
Changing the system disk's password also set the login and root passwords
to match. That changes account-password policy on every platform, x86
included, and overwrites passwords someone chose to keep different, so it
moves to its own change with an explicit confirm.
The disk change itself stays as it was: the new key must open the disk and
the old one must not before the change counts, the journal finishes an
interrupted change, and where the platform's boot checks prove the key
slots, the owner's new slot is recorded before the run finishes. The
journal's confirmed phase is now "record" and no longer needs a login user.
* Cancel a pending recording start instead of queueing behind it
Starting a recording holds the recorder lock through the picker and until the
recorder creates its file. A second toggle or a stop then waited on that lock,
with no end while a portal pick or a stuck recorder never finished, and once
the start gave up the queued toggle began another recording.
While a start is pending, a toggle or stop that cannot take the lock within a
second now leaves a cancel marker and returns. The start checks it after the
picker, after the webcam overlay and while waiting on its recorder, then stops
that recorder and releases its audio. A recorder other than the portal's that
has not created its file after 15 seconds is stopped and the next backend
tried.
* Move automatic keyboard lighting to its own change
Ambient-light keyboard backlight is a new feature on every laptop with the
sensor, not something Apple hardware needs to run Omarchy, so it leaves this
change for a separate review. The keyboard-brightness command returns to
upstream's version, and first run no longer enables the user service.
* Move workspace refocus on monitor removal to its own change
Keeping the viewed workspace in front after a monitor is unplugged changes
desktop behaviour on every machine, not just Apple hardware, so it leaves
this change for a separate review.
* Move prebuilt-image hardware deferral to its own change
Building an image away from its machine needs a manifest the detector reads,
a queue of hardware steps for the first boot, a first-boot service and a
per-machine pacman keyring. An install on the machine itself needs none of
it, so it leaves this change for a separate review with an installer and
package boundary of its own. Hardware and user setup now run where they are
installed, and the platform setup operations take no image argument.
* Move the Qualcomm platform to its own change
Snapdragon support is separable from Apple compatibility, so the detector no
longer names a qualcomm platform: a "qcom," device tree is generic aarch64,
which Snapdragon already behaves as everywhere else here, and a tree naming
both Apple and Qualcomm is still refused. linux-firmware-qcom's platform list
leaves with it.
* Keep a cancel that arrives while a recording start finishes
A stop could land after the start's last cancel check, while it saved its
recording as started, and be dropped with the marker. The start now removes
its starting marker before a final check, a request reads that marker only
after leaving its own, and one that finds the start gone waits for the lock
and acts unless the start already used its request. A recorder ended by a
cancel or a readiness timeout gets two seconds after TERM before KILL, so a
recorder ignoring TERM cannot hold the start and its lock.
* Defer an image build's hardware setup to the machine's first boot
Brings back prebuilt-image support split out of #14431 so it gets its own review. An image built away from its machine names its target in a root-owned manifest the detector reads; hardware setup is queued for the machine's first boot, run there by omarchy-provision-hardware, which also makes the machine's own pacman keyring; and the platform setup operations learn they run on an image's first boot. Apple Silicon images built by the omarchy-mac installer need this; an install on the machine itself does not.
* Rebuild the initramfs when deferred setup changes module options
mkinitcpio's modconf hook copies /etc/modprobe.d into the initramfs, but the
deferred first-boot setup only compared mkinitcpio's configuration and the
kernel command line when deciding whether to rebuild. A step that only wrote
module options, as nvidia.sh does, left the old ones in the boot image.
* Put the Omarchy repository first on aarch64, as on x86_64
quattro now searches [omarchy] before the Arch repositories (34016cfb2), and
its migration 1791403252 moves the section first in /etc/pacman.conf on every
machine. The aarch64 edge template still listed it last, so refreshing pacman
on a generic aarch64 machine would undo that migration. Apple Silicon's
template already lists it first.
* Refuse disk password changes that recovery could not settle
An interrupted change to the system disk is finished from the one key slot
the password opened. If the old password also sat in another slot, a LUKS2
change made in place and stopped before the old key was retired let the rerun
accept the new password as the old one and finish while the other copy still
opened the disk. A system disk whose current password opens more than one slot
is now refused before anything changes, naming the extra slot to remove and,
where the platform records key slots, how to record the ones kept.
The journal now notes that check (checked=1). A change journaled without it,
by an earlier version, on a disk that has other key slots may still hold a
copy of the old password, and no password entered now can show which slot is
whose, so it is left for the owner to settle by hand, in every phase, with the
commands to do it. On a disk with one slot it settles as before.
A new password that already opens a key slot is refused on every drive too:
the new key's slot is how the change is followed, and an existing copy made
the run take the wrong slot as the owner's. A settled interrupted change now
says the disk opens with the password just entered, which is true whether it
finished the change or rolled it back.
* Ignore the start marker of a recording start that was killed
A start killed before it removed its marker left it behind, and a later toggle that had to wait for the lock took that marker for a start still in progress and exited without doing anything. The marker now holds the starting process's pid and counts only while that process runs.
* Say why generic aarch64 searches Omarchy's repository first
Putting [omarchy] ahead of Arch Linux ARM follows quattro's x86_64 order and its migration 1791403252, which already reorders existing aarch64 machines. It means a package Omarchy also publishes now comes from Omarchy, and a refresh can move it there, downgrading it if Omarchy's build is older. The update-process notes and the template now say so.
* Keep a working key in the manual settlement steps
When an earlier version's interrupted change has to be settled by hand, the steps now start by finding and keeping the slot of the password to keep, test the other slots with passphrases only, never remove the kept slot, and record a Mac's slots before the journal goes. Before, removing every slot the old password opened could remove the only key when the change had not landed.
* Keep Superwhisper's x86_64-only default off aarch64
quattro now ships Superwhisper as the default dictation backend and puts
superwhisper-bin in the base list, but it has no aarch64 build. On an aarch64
machine omarchy-reinstall-pkgs and omarchy-upgrade-to-quattro asked for a
package no repository carries, Setup > Defaults offered an installer that
cannot work, new users were preset to that backend, and the dictation keys, F9
and Right Alt (AltGr on many layouts) included, were bound to it.
install/omarchy-x86_64-only.packages names the base packages only x86_64
builds. omarchy-pkg-defaults leaves them out of every aarch64 platform's set,
and omarchy-upgrade-to-quattro out of the base list it installs there. The
Superwhisper default row shows on x86_64 only, like the other x86_64-only
rows. User setup on aarch64 removes the shipped Superwhisper preset, so a new
user starts with no backend and picks one; any other selection stays.
The dictation keys are bound only once the selected backend's own program is
installed, which changes nothing where it is.
* Install the stub dictation backend where binding tests expect its keys
The dictation keys are now bound only once the selected backend's program is installed. The binding-conflict check selected Voxtype without providing it, so it no longer saw the dictation keys and failed its F9 check; the default-config check now also covers a selected backend that is not installed.
* Make the stale start marker test's dead pid reliably dead
The test killed a backgrounded sleep to get a pid that no longer runs. When the signal landed before sleep started, wait never returned and the suite timed out, about one run in three. A process that exits on its own gives the same dead pid without the race.
* Drop the virtual input level meter the panel ran through a helper
Volume and mute for an untyped input still go through wpctl; typed inputs
keep Quickshell's peak monitor. Quickshell's monitor takes typed nodes only,
so the panel hides the level bar for a virtual input rather than showing an
empty bar that reads as a silent mic.
* Find recording processes with pgrep instead of a Python /proc scan
pgrep matches the kernel's 15-character process name, so gpu-screen-recorder
is matched as gpu-screen-reco. That prefix also lets any gpu-screen-reco*
executable, or a process that renames itself, pass for a recorder, so each
match is confirmed against its /proc exe link, ignoring the " (deleted)"
suffix an upgrade leaves.
--signal re-checks that link just before signalling each recorder, which
narrows the pid-reuse window the old pidfd-based scan closed, and exits 1 when
no signal was delivered. --pid rejects values above the Linux pid limit
instead of letting bash arithmetic wrap them.
* Leave 1Password's software GL wrapper to its own change
This reverts f200105c2; the render-GPU check, Electron GL wrapper and desktop entry repair return in a separate branch.
* Keep Gliff's x86_64-only package off aarch64
quattro added gliff to the base list and a migration that installs it, but its recipe builds for x86_64 alone. It joins Superwhisper in install/omarchy-x86_64-only.packages, so aarch64 package sets leave it out, and the migration installs it on x86_64 only.
* Stub accessory enrollment in the first-boot re-key test
Owner setup now enrolls the owner's USB devices and Thunderbolt accessories after the staged LUKS key is gone, and the re-key test's provisioning run reached that step with nothing to answer it. It now stubs both and checks that enrollment runs once, after the staged unlock is retired.
* Keep USB authorization from boot off Apple Silicon
Blocking USB from the start of kernel boot writes Limine's entry-tool drop-in
and checks the Limine menu and UKI directly, but a Mac's boot chain belongs to
its boot package. omarchy-setup-security-usb-authorization --boot is refused
there before anything changes, and Setup and Remove > Security hide USB at
Boot. The boot helper itself refuses enable on Apple Silicon and treats
disable as done when nothing is configured, so turning USB device
authorization off still works on a Mac; a boot-time setting configured some
other way is left with steps to remove it by hand rather than reported
removed. USB device authorization after login is unchanged.
* Skip fixture-only checks when the shell suite runs as root
Root's locale step always writes the real /etc/locale.conf and locale.gen, and root's platform check always reads this machine, so their fixtures do not apply there. The locale test now stops after its static checks as root instead of changing the runner's locale, and the Apple Silicon refusal in the USB authorization test runs unprivileged only.
* Fetch aarch64 packages from Omarchy's HTTPS Arch Linux ARM mirror
* Name platforms x86, aarch64 and aarch64-apple
omarchy-hw-platform now prints the CPU architecture, then the hardware
family when it needs handling of its own: x86, aarch64 (every ARM machine
without a family) and aarch64-apple (Apple Silicon). A later family such
as aarch64-qualcomm or aarch64-n1x follows the same form, so aarch64*
always means ARM.
The Apple predicate is now omarchy-hw-aarch64-apple; omarchy-hw-apple-silicon
stays as a hidden alias while omarchy-mac's packages and units call it.
The Apple package list and pacman templates move to
install/omarchy-aarch64-apple.packages and default/pacman/aarch64-apple/.
Image manifests and omarchy-pkg-defaults still accept apple-silicon,
generic-aarch64 and generic from builders written before the names
settled, and the mkinitcpio baseline accepts a detector that still says
apple-silicon while omarchy-settings and omarchy update apart.
* Add omarchy-hw-aarch64 and gate architecture checks on the helpers
omarchy-hw-aarch64 is the counterpart of omarchy-hw-x86: true on every ARM
machine, Apple Silicon included. The Windows VM asks omarchy-hw-x86 instead
of reading uname itself, and the keyring refresh asks omarchy-hw-aarch64, so
an x86 update no longer runs the platform detector for it.
* Document platform names, helpers and the consistency rule for agents
AGENTS.md gains a Platforms section: the x86 / aarch64 / aarch64-<family>
naming standard, which helper answers architecture and which answers
hardware family, where platform-specific packages, templates, setup and
boot code live, and that every platform should get the same experience,
with missing packages fixed upstream before any workaround and alternative
packages kept as a last resort.
---------
Co-authored-by: Marcelo Alcantara <maralc@gmail.com>
Co-authored-by: Birk Skyum <74932975+birkskyum@users.noreply.github.com>
Co-authored-by: Jimmy Van Veen <8206925+JimmayVV@users.noreply.github.com>
Co-authored-by: Scott Jones <444693+scottjones@users.noreply.github.com>
Co-authored-by: Scott Jones <scottajones@gmail.com>
Co-authored-by: Marcelo Alcantara <marcelo.alcantara@bp.com>
Co-authored-by: Oliver Lukschander <33756270+oliverlukschander@users.noreply.github.com>
Co-authored-by: Caleb Marshall <68087341+cajomar@users.noreply.github.com>
Co-authored-by: Ryan Hughes <1630358+ryanrhughes@users.noreply.github.com>
Co-authored-by: Issac Trotts <15530+ijt@users.noreply.github.com>
Co-authored-by: Gabe Ohlson <3355909+6abe@users.noreply.github.com>
Co-authored-by: Sparsh Kumar <22640187+skkumarsparsh@users.noreply.github.com>
Co-authored-by: dl-alexandre <166029845+dl-alexandre@users.noreply.github.com>
Co-authored-by: kx0101 <90087463+kx0101@users.noreply.github.com>
Co-authored-by: Jim Martin <1334144+jdvmi00@users.noreply.github.com>
Co-authored-by: Ryan Hughes <ryan@heyoodle.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Omarchybot <317366263+omarchybot@users.noreply.github.com>
Co-authored-by: Greptile <165735046+greptile-apps[bot]@users.noreply.github.com>
2478 lines
97 KiB
Bash
Executable File
2478 lines
97 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# omarchy:summary=Upgrade a legacy Omarchy install to the package-backed Omarchy quattro layout.
|
|
# omarchy:args=[--yes] [--reboot] [--dev] [--channel stable|rc|edge] [--user USER]
|
|
# omarchy:examples=omarchy upgrade to quattro | omarchy upgrade to quattro --dev
|
|
# omarchy:requires-sudo=true
|
|
|
|
set -euo pipefail
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage: omarchy-upgrade-to-quattro [--yes] [--reboot] [--dev] [--channel stable|rc|edge] [--user USER]
|
|
|
|
Upgrade any pre-quattro Omarchy install to the package-backed Omarchy quattro layout.
|
|
This script is intentionally self-contained so it can be shipped as a command
|
|
in Omarchy 3.8.x or run directly with curl | bash on older systems.
|
|
|
|
Options:
|
|
-y, --yes Do not prompt before upgrading
|
|
--reboot Reboot automatically after a successful upgrade
|
|
--dev Use dev packages [omarchy-dev / omarchy-settings-dev] (uses edge channel)
|
|
--channel stable|rc|edge Override the default stable Omarchy package channel
|
|
--user USER User account to refresh after package install
|
|
-h, --help Show this help
|
|
USAGE
|
|
}
|
|
|
|
log() {
|
|
printf '\033[32m==>\033[0m %s\n' "$*"
|
|
}
|
|
|
|
warn() {
|
|
printf '\033[33mWarning:\033[0m %s\n' "$*" >&2
|
|
}
|
|
|
|
fail() {
|
|
printf '\033[31mError:\033[0m %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
normalize_channel() {
|
|
case "${1:-}" in
|
|
stable|master|main) echo stable ;;
|
|
rc) echo rc ;;
|
|
edge|dev) echo edge ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
valid_channel() {
|
|
normalize_channel "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
# The channel the legacy install is already on, read the way
|
|
# omarchy-version-channel reads it. Order matters: mirror.omarchy.org is a
|
|
# substring of both of the others.
|
|
detect_installed_channel() {
|
|
[[ -f /etc/pacman.d/mirrorlist ]] || return 1
|
|
|
|
if grep -q 'https://stable-mirror.omarchy.org/' /etc/pacman.d/mirrorlist; then
|
|
echo stable
|
|
elif grep -q 'https://rc-mirror.omarchy.org/' /etc/pacman.d/mirrorlist; then
|
|
echo rc
|
|
elif grep -q 'https://mirror.omarchy.org/' /etc/pacman.d/mirrorlist; then
|
|
echo edge
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
channel_override="${OMARCHY_UPGRADE_CHANNEL:-}"
|
|
channel_override_cli=0
|
|
target_user="${OMARCHY_INSTALL_USER:-}"
|
|
yes=0
|
|
auto_reboot=0
|
|
boot_cmdline_unsafe=0
|
|
kernel_cmdline_root_checked=0
|
|
use_dev_packages=${OMARCHY_UPGRADE_DEV:-0}
|
|
|
|
while (($#)); do
|
|
case "$1" in
|
|
-y|--yes)
|
|
yes=1
|
|
shift
|
|
;;
|
|
--reboot)
|
|
auto_reboot=1
|
|
shift
|
|
;;
|
|
--dev)
|
|
use_dev_packages=1
|
|
shift
|
|
;;
|
|
--channel)
|
|
channel_override="${2:-}"
|
|
channel_override_cli=1
|
|
shift 2
|
|
;;
|
|
--user)
|
|
target_user="${2:-}"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
fail "Unknown option: $1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
case "$use_dev_packages" in
|
|
1|true|yes|on) use_dev_packages=1 ;;
|
|
0|false|no|off|"") use_dev_packages=0 ;;
|
|
*) fail "Invalid OMARCHY_UPGRADE_DEV value '$use_dev_packages'. Use 1/0, true/false, or pass --dev." ;;
|
|
esac
|
|
|
|
# Land on the Quattro equivalent of wherever the machine already is, unless the
|
|
# caller said otherwise. Stable machines get the production packages off the
|
|
# stable channel. rc and edge machines stay on their own mirror, but take the
|
|
# dev packages, because that is where Quattro lives until it ships.
|
|
if [[ -z $channel_override ]] && (( ! use_dev_packages )); then
|
|
case "$(detect_installed_channel || true)" in
|
|
stable) channel_override=stable ;;
|
|
rc) channel_override=rc use_dev_packages=1 ;;
|
|
edge) channel_override=edge use_dev_packages=1 ;;
|
|
esac
|
|
fi
|
|
|
|
if (( use_dev_packages )); then
|
|
# The dev packages are only published to the edge package repo, which the rc
|
|
# and edge channels both point at; only stable is incompatible. rc keeps its
|
|
# own Arch mirror either way.
|
|
if [[ -n $channel_override ]]; then
|
|
if ! valid_channel "$channel_override"; then
|
|
fail "Invalid channel '$channel_override'. Use stable, rc, or edge."
|
|
fi
|
|
if [[ $(normalize_channel "$channel_override") == stable ]]; then
|
|
fail "--dev needs the edge package repo; use --channel rc or --channel edge."
|
|
fi
|
|
else
|
|
channel_override=edge
|
|
fi
|
|
fi
|
|
|
|
if { [[ -n $channel_override ]] || (( channel_override_cli )); } && ! valid_channel "$channel_override"; then
|
|
fail "Invalid channel '$channel_override'. Use stable, rc, or edge."
|
|
fi
|
|
|
|
if ! command -v pacman >/dev/null 2>&1; then
|
|
fail "This upgrade is only supported on Arch/Omarchy systems with pacman."
|
|
fi
|
|
|
|
if ! command -v sudo >/dev/null 2>&1 && (( EUID != 0 )); then
|
|
fail "sudo is required when not running as root."
|
|
fi
|
|
|
|
if [[ -z $target_user ]]; then
|
|
if (( EUID == 0 )); then
|
|
target_user="${SUDO_USER:-}"
|
|
else
|
|
target_user="${USER:-$(id -un)}"
|
|
fi
|
|
fi
|
|
|
|
if [[ -z $target_user || $target_user == root ]]; then
|
|
fail "Could not determine the non-root Omarchy user. Re-run with --user USER."
|
|
fi
|
|
|
|
if ! getent passwd "$target_user" >/dev/null; then
|
|
fail "User '$target_user' does not exist."
|
|
fi
|
|
|
|
target_home=$(getent passwd "$target_user" | cut -d: -f6)
|
|
[[ -n $target_home && -d $target_home ]] || fail "Home directory for '$target_user' was not found."
|
|
target_uid=$(id -u "$target_user")
|
|
target_runtime_dir="/run/user/$target_uid"
|
|
# User-local commands are needed only after dropping to the target user. Never
|
|
# expose their search path to commands run through as_root.
|
|
root_path=/usr/share/omarchy/bin:/usr/local/bin:/usr/bin:/bin
|
|
package_path="$root_path:$target_home/.local/bin"
|
|
|
|
as_root() {
|
|
if (( EUID == 0 )); then
|
|
"$@"
|
|
else
|
|
sudo "$@"
|
|
fi
|
|
}
|
|
|
|
run_as_user() {
|
|
if (( EUID == 0 )); then
|
|
if command -v sudo >/dev/null 2>&1; then
|
|
sudo -u "$target_user" -H "$@"
|
|
else
|
|
runuser -u "$target_user" -- "$@"
|
|
fi
|
|
else
|
|
"$@"
|
|
fi
|
|
}
|
|
|
|
run_as_user_session() {
|
|
run_as_user env \
|
|
HOME="$target_home" \
|
|
USER="$target_user" \
|
|
LOGNAME="$target_user" \
|
|
XDG_RUNTIME_DIR="$target_runtime_dir" \
|
|
DBUS_SESSION_BUS_ADDRESS="unix:path=$target_runtime_dir/bus" \
|
|
XDG_CURRENT_DESKTOP=Hyprland \
|
|
XDG_SESSION_DESKTOP=Hyprland \
|
|
DESKTOP_SESSION=hyprland \
|
|
"$@"
|
|
}
|
|
|
|
# Run an Omarchy command as the user with the package-backed paths, without
|
|
# assuming a live session. Extra NAME=VALUE args extend the environment.
|
|
run_as_user_omarchy() {
|
|
run_as_user env \
|
|
HOME="$target_home" \
|
|
USER="$target_user" \
|
|
LOGNAME="$target_user" \
|
|
OMARCHY_PATH=/usr/share/omarchy \
|
|
PATH="$package_path" \
|
|
"$@"
|
|
}
|
|
|
|
package_installed_exact() {
|
|
local pkg="$1"
|
|
pacman -Qq "$pkg" 2>/dev/null | grep -Fxq "$pkg"
|
|
}
|
|
|
|
mark_packages_explicit() {
|
|
local pkg installed_pkg
|
|
local installed_packages=()
|
|
|
|
for pkg in "$@"; do
|
|
# pacman -Qq resolves installed providers too (for example nvim -> neovim),
|
|
# while package_installed_exact intentionally does not.
|
|
installed_pkg=$(pacman -Qq "$pkg" 2>/dev/null | head -n1 || true)
|
|
[[ -n $installed_pkg ]] && installed_packages+=("$installed_pkg")
|
|
done
|
|
|
|
((${#installed_packages[@]})) || return 0
|
|
mapfile -t installed_packages < <(printf '%s\n' "${installed_packages[@]}" | sort -u)
|
|
|
|
if ! as_root pacman -D --asexplicit "${installed_packages[@]}" >/dev/null; then
|
|
warn "Could not mark installed default packages as explicit; retired-package cleanup may leave extra dependencies installed."
|
|
fi
|
|
}
|
|
|
|
target_hyprland_pid() {
|
|
pgrep -u "$target_uid" -x Hyprland 2>/dev/null | head -n1 || true
|
|
}
|
|
|
|
target_process_env() {
|
|
local name="$1" pid value
|
|
pid=$(target_hyprland_pid)
|
|
[[ -n $pid && -r /proc/$pid/environ ]] || return 1
|
|
value=$(tr '\0' '\n' <"/proc/$pid/environ" 2>/dev/null | awk -v name="$name" '
|
|
BEGIN { prefix = name "=" }
|
|
index($0, prefix) == 1 { print substr($0, length(prefix) + 1); exit }
|
|
' || true)
|
|
[[ -n $value ]] || return 1
|
|
printf '%s\n' "$value"
|
|
}
|
|
|
|
valid_wayland_display() {
|
|
[[ -n ${1:-} && -S $target_runtime_dir/$1 ]]
|
|
}
|
|
|
|
discover_wayland_display() {
|
|
local candidate
|
|
candidate="${WAYLAND_DISPLAY:-}"
|
|
if valid_wayland_display "$candidate"; then
|
|
printf '%s\n' "$candidate"
|
|
return 0
|
|
fi
|
|
|
|
candidate=$(target_process_env WAYLAND_DISPLAY || true)
|
|
if valid_wayland_display "$candidate"; then
|
|
printf '%s\n' "$candidate"
|
|
return 0
|
|
fi
|
|
|
|
local candidates=()
|
|
mapfile -t candidates < <(find "$target_runtime_dir" -maxdepth 1 -type s -name 'wayland-*' -printf '%f\n' 2>/dev/null || true)
|
|
(( ${#candidates[@]} == 1 )) || return 1
|
|
printf '%s\n' "${candidates[0]}"
|
|
}
|
|
|
|
valid_hyprland_signature() {
|
|
[[ -n ${1:-} ]] || return 1
|
|
[[ -S $target_runtime_dir/hypr/$1/.socket.sock || -S $target_runtime_dir/hypr/$1/.socket2.sock ]]
|
|
}
|
|
|
|
discover_hyprland_signature() {
|
|
local candidate
|
|
candidate="${HYPRLAND_INSTANCE_SIGNATURE:-}"
|
|
if valid_hyprland_signature "$candidate"; then
|
|
printf '%s\n' "$candidate"
|
|
return 0
|
|
fi
|
|
|
|
candidate=$(target_process_env HYPRLAND_INSTANCE_SIGNATURE || true)
|
|
if valid_hyprland_signature "$candidate"; then
|
|
printf '%s\n' "$candidate"
|
|
return 0
|
|
fi
|
|
|
|
local candidates=()
|
|
mapfile -t candidates < <(find "$target_runtime_dir/hypr" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' 2>/dev/null || true)
|
|
(( ${#candidates[@]} == 1 )) || return 1
|
|
printf '%s\n' "${candidates[0]}"
|
|
}
|
|
|
|
channel=$(normalize_channel "${channel_override:-stable}")
|
|
|
|
case "$channel" in
|
|
stable)
|
|
mirror_server='https://stable-mirror.omarchy.org/$repo/os/$arch'
|
|
package_server='https://pkgs.omarchy.org/stable/$arch'
|
|
;;
|
|
rc)
|
|
mirror_server='https://rc-mirror.omarchy.org/$repo/os/$arch'
|
|
package_server='https://pkgs.omarchy.org/edge/$arch'
|
|
;;
|
|
edge)
|
|
mirror_server='https://mirror.omarchy.org/$repo/os/$arch'
|
|
package_server='https://pkgs.omarchy.org/edge/$arch'
|
|
;;
|
|
esac
|
|
|
|
if (( ! yes )); then
|
|
printf '\033[2J\033[3J\033[H' >/dev/tty
|
|
cat <<EOF
|
|
████████▄ ███ █▄ ▄████████ ███ ███ ▄████████ ▄██████▄
|
|
███ ███ ███ ███ ███ ███ ▀█████████▄ ▀█████████▄ ███ ███ ███ ███
|
|
███ ███ ███ ███ ███ ███ ▀███▀▀██ ▀███▀▀██ ███ ███ ███ ███
|
|
███ ███ ███ ███ ███ ███ ███ ▀ ███ ▀ ▄███▄▄▄▄██▀ ███ ███
|
|
███ ███ ███ ███ ▀███████████ ███ ███ ▀▀███▀▀▀▀▀ ███ ███
|
|
███ ███ ███ ███ ███ ███ ███ ███ ▀███████████ ███ ███
|
|
███ ▀ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
|
|
▀██████▀▄█ ████████▀ ███ █▀ ▄████▀ ▄████▀ ███ ███ ▀██████▀
|
|
███ ███
|
|
|
|
Upgrading Omarchy to Quattro is a one-way street!
|
|
You cannot downgrade from Quattro.
|
|
Make sure you have a backup.
|
|
|
|
EOF
|
|
|
|
gum confirm "Continue with upgrade?" </dev/tty || exit 0
|
|
fi
|
|
|
|
sudo_keepalive_pid=""
|
|
hyprland_config_reload_suppressed=0
|
|
upgrade_started=0
|
|
upgrade_completed=0
|
|
|
|
cleanup_on_exit() {
|
|
local exit_status=$?
|
|
|
|
if (( hyprland_config_reload_suppressed )); then
|
|
restore_hyprland_config_reload >/dev/null 2>&1 || true
|
|
fi
|
|
|
|
if [[ -n ${sudo_keepalive_pid:-} ]]; then
|
|
kill "$sudo_keepalive_pid" 2>/dev/null || true
|
|
fi
|
|
|
|
# set -e aborts without a word, and the last thing on screen is a green
|
|
# progress line, so a half-finished upgrade otherwise reads as a finished one.
|
|
if (( exit_status != 0 && upgrade_started && ! upgrade_completed )); then
|
|
printf '\n\033[31mUpgrade incomplete - do NOT reboot.\033[0m\n' >&2
|
|
printf 'The system is part Omarchy 3 and part Omarchy quattro; rebooting now can leave it without a working network or desktop.\n' >&2
|
|
printf 'Fix the error reported above and run this script again. Re-running is safe and resumes the remaining steps.\n' >&2
|
|
fi
|
|
|
|
exit "$exit_status"
|
|
}
|
|
trap cleanup_on_exit EXIT
|
|
|
|
if (( EUID != 0 )); then
|
|
sudo -v
|
|
while true; do sudo -n true; sleep 60; done 2>/dev/null &
|
|
sudo_keepalive_pid=$!
|
|
fi
|
|
|
|
backup_suffix=$(date +%Y%m%d%H%M%S)
|
|
|
|
configure_pacman_channel() {
|
|
log "Configuring Omarchy pacman repositories ($channel)"
|
|
|
|
as_root install -d -m 0755 /etc/pacman.d
|
|
if [[ -f /etc/pacman.d/mirrorlist ]]; then
|
|
as_root cp -f /etc/pacman.d/mirrorlist "/etc/pacman.d/mirrorlist.omarchy-upgrade-to-quattro.$backup_suffix.bak"
|
|
fi
|
|
printf 'Server = %s\n' "$mirror_server" | as_root tee /etc/pacman.d/mirrorlist >/dev/null
|
|
|
|
if [[ ! -f /etc/pacman.conf ]]; then
|
|
fail "/etc/pacman.conf does not exist."
|
|
fi
|
|
|
|
as_root cp -f /etc/pacman.conf "/etc/pacman.conf.omarchy-upgrade-to-quattro.$backup_suffix.bak"
|
|
|
|
# Replace the normal Omarchy package repository. Dev package mode uses the
|
|
# edge channel and resolves omarchy-dev / omarchy-settings-dev from [omarchy].
|
|
local tmp
|
|
|
|
tmp=$(mktemp)
|
|
awk -v server="$package_server" '
|
|
BEGIN { in_omarchy = 0; wrote = 0 }
|
|
/^[[:space:]]*\[omarchy\][[:space:]]*$/ {
|
|
if (!wrote) {
|
|
print "[omarchy]"
|
|
print "SigLevel = Optional TrustAll"
|
|
print "Server = " server
|
|
wrote = 1
|
|
}
|
|
in_omarchy = 1
|
|
next
|
|
}
|
|
/^[[:space:]]*\[/ { in_omarchy = 0 }
|
|
!in_omarchy { print }
|
|
END {
|
|
if (!wrote) {
|
|
print ""
|
|
print "[omarchy]"
|
|
print "SigLevel = Optional TrustAll"
|
|
print "Server = " server
|
|
}
|
|
}
|
|
' /etc/pacman.conf >"$tmp"
|
|
as_root install -m 0644 "$tmp" /etc/pacman.conf
|
|
rm -f "$tmp"
|
|
}
|
|
|
|
install_keyrings() {
|
|
log "Refreshing package databases and keyrings"
|
|
|
|
# Pre-quattro installs cached omarchy packages downloaded from the legacy
|
|
# repo. The quattro package server rebuilds some of them under the same
|
|
# name and version with different bytes, so the cached copies fail the new
|
|
# database's checksum and abort --noconfirm transactions as "corrupted".
|
|
as_root find /var/cache/pacman/pkg -maxdepth 1 -name 'omarchy-*' -delete 2>/dev/null || true
|
|
|
|
# -Syy forces the database download. configure_pacman_channel just repointed
|
|
# the mirrors, and a plain -Sy keeps the legacy database whenever the new
|
|
# server's copy isn't newer, leaving stale checksums that no amount of
|
|
# re-downloading can satisfy.
|
|
|
|
as_root pacman -Syy --noconfirm archlinux-keyring omarchy-keyring || {
|
|
warn "Keyring package install failed; attempting to trust the Omarchy packaging key directly."
|
|
as_root pacman-key --recv-keys 40DFB630FF42BCFFB047046CF0134EE680CAC571 --keyserver keys.openpgp.org || true
|
|
as_root pacman-key --lsign-key 40DFB630FF42BCFFB047046CF0134EE680CAC571 || true
|
|
as_root pacman -Syy --noconfirm archlinux-keyring omarchy-keyring
|
|
}
|
|
}
|
|
|
|
remove_legacy_installer_package() {
|
|
if pacman -Q omarchy-installer >/dev/null 2>&1; then
|
|
log "Removing legacy omarchy-installer package"
|
|
# The pre-quattro omarchy meta package may depend on omarchy-installer. Ignore
|
|
# that edge temporarily; the next transaction installs the Omarchy quattro meta.
|
|
as_root pacman -Rdd --noconfirm omarchy-installer
|
|
fi
|
|
}
|
|
|
|
remove_legacy_limine_configs() {
|
|
# Mirrors the old 1762446739 migration: old Limine installs sometimes left
|
|
# alternate limine.conf files in paths Limine searches before /boot/limine.conf.
|
|
# If any are still present, Limine may ignore the package-managed default config.
|
|
local canonical_limine_config=/boot/limine.conf
|
|
local legacy_limine_configs=(
|
|
/boot/EFI/arch-limine/limine.conf
|
|
/boot/EFI/limine/limine.conf
|
|
/boot/EFI/BOOT/limine.conf
|
|
/boot/limine/limine.conf
|
|
)
|
|
local legacy_limine_config
|
|
local found_legacy_limine_configs=()
|
|
|
|
for legacy_limine_config in "${legacy_limine_configs[@]}"; do
|
|
if as_root test -f "$legacy_limine_config"; then
|
|
found_legacy_limine_configs+=("$legacy_limine_config")
|
|
fi
|
|
done
|
|
|
|
((${#found_legacy_limine_configs[@]})) || return 0
|
|
|
|
if ! as_root test -f "$canonical_limine_config"; then
|
|
fail "Legacy Limine configs exist (${found_legacy_limine_configs[*]}) but $canonical_limine_config does not. Do not reboot until the bootloader config is repaired."
|
|
fi
|
|
|
|
log "Removing legacy alternate Limine configs"
|
|
as_root rm -f "${found_legacy_limine_configs[@]}"
|
|
}
|
|
|
|
normalize_limine_config() {
|
|
local machine_id legacy_uki
|
|
|
|
as_root test -f /boot/limine.conf || return 0
|
|
|
|
log "Normalizing Limine config"
|
|
# Mirrors old 1777570652: normalize British/American spelling, the old color
|
|
# index, and help colors that pre-quattro installs may never have migrated.
|
|
as_root sed -i -E 's/^interface_branding_colou?r: 2$/interface_branding_color: 9ece6a/' /boot/limine.conf
|
|
as_root sed -i 's/^interface_branding_colour: /interface_branding_color: /' /boot/limine.conf
|
|
as_root sed -i -E '/^interface_help_colou?r(_bright)?:/d' /boot/limine.conf
|
|
as_root sed -i '/^interface_branding_color:/a interface_help_color_bright: 9ece6a' /boot/limine.conf
|
|
as_root sed -i '/^interface_branding_color:/a interface_help_color: 9ece6a' /boot/limine.conf
|
|
|
|
# Pre-quattro custom-UKI installs used <machine-id>_linux.efi. Omarchy quattro uses the
|
|
# stable omarchy_linux.efi path generated by limine-entry-tool.
|
|
machine_id=$(as_root cat /etc/machine-id 2>/dev/null || true)
|
|
legacy_uki="/boot/EFI/Linux/${machine_id}_linux.efi"
|
|
if [[ -n $machine_id ]] && as_root test -f /boot/EFI/Linux/omarchy_linux.efi && as_root test -f "$legacy_uki"; then
|
|
as_root rm -f "$legacy_uki"
|
|
fi
|
|
}
|
|
|
|
# True when the limine config layers state root= themselves, rather than leaving
|
|
# it to the /proc/cmdline fallback that the first += drop-in switches off.
|
|
# Only the default profile matters here: that is the key this function appends
|
|
# to and the one the boot entries are built from. The layers are merged in
|
|
# limine-entry-tool's order, where = replaces and += appends, so a later layer
|
|
# can take away a root= an earlier one stated. Globbing and reading both run as
|
|
# root so an unreadable drop-in directory cannot read as "nothing is pinned".
|
|
kernel_cmdline_root_pinned() {
|
|
as_root bash -c '
|
|
shopt -s nullglob extglob
|
|
cmdline=""
|
|
for conf in /usr/share/limine-entry-tool.d/*.conf /etc/limine-entry-tool.conf /etc/limine-entry-tool.d/*.conf /etc/default/limine; do
|
|
[[ -f $conf ]] || continue
|
|
while IFS= read -r line || [[ -n $line ]]; do
|
|
[[ $line =~ ^[[:space:]]*KERNEL_CMDLINE\[default\][[:space:]]*(\+?)=[[:space:]]*(.*)$ ]] || continue
|
|
value=${BASH_REMATCH[2]}
|
|
[[ $value == \"*\" ]] && value=${value:1:-1}
|
|
if [[ -n ${BASH_REMATCH[1]} ]]; then
|
|
cmdline+=" $value"
|
|
else
|
|
cmdline=$value
|
|
fi
|
|
done <"$conf"
|
|
done
|
|
# The kernel splits parameters only outside double quotes, and an unmatched
|
|
# quote runs to the end of the line.
|
|
cmdline=${cmdline//\"*([^\"])\"/_}
|
|
[[ " ${cmdline%%\"*}" =~ [[:space:]]root= ]]
|
|
'
|
|
}
|
|
|
|
preserve_kernel_cmdline_root() {
|
|
local default_conf=/etc/default/limine
|
|
local cmdline param key root_source root_stack root_uuid subvol
|
|
local boot_params=()
|
|
local have_root=0 have_mount_mode=0 have_unlock=0
|
|
|
|
# Gate on limine being what boots this machine, not on limine-mkinitcpio being
|
|
# installed: this now runs before the transaction that can install it, and a
|
|
# machine still missing it is exactly one that must be pinned first.
|
|
as_root test -f /boot/limine.conf || return 0
|
|
kernel_cmdline_root_checked=1
|
|
|
|
# The omarchy-defaults.conf drop-in appends to KERNEL_CMDLINE[default] with
|
|
# +=, which makes limine-entry-tool ignore /etc/kernel/cmdline and
|
|
# /proc/cmdline. Fresh installs pin root= in /etc/default/limine via the ISO;
|
|
# upgrades never created that file, so root= silently drops out and the next
|
|
# boot lands in an emergency shell.
|
|
#
|
|
# Ask the config layers whether root= is stated explicitly rather than asking
|
|
# the tool for its effective cmdline. The effective cmdline still resolves
|
|
# root= from the /proc/cmdline fallback right up until the first += drop-in
|
|
# lands, so it reports healthy on exactly the machines that are about to
|
|
# break — and this has to run before the package transaction installs that
|
|
# drop-in.
|
|
kernel_cmdline_root_pinned && return 0
|
|
|
|
# Copy the boot-critical parameters of the running kernel verbatim, so a
|
|
# PARTUUID, a LABEL or a bare device node all survive.
|
|
cmdline=$(cat /proc/cmdline 2>/dev/null || true)
|
|
for param in $cmdline; do
|
|
key=${param%%=*}
|
|
case $key in
|
|
root)
|
|
have_root=1
|
|
boot_params+=("$param")
|
|
;;
|
|
rw | ro)
|
|
have_mount_mode=1
|
|
boot_params+=("$param")
|
|
;;
|
|
cryptdevice | cryptkey | rd.luks.name | rd.luks.uuid | rd.luks.options | rd.luks.key | rd.luks.crypttab | dm-mod.create)
|
|
have_unlock=1
|
|
boot_params+=("$param")
|
|
;;
|
|
rootflags | rootfstype | rootwait | rootdelay | resume | resume_offset | rd.lvm.lv | rd.lvm.vg | rd.md.uuid | rd.dm.uuid)
|
|
boot_params+=("$param")
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Already-broken system (booted through the emergency shell): derive root=
|
|
# from the mounted root instead. Refuse when the root sits on dm-crypt and
|
|
# the cmdline carries no unlock parameters, since root= alone would still not
|
|
# boot. lsblk -s walks the parents, where the crypt layer hides on
|
|
# LVM-on-LUKS; --nofsroot keeps findmnt from appending a btrfs subvolume
|
|
# lsblk cannot resolve; the capture keeps a SIGPIPE under pipefail from
|
|
# reading as "no crypt layer".
|
|
if ((!have_root)); then
|
|
root_source=$(findmnt -no SOURCE --nofsroot / 2>/dev/null || true)
|
|
root_stack=$(lsblk -nso TYPE "$root_source" 2>/dev/null || true)
|
|
if grep -qx crypt <<<"$root_stack" && ((!have_unlock)); then
|
|
warn "The root filesystem is on dm-crypt and the booted kernel cmdline carries no unlock parameters, so they cannot be recovered automatically. Add root= and the matching cryptdevice or rd.luks.* parameters to $default_conf by hand, then run limine-mkinitcpio."
|
|
boot_cmdline_unsafe=1
|
|
return 0
|
|
fi
|
|
|
|
root_uuid=$(findmnt -no UUID / 2>/dev/null || true)
|
|
if [[ -z $root_uuid ]]; then
|
|
warn "Could not determine the root filesystem UUID; the kernel cmdline was left untouched."
|
|
boot_cmdline_unsafe=1
|
|
return 0
|
|
fi
|
|
|
|
boot_params=("root=UUID=$root_uuid" "${boot_params[@]}")
|
|
((have_mount_mode)) || boot_params+=(rw)
|
|
|
|
# Btrfs needs its subvolume too, or the initramfs mounts the wrong tree.
|
|
subvol=$(findmnt -no FSROOT / 2>/dev/null || true)
|
|
if [[ $(findmnt -no FSTYPE / 2>/dev/null) == "btrfs" && -n $subvol && $subvol != "/" ]]; then
|
|
boot_params+=("rootflags=subvol=${subvol#/}")
|
|
fi
|
|
fi
|
|
|
|
log "Preserving the kernel cmdline root parameters in $default_conf"
|
|
# omarchy:heredoc-expands paths=none -- ${boot_params[*]} is the kernel
|
|
# cmdline fragment assembled above from findmnt output (root=, rootflags=),
|
|
# not a filesystem path this script controls.
|
|
as_root tee -a "$default_conf" >/dev/null <<EOF
|
|
# Written by omarchy-upgrade-to-quattro. The += drop-ins in
|
|
# /etc/limine-entry-tool.d/ stop limine-entry-tool from reading
|
|
# /etc/kernel/cmdline and /proc/cmdline, so the parameters identifying the
|
|
# root filesystem have to be stated explicitly.
|
|
KERNEL_CMDLINE[default]+=" ${boot_params[*]}"
|
|
EOF
|
|
}
|
|
|
|
# Runs after the package transaction, which is what rebuilds the UKIs through
|
|
# the limine hook. Regenerating here too keeps a machine whose kernel was not
|
|
# bumped from carrying stale entries.
|
|
verify_kernel_cmdline_root() {
|
|
local uki
|
|
local missing=()
|
|
|
|
command -v limine-mkinitcpio >/dev/null 2>&1 || return 0
|
|
as_root test -f /boot/limine.conf || return 0
|
|
|
|
# Installing limine-mkinitcpio-hook deploys limine, so a machine can have a
|
|
# limine.conf now that had none when the pin ran ahead of the transaction.
|
|
((kernel_cmdline_root_checked)) || preserve_kernel_cmdline_root
|
|
|
|
# Keep going on failure so the verification below still runs; the unsafe
|
|
# flag blocks the reboot at the end of the upgrade.
|
|
if ! as_root limine-mkinitcpio; then
|
|
warn "limine-mkinitcpio failed; the boot entries were not regenerated."
|
|
boot_cmdline_unsafe=1
|
|
fi
|
|
|
|
as_root grep -qE '(^|[[:space:]])root=' /boot/limine.conf || missing+=(/boot/limine.conf)
|
|
|
|
# The cmdline that boots is the one embedded in the UKIs, so check those too.
|
|
if command -v objcopy >/dev/null 2>&1; then
|
|
while read -r uki; do
|
|
[[ -n $uki ]] || continue
|
|
as_root objcopy -O binary --only-section=.cmdline "$uki" /dev/stdout 2>/dev/null |
|
|
tr -d '\0' | grep -E '(^|[[:space:]])root=' >/dev/null || missing+=("$uki")
|
|
done < <(as_root find /boot/EFI/Linux -maxdepth 1 -name 'omarchy_linux*.efi' 2>/dev/null)
|
|
fi
|
|
|
|
if ((${#missing[@]})); then
|
|
warn "root= is still missing from ${missing[*]}. Do not reboot until the kernel cmdline is repaired, or the system will drop to an emergency shell."
|
|
boot_cmdline_unsafe=1
|
|
fi
|
|
}
|
|
|
|
configure_snapper_policy() {
|
|
local snapper_config_script=/usr/share/omarchy/install/config/snapper.sh
|
|
|
|
if ! as_root test -f "$snapper_config_script"; then
|
|
warn "$snapper_config_script is unavailable; Snapper snapshot retention was not normalized."
|
|
return 0
|
|
fi
|
|
|
|
log "Configuring Omarchy snapshot retention"
|
|
as_root env OMARCHY_PATH=/usr/share/omarchy bash -euo pipefail "$snapper_config_script"
|
|
}
|
|
|
|
configure_lock_authentication() {
|
|
# The upgrade runs against whatever the channel currently ships, which can
|
|
# still predate the rename of omarchy-setup-lock to omarchy-apply-lock.
|
|
local apply_lock="" candidate
|
|
for candidate in /usr/share/omarchy/bin/omarchy-{apply,setup}-lock; do
|
|
if [[ -x $candidate ]]; then
|
|
apply_lock=$candidate
|
|
break
|
|
fi
|
|
done
|
|
|
|
[[ -n $apply_lock ]] || fail "Neither omarchy-apply-lock nor omarchy-setup-lock is available under /usr/share/omarchy/bin; lock screen authentication could not be configured."
|
|
|
|
log "Configuring lock screen authentication"
|
|
as_root env \
|
|
OMARCHY_INSTALL_USER="$target_user" \
|
|
OMARCHY_PATH=/usr/share/omarchy \
|
|
PATH="$root_path" \
|
|
"$apply_lock"
|
|
}
|
|
|
|
create_pre_upgrade_snapshot() {
|
|
# 127 means Snapper is deliberately absent. Any other failure already said
|
|
# what went wrong, and a missing snapshot is not worth blocking the upgrade
|
|
# over, but it must not pass for one either.
|
|
omarchy-snapshot create || (($? == 127)) ||
|
|
echo -e "\e[33mContinuing the upgrade without a snapshot.\e[0m" >&2
|
|
}
|
|
|
|
remove_conflicting_legacy_packages() {
|
|
# Remove legacy packages that are known to conflict with Omarchy quattro defaults
|
|
# before the main package transaction. Broader retired-package cleanup runs
|
|
# after NetworkManager and the rest of the new stack are installed.
|
|
local conflicting_packages=(
|
|
asdcontrol-git
|
|
localsend-bin
|
|
omarchy-chromium
|
|
omarchy-lazyvim
|
|
openai-codex-bin
|
|
ttf-jetbrains-mono
|
|
ttf-jetbrains-mono-nerd
|
|
wayfreeze
|
|
wayfreeze-git
|
|
)
|
|
|
|
local pkg
|
|
for pkg in "${conflicting_packages[@]}"; do
|
|
if package_installed_exact "$pkg"; then
|
|
# Some pre-quattro meta packages depend on these retired/default packages. We
|
|
# intentionally break those old dependency edges before installing the
|
|
# Omarchy quattro meta, otherwise pacman prompts to remove conflicts (for
|
|
# example ttf-jetbrains-mono-nerd -> ttf-jetbrains-mono-nerd-basic) and
|
|
# --noconfirm answers "no".
|
|
as_root pacman -Rdd --noconfirm "$pkg" || warn "Could not remove conflicting legacy package '$pkg'; continuing."
|
|
fi
|
|
done
|
|
}
|
|
|
|
migrate_1password_beta_package() {
|
|
if ! package_installed_exact 1password-beta && ! package_installed_exact 1password-beta-bin; then
|
|
return 0
|
|
fi
|
|
|
|
log "Replacing 1Password Beta with stable 1Password"
|
|
if ! as_root pacman -S --noconfirm --ask 4 1password; then
|
|
warn "Could not replace 1Password Beta with stable 1Password; leaving the existing 1Password package installed."
|
|
fi
|
|
}
|
|
|
|
migrate_lazydocker_package() {
|
|
if package_installed_exact lazydocker-bin; then
|
|
log "Replacing existing Lazydocker with the repo package"
|
|
if as_root pacman -S --needed --noconfirm --ask 4 lazydocker; then
|
|
mark_packages_explicit lazydocker
|
|
else
|
|
warn "Could not replace lazydocker-bin; leaving existing Lazydocker installed."
|
|
fi
|
|
fi
|
|
}
|
|
|
|
install_omarchy_quattro_packages() {
|
|
local omarchy_package=omarchy
|
|
local settings_package=omarchy-settings
|
|
if (( use_dev_packages )); then
|
|
omarchy_package=omarchy-dev
|
|
settings_package=omarchy-settings-dev
|
|
fi
|
|
|
|
local core_packages=(
|
|
omarchy-keyring
|
|
"$settings_package"
|
|
omarchy-nvim
|
|
"$omarchy_package"
|
|
fakeroot
|
|
pacman-contrib
|
|
)
|
|
local base_packages=()
|
|
|
|
log "Upgrading system packages and installing Omarchy quattro packages"
|
|
(( use_dev_packages )) && log "Using dev packages [omarchy-dev / omarchy-settings-dev]"
|
|
# --noconfirm answers "no" to conflict-removal prompts. --ask 4 preselects
|
|
# removing the conflicting package, which is required for legacy packages like
|
|
# ttf-jetbrains-mono-nerd -> ttf-jetbrains-mono-nerd-basic.
|
|
as_root env OMARCHY_UPDATE_PACMAN=1 pacman -Syu --needed --noconfirm --ask 4 --overwrite='*' "${core_packages[@]}"
|
|
mark_packages_explicit "${core_packages[@]}"
|
|
|
|
if [[ -f /usr/share/omarchy/install/omarchy-base.packages ]]; then
|
|
log "Installing Omarchy quattro default package set"
|
|
mapfile -t base_packages < <(sed -e 's/[[:space:]]*#.*$//' -e '/^[[:space:]]*$/d' /usr/share/omarchy/install/omarchy-base.packages)
|
|
# Some base packages have no aarch64 build.
|
|
if [[ $(uname -m) != "x86_64" && -f /usr/share/omarchy/install/omarchy-x86_64-only.packages ]]; then
|
|
mapfile -t base_packages < <(printf '%s\n' "${base_packages[@]}" |
|
|
grep -vxF -f <(sed -e 's/[[:space:]]*#.*$//' -e '/^[[:space:]]*$/d' /usr/share/omarchy/install/omarchy-x86_64-only.packages))
|
|
fi
|
|
if (( ${#base_packages[@]} )); then
|
|
as_root pacman -S --needed --noconfirm --ask 4 --overwrite='*' "${base_packages[@]}"
|
|
mark_packages_explicit "${base_packages[@]}"
|
|
fi
|
|
else
|
|
warn "/usr/share/omarchy/install/omarchy-base.packages was not found; skipping default package set install."
|
|
fi
|
|
|
|
# Archinstall installs the audio application during fresh ISO installs, so
|
|
# legacy upgrades need to ensure the same PipeWire stack is present without
|
|
# depending on Archinstall or Omarchy setup helpers.
|
|
local audio_packages=(
|
|
pipewire
|
|
pipewire-alsa
|
|
pipewire-jack
|
|
pipewire-pulse
|
|
gst-plugin-pipewire
|
|
libpulse
|
|
wireplumber
|
|
)
|
|
as_root pacman -S --needed --noconfirm --ask 4 --overwrite='*' "${audio_packages[@]}"
|
|
mark_packages_explicit "${audio_packages[@]}"
|
|
}
|
|
|
|
install_hardware_transition_packages() {
|
|
local hardware_packages=()
|
|
|
|
if lspci | grep -iE '(Multimedia audio controller|Audio device).*Intel' >/dev/null; then
|
|
hardware_packages+=(sof-firmware)
|
|
fi
|
|
|
|
if lspci | grep -iE '(VGA|Display).*Intel' >/dev/null; then
|
|
hardware_packages+=(vulkan-intel)
|
|
fi
|
|
if lspci | grep -iE '(VGA|Display).*AMD' >/dev/null; then
|
|
hardware_packages+=(vulkan-radeon)
|
|
fi
|
|
if lspci | grep -iE '(VGA|Display).*Apple' >/dev/null; then
|
|
hardware_packages+=(vulkan-asahi)
|
|
fi
|
|
|
|
if (( ${#hardware_packages[@]} > 0 )); then
|
|
log "Installing hardware support needed by the Omarchy quattro layout"
|
|
as_root pacman -S --needed --noconfirm "${hardware_packages[@]}"
|
|
mark_packages_explicit "${hardware_packages[@]}"
|
|
fi
|
|
}
|
|
|
|
remove_retired_default_packages() {
|
|
local retired_packages=(
|
|
asdcontrol-git
|
|
blueberry
|
|
bluetui
|
|
chaotic-keyring
|
|
chaotic-mirrorlist
|
|
claude-code
|
|
dust
|
|
elephant
|
|
elephant-all
|
|
elephant-bluetooth
|
|
elephant-calc
|
|
elephant-clipboard
|
|
elephant-desktopapplications
|
|
elephant-files
|
|
elephant-menus
|
|
elephant-providerlist
|
|
elephant-runner
|
|
elephant-symbols
|
|
elephant-todo
|
|
elephant-unicode
|
|
elephant-websearch
|
|
fcitx5-configtool
|
|
gcc14
|
|
hypridle
|
|
hyprlock
|
|
impala
|
|
iwd
|
|
lmstudio
|
|
lmstudio-bin
|
|
localsend-bin
|
|
makima-bin
|
|
mako
|
|
omarchy-chromium
|
|
omarchy-lazyvim
|
|
omarchy-walker
|
|
openai-codex
|
|
openai-codex-bin
|
|
opencode
|
|
pavucontrol
|
|
playerctl
|
|
polkit-gnome
|
|
qt5-remoteobjects
|
|
satty
|
|
swaybg
|
|
swayosd
|
|
walker-bin
|
|
ttf-jetbrains-mono
|
|
ttf-jetbrains-mono-nerd
|
|
vlc
|
|
waybar
|
|
wayfreeze
|
|
wayfreeze-git
|
|
wf-recorder
|
|
wiremix
|
|
wl-screenrec
|
|
yay-bin-debug
|
|
zoom
|
|
)
|
|
|
|
log "Removing packages retired from the Omarchy quattro default install"
|
|
local pkg
|
|
local installed_retired=()
|
|
for pkg in "${retired_packages[@]}"; do
|
|
if package_installed_exact "$pkg"; then
|
|
installed_retired+=("$pkg")
|
|
fi
|
|
done
|
|
|
|
((${#installed_retired[@]})) || return 0
|
|
|
|
# Remove as one transaction so dependency anchors like waybar/playerctl and
|
|
# omarchy-walker/elephant-* disappear together instead of failing one-by-one.
|
|
# Preflight first so dependency failures do not print scary pacman errors in
|
|
# the middle of an otherwise successful upgrade.
|
|
if pacman -Rs --print "${installed_retired[@]}" >/dev/null 2>&1; then
|
|
if as_root pacman -Rns --noconfirm --ask 4 "${installed_retired[@]}"; then
|
|
return 0
|
|
fi
|
|
warn "Batch retired-package removal failed; retrying dependency-aware groups."
|
|
else
|
|
warn "Some retired packages are still required by non-retired packages; retrying dependency-aware groups."
|
|
fi
|
|
|
|
local group group_pkg required_by_line
|
|
local group_packages=()
|
|
local installed_group=()
|
|
local fallback_groups=(
|
|
"omarchy-walker walker elephant-all elephant elephant-bluetooth elephant-calc elephant-clipboard elephant-desktopapplications elephant-files elephant-menus elephant-providerlist elephant-runner elephant-symbols elephant-todo elephant-unicode elephant-websearch"
|
|
"waybar playerctl"
|
|
"impala iwd"
|
|
"blueberry bluetui"
|
|
"wiremix pavucontrol"
|
|
)
|
|
|
|
for group in "${fallback_groups[@]}"; do
|
|
read -r -a group_packages <<<"$group"
|
|
installed_group=()
|
|
for group_pkg in "${group_packages[@]}"; do
|
|
if package_installed_exact "$group_pkg"; then
|
|
installed_group+=("$group_pkg")
|
|
fi
|
|
done
|
|
((${#installed_group[@]})) || continue
|
|
|
|
if pacman -Rs --print "${installed_group[@]}" >/dev/null 2>&1; then
|
|
as_root pacman -Rns --noconfirm --ask 4 "${installed_group[@]}" || warn "Could not remove retired package group: ${installed_group[*]}"
|
|
else
|
|
warn "Leaving retired package group installed because other packages still depend on it: ${installed_group[*]}"
|
|
fi
|
|
done
|
|
|
|
for pkg in "${installed_retired[@]}"; do
|
|
package_installed_exact "$pkg" || continue
|
|
|
|
if pacman -Rs --print "$pkg" >/dev/null 2>&1; then
|
|
as_root pacman -Rns --noconfirm --ask 4 "$pkg" || warn "Could not remove retired package '$pkg'; leaving it installed."
|
|
else
|
|
required_by_line=$(LC_ALL=C pacman -Qi "$pkg" 2>/dev/null | awk -F: '/^Required By/ { gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit }')
|
|
if [[ -n $required_by_line && $required_by_line != None ]]; then
|
|
warn "Leaving retired package '$pkg' installed because it is still required by: $required_by_line"
|
|
else
|
|
warn "Leaving retired package '$pkg' installed because pacman reports remaining dependency constraints."
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
cleanup_legacy_user_paths() {
|
|
log "Cleaning legacy user command shims"
|
|
run_as_user env TARGET_HOME="$target_home" BACKUP_SUFFIX="$backup_suffix" bash <<'USER_CLEANUP'
|
|
set -euo pipefail
|
|
local_bin="$TARGET_HOME/.local/bin"
|
|
legacy_root="$TARGET_HOME/.local/share/omarchy"
|
|
legacy_bin="$legacy_root/bin/"
|
|
if [[ -d $local_bin ]]; then
|
|
shopt -s nullglob
|
|
for link in "$local_bin"/omarchy*; do
|
|
[[ -L $link ]] || continue
|
|
target=$(readlink "$link" || true)
|
|
case "$target" in
|
|
"$legacy_bin"*|../share/omarchy/bin/*|*.local/share/omarchy/bin/*)
|
|
rm -f "$link"
|
|
;;
|
|
esac
|
|
done
|
|
fi
|
|
|
|
legacy_hypr_config_active() {
|
|
local hyprland_conf="$TARGET_HOME/.config/hypr/hyprland.conf"
|
|
[[ -f $hyprland_conf ]] || return 1
|
|
grep -q '\.local/share/omarchy/default/hypr' "$hyprland_conf"
|
|
}
|
|
|
|
populate_legacy_hypr_defaults() {
|
|
local shim_hypr_dir="$1" backup="$2" tmp_dir archive_dir
|
|
|
|
rm -rf "$shim_hypr_dir"
|
|
mkdir -p "$shim_hypr_dir"
|
|
|
|
# The backup is the checkout the running session is sourcing right now, so it
|
|
# is both the correct content and already on disk. Only reach for the network
|
|
# when there is no backup, which means the legacy root was already a symlink.
|
|
if [[ -n $backup && -d $backup/default/hypr ]]; then
|
|
cp -a "$backup/default/hypr/." "$shim_hypr_dir/"
|
|
elif command -v curl >/dev/null 2>&1 && command -v tar >/dev/null 2>&1; then
|
|
tmp_dir=$(mktemp -d)
|
|
archive_dir="$tmp_dir/archive"
|
|
mkdir -p "$archive_dir"
|
|
if curl -fsSL https://github.com/omacom/omarchy/archive/refs/heads/master.tar.gz | tar -xz -C "$archive_dir" &&
|
|
[[ -d $archive_dir/omarchy-master/default/hypr ]]; then
|
|
cp -a "$archive_dir/omarchy-master/default/hypr/." "$shim_hypr_dir/"
|
|
fi
|
|
rm -rf "$tmp_dir"
|
|
fi
|
|
|
|
find "$shim_hypr_dir" -type f -name '*.conf' -print -quit | grep -q .
|
|
}
|
|
|
|
create_live_omarchy_overlay() {
|
|
local backup="$1" compat_root shim_hypr_dir entry base cleanup_hook
|
|
|
|
compat_root="$TARGET_HOME/.local/state/omarchy/upgrade-to-quattro-live-omarchy"
|
|
shim_hypr_dir="$TARGET_HOME/.local/state/omarchy/upgrade-to-quattro-hypr-defaults"
|
|
|
|
populate_legacy_hypr_defaults "$shim_hypr_dir" "$backup" || return 1
|
|
|
|
rm -rf "$compat_root"
|
|
mkdir -p "$compat_root/default"
|
|
|
|
shopt -s dotglob nullglob
|
|
for entry in /usr/share/omarchy/*; do
|
|
[[ -e $entry ]] || continue
|
|
base=$(basename "$entry")
|
|
[[ $base == default ]] && continue
|
|
ln -s "$entry" "$compat_root/$base"
|
|
done
|
|
for entry in /usr/share/omarchy/default/*; do
|
|
[[ -e $entry ]] || continue
|
|
base=$(basename "$entry")
|
|
[[ $base == hypr ]] && continue
|
|
ln -s "$entry" "$compat_root/default/$base"
|
|
done
|
|
ln -s "$shim_hypr_dir" "$compat_root/default/hypr"
|
|
|
|
cleanup_hook="$TARGET_HOME/.config/omarchy/hooks/post-boot.d/cleanup-upgrade-to-quattro-live-shim"
|
|
mkdir -p "$(dirname "$cleanup_hook")"
|
|
cat >"$cleanup_hook" <<CLEANUP_HOOK
|
|
#!/bin/bash
|
|
set -euo pipefail
|
|
legacy_root="\$HOME/.local/share/omarchy"
|
|
compat_root="$compat_root"
|
|
shim_hypr_dir="$shim_hypr_dir"
|
|
if [[ -L \$legacy_root && \$(readlink "\$legacy_root") == "\$compat_root" ]]; then
|
|
ln -sfn /usr/share/omarchy "\$legacy_root"
|
|
fi
|
|
rm -rf "\$compat_root" "\$shim_hypr_dir"
|
|
rm -f "\$0"
|
|
CLEANUP_HOOK
|
|
chmod +x "$cleanup_hook"
|
|
|
|
printf '%s\n' "$compat_root"
|
|
}
|
|
|
|
# Existing terminals still have OMARCHY_PATH=$HOME/.local/share/omarchy and
|
|
# often have $OMARCHY_PATH/bin early in PATH. Make that stale path resolve to
|
|
# the package-backed tree for the rest of the live session, while backing up
|
|
# the old git checkout for inspection/rollback. If the live Hyprland session is
|
|
# still using legacy ~/.config/hypr/*.conf files, keep those user files in place
|
|
# and provide a temporary default/hypr/*.conf shim so reloads do not explode.
|
|
if [[ -e /usr/share/omarchy ]]; then
|
|
mkdir -p "$TARGET_HOME/.local/share"
|
|
backup=""
|
|
if [[ -L $legacy_root ]]; then
|
|
rm -f "$legacy_root"
|
|
elif [[ -e $legacy_root ]]; then
|
|
backup="$legacy_root.omarchy-upgrade-to-quattro.$BACKUP_SUFFIX.bak"
|
|
mv "$legacy_root" "$backup"
|
|
fi
|
|
|
|
if legacy_hypr_config_active; then
|
|
if compat_root=$(create_live_omarchy_overlay "$backup"); then
|
|
ln -sfn "$compat_root" "$legacy_root"
|
|
else
|
|
echo "Warning: Could not prepare legacy Hyprland conf shim; linking ~/.local/share/omarchy directly to /usr/share/omarchy." >&2
|
|
ln -sfn /usr/share/omarchy "$legacy_root"
|
|
fi
|
|
else
|
|
ln -sfn /usr/share/omarchy "$legacy_root"
|
|
fi
|
|
fi
|
|
USER_CLEANUP
|
|
}
|
|
|
|
retired_user_units=(
|
|
hyprpolkitagent.service
|
|
mako.service
|
|
omarchy-battery-monitor.service
|
|
omarchy-battery-monitor.timer
|
|
omarchy-shell.service
|
|
swayosd-server.service
|
|
elephant.service
|
|
app-walker@autostart.service
|
|
)
|
|
|
|
cleanup_retired_user_unit_files() {
|
|
run_as_user env RETIRED_UNITS="${retired_user_units[*]}" bash <<'USER_UNITS'
|
|
set -euo pipefail
|
|
read -r -a units <<<"$RETIRED_UNITS"
|
|
shopt -s nullglob
|
|
for base in "$HOME/.config/systemd/user" "$HOME/.local/share/systemd/user"; do
|
|
[[ -d $base ]] || continue
|
|
for unit in "${units[@]}"; do
|
|
rm -f "$base/$unit"
|
|
for link in "$base"/*.wants/"$unit" "$base"/*.requires/"$unit"; do
|
|
rm -f "$link"
|
|
done
|
|
done
|
|
done
|
|
USER_UNITS
|
|
}
|
|
|
|
cleanup_retired_services() {
|
|
log "Disabling services retired by Omarchy quattro"
|
|
|
|
# iwd is retired alongside the NetworkManager enable it depends on. Do not
|
|
# stop systemd-networkd mid-upgrade; disabling is enough and avoids dropping a
|
|
# running connection before the user can reboot into NetworkManager.
|
|
local networkd_unit
|
|
for networkd_unit in \
|
|
systemd-networkd.service \
|
|
systemd-networkd.socket \
|
|
systemd-networkd-varlink.socket \
|
|
systemd-networkd-varlink-metrics.socket \
|
|
systemd-networkd-resolve-hook.socket; do
|
|
as_root systemctl disable "$networkd_unit" >/dev/null 2>&1 || true
|
|
done
|
|
as_root systemctl disable systemd-networkd-wait-online.service >/dev/null 2>&1 || true
|
|
as_root systemctl mask systemd-networkd-wait-online.service >/dev/null 2>&1 || true
|
|
|
|
cleanup_retired_user_unit_files
|
|
if [[ -S $target_runtime_dir/bus ]]; then
|
|
run_as_user_session systemctl --user disable "${retired_user_units[@]}" >/dev/null 2>&1 || true
|
|
run_as_user_session systemctl --user daemon-reload >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
|
|
# The Omarchy 3 session keeps its bar, launcher, and notifications until the
|
|
# reboot, which is the real cutover. Swapping the UI out underneath a running
|
|
# session buys nothing and only creates ways for the upgrade to look broken.
|
|
|
|
# Run a command inside the user's live Wayland session. Fails if no session
|
|
# can be discovered. Extra NAME=VALUE args extend the environment.
|
|
run_as_user_wayland_session() {
|
|
local wayland_display hyprland_signature
|
|
[[ -d $target_runtime_dir ]] || return 1
|
|
|
|
wayland_display=$(discover_wayland_display) || return 1
|
|
hyprland_signature=$(discover_hyprland_signature) || return 1
|
|
|
|
run_as_user_session \
|
|
WAYLAND_DISPLAY="$wayland_display" \
|
|
HYPRLAND_INSTANCE_SIGNATURE="$hyprland_signature" \
|
|
XDG_SESSION_TYPE=wayland \
|
|
OMARCHY_PATH=/usr/share/omarchy \
|
|
PATH="$package_path" \
|
|
"$@"
|
|
}
|
|
|
|
run_hyprctl_session() {
|
|
local hyprctl_bin
|
|
hyprctl_bin=/usr/bin/hyprctl
|
|
[[ -x $hyprctl_bin ]] || hyprctl_bin=$(command -v hyprctl || true)
|
|
[[ -n $hyprctl_bin ]] || return 1
|
|
|
|
run_as_user_wayland_session "$hyprctl_bin" "$@"
|
|
}
|
|
|
|
suppress_hyprland_config_reload() {
|
|
# Say so up front when the session cannot be reached at all. Otherwise this
|
|
# silently does nothing and the only evidence is the config error bar that
|
|
# shows up minutes later.
|
|
if ! run_as_user_wayland_session true >/dev/null 2>&1; then
|
|
warn "Could not reach the live Hyprland session; it will report config errors during the swap, and the Omarchy shell will start after reboot."
|
|
return 0
|
|
fi
|
|
|
|
log "Suppressing live Hyprland reloads during the config swap"
|
|
set_hyprland_reload_state true
|
|
hyprland_config_reload_suppressed=1
|
|
}
|
|
|
|
# Which API answers depends on the parser the running Hyprland was started
|
|
# with: the legacy .conf parser only takes keyword, and Quattro's Lua parser
|
|
# rejects it outright with "keyword can't work with non-legacy parsers". The
|
|
# session being upgraded is the legacy one, but the hyprctl binary is whichever
|
|
# the package transaction has installed by then, so ask both ways.
|
|
set_hyprland_reload_state() {
|
|
local value="$1"
|
|
|
|
run_hyprctl_session keyword misc:disable_autoreload "$value" >/dev/null 2>&1 || true
|
|
run_hyprctl_session keyword debug:suppress_errors "$value" >/dev/null 2>&1 || true
|
|
run_hyprctl_session eval \
|
|
"hl.config({ misc = { disable_autoreload = $value }, debug = { suppress_errors = $value } })" \
|
|
>/dev/null 2>&1 || true
|
|
}
|
|
|
|
restore_hyprland_config_reload() {
|
|
(( hyprland_config_reload_suppressed )) || return 0
|
|
|
|
set_hyprland_reload_state false
|
|
|
|
# Backstop. An explicit hyprctl reload re-reads the config from disk, which
|
|
# resets both keywords above before it reports what it found, so suppression
|
|
# cannot survive one, and Hyprland keeps the resulting error bar up until a
|
|
# later clean reload that this script deliberately never performs. Callers
|
|
# that reload during the swap are supposed to check
|
|
# OMARCHY_UPGRADE_TO_QUATTRO_LIVE, but one slipping through should not leave
|
|
# an error bar on a finished upgrade. By now the shims have made the legacy
|
|
# config resolve again, so clearing the overlay is safe.
|
|
run_hyprctl_session seterror disable >/dev/null 2>&1 || true
|
|
|
|
hyprland_config_reload_suppressed=0
|
|
}
|
|
|
|
ensure_sleep_lock_service() {
|
|
[[ -S $target_runtime_dir/bus ]] || return 0
|
|
|
|
log "Ensuring Omarchy sleep lock service is enabled"
|
|
run_as_user_session systemctl --user daemon-reload >/dev/null 2>&1 || true
|
|
run_as_user_session systemctl --user reset-failed omarchy-sleep-lock.service >/dev/null 2>&1 || true
|
|
if ! run_as_user_session systemctl --user enable --now omarchy-sleep-lock.service >/dev/null 2>&1; then
|
|
warn "Could not start omarchy-sleep-lock.service; it should start after reboot."
|
|
fi
|
|
}
|
|
|
|
run_post_upgrade_migrations() {
|
|
local pending_status
|
|
|
|
log "Running Omarchy migrations"
|
|
if ! run_as_user_omarchy OMARCHY_UPGRADE_TO_QUATTRO_LIVE=1 omarchy-migrate; then
|
|
fail "Omarchy migrations did not complete. Fix the error above and rerun the upgrade before rebooting."
|
|
fi
|
|
if run_as_user_omarchy omarchy-migrate --pending >/dev/null; then
|
|
fail "Omarchy migrations are still pending. Rerun the upgrade before rebooting."
|
|
else
|
|
pending_status=$?
|
|
if (( pending_status != 1 )); then
|
|
fail "Could not verify that Omarchy migrations completed. Rerun the upgrade before rebooting."
|
|
fi
|
|
fi
|
|
}
|
|
|
|
run_final_system_package_upgrade() {
|
|
log "Checking for remaining system package updates"
|
|
as_root env OMARCHY_UPDATE_PACMAN=1 pacman -Syu --noconfirm --ask 4 \
|
|
--overwrite '/etc/docker/daemon.json' \
|
|
--overwrite '/etc/gnupg/dirmngr.conf' \
|
|
--overwrite '/etc/mkinitcpio.conf.d/omarchy_hooks.conf' \
|
|
--overwrite '/etc/mkinitcpio.conf.d/thunderbolt_module.conf' \
|
|
--overwrite '/etc/modprobe.d/omarchy-usb-autosuspend.conf' \
|
|
--overwrite '/etc/sddm.conf.d/10-theme.conf' \
|
|
--overwrite '/etc/sddm.conf.d/10-wayland.conf' \
|
|
--overwrite '/etc/sudoers.d/omarchy-asdcontrol' \
|
|
--overwrite '/etc/sudoers.d/omarchy-passwd-tries' \
|
|
--overwrite '/etc/sudoers.d/omarchy-tzupdate' \
|
|
--overwrite '/etc/sysctl.d/90-omarchy-file-watchers.conf' \
|
|
--overwrite '/etc/sysctl.d/99-omarchy-sysctl.conf' \
|
|
--overwrite '/etc/systemd/logind.conf.d/10-ignore-power-button.conf' \
|
|
--overwrite '/etc/systemd/resolved.conf.d/10-disable-multicast.conf' \
|
|
--overwrite '/etc/systemd/resolved.conf.d/20-docker-dns.conf' \
|
|
--overwrite '/etc/systemd/system.conf.d/10-faster-shutdown.conf' \
|
|
--overwrite '/etc/systemd/system/user@.service.d/10-faster-shutdown.conf' \
|
|
--overwrite '/etc/systemd/system/docker.service.d/no-block-boot.conf' \
|
|
--overwrite '/etc/systemd/system/plocate-updatedb.service.d/ac-only.conf' \
|
|
--overwrite '/etc/systemd/system.conf.d/20-omarchy-nofile.conf' \
|
|
--overwrite '/etc/systemd/user.conf.d/20-omarchy-nofile.conf' \
|
|
--overwrite '/usr/lib/systemd/system-sleep/unmount-fuse' \
|
|
--overwrite '/usr/share/plymouth/themes/omarchy/*' \
|
|
--overwrite '/usr/share/sddm/hyprland.lua' \
|
|
--overwrite '/usr/share/sddm/themes/omarchy/*'
|
|
}
|
|
|
|
run_post_upgrade_update_steps() {
|
|
local update_output update_status
|
|
|
|
if PATH="$package_path" command -v omarchy-update-aur-pkgs >/dev/null 2>&1; then
|
|
if ! run_as_user_omarchy omarchy-update-aur-pkgs; then
|
|
warn "Could not update AUR packages; running omarchy-update after reboot may still update AUR packages."
|
|
fi
|
|
fi
|
|
|
|
if PATH="$package_path" command -v omarchy-update-mise >/dev/null 2>&1; then
|
|
if ! run_as_user_omarchy omarchy-update-mise; then
|
|
warn "Could not update mise tools; running omarchy-update after reboot may still update mise-managed tools."
|
|
fi
|
|
fi
|
|
|
|
if PATH="$package_path" command -v omarchy-update-available >/dev/null 2>&1; then
|
|
log "Refreshing update indicator state"
|
|
update_status=0
|
|
update_output=$(run_as_user_omarchy omarchy-update-available 2>&1) || update_status=$?
|
|
|
|
if (( update_status == 0 )); then
|
|
warn "Updates are still available after the upgrade; run omarchy-update after reboot:"
|
|
printf '%s\n' "$update_output" >&2
|
|
fi
|
|
fi
|
|
}
|
|
|
|
enable_system_service() {
|
|
local unit="$1"
|
|
as_root systemctl enable "$unit" >/dev/null 2>&1 || warn "Could not enable $unit; it may not be installed on this system."
|
|
}
|
|
|
|
apply_firewall_defaults() {
|
|
# Run the packaged config script rather than a copy of it, the same way
|
|
# configure_snapper_policy and configure_lock_authentication already do. The
|
|
# copy this replaces had drifted: it never installed the ufw-docker rules, so
|
|
# upgraded machines came up without the Docker firewall protections a fresh
|
|
# install gets.
|
|
local firewall_script=/usr/share/omarchy/install/config/firewall.sh
|
|
|
|
if ! command -v ufw >/dev/null 2>&1; then
|
|
warn "ufw is not installed; skipping firewall defaults."
|
|
return 0
|
|
fi
|
|
|
|
if ! as_root test -f "$firewall_script"; then
|
|
warn "$firewall_script is unavailable; firewall defaults were not applied."
|
|
return 0
|
|
fi
|
|
|
|
log "Applying Omarchy firewall defaults"
|
|
as_root env OMARCHY_PATH=/usr/share/omarchy PATH="$root_path" \
|
|
bash -euo pipefail "$firewall_script" ||
|
|
warn "Could not apply firewall defaults; run 'sudo bash $firewall_script' after reboot."
|
|
}
|
|
|
|
root_filesystem_encrypted() {
|
|
local root_source root_type
|
|
|
|
root_source=$(findmnt -n -o SOURCE / 2>/dev/null || true)
|
|
[[ $root_source == /dev/mapper/* ]] && return 0
|
|
|
|
if [[ -n $root_source ]]; then
|
|
root_type=$(lsblk -no TYPE "$root_source" 2>/dev/null | head -n1 || true)
|
|
[[ $root_type == crypt ]] && return 0
|
|
fi
|
|
|
|
as_root bash -c '[[ -s /etc/crypttab ]] && grep -qvE "^[[:space:]]*(#|$)" /etc/crypttab' >/dev/null 2>&1
|
|
}
|
|
|
|
should_enable_sddm_autologin() {
|
|
as_root test -f /etc/sddm.conf.d/autologin.conf && return 0
|
|
as_root systemctl is-enabled omarchy-seamless-login.service >/dev/null 2>&1 && return 0
|
|
root_filesystem_encrypted
|
|
}
|
|
|
|
apply_system_transition() {
|
|
log "Applying Omarchy quattro system transition"
|
|
[[ -d /usr/share/omarchy ]] || fail "/usr/share/omarchy was not installed."
|
|
|
|
as_root install -d -m 0755 /usr/share/icons/Yaru/scalable/actions
|
|
as_root ln -snf /usr/share/icons/Adwaita/symbolic/actions/go-previous-symbolic.svg \
|
|
/usr/share/icons/Yaru/scalable/actions/go-previous-symbolic.svg
|
|
as_root ln -snf /usr/share/icons/Adwaita/symbolic/actions/go-next-symbolic.svg \
|
|
/usr/share/icons/Yaru/scalable/actions/go-next-symbolic.svg
|
|
as_root gtk-update-icon-cache /usr/share/icons/Yaru >/dev/null 2>&1 || true
|
|
|
|
local browser_policy_helper=/usr/share/omarchy/install/helpers/browser-policy.sh
|
|
if ! as_root test -f "$browser_policy_helper"; then
|
|
warn "$browser_policy_helper is unavailable; Chromium policy directories were not hardened."
|
|
else
|
|
as_root env OMARCHY_PATH=/usr/share/omarchy \
|
|
bash -euo pipefail -c '
|
|
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
|
browser_policy_setup_dir /etc/chromium/policies/managed
|
|
for dir in "${BROWSER_POLICY_MANAGED_DIRS[@]}"; do
|
|
[[ $dir == "/etc/chromium/policies/managed" ]] && continue
|
|
[[ -d $dir || -L $dir ]] || continue
|
|
browser_policy_setup_dir "$dir"
|
|
done
|
|
'
|
|
fi
|
|
as_root install -d -m 0755 /usr/lib/chromium
|
|
printf '%s\n' '{"distribution":{"require_eula":false},"browser":{"theme":{"color_scheme":0,"color_scheme2":0}}}' | \
|
|
as_root tee /usr/lib/chromium/initial_preferences >/dev/null
|
|
|
|
# Deliberately do NOT add the user to the docker group. That group is
|
|
# root-equivalent, so it is opt-in now (Setup > Security > Sudoless Docker);
|
|
# re-granting it on upgrade would silently undo that default for everyone.
|
|
|
|
if [[ -f /usr/bin/powerprofilesctl ]]; then
|
|
as_root sed -i '/env python3/ c\#!/bin/python3' /usr/bin/powerprofilesctl || true
|
|
fi
|
|
|
|
as_root install -d -m 0755 /etc/systemd/resolved.conf.d /etc/docker
|
|
cat <<'EOF' | as_root tee /etc/systemd/resolved.conf.d/10-disable-multicast.conf >/dev/null
|
|
[Resolve]
|
|
LLMNR=no
|
|
MulticastDNS=no
|
|
EOF
|
|
cat <<'EOF' | as_root tee /etc/systemd/resolved.conf.d/20-docker-dns.conf >/dev/null
|
|
[Resolve]
|
|
DNSStubListenerExtra=172.17.0.1
|
|
EOF
|
|
cat <<'EOF' | as_root tee /etc/docker/daemon.json >/dev/null
|
|
{
|
|
"log-driver": "json-file",
|
|
"log-opts": { "max-size": "10m", "max-file": "5" },
|
|
"dns": ["172.17.0.1"],
|
|
"bip": "172.17.0.1/16"
|
|
}
|
|
EOF
|
|
as_root ln -sfn ../run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
|
|
|
|
# Fresh Omarchy socket-activates Docker. Disable the old always-on service if
|
|
# an upgraded install had enabled it, but do not stop running containers mid-upgrade.
|
|
as_root systemctl disable docker.service >/dev/null 2>&1 || true
|
|
|
|
enable_system_service cups.service
|
|
enable_system_service avahi-daemon.service
|
|
enable_system_service linux-modules-cleanup.service
|
|
enable_system_service docker.socket
|
|
enable_system_service systemd-resolved.service
|
|
# NetworkManager replaces iwd, and both halves of that swap belong in one
|
|
# step: a machine that boots with both enabled has two daemons fighting over
|
|
# the Wi-Fi adapter. iwd is disabled but never stopped, so the running
|
|
# connection survives until the reboot.
|
|
enable_system_service NetworkManager.service
|
|
as_root systemctl disable iwd.service >/dev/null 2>&1 || true
|
|
enable_system_service bluetooth.service
|
|
enable_system_service power-profiles-daemon.service
|
|
enable_system_service sddm.service
|
|
|
|
apply_firewall_defaults
|
|
|
|
log "Normalizing SDDM login configuration"
|
|
# The omarchy package already installed 10-theme.conf and 10-wayland.conf.
|
|
as_root install -d -m 0755 /etc/sddm.conf.d
|
|
cat <<'EOF' | as_root tee /etc/sddm.conf.d/99-omarchy-login.conf >/dev/null
|
|
[Users]
|
|
RememberLastUser=true
|
|
RememberLastSession=true
|
|
EOF
|
|
|
|
local autologin_user
|
|
if should_enable_sddm_autologin; then
|
|
if as_root test -f /etc/sddm.conf.d/autologin.conf; then
|
|
autologin_user=$(as_root awk -F= '/^User=/ { print $2; exit }' /etc/sddm.conf.d/autologin.conf 2>/dev/null || true)
|
|
fi
|
|
[[ -n ${autologin_user:-} ]] || autologin_user="$target_user"
|
|
# omarchy:heredoc-expands paths=none -- $autologin_user is a username, read
|
|
# back from the root-owned drop-in or falling back to $target_user. Same
|
|
# mechanism as the old getty override: a name expands, no path does.
|
|
cat <<EOF | as_root tee /etc/sddm.conf.d/autologin.conf >/dev/null
|
|
[Autologin]
|
|
User=$autologin_user
|
|
Session=omarchy.desktop
|
|
EOF
|
|
else
|
|
as_root rm -f /etc/sddm.conf.d/autologin.conf
|
|
fi
|
|
|
|
as_root install -d -m 0755 -o sddm -g sddm /var/lib/sddm 2>/dev/null || as_root install -d -m 0755 /var/lib/sddm
|
|
# omarchy:heredoc-expands paths=none -- $target_user is a username, not a
|
|
# path; SDDM's state file records who logged in last.
|
|
cat <<EOF | as_root tee /var/lib/sddm/state.conf >/dev/null
|
|
[Last]
|
|
Session=omarchy.desktop
|
|
User=$target_user
|
|
EOF
|
|
as_root chown sddm:sddm /var/lib/sddm /var/lib/sddm/state.conf 2>/dev/null || true
|
|
|
|
as_root rm -f /etc/systemd/system/getty@tty1.service.d/autologin.conf
|
|
as_root rm -f /etc/systemd/system/plymouth-quit.service.d/wait-for-graphical.conf
|
|
as_root rm -f /etc/systemd/system/omarchy-seamless-login.service
|
|
as_root rm -f /usr/local/bin/seamless-login
|
|
as_root systemctl disable omarchy-seamless-login.service >/dev/null 2>&1 || true
|
|
|
|
as_root systemctl disable makima.service >/dev/null 2>&1 || true
|
|
as_root rm -rf /etc/systemd/system/makima.service.d
|
|
as_root rm -f /etc/udev/rules.d/99-uinput.rules
|
|
as_root systemctl disable dell-xps-haptic-touchpad.service >/dev/null 2>&1 || true
|
|
as_root rm -f /etc/systemd/system/dell-xps-haptic-touchpad.service
|
|
as_root rm -f /etc/systemd/system/multi-user.target.wants/dell-xps-haptic-touchpad.service
|
|
as_root rm -rf /etc/systemd/system/dell-xps-haptic-touchpad.service.d
|
|
as_root rm -f /etc/udev/rules.d/99-dell-xps-haptic-touchpad.rules
|
|
as_root rm -f /etc/omarchy-dell-haptic-touchpad.env
|
|
as_root rm -f /etc/pacman.d/hooks/walker-restart.hook
|
|
as_root rm -f \
|
|
/etc/systemd/system.conf.d/99-omarchy-nofile.conf \
|
|
/etc/systemd/user.conf.d/99-omarchy-nofile.conf
|
|
as_root systemctl daemon-reload >/dev/null 2>&1 || true
|
|
|
|
if [[ -f /etc/pam.d/sddm ]]; then
|
|
as_root sed -i '/-auth.*pam_gnome_keyring\.so/d' /etc/pam.d/sddm
|
|
as_root sed -i '/-password.*pam_gnome_keyring\.so/d' /etc/pam.d/sddm
|
|
fi
|
|
|
|
if [[ -f /etc/pam.d/system-auth ]]; then
|
|
as_root sed -i 's|^\(auth\s\+required\s\+pam_faillock.so\)\s\+preauth.*$|\1 preauth silent deny=10 unlock_time=120|' /etc/pam.d/system-auth
|
|
as_root sed -i 's|^\(auth\s\+\[default=die\]\s\+pam_faillock.so\)\s\+authfail.*$|\1 authfail deny=10 unlock_time=120|' /etc/pam.d/system-auth
|
|
fi
|
|
if [[ -f /etc/pam.d/sddm-autologin ]]; then
|
|
as_root sed -i '/pam_faillock\.so preauth/d' /etc/pam.d/sddm-autologin
|
|
as_root sed -i '/pam_faillock\.so authsucc/d' /etc/pam.d/sddm-autologin
|
|
as_root sed -i '/auth.*pam_permit\.so/a auth required pam_faillock.so authsucc' /etc/pam.d/sddm-autologin
|
|
fi
|
|
}
|
|
|
|
apply_user_transition() {
|
|
log "Applying Omarchy quattro user transition"
|
|
[[ -d /usr/share/omarchy ]] || fail "/usr/share/omarchy was not installed."
|
|
|
|
run_as_user env \
|
|
TARGET_HOME="$target_home" \
|
|
BACKUP_SUFFIX="$backup_suffix" \
|
|
OMARCHY_UPGRADE_YES="$yes" \
|
|
OMARCHY_USER_NAME="${OMARCHY_USER_NAME:-}" \
|
|
OMARCHY_USER_EMAIL="${OMARCHY_USER_EMAIL:-}" \
|
|
bash <<'USER_SETUP'
|
|
set -euo pipefail
|
|
root=/usr/share/omarchy
|
|
state_dir="$HOME/.local/state/omarchy"
|
|
mkdir -p "$state_dir" "$HOME/.config"
|
|
|
|
file_sha256() {
|
|
sha256sum "$1" | awk '{print $1}'
|
|
}
|
|
|
|
known_config_default_hashes() {
|
|
cat <<'DEFAULT_HASHES'
|
|
# action<TAB>relative ~/.config path<TAB>sha256
|
|
# refresh: hash-matched shipped defaults
|
|
refresh alacritty/alacritty.toml d43ab27e10ba1fd0505d50fe374b9bd02fa2e1d888fe2252c5208542fe5ddc8f
|
|
refresh alacritty/alacritty.toml 4e627909552ceebc80f5839955a09a7f804c501fee9a1fe76e13d95ad6cf5030
|
|
refresh alacritty/alacritty.toml 73dc34be744039613ef522aab84c27468b61ff13d7bc0ed2d57a83904e13f613
|
|
refresh alacritty/alacritty.toml e25fae44db040d97714cf4ca3d77e48f0ae32fed9e02e6b3db9ad4d705bdaed5
|
|
refresh alacritty/alacritty.toml 999562ef35642910bec05c2444c3acdcd6e29bc60ec541529d9c69a20cad5c88
|
|
refresh alacritty/alacritty.toml 8bf6dee79e634949617cd7acdd2756da52f7c3031b8eac98a7948f2fef0cf07e
|
|
refresh alacritty/alacritty.toml e4d201b6b9afd2f591d64f98eb50eb19247b8b839c2eaacfe2623628203c486f
|
|
refresh autostart/org.fcitx.Fcitx5.desktop a0260e7f35579a73f77e257e9e3795abd8b33119a17b931ca6016a07036e1670
|
|
refresh btop/btop.conf e6b86ddda073e06bf64cfbbde9652f9335171d8ca8c622e617478a3c696a6a3d
|
|
refresh btop/btop.conf 7f4122f2ed5f4c95489950cce2c0b192e19ae6fa74b0666d25e0b10040b2cee8
|
|
refresh btop/btop.conf 4e17dd28c43948af48a408ed9b0901c008e57dfe3b8fcf09649ec10f28746137
|
|
refresh chromium/Default/Preferences 67aef8ae3a6e0ee1180057198f47f2ea412ea1f01051d77edce4c465fb83d887
|
|
refresh chromium-flags.conf 3a8fe5b85bc42b27f5a0f36f3b674dc8e22a7d670a6aabbdc4f2d4057b97a1d4
|
|
refresh chromium-flags.conf 3d52925a48ac3704f9427ecec70310d9c89be5442c41ece183122a9246837be4
|
|
refresh chromium-flags.conf 4bc8710615d2151464e9bd6d1123fbfbb8f56dea4cf0568320bc7b50d24ad205
|
|
refresh chromium-flags.conf 1df1a5282f9525bfbb97a697c8169c08c1adb2b5a4de5ae94d84df7b4a78f2fc
|
|
refresh chromium-flags.conf 0408c524892830f5cf43e94c389b897cbc093ede312f49a4627bfabf692653c2
|
|
|
|
refresh fcitx5/conf/clipboard.conf 2d53b40811ee7ceb71c7db0422a3ed733647f389379a57dbf2915d522dff4193
|
|
refresh fcitx5/conf/xcb.conf f5cf059182c4361fa7b16d5149f20a09eb1e0915f4463ba60dae626b86df7d9c
|
|
refresh foot/foot.ini 52e30647172522459b2179af714c19b62a35fe11bc23c49839fda5dca3253e84
|
|
refresh foot/foot.ini ac16453a4b4ebc997ff6732e7e4448dc759c25587fdb8aa1d1a7eee92b518987
|
|
refresh ghostty/config ed589d69296f5aa083e4dba43fc3d40cfbe2f6d10b382f3d0dc2ff76d5ec8fe5
|
|
refresh ghostty/config 2832c9c27cdcd735e913d538c3c61005fd90e909541bdcb746bb454851ae79ca
|
|
refresh ghostty/config 8200bc34b7ced8170d512c619f0408ac7dd7d7b4e64021ac66393d8a8bf1d951
|
|
refresh ghostty/config 006fbe4048de02fc040eeefd0974081c6f7df5dae419f8d542fc1adf08b0d6fb
|
|
refresh ghostty/config e55a409a490d4f80c19c9242a59db8e00e271e01feddb8fda6a5a7102d39af20
|
|
refresh ghostty/config b65e4903813d442a25540eef5a24313121c640a57114ee3d76d1ec7408d86b27
|
|
refresh ghostty/config 4e6ccba3d0584832cedf22713842f8ce7eca383c2ea3bfe61286d2b8df73f3fe
|
|
refresh git/config 3e1bdf77aa65b7bb412cc25c02bac4734e98c10e4a63815b98737bf817b6f682
|
|
refresh hypr/hyprsunset.conf 3ac2e9fe716fb8c612f07ca608ba7eee491921c2386118aae54ff9c8121726f2
|
|
refresh hypr/hyprsunset.conf e6fa7d909a90b64df7f75e8842bc2541e75463a47bbc7213ec79a56364f563f2
|
|
refresh hypr/hyprsunset.conf afe595d9d31574bde0f3ab8f7d370d0710bc188d6e5643f73255ffa7a202b647
|
|
refresh hypr/hyprsunset.conf 3042e4a923030239f4edf7b1943f41f4ed57107550177130c96f3b215b226e22
|
|
refresh hypr/xdph.conf 637899cd3926c4b8c758d3cb812cc1e7ac7643aca83a8e5030208234d064c839
|
|
refresh hypr/xdph.conf 2fded88a27b89f33cbc2b64a1258117df74b591f39a84c78ec3504a7d3b09136
|
|
refresh hyprland-preview-share-picker/config.yaml cb7f8768566ce41f8b09249b504ea8f6dbd6ed830637eef24c4e94e94486d113
|
|
refresh hyprland-preview-share-picker/config.yaml cf88364e497e4af2adaff9a0b7ca12227d29f831bc7e18846dd8f03bf4c618a8
|
|
refresh imv/config 58fd37d4862eb484d50c2c0cc507836e685a776d8da8dd9f20bd894809c31cd6
|
|
refresh imv/config 569b52b9614a78be67ce9f0a828ff1a7a73e4741d4751577ec6b247ab78e29ea
|
|
refresh imv/config 832b19821b4c69f9381aea6902b1b80c5682c30091e6d4a59493c8edf827bf72
|
|
refresh kitty/kitty.conf 69d6cbd2f8adcaec09407b5fb71ec7956cd510f6d64c36600ab7f7e7d3812cc6
|
|
refresh kitty/kitty.conf 6c5fe293bb92b1ea5fd955afcec126597ae0a665395db1d38b9beb6461c587d1
|
|
refresh kitty/kitty.conf 68779240358653a03ede95c4c872d94e3ab3742cf64a982b17e94f42aa2b74d4
|
|
refresh kitty/kitty.conf 9599cfd1f1fbd7e14fe1ba48196137be3cf8bcfa95f6c24d15d8053d88f73f0a
|
|
refresh kitty/kitty.conf ea3bc5e353f7312d774362310f7c511584efd6076c163b4cad1a9cc05c0f80ab
|
|
refresh kitty/kitty.conf 8984216fe0c0d006b969a7494dd4aad79ac78e9d15bd7a82ccbf5f10eac48fa6
|
|
refresh lazygit/config.yml e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
|
refresh obsidian/user-flags.conf 6c00f4daeff2b018bda247151a8edee44115273f554cfda00909bc5e56a3ae85
|
|
refresh omarchy/hooks/battery-low.d/play-warning-sound.sample 9623f5534cd52f76a51cfbb4e8a0699a550bf7f0f5f2ff54a1c78534d907fdf1
|
|
refresh omarchy/hooks/font-set.d/show-font-notification.sample d28090fd796b02fa799dcf7160e46ce9fc8d7a17b476ef87639d0b83a347dc37
|
|
refresh omarchy/hooks/post-boot.d/weather.sample fe37567a09046f1e4fcff9a9c74e4a036270cdf6da73dcc5cd3aa617f9a7c2ba
|
|
refresh omarchy/hooks/post-update.d/show-update-notification.sample 7995adc93a0ac6cd1afcd7ed9ac6d0ff058751ce4df4594aa78855603a613815
|
|
refresh omarchy/hooks/theme-set.d/show-theme-notification.sample b9d6754d95057bdb2f37595fd2f40055872118cb2f442bdc7b0b900317bac332
|
|
refresh omarchy/themed/alacritty.toml.tpl.sample ea5889bf79e273aa9cf1e7a1b2916dae7751fc0dced2cd53af36a2ebeecb4f1e
|
|
refresh omarchy/themed/alacritty.toml.tpl.sample a05d015227a406470b571d9d8f372eb8042df94b32cf746e71fd2f513c9880ac
|
|
refresh opencode/opencode.json 3806450805c8661a729c46aea325826e9ee7d158d650c9d750f5d0a763ec2c03
|
|
refresh opencode/opencode.json fb067af039252d48ef5b5c573a5e12c17357af4324b218aca5efa36f882846dd
|
|
refresh starship.toml b17c9b5f096fc125e97050e359171c6011d6b3c7d7e9ac28f630e75b4d4bb9db
|
|
refresh tmux/tmux.conf 345abaa3f128529abbbe4da3a24c227170c9679fb0af1f3d87eb15959e183219
|
|
refresh tmux/tmux.conf 93088311b8b67eb10ede3040b55e81193ff43fe16c5b2a6e3799300e00f39e3d
|
|
refresh tmux/tmux.conf 02d7c3914b73b8fec139316b9e1e88a40764617c6f4f1b6a3c05ad9c4a68b944
|
|
refresh tmux/tmux.conf 6633a3c59441087fdead8c30601e50c39fc7e74a66a32514296657332ff6cde9
|
|
refresh tmux/tmux.conf 473abae53d3aa7da59be65cc6301a15f6cc878045ae3f4c5e180ee013a0a04a7
|
|
refresh tmux/tmux.conf bf881713284aa2cc6722a199e42d57c005ae5922ef4eb6305593ead09b13ca7c
|
|
refresh xournalpp/settings.xml 47eafeeb3c246af1a5fb2cd1d87a748ce127d13410c351194c5d89bbaefb623d
|
|
refresh xournalpp/settings.xml 1c1a9efbf1b6dc7813bf3440fd5bf8409fc283e8d0192ca297dc36a10e12c846
|
|
# retire: nothing is actually copied. The new package owns the file at the
|
|
# system path below (shipped by omarchy-settings), so once the user's
|
|
# hash-matched copy in ~/.config is removed, the system path takes effect
|
|
# via the usual XDG/fontconfig/systemd lookup order. User overrides
|
|
# (hash mismatch) are kept active as backups.
|
|
# Legacy ~/.config path -> package-owned destination (where the file now lives):
|
|
# environment.d/fcitx.conf -> /usr/lib/environment.d/10-omarchy-fcitx.conf
|
|
# fastfetch/config.jsonc -> /etc/fastfetch/config.jsonc
|
|
# fontconfig/fonts.conf -> /usr/share/fontconfig/conf.avail/50-omarchy.conf
|
|
# mimeapps.list -> /usr/share/applications/mimeapps.list
|
|
# systemd/user/bt-agent.service -> /usr/lib/systemd/user/bt-agent.service
|
|
# systemd/user/omarchy-recover-internal-monitor.service -> /usr/lib/systemd/user/omarchy-recover-internal-monitor.service
|
|
# systemd/user/omarchy-sleep-lock.service -> /usr/lib/systemd/user/omarchy-sleep-lock.service
|
|
# uwsm/default -> /usr/share/omarchy/default/uwsm/default
|
|
# uwsm/env -> /usr/share/uwsm/env.d/10-omarchy
|
|
# xdg-terminals.list -> /usr/share/xdg-terminal-exec/hyprland-xdg-terminals.list
|
|
retire environment.d/fcitx.conf faacbd703a8f797013968b235fec6289a9c921d3ec7b7f8b4e1cd24392f68b9c
|
|
retire environment.d/fcitx.conf 8b077b6ea2594dc1d4b57592a3bc62703df4959e86b93ea0fc488b6686309095
|
|
retire fastfetch/config.jsonc 6adebf87d93902fb4bc68824a8e7c71e482846af23fea199e489c5496f791590
|
|
retire fastfetch/config.jsonc fe61d9f7cfac800adb7bd21f75f1bfcca266c609b6efa02a6e25303ac192e86e
|
|
retire fastfetch/config.jsonc 62fa26fa5fb8e11d31c5ec9f3a53b9b6b1124b29134b19329a092878ff624581
|
|
retire fastfetch/config.jsonc abb336459df8dea8f84df1dd54a2f223adec558df3de39cce84ca83f88acc02f
|
|
retire fastfetch/config.jsonc f1c9a999a4f31a215b10474eeac8894ce6da220d680c30d2aab7b3c1b7a539a6
|
|
retire fastfetch/config.jsonc 97aefdea2103d6c73e063bf0d11ad26da8fe60630305ac4eb2970cfc50cb0d91
|
|
retire fastfetch/config.jsonc 22565532d139ab8c2ddb21dcfe56460dc9f2f8a712b8d29f125c1c8b29beca37
|
|
retire fastfetch/config.jsonc b65cb9ca36eed45929bfc35443762b14957d3847f6bed361a338224a87450e3a
|
|
retire fastfetch/config.jsonc 3d153225131d3031b99b2e1a07e84d22cd0e51fcae1e701b08f6b6a558bc7a9c
|
|
retire fastfetch/config.jsonc 404d0273c380cfdad4a037fa87202fc1d96be178c867e8a9d27b0a772f6e2fa1
|
|
retire fastfetch/config.jsonc 4a98ff77ed46109d08ef7301249ea6823d1abba6c39c0871680feebf96620bf2
|
|
retire fastfetch/config.jsonc 2cc59c562e51ed3a58a784a17414ad40699253d0a9522eb7ce01faf6d7cff7c2
|
|
retire fastfetch/config.jsonc 07027778c12914c7077022cc835de4ac6c38af90aed539a871b13c7c89603e87
|
|
retire fastfetch/config.jsonc 0bc8cb0a24ec07e786cccbad446fd80b72520e7effd9bff8cfa27b7361211f4b
|
|
retire fastfetch/config.jsonc c827c3a24f64c418d309244a8a967def0c522d824addf65d8ee0741afbc970bd
|
|
retire fastfetch/config.jsonc c909b43ba000887cd418d9eaf98c1c9c5ba938aa27f9246cf8fa5662946c4e87
|
|
retire fastfetch/config.jsonc b2e5757851f0703edb8bcfc4b51a00bf5e303c1df26b8c63e6b7df6be4ead4ed
|
|
retire fastfetch/config.jsonc 1a0a085e7a80036f415148da21f772e99898de7e70768ec7b7613f8cc620700c
|
|
retire fontconfig/fonts.conf 7db33b0f8673f71f42604255cad222194294cd401315fecde6b83151eecebe91
|
|
retire fontconfig/fonts.conf f35b5b4bd0d8d3926f5d9a49ca791fc66b00793786420508d4a4c41ae9ccbefc
|
|
retire fontconfig/fonts.conf 6dec98b539388b95ecfdb3c7ab951001c712820eb0581002832cb7bfdde1a654
|
|
retire fontconfig/fonts.conf 3545f6c5a8c1465df7a4e251b3c2047d4ba03654c86636794f64cb0d8ea8ef63
|
|
retire fontconfig/fonts.conf 0f085b449f1cbe8eda3b59a6235bf2a3ddb6e982ab5d22fa642248916e63bafc
|
|
retire mimeapps.list 3b574cef135b5deb7a8a0c7e17139037cf1fe155300e3809f60bed0e04120975
|
|
retire systemd/user/bt-agent.service 0406b577a1225dc2a9f86638d3c346eb3635168576f04050be50ebcc0be6be12
|
|
retire systemd/user/omarchy-recover-internal-monitor.service b9b92cedc44cf3cb6216948629be55b53d16746e31896dc6469fd49ba55e82f4
|
|
retire systemd/user/omarchy-recover-internal-monitor.service e1483079b9f2aefcd43b4722c75a31643e5f3bb5a2eada5f52f1d7d201e8c289
|
|
retire systemd/user/omarchy-sleep-lock.service 6870b232a6c0474b59187882e6d25ae771bba735098bcbedef8a2b73b97e2b6a
|
|
retire systemd/user/omarchy-sleep-lock.service bcd1a76cb5c63514922bc5e11af22ae480fc6d06a99863364e02bdf3c7bdceaf
|
|
retire uwsm/default 2e89b03a3710b70cf754ea0c8db4388bb35a7519870036abff5ea8a584eafbc0
|
|
retire uwsm/default 3622ba134d29b639a155424a5226215e213c8508144e98001ffe128a8967c9b5
|
|
retire uwsm/default 73712c5a677532bd4afcee5800ae9b76e3d02a2130659a96a8b7fbe2fd2c3d53
|
|
retire uwsm/default 56f968d461da1b8360bc21d5fb7d5375121b772dd06d9397ebcda55a9097c2a0
|
|
retire uwsm/default f7558795e1f6b89357a780ad4e1c63ffdd56e423f31c9a888738afe894e16623
|
|
retire uwsm/env 60eae2629f2dbc70bb068de7bcb245032a429dd989f98b573eb315c0760b4ea5
|
|
retire uwsm/env daf70280edd80bae94660f8509d294df2aaa242d3428320bf6542341efff5a6d
|
|
retire uwsm/env 61a4147404a2b6b920f6f414a8c9bcef69b8bcf2a1251e1ddd2fbc37fe45e04d
|
|
retire uwsm/env 32130b0033e1d3229d89e6f73394e272eb3adad07314f740f8df9629c1f89beb
|
|
retire uwsm/env 278df755a1679f2735742c61de9789614cdb2021fbbb4e802f056c0403d1a6b8
|
|
retire uwsm/env 7be477d4e734d30ebc2caf9dbad6133dcb4aef38d64eb0979beab46f1c0e2464
|
|
retire uwsm/env 0c2cd9c32bd8aea26c62bc18c4a287a29999d4b2ec2b3216b666fb204ab3c471
|
|
retire uwsm/env 957dc0a95f8e909a378ce82967d7a55eb474e2fe5b2672212272e88530c4d4aa
|
|
retire uwsm/env 425036ed11f004f59a5fca324305b13189a6aca4b4dd1582532f075d14413d93
|
|
retire uwsm/env 9eb186609afce36a525e58e0b0858eb586869c5d067a7e5545bb5be606ab8a44
|
|
retire xdg-terminals.list 7303fa8d1d74eda73cf5fb4c90183a8608c191bb80d7fb9d96ab60f9df06022e
|
|
retire xdg-terminals.list d035da571a6bfc7d626d4a88846ea86a8adb6f6662f1f8b4abe2330e53ceb71b
|
|
DEFAULT_HASHES
|
|
}
|
|
|
|
declare -A known_config_default_hashes_by_key=()
|
|
while IFS=$' ' read -r action rel hash; do
|
|
[[ -n ${action:-} && $action != \#* ]] || continue
|
|
known_config_default_hashes_by_key["$action:$rel:$hash"]=1
|
|
done < <(known_config_default_hashes)
|
|
|
|
default_hash_paths() {
|
|
local action_filter="$1" action rel hash
|
|
known_config_default_hashes | while IFS=$' ' read -r action rel hash; do
|
|
[[ -n ${action:-} && $action != \#* ]] || continue
|
|
[[ $action == "$action_filter" ]] || continue
|
|
printf '%s\n' "$rel"
|
|
done | sort -u
|
|
}
|
|
|
|
is_known_default_hash() {
|
|
local action="$1" rel="$2" file="$3" hash
|
|
[[ -f $file ]] || return 1
|
|
hash=$(file_sha256 "$file")
|
|
[[ -n ${known_config_default_hashes_by_key["$action:$rel:$hash"]+set} ]]
|
|
}
|
|
|
|
backup_config_file() {
|
|
local file="$1"
|
|
[[ -e $file || -L $file ]] || return 0
|
|
cp -af "$file" "$file.omarchy-upgrade-to-quattro.$BACKUP_SUFFIX.bak"
|
|
}
|
|
|
|
mapfile -t retired_config_files < <(default_hash_paths retire)
|
|
|
|
# These are new Omarchy quattro config entry points. Older installs cannot have an
|
|
# Omarchy-shipped legacy copy to hash-match, so always install the quattro default.
|
|
always_copy_config_files=(
|
|
hypr/.luarc.json
|
|
hypr/autostart.lua
|
|
hypr/bindings.lua
|
|
hypr/hyprland.lua
|
|
hypr/input.lua
|
|
hypr/looknfeel.lua
|
|
hypr/monitors.lua
|
|
omarchy/extensions/omarchy-menu.jsonc
|
|
omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample
|
|
omarchy/shell.json
|
|
)
|
|
|
|
migrate_uwsm_env_customizations() {
|
|
local source="$1" dest="$HOME/.config/uwsm/env.d/99-omarchy-upgrade-env" tmp
|
|
[[ -f $source ]] || return 0
|
|
is_known_default_hash retire uwsm/env "$source" && return 0
|
|
|
|
tmp=$(mktemp)
|
|
awk '
|
|
function omitted(reason) { print ": # omarchy-upgrade omitted legacy Omarchy " reason; next }
|
|
|
|
/^[[:space:]]*export[[:space:]]+OMARCHY_PATH=\$HOME\/\.local\/share\/omarchy[[:space:]]*$/ { omitted("OMARCHY_PATH") }
|
|
/^[[:space:]]*export[[:space:]]+OMARCHY_PATH="\$\{OMARCHY_PATH:-\/usr\/share\/omarchy\}"[[:space:]]*$/ { omitted("OMARCHY_PATH") }
|
|
/^[[:space:]]*\[ -f \/etc\/omarchy\.conf \] && \. \/etc\/omarchy\.conf[[:space:]]*$/ { omitted("/etc/omarchy.conf source") }
|
|
/^[[:space:]]*export[[:space:]]+PATH=\$OMARCHY_PATH\/bin:\$PATH:\$HOME\/\.local\/bin[[:space:]]*$/ { omitted("PATH setup") }
|
|
/^[[:space:]]*export[[:space:]]+PATH="\$OMARCHY_PATH\/bin:\$PATH:\$HOME\/\.local\/bin"[[:space:]]*$/ { omitted("PATH setup") }
|
|
/^[[:space:]]*source[[:space:]]+~\/\.config\/uwsm\/default[[:space:]]*$/ { omitted("uwsm/default source") }
|
|
/^[[:space:]]*\[ -f "\$HOME\/\.config\/uwsm\/default" \] && \. "\$HOME\/\.config\/uwsm\/default"[[:space:]]*$/ { omitted("uwsm/default source") }
|
|
/^[[:space:]]*omarchy-cmd-present[[:space:]]+mise && eval "\$\(mise activate bash --shims\)"[[:space:]]*$/ { omitted("mise activation") }
|
|
|
|
{ print }
|
|
' "$source" >"$tmp"
|
|
|
|
if [[ -s $tmp ]]; then
|
|
if ! sh -n "$tmp" >/dev/null 2>&1; then
|
|
cp -f "$source" "$tmp"
|
|
fi
|
|
|
|
mkdir -p "$(dirname "$dest")"
|
|
{
|
|
echo '# Preserved custom ~/.config/uwsm/env content during Omarchy quattro upgrade.'
|
|
echo '# Exact legacy Omarchy-managed setup lines were replaced with no-op markers where safe.'
|
|
cat "$tmp"
|
|
} >"$dest"
|
|
fi
|
|
|
|
rm -f "$tmp"
|
|
}
|
|
|
|
is_retired_config_file() {
|
|
local rel="$1" retired
|
|
[[ $rel == systemd/user/* ]] && return 0
|
|
|
|
for retired in "${retired_config_files[@]}"; do
|
|
[[ $rel == "$retired" ]] && return 0
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
copy_config_default() {
|
|
local rel="$1" source="$root/config/$1" target="$HOME/.config/$1"
|
|
[[ -e $source || -L $source ]] || return 0
|
|
|
|
if [[ -e $target || -L $target ]]; then
|
|
if [[ -f $source && -f $target ]] && cmp -s "$source" "$target"; then
|
|
return 0
|
|
fi
|
|
backup_config_file "$target"
|
|
rm -f "$target"
|
|
fi
|
|
|
|
mkdir -p "$(dirname "$target")"
|
|
cp -P "$source" "$target"
|
|
}
|
|
|
|
copy_missing_config_defaults() {
|
|
local source_dir="$1" target_dir="$2" source_path rel target_path
|
|
[[ -d $source_dir ]] || return 0
|
|
|
|
while IFS= read -r -d '' source_path; do
|
|
rel="${source_path#$source_dir/}"
|
|
is_retired_config_file "$rel" && continue
|
|
|
|
target_path="$target_dir/$rel"
|
|
[[ -e $target_path || -L $target_path ]] && continue
|
|
|
|
mkdir -p "$(dirname "$target_path")"
|
|
cp -P "$source_path" "$target_path"
|
|
done < <(find "$source_dir" \( -type f -o -type l \) -print0)
|
|
}
|
|
|
|
refresh_known_config_defaults() {
|
|
local rel file default
|
|
|
|
while IFS= read -r rel; do
|
|
file="$HOME/.config/$rel"
|
|
default="$root/config/$rel"
|
|
[[ -f $file && -f $default ]] || continue
|
|
is_known_default_hash refresh "$rel" "$file" || continue
|
|
copy_config_default "$rel"
|
|
done < <(default_hash_paths refresh)
|
|
}
|
|
|
|
mark_removed_preinstalls_from_legacy_bindings() {
|
|
local bindings_file="$HOME/.config/hypr/bindings.lua"
|
|
local plain_bindings_sha="5d92d4008b04848256445da3cf5c10422a8d537baad499a4e63f60daecaefc6f"
|
|
|
|
[[ -f $bindings_file ]] || return 0
|
|
[[ $(file_sha256 "$bindings_file") == "$plain_bindings_sha" ]] || return 0
|
|
|
|
mkdir -p "$state_dir"
|
|
touch "$state_dir/preinstalls-removed"
|
|
}
|
|
|
|
copy_always_config_defaults() {
|
|
local rel
|
|
for rel in "${always_copy_config_files[@]}"; do
|
|
copy_config_default "$rel"
|
|
done
|
|
}
|
|
|
|
repair_chromium_copy_url_extension_flags() {
|
|
local file
|
|
local -a flag_files=(
|
|
"$HOME/.config/chromium-flags.conf"
|
|
"$HOME/.config/chrome-flags.conf"
|
|
"$HOME/.config/google-chrome-flags.conf"
|
|
"$HOME/.config/google-chrome-stable-flags.conf"
|
|
"$HOME/.config/brave-flags.conf"
|
|
"$HOME/.config/brave-browser-flags.conf"
|
|
"$HOME/.config/brave-origin-beta-flags.conf"
|
|
"$HOME/.config/microsoft-edge-flags.conf"
|
|
"$HOME/.config/microsoft-edge-stable-flags.conf"
|
|
"$HOME/.config/opera-flags.conf"
|
|
"$HOME/.config/vivaldi-flags.conf"
|
|
"$HOME/.config/helium-flags.conf"
|
|
)
|
|
|
|
for file in "${flag_files[@]}"; do
|
|
[[ -f $file ]] || continue
|
|
grep -qF '.local/share/omarchy/default/chromium/extensions/copy-url' "$file" || continue
|
|
|
|
backup_config_file "$file"
|
|
python3 - "$file" <<'CHROMIUM_FLAGS_PATCH_PY'
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
path = Path(sys.argv[1])
|
|
new_path = "/usr/share/omarchy/default/chromium/extensions/copy-url"
|
|
legacy_paths = [
|
|
"~/.local/share/omarchy/default/chromium/extensions/copy-url",
|
|
"$HOME/.local/share/omarchy/default/chromium/extensions/copy-url",
|
|
str(Path.home() / ".local/share/omarchy/default/chromium/extensions/copy-url"),
|
|
]
|
|
text = path.read_text()
|
|
for legacy_path in legacy_paths:
|
|
text = text.replace(legacy_path, new_path)
|
|
path.write_text(text)
|
|
CHROMIUM_FLAGS_PATCH_PY
|
|
done
|
|
}
|
|
|
|
repair_sleep_lock_unit_override() {
|
|
local unit="$HOME/.config/systemd/user/omarchy-sleep-lock.service"
|
|
local legacy_exec='ExecStart=%h/.local/share/omarchy/bin/omarchy-system-sleep-monitor'
|
|
local current_exec='ExecStart=/usr/bin/omarchy-system-sleep-monitor'
|
|
local tmp
|
|
[[ -f $unit ]] || return 0
|
|
|
|
if grep -qxF "$legacy_exec" "$unit"; then
|
|
backup_config_file "$unit"
|
|
tmp=$(mktemp)
|
|
LEGACY_EXEC="$legacy_exec" CURRENT_EXEC="$current_exec" awk '
|
|
$0 == ENVIRON["LEGACY_EXEC"] { print ENVIRON["CURRENT_EXEC"]; next }
|
|
{ print }
|
|
' "$unit" >"$tmp" && mv "$tmp" "$unit"
|
|
rm -f "$tmp"
|
|
fi
|
|
}
|
|
|
|
is_known_bashrc_default() {
|
|
local file="$1" hash
|
|
[[ -f $file ]] || return 1
|
|
hash=$(file_sha256 "$file")
|
|
|
|
case "$hash" in
|
|
06ef4dcaa0530d7410513b65b82eea45f147d386a7689dcf1f5636fdad7561ff|\
|
|
907d367c7589361788a2b0456003f6ef0a2e049548b77b9acf5b0f8cecc2ab0f|\
|
|
7f46fdc7e3016ab2309d49607a9dddc907b06066cebcf23a63e0110f5d962d73|\
|
|
322f495563435da1dc70e16968ee33f6a9d81ab361b79ebfd97814b83a89e011|\
|
|
56009cd83e46e0715911877f173bf1dab966f3e22b6995c530937f2feb18b667|\
|
|
1a8c98df85360a85acde31effa33c419ce9d27bc17e3a0d5900d4a6cb1743bcc)
|
|
return 0
|
|
;;
|
|
esac
|
|
|
|
return 1
|
|
}
|
|
|
|
install_bash_startup() {
|
|
local bashrc="$HOME/.bashrc" bash_profile="$HOME/.bash_profile"
|
|
[[ -f $root/default/bashrc ]] || return 0
|
|
|
|
backup_config_file "$bashrc"
|
|
|
|
if [[ ! -e $bashrc && ! -L $bashrc ]] || is_known_bashrc_default "$bashrc"; then
|
|
cp "$root/default/bashrc" "$bashrc"
|
|
elif grep -qxF 'source ~/.local/share/omarchy/default/bash/rc' "$bashrc" || grep -qxF 'source "$OMARCHY_PATH/default/bash/rc"' "$bashrc"; then
|
|
python3 - "$bashrc" <<'BASHRC_PATCH_PY'
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
path = Path(sys.argv[1])
|
|
text = path.read_text()
|
|
new_block = '''# /etc/omarchy.conf is written by omarchy-dev-link. When absent, force the
|
|
# package default instead of preserving a stale inherited dev-link value before
|
|
# we decide which rc file to source.
|
|
if [[ -f /etc/omarchy.conf ]]; then
|
|
source /etc/omarchy.conf
|
|
export OMARCHY_PATH="${OMARCHY_PATH:-/usr/share/omarchy}"
|
|
else
|
|
export OMARCHY_PATH=/usr/share/omarchy
|
|
fi
|
|
source "$OMARCHY_PATH/default/bash/rc"
|
|
'''
|
|
text = text.replace('source ~/.local/share/omarchy/default/bash/rc\n', new_block)
|
|
text = text.replace(': "${OMARCHY_PATH:=/usr/share/omarchy}"\nsource "$OMARCHY_PATH/default/bash/rc"\n', new_block)
|
|
path.write_text(text)
|
|
BASHRC_PATCH_PY
|
|
fi
|
|
|
|
if [[ ! -e $bash_profile && ! -L $bash_profile ]]; then
|
|
echo '[[ -f ~/.bashrc ]] && . ~/.bashrc' >"$bash_profile"
|
|
fi
|
|
}
|
|
|
|
# Some former ~/.config defaults are now package-owned elsewhere so future
|
|
# updates can change defaults without rewriting user files. Back them up before
|
|
# cleanup, then keep only real user customizations active.
|
|
uwsm_env="$HOME/.config/uwsm/env"
|
|
if [[ -f $uwsm_env ]]; then
|
|
cp -f "$uwsm_env" "$uwsm_env.omarchy-upgrade-to-quattro.$BACKUP_SUFFIX.bak"
|
|
migrate_uwsm_env_customizations "$uwsm_env"
|
|
fi
|
|
|
|
for rel in "${retired_config_files[@]}"; do
|
|
file="$HOME/.config/$rel"
|
|
backup_config_file "$file"
|
|
done
|
|
|
|
# Existing user config may be customized. Refresh only files whose hash matches
|
|
# a default Omarchy has shipped before, fill in missing defaults for old installs,
|
|
# and always write the quattro-only entry points that older installs cannot have.
|
|
refresh_known_config_defaults
|
|
copy_missing_config_defaults "$root/config" "$HOME/.config"
|
|
mark_removed_preinstalls_from_legacy_bindings
|
|
copy_always_config_defaults
|
|
repair_chromium_copy_url_extension_flags
|
|
|
|
# Carry over user-added backgrounds from the legacy git checkout backup, then
|
|
# remove old symlinks that pointed ~/.config/omarchy/themes/* back into the
|
|
# pre-quattro checkout. Omarchy quattro reads stock themes from /usr/share/omarchy/themes.
|
|
legacy_omarchy_backup="$HOME/.local/share/omarchy.omarchy-upgrade-to-quattro.$BACKUP_SUFFIX.bak"
|
|
if [[ -d $legacy_omarchy_backup/themes && -d $legacy_omarchy_backup/.git ]]; then
|
|
(
|
|
cd "$legacy_omarchy_backup"
|
|
git ls-files --others -- 'themes/*/backgrounds/*' 2>/dev/null | while IFS= read -r bg_file; do
|
|
theme_name=${bg_file#themes/}
|
|
theme_name=${theme_name%%/*}
|
|
mkdir -p "$HOME/.config/omarchy/backgrounds/$theme_name"
|
|
mv "$bg_file" "$HOME/.config/omarchy/backgrounds/$theme_name/"
|
|
done
|
|
)
|
|
fi
|
|
if [[ -d $HOME/.config/omarchy/themes ]]; then
|
|
find "$HOME/.config/omarchy/themes" -mindepth 1 -maxdepth 1 -type l -delete
|
|
fi
|
|
|
|
# Retire legacy UI configs replaced by omarchy-shell. Hyprland .conf files are
|
|
# intentionally left in place for users to reference/port after the upgrade.
|
|
for rel in waybar swayosd mako walker; do
|
|
file="$HOME/.config/$rel"
|
|
if [[ -e $file || -L $file ]]; then
|
|
backup="$file.omarchy-upgrade-to-quattro.$BACKUP_SUFFIX.bak"
|
|
rm -rf "$backup"
|
|
mv "$file" "$backup"
|
|
fi
|
|
done
|
|
|
|
for rel in "${retired_config_files[@]}"; do
|
|
file="$HOME/.config/$rel"
|
|
backup="$file.omarchy-upgrade-to-quattro.$BACKUP_SUFFIX.bak"
|
|
|
|
# UWSM env was hardcoded to the legacy checkout on older installs. Custom
|
|
# non-Omarchy lines were migrated to env.d above; never keep the active file.
|
|
if [[ $rel == uwsm/env ]]; then
|
|
rm -f "$file"
|
|
continue
|
|
fi
|
|
|
|
if [[ -f $backup ]] && ! is_known_default_hash retire "$rel" "$backup"; then
|
|
# Keep intentional user overrides active; package defaults remain lower
|
|
# priority under /usr/lib or /usr/share.
|
|
mkdir -p "$(dirname "$file")"
|
|
cp -af "$backup" "$file"
|
|
else
|
|
rm -f "$file"
|
|
fi
|
|
done
|
|
repair_sleep_lock_unit_override
|
|
install_bash_startup
|
|
|
|
mkdir -p "$HOME/.agents/skills" "$HOME/.claude/skills" "$HOME/.codex/skills" "$HOME/.pi/agent/skills"
|
|
if [[ -d $root/default/agents/skills/omarchy ]]; then
|
|
ln -sfn "$root/default/agents/skills/omarchy" "$HOME/.agents/skills/omarchy"
|
|
ln -sfn "$root/default/agents/skills/omarchy" "$HOME/.claude/skills/omarchy"
|
|
ln -sfn "$root/default/agents/skills/omarchy" "$HOME/.codex/skills/omarchy"
|
|
ln -sfn "$root/default/agents/skills/omarchy" "$HOME/.pi/agent/skills/omarchy"
|
|
fi
|
|
|
|
mkdir -p "$HOME/.local/state/omarchy/toggles/hypr"
|
|
if [[ -f $root/default/hypr/toggles/flags.lua ]]; then
|
|
cp "$root/default/hypr/toggles/flags.lua" "$HOME/.local/state/omarchy/toggles/hypr/"
|
|
fi
|
|
# Legacy Hyprland .conf entry points source ~/.local/state/omarchy/toggles/hypr/*.conf.
|
|
# Keep an empty match around for the live pre-reboot session; quattro Lua ignores it.
|
|
if [[ -f $HOME/.config/hypr/hyprland.conf ]]; then
|
|
: >"$HOME/.local/state/omarchy/toggles/hypr/flags.conf"
|
|
fi
|
|
|
|
mkdir -p "$HOME/.local/share/nautilus-python/extensions"
|
|
for extension in localsend.py transcode.py; do
|
|
if [[ -f $root/default/nautilus-python/extensions/$extension ]]; then
|
|
cp "$root/default/nautilus-python/extensions/$extension" "$HOME/.local/share/nautilus-python/extensions/"
|
|
fi
|
|
done
|
|
|
|
mkdir -p "$HOME/.config/omarchy/branding"
|
|
[[ -f $root/icon.txt ]] && cp "$root/icon.txt" "$HOME/.config/omarchy/branding/about.txt"
|
|
[[ -f $root/logo.txt ]] && cp "$root/logo.txt" "$HOME/.config/omarchy/branding/screensaver.txt"
|
|
|
|
mkdir -p "$HOME/Downloads" "$HOME/Pictures" "$HOME/Videos" "$HOME/Projects" "$HOME/.config/gtk-3.0"
|
|
if command -v xdg-user-dirs-update >/dev/null 2>&1; then
|
|
xdg-user-dirs-update --set TEMPLATES "$HOME" || true
|
|
xdg-user-dirs-update --set PUBLICSHARE "$HOME" || true
|
|
xdg-user-dirs-update --set DESKTOP "$HOME" || true
|
|
fi
|
|
rmdir "$HOME/Templates" "$HOME/Public" "$HOME/Desktop" 2>/dev/null || true
|
|
touch "$HOME/.config/gtk-3.0/bookmarks"
|
|
for dir in Downloads Projects Pictures Videos; do
|
|
bookmark="file://$HOME/$dir $dir"
|
|
grep -qxF "$bookmark" "$HOME/.config/gtk-3.0/bookmarks" || echo "$bookmark" >>"$HOME/.config/gtk-3.0/bookmarks"
|
|
done
|
|
|
|
if [[ -n ${OMARCHY_USER_NAME//[[:space:]]/} ]]; then
|
|
git config --global user.name "$OMARCHY_USER_NAME" || true
|
|
fi
|
|
if [[ -n ${OMARCHY_USER_EMAIL//[[:space:]]/} ]]; then
|
|
git config --global user.email "$OMARCHY_USER_EMAIL" || true
|
|
fi
|
|
if [[ ! -f $HOME/.XCompose ]]; then
|
|
cat >"$HOME/.XCompose" <<EOF
|
|
# Run omarchy-restart-xcompose to apply changes
|
|
|
|
# Include fast emoji access
|
|
include "/usr/share/omarchy/default/xcompose"
|
|
|
|
# Identification
|
|
<Multi_key> <space> <n> : "$OMARCHY_USER_NAME"
|
|
<Multi_key> <space> <e> : "$OMARCHY_USER_EMAIL"
|
|
EOF
|
|
fi
|
|
|
|
mkdir -p "$HOME/.config/omarchy/themes" "$HOME/.config/btop/themes"
|
|
legacy_current_dir="$HOME/.config/omarchy/current"
|
|
current_state_dir="$HOME/.local/state/omarchy/current"
|
|
if [[ -e $legacy_current_dir || -L $legacy_current_dir ]]; then
|
|
mkdir -p "$HOME/.local/state/omarchy"
|
|
if [[ ! -e $current_state_dir && ! -L $current_state_dir ]]; then
|
|
mv "$legacy_current_dir" "$current_state_dir"
|
|
else
|
|
if [[ -d $legacy_current_dir && -d $current_state_dir ]]; then
|
|
cp -an "$legacy_current_dir/." "$current_state_dir/" 2>/dev/null || true
|
|
fi
|
|
rm -rf "$legacy_current_dir"
|
|
fi
|
|
fi
|
|
replace_legacy_current_path() {
|
|
local file="$1"
|
|
|
|
[[ -f $file ]] || return 0
|
|
python3 - "$file" "$HOME" <<'PY'
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
path = Path(sys.argv[1])
|
|
home = sys.argv[2]
|
|
text = path.read_text()
|
|
replacements = {
|
|
f"{home}/.config/omarchy/current": f"{home}/.local/state/omarchy/current",
|
|
"~/.config/omarchy/current": "~/.local/state/omarchy/current",
|
|
"../omarchy/current": "../../.local/state/omarchy/current",
|
|
}
|
|
for old, new in replacements.items():
|
|
text = text.replace(old, new)
|
|
path.write_text(text)
|
|
PY
|
|
}
|
|
for file in \
|
|
"$HOME/.config/alacritty/alacritty.toml" \
|
|
"$HOME/.config/foot/foot.ini" \
|
|
"$HOME/.config/ghostty/config" \
|
|
"$HOME/.config/hypr/hyprland.conf" \
|
|
"$HOME/.config/hyprland-preview-share-picker/config.yaml" \
|
|
"$HOME/.config/kitty/kitty.conf"; do
|
|
replace_legacy_current_path "$file"
|
|
done
|
|
ln -snf "$HOME/.local/state/omarchy/current/theme/btop.theme" "$HOME/.config/btop/themes/current.theme"
|
|
if [[ ! -e $HOME/.local/state/omarchy/current/background && -d $HOME/.local/state/omarchy/current/theme/backgrounds ]]; then
|
|
background=$(find "$HOME/.local/state/omarchy/current/theme/backgrounds" -maxdepth 1 -type f \
|
|
\( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' \
|
|
-o -iname '*.mp4' -o -iname '*.m4v' -o -iname '*.mov' -o -iname '*.webm' -o -iname '*.mkv' -o -iname '*.avi' \) \
|
|
| sort | head -n1)
|
|
[[ -n ${background:-} ]] && ln -snf "$background" "$HOME/.local/state/omarchy/current/background"
|
|
fi
|
|
|
|
mkdir -p "$HOME/.config/wireplumber/wireplumber.conf.d"
|
|
if [[ -f $root/default/wireplumber/wireplumber.conf.d/bluetooth-a2dp-autoconnect.conf ]]; then
|
|
cp "$root/default/wireplumber/wireplumber.conf.d/bluetooth-a2dp-autoconnect.conf" "$HOME/.config/wireplumber/wireplumber.conf.d/"
|
|
fi
|
|
|
|
enable_user_unit() {
|
|
local unit="$1" unit_path link_target target
|
|
local -a targets=()
|
|
|
|
if [[ -f $HOME/.config/systemd/user/$unit ]]; then
|
|
unit_path="$HOME/.config/systemd/user/$unit"
|
|
link_target="../$unit"
|
|
elif [[ -f /usr/lib/systemd/user/$unit ]]; then
|
|
unit_path="/usr/lib/systemd/user/$unit"
|
|
link_target="$unit_path"
|
|
elif [[ -f $root/default/systemd/user/$unit ]]; then
|
|
unit_path="$root/default/systemd/user/$unit"
|
|
link_target="$unit_path"
|
|
else
|
|
return 0
|
|
fi
|
|
|
|
mapfile -t targets < <(awk -F= '
|
|
/^\[Install\]/ { in_install=1; next }
|
|
/^\[/ { in_install=0 }
|
|
in_install && $1 == "WantedBy" {
|
|
n=split($2, values, /[[:space:]]+/)
|
|
for (i = 1; i <= n; i++) if (values[i] != "") print values[i]
|
|
}
|
|
' "$unit_path")
|
|
((${#targets[@]})) || targets=(default.target)
|
|
for target in "${targets[@]}"; do
|
|
mkdir -p "$HOME/.config/systemd/user/$target.wants"
|
|
ln -sfn "$link_target" "$HOME/.config/systemd/user/$target.wants/$unit"
|
|
done
|
|
}
|
|
enable_user_unit bt-agent.service
|
|
enable_user_unit omarchy-sleep-lock.service
|
|
enable_user_unit omarchy-recover-internal-monitor.service
|
|
mkdir -p "$HOME/.config/systemd/user/default.target.wants"
|
|
for unit in pipewire-pulse.service pipewire-pulse.socket; do
|
|
if [[ -e /usr/lib/systemd/user/$unit ]]; then
|
|
ln -sfn "/usr/lib/systemd/user/$unit" "$HOME/.config/systemd/user/default.target.wants/$unit"
|
|
fi
|
|
done
|
|
|
|
keyring_dir="$HOME/.local/share/keyrings"
|
|
keyring_file="$keyring_dir/Default_keyring.keyring"
|
|
default_file="$keyring_dir/default"
|
|
mkdir -p "$keyring_dir"
|
|
if [[ ! -f $keyring_file ]]; then
|
|
cat >"$keyring_file" <<EOF
|
|
[keyring]
|
|
display-name=Default keyring
|
|
ctime=$(date +%s)
|
|
mtime=0
|
|
lock-on-idle=false
|
|
lock-after=false
|
|
EOF
|
|
fi
|
|
if [[ ! -f $default_file ]]; then
|
|
printf 'Default_keyring\n' >"$default_file"
|
|
fi
|
|
chmod 700 "$keyring_dir"
|
|
chmod 600 "$keyring_file"
|
|
chmod 644 "$default_file"
|
|
|
|
mkdir -p "$HOME/.local/share/applications"
|
|
rm -f "$HOME/.config/autostart/walker.desktop"
|
|
rm -rf "$HOME/.config/elephant" "$HOME/.config/makima" "$HOME/.config/wiremix"
|
|
rm -rf "$HOME/.config/systemd/user/app-walker@autostart.service.d"
|
|
rm -f \
|
|
"$HOME/.config/systemd/user/omarchy-battery-monitor.service" \
|
|
"$HOME/.config/systemd/user/omarchy-battery-monitor.timer" \
|
|
"$HOME/.config/systemd/user/omarchy-shell.service" \
|
|
"$HOME/.config/systemd/user/swayosd-server.service" \
|
|
"$HOME/.config/systemd/user/elephant.service"
|
|
rm -f \
|
|
"$HOME/.local/state/omarchy/toggles/mako.ini" \
|
|
"$HOME/.local/state/omarchy/toggles/waybar-off" \
|
|
"$HOME/.local/state/omarchy/toggles/hyprlock.conf" \
|
|
"$HOME/.local/state/omarchy/toggles/lock.conf" \
|
|
"$HOME/.local/state/omarchy/toggles/hypr/rounded-corners.lua"
|
|
|
|
for desktop in \
|
|
About.desktop \
|
|
Activity.desktop \
|
|
Alacritty.desktop \
|
|
Cliamp.desktop \
|
|
blueberry.desktop \
|
|
nvim.desktop \
|
|
omarchy.desktop \
|
|
org.pulseaudio.pavucontrol.desktop \
|
|
vlc.desktop \
|
|
wiremix.desktop; do
|
|
rm -f "$HOME/.local/share/applications/$desktop"
|
|
done
|
|
|
|
legacy_icons_dir="$HOME/.local/share/applications/icons"
|
|
legacy_icons_backup="$HOME/.local/share/applications/icons.omarchy-upgrade-to-quattro.$BACKUP_SUFFIX.bak"
|
|
if [[ -d $legacy_icons_dir ]]; then
|
|
mkdir -p "$legacy_icons_backup"
|
|
for icon in \
|
|
Basecamp.png \
|
|
Battle.net.png \
|
|
ChatGPT.png \
|
|
Cliamp.png \
|
|
Discord.png \
|
|
"Disk Usage.png" \
|
|
Docker.png \
|
|
GitHub.png \
|
|
"Google Contacts.png" \
|
|
"Google Maps.png" \
|
|
"Google Messages.png" \
|
|
"Google Photos.png" \
|
|
HEY.png \
|
|
imv.png \
|
|
"Retro Gaming.png" \
|
|
WhatsApp.png \
|
|
windows.png \
|
|
X.png \
|
|
YouTube.png \
|
|
Zoom.png; do
|
|
if [[ -e $legacy_icons_dir/$icon ]]; then
|
|
mv -f "$legacy_icons_dir/$icon" "$legacy_icons_backup/"
|
|
fi
|
|
done
|
|
rmdir "$legacy_icons_dir" 2>/dev/null || true
|
|
rmdir "$legacy_icons_backup" 2>/dev/null || true
|
|
fi
|
|
|
|
if [[ -d $root/applications ]]; then
|
|
find "$root/applications" -maxdepth 1 -type f -name '*.desktop' -exec cp -f {} "$HOME/.local/share/applications/" \;
|
|
fi
|
|
if [[ -f $root/default/alacritty/Alacritty.desktop && ! -f $HOME/.local/share/applications/Alacritty.desktop ]]; then
|
|
cp "$root/default/alacritty/Alacritty.desktop" "$HOME/.local/share/applications/"
|
|
fi
|
|
if command -v update-desktop-database >/dev/null 2>&1; then
|
|
update-desktop-database "$HOME/.local/share/applications" >/dev/null 2>&1 || true
|
|
fi
|
|
if command -v xdg-settings >/dev/null 2>&1; then
|
|
env -u BROWSER xdg-settings set default-web-browser chromium.desktop || true
|
|
fi
|
|
if command -v xdg-mime >/dev/null 2>&1; then
|
|
xdg-mime default HEY.desktop x-scheme-handler/mailto || true
|
|
fi
|
|
|
|
# This script is fetched from the branch and runs against whatever packaged
|
|
# tree the channel currently serves, so the packaged tree can be older than the
|
|
# script. The completion markers are written directly rather than through
|
|
# omarchy-done, because a command missing from an older build would abort the
|
|
# upgrade two thirds of the way through.
|
|
done_dir="$state_dir/done"
|
|
mkdir -p "$done_dir"
|
|
touch "$done_dir/first-run-user" "$done_dir/finalize-user"
|
|
rm -f "$state_dir/first-run-user.done" "$state_dir/finalize-user.done"
|
|
USER_SETUP
|
|
}
|
|
|
|
apply_user_hardware_transition() {
|
|
if grep -qi "DX13260" /sys/class/dmi/id/product_name 2>/dev/null ||
|
|
grep -qi "DX13260" /sys/class/dmi/id/product_family 2>/dev/null; then
|
|
log "Adjusting text scaling for Dell XPS 13 DX13260"
|
|
if ! run_as_user_session gsettings set org.gnome.desktop.interface text-scaling-factor 0.95; then
|
|
warn "Could not adjust text scaling for Dell XPS 13 DX13260; continuing."
|
|
fi
|
|
fi
|
|
}
|
|
|
|
write_legacy_theme_hyprland_conf() {
|
|
run_as_user env HOME="$target_home" bash <<'THEME_COMPAT'
|
|
set -euo pipefail
|
|
hyprland_conf="$HOME/.config/hypr/hyprland.conf"
|
|
[[ -f $hyprland_conf ]] || exit 0
|
|
# Legacy current-path references were already rewritten during the user
|
|
# transition; only the theme hyprland.conf shim is needed here.
|
|
grep -q 'current/theme/hyprland.conf' "$hyprland_conf" || exit 0
|
|
|
|
theme_dir="$HOME/.local/state/omarchy/current/theme"
|
|
colors="$theme_dir/colors.toml"
|
|
target="$theme_dir/hyprland.conf"
|
|
mkdir -p "$theme_dir"
|
|
accent="7aa2f7"
|
|
if [[ -f $colors ]]; then
|
|
parsed=$(awk -F= '/^[[:space:]]*accent[[:space:]]*=/ { gsub(/[[:space:]"#]/, "", $2); print $2; exit }' "$colors")
|
|
[[ -n ${parsed:-} ]] && accent="$parsed"
|
|
fi
|
|
{
|
|
printf '$activeBorderColor = rgb(%s)\n\n' "$accent"
|
|
cat <<'EOF'
|
|
general {
|
|
col.active_border = $activeBorderColor
|
|
}
|
|
|
|
group {
|
|
col.border_active = $activeBorderColor
|
|
}
|
|
EOF
|
|
} >"$target"
|
|
THEME_COMPAT
|
|
}
|
|
|
|
refresh_current_theme_after_upgrade() {
|
|
local theme_name runtime_dir
|
|
|
|
theme_name=$(run_as_user env HOME="$target_home" bash -c '
|
|
set -euo pipefail
|
|
theme_name_path="$HOME/.local/state/omarchy/current/theme.name"
|
|
current_theme_path="$HOME/.local/state/omarchy/current/theme"
|
|
legacy_theme_name_path="$HOME/.config/omarchy/current/theme.name"
|
|
legacy_current_theme_path="$HOME/.config/omarchy/current/theme"
|
|
|
|
if [[ -f $theme_name_path ]]; then
|
|
read -r theme_name <"$theme_name_path" || true
|
|
printf "%s" "${theme_name:-}"
|
|
elif [[ -f $legacy_theme_name_path ]]; then
|
|
read -r theme_name <"$legacy_theme_name_path" || true
|
|
printf "%s" "${theme_name:-}"
|
|
elif [[ -L $current_theme_path ]]; then
|
|
basename -- "$(readlink "$current_theme_path")"
|
|
elif [[ -L $legacy_current_theme_path ]]; then
|
|
basename -- "$(readlink "$legacy_current_theme_path")"
|
|
else
|
|
printf ""
|
|
fi
|
|
')
|
|
theme_name=${theme_name//$'\r'/}
|
|
theme_name=${theme_name//$'\n'/}
|
|
[[ -n $theme_name ]] || theme_name="Tokyo Night"
|
|
|
|
log "Refreshing current theme with Omarchy quattro templates ($theme_name)"
|
|
|
|
runtime_dir="$target_runtime_dir"
|
|
[[ -d $runtime_dir ]] || runtime_dir=/tmp
|
|
|
|
if ! run_as_user_omarchy \
|
|
XDG_RUNTIME_DIR="$runtime_dir" \
|
|
DBUS_SESSION_BUS_ADDRESS="unix:path=$runtime_dir/bus" \
|
|
XDG_CURRENT_DESKTOP=Hyprland \
|
|
XDG_SESSION_DESKTOP=Hyprland \
|
|
XDG_SESSION_TYPE=wayland \
|
|
DESKTOP_SESSION=hyprland \
|
|
OMARCHY_THEME_HEADLESS=1 \
|
|
omarchy-theme-set "$theme_name"; then
|
|
warn "Could not refresh current theme '$theme_name'. Run 'omarchy theme set \"$theme_name\"' after reboot if theme-dependent app config looks stale."
|
|
fi
|
|
|
|
write_legacy_theme_hyprland_conf
|
|
|
|
# Headless theme refresh intentionally skips omarchy-theme-set's live post
|
|
# hooks because one of them runs `hyprctl reload`. Still poke terminal
|
|
# emulators so the active upgrade terminal picks up generated theme files.
|
|
run_as_user_omarchy omarchy-restart-terminal >/dev/null 2>&1 || true
|
|
|
|
# apply_system_transition purged user-owned color.json. Headless theme-set
|
|
# skipped omarchy-theme-set-browser, so rewrite the colour here.
|
|
run_as_user_omarchy omarchy-theme-set-browser >/dev/null 2>&1 ||
|
|
warn "Could not apply browser theme colour. Run 'omarchy theme set \"$theme_name\"' after reboot if Chromium's theme looks stale."
|
|
}
|
|
|
|
# Everything below mutates the system, so a non-zero exit from here on leaves a
|
|
# half-upgraded machine that must not be rebooted.
|
|
upgrade_started=1
|
|
|
|
# Suppress live Hyprland reloads before anything touches the legacy config
|
|
# trees; the swap windows otherwise surface transient "source= globbing error"
|
|
# popups in the running session.
|
|
suppress_hyprland_config_reload
|
|
create_pre_upgrade_snapshot
|
|
configure_pacman_channel
|
|
install_keyrings
|
|
remove_legacy_installer_package
|
|
remove_legacy_limine_configs
|
|
remove_conflicting_legacy_packages
|
|
preserve_kernel_cmdline_root
|
|
install_omarchy_quattro_packages
|
|
install_hardware_transition_packages
|
|
normalize_limine_config
|
|
verify_kernel_cmdline_root
|
|
configure_snapper_policy
|
|
configure_lock_authentication
|
|
migrate_1password_beta_package
|
|
migrate_lazydocker_package
|
|
cleanup_legacy_user_paths
|
|
apply_system_transition
|
|
apply_user_transition
|
|
apply_user_hardware_transition
|
|
run_as_user_omarchy omarchy-refresh-applications ||
|
|
warn "Could not refresh application launchers; run 'omarchy refresh applications' after reboot."
|
|
run_as_user_omarchy omarchy-bar defaults ||
|
|
warn "Could not restore the default bar widgets; run 'omarchy bar defaults' after reboot."
|
|
cleanup_retired_services
|
|
ensure_sleep_lock_service
|
|
remove_retired_default_packages
|
|
run_final_system_package_upgrade
|
|
# This also retires the stock ~/.local/share/fonts/omarchy.ttf missed by the
|
|
# former ~/.config retirement entry, through migration 1788848726.
|
|
run_post_upgrade_migrations
|
|
run_post_upgrade_update_steps
|
|
refresh_current_theme_after_upgrade
|
|
# Do not force-reload Hyprland in the live upgraded session. The legacy
|
|
# default/hypr and theme hyprland.conf shims above exist specifically so the
|
|
# pre-reboot session can keep running safely; the reboot performs the real
|
|
# Omarchy quattro Lua-config cutover.
|
|
restore_hyprland_config_reload
|
|
|
|
upgrade_completed=1
|
|
|
|
cat <<EOF
|
|
|
|
WARNING: You must address any errors in the above before rebooting.
|
|
|
|
EOF
|
|
|
|
if (( boot_cmdline_unsafe )); then
|
|
warn "Skipping reboot: root= could not be confirmed in the boot entries. Repair the kernel cmdline (see the warnings above) before rebooting, or the system will drop to an emergency shell."
|
|
elif (( auto_reboot )); then
|
|
log "Rebooting because --reboot was passed"
|
|
as_root systemctl reboot
|
|
elif gum confirm "Reboot to complete Quattro upgrade now?" </dev/tty; then
|
|
as_root systemctl reboot
|
|
else
|
|
echo "Not rebooting. Please reboot manually after confirming there were no errors."
|
|
fi
|