Commit Graph
47 Commits
Author SHA1 Message Date
ZacharyZhang-NYandClaude Fable 5 763ebb83c3 Purge remaining references to removed western apps
The quattro upgrade script no longer registers the HEY mailto
handler, installer tips stop naming LibreOffice, and the hotkeys,
GUIs, and web-apps manuals drop the entries for removed apps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QKxGW1raAWaqeU8WdHsMsp
2026-08-25 10:30:28 -04:00
b5ded31e2f Don't put the user in the docker group; make it opt-in (#8056)
* Don't put the user in the docker group; make it opt-in

The docker group is root-equivalent: anything in it can `docker run -v /:/host`
and rewrite the host as root with no password. On a single-user box that's not
an escalation (the owner is already a wheel/sudo user), but it hands any code
running as the user — a rogue plugin, a poisoned dependency — a silent, headless,
passwordless path to root that sudo's password prompt would otherwise gate.

Stop granting the docker group by default. The daemon still runs (docker.socket);
the Docker TUI and the Windows VM reach it through a polkit prompt, and the plain
`docker` CLI runs under sudo. Sudoless Docker is a warned opt-in via
Setup > Security (omarchy-setup-security-sudoless-docker).

No automatic path may re-grant it: install and first-boot provisioning never
record or apply the group (provisioning also filters a docker line left in an
older factory snapshot), and the Quattro upgrade no longer adds it.

The Windows VM keeps needing the root daemon for a privileged container (KVM,
NET_ADMIN), so it is reworked to run without the group and without becoming a new
way in:

- The compose lives in a root-owned dir and is only written by an elevated,
  input-validated writer. A root-invoked bring-up must never consume a file a
  user-process could rewrite to bind-mount / into the guest — the old
  ~/.config/windows compose was exactly that. Volume paths are rebuilt from
  $HOME on migration rather than trusted from the (user-writable) legacy file,
  path validation rejects traversal, and the privileged sub-action is checked
  against an allowlist before dispatch (a slash in it would otherwise run as a
  path).
- pkexec elevates a verified root-owned command path, not a PATH-resolved one,
  so an authorized prompt can't be redirected to an attacker's binary.
- The guest password is kept in a private 0600 per-user file for RDP instead of
  a world-readable compose, and a declined authorization is reported as such,
  never as a completed stop.

Existing installs auto-migrate the VM (no redownload) and refresh the stale
Docker launcher entry.

🤖 Generated by Opus 4.8 in Claude Code. Reviewed by Codex XHigh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <codex@openai.com>
Claude-Session: https://claude.ai/code/session_01Gb7x6poap4hGCndPx5qt5T

* Migrate existing installs off the docker group

The default flip only reaches new installs; existing users keep their docker
group membership and stay exposed. Extend the migration that already refreshes
the Docker launcher to also remove the current user from the group when present,
reusing omarchy-remove-security-sudoless-docker so there is one source of truth
for the change and its notice. It takes effect at next login (the current
session keeps working), and passwordless docker can be turned back on from
Setup > Security > Sudoless Docker.

Migrations run with sudo available — during `omarchy update`, or in the terminal
the pending-migrations notification opens — so the privileged removal does not
prompt at an unattended login. The no-op path (already out of the group) needs
no privilege.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gb7x6poap4hGCndPx5qt5T

* Refuse symlinked VM mount sources; correct the docker CLI docs

Review follow-ups.

valid_path keeps a traversal string (/./, //, ..) out of the compose, but it is
a string check: a symlink planted at ~/.windows or ~/Windows redirects the
privileged bind mount exactly as traversal would, because docker follows it. So
verify the mount sources as root immediately before bringing the VM up — refuse
a source that is a symlink or resolves through one — which is where the string
check cannot help. A missing source stays fine (docker creates a plain dir).

Also correct the development-tools manual: the CLI is not transparently elevated
(there is no docker wrapper and `d` is still plain docker), so say plainly that
docker on the command line takes `sudo` until sudoless Docker is enabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gb7x6poap4hGCndPx5qt5T

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Codex XHigh <codex@openai.com>
2026-08-24 18:56:00 +02:00
de854d3f0c Free the Copy URL shortcut from ghost extension registrations (#6821)
* Rebind ghost Copy URL shortcut registrations to the pinned id

Chromium never hands a suggested shortcut to one extension while
another — even a long-gone one — still holds the registration. Profiles
that first loaded Copy URL before its id was pinned registered
Alt+Shift+L under an id derived from the extension's load path at the
time, so the pinned extension never receives the shortcut and the
keypress does nothing (#6816).

The quattro upgrade tried to repair this against one hardcoded
path-derived id, which only ever matched a single home directory. The
historical ids are unknowable in general — they hash long-gone absolute
paths through whatever symlinks existed then — but the registration
itself names the command, so a migration now rebinds any copy-url
command that points away from the pinned id, unless that id belongs to
an extension that is actually installed or the pinned extension already
holds a binding of its own.

Browsers rewrite Preferences on exit, which reverts any repair made
while one runs, so the migration asks for this user's browser windows to
be closed first — failing and staying pending when there is no terminal
to ask in or the prompt is declined. The backup a repair leaves behind
marks it as attempted but unverified: until a browser-free run confirms
the registration stayed repaired, the migration keeps itself pending
rather than trusting a disk state an open browser may still overwrite.

The upgrade-time repair is dropped: the upgrade already runs migrations,
so the migration is the single implementation.

Fixes #6816

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Pin the WhatsApp Slim extension id

Keyless unpacked extensions get path-derived ids, which go stale if the
load path or packaging ever changes — the same class of bug that broke
the Copy URL shortcut for pre-package installs. Pin the id with a
manifest key like the other bundled extensions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 22:37:29 +02:00
b99fd91cf1 Simplify the Quattro upgrade, and stop it leaving an error bar behind (#6716)
* Accept either name for the lock authentication command

omarchy-setup-lock was renamed to omarchy-apply-lock in 536fcd5c, but
the upgrade calls into whatever the channel just installed, and every
released package still ships the old name. The rename only moves in
lockstep for the ISO, which installs the runtime from the mirror it
ships with; the upgrade has no such guarantee, so it aborted every run
with "omarchy-apply-lock is unavailable" right before the point of no
return.

Prefer the new name and fall back to the old one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Leave the Omarchy 3 session alone until the reboot

The upgrade tried to start Quickshell in the live session, and only
stopped waybar/walker/elephant if that worked. Every branch of it could
fail, so it needed a warning for each, and those warnings were the first
thing users read at the end of an upgrade that had otherwise succeeded.

The reboot is the cutover. Swapping the UI out underneath a running
session buys nothing, so drop the attempt and both functions with it.
The Omarchy 3 bar, launcher, and notifications keep working until the
reboot, which is what happened anyway whenever the start failed.

Also warn up front when the live Hyprland session cannot be reached, and
drop the package_mode label that was set on both branches of the
dev-package check and never read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Shim the legacy Hyprland defaults from the backup, not from GitHub

populate_legacy_hypr_defaults tried a sparse git clone of master, then a
curl of the master tarball, and only if both failed the backup taken a
few lines earlier. That backup is the checkout the running session is
sourcing right now: it is the correct content, it is already on disk,
and it needs no network in the middle of an upgrade. master is a guess
that is wrong for any machine not on master.

Try the backup first and drop the git clone, which fetched the same
thing as the tarball by a longer route. The network path stays for the
case that has no backup, where the legacy root was already a symlink.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Clear the Hyprland error bar the upgrade leaves behind

The two settings were applied through both the keyword and the Lua API,
in four calls on the way out and three on the way in, split differently
each time. Fold that into one helper that takes the value.

The error bar needed more than suppression. An explicit hyprctl reload
re-reads the config from disk, which resets both keywords before it
reports what it found, so suppression cannot survive one; anything that
reloaded during the swap left the bar on screen. Hyprland then keeps it
up until a later clean reload, which this script deliberately never
performs, so it was still there when the upgrade finished. Clear the
overlay on the way out, once the shims have made the legacy config
resolve again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Run the packaged firewall config instead of a copy of it

configure_snapper_policy and configure_lock_authentication already call
into the installed tree; apply_firewall_defaults reimplemented
install/config/firewall.sh inline instead, and had already drifted from
it. The packaged script also installs the ufw-docker rules, so upgraded
machines came up without the Docker firewall protections a fresh install
gets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Stop the Voxtype toggle migration reloading a mid-swap session

This is the reload behind the "source= globbing error" bar users see
during a Quattro upgrade. The upgrade runs the packaged migrations
against a still-running Omarchy 3 session, and this one ends with
hyprctl reload. That re-parses a legacy config whose theme source= has
nothing to resolve to yet, so Hyprland paints an error bar and keeps it
up until a later clean reload the upgrade deliberately never performs.
It was still on screen when the upgrade finished.

The upgrade already exports OMARCHY_UPGRADE_TO_QUATTRO_LIVE for exactly
this, and 1782002156 honors it. Do the same here. Nothing in that
session reads the toggle being removed; the reboot applies it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Assert the retired session entry points stay gone

The ordering check would still pass if either came back, while the
comment above it claims they cannot. Name them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Upgrade onto the channel the machine is already on

The upgrade always defaulted to stable, so an rc machine taking it
landed on production packages from the stable repo. rc callers worked
around that by passing --channel rc, which then forced every caller
onto rc, stable machines included.

Read the channel off the mirrorlist the way omarchy-version-channel
does and follow it: stable machines get omarchy and omarchy-settings
from stable, and rc or edge machines get omarchy-dev and
omarchy-settings-dev from edge, which is what --dev already selects.
An explicit --channel or --dev still wins, and an unrecognized
mirrorlist still falls back to stable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Keep rc machines on the rc mirror when taking dev packages

--dev forced the edge channel, so an rc machine following its own
channel was moved onto the edge Arch mirror as well. The constraint is
narrower than that: the dev packages are only published to the edge
package repo, which the rc and edge channels both already point at.
Only stable is incompatible.

Reject --dev only for stable, and default to edge just when no channel
was chosen. rc machines now upgrade against rc-mirror with the dev
packages out of the edge repo, which is where Quattro lives until it
ships.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 22:47:39 +02:00
David Heinemeier HanssonandClaude Opus 5 536fcd5c6c Move install-time plumbing out of the setup namespace
setup is where a user goes to configure something: direct boot, security
keys, hibernation. These three are not that. omarchy-apply-system is the
ISO's entry point in the target chroot, omarchy-apply-hardware is what it
calls for device quirks, and omarchy-apply-lock is called by
install/config/lockscreen-pam.sh.

apply is the verb they already used to describe themselves, and it carries
the contract: declared state under install/ converged onto the machine,
idempotent, safe to repeat.

The group gets no GROUP_DESCRIPTIONS entry on purpose. That table drives the
top-level group list on its own, so an entry would put apply back in front of
users even with every command in it hidden, the way provision already stays
out. A test covers it.

The ISO installs the runtime from the mirror it ships with, so it moves to
the new names in lockstep and no compatibility route is needed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 14:14:57 -07:00
318bf2c43a Keep the Quattro upgrade from aborting silently into an unsafe state (#6617)
The script is fetched from the branch but calls into the installed
/usr/share/omarchy tree, which can lag it. A packaged build without
bin/omarchy-done aborted apply_user_transition under set -e two thirds of
the way through: NetworkManager was already enabled, iwd was not yet
disabled, and nothing was printed, so the run read as finished.

The completion markers are now written directly instead of through
omarchy-done, and the two remaining unguarded packaged commands warn
rather than abort. Retiring iwd moves up next to the NetworkManager
enable it depends on, so no failure in between can leave both enabled.
An aborted run now says so instead of returning to the prompt on a green
progress line.

Fixes #6575

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 10:16:41 +02:00
f76d058a6d Force a database refresh before installing keyrings during the Quattro upgrade (#6615)
The upgrade repoints the mirrorlist and the [omarchy] server, then ran
pacman -Sy. A plain -Sy keeps the legacy database whenever the new server's
copy isn't newer, so the checksums stay stale and every re-download of a
rebuilt package aborts as corrupted.

Fixes #6576

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 23:52:24 +02:00
b85ae70ebd Stop pipefail from turning grep -q SIGPIPE exits into false negatives (#6614)
* Stop pipefail from turning grep -q SIGPIPE exits into false negatives

grep -q exits at the first match, and when the producer is still writing
it dies with SIGPIPE. Under pipefail that 141 becomes the pipeline's
status, so hardware checks like lspci | grep -q read as "not found" on
exactly the machines they target. The T2 defaults migration hit this and
silently skipped real T2 Macs (#6608).

Redirect grep to /dev/null instead of -q wherever a pipeline feeds grep
in a pipefail context, so grep reads all input and the producer never
gets killed. The install-time T2 checks aren't run under pipefail today
but are switched too, since they're the same detection line the issue
calls out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Re-run the T2 defaults migration its broken hardware check skipped

The SIGPIPE bug marked 1785944594 as applied without doing anything on
affected T2 Macs. The original migration is idempotent, so a fresh
migration can just source it now that the guard is fixed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address Copilot review: fix OCR grep pipeline and prove the T2 repair

screen_contains piped tesseract into grep -Fqi under the acceptance
suite's pipefail, the same SIGPIPE false negative the rest of the branch
fixes. The T2 test's lspci stub now keeps writing past the pipe buffer
after the match so every scenario exercises the SIGPIPE case, and a new
case runs the rerun migration against fixtures a bitten install would
have.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 23:43:49 +02:00
3bfea9b840 Fail loudly when a pre-update snapshot isn't actually created (#6580)
* Fail the snapshot when Snapper is installed but has no configs

omarchy-snapshot create loops over the configs snapper reports. With none,
the loop body never runs, so it prints "Create system snapshot" and exits 0
without capturing anything. Every update then reports a snapshot it never
took, and the absence only surfaces when a rollback is needed and the
snapshot list turns out to be empty.

* Say so when the update proceeds without a snapshot

The update ignores exit 127 so a system without snapper updates quietly.
Any other snapshot failure was being swallowed by the same expression,
which let the update continue with no indication that it was now
unprotected. Keep continuing, but say it out loud.

* Point the snapshot repair hint at how the installer runs it

Also hold the green header until a snapshot will actually be attempted,
so the no-config failure doesn't open with a success banner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Continue the quattro upgrade when the pre-upgrade snapshot fails

The upgrade runs under set -e, so the new non-zero exit from an
unconfigured Snapper would have aborted a re-run at the snapshot step
instead of proceeding like omarchy-update does.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 18:44:37 +02:00
9d5c6e25e7 Keep root= in the kernel cmdline when upgrading to Quattro (#6579)
* Keep root= in the kernel cmdline when upgrading to Quattro

The packaged drop-in /etc/limine-entry-tool.d/omarchy-defaults.conf sets
KERNEL_CMDLINE[default] with the += operator. limine-entry-tool.conf documents
what that costs: "+= appends parameters to an existing cmdline ... Ignores
/etc/kernel/cmdline and /proc/cmdline". As soon as the drop-in lands, the tool
stops auto-detecting the cmdline.

Fresh installs are unaffected, because the ISO writes /etc/default/limine from
default/limine/default.conf with @@CMDLINE@@ substituted. The upgrade path never
created that file. A pre-quattro install that relied on the auto-detected root=
therefore ends up with a cmdline that has no root= at all, in both limine.conf
entries and in the cmdline embedded in the UKIs. The next boot fails with
"ERROR: Failed to mount '' on real root" and drops to an emergency shell, where
the error gives no hint that the cmdline is the cause.

Capture the boot-critical parameters from /proc/cmdline before the reboot, while
the still-correct cmdline of the running kernel is readable, and write them to
/etc/default/limine, which is loaded last so += keeps the drop-in parameters
instead of replacing them. Copy root=, rootflags, rootfstype, resume,
resume_offset, the cryptdevice and rd.luks keys and rw/ro verbatim rather than
reconstructing them, so LUKS and hibernation setups survive too.

Re-running the upgrade on an already-broken system has no root= left to copy, so
fall back to deriving it from the mounted root, including the subvolume on
btrfs. Any config layer that already pins root= is treated as authoritative and
left untouched.

* Anchor the cmdline guard and harden the repair path

The early-return guard searched for the bare string root=, which matches the
commented example limine-entry-tool.conf ships at line 53:

  #KERNEL_CMDLINE[default]+=rw root=UUID=...

That file is present on every stock machine, so the guard always fired and the
function never wrote anything. Match assignments instead, and check
/etc/kernel/cmdline separately since it holds bare parameters rather than shell
assignments.

Three fixes on the repair path:

Assigning boot_params discarded every parameter the collection loop had just
captured, so cryptdevice, cryptkey, resume and ro were dropped, and rw was
forced over a captured ro. Prepend the derived root= instead, and only add rw
when the booted cmdline stated no mount mode.

On an encrypted root, findmnt reports the unlocked mapper device, whose UUID
says nothing about which container to unlock. Emitting it produced a cmdline
that still could not boot while satisfying the final check, so the user rebooted
into the same emergency shell believing it was repaired. Warn and write nothing
in that case.

The allowlist gained rd.luks.key, rd.luks.crypttab, rd.md.uuid, rd.dm.uuid,
rootwait, rootdelay and dm-mod.create.

Verification now also reads the .cmdline section of each UKI. With
omarchy-uki.conf among the drop-ins that embedded copy is what actually boots,
so a green limine.conf alone did not prove the machine would come up.

* Filter guard paths and narrow the dm-crypt and UKI checks

The guard passed /etc/default/limine to grep unconditionally, and that file is
absent on exactly the machines this targets. A missing operand makes grep exit 2
without -q, so a drop-in pinning a real root= went undetected and the function
appended a second one, overriding the explicit setup it promises to leave alone.

Rather than relying on -q returning 0 despite the error, which is a GNU grep
special case and not true of every implementation, filter the paths first and
only grep the ones that exist. The exit status is then unambiguous.

The dm-crypt check gated on the /dev/mapper/* prefix, which also matches plain
LVM, dm-raid and multipath. Those roots need no unlock parameters and were
repairable before, so the prefix test denied them a working root=UUID= and told
them they were encrypted. Gate on the device mapper target type instead.

root_filesystem_encrypted() is not reused here on purpose: it treats every
/dev/mapper/* path and any non-empty /etc/crypttab as an encrypted root, which
suits its own call site but would reintroduce the same false positive.

UKI verification now runs through as_root, since a restrictive ESP fmask would
otherwise make find return nothing and the check pass in silence, and is scoped
to the omarchy_linux*.efi images limine-entry-tool generates so a shared ESP or a
stub without a .cmdline section cannot raise a false "do not reboot" warning.

The allowlist gained rd.lvm.lv and rd.lvm.vg.

* Strip the subvolume before resolving the root device type

findmnt appends the subvolume for btrfs mounts, so the source read back for an
encrypted btrfs root is /dev/mapper/cryptroot[/@]. lsblk cannot resolve that
path, the device type came back empty, and the crypt gate never fired. The
function then wrote root=UUID= with no unlock parameters, limine.conf ended up
carrying a root= so the final check stayed quiet, and the machine still booted to
an emergency shell. That is the layout Omarchy installs when encryption is
picked, so the gate missed exactly the roots it exists for.

The previous /dev/mapper/* prefix test matched the bracketed form by accident.
Moving to the device mapper target type is still the right call, it just needs
the unbracketed source, which findmnt --nofsroot provides.

Also give root_type an explicit empty default. It is assigned inside a branch and
read outside it, and set -u treats a declared-but-unassigned local as unbound, so
a findmnt that cannot answer would abort the upgrade with the quattro packages
already installed and everything from configure_snapper_policy onward skipped.

* Look for the crypt layer across the whole device stack

lsblk -no TYPE reports only the target's own type. On the standard full-disk
encryption layout, LUKS container -> LVM PV -> root LV, that type is lvm and the
crypt layer sits in the parents, so the gate never fired: the function wrote
root=UUID= with no unlock parameters, the final check found a root= and stayed
quiet, and the machine still booted to an emergency shell.

Walk the parents with lsblk -s and look for a crypt layer anywhere in the chain.
That keeps LVM, dm-raid and multipath roots on the repair path, since they carry
no crypt layer and root=UUID= is enough once mkinitcpio assembles them.

root_type is replaced by root_stacks_crypt, which says what is actually being
tested and drops the LVM-versus-crypt caveat the old target-type check needed.

Also drop a vacuous test assertion: piping a bracketed literal through grep -qv
'\[' selects nothing, so the branch was unreachable and the case passed whatever
the script did. The --nofsroot assertion above it is what holds that fix.

* Keep the crypt gate off a pipeline exit status

Capture the device stack and match it from a here-string rather than piping lsblk
into grep -q. Under pipefail a short-circuiting grep can leave the producer with
SIGPIPE and turn the pipeline into 141, which reads as "no crypt layer" and
disarms the gate silently. lsblk writes its whole table in one go, so this is out
of reach in practice, but nothing about the gate should depend on how much output
a helper happens to buffer.

Also correct a stale test comment that described the target-type check the
previous revision used, four lines above the comment explaining why that check
was insufficient.

* Harden the kernel cmdline preservation against false root= pins

The /etc/kernel/cmdline early return trusted a file limine-entry-tool
ignores once a += drop-in sets KERNEL_CMDLINE[default], leaving exactly
the targeted machines unbootable. The pin guard now reads only the
*.conf layers the tool loads, only the default key, and tokenizes the
assignment value so quoted decoys and volatile-root= cannot pin.
/proc/cmdline is tokenized quote-aware so dm-mod.create="..." survives
verbatim, the root= verification is token-anchored, and an unverified
cmdline now blocks the reboot instead of only warning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Ask limine-entry-tool for the effective cmdline instead of parsing its configs

--get-cmdline default answers whether root= survives the tool's own
config merge, replacing the glob, grep and quote-aware tokenizer walk
over the config layers, and the quote-aware /proc/cmdline parsing
reverts to plain word splitting. The verification and the reboot gate
stay: they are what catches anything the simpler paths miss.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 18:12:13 +02:00
6ee243cc37 Split the end-user omarchy skill into topic guides (#6602)
* Split the end-user omarchy skill into topic guides

Move default/omarchy-skill to default/agents/skills/omarchy and break the
monolithic SKILL.md into on-demand topic files for Hyprland config, shell
plugins, theming, and hooks. Update the skill symlink wiring, relink
existing installs through a migration, and correct claims that had drifted
from the implementation: plugin hot-reload, terminal reload, menu
customization, refresh scopes, theme overlays, background locations, hook
timing, and the packaged (not git-managed) system directory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Add capture and contributing guides to the omarchy skill

Cover screenshots, screen recording, OCR text capture, and LocalSend or
Taildrop sharing, plus how to route bug reports, suggestions, and support
questions upstream with diagnostics and captures of the problem attached.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Scope Hyprland reload guidance to the Lua config files

hyprsunset.conf and xdph.conf are read by separate processes, so hyprctl
neither applies nor validates them. Document restarting hyprsunset after
editing its config, including in the night light example.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 14:36:42 +02:00
David Heinemeier HanssonandGitHub 2cff67fa45 Fix sleep lock session environment race (#6493)
Found by @tobi.
2026-08-01 15:38:55 -05:00
David Heinemeier HanssonandClaude Opus 5 2e04f093c1 Ship zram tuning as a vendor drop-in
pacman checks file conflicts during transaction prepare, so the --overwrite
entry added alongside /etc/systemd/zram-generator.conf never got to run: the
copy of omarchy-update-system-pkgs driving the upgrade is the one already on
disk. Every machine carrying archinstall's copy aborted the transaction.

zram-generator.conf(5) reserves /etc for the local admin and has vendors ship
snippets under /usr/lib/systemd/zram-generator.conf.d/, where drop-ins outrank
the main config file. Nothing collides, and the tuning wins whether or not
archinstall's file is still around.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjeyQZsNBxqyYy9z8KaKm7
2026-07-25 14:01:38 -07:00
David Heinemeier HanssonandClaude Opus 5 5884397e46 Drop the bar.json nobody reads
It was the user override file for the first Quickshell bar, back when the
bar layered it over bar-defaults.json. Unifying everything into
shell.json left an empty file that installs and the quattro upgrade kept
faithfully copying around.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 08:21:04 -07:00
David Heinemeier HanssonandClaude Opus 5 4dcbed7728 Tune swap on zram instead of leaving it at kernel defaults
Omarchy installed zram-generator but never shipped a config for it, so the
only zram tuning any machine had was whatever the ISO wrote once, and the VM
sysctls were untouched kernel defaults aimed at spinning disks.

The generator's own default caps the device at 4G, which on a large machine
sends reclaim to the hibernation swapfile far earlier than it needs to go.
Ship min(ram / 2, 8192) instead, matching what Fedora settles on, and state
the priority explicitly since it has to sit above the pri=0 that
omarchy-hibernation-setup gives the disk swapfile.

The four sysctls follow from swap being RAM rather than a disk. swappiness
above 100 says evicting an anonymous page beats dropping a page-cache page
that would have to be read back, which is true once swap is compressed
memory. page-cluster drops to one page per fault because the default of 8
buys readahead for a seek zram doesn't have and pays a decompression for
each page. Zeroing watermark_boost_factor stops fragmentation from producing
reclaim bursts while memory is still free, and raising watermark_scale_factor
gives kswapd room to reclaim in the background instead of letting allocations
stall in direct reclaim. The last two are what actually addresses the stutter
people notice and misread as swap being used too eagerly: proactive swapping
to zram is the cure, and synchronous direct reclaim is the disease.

Every ISO-installed machine already has an unowned zram-generator.conf, which
pacman would refuse to overwrite, so both upgrade paths need it listed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 19:48:00 -07:00
Kyunghyun Park e5924ef7d1 Fix XDG browser defaults when BROWSER is set 2026-07-24 03:26:49 +09:00
David Heinemeier HanssonandClaude Fable 5 ff1d1c325e Suppress Hyprland reload errors for the whole quattro upgrade
Suppression was enabled only after cleanup_legacy_user_paths had already
moved ~/.local/share/omarchy aside, so the live session could auto-reload
during the swap window and pop "source= globbing error: found no match"
for every legacy hyprland.conf source line. Enable suppression as the
first step of the upgrade sequence instead; the EXIT trap still restores
it if the upgrade dies early.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 11:45:23 -07:00
David Heinemeier HanssonandClaude Fable 5 f96827c909 Simplify the quattro upgrade script
- Extract run_as_user_omarchy and run_as_user_wayland_session helpers to
  replace ten hand-rolled run_as_user env blocks
- Declare the retired user-unit list once instead of three times
- Use git ls-files --others to find user-added theme backgrounds instead
  of diffing against the tracked list by hand
- Stop rewriting the SDDM 10-theme/10-wayland confs the omarchy package
  already installs, and drop the duplicate [Theme] section from
  99-omarchy-login.conf
- Drop the legacy-path rewrite in the theme hyprland.conf shim; the user
  transition already rewrote those references
- Remove dead code: the unreachable channel re-validation, the declare -F
  guard in cleanup_on_exit, and the unset block at the end of the
  user-transition script

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 11:44:50 -07:00
David Heinemeier HanssonandClaude Fable 5 88d60e1468 Point gum prompts at the terminal in the quattro upgrade
The user-transition script is fed to bash on stdin, so the browser-close
gum confirm read the script itself: it auto-declined and swallowed the
remaining script text, leaving bash to fail on a mid-construct syntax
error. The outer confirm and reboot prompts had the same exposure when
the script runs via curl | bash. Read all three from /dev/tty.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 11:39:50 -07:00
David Heinemeier HanssonandClaude Fable 5 4f1b1fceb8 Pass the --yes flag into the quattro user transition
The Copy URL shortcut repair checked the outer script's yes variable,
which never reached the user-transition heredoc's bash process, so the
upgrade died with "yes: unbound variable" under set -u whenever a
browser was running with the old extension id in its Preferences.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 11:26:33 -07:00
David Heinemeier HanssonandClaude Fable 5 17c8af3f14 Simplify shell restart around quickshell instance management
omarchy-restart-shell now kills by config dir via quickshell kill and
relaunches with --no-duplicate, polling kill until every instance is
gone since kill returns without waiting and -n silently exits if one
remains. The locked-session refusal reads Hyprland monitor state alone,
which covers a hung shell holding the lock; the redundant shell IPC
probe, availability guards, and omarchy-shell path indirection are gone.

Shell IPC calls now time out (2s default, OMARCHY_SHELL_IPC_TIMEOUT to
override) so probing an unresponsive shell fails fast instead of
hanging the caller.

omarchy-hyprland-launch checks dispatch output for "ok" rather than the
exit code, which is 0 even on Lua errors, so a failed dispatch falls
through to the bash -lc fallback instead of silently launching nothing.

The Quattro upgrade cutover delegates to omarchy-restart-shell instead
of carrying its own copy of the lock check, pkill, and readiness loop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 10:09:54 -07:00
David Heinemeier Hansson 73efa7d926 Remove stale nofile config during Quattro upgrade 2026-07-18 21:42:00 -07:00
David Heinemeier Hansson 1620b3467b Refresh applications during Quattro upgrade 2026-07-18 12:56:46 -07:00
1024bf5e7a Backfill pre-quattro hardware support and tmux fixes for existing installs
Existing quattro installs get sof-firmware, the Mesa Vulkan drivers,
the tmux terminal-features and pane-binding fixes, and the XPS 13 text
scaling via migration, while Omarchy 4 upgraders get the same hardware
support and scaling through omarchy-upgrade-to-quattro.

The Vulkan driver backfill adapts da46ad85 from master.

Co-authored-by: Sai Sridhar <tarrasridhar1154@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 12:31:34 -07:00
David Heinemeier Hansson b39313abcf Simpler finish 2026-07-18 10:45:47 -07:00
David Heinemeier Hansson d0cc4b9674 More explicit clear 2026-07-18 10:40:43 -07:00
David Heinemeier Hansson 79e408b323 Clear before the logo 2026-07-18 10:33:02 -07:00
David Heinemeier Hansson 4db7a03235 Let's try a logo 2026-07-18 10:26:55 -07:00
David Heinemeier Hansson 77353e9670 Slim down celebration text 2026-07-18 10:03:15 -07:00
David Heinemeier Hansson 0b7c440009 Use gum to prompt for consent 2026-07-17 18:27:32 -07:00
David Heinemeier Hansson 28d853402e Make the Copy URL extension reliable across install and upgrade
The Copy URL extension was intermittently failing to copy anything, and
its notification looked oversized with no icon. Several distinct problems:

- The MV3 service worker was pinned to stale cached code. Chromium caches
  the worker script for a --load-extension command-line extension and does
  NOT re-register it when the file changes in place — not on restart, not on
  a manifest version bump, not even after a clean shutdown. So updated code
  never took effect: the old worker kept calling chrome.scripting.executeScript,
  which throws once the scripting permission is gone, and the copy silently
  failed. Renaming the worker to a versioned filename (background-2.js) is a
  new script URL, which forces a fresh registration for everyone — new
  installs and existing installs alike. Rename it again on any future worker
  change (see the note at the top of background-2.js).

- Clipboard writes now go through an offscreen document (MV3's sanctioned
  path) using a textarea + execCommand('copy'), replacing executeScript +
  navigator.clipboard. This works on chrome:// pages and needs no scripting
  permission. execCommand is used deliberately: navigator.clipboard.writeText
  rejects in an unfocused offscreen document.

- Add a toolbar action (chrome.action.onClicked) so the extension is
  clickable, not keyboard-only — it was greyed out in the extensions menu
  with nothing to invoke.

- Slim the "URL copied" notification: lead the summary with a glyph so the
  omarchy notification shell collapses it to a single-line toast with an
  icon, instead of an oversized card with a blank icon slot.

- Guard the Quattro shortcut-repair migration: it edits Chromium Preferences
  to move the Alt+Shift+L binding to the new extension id, but a running
  browser rewrites Preferences on exit and reverts the edit. Prompt (via gum)
  to close the browser first, only when a browser is running and there is
  actually a stale binding to repair.
2026-07-17 16:28:18 -07:00
David Heinemeier Hansson 09aac40eae Gate first-run setup as one lifecycle 2026-07-17 07:59:16 -07:00
David Heinemeier Hansson e82df1d4d0 Track one-time setup with completion markers 2026-07-16 21:53:15 -07:00
David Heinemeier Hansson 93181b6a22 Restore service widgets with bar defaults 2026-07-16 21:15:00 -07:00
David Heinemeier Hansson af601381ca Ensure lock is setup on migration 2026-07-16 20:46:41 -07:00
David Heinemeier Hansson c58d00f203 Repair Copy URL shortcut during Quattro upgrade 2026-07-16 16:50:29 -07:00
David Heinemeier Hansson 298e14071b Take a system snapshot before upgrading to quattro 2026-07-15 17:08:24 -07:00
David Heinemeier HanssonandClaude Fable 5 7ff91d9d5c Only carry over user-added theme backgrounds during the quattro upgrade
The trailing slash from the themes/*/ glob put a double slash in the
path comparison, so stock backgrounds never matched git ls-files output
and were all moved into ~/.config/omarchy/backgrounds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 18:16:16 -07:00
David Heinemeier HanssonandClaude Fable 5 4abd084f7f Don't glob dotfiles twice when building the live omarchy overlay
With dotglob set, * already matches dotfiles, so the extra dot patterns
made the second ln -s fail and silently skip the legacy hypr shim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 18:16:16 -07:00
David Heinemeier HanssonandClaude Fable 5 f1bc8dad08 Purge stale omarchy package cache before the first quattro transaction
Legacy repos cached omarchy packages under the same name and version as
quattro rebuilds with different bytes, so pacman aborted --noconfirm
transactions with checksum errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 18:16:16 -07:00
David Heinemeier Hansson e49c74eee8 Keep rc on its own mirror but use the edge package repo 2026-06-29 11:28:32 -05:00
David Heinemeier Hansson d824c1bd33 Remove Figma as a default install 2026-06-29 11:04:51 -05:00
David Heinemeier Hansson f67ed326df Let's save this for a direct install 2026-06-29 10:19:28 -05:00
David Heinemeier Hansson 3fd36802ac Simplify the system-update check
No need for a panel, just focus on the main omarchy package
2026-06-23 15:59:27 +02:00
David Heinemeier Hansson 95888e69ff Replace Typora with Omawrite 2026-06-22 17:38:50 +02:00
David Heinemeier Hansson 36c1bc7dc5 Refer to Quattro 2026-06-21 14:59:18 +02:00
David Heinemeier Hansson 428b9c7b29 Use omarchy-upgrade-to-quattro 2026-06-21 12:31:06 +02:00