Commit Graph
13 Commits
Author SHA1 Message Date
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>
2026-08-10 22:49:44 +02:00
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>
2026-08-10 20:27:40 +02:00
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>
2026-08-09 21:54:21 +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
David Heinemeier HanssonandGitHub 9cb3640c9f Fix T2 Mac suspend and fan defaults (#6562)
* Fix T2 Mac suspend and fan defaults

* Avoid repeated T2 boot image rebuilds

* Harden T2 migration test matching
2026-08-07 11:15:28 +02:00
25a74f4e39 Stop Tuxedo backlight fix from aborting setup (#6583)
The orphaned module cleanup loop was the last statement in the script, so
when the glob matched nothing the trailing && list left an exit status of
1. run_logged propagates that, aborting hardware setup on every Tuxedo and
Slimbook install.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 11:14:30 +02:00
Ryan Hughes 38c1352f6d Fix t2 module 2026-07-28 21:07:50 -04:00
David Heinemeier Hansson a8977c4433 Only show linux-ptl kernel when that's installed so we don't end up with two options on XPS 2026-07-26 10:53:18 -07:00
David Heinemeier HanssonandClaude Opus 5 1756864129 Swap the Panther Lake kernel in before the camera DKMS modules
A fresh install on a Dell XPS 14 spent 73s in "Configuring system", and two
silent gaps in the install log account for 66s of it. Both are the same
redundant work.

hardware/all.sh ran ipu7-camera.sh before ptl-kernel.sh. intel-ipu7-camera
pulls in ipu7-drivers, vision-drivers and v4l2loopback, so DKMS compiled all
three against the stock linux kernel and built a UKI for it. ptl-kernel.sh
then installed linux-ptl, which recompiled the same three modules against the
new kernel, and its pacman -Rdd of linux followed with three dkms removes and
yet another UKI rebuild. The log shows four UKI builds where one would do, and
everything the first script produced was deleted 42 seconds after it was made.

Running the kernel swap first means the DKMS modules are only ever built
against the kernel the machine actually keeps, which returns roughly 25s.

ptl-kernel.sh still installs linux-ptl before removing linux, so one redundant
UKI build for the stock kernel remains. Removing first would save it but would
leave the machine with no kernel at all if the linux-ptl install failed, so the
safe order stays.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017q97Rsk8KwgaPUVq5ArLgM
2026-07-25 13:37:07 -07:00
David Heinemeier HanssonandClaude Opus 5 aa9f0c54c5 Add per-laptop speaker tunings, starting with the XPS 14
Laptop speakers ship voiced by the vendor's Windows DSP layer, which Linux does
not get. A tuning restores that as a PipeWire filter-chain in front of the
internal speaker sink, matched to the machine by DMI string and expected sink.

Adding a laptop is a directory under default/audio/tunings with two files and no
new code: matching is data. The XPS 14 DA14260 tuning included here was derived by
measuring the xps-audio-linux EasyEffects profile (MIT) and fitting a biquad chain
to it, so no impulse response or other upstream asset is redistributed. It measures
1.24 dB RMS against that reference, and matches its dynamic range within 0.1 LU --
the reference's multiband compressor turned out to contribute nothing, so a linear
chain replaces it. Bass Q is capped deliberately: a closer magnitude fit swung
group delay 31 ms across 63-80 Hz, which smears bass transients.

The graph runs as its own PipeWire client under its own config name rather than
loading into the audio daemon. The daemon only reads its config at startup, so a
daemon-loaded tuning could only be switched by restarting PipeWire -- which drops
every PulseAudio client's connection, and applications that do not reconnect
(Spotify) then have to be restarted by hand. Hosting it separately also contains
failure, since a malformed tuning breaks only that service.

Three things about the surrounding audio graph needed fixing for this to behave:

- Volume must live downstream of the tuning. omarchy-audio-output-sink is now the
  single definition of which sink an output's volume really uses, shared by the
  volume keys, the output switcher's OSD and the audio panel, so they cannot
  disagree. It resolves the current default output, which keeps it correct when
  headphones are selected while a tuning exists.
- The tuning's own output is a movable sink input, so rerouting "all streams" to a
  newly selected output would drag the processing onto headphones, or into the
  tuning's own sink, which is a cycle. It is pinned, and stream moves are limited
  to streams carrying an application.name.
- The physical sink a tuning fronts is not independently selectable, since picking
  it would only bypass the tuning, so it is kept out of the output list.

Applying happens at first-run, not finalize-user, because finalize-user also runs
in the ISO chroot where there is no audio server and nothing would retry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-24 18:25:02 -07:00
David Heinemeier HanssonandClaude Fable 5 804d138909 Widen sof-firmware install to all Intel SOF audio platforms
The sof-audio-pci-intel-* driver family needs sof-firmware to boot the
DSP, but mainline linux only optdeps it, so affected installs came up
with a Dummy Output sink only. Replace the Panther Lake check with
omarchy-hw-intel-sof, which detects any Intel PCI audio controller.

Ports ffa81021 from master, which adapts basecamp/omarchy#6200.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 12:31:25 -07:00
Ryan Hughes 34ddf5d54e Fix NetworkManager transition during upgrade 2026-06-21 02:25:42 -04:00
Ryan Hughes 75cb4f7195 Make setup ISO-only
Remove legacy online installer entrypoints, collapse migrations for 4.0, and move setup responsibilities into target-side system, hardware, and user commands.
2026-06-04 18:37:32 -04:00