05bb82b34efe066e82349d1bc41e8e87ec6501f6
5982
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
05bb82b34e | Add checkout bin to sudoers path | ||
|
|
5817feb93f |
Mute all audio on right-click (#6708)
Previously, right-clicking the audio icon muted only the output. Whereas the switch at the top of the panel mutes all audio (inputs and outputs). Pairing the right-click to the switch seems a bit less confusing, and also matches the behaviour with other panels (e.g. Bluetooth, Tailscale, etc). |
||
|
|
1c9dfc55f4 |
Greet the first login with a keybindings toast again
Opening the cheatsheet outright put a menu in front of someone who had not asked for one, and it blocked first run until they dismissed it. Go back to a toast that opens the same menu when clicked. The body carries real newlines now. It was written with a literal \n, which the card renders as the two characters rather than a line break. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
77bf2ef704 |
Share files and folders with the desktop file chooser (#6707)
Sharing a file or folder over LocalSend opened a terminal to run an fzf pick over a find of the whole home directory, which is slow on a large home, shows no previews, and looks nothing like the rest of the desktop. The portal chooser is already how the other pickers here ask. The chooser has a directory mode, so folder sharing asks for one the same way, and neither entry needs a terminal to host a picker anymore. A chooser that never opens is told apart from nobody picking anything, so a portal failure says so rather than passing for a cancelled share. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
27d1b6bebc |
Resolve omarchy_monitor_scale variable reference in clamshell recovery (#6688)
A pinned internal monitor rule that referenced the omarchy_monitor_scale local had the variable name captured as the scale, so clamshell recovery fell all the way to the hardcoded 2. Read the config the way Hyprland writes it: a key's value is whatever sits between the `=` and the next separator, and a bare word resolves against a local only when one exists, so a quoted string stays a string. Comments are cut before anything is matched, and position gets the same resolution since it had the identical bug. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4d0531f351 |
Add QR code capture (#6705)
Select a screen region and decode the QR code in it to the clipboard, so an otpauth:// setup code shown on screen no longer needs a phone. The decoded value is only ever placed on the clipboard, and marked sensitive so clipboard history skips it. Decoding is restricted to QR so a stray barcode elsewhere on screen can't take the clipboard instead. Co-authored-by: Hlib Kanunnikov <hlibwondertan@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3b08a85ad1 |
Recover monitors Hyprland brought up with no mode (#6701)
A monitor powered off when the machine boots — a smart strip cutting AC, the PC coming back on its own — still answers DDC, but with a partial EDID that carries no video modes. Hyprland takes the connector as present and brings the monitor up at 0x0. Powering it on afterwards changes nothing: the connector never dropped at DRM level, so no hotplug fires, nothing re-reads the EDID, and the screen stays black until a reboot. Only a reload re-reads it. Forcing a DRM re-probe would work too but needs root, and the kernel's cached mode list stays empty without one, so there is nothing cheaper to poll: the reload is both the fix and the only way to learn whether it was needed. Poll only while a monitor is in that state, back off from three seconds to a minute, and stop as soon as one reports a mode — the machine can sit black all night, and powering the monitor on fires no event to stop on. Nothing will ask again if this loop gives up, so an unreadable answer is not taken for a healthy monitor. It is also not waited on forever: a compositor that stays silent has gone, and with it the session and any reason to keep asking. Reloading on our own schedule means minding the reload guard, which exists to keep Hyprland out of package-owned config mid-transaction, and which only disables the automatic reloads. The guard can now be asked, and recovery holds off while a transaction is in flight. A reload lands a monitor at 0x0 the same way a boot does, so configreloaded is watched alongside the hotplug events. The recovery's own reload comes back through it, and a lock keeps that from stacking a second loop. Contention waits rather than drops: a trigger arriving while a loop is exiting is the last one that will come, and one arriving while a loop is running is answered by its next pass anyway. Mirrors are dropped from `hyprctl monitors`, so the check asks for all of them and filters the disabled ones itself. Monitors turned off on purpose sit at 0x0 too, and re-applying config would fight the user over those. The existing poll here recovers internal panels on docked laptops and never runs on a desktop, which is where this happens. Reported in #6668. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9f0c4b9792 | Ensure we don't register duplicate bindings | ||
|
|
66f3155f0c |
Label the keyboard widget with the xkb language code (#6699)
* Label the keyboard widget with the xkb language code The label was the first word of the layout description cut to three characters, so a US layout read ENG and a Portuguese one read POR. xkb already pairs every layout and variant with a short language code, which is the code GNOME shows in its own indicator. Read that table once at startup from xkbcli list and key it by description, which is what hyprctl reports as the active keymap, so the same layouts read EN and PT. The code is a language rather than a country, so it stays sensible for the layouts named after neither: Esperanto is EO, Arabic is AR, and Latin American Spanish is ES. Layouts missing from the table keep the old truncated description. * Read the exotic xkb rulesets for the keyboard label xkbcli list leaves out the exotic rulesets, so layouts like trans were missing from the table and fell back to the truncated description: the IPA layout read INT rather than IPA. Those layouts ship in the same xkeyboard-config package and set just as well, so read them too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Keep the keyboard label to three characters The brief was used verbatim while the fallback was truncated, but not every brief is two or three characters: Burmese (Zawgyi) is my-zwg and Shan (Zawgyi) is shn-zwg. Selecting either widened the widget past its neighbours on the bar. Drop the script suffix and cap the brief the same way the fallback is capped, so those read MY and SHN. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Stop an xkb brief carrying past its own block The brief was only cleared once a description consumed it, so a block printing a brief without one would hand its code to the next block's description and label it wrongly rather than falling back. Nothing in the current xkb data does that, and the option groups were skipped only because the last layout happened to consume its brief first. Clear the brief when a line starts a new block so the pairing is explicit, and cover the option list the 2-space match is what keeps out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fall back when a layout description names a built-in A custom xkb group called constructor or toString reached an inherited member of the lookup rather than a brief, and splitting it threw a TypeError that took the whole label binding down instead of falling back to the truncated description. Take the lookup only when it returns a string. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: David Heinemeier Hansson <david@hey.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5edc3497fa |
Bind SUPER + CTRL + a number to the bar's right panels (#6702)
The letters name a panel; the numbers count them. One is the leftmost panel in the right section, so the number matches the icon a user would point at: a widget with no panel of its own is passed over, and so is one that is hiding itself. Counting rather than naming means the hotkeys follow the bar. Rearranging the section, or adding a widget to it, renumbers the panels with no binding to rewrite. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
efe805387e |
Title a model-scoped limit the way the flat ones title themselves
A scoped window read as "Fable weekly" beside "Session" and "Weekly", so the one row that names a model was also the one row in lowercase. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0ad64a59df |
Fix command injection in theme install, drop tzupdate NOPASSWD (#6694)
* Fix theme install code execution and drop tzupdate NOPASSWD
VULN-01 (C, D, E): a malicious theme can execute arbitrary code
during install through three injection sinks:
C: colors.toml values reach a sed script unsanitized.
GNU sed's `e` flag runs the pattern space as a shell command.
D: vscode.json `.name` is interpolated into a sed replacement
string without escaping sed metacharacters.
E: keyboard.rgb content is interpolated into a python3 -c
argument without validation.
Fix C by validating keys and values in omarchy-theme-color's parser
with a character allowlist. Byte-identical output for all 22 shipped
themes.
Fix D by escaping backslash, ampersand, and slash in the theme name
before sed interpolation.
Fix E by gating on ^[0-9A-Fa-f]{6}$ before interpolation, in both
the Framework 16 and ASUS ROG keyboard scripts.
VULN-02: the tzupdate sudoers grant has no argument constraint.
tzupdate -l lets any wheel user write a root-owned symlink to any
path. Drop it; nothing has invoked tzupdate since omarchy-cmd-tzupdate
was removed. Keep timedatectl set-timezone.
* Harden keyboard and vscode theme scripts
keyboard-f16: pass hex as sys.argv instead of interpolating into
python3 -c. The hex validation gate stays as the primary defense;
argv separation is defense-in-depth per OWASP guidance.
vscode: replace sed interpolation of theme name with jq, which
handles arbitrary strings safely via --arg. Validate extension IDs
against ^[a-zA-Z0-9._-]+$ before passing to --install-extension.
* Keep VS Code settings edits JSONC-safe
settings.json is JSONC, so routing the write through jq dropped theme sync
entirely for anyone with a comment or trailing comma in the file, including
the `{ "workbench.colorTheme": "",\n}` shape Omarchy itself creates. Edit in
place again and close the injection by validating the theme label instead.
Scope the extension-id guard to the install so a malformed id no longer skips
the colorTheme write, and treat a missing descriptor field as empty rather
than the literal string "null".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Widen the accepted colors.toml value charset
The sanitizer dropped gradient angles, decimals, underscored palette
references, and paths, which vanish from --raw/--all and leave a raw
{{ placeholder }} in the generated config. Allow the punctuation real
palettes use, keep out everything sed treats as special, and say so on
stderr rather than dropping a key silently.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
9ea9f804cd | Missed the glyph | ||
|
|
2b38f75506 |
Show the per-model weekly limit Claude's usage endpoint reports (#6691)
* Show the per-model weekly limit Claude's usage endpoint reports Model-scoped allowances arrive in the payload's limits array, not in the seven_day_<model> buckets, which come back null. Read them so a window like Fable's own weekly limit stops being spent against invisibly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Read every model-scoped window, and title it for what it is A model can hold more than one scoped window, so keying the dedupe on the model alone dropped whichever came second — including a fuller one that decides the headline. The model and the window kind together make the key, and both make the title, so one model's two rows read apart. The panel guesses a window out of the label, and that guess cannot survive a model name: "Opus 5 (1M context)" parses as a one-minute window and renders as a second "Session". The collector states the title outright now and the panel takes it, eliding a long one rather than running it into the percentage. Scoped percentages are read on whatever scale the payload speaks, the way the flat buckets already are, rather than assuming percentages, and a model that names only an id still names a window worth showing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: David Heinemeier Hansson <david@hey.com> |
||
|
|
f97ba7375e |
Wait for a connection before prompting to update
A ping at hyprland.start answers for a machine that has not finished coming up. Ethernet is still negotiating DHCP, so a working desktop was told to set up Wi-Fi and offered an update it could already have run. Ask NetworkManager instead: -s returns once it has tried every connection it could auto-activate, which is the first moment the answer means anything, and -x then takes that answer as it stands rather than waiting out a timeout that a laptop with nothing to connect to would spend in silence. The update prompt now waits for a connection rather than being phrased around not having one. There is nothing to update against until a link lands, and one usually does land later on the machines that started without it, so the prompt follows the connection whenever it arrives. That wait runs detached. It outlasts first run by design, and the keybindings menu is on screen behind it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
23d85a992b |
Open the keybindings menu on first login
The welcome toast spent three lines telling you about a cheatsheet that takes one keystroke to read, and the only way to act on it was to click the toast, which opened that cheatsheet. Open it directly instead. Dismissing the menu exits non-zero, since no selection was made, so the step tolerates that rather than failing first run and retrying the whole sequence next login. It also goes last now. The menu blocks until it is answered, and the Wi-Fi and update toasts are the only other things left to show, so sending those first leaves them waiting underneath rather than behind an open menu. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
383addc8c4 | Rearrange keybindings presentation | ||
|
|
1e7bb66556 |
Recover a session lock stranded by a dead shell (#6692)
* Detect a compositor session lock through one helper omarchy-restart-shell decided whether the session was locked by looking for "LOCK" anywhere in the hyprctl monitors payload. That works, but not for the reason the code reads like: Hyprland reports no lock state of its own, and the string comes from solitaryBlockedBy, the list of reasons a monitor cannot hand a client the whole screen. An active ext-session-lock is one of those reasons. A substring match over the whole payload also answers yes to a workspace or a monitor description that merely spells LOCK, and locking a desktop nobody asked to lock is the worst way to be wrong. Match the reason list itself, and put it behind a helper now that a second caller needs the same answer. That second caller needs a third answer too, because the reason list is not always readable. Hyprland stops at the first reason on a monitor with no workspace yet — one just coming back — and returns before it ever looks at the lock, so a missing LOCK there means nothing was asked rather than nothing was found. Neither that nor an unreachable compositor is an unlocked session, and locks strand precisely while outputs are coming and going, so both exit 2. Callers that only branch on success are unaffected. The test fixture claimed the string came from a workspace name, so it was encoding the wrong model of the compositor. It now returns what Hyprland actually returns. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Retake a session lock stranded by a dead shell ext-session-lock keeps the session locked when its client goes away — that is the point of the protocol, so a crashing lock screen cannot expose the desktop. The cost is that a shell which dies while locked leaves the compositor locked with nothing left to authenticate against: Hyprland's failsafe, which takes a TTY or another machine to clear. Nothing carried the lock across a restart. Quickshell relaunches itself after a crash and omarchy-restart-shell can be run by hand, but both bring back a shell holding no lock, so the failsafe stayed up. A fresh shell never holds a lock, so a session already locked as the lock service starts can only be that orphan: take it back and let the user type their way out. Asking once is not enough. These deaths happen while outputs are going away, and the replacement shell comes up inside that same window, where there is nothing to read a lock off. So the question is asked until the answer means something: on a short timer while the session settles, and again when a screen comes back, since a display asleep for hours outlasts any timer worth running and returns through a state the compositor cannot answer for either. Once an answer does arrive the search ends, so the timer stops and later screen changes cost nothing. Three ways this could lock a desktop nobody asked to lock, all closed. A lock this shell took itself is not an orphan, including one taken while the question was in flight — omarchy-restart-shell re-locks a fresh shell, and the answer cannot tell whose lock it found. Recovery runs once and clears the flag, so nothing lingers to fire after an unlock. And PAM landing late reopens the question rather than answering it: clearing the failsafe from a TTY is the documented way out, so a yes from before there was anything to do about it may be stale by the time it can be acted on. The check has to live here rather than in the launcher. Quickshell's crash handler re-execs in place, keeping the same pid, so a supervising process never sees the restarts that recovery matters most for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Relaunch the shell when it dies without a signal Quickshell restarts itself after a crash, but only from its signal handlers: SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, SIGTRAP. Qt does not always leave that way. When the Wayland connection fails, QWaylandDisplay::checkWaylandError calls _exit() directly, which raises no signal at all — so the crash handler never runs, no report lands in ~/.cache/quickshell/crashes, and the desktop is left with no bar and no explanation. That is how #6684 ends: the lock path meets a screen with no valid Wayland output, declines to create a lock surface for it, and the connection dies with EINVAL. Supervise the launcher so those deaths come back. A clean exit is deliberate — omarchy-restart-shell stops the shell over IPC and starts its own replacement — and a signal to the supervisor means the session is going away, so neither relaunches. Neither does a shell that outlived its compositor, though that takes more than one unanswered query to conclude: the shell dies while outputs are being reconfigured, which is also when a busy compositor can miss one without being gone. A shell that cannot stay up gives up after five tries in a minute rather than spinning. Signals need care now that a launcher stands between the session and the shell. Bash defers a trap until a foreground command returns, so the shell runs as a job and the supervisor waits on it. Stopping the launcher used to stop the shell with it, back when this script exec'd Quickshell, so the signal is passed on rather than leaving a desktop nobody is watching. One arriving during the backoff sleep only reaches the trap afterwards, so the flag is read again at the top of the loop: a shutdown racing a crash would otherwise get one more Quickshell on its way out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3d1914a8cd |
Let bar put place a widget on a bar it does not recognize (#6687)
* Place a bar widget on a bar without the widget it names 'omarchy bar put X --after Y' refused outright when Y was not on the bar, so migration 1786279107 failed for every user whose clock is their own clone of omarchy.clock rather than the built-in, and took the rest of the migration chain down with it. put is the verb a migration or an install reaches for precisely because it cannot know what the bar it places into looks like, so it now falls back to the widget's usual spot instead of failing. 'plugin enable', which someone types, still says when it cannot find the target. A clone also answers as a placement target now, whether it is the widget the placement named or the anchor the fallback lands against: cloning the clock leaves a bar carrying your id where omarchy.clock used to be, and a caller naming the source means the clone that took its place, the way resolveEnabledId already routes calls to it. So the widget sits next to that clock rather than at the end of the section. Fixes #6678 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Keep asking a shell that is still starting An 'omarchy update' landing while the shell restarts failed migration 1786279107 twice over. Quickshell answers a call made before it finishes loading with "Not ready to accept queries yet." on stdout and exits 0, so a caller polling with a ping read a starting shell as up and then took that sentence for the answer to its real call; report it as unreachable, which every caller already knows how to handle, and omarchy-restart-shell stops cutting its readiness loop short on it too. Reading the plugin manifests is a subprocess behind that, so IPC starts answering before the registry knows the widget it is being asked to place, and put refused it as unknown. Say which of the two it is and let put keep asking. Only a shell that was never there is nothing to fail over. One that never finishes starting, one that stops responding, one too old to know the call at all: each has to fail, since omarchy-migrate records a migration that returns 0 as done, and the widget is then never placed and never asked for again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fall back for the shell an update has not restarted yet omarchy-update runs its migrations before omarchy-update-restart, so the shell answering migration 1786279107 on the update that carries this fix is still the one that shipped without it, and it refuses the placement exactly as before. The users this is for would have watched one more update go wrong. put owns the fallback it documents, so let the command carry it: asked again without the neighbour the shell says it cannot find, that shell places the widget. A restarted shell never answers this way — it falls back itself, and knows to look for a clone of the widget the placement named, which the command cannot. Having answered once is now remembered across both asks. A shell that speaks and is then gone has stopped mid-request, and reading that as a machine that never had one would leave the migration recorded as done. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Wait for a shell that has not appeared yet A shell being spawned has no socket to answer on, and nothing tells the command a launch is under way, so a put landing in that window read the silence as a machine without a shell and carried on — leaving the migration recorded as done with nothing placed. Give one three seconds to turn up first. A machine that genuinely has no shell still carries on, three seconds later. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Leave a clone of the widget being put where it is A clone is the widget it was cloned from wearing its owner's name, so a bar carrying one already has what put is being asked to place. put only saw the literal id, and enabling a first-party source whose clone is active is how you switch back to the built-in — so a migration placing omarchy.keyboard-layout would have handed a user's own copy back for the shipped one, and called it done. Targeting learned to read a clone as its source; presence had not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Trim the comments on the bar put path Roughly a line of comment per line of code, most of it restating what the code and the assertion messages already say. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0d45f0979b |
Note that fully hidden groups stay out of GROUP_DESCRIPTIONS
Following the rule as written is what put apply back in the top-level group listing right after every command in it was hidden: the table drives that listing on its own, with no regard for whether anything in the group is visible. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
477284f002 |
Hide install-time setup plumbing from the command listing
The setup group is described as interactive setup wizards, but these three are not that. omarchy-setup-system is the ISO's entry point in the target chroot, omarchy-setup-hardware is what it calls for device quirks, and omarchy-setup-lock is called by install/config/lockscreen-pam.sh. None of them is something to browse to and run. Hiding only affects listings, so the ISO and the install leaves keep routing through the CLI exactly as before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
186668a70f |
Apply the Broadcom Wi-Fi quirk to Macs without a T2 (#6652)
* Apply the Broadcom Wi-Fi quirk to Macs without a T2 brcmfmac lets the Wi-Fi firmware run the WPA handshake itself, and on Apple hardware that offload fails against an access point in WPA2/WPA3 transition mode: the client associates, the four-way handshake never completes, and NetworkManager reports the password as wrong. feature_disable=0x82000 turns off the firmware supplicant and authenticator so wpa_supplicant does the handshake in software. That quirk already shipped, but only for Macs with a T2 chip. The bug is in the Broadcom firmware rather than in the T2 bridge, so it was never the right thing to gate on: a MacBookPro11,4 has BCM43602 with 2015 firmware, fails exactly this way, and got nothing. Gate on the hardware that actually has the firmware — an Apple machine with a Broadcom wireless part — which covers both. Moving it out of fix-t2.sh also leaves one owner for the file. Two leaves writing the same config would have meant the later one silently winning, decided by an ordering in all.sh nobody would think to check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Gate the Broadcom Wi-Fi quirk on the T2 ID or a brcmfmac chip ID Sniffing lspci for an Apple vendor with a Broadcom network controller made T2 Macs depend on a detection line they never needed: they carry a T2 PCI ID that is always there, and the class name half of `lspci -nn` comes from the pci.ids database. Keep their original gate untouched. Naming the rest by DMI model does not hold up either, because the model year does not predict the part. A MacBookPro11,4 from Mid 2015 carries a BCM43602 and needs this; a MacBookAir7,2 from Early 2015 carries a BCM4360 and does not. Covering the lineup by name takes around twenty identifiers across four product lines and grows every time Apple ships hardware. The set has an exact definition already: the PCI IDs brcmfmac binds, from the driver's own brcm_hw_ids.h. That reaches the 2016 and 2017 MacBook Pros and the T2-less iMac19,1 and iMac19,2 that a hand-written list missed, and it leaves out the BCM4360 Macs for free, since their out-of-tree wl driver would never read a brcmfmac option anyway. Matching an exact vendor:device ID also drops the piped `grep -q`, which returns 141 under pipefail once the producer is killed by SIGPIPE (#6608). The test runs the leaf with pipefail so the chatty lspci stub proves it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix Macs already installed without the Broadcom Wi-Fi quirk The quirk is written at install time, so a machine set up before it shipped never gets it, and no pre-T2 Mac ever did. Those installs still fail the WPA four-way handshake against an access point in WPA2/WPA3 transition mode, which is the state the reporter had to repair by hand. Appending leaves anything else in the config alone: modprobe reads every options line for a module, and nothing else sets feature_disable. Only an active options line counts as already applied, and the driver keeps the old behaviour until it reloads, so this asks for a reboot rather than pulling brcmfmac out from under a connection that currently works. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: David Heinemeier Hansson <david@hey.com> |
||
|
|
8bc854069b |
List Herdr right below Tmux in the keybindings menu
The menu ranks the launcher bindings by hand, and Herdr had no rule, so it fell into the unranked middle far from the terminal it sits beside in the bindings. Rank it right after Tmux and shift the rest down, and give "Show Herdr key bindings" the same treatment next to its Tmux counterpart at the bottom. The cache key is bumped so machines holding records from the old order rebuild them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5649740f51 |
Build the menu IPC payload with jq instead of perl
Forking perl to produce {"menu":"<route>"} cost more than the IPC call it
fed. jq is already a runtime dependency and emits identical JSON.
Opening the menu drops from ~42ms to ~28ms.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
e4604fbcfb |
Generate image picker thumbnails with libvips in parallel (#6686)
Thumbnails were generated one ImageMagick process at a time. Queue the missing ones and drain them across every core with vipsthumbnail, which decodes and encodes faster and lets the per-process startup overlap. Cold cache for a 92 wallpaper directory drops from 14.2s to 1.3s, and the bundled theme previews from 2.0s to 0.26s. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c53190be07 |
Remember Bluetooth on/off through the rfkill soft block (#6682)
* Turn Bluetooth off with an rfkill soft block BlueZ never persists an adapter's Powered property, so turning Bluetooth off in the panel lasted only until the next boot. Omarchy's answer was AutoEnable=false, which persists nothing either — it just means "never power the adapter on", so Bluetooth came up off every boot whatever the user had chosen. The soft block already does the job. systemd-rfkill saves every switch under /var/lib/systemd/rfkill and restores it early on the next boot; that is the entire purpose of the unit. Blocking also covers every controller at once, where bluetoothctl only ever addresses the default one. So the block becomes the state and BlueZ follows it: with AutoEnable back at its stock default, lifting the block is enough for bluetoothd to power the adapter up on its own. Powered still tracks the block, so the panel switch and icon read it exactly as before. Everything that turns Bluetooth on or off goes through omarchy-bluetooth-power, because bluetoothctl power on fails while a block is set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Carry installed machines over to the rfkill block Existing installs have AutoEnable=false, so their adapter is down at every boot and Powered is the only record of what the user actually wants. Read it before anything changes, hand it to the block, then put AutoEnable back to its default so bluetoothd can act on that block. Only the exact line Omarchy wrote is reverted, so a hand-edited opt-out survives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Ask the power helper for a direction, not a toggle The helper runs detached and the switch only moves once BlueZ catches up, so a second click inside that window re-read the pre-click state and undid the first. The panel already knows which way it wants to go, so let it say. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Read every controller and bound the power-up wait The block hits every Bluetooth radio at once, but the state was read from a bare bluetoothctl show, which reports the default controller only. A powered dongle sitting behind a powered-down internal controller read as off and got blocked along with it. Enumerate the controllers and take any powered one as on, exposed as is-on so callers do not each reinvent the read. The wait counted probes rather than time, so a wedged D-Bus turned a two-second bound into roughly fifty across a full power-up. One deadline around the whole wait holds it near nine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Change the radio through sudo in the migration /dev/rfkill is only writable unelevated from an active graphical seat, so an update run over SSH failed here with EACCES. Migrations run under bash -e, so that aborted before the config revert and the marker, and aborted again on every retry. The privilege guidance already calls for sudo on machine-wide work run from a visible terminal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
567e24cd90 |
Hold the indicator peek open while the pointer is on the bar (#6663)
* Hold the indicator peek open while the pointer is on the bar Revealing the hidden indicators widens their section, and a section that grows can slide a neighbouring widget under a pointer that never moved. Collapsing the peek on that un-hover narrowed the section again, moved the neighbour back out, and re-opened the peek, so a pointer resting in the bar space beside a grown section stuttered the bar until it moved away. Hold the peek while the pointer is anywhere on the bar and close it only once the pointer has left, which keeps the reveal-on-empty-space gesture and drops the feedback loop. Fixes #6581 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Assert the whole-bar hover helper does what the peek depends on The earlier assertions all held against a no-op setBarHovered, which would leave barHovered false and let the oscillation straight back in. Pin the assignment and the collapse re-run too, so the helper cannot be emptied without the suite noticing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Let the delayed peek re-check collapse only, never open The timer assigned centerSectionRevealHeld outright, so it opened the peek from bar hover alone. A pointer resting on the left section that dipped off the bar and returned inside 120ms left the timer pending with barHovered true again, and the indicators revealed without the pointer ever touching the center section. Opening stays the center section's own gesture in setCenterSectionHovered. The timer now only closes what that opened, and the test asserts the invariant against the whole file rather than one helper body that never had the offending assignment in it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Tally bar hover per monitor instead of sharing one flag Every screen's bar wrote the same barHovered bool, last writer wins. Sliding along the top edge from one monitor's bar to the next can deliver the enter before the leave, leaving the flag false under a live pointer; the collapse then fired on a peek the user was still hovering, and no further hover change arrived to correct it until the pointer left and came back. Counting each surface's hover makes the order irrelevant. A bar destroyed mid-hover — unplugging a monitor — never sends a leave, so it hands its tally back on destruction rather than holding the peek open for good. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: David Heinemeier Hansson <david@hey.com> |
||
|
|
199bd01f94 |
Reach every window the region picker can highlight
Warping to a target window's center selects the wrong window when a smaller one covers that center: slurp keeps highlighting the coverer, so Tab could never leave it. Navigation now warps to the most central point that resolves back to the target, and skips windows that hovering could not reach either. Unbinding the picker's transient keys by name also took a same-key binding out of the user's own config with it; the bind handles are now kept and removed individually. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
366c708e44 |
Keyboard window capture in the region picker: Return, Tab, arrows (#6466)
* Keyboard window capture in the region picker: Ctrl+Return, Tab, arrows Return already captures the entire focused monitor while slurp is open, but grabbing a single window required the mouse: hover highlights a window, a click captures it. Now the keyboard can do the same. Ctrl+Return captures the selected window — the one under the cursor, falling back to the focused window. Tab and Ctrl+Tab cycle that selection through the workspace's windows in reading order, and the arrow keys move it spatially; both warp the cursor to the target window's center, so slurp's own hover highlight tracks the selection. Ctrl+Return is implemented like --take-fullscreen: a layer-scoped bind flags the intent via a marker file and dismisses slurp, and the picker resolves the window under the cursor. On an empty workspace the pick resolves to nothing and exits as cancelled. Ctrl is the chord modifier because slurp reacts to held Shift by squaring the selection's aspect ratio, which visibly reshapes the hover highlight mid-chord. None of the keys slurp itself uses (Escape, held Space to move a selection, held Shift) are bound, so its own keyboard behavior is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Resolve overlapping windows the way slurp highlights them slurp highlights the smallest box under the cursor, but the capture and selection hit-tests returned the first geometric match in hyprctl clients order. With a floating window over a tiled one, Ctrl+Return could capture the window underneath the visibly highlighted one. Track the smallest containing window instead, in both the capture resolution and the selection-movement origin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Document the region picker's keyboard modes in its usage line Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Resolve keyboard capture and navigation from one rectangle list Duplicate window geometry (tabbed groups, stacked floating windows) stalled the Tab cycle on the first copy, and the smallest-window-under-cursor tie-break was applied to two differently ordered lists, so navigation could cycle from one window while Ctrl+Return captured another. Ctrl+Return over a gap or an empty workspace also ignored the monitor rectangle slurp was highlighting. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Capture the highlighted window with Return, the display with Ctrl+Return The picker highlights a window under the cursor far more often than a whole display, so the unmodified key takes the highlighted rectangle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: David Heinemeier Hansson <david@hey.com> |
||
|
|
03b825f59a |
Show Herdr keybindings with SUPER + CTRL + K
Mirrors the Tmux keybindings menu on SUPER + ALT + K. Herdr has no CLI that dumps resolved bindings, so the action list and its defaults come from `herdr --default-config`, where every action appears as a commented assignment, and the user config overrides what it sets. Prose in that default config can read like an assignment, as in `# type = "popup" opens a session-modal terminal`, so a line counts only when its value is a bare string or an array of them. Both TOML quote characters open a string, and with no config file at all the listing shows Herdr's own defaults rather than the Omarchy seed config Herdr never loaded. |
||
|
|
507059e548 |
Open Herdr with SUPER + CTRL + RETURN
Mirrors the Tmux binding on SUPER + ALT + RETURN. Herdr reattaches to the persistent session on its own, so the launcher needs no attach-or-create dance. |
||
|
|
932efbd58b |
Center the first-boot setup screen and fix its greeter animation
The setup progress screen stacked from row one while the greeter that precedes it was already centered, so first boot changed shape between its two frames. Center it on the same block height the ISO install dashboard uses — logo, blank, title, blank, bar, blank, tip — and repaint it if the VT resizes, which is the same virtio-gpu KMS handoff the greeter already watches for. The greeter's ttfx call never passed --xterm-colors, so it hit exactly the failure the comment above it describes: ttfx resolves even indexed stops to truecolor, and the console reduces 256-colour codes well but 24-bit ones badly. The gradient was being crushed rather than rendering as the indexed palette it was written for, and the settle colour drifted off the green logo drawn beneath it. --canvas-width was cols-1 to stay off the autowrap column, which held for tte. ttfx centres its text two columns right of plain centering, so cols-1 puts the animated logo a column off the static one and it jumps when the effect starts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4ab51df2b0 |
Require a factory snapshot to reset a computer (#6680)
Machines without @factory fell back to a degraded reset that kept the current system and only wiped user state. Turn them away with an explanation instead, and drop the degraded staging path. The first-boot worker still honors a wipe-degraded marker so a reset staged by an older version finishes its scrub rather than handing the machine over with the seller's accounts intact. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0b670cd13e |
Title herdr's terminal window like tmux did
tmux set the outer terminal title from `set-titles-string '#h:#W'`, which is what Hyprland reads for the group bar label. herdr had no equivalent, so grouped terminals kept whatever the shell or ssh last set - most visibly wrong when connected to a remote machine. herdr now renders `ui.window_title` on the server, so mirror the tmux line. herdr's own default matches, but setting it here keeps the group bar correct regardless of what upstream picks as its default. |
||
|
|
9f13bfc157 |
Center the timezone picker in first-boot setup
The timezone prompt falls back to `gum filter` when tzupdate can't guess a zone, which is the common case on a first boot with no network yet. That was the one widget in the form without a GUM_*_PADDING export, so it drew flush left while every other step sat centered under the logo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2d44ad59be |
End deferred first boot on the finished progress bar
Clearing it for a "Starting Omarchy..." card only flashed another screen before SDDM took over. The cursor now stays hidden through the handoff and comes back only on the failure path, where the retry prompt needs it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6d7826d635 |
Give non-login shells the system locale
/etc/profile.d/locale.sh only runs for login shells, so bash started by SSH or herdr's remote bridge ran in the C locale, where printf emits \u/\U escapes literally instead of the character. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
7633d8dee4 |
Keep the bar mapped while hidden so revealing it is instant (#6677)
* Keep the bar mapped while hidden so revealing it is instant Hiding the bar set the panel invisible, which unmaps the layer surface and releases the scene graph with it. Every reveal then had to rebuild all of it: a new layer surface, a configure roundtrip, re-shaped glyphs and re-uploaded textures, and a first frame before anything appeared. Measured on a 2560x1440 screen, showing took 155-175ms against 20ms to hide, and 400-595ms on the first reveal after a cold start. Splitting the cost showed the exclusive-zone reflow was not to blame: show latency was the same on an empty workspace as on a tiled one, and windows finished moving ~15ms after the bar was already on screen. Park the bar one bar-width past its anchored edge instead, and drop its exclusion zone while hidden. The surface stays alive, so showing is only a margin change: 10-14ms in both directions, at every bar position. Since a hidden bar is now mapped, layer_present no longer proves the bar is visible; the session acceptance test asserts on-screen geometry. * Fix layer visibility checks on offset monitors * Handle rotated outputs in layer visibility checks * Cover hidden bar behavior in acceptance tests --------- Co-authored-by: David Heinemeier Hansson <david@hey.com> |
||
|
|
354c2f0060 |
Wait for the terminal resize before the first screensaver effect
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4cc14933a4 | Use sudo for terminal update inhibition | ||
|
|
dc1224c03a |
Stop the sleep lock budget assertion from flaking
A 1500ms budget plus the one 100ms poll interval the trailing sleep can overshoot is exactly 1600ms, which was the bound — but the test measures a whole process around that, so startup pushed real runs to 1602ms. Carry another interval. Two intervals of overshoot, the regression this guards, still trips it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f228c4d390 |
Stop checking for commands the package set guarantees
gum and ttfx ship in omarchy-base.packages and setfont comes with kbd, so none of them needs a presence check — and every setfont call already tolerates a failure anyway. btrfs-progs is in omarchy-other.packages and a reset genuinely cannot proceed without it, so that one keeps its guard and moves to the helper. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4564c24a0e |
Cover the shared setup form's cancel contract
The form's 0/1/130 statuses gate both the ISO configurator and first-boot setup, and nothing tested them. Stubs gum with scripted per-screen answers and drives each prompt bare under `set -euo pipefail` — the shape that makes the status capture load-bearing, since a cancelled prompt is a failing assignment. A RETURN trap marks that the prompt returned its status rather than the shell dying inside it; both exit identically otherwise, so that marker is what catches a regression to a plain `status=$?`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4e31b61af0 |
Swap terminaltexteffects for ttfx (#6670)
* Swap terminaltexteffects for ttfx ttfx is a Rust port of terminaltexteffects that renders byte-identical frames as a single dependency-free binary. Same option names, defaults, and exit codes, so every invocation here is unchanged apart from the command name. The screensaver runs at --frame-rate 120 with --random-effect. On a fullscreen canvas Python cannot hold that for the heavier effects (beams: 14.1 ms/frame against an 8.3 ms budget, so ~71fps); ttfx renders the same effect at 564fps. Startup drops from ~107 ms to ~1 ms, and the base image no longer needs Python for the screensaver. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Need a migration --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7644b56d4e |
Extract the setup form shared with the ISO installer (#6669)
The keyboard layout list, the account and hostname validation rules, and the gum prompts that ask for them all existed twice: once in the ISO configurator's user step, once in first-boot owner setup. Nothing kept the copies honest, and they had already drifted — a layout removed on one side moved English (US) onto a page boundary on the other, burying the default at the bottom of a screen of layouts. install/provisioning/setup-form.sh is now the only copy. The PKGBUILD's existing `cp -a install` ships it to /usr/share/omarchy/install/provisioning/, and the ISO build vendors that very file out of the runtime package it bundles, so an install and the first boot that finishes it cannot offer different layouts or accept different usernames. Cancel handling is unified along the way, which is what made the prompts shareable at all. Every prompt reports 0 (answered), 1 (Esc — unwind to the start of the form), or 130 (Ctrl+C — a side channel each caller defines). Previously Esc and Ctrl+C were indistinguishable here: both re-asked the same field, so there was no way back to an earlier answer. Ctrl+C now offers a confirmed reboot instead. It cannot be a SIGINT trap — gum reads Ctrl+C as a byte in raw mode, so the shell never receives the signal — so it hangs off the exit status. Status capture is written as `x=$(gum ...) && status=0 || status=$?` because this script runs under `set -e`, where a cancelled prompt is a failing assignment that would kill setup before the status could be read. English (US) also leads the layout list now, ahead of the other English variants. gum choose paginates in --height-sized pages and jumps to the page holding --selected, so an alphabetical default landed wherever the list length happened to put it. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c7c897cb4f | Follow herdr's new tab bar status config (#6674) | ||
|
|
0a8359072c |
Show install-style progress through first-boot account setup (#6631)
* Add OEM first-boot setup and factory reset An OEM-mode ISO install (or omarchy-reset-computer) leaves the machine in OEM state: fully installed, no user, /var/lib/omarchy/oem/pending armed. On the next boot omarchy-oem-setup.service runs the configurator's user form on tty1, creates the user with the groups system setup recorded, finalizes it offline from the stashed Node tarball, re-keys LUKS from the throwaway install passphrase to the user's password, and hands off to SDDM. omarchy-reset-computer returns a machine to that state: it swaps the running root for a fresh clone of the @factory snapshot the ISO takes at install time, scrubs machine identity and prior users, and stages omarchy-factory-wipe to drop the old root and recreate @home/@log on the next boot. Machines installed before @factory existed get a degraded reset (current system kept, users and state wiped) with that caveat surfaced in the confirmation. omarchy-setup-system/-hardware gain --oem to run without an install user; the group-granting install scripts now record their groups in /var/lib/omarchy/oem/groups and only call usermod when the user exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Harden OEM setup: correct cryptsetup key-file usage, retry on failure cryptsetup reads --test-passphrase/--key-file inputs byte-for-byte, so feed passphrases through process substitution consistently instead of positional args or stdin (which has different newline semantics). Run each first-boot setup attempt as its own process so a failure offers a retry instead of stranding the machine at a user-less login screen — bash ignores errexit inside `while !` conditions, a child process does not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Always grant wheel sudo in OEM first-boot setup Detecting an existing %wheel grant by grepping sudoers is error-prone: omarchy ships narrow '%wheel ALL=(ALL) NOPASSWD: <command>' rules (e.g. asdcontrol) that match the naive pattern, which left the OEM-created user matching sudoers entries but unable to run anything. Write the drop-in unconditionally — a duplicate of an existing full grant is harmless. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix LUKS re-key device resolution and OEM state readability archinstall's encrypted installs put cryptdevice=PARTUUID=... on the kernel cmdline, not UUID=, so the first-boot re-key never found its device and silently skipped — leaving the throwaway auto-unlock keyfile in place, i.e. the disk effectively unencrypted. Parse every cryptdevice= source spec form and make any re-key failure abort the attempt loudly: a retry prompt beats a machine that quietly boots without a passphrase forever. The OEM state directory also has to be world-readable (its one secret, luks-key, stays 0600): user finalization reads the stashed Node tarball as the new user, and the 0700 directory forced it onto the network fallback. Step markers now land in /var/log/omarchy-oem-setup.log for debuggability. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Purge stale machine-id boot entries when resetting or re-keying limine-entry-tool keys its limine.conf OS entries by machine-id. A factory reset gives the machine a fresh identity, so the previous system's entry survived every rebuild, sorted first, and made Limine stop at a Blake2b hash-mismatch warning once the UKI was rebuilt. Start limine.conf over from the shipped template (and drop foreign machine-id history directories on the ESP) before any post-reset rebuild: in the staged chroot rebuild, in the first-boot LUKS re-key, and — for unencrypted resets, where nothing else rebuilds — in a dedicated first-boot refresh when foreign entries are found. The staged rebuild also verifies every UKI hash referenced by limine.conf against the file on the ESP before the subvolume swap, and the running system's limine-snapper-sync is runtime-masked during staging so it cannot rewrite the config behind the rebuild. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Harden reset and first-boot setup failure paths Review findings from codex and Copilot: - Generate throwaway passphrases without a trailing head stage: under pipefail, SIGPIPE from the infinite tr failed the substitution and errexit aborted every encrypted reset before it could stage anything. - Stage the fallible parts of a degraded reset (LUKS re-key, boot rebuild) before arming the wipe, so a staging failure leaves the machine untouched instead of scheduling a wipe for a reset that never finished. - Gate first-boot setup on the factory wipe having succeeded (ConditionPathExists=!wipe-pending plus an in-script guard): creating the new user on a half-wiped system would hand their data to the wipe retry. - Abort the wipe (keeping its retry marker) when deleting the old root or recreating @home/@log fails, and abort resets that cannot remove a prior account — a surviving account keeps its password and wheel membership. - Resume a partially-created account on setup retry instead of rejecting the username the failed attempt just created. - Only purge machine-id directories the old limine.conf actually referenced; a shared ESP may hold other installations' boot artifacts. - Recreate the hibernation swapfile (nested subvolume, so never captured by the factory snapshot) inside the factory root before its UKI rebuild, so a reset machine keeps disk-backed swap and a valid resume offset. - Source base-test.sh in the OEM groups test per test conventions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Recreate the hibernation swapfile even when resume drop-ins survive omarchy-hibernation-setup short-circuits as 'already set up' when the resume mkinitcpio drop-in exists — which it always does in a factory root, while the swapfile itself never survives the snapshot (nested subvolume). Drop the marker when the swapfile is gone so setup reconfigures from scratch, and verify the swapfile actually exists before proceeding with the reset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Second review pass: encrypted-config coverage, factory-baseline sanitization, recoverable rekey Codex xhigh round 2: - Detect the LUKS backing device by walking the root's device tree, not only the cmdline cryptdevice=; reset/first-boot now re-key roots reached via rd.luks/crypttab too, instead of silently leaving the seller's slots valid. - Sanitize the retained @factory baseline (accounts, /etc/shadow, machine identity) during a full reset: the new wheel user could otherwise mount it to recover the seller's data, and a second reset would restore the account. - Re-key the disk recoverably: rebuild the no-auto-unlock UKI before killing the throwaway slot or destroying the staged key, and restore the keyfile if that rebuild fails, so a retry with a different password can never leave the disk locked to the first attempt's password. - Roll back a degraded reset's live-root auto-unlock material if its boot rebuild fails, instead of leaving it for a later rebuild to embed. - Treat a missing current-machine limine entry as stale so a retry after a failed rebuild repairs the config instead of clearing OEM state over it. - Erase fingerprint enrollments (/var/lib/fprint) in degraded wipes. - Remove the resume-offset drop-in too when recreating the factory swapfile, so the rebuilt UKI gets a correct offset. - Pin first-boot retries to the account the first attempt created. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Expose factory reset in the Setup menu Add a 'Reset Computer' entry under Setup (Omarchy's Settings menu, where OS factory resets conventionally live), guarded to btrfs roots and launched in a floating terminal. omarchy-reset-computer now self-elevates via sudo so the menu entry needs no sudo prefix, forwarding the caller's gum theme env as env arguments so styling survives an env_reset sudoers. The typed 'reset' confirmation and the sudo password prompt remain as the guards against accidental triggering. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Defer keyboard selection to first boot for OEM installs The OEM first-boot setup now runs a keyboard step before the user form, mirroring the ISO configurator: it loads the chosen layout on the live VT so the password (and the LUKS re-key that follows) are typed under it, and persists it with systemd-firstboot so the installed system gets both the console KEYMAP and the XKB layout Hyprland reads — exactly what a normal install writes. Layouts localectl doesn't know keep the default, same as the installer. This lets the OEM operator set nothing user-specific: the machine's owner picks their keyboard alongside their account at first boot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Rename factory-reset commands to omarchy-system-factory-reset[-finish] omarchy-reset-computer -> omarchy-system-factory-reset omarchy-factory-wipe -> omarchy-system-factory-reset-finish (and its systemd unit, log path, and temp mount to match) Pure rename: every reference — the Setup menu action, the first-boot finish service the reset stages and enables, the oem-setup ordering/gating, comments, and the menu test — moves together, with no behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Rename OEM vocabulary to provisioning (runtime) Commands unify under the provisioning family: omarchy-oem-setup → omarchy-provision-owner omarchy-finalize-user → omarchy-provision-user omarchy-first-run → omarchy-provision-first-run And the deferred-provisioning state/vocabulary replaces 'OEM': /var/lib/omarchy/oem/ → /var/lib/omarchy/provisioning/ /etc/omarchy/oem.key → /etc/omarchy/provisioning.key install/oem/ → install/provisioning/ OMARCHY_SETUP_CONTEXT=oem-firstboot → provision-owner omarchy-setup-system/-hardware --oem → --defer-provisioning All callers (provision-first-run→provision-user, autostart, factory-reset staging the provisioning units, the group-recording scripts) and comments move together. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Drop remaining OEM mentions from the provisioning groups test Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Show install-style progress through first-boot account setup The owner provisioning flow dropped to plain status lines after the confirm form, went dark for the minutes finalize-user takes, and ended with a two-second spinner. Give it the same treatment as the ISO install dashboard: the logo header with a live progress bar and rotating tips while the account is created, finalized, re-keyed, and the boot entries refreshed, then the same tte celebration — ending in a Start Omarchy Now button that hands off to SDDM, since first boot continues into the session rather than rebooting. The bar is the dashboard's engine in miniature: monotonic per-mille position from an asymptotic time floor per phase, with finalize-user's run_logged scripts in the log as the work signal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Match the first-boot console font to the ISO installer The ISO installer runs on a low-resolution pre-KMS console, where the default 8x16 console font looks large. By the time omarchy-provision-owner runs, the installed system has reached full KMS resolution, where that same 8x16 font is physically tiny — so first-boot owner provisioning looked a lot smaller than the installer it continues. Scale the console font up on high-resolution framebuffers so the two read at the same size: latarcyrheb-sun32 (16x32) at >=1600px tall, sun12x22 at >=1150px, and the default left untouched below that (already installer-sized). Both fonts ship with kbd, so there's no new dependency, and it's a no-op off a real VT. Co-Authored-By: Claude <noreply@anthropic.com> * Make first-boot font resolution-adaptive; widen the encrypting band Two fixes to the first-boot provisioning screen surfaced on a real high-resolution display. Font: the earlier fixed thresholds could pick a 16px-wide font on a ~1280px-wide console — 80 columns, one short of the 81-column logo — so the logo wrapped and the layout looked misaligned. Replace the thresholds with a resolution-adaptive choice: pick the kbd font whose row count is closest to the ISO installer's ~48-row feel, but never one wide enough to drop below 90 columns, so the logo can never wrap. Low-resolution consoles land on the default font (a no-op), matching the installer. Progress: the LUKS re-key (a full UKI rebuild, the slowest step of an encrypted first boot) had only an 8%-wide band, so the bar looked stuck near the top while it worked. Make the bands adaptive — when a re-key is pending, finalize yields most of its room to a wide, steadily-moving re-key band; unencrypted installs keep finalize as the dominant step. Co-Authored-By: Claude <noreply@anthropic.com> * Add a Welcome greeter before first-boot setup Deferred-provisioning first boot dropped the new owner straight onto the keyboard picker. Open with a greeter instead — the same frame the setup ends on: static logo, a centered "Welcome to Omarchy", and a single "Start setup" button. No logo animation; it's the starting line, not the celebration. Shown once in main(), before the keyboard step, so retries don't repeat it. Co-Authored-By: Claude <noreply@anthropic.com> * Center the greeter and show the Omarchy tagline Vertically center the greeter block (logo, tagline, button) on the console like the boot logo, and replace "Welcome to Omarchy" with the tagline "Beautiful, Modern & Opinionated Linux by DHH". Co-Authored-By: Claude <noreply@anthropic.com> * Animate the greeter logo with a skippable ColorShift Run a looping tte ColorShift over the centered logo and replace the button with a "Press Return to Start Setup" hint. The effect reads from /dev/null so it never swallows the Return a foreground read waits on, and --reuse-canvas is anchored one row below the logo so it repaints exactly the rows drawn above. Return skips ahead into setup at any time. Co-Authored-By: Claude <noreply@anthropic.com> * Theme the greeter ColorShift to the Omarchy palette Sweep the logo ColorShift through the Tokyo Night accent colors (green, cyan, blue, purple, pink, orange) instead of tte's default rainbow, settling toward the Omarchy green. Co-Authored-By: Claude <noreply@anthropic.com> * Fix greeter ColorShift: green base, no flash, clean handoff - Use indexed ANSI colors (green 2 + cyan 6 accent), since the framebuffer console can't render tte's truecolor and crushed it to a muddy lavender. The result is a green-based shift with a cyan accent, settling on green. - Run one long tte invocation (--cycles 1000) instead of restarting every couple cycles, removing the flash at each loop. - Restore the tty with `stty sane` after killing the effect (tte leaves it raw/no-echo, which silently killed the following gum prompts) and clear the screen so the leftover frame doesn't linger under the keyboard step. Co-Authored-By: Claude <noreply@anthropic.com> * Kill the greeter animation cleanly so the form starts fresh The ColorShift ran inside a `while` subshell that was backgrounded and killed by its subshell PID — which orphaned the tte child, leaving it painting the logo over the keyboard step (the screen never cleared and the form was garbled). Run tte directly so the tracked PID is tte's own, and killing it actually stops the animation before the screen is cleared. Co-Authored-By: Claude <noreply@anthropic.com> * Don't let the killed animation abort provisioning under set -e wait on the tte PID reports its kill signal (143). Under set -euo pipefail that nonzero status aborted greeter_screen — and the whole service — right after Return, dropping first boot straight to the login screen instead of the keyboard step. Tolerate it with `|| true`. Co-Authored-By: Claude <noreply@anthropic.com> * Wait for the terminal size to settle before drawing the greeter A terminal emulator — or sudo's pty — reports a stale 24x80 for a few hundred milliseconds after the process starts, before its real winsize is set. The greeter measured immediately and fell into the 80x24 fallback, rendering small in the top-left instead of centered (the real first-boot console, already settled, was unaffected). Wait for stty to report a stable size at least as wide as the logo before measuring. Co-Authored-By: Claude <noreply@anthropic.com> * Fall back to the default font when the greeter console is too narrow scale_console_font sizes the console font from /sys/class/graphics/fb0, but virtio-gpu (notably virtio-vga-gl) can report a resolution that the console then settles below — leaving a font wide enough that an 81-column logo no longer fits (e.g. sun32 at a settled 1280px is 80 columns). The logo wrapped and hugged the top-left, with the animation tiling over it. After the greeter measures the settled size, if it is still narrower than the logo, drop to the default (narrowest) console font and re-measure so the logo always fits and centers. Verified by forcing an 80-column console: the fallback recovers it to 160 columns, centered, and provisioning completes. Co-Authored-By: Claude <noreply@anthropic.com> * Redraw the greeter on console resize instead of measuring once The first-boot greeter measured the console once, painted the logo and launched the tte animation, then blocked on Return. On a fresh deferred-provisioning boot under virtio-gpu the VT comes up in a transitional ~80x25 mode and only widens to the real resolution a second or more later, once KMS takes over (or, on virtio-vga-gl, the SDL window's size lands). The old settle-wait accepted that transient (two matching 100ms samples was enough), so the greeter committed to an 80-column geometry. When the VT then resized, Linux scrolled the stranded frame into the top-left and reset the DEC saved cursor tte paints from with --reuse-canvas — the small, top-left, tiled/garbled logo. The earlier "fall back to default8x16 when cols<81" fix couldn't help: if the console genuinely offers 80 columns, re-applying the narrowest font still leaves 80. Treat a resize as a redraw trigger instead. greeter_screen now: - waits for a real quiet period (console signature = VT size + framebuffer identity/size held steady ~1.5s) before the first paint; - sizes the font empirically — apply each candidate, read the columns fbcon actually returns, keep the one nearest ~48 rows that still clears the 81-column logo — rather than trusting fb0/virtual_size, which under virgl can report a size the text console never reaches; - paints from a nested _greeter_draw and, while waiting for Return, watches SIGWINCH and the console signature; on any change it kills tte, settles, re-fits the font, and repaints — so a resize arriving five seconds in looks the same as one that never happened; - draws each logo row at an explicit column and falls back to a centered text-only greeter (no logo, no animation) when the console is narrower than the logo, so a still-transient mode never wraps it into mush; - runs tte at canvas-width cols-1 to stay off the autowrap column. Verified the resize path in a tmux pane (a resizable pty): narrow first paint falls back to centered text, and each later resize repaints a correctly centered logo, with a clean exit on Return. Co-Authored-By: Claude <noreply@anthropic.com> * Ask the owner's timezone at first boot and hand off straight to Omarchy Three changes to the deferred first-boot setup: - Timezone: deferred provisioning skips the installer's user step entirely (it defers user creation to first boot), but the first-boot form only re-asked username/password/name/email — the timezone picker was never carried over, so every deferred machine silently kept archinstall's UTC default and the owner was never asked. Add the timezone step to the first-boot user form (geo-guessed default via tzupdate, same as the ISO installer's user step), show it on the confirmation screen, and apply it during provisioning with timedatectl. - Rename the progress-screen title from "Setting up your account" to "Setting up your machine": first boot provisions the whole machine, not just an account. - Drop the timed "Installed Omarchy in Xm Ys" celebration screen and the "Start Omarchy Now" button from first boot. That send-off belongs to a direct install; here the oneshot service just hands straight off to SDDM (Before=display-manager.service), which autologins on encrypted installs. A brief "Starting Omarchy..." covers the handoff. Co-Authored-By: Claude <noreply@anthropic.com> * Autologin only the first deferred boot on unencrypted installs The owner authenticates in the first-boot form, so setup hands straight into the desktop instead of asking again at SDDM. Encrypted installs keep autologin permanently (the LUKS prompt is the auth boundary); unencrypted installs autologin just this once and a self-removing service deletes the drop-in after the desktop is up, so later boots use the normal SDDM login and the disk isn't left permanently open. Co-Authored-By: Claude <noreply@anthropic.com> * Ask the owner's hostname at first boot too Deferred provisioning no longer bakes a hostname into the install, so add the hostname to the first-boot user form (same letters/digits/dashes validation as the ISO installer's user step, defaulting to "omarchy" on empty), show it on the confirmation screen, and apply it with hostnamectl during provisioning — alongside the timezone step. Co-Authored-By: Claude <noreply@anthropic.com> * Let the greeter logo appear from the animation, not a static pre-render The greeter drew the logo statically in green and then started the tte ColorShift over the same rows, so the logo visibly flashed as tte's first frame repainted it. ColorShift shows the full logo on frame one (it's a color effect, not a type-in), so let tte paint the logo itself: skip the static render when tte is available (keep it only as the no-tte fallback). The logo now simply appears, already animating, with no flash. Co-Authored-By: Claude <noreply@anthropic.com> * Drop the greeter color animation; show the logo in solid green The greeter "flash" was tte's ColorShift crushing on the framebuffer console: tte emits 24-bit truecolor, the console only has 16 flat colors, and consecutive frames' greens snap to different buckets (green/bright-green/cyan/ gray) — a frame-to-frame judder. A smooth color fade is impossible on the console (no blending), so the animation can't be made clean there. Show the logo in solid green (indexed color 2, which renders stably) with no animation. The resize-redraw resilience stays; there's just no tte to tear down now. Co-Authored-By: Claude <noreply@anthropic.com> * Fix first-boot review findings: autologin-once ordering, offline timezone Review of the deferred first-boot flow surfaced two real, user-facing bugs: - Autologin fired twice on unencrypted installs. The one-shot cleanup unit was ordered After=display-manager and enabled via a graphical.target.wants symlink + daemon-reload during the first boot — but graphical.target's job for that boot is already computed, so systemd never pulled the new unit in. It only ran on the *second* boot (which also autologged in) and removed the drop-in there. Order it Before=display-manager instead (and drop the sleep fudge): this boot autologins, the next boot's fresh transaction runs the cleanup before SDDM reads its config, so that boot shows the normal login. Exactly one autologin, deterministic, no race. - Offline first boot aborted setup. `geo_guessed_timezone=$(tzupdate -p)` is a plain assignment under `set -e`, and tzupdate exits non-zero with no network — the common case for a fresh machine — killing the attempt before the offline gum-filter fallback. Guard it with `|| geo_guessed_timezone=""`. Also: configure_timezone no longer symlinks /etc/localtime to a nonexistent zone file on a bad zone (guards on the zoneinfo file existing); scale_console_font's stty-size fallback emits a trailing newline so `read` can't trip `set -e`; and drop two stty-sane calls that only existed to recover from tte's raw mode (tte is gone). Co-Authored-By: Claude <noreply@anthropic.com> * Seed placeholder hostname/timezone in deferred install, overwrite at first boot Simpler than the empty-string approach: instead of writing hostname=""/timezone="" and having the orchestrator fall back, the deferred install now seeds neutral valid defaults (hostname=omarchy, timezone=UTC) and the first-boot wizard overwrites both. Identical end state, one decision in one place, and no reliance on archinstall accepting empty config values. Runtime comments updated; the configure_* functions are unchanged (they still overwrite whatever was seeded). Co-Authored-By: Claude <noreply@anthropic.com> * Bring back the greeter ColorShift animation Reverts the "drop the color animation" change — the framebuffer's truecolor crush gives the ColorShift a quick flash, but that's acceptable; removing the effect entirely was overreach. Restores the static green logo + looping tte ColorShift overlay (green base, cyan accent, settling on green), keeping the resize-redraw resilience and every other fix intact. Co-Authored-By: Claude <noreply@anthropic.com> * Hide the omarchy-provision-* commands from the listing These run from services and install hooks (first-boot provisioning, per-user finalize, first-login) — never something a user invokes directly. Mark them omarchy:hidden=true and drop the group/examples that implied a user-facing route. The binaries still work when called directly by their callers. Co-Authored-By: Claude <noreply@anthropic.com> * Hide the factory-reset first-boot worker from the listing omarchy-system-factory-reset-finish runs from a first-boot service after a reset — never invoked by hand — so hide it like the provision workers. omarchy-system-factory-reset itself stays visible (Setup > Reset Computer). Co-Authored-By: Claude <noreply@anthropic.com> * Address final-review findings: LUKS fail-open, set -e aborts, drop-to-console Codex xhigh final pass surfaced several real issues; the contained ones: - LUKS re-key could fail open (security). If the user's slot couldn't be identified, or luksDump/luksKillSlot failed, the code shredded the staged key anyway while the throwaway/seller slots stayed valid — leaving the disk unlockable by the install passphrase forever. Now it's all-or-nothing: require the user's slot, enumerate and kill every other slot, and only retire the staged key once all kills succeed; otherwise fail loudly and keep it for retry. - Greeter animation cleanup: `kill "$anim"` was unguarded, so if tte had already exited (crash or resize race) it returned non-zero and `set -e` aborted setup, dropping to SDDM with no user. Guard it with `|| true`. - reset_limine_config: `old_ids=$(grep ... | ...)` on an entry-less limine.conf (left by a failed rebuild) exits 1 under pipefail and aborted the retry. Guard with `|| true` so a rebuild failure stays recoverable. - "Drop to console" on a failed setup exited to nothing usable (no user, root locked, tty1 getty conflicted). Exec a root shell on tty1 instead; SDDM starts when it exits. Co-Authored-By: Claude <noreply@anthropic.com> * Drop Bosnian and Khmer from the keyboard picker Neither has a console keymap (loadkeys ba / loadkeys khmer both fail), so picking them showed the choice as confirmed but silently left the system on the default US layout. Remove them from the first-boot picker rather than offer a layout that can't apply. The orchestrator keeps its defensive unsupported-keymap handling for install-time robustness. Co-Authored-By: Claude <noreply@anthropic.com> * Finish the omarchy-first-run rename in the docs Two doc references to omarchy-first-run were missed when the script was renamed to omarchy-provision-first-run; update them to match. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6fa4f78ee1 |
Add deferred first-boot provisioning and factory reset (#6621)
* Add OEM first-boot setup and factory reset An OEM-mode ISO install (or omarchy-reset-computer) leaves the machine in OEM state: fully installed, no user, /var/lib/omarchy/oem/pending armed. On the next boot omarchy-oem-setup.service runs the configurator's user form on tty1, creates the user with the groups system setup recorded, finalizes it offline from the stashed Node tarball, re-keys LUKS from the throwaway install passphrase to the user's password, and hands off to SDDM. omarchy-reset-computer returns a machine to that state: it swaps the running root for a fresh clone of the @factory snapshot the ISO takes at install time, scrubs machine identity and prior users, and stages omarchy-factory-wipe to drop the old root and recreate @home/@log on the next boot. Machines installed before @factory existed get a degraded reset (current system kept, users and state wiped) with that caveat surfaced in the confirmation. omarchy-setup-system/-hardware gain --oem to run without an install user; the group-granting install scripts now record their groups in /var/lib/omarchy/oem/groups and only call usermod when the user exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Harden OEM setup: correct cryptsetup key-file usage, retry on failure cryptsetup reads --test-passphrase/--key-file inputs byte-for-byte, so feed passphrases through process substitution consistently instead of positional args or stdin (which has different newline semantics). Run each first-boot setup attempt as its own process so a failure offers a retry instead of stranding the machine at a user-less login screen — bash ignores errexit inside `while !` conditions, a child process does not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Always grant wheel sudo in OEM first-boot setup Detecting an existing %wheel grant by grepping sudoers is error-prone: omarchy ships narrow '%wheel ALL=(ALL) NOPASSWD: <command>' rules (e.g. asdcontrol) that match the naive pattern, which left the OEM-created user matching sudoers entries but unable to run anything. Write the drop-in unconditionally — a duplicate of an existing full grant is harmless. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fix LUKS re-key device resolution and OEM state readability archinstall's encrypted installs put cryptdevice=PARTUUID=... on the kernel cmdline, not UUID=, so the first-boot re-key never found its device and silently skipped — leaving the throwaway auto-unlock keyfile in place, i.e. the disk effectively unencrypted. Parse every cryptdevice= source spec form and make any re-key failure abort the attempt loudly: a retry prompt beats a machine that quietly boots without a passphrase forever. The OEM state directory also has to be world-readable (its one secret, luks-key, stays 0600): user finalization reads the stashed Node tarball as the new user, and the 0700 directory forced it onto the network fallback. Step markers now land in /var/log/omarchy-oem-setup.log for debuggability. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Purge stale machine-id boot entries when resetting or re-keying limine-entry-tool keys its limine.conf OS entries by machine-id. A factory reset gives the machine a fresh identity, so the previous system's entry survived every rebuild, sorted first, and made Limine stop at a Blake2b hash-mismatch warning once the UKI was rebuilt. Start limine.conf over from the shipped template (and drop foreign machine-id history directories on the ESP) before any post-reset rebuild: in the staged chroot rebuild, in the first-boot LUKS re-key, and — for unencrypted resets, where nothing else rebuilds — in a dedicated first-boot refresh when foreign entries are found. The staged rebuild also verifies every UKI hash referenced by limine.conf against the file on the ESP before the subvolume swap, and the running system's limine-snapper-sync is runtime-masked during staging so it cannot rewrite the config behind the rebuild. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Harden reset and first-boot setup failure paths Review findings from codex and Copilot: - Generate throwaway passphrases without a trailing head stage: under pipefail, SIGPIPE from the infinite tr failed the substitution and errexit aborted every encrypted reset before it could stage anything. - Stage the fallible parts of a degraded reset (LUKS re-key, boot rebuild) before arming the wipe, so a staging failure leaves the machine untouched instead of scheduling a wipe for a reset that never finished. - Gate first-boot setup on the factory wipe having succeeded (ConditionPathExists=!wipe-pending plus an in-script guard): creating the new user on a half-wiped system would hand their data to the wipe retry. - Abort the wipe (keeping its retry marker) when deleting the old root or recreating @home/@log fails, and abort resets that cannot remove a prior account — a surviving account keeps its password and wheel membership. - Resume a partially-created account on setup retry instead of rejecting the username the failed attempt just created. - Only purge machine-id directories the old limine.conf actually referenced; a shared ESP may hold other installations' boot artifacts. - Recreate the hibernation swapfile (nested subvolume, so never captured by the factory snapshot) inside the factory root before its UKI rebuild, so a reset machine keeps disk-backed swap and a valid resume offset. - Source base-test.sh in the OEM groups test per test conventions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Recreate the hibernation swapfile even when resume drop-ins survive omarchy-hibernation-setup short-circuits as 'already set up' when the resume mkinitcpio drop-in exists — which it always does in a factory root, while the swapfile itself never survives the snapshot (nested subvolume). Drop the marker when the swapfile is gone so setup reconfigures from scratch, and verify the swapfile actually exists before proceeding with the reset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Second review pass: encrypted-config coverage, factory-baseline sanitization, recoverable rekey Codex xhigh round 2: - Detect the LUKS backing device by walking the root's device tree, not only the cmdline cryptdevice=; reset/first-boot now re-key roots reached via rd.luks/crypttab too, instead of silently leaving the seller's slots valid. - Sanitize the retained @factory baseline (accounts, /etc/shadow, machine identity) during a full reset: the new wheel user could otherwise mount it to recover the seller's data, and a second reset would restore the account. - Re-key the disk recoverably: rebuild the no-auto-unlock UKI before killing the throwaway slot or destroying the staged key, and restore the keyfile if that rebuild fails, so a retry with a different password can never leave the disk locked to the first attempt's password. - Roll back a degraded reset's live-root auto-unlock material if its boot rebuild fails, instead of leaving it for a later rebuild to embed. - Treat a missing current-machine limine entry as stale so a retry after a failed rebuild repairs the config instead of clearing OEM state over it. - Erase fingerprint enrollments (/var/lib/fprint) in degraded wipes. - Remove the resume-offset drop-in too when recreating the factory swapfile, so the rebuilt UKI gets a correct offset. - Pin first-boot retries to the account the first attempt created. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Expose factory reset in the Setup menu Add a 'Reset Computer' entry under Setup (Omarchy's Settings menu, where OS factory resets conventionally live), guarded to btrfs roots and launched in a floating terminal. omarchy-reset-computer now self-elevates via sudo so the menu entry needs no sudo prefix, forwarding the caller's gum theme env as env arguments so styling survives an env_reset sudoers. The typed 'reset' confirmation and the sudo password prompt remain as the guards against accidental triggering. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Defer keyboard selection to first boot for OEM installs The OEM first-boot setup now runs a keyboard step before the user form, mirroring the ISO configurator: it loads the chosen layout on the live VT so the password (and the LUKS re-key that follows) are typed under it, and persists it with systemd-firstboot so the installed system gets both the console KEYMAP and the XKB layout Hyprland reads — exactly what a normal install writes. Layouts localectl doesn't know keep the default, same as the installer. This lets the OEM operator set nothing user-specific: the machine's owner picks their keyboard alongside their account at first boot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Rename factory-reset commands to omarchy-system-factory-reset[-finish] omarchy-reset-computer -> omarchy-system-factory-reset omarchy-factory-wipe -> omarchy-system-factory-reset-finish (and its systemd unit, log path, and temp mount to match) Pure rename: every reference — the Setup menu action, the first-boot finish service the reset stages and enables, the oem-setup ordering/gating, comments, and the menu test — moves together, with no behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Rename OEM vocabulary to provisioning (runtime) Commands unify under the provisioning family: omarchy-oem-setup → omarchy-provision-owner omarchy-finalize-user → omarchy-provision-user omarchy-first-run → omarchy-provision-first-run And the deferred-provisioning state/vocabulary replaces 'OEM': /var/lib/omarchy/oem/ → /var/lib/omarchy/provisioning/ /etc/omarchy/oem.key → /etc/omarchy/provisioning.key install/oem/ → install/provisioning/ OMARCHY_SETUP_CONTEXT=oem-firstboot → provision-owner omarchy-setup-system/-hardware --oem → --defer-provisioning All callers (provision-first-run→provision-user, autostart, factory-reset staging the provisioning units, the group-recording scripts) and comments move together. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Drop remaining OEM mentions from the provisioning groups test Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Finish the omarchy-first-run rename in the docs Two doc references to omarchy-first-run were missed when the script was renamed to omarchy-provision-first-run; update them to match. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9b03f15b4f |
Detect Elan match-on-chip fingerprint readers again (#6578)
* Detect Elan match-on-chip fingerprint readers again Elan readers report "ELAN:ARM-M4" as their product string, so the *fingerprint* and *biometric* checks miss them. Elan's 04f3 is also left out of the vendor list on purpose, because Elan makes touchscreens too. Both checks fail, so the machine looks like it has no reader. Add "elan:arm-m4" to the product string check. The comment above the vendor list already says the excluded vendors should still match there, so this makes that true. The vendor list and its has_kernel_driver guard are unchanged, and touchscreens still cannot cause a false positive. The string is a family name, not one device. libfprint uses it for 04f3:0c9c and 04f3:0ca7 as well as 04f3:0ca8. Tested on an HP EliteBook X G2i with 04f3:0ca8. * Point the Elan comment at the vendor list above it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Configure PAM only after a fingerprint enrolls and verifies Detection proves a reader is present, not that libfprint can drive it. Elan MOC sensors outside the elanmoc table pass the gate and then fail to enroll, which left pam_fprintd in the sudo and polkit stacks with no print to match. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: David Heinemeier Hansson <david@hey.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |