Commit Graph
222 Commits
Author SHA1 Message Date
ZacharyZhang-NY e8715fc888 Keep the chooser's region when the summary shows the timezone's region 2026-10-09 00:08:43 -04:00
ZacharyZhang-NY 75d00a2036 Merge omacom/omarchy-iso#241: region from the timezone in one ISO
Drop our --region build, prepare-region.sh, region skel, the preselected
China timezone, the explicit region in the configurator's JSON, Rime
bundling and its pin.
2026-10-09 00:03:17 -04:00
ZacharyZhang-NY abe2d6a1b4 Merge upstream quattro: installer input methods (#237) and OPR priority (#240) 2026-10-08 23:50:48 -04:00
Kros DaiandClaude Opus 5.5 abc1a7716d 🐛 fix(install): reject non-string region overrides
- 🐛 a boolean or number in user_configuration.json crashed with a traceback instead of a configuration error
- ✅ pin regional packages inside the offline mirror mount window

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuATgjU4mnqpqSLAKMQ14g
2026-10-08 15:32:46 -04:00
Kros DaiandClaude Opus 5.5 e121561851 ✨ feat(install): pick the region from the timezone
- ✨ the timezone the user already chose selects the region, so no new prompt is needed
- 🔒 populate only the selected region's keys so global installs never trust them
- ✅ cover the mapping, autoinstall override, marker, keyring setup, and builder staging

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuATgjU4mnqpqSLAKMQ14g
2026-10-08 15:03:31 -04:00
David Heinemeier Hansson 595d38cb04 Carry installer input-method selection into the target (#237)
Install test / install (push) Failing after 13s
Unit tests / unit (push) Failing after 17s
* Carry the installer input-method choice into the target

* Support Archinstall 4.5 sanity checks

* Constrain VM keyboard choices to supported credential layouts

* Cover VM keyboard offsets and reject absent choices
2026-10-07 21:21:55 +02:00
ZacharyZhang-NY 0c75c60e19 Pick the requested region whatever the ISO preselects; one-line comments 2026-10-07 11:46:38 -04:00
ZacharyZhang-NY 02ee068504 Merge omacom/omarchy-iso#236 regional ISO builds, as an install-time choice
Every ISO bundles the cn profile; the first-screen pick decides. --region cn
only preselects China and names the artifact. The region value is cn, the cn
timezone is preselected when China is chosen, and the profile packages and
ArchLinuxCN keyring reach the target only for China.
2026-10-07 11:32:14 -04:00
ZacharyZhang-NY 22f0b5acfb Install every pinned local build so a later switch to China finds rime-ice-installer 2026-10-06 21:14:50 -04:00
Kros DaiandClaude Opus 5.5 d2e37df6a4 ✨ feat(region): preselect the profile timezone in setup
- ✨ regional ISOs pass their profile's timezone to the shared setup form, so an offline China install opens on `Asia/Shanghai`
- ✅ cover the payload copy and the configurator hand-off

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdXqTwm63rRMwotZ1xqLC8
2026-10-06 20:57:31 -04:00
Kros Dai 27b33e7f1c ✨ feat(install): seed regional user defaults
- ✨ support offline input for immediate and deferred owners
- 🔒 initialize target trust before enabling regional repositories

Amp-Thread-ID: T-01a0df38-0ea7-759b-bcff-29da223cc567
2026-10-06 20:57:29 -04:00
ZacharyZhang-NY 02c803cb40 Send Global back to the VT, keep a US keyboard in the CJK console and translate the media diagnosis 2026-10-06 18:44:49 -04:00
ZacharyZhang-NY 48471b25c1 Rerun China installs in a console that draws Chinese and show the installer in Chinese 2026-10-06 18:34:35 -04:00
ZacharyZhang-NY 45b31ae98c Abort transactions that would replace the pinned local builds and stage the refresh hook for new users 2026-10-06 09:01:09 -04:00
ZacharyZhang-NY 947a69ba91 Pin a local-source install's locally built packages so updates keep them 2026-10-06 08:48:14 -04:00
Tommy Back 8f3bdc1fb9 Stop passing offline to Installer.sanity_check
archinstall 4.5 only accepts skip_ntp and skip_wkd. offline used to
skip the reflector wait, and that wait is gone.
2026-10-06 01:24:01 -04:00
ZacharyZhang-NY 22d02f0069 Default the region to Global so deferring before choosing still installs 2026-10-05 23:25:57 -04:00
ZacharyZhang-NY c1d5725837 Ask for the region on the first screen and hand it to target setup 2026-10-05 23:20:36 -04:00
Ryan Hughes 9373d0e139 Draw the Omarchy icon as installation progresses 2026-09-23 23:18:10 -04:00
Ryan Hughes 1daf1202d4 Install and validate matching kernel headers in the ISO 2026-09-15 00:06:40 -04:00
Ryan Hughes b507a20b7e Install linux-omarchy by default except on T2 Macs 2026-09-14 18:45:34 -04:00
David Heinemeier Hansson 2673c613d9 Update the tagline to Beautiful, Fun & Agentic (#144)
Matches omarchy.org and the omarchy repo. The centring math keys off the string length, so the shorter line stays centred.
2026-09-01 11:54:27 +02:00
David Heinemeier Hansson 3559b2bc9d Change the greeter tagline to "Beautiful, Fun & Opinionated"
The same frame opens omarchy's first boot from its own copy of the string, so both repositories change together or an install and the deferred first boot after it bookend the setup with different words. Nothing generates one copy from the other: the vendoring between the repositories covers the setup form, not the greeter.

The line is centred from its own length, so the three characters it loses move it one column and nothing else.
2026-08-30 14:33:48 +02:00
OmarchybotandCodex XHigh 268bac16d3 Name the install medium when pacstrap rejects a package off it (#120)
* Name the install medium when pacstrap rejects a package off it

A failed install showed "Omarchy installation stopped", the pacstrap error, a log tail, and a link to Discord. Somewhere in that tail was the whole answer -- pacman rejecting a package as corrupt -- and nothing on the screen connected it to the USB stick it came off.

Nothing else can put it there. The offline mirror is bind-mounted onto the target's package cache for the duration of pacstrap, so a package rejected under that path was read straight off the medium; nothing else ever writes there. That is worth saying in the words that name the fix rather than leaving it as a package name nobody recognises.

Which way the medium failed is a separate question, and usually unanswerable after the fact: pacstrap runs with --noconfirm, which answers yes to pacman's "delete it?", so the evidence is unlinked through the bind mount before anything can weigh it. The usual verdict therefore names the medium and gives both remedies in the order worth trying -- verify the download first, because it is cheap and rules out the common case. When the package does survive, the sha256 repo-add recorded at build time separates a bad write from a bad read, and the wording follows.

Co-Authored-By: Codex XHigh <codex@openai.com>

* Keep the media diagnosis whole and print it once

Two things only visible by looking at the rendered screen.

The diagnosis was recorded in the support log before the failure screen was drawn, and the log tail on that screen reads the same file -- so the verdict appeared twice, once as the banner and again eight lines below it. The same finding printed twice reads as two findings. It is recorded after the render now, and the tail carries the installer's own output the way it did before.

The package name shared a line with the sentence explaining it, and the screen is only as wide as the logo. npth's name already pushed the mismatch wording past the edge, and a firmware package would have taken the whole sentence with it. Every verdict puts the name on a line of its own, and the test walks all three with a sixty-three character package name to keep them there.

Co-Authored-By: Codex XHigh <codex@openai.com>

* Break the media diagnosis between what happened and what to do

The package name and the remedy beneath it ran together as one block, so the line telling someone to verify their download read as another clause about the package rather than the first thing to go and do. A blank line separates them in all three verdicts.

The renderer dropped blank lines, on the assumption that any it saw were slack rather than shape, so the break is drawn now instead of skipped.

* Give the log tail back the rows the diagnosis spends

The tail sizes itself from the terminal height less a constant for everything drawn above it, and the diagnosis is seven rows that constant never knew about. On a forty-row console the failure screen came to forty-one and scrolled its own logo off the top. The tail is the part that should give way, so it is told what was spent above it.

The test that came with this first asserted the logo was still on screen, which it cannot: it reads the output stream, and the stream keeps every row ever emitted whether or not it stayed visible. It passed with the fix reverted. It checks the mechanism instead -- a tail that reaches fewer lines of the log when a diagnosis is present -- against two logs identical line for line but for the one path that decides whether there is a diagnosis at all.

---------

Co-authored-by: Codex XHigh <codex@openai.com>
2026-08-23 21:20:13 +02:00
OmarchybotandCodex XHigh df80b320cb Stop the install dying on non-UTF-8 command output (#118)
* Stop the install dying on non-UTF-8 command output

efibootmgr prints what firmware gave it, and an HP ProBook's NVRAM held a legacy BBS entry whose device path was raw binary. `_read_efibootmgr` captured that with `text=True`, so Python decoded it strictly and a byte in a boot entry nothing here reads killed the whole "Installing Arch + Omarchy" phase. Two machines have hit it now, on 0x80 and 0xe0.

Every text capture in the orchestrator had the same exposure — partition labels reach lsblk and blkid the same way firmware strings reach efibootmgr — so they all go through one `capture()` that replaces what it cannot decode. The file reads here already did this; the command reads were the gap.

Replacing bytes has its own failure mode, and it is worse than the crash: a mangled UUID would be written into fstab and the kernel cmdline, and `_validate_pre_mounted_filesystems` reads it back from the same command, agrees with itself, and ships a machine that cannot find its root. So `require_text()` refuses a value the installed system depends on — UUIDs, device names, the btrfs device handed to mount — while the boot-entry labels stay tolerant on purpose: the decisions taken from those are advisory, and refusing a junk one would abort the install this change exists to let finish.

The regression test runs a stand-in efibootmgr emitting the reported byte sequence, so it exercises the decode rather than a mock. test_keyboard.py loads the module through the package now that keyboard.py imports a sibling.

Closes #116

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

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Say accurately why the boot-entry parsing stays tolerant

The previous commit called the decisions taken from a firmware label "advisory". They are not: a label match selects entries for deletion and rewrites the boot order, and a missing one aborts validation. Staying tolerant is still right, for a reason the comment did not give — an entry's text is its label and its device path together, the junk lives in the path, and nothing mangled reaches efibootmgr from there anyway, since only four-digit ASCII boot numbers become arguments and a boot order is filtered through the entries that parsed. A label that loses a byte costs a stale entry left behind, not a broken system.

The test docstring credited one machine with a fixture that carries the bytes from two.

Co-Authored-By: Codex XHigh <noreply@openai.com>

---------

Co-authored-by: Codex XHigh <noreply@openai.com>
2026-08-23 12:57:03 +02:00
Ryan Hughes 7d3b01eae9 Fix partition issue 2026-08-13 23:57:22 -04:00
David Heinemeier HanssonandClaude Opus 5 7c50d72f40 Keep offering the deferred install after backing out (#107)
* Keep offering the deferred install after backing out

The keyboard screen only advertised Ctrl+C on its first showing. Backing out
of the user step — Esc from the account form, or "No, change it" on the review
table — returned to the same screen with the hint gone and Ctrl+C back to
aborting the install, so an operator who started a direct install could no
longer switch to preparing the machine for another owner.

Offer it on every visit instead. Nothing has touched the disk at that point in
either path, and the confirmation screen still catches a stray Ctrl+C. The user
step now returns as soon as the keyboard screen arms deferral, and the main
flow re-checks the flag before choosing a branch, so a late deferral clears the
half-entered credentials and goes straight to disk selection.

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

* Trim the comments around the deferral check

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 20:55:07 +02:00
David Heinemeier HanssonandClaude Opus 5 d6cd2d30a6 Call the renamed omarchy-apply-system finalizer
The runtime moved its install-time plumbing out of the setup namespace, where
it sat among the interactive wizards a user browses to. The ISO installs the
runtime from the mirror it ships with, so the two move together and no
compatibility route is needed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 14:15:24 -07:00
David Heinemeier HanssonandClaude Opus 5 be618cc73b Send the installer off with laseretch again
Smoke read as a puff of glyphs rather than a finish. Go back to laseretch at the
260fps it ran at before the ttfx swap, where it takes about four seconds on this
logo — still well inside the 8s cap, which is a stuck-process guard rather than
a trim.

Hold it to the 256-index palette with --xterm-colors, the way the greeter holds
its own: the framebuffer console reduces indexed codes well and truecolor ones
badly, and the laser and sparks carry their hot palette through the reduction
intact. Settle on indexed 2 so the effect ends on the same green gum drew the
static logo in a few lines above, instead of recoloring a logo that is already
on screen.

Log a failing effect to the support log rather than dropping it. ttfx is in the
ISO's package list so this should not fire there, but a silent `|| true` is why
a missing binary looks like a still logo and no reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 13:51:31 -07:00
David Heinemeier HanssonandClaude Opus 5 a6a442b9a0 Center the installer logo in a locale without a charset
logo_width measured the logo with awk's `length`, which counts bytes unless the
locale is UTF-8. The logo is drawn in box-drawing glyphs, three bytes each, so
the answer was 197 columns rather than 81 anywhere LANG went unset — a console
booting before locale.conf applies, a shell that never inherited one.

A width wider than the terminal makes left_padding clamp its pad to zero, so the
logo sat flush left while the title, bar, and tip centered against a phantom
197-wide box and drifted right of it. The 81 this function already falls back to
when logo.txt is missing is the character count, so the intent was never in
question, only the measurement.

Count bytes deliberately under LC_ALL=C and drop the UTF-8 continuation bytes
before measuring. That is the same answer in every locale rather than the right
one only in those that have a charset.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 13:51:24 -07:00
David Heinemeier HanssonandClaude Opus 5 e5f2b466ef Center the installer screens and move the effects to ttfx
The progress screen and the finish screen now center vertically the way the
greeter does, instead of stacking from row one. Both derive their block height
from the logo, so the frame the installer sits on for several minutes is
composed rather than top-aligned. The progress screen redraws itself if the
console resizes, which the framebuffer still does late in VMs.

Centering the finish screen turned up a spacing bug: gum confirm paints
prompt/blank/button from the cursor and clears all three rows, so anchoring one
row below the title left three blank rows under it, not one. Anchoring a row
higher closes that to the two rows gum allows, one more than the greeter's frame.

Swap python-terminaltexteffects for ttfx. The flags carry over, but two things
do not. ttfx resolves even indexed stops to truecolor, so the greeter needs
--xterm-colors to keep its palette indexed: the console reduces 256-colour codes
well and 24-bit ones badly, and the settle colour has to match the logo drawn
underneath with gum style --foreground 2. And ttfx centres its text two columns
right of plain centering, so both screens hand it a canvas two columns narrower
than the console or the logo jumps sideways when the effect starts.

The finish screen uses smoke rather than laseretch, at 60fps rather than 260 —
smoke finishes in under a second at the rate laseretch was tuned for, which
reads as a flicker instead of a send-off. Smoke also survives the console's
16-colour collapse better, carrying its motion in glyphs rather than gradients.

ttfx is in the ISO's package list and timeout is coreutils, so neither needs an
availability check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 09:19:44 -07:00
a12bfea7a8 Stop the live installer crashing the kernel on pre-T2 Macs
The ISO boots linux-t2 on every machine. On a MacBookPro11,4 applesmc finds the
SMC and then takes a NULL pointer dereference registering the keyboard-backlight
LED, in led_trigger_set_default -> queue_work_on.

The udev worker dies mid-init holding locks and the damage spreads: reading the
half-registered LED blocks systemd-backlight, opening /dev/rfkill blocks iwd
while it holds the global misc_mtx, and every later misc device open blocks
behind that mutex. LVM opens one, so the install stops at "Cleaning up existing
holders on install disk" and never resumes — no error, no progress, nothing to
see.

Nothing in the installer needs the SMC. It reads sensors and drives the fans and
keyboard backlight, and the firmware manages the fans by itself; the installed
system still loads applesmc normally. Blacklisting it in the live environment
only, alongside the Panther Lake audio blacklist that exists for the same class
of problem.

Extracted from #102, which also carried an unrelated rework of the disk probes.

Co-Authored-By: cupatea <vladyslav@hey.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 04:49:50 -07:00
David Heinemeier HanssonandClaude Opus 5 8b3dcc68b2 Tell BitLocker users to decrypt, not suspend (#105)
The free-space install detects BitLocker by the -FVE-FS- signature at
offset 3, and suspending BitLocker leaves that signature in place: the
volume stays encrypted, it just gains a clear-key protector. So the old
message sent users to do something that could never let the install
proceed. Aborting on a suspended volume is deliberate, so only the
wording needed fixing.

Fixes #99

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 13:48:20 +02:00
David Heinemeier HanssonandClaude Opus 5 22eb81ebf5 Correct two misleading messages from the setup-form split
The build's missing-form error only ever described the published-package
case, so a --local-source build against a checkout without the form told
you to publish a runtime or pass --local-source — advice you had already
taken. Branch on /omarchy-source and name the checkout instead.

The password comment has claimed yescrypt since 880d8c5 swapped the
command to `openssl passwd -6`, which is SHA-512 crypt. openssl passwd
cannot produce yescrypt at all, so the comment, not the command, is what
was wrong.

Both spotted by Copilot's review on #104.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 02:23:30 -07:00
David Heinemeier HanssonandClaude Opus 5 8858ac2544 Source the setup form from the Omarchy runtime (#104)
The keyboard layout list, the account and hostname validation rules, and the
gum prompts that ask for them existed here and again in the runtime's
first-boot owner setup, with nothing keeping the copies honest. They had
already drifted: dropping two layouts from this list moved English (US) from
the top of one gum choose page to the bottom of the previous one, leaving the
preselected default pinned under a screenful of layouts.

The form now lives in the runtime at install/provisioning/setup-form.sh, and
build-iso.sh vendors it out of the very runtime package this ISO bundles —
the same two-branch machinery already used for omarchy-base.packages, so a
--local-source build takes it from the mounted checkout and everything else
extracts it from the downloaded package. An ISO therefore ships the form
belonging to the runtime it installs.

That file is extracted on its own rather than alongside the package lists:
bsdtar exits non-zero for a member it cannot find, so bundling the request
would abort the build under set -e with a bare "Not found in archive" instead
of the actionable error about publishing a runtime that carries the form.

Cancel handling is unified with first-boot setup, which is what made the
prompts shareable. Every prompt reports 0 (answered), 1 (Esc), or 130
(Ctrl+C). Esc now unwinds to the start of the form instead of ending the
install — it previously hit the same `|| abort` as Ctrl+C. Ctrl+C keeps both
of its hidden affordances: arming deferred provisioning on the first keyboard
screen, and toggling unencrypted on the disk screens.

test_keyboard.py reads the layout list from a runtime checkout now, and skips
its localectl coverage rather than failing when there is none to read.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 11:20:48 +02:00
David Heinemeier HanssonandClaude 6b2f8a6ac7 Add deferred-provisioning install mode and factory snapshots (#98)
* Add OEM install mode and factory snapshots

OEM mode installs the entire system but defers user creation to first boot,
where the runtime's omarchy-oem-setup asks for the owner and lands them in the
already-installed system — and the same machinery arms omarchy-reset-computer.

The configurator's install-mode step gains an OEM choice, which required
moving the user step after mode selection (OEM skips it entirely). Autoinstall
rigs select OEM with an `oem` marker file on the cidata drive, which also
replaces the user_credentials.json requirement.

In the orchestrator, OEM swaps the user phases for a stage_oem_state phase:
it stashes the bundled Node tarball (every install, for offline factory
resets), arms omarchy-oem-setup.service in the target, and on encrypted
installs stages the throwaway LUKS passphrase — generated by InstallContext
when the input carries none — with a keyfile embedded in the UKI so boot
auto-unlocks until first-boot setup re-keys to the owner's password. The
install fails with a clear error if the bundled runtime lacks OEM support.

Every install now also ends with a read-only @factory snapshot of @ at the
btrfs top level — outside snapper's reach — which is what makes
omarchy-reset-computer a true factory reset.

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

* Offer OEM mode only when the bundled runtime supports it

The configurator now peeks into the offline mirror's runtime package for
omarchy-oem-setup before offering the OEM install mode, so an ISO built
against a pre-OEM runtime can't start an install doomed to fail at the
staging phase (which still hard-checks as the backstop). Blank disks with no
OEM choice to offer skip the mode picker entirely, like the pre-OEM flow.

Also keep the test harness QMP socket in /tmp — unix socket paths cap at
~108 bytes, which nested test-run directories can exceed.

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

* Isolate the test harness SSH identity from the local ssh-agent

A loaded agent offers its keys before the harness identity and can exhaust
the server's MaxAuthTries before the right key is ever tried; IdentitiesOnly
pins authentication to the per-run key.

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

* Make OEM state world-readable for user-context finalization

The Node tarball in /var/lib/omarchy/oem/packages is read by
omarchy-finalize-user running as the freshly created user; a 0700 directory
forced the offline install onto its network fallback. The only secret inside
(luks-key) is itself 0600 root.

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

* Keep provisioning credentials out of OEM installs and factory snapshots

Review findings from codex and Copilot:

- A rig-supplied credentials file in OEM mode now contributes only its LUKS
  passphrase: preserved users/root_enc_password fields would have created a
  hidden provisioning account despite OEM promising no user until first boot.
- The factory snapshot is taken writable, scrubbed of deployment credentials
  (staged authorized_keys, tailscale auth key and join unit, the dead OEM
  LUKS key and its initramfs drop-ins), then set read-only — a reset years
  later must not authorize the original deployment key or rejoin its tailnet.
- The missing-Node-tarball case is a hard install error on every install,
  not just OEM: the stash is what lets a factory reset finalize offline.
- The configurator's OEM gate checks the runtime package for the service
  unit as well as the setup binary.
- The test harness handles the deliberately skipped mode picker on blank
  disks when the bundled runtime lacks OEM support.

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

* Second review pass: strip account fields, clear stale cidata inputs, await OEM finalization

Codex xhigh round 2:

- Strip account authentication fields (!users, !root-password,
  root_enc_password, auth_config account entries) from the archinstall config
  in OEM mode, not just user_credentials — a combined/legacy config could
  otherwise have archinstall create a provisioning account before OOBE.
- Clear stale OEM inputs (oem marker, credentials, optional files) from /root
  before copying a cidata drive, so a retried install in the same live session
  can't force a fresh drive back into OEM mode or reuse an old LUKS passphrase.
- The test harness waits for the OEM pending flag to clear before saving the
  base image, so it never snapshots a half-finalized first boot; it also
  handles the deliberately skipped mode picker on blank disks built against a
  pre-OEM runtime.

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

* Reorder configurator: disk before keyboard, skip keyboard for OEM

The wizard now opens on disk selection; keyboard moves to after the install
mode is chosen and stays before the user step (so the LUKS passphrase is
still typed under the chosen layout). OEM installs skip keyboard and user
entirely — the machine's owner picks both at first boot — so the emitted
config just carries a default 'us' keymap as a placeholder.

The QEMU harness drives the new order and the OEM first-boot keyboard step.

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

* Harness: prove the OEM keyboard step applies a non-default layout

Drive the OEM first-boot keyboard picker to English (UK) and assert the
installed system ends up with KEYMAP=uk and XKBLAYOUT=gb — proving the
deferred keyboard step actually applied a layout, not just rendered. UK keeps
every letter identical to US so the raw-keycode password still matches, and
the email's '@' (Shift+' under UK, not Shift+2) is typed with the UK keycode.
The console bootstrap now fetches-then-runs instead of piping, so it survives
the non-US layout the keyboard step installs.

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

* Update OEM comments to the renamed factory-reset command

Reference omarchy-system-factory-reset (was omarchy-reset-computer) in the
orchestrator comments and the plan.

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

* Drop the configurator's runtime-support gate for OEM mode

Remove oem_supported() and its branches: the configurator always offers OEM
install now, rather than scanning the offline mirror's runtime package to
decide. Building an ISO against a runtime that ships OEM support is the
build's responsibility; the orchestrator still fails an OEM install with a
clear error if the staged target somehow lacks omarchy-oem-setup.

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

* OEM mode via Ctrl+C on the first screen, not a mode-picker entry

Restore the original screen order (keyboard first, then disk) and make Ctrl+C
on the keyboard screen the entry into OEM mode — the same hidden-Ctrl+C idiom
the disk-overwrite screen already uses for unencrypted installs. A confirm
guards against an accidental Ctrl+C; once confirmed, OEM jumps straight to
disk selection and the overwrite confirmation (no keyboard, no user), since
OEM is always a full-disk install. The 'OEM install' entry leaves the mode
picker. The harness drives the new path.

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

* Restore keyboard→user→disk order; drop OEM wording from the configurator UI

The normal install flow returns to its original order: keyboard, then the
user step, then disk selection and mode. The Ctrl+C shortcut on the first
screen and its confirmation now speak only of preparing the machine for
another owner — no 'OEM' in any user-facing string (the omarchy_install.oem
data contract is unchanged).

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

* Only show the mode picker when free space is also an option

Restore the pre-OEM behavior: a disk with no unallocated free space has just
one viable mode, so the wizard skips the mode picker and goes straight to the
overwrite confirmation. The picker appears only when a free-space install is
also on offer.

Also give the harness's first-boot SSH bootstrap more headroom (360s): an
encrypted OEM first boot re-keys LUKS and rebuilds the UKI before sshd
settles, which can exceed 180s under parallel VM load.

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

* Tighten the prepare-for-another-owner confirm wording

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

* Keyboard screen: Ctrl+C hint directly under the header, no blank line

Match the disk-overwrite screen, where the unencrypted-install hint sits on
the line right below its message.

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

* Rename OEM vocabulary to provisioning (ISO)

Matches the runtime rename. The deferred-provisioning install mode replaces
'OEM' everywhere:
  omarchy_install.oem            → omarchy_install.defer_provisioning
  --oem / --oem-file             → --defer-provisioning / --defer-provisioning-file
  OMARCHY_INSTALL_OEM_FILE       → OMARCHY_INSTALL_DEFER_PROVISIONING_FILE
  cidata 'oem' marker            → 'defer-provisioning'
  /var/lib/omarchy/oem/          → /var/lib/omarchy/provisioning/
  /etc/omarchy/oem.key           → /etc/omarchy/provisioning.key
  install/oem/omarchy-oem-setup.service → install/provisioning/omarchy-provision-owner.service
  stage_oem_state phase          → stage_provisioning_state ('Staging provisioning')

Orchestrator internals (OEM_STATE_DIR, _validate_oem_state, ctx.oem, …),
the configurator, cidata-load, the dashboard phase band, the harness
(--oem → --provision), and the tests all move together.

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

* Fix orchestrator caller of the renamed user-provisioning command

run_chroot_finalizer still invoked /usr/bin/omarchy-finalize-user, which the
rename moved to omarchy-provision-user — so a normal install died with 'No
such file'. (Deferred-provisioning installs skip this phase, so only normal
installs were affected.)

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

* Wait for the live VT to settle before drawing the first screen

tty1 boots as an 80-column console and only widens once the kernel
framebuffer/KMS takes over, which lands late — especially in VMs. The
configurator's logo is wider than 80 columns, so the very first draw
measured against that transient narrow console: the logo wrapped
("smushed") and, with left padding clamped to zero, hugged the left
edge. Every later screen re-measures once the console has grown, which
is why only the opening keyboard screen looked wrong.

Hold the first draw until the width settles at or above the logo width,
capped so a console that never widens still proceeds. Terminal emulators
(dry runs) are already at final size and are skipped.

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

* Confirm the Start Omarchy Now prompt after provisioning setup (#100)

* Confirm the Start Omarchy Now prompt after provisioning setup

First-boot account setup now ends on a celebration screen whose Start
Omarchy Now button gates the SDDM handoff, so the provisioning test
must press it — same as the installer's Reboot Now prompt — once the
pending flag clears.

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

* Add a centered greeter before the installer keyboard step

Open the ISO configurator with the same greeter the deferred-provisioning
first boot uses — vertically centered logo, the Omarchy tagline, and a
single button — so install and first boot bookend setup identically. The
installer's button reads "Start install"; first boot's reads "Start setup".

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

* Animate the installer greeter logo with a skippable ColorShift

Match the first-boot greeter: a looping tte ColorShift over the centered
logo with a "Press Return to Start Install" hint instead of a button.
Return begins the install at any time.

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

* Theme the installer greeter ColorShift to the Omarchy palette

Match the first-boot greeter: sweep the logo ColorShift through the
Tokyo Night accent colors instead of tte's default rainbow.

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

* Fix installer greeter ColorShift: green base, no flash, clean handoff

Mirror the first-boot greeter fixes: indexed ANSI colors (green base with
a cyan accent) because the framebuffer console can't render tte truecolor;
one long tte invocation so the loop never flashes; stty sane + clear after
Return so the keyboard step starts clean.

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

* Dismiss the greeter in the QEMU test drivers

The installer and first-boot flows now open with a greeter (animated logo
+ tagline). Wait for the tagline and press Return before the keyboard
step, in both drive paths, so the harness gets past the greeter.

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

* Kill the installer greeter animation cleanly

Run tte directly instead of inside a backgrounded `while` subshell, so
killing the tracked PID actually stops the effect. Killing the subshell
orphaned tte, which kept painting the logo over the keyboard step.

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

* Guard the greeter wait against set -e in the configurator too

Harmless today (no set -e), but keeps the greeter's animation teardown
robust if that changes.

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

* Also settle the terminal in emulators, not just real VTs

wait_for_stable_terminal skipped terminal emulators on the assumption
they're already at final size, but a fresh terminal (or sudo's pty)
reports a stale 24x80 for a moment at startup too. Drop the VT-only gate
so the greeter centers there as well.

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

* Test harness: drive the first-boot timezone step and direct SDDM handoff

Match the deferred-provisioning first-boot flow changes: the owner now picks
a timezone after the email step, and setup hands straight off to SDDM instead
of pausing on a celebration screen with a Start Omarchy Now button. Drive the
new Timezone screen (accept the geo-guessed default) and drop the button
confirmation.

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

* Don't bake hostname/timezone into deferred installs; set them at first boot

Deferred provisioning defers the user step to first boot, so the machine's
owner should name the machine and pick the timezone there — not inherit values
chosen at install time. Stop setting hostname/timezone in the configurator's
deferred branch: the orchestrator already skips an empty timezone (the system
stays UTC until first boot), and it now falls back to a neutral "archlinux"
hostname for the install so archinstall still succeeds. The first-boot
omarchy-provision-owner form collects and applies both.

Harness: drive the new Hostname screen and assert both hostname and timezone
actually landed on the running system after first boot.

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

* Match the installer greeter: solid green logo, no color animation

Same fix as the first-boot greeter — tte's ColorShift judders on the 16-color
framebuffer console (truecolor crush). Show the logo in solid green and drop
the animation so both greeters look identical and flash-free.

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

* Harden the provision harness: SSH-blip wait, timezone timeout, hostname assert

- The pending-flag wait treated an SSH connection failure (rc 255) the same as
  "flag absent" (rc 1), so a transient blip during finalization would exit early
  and snapshot the base mid-finalization. Only break on a clean rc 1; keep
  waiting on 0 or 255.
- Bump the first-boot Timezone screen wait 60s -> 180s (tzupdate geo-guess over
  first-boot networking can lag; matches the installer's headroom).
- Make the applied-hostname check a hard failure (it's deterministic — we typed
  it), so a first-boot apply regression can't pass green; timezone stays a
  report since it's geo-guessed.

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

* Seed hostname=omarchy/timezone=UTC in deferred install instead of empties

Write neutral valid defaults in the configurator's deferred branch rather than
empty strings, and drop the orchestrator's `config.hostname or "archlinux"`
fallback (hostname is always non-empty now). The first-boot wizard overwrites
both. This keeps the "owner sets them at first boot" behavior without depending
on archinstall tolerating empty hostname/timezone at config-load time.

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

* Fix set -e regression in the provision pending-wait loop

The previous hardening moved `ssh_guest "test -f pending"` out of the `while`
condition into a standalone command, but the harness runs `set -euo pipefail` —
so the moment the flag cleared (ssh returns 1, the intended "done" signal) it
tripped errexit and aborted the whole run right after provisioning, before the
session/acceptance phase. Capture the result with `|| rc=$?` so the non-zero is
the signal, not a fatal error. Also guard the follow-on hostname/timezone
substitutions with `|| got=""` so a transient SSH failure there can't abort
either (an empty value then correctly fails the hostname assertion).

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

* Bring back the installer greeter ColorShift animation

Match the first-boot greeter: restore the static green logo + looping tte
ColorShift overlay. The framebuffer truecolor crush gives it a quick flash,
which is acceptable — both greeters keep the animation.

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

* Skip the install celebration on deferred installs; reboot directly

A deferred-provisioning install has nothing to celebrate yet — the owner
finishes setup (and gets the send-off) at first boot. So the installer's
"Installed Omarchy in ..." screen and the Reboot Now prompt now belong to a
direct install only: for a deferred install the dashboard clears and reboots on
its own. .automated_script.sh detects deferred mode (the config's
defer_provisioning flag, or the cidata defer-provisioning marker) and passes
OMARCHY_UI_DEFER_PROVISIONING to the dashboard.

Harness: a deferred install no longer shows Reboot Now, so wait_for_install
recognizes the auto-reboot by the first-boot greeter tagline appearing.

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

* Detect deferred mode with jq, not a line regex

The dashboard's deferred detection grepped user_configuration.json for the
defer_provisioning boolean on one line; a reformatted config would read as a
direct install and wrongly show the celebration. Parse it with jq using the
same path the orchestrator reads (.omarchy_install.defer_provisioning).

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

* Drop Bosnian and Khmer from the installer keyboard picker

Match the first-boot picker: neither has a console keymap, so both silently
fell back to US. Remove them rather than offer a layout that can't apply.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 21:56:55 +02:00
David Heinemeier HanssonandClaude Fable 5 0339b13d12 Join the tailnet from a cidata tailscale_authkey (#97)
* Join the tailnet from a cidata tailscale_authkey

A tailscale_authkey file on the autoinstall drive gets the installed
machine onto the tailnet on its own: the tailscale package is bundled in
the offline mirror and installed only when the key is present, the key is
staged root-only at /etc/tailscale/authkey, and a oneshot first-boot unit
runs tailscale up once the network is really there. On success the key is
deleted and the unit disables itself; on failure both survive to retry on
the next boot, so an offline install joins whenever it first gets
connectivity. ufw allows traffic in on tailscale0 via the same
write-the-rule-then-ignore-the-chroot-exit-code dance as the SSH phase.

Bundling rather than fetching at first boot is deliberate: pacman -Sy
tailscale on a fresh machine is a partial upgrade, and pacman -Syu means
an unattended full system upgrade racing the user's first login. The
offline mirror is a coherent snapshot; installing from it keeps the
target consistent and the install offline-capable. See plans/tailscale.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xc74h4WSYpuYPGuYbFAR1q

* Do not hold boot hostage for the tailnet join

Review caught that the join unit was Type=oneshot wanted by
multi-user.target: target units implicitly gain After= for their Wants=,
so an offline machine's boot -- SDDM included -- stalled for the full
10-minute timeout, and once timed out the join never retried within that
boot. Type=simple counts as started the moment it forks, so boot proceeds
while the join retries in the background for as long as the boot lasts.
Cleanup moves inside the script, sequenced after tailscale up succeeds,
which preserves the retry-across-boots behavior: key and unit survive a
boot with no connectivity, and the key never outlives a successful join.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xc74h4WSYpuYPGuYbFAR1q

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 15:10:30 +02:00
David Heinemeier HanssonandClaude Fable 5 f11a70c6ba Take authorized_keys on the cidata drive instead of ssh.json
The JSON wrapper was the one piece of autoinstall input that wasn't an
existing format: the other files are the configurator's own output, and
the keys end up in sshd's authorized_keys anyway. So take that file
directly -- one key per line, comments and key options work as they do
everywhere else -- and drop the parsing in between. The only thing JSON
bought was parse-error detection, and the phase still fails loudly when
the file carries no usable keys.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 06:08:33 -07:00
David Heinemeier HanssonandClaude Fable 5 dd5c53cd3c Trust the target to have ufw when opening SSH access
ufw is part of Omarchy's default package set, so the presence check was
guarding a machine that cannot exist -- and if it somehow did, skipping
the firewall would produce an unreachable install, which is exactly what
this phase fails loudly to prevent. The user.rules assertion already
raises in that case. With the check gone, the helper is small enough to
live inline in the phase.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 05:56:33 -07:00
David Heinemeier HanssonandClaude Fable 5 e8a7e3cfce Extract cidata loading into a standalone omarchy-cidata-load command
The loader now settles udev before probing, so a fast boot cannot miss
the drive and silently drop a headless install into the wizard. All the
files come over in a single cp, and a failed copy removes anything it
already copied and falls back to the wizard: the wizard cleans up only
what it writes itself, so a half-copied ssh.json would otherwise leak
into the interactive install that follows. The command takes an optional
path prefix so tests can run it against a sandbox instead of the live
system.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-04 05:56:33 -07:00
Jeffrey Guenther 5f601f2c54 Make the installed machine reachable over SSH
An autoinstall drive can carry an ssh.json of public keys. Install them
for the user, enable sshd, and open port 22.

All three are needed: Omarchy ships openssh with the service disabled,
and install/config/firewall.sh opens LocalSend and docker DNS but not
22, so keys alone leave the machine pingable with SSH timing out.

The ufw call exits non-zero from a chroot -- it cannot reach netfilter
-- but writes the rule to user.rules before failing, and that file is
what ufw.service loads on first boot. Check the file rather than the
exit status, since the file is the thing that has to be right.

Unusable key files fail the phase. A machine that installs
"successfully" and then can't be logged into is worse than one that
stops with the reason on screen.

Claude-Session: https://claude.ai/code/session_01Sy3HeoBqb13ahLuaiP2UkM
2026-08-04 05:54:00 -07:00
Jeffrey Guenther b323a4cf08 Skip the dashboard's prompts when nobody is at the keyboard
The dashboard owns the two prompts that outlive an install -- the reboot
confirm and the failure menu -- and both read from the TTY, so both hang
forever on a headless VM. Gate them on OMARCHY_UI_INTERACTIVE here, in
the one process that owns the visible UI, rather than patching prompts
out of the scripts they live in.

The existing OMARCHY_UI_AUTO_REBOOT check still guards the reboot
itself, so setting both stops on the finish screen without a keyboard.

Claude-Session: https://claude.ai/code/session_01Sy3HeoBqb13ahLuaiP2UkM
2026-08-04 05:54:00 -07:00
Jeffrey Guenther a4ec97d174 Autoinstall from a cidata drive instead of the configurator
Look for a drive labeled cidata at startup. If it carries the
configurator's own output files, copy them into /root and skip the
wizard -- everything downstream then runs the ordinary path against
ordinary inputs. No drive, no change.

The required pair matches what the orchestrator treats as mandatory, so
a drive with less than that falls back to the wizard rather than failing
the boot.

Claude-Session: https://claude.ai/code/session_01Sy3HeoBqb13ahLuaiP2UkM
2026-08-04 05:54:00 -07:00
17532793fd [INSTALL SPEED] Configure target keyboard without temporary boot (#93)
* Configure target keyboard without temporary boot

* Slim the offline keyboard configuration to what Omarchy consumes

Drop the Xorg 00-keyboard.conf writer: the installed system only reads
the XKB* settings in vconsole.conf (via detect-keyboard-layout.sh, which
copies XKBLAYOUT/XKBVARIANT into Hyprland's kb_layout), and nothing on
an Omarchy system reads xorg.conf.d. systemd-firstboot already writes
those vconsole settings.

Drop the set_keyboard_language monkey-patch: passing an empty kb_layout
to minimal_installation takes archinstall's documented skip path (no
nspawn boot), and the keymap is configured offline right after instead.

Append the preserved FONT= line instead of reproducing localectl's exact
file layout, and slim the tests to the behavior that matters: XKB
settings present, font preserved, every configurator keymap valid, and
archinstall-compatible invalid/empty handling.

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

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 20:00:56 -05:00
N1R4 0b4d74f6af Filter phantom entries when rewriting EFI BootOrder (#94)
The Limine registration helper rebuilds the UEFI BootOrder from the
pre-state read, but passes every entry through verbatim. Some firmware
(listed in the BootOrder but with no matching BootXXXX variable) causes
efibootmgr --bootorder to fail with exit status 8, aborting the whole
install at the bootloader step.

Only carry forward entries that still exist as boot variables.
2026-08-01 13:07:09 -05:00
Ryan Hughes 56eb451b92 Merge pull request #86 from omacom-io/fix-t2-kernel
Boot the T2 kernel the ISO says it boots
2026-07-28 15:52:12 -04:00
3efdb5c94b Reject reserved system usernames in the configurator
The username regex accepted names that already exist on the target, so
useradd failed inside arch_install_system — after the disk had been
partitioned and formatted, which is the worst point to discover it.

From #59 by @wolfgangw, applied to quattro; it was opened against main.

Co-Authored-By: Wolfgang Woehl <tito@online.de>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 08:43:43 -07:00
b3cf0c6f29 Run the live CPUs at the performance governor during install (#89)
archiso boots on whatever governor the kernel defaults to, which on most
laptops ramps conservatively. Package extraction and the UKI build are both
CPU-bound, so pin performance for the whole install and put it back afterwards.

Reduced from #87 by @davydotcom, which also disabled every non-POLL cpuidle
state. That half is left out deliberately: C1 wake latency is 1us on current
hardware, and disabling it leaves POLL as the only idle state, so idle cores
busy-spin instead of halting. Package extraction is single-threaded, so on an
8-core laptop that is 7 cores burning power for no work — on a thermally
limited machine that costs the working core its turbo headroom, which is a
plausible way to make the install slower. #87 measured the two tweaks together
with a page-cache prefetch and an updatedb deferral, so the C-state
contribution was never isolated.

Scoped to the whole install rather than just the pacstrap window, which #87
used: finalize_limine_boot runs mkinitcpio and compresses a UKI, and is the
most purely CPU-bound phase there is.

Restoring only matters when an install fails and the user keeps using the live
environment; a successful one reboots out of it.

Co-authored-by: David Estes <davydotcom@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 08:24:13 -07:00
David Heinemeier HanssonandClaude Opus 5 4f60a18c87 Drive the install bar from packages, not from a stopwatch
install_progress() mapped each phase to a start%, an end% and a hard-coded
"expected seconds", then interpolated on elapsed time. Those seconds were
calibrated on one machine, so the bar was only ever honest on that machine.
Measured against a real 69s install on an XPS 13, the table expected 145s: 95s
for a phase that took 58.6s, 10s for one that took 2.6s. On slow media it is
wrong the other way — the bar reaches each phase's ceiling in a fraction of the
real time and then sits there. Today's clamp turns that into a 386-second freeze
at 85% on a USB2 install.

Phases do not even scale together: package installation is bound by media read
and disk write, the UKI rebuild is CPU and compression bound. No single seconds
table can be right for both.

So stop predicting durations. Position is now max(floor, work), both
non-decreasing:

  floor  an asymptote over the phase's band, on time since this process first
         saw the phase. tau is a shape constant, not a prediction — wrong by 3x
         it changes the pace of a crawl, never the correctness.
  work   only for "Installing Arch + Omarchy", which is ~85% of the install.
         libalpm creates one directory per package under
         <target>/var/lib/pacman/local, so counting them counts finished work.
         The denominator is resolved at ISO build time by pacman's own resolver
         against the offline mirror, so there is no second package list to
         drift. phases.py records expected and actual in the timing JSON on
         every install, so drift shows up in acceptance runs.

There is deliberately no give-up predicate: two discontinuous curves selected by
a toggle, behind a monotonic clamp, is a freeze generator. If the signal never
appears — no denominator, wrong target, implausible count — the floor alone
still walks the band to its end.

Position moves to per-mille. That is load-bearing, not precision theatre: these
are asymptotes, and in whole percent an asymptote over a 2-point band has two
reachable values and stops moving within seconds.

Time now comes from /proc/uptime. The live ISO runs tzupdate and
systemd-timesyncd, and the old code read EPOCHSECONDS, so a clock step
mid-install jumped every band straight to its ceiling.

Also fixes drift the old table had accumulated: it still weighted five phases
that no longer exist and had no entry at all for "Preparing install target",
which fell through to a 1->99 sweep. Unknown names now derive an even band from
the index the orchestrator already publishes.

bin/omarchy-iso-installer fakes a pacman database so the preview exercises the
package path instead of the fallback; PREVIEW_PACKAGES tunes the count.

Verified by simulation against the real logic: a 69s NVMe install, a 6-minute
USB2 install with a 70s pre-extraction stall, an implausible denominator, an
unknown phase name, and a target that already holds packages. All monotonic, all
reach 100%. Not yet run on real hardware.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 07:54:05 -07:00
David Heinemeier HanssonandClaude Opus 5 ed710a3b41 Boot the T2 kernel the ISO says it boots
Every boot entry has named vmlinuz-linux-t2 since 0638855, but the file has
contained stock Arch. Verified against the shipped ISO:

  arch/boot/x86_64/vmlinuz-linux-t2:
    bzImage, version 7.1.5-arch1-1 (linux@archlinux)

while the mirror carries linux-t2-7.1.4-2. T2 MacBooks have been installing
with no keyboard or trackpad in the live environment.

Kernel packages ship nothing in /boot — only usr/lib/modules/<kver>/vmlinuz.
install_kernel() in /usr/share/libalpm/scripts/mkinitcpio sources
/etc/mkinitcpio.d/<pkgbase>.preset and copies THAT package's kernel to whatever
ALL_kver names, whenever is_kernelcopy() sees an unowned absolute path. Our file
was named linux.preset, so it overrode releng's copy of the same name and
pointed the STOCK kernel at the T2 filenames. linux-t2's own generated preset
writes those same two paths, and stock won.

c23e0e7 introduced it a week after 0638855 added T2 support, so the feature
worked for exactly seven days: before the preset existed, releng's linux.preset
kept the stock kernel on its own filenames and linux-t2's generated preset
produced the real thing.

The fix is the filename. releng's linux.preset takes over the stock kernel
again, which now writes /boot/vmlinuz-linux and initramfs-linux.img instead of
squatting on the T2 names — so the ISO grows by one kernel and one archiso
initramfs. That cost is not free and is worth revisiting: the stock kernel is
in the live root only because releng's broadcom-wl hard-depends on it, and
broadcom-wl is a prebuilt module for that kernel which cannot load on the T2
one we now boot. Dropping both is ~147MB and old Broadcom Macs lose live Wi-Fi.

Boot the ISO on a T2 Mac before trusting this; the version string is only
evidence that the right kernel ships, not that it works.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 22:20:15 -07:00