The first screen offers English or Simplified Chinese. Chinese reruns
the configurator in cage + foot, which draw Chinese, with Noto Sans
Mono CJK unpacked from the offline mirror's noto-fonts-cjk; no font
joins the live image. There the keyboard list keeps US-keymap layouts
and preselects Chinese (Simplified, Pinyin), every later screen and
the install dashboard read in Chinese, and the target gets
LANG=zh_CN.UTF-8 through locale_config. Without a display or the
font, Chinese continues in English on the VT. The summary shows the
keyboard by its label instead of its keymap.
Needs omarchy's omarchy_prompt_language and omarchy_text in
install/provisioning/setup-form.sh.
* 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>
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>
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>
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>
* 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>
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
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>
The install dashboard shelled out to a full Python interpreter to read
four keys from the installer state JSON — including inside the render
loop, twice a second. jq is already on the ISO and does the same job
with plain bash arithmetic for the phase-weight interpolation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>