Commit Graph
117 Commits
Author SHA1 Message Date
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 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 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
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
David Heinemeier HanssonandClaude Opus 5 89245f44ec Warm the offline mirror while the user answers the wizard
The install reads ~3GB out of the offline mirror, and pacman consumes it at
only ~33MB/s (3.14GB of packages across the 95s the package phase is calibrated
at), so on any medium slower than that the install is read-bound. The ISO medium
is idle for the minutes the wizard takes, and the target disk written to later
is a different device, so reading ahead costs nothing that is otherwise in use.

Warms largest-first under a MemAvailable/2 budget. Deliberately no dependency
resolution: asking pacman exactly which files the install will read would copy
the package-set knowledge in phases_impl.py — the early list, the runtime
target, the kernel from the wizard's JSON — into a shell script that would drift
silently. The install reads roughly 69% of the mirror, so warming blindly is
close enough for a cache hint.

The page cache this fills is clean and reclaimable, so the failure mode under
memory pressure is wasted bandwidth rather than OOM. The budget stops small
machines evicting what was just warmed. The job ends when that budget is spent
rather than when the wizard exits, because the partitioning and format window
afterwards is also free medium time.

OMARCHY_NO_PREFETCH=1 turns it off so a single ISO can measure both arms against
the Installing Arch + Omarchy entry in /var/log/omarchy-install-timing.json.
Not yet measured on real USB media, and expected to be worth nothing in the VM
harness, where the ISO is backed by the host's page cache.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 16:16:16 -07:00
David Heinemeier HanssonandClaude Fable 5 434db24b81 Keep the installer centered when console geometry shifts
Remeasure the terminal at every screen redraw instead of once at boot:
the framebuffer can settle after the configurator starts (VM windows
especially), which left stale padding for the whole flow. Pad the
confirm-screen warnings with say so every line shares the same left
edge. Boot the test VM with SDL instead of GTK, whose GL display path
crops the guest framebuffer on HiDPI Wayland desktops.

Verified in a live VM by driving the whole flow over QMP and reading
each screen back from /dev/vcs1: all screens center on the real console
width.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 16:41:23 -07:00
David Heinemeier HanssonandClaude Fable 5 969d461eb9 Parse the free-space fields with a single read
Three echo-through-awk substitutions to split one line into three
variables is what read was made for.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 10:55:49 -07:00
David Heinemeier HanssonandClaude Fable 5 4583a0e5e6 Extract the helpers shared by both install paths
The free-space and full-disk paths each carried verbatim copies of the
T2 Mac kernel detection, the credentials JSON heredoc, the identity/
encryption answer files, the dry-run print-and-cleanup block, and the
udev settle loops. Pull those into detect_kernel, write_user_files,
print_dry_run_files, and wait_for_device, which also removes a subtle
rm vs rm -f drift between the two dry-run cleanups.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 10:55:32 -07:00
David Heinemeier HanssonandClaude Fable 5 f79578f2db Remove the dead shared-ESP path from the free-space install
We decided Omarchy always creates its own dedicated ESP and never adopts
a Windows one, so shared_esp was declared empty and never assigned —
every conditional on it was guaranteed to take the dedicated-ESP branch.
Collapse the whole dual-path structure into straight-line code and keep
detect_windows_esp only for the "leaving it untouched" message.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 10:54:38 -07:00
David Heinemeier HanssonandClaude Fable 5 4e3e82922d Trust that tzupdate is on the ISO
build-iso.sh bakes tzupdate into the live environment, so the runtime
pacman fallback was dead there — and in a dry run on a dev machine it
would install a package as a side effect. tzupdate -p already fails
gracefully into the manual timezone filter when the tool is missing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 10:53:57 -07:00
David Heinemeier HanssonandClaude Fable 5 59ffc3321e Simplify the configurator preamble
Drop the dead LOGO_PATH override, the redundant package-targets sourcing,
the unused TERM_HEIGHT and OMARCHY_NVIM_PACKAGE, the non-gum exports, and
the logo fallback branches. Trust that logo.txt is where OMARCHY_PATH says.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 10:40:33 -07:00
David Heinemeier Hansson 1463936de3 Simplify the free-space install selection 2026-07-16 14:41:18 -07:00
David Heinemeier Hansson 588973b0cd Only offer a choice of installing alongside existing data if it's actually possible 2026-07-14 17:05:45 -07:00
David Heinemeier Hansson c77a7b555b Drop the tool offering during this initial install 2026-07-14 17:03:06 -07:00
Ryan Hughes 8c77631f68 Delegate Snapper setup to Omarchy packages 2026-06-21 02:29:09 -04:00
Ryan Hughes b1c09fa1a8 Use dev Omarchy packages in dev ISO builds 2026-06-08 12:53:01 -04:00
Ryan Hughes ef3b0e4b61 Always create our own ESP 2026-05-30 01:38:24 -04:00
Ryan Hughes afa8293b2b Improve ISO installer flow and VM tooling 2026-05-25 19:54:12 -04:00
Ryan Hughes c50d0bc299 Unify ISO install orchestration
Drive installs through the common ISO pipeline, carry install intent in configurator JSON, and finalize Limine boot setup from the ISO.
2026-05-25 12:50:31 -04:00
Ryan Hughes 38f56103e1 builder: add debug install instrumentation 2026-05-23 12:45:12 -04:00
Ryan Hughes 6232620806 installer: unify finalizer logging 2026-05-23 10:21:38 -04:00
Ryan Hughes 1d656af51d installer: make dashboard own install flow 2026-05-23 04:50:32 -04:00
Ryan Hughes af4679b153 installer: clear stale dashboard state 2026-05-23 03:43:24 -04:00
Ryan Hughes 7d7f6451a3 installer: track real dashboard pid 2026-05-23 02:55:44 -04:00
Ryan Hughes 0890472c9b installer: pass dashboard tty explicitly 2026-05-23 02:14:13 -04:00
Ryan Hughes 1162d111bd installer: force dashboard shutdown before finish 2026-05-22 23:10:44 -04:00
Ryan Hughes 0734e982ca installer: harden dashboard handoff 2026-05-22 19:15:06 -04:00
Ryan Hughes 61f84a95cf installer: smooth dashboard progress 2026-05-22 18:09:00 -04:00
Ryan Hughes 337ba2e590 installer: show dashboard during ISO install 2026-05-22 17:34:01 -04:00
Ryan Hughes de0f013b5e orchestrator: converge free-space boot on finalizer 2026-05-22 16:33:11 -04:00
Ryan Hughes 948a53cc0a orchestrator: recover missing ESP mount in free-space installs 2026-05-22 15:00:19 -04:00
Ryan Hughes cab315bb8a configurator: count EFI inside free-space size 2026-05-22 14:47:57 -04:00
Ryan Hughes 2178c26a92 configurator: let free-space installs choose size 2026-05-22 14:05:28 -04:00
Ryan Hughes f3973abb4c configurator: use valid bootloader value for free-space installs 2026-05-22 13:51:40 -04:00
Ryan Hughes 3e0cd31c2b configurator: clarify free-space install semantics 2026-05-22 13:51:02 -04:00
Ryan Hughes 7e79355c71 configurator: add partition tool and recoverable partition flow 2026-05-22 13:29:10 -04:00
Ryan Hughes c179d78814 configurator: emit explicit bootloader_config with removable=false
archinstall 4.3 maps the deprecated top-level 'bootloader': 'Limine'
to BootloaderConfiguration(bootloader=Limine, removable=True) (per
lib/args.py:185-190 and lib/models/bootloader.py:56-69). Our new
_install_limine_omarchy mirrors archinstall's removable handling: it
would copy BOOTX64.EFI to EFI/BOOT (fallback path) and SKIP
efibootmgr, neither of which is what we want.

Switch to the explicit bootloader_config form with removable=false
so we take the EFI/limine path + register the 'Limine' NVRAM entry
as intended.
2026-05-22 00:15:04 -04:00
Ryan Hughes 16cc1058be automated_script: restore proven gum stream/env contract
Restore the env + redirect setup that the previously-working ISO
installer used (matching /home/ryan/Downloads/ISO scripts/.automated_script.sh):

  export COLUMNS=$(tput cols)
  export LINES=$(tput lines)
  exec > >(tee >(sed CSI-strip >> log) 2>/dev/null) 2>/dev/tty
  export CLICOLOR_FORCE=1
  export FORCE_COLOR=1

The cutover commit (064b5e2) silently dropped these. Subsequent
attempts to fix the TTY/gum rendering by reshaping the redirect were
all symptom-chasing — the proven shape was correct. The key insight:

  - gum draws its TUI on stderr, not stdout. 2>/dev/tty keeps that
    rendering on the real terminal.
  - stdout-as-pipe (via tee) is *desired*: it lets gum detect a
    non-TTY stdout and skip cursor-position probes (CSI 6n). Without
    that detection, the DSR replies leak as visible ^[[13;1R bytes.
  - CLICOLOR_FORCE/FORCE_COLOR force gum to still emit colour despite
    stdout being a pipe.

Also drops the configurator's _drain_tty workaround — unnecessary
once gum stops sending DSR queries in the first place.
2026-05-21 20:54:05 -04:00
Ryan Hughes 8734136908 configurator/shim: drain tty without hanging (use -t 0.05, not -t 0)
bash's `read -t 0` only TESTS whether input is available; it does not
consume the bytes. So the drain loop `while read -t 0 ...; do :; done`
spun forever the moment any bytes were pending (e.g., the DSR reply
sitting in the tty input buffer after gum style sent CSI 6n).

That hung the configurator at the first step() title screen — blank
prompt, blinking cursor, no keyboard form.

Use `read -t 0.05 -N 9999` once, which consumes up to 9999 bytes with
a 50ms timeout. Always returns after 50ms whether there's data or not.
Single call replaces the while loop.
2026-05-21 20:46:50 -04:00
Ryan Hughes a5a7110bea configurator: drain tty input after each step() title
gum's TUI startup queries the terminal (e.g., cursor position via
CSI 6n) and doesn't always read the reply. The reply bytes
("^[[13;1R") then surface visibly next to the next step title.
A one-time drain at script start (in .automated_script.sh) catches
the boot-time replies but not the gum-per-call ones, since the
artifact came back at every new step().

Add a small _drain_tty helper and call it from step() after the
title gum-style invocation so any pending DSR replies are consumed
before the next prompt runs.
2026-05-21 20:34:21 -04:00
Ryan Hughes be721e17f4 installer: route kernel/initramfs to ESP for encrypted protected installs (Limine can't read LUKS) 2026-05-21 20:09:50 -04:00
Ryan Hughes c1828b13ad automated_script: drain stale tty input + drop redundant stdin redirect
Fix the ^[[13;1R cursor-position-report artifacts visible next to the
first configurator step. archiso's boot init leaves DSR responses in the
kernel tty input buffer; the first gum invocation reads them as stray
input and echoes them as visible escape sequences.

- Drain stdin (with a short timeout) from /dev/tty before configurator
  runs so any pending replies are consumed.
- Drop the explicit </dev/tty stdin redirect — the parent script's
  stdin is already tty1 (inherited from archiso autostart), and the
  explicit redirect opened a new fd with subtly different termios.
2026-05-21 19:51:35 -04:00
Ryan Hughes 0477275d08 configurator: shorten install-mode labels to fit 80-col terminals 2026-05-21 18:44:09 -04:00
Ryan Hughes 13e0201e9b automated_script: keep configurator on real TTY; scope logging to install phase 2026-05-21 18:44:09 -04:00
Ryan Hughes 26e6a78d3a configurator: add partition install mode with BitLocker/ESP guards and pre_mounted_config output 2026-05-21 18:12:55 -04:00
Ryan Hughes 064b5e2393 automated_script: collapse to configurator + omarchy-iso-install wrapper
All install-phase logic now lives in the Python orchestrator and the
omarchy-iso-* bash helpers. .automated_script.sh becomes a thin shim that:

  - sources install helpers (env for configurator)
  - sets the Tokyo Night palette on the live VT
  - tees stripped stdout/stderr to /var/log/omarchy-install.log
  - runs the configurator wizard
  - exec's omarchy-iso-install with absolute config paths

Absolute paths are required because the wrapper cd's into
/usr/share/omarchy-iso before invoking the orchestrator.

Removed install_base_system, install_omarchy, install_arch, install_disk,
cleanup_install_disk, configure_login_for_unencrypted_install, and
chroot_run_as_user — all replaced by Python phases or bash helpers.
2026-05-21 17:53:05 -04:00
Ryan Hughes 0c63044066 chroot_run_as_user: unset XDG_RUNTIME_DIR
The live ISO runs as root with XDG_RUNTIME_DIR=/run/user/0; arch-chroot
-u ryan switches the UID but doesn't reset the env, so tools running as
ryan tried to write to root's runtime dir (dconf failed to commit,
omarchy-theme-set's flock target hit Permission denied). Drop the var
so tools fall back to no-runtime-dir behaviour, which works inside the
chroot.
2026-05-21 00:39:39 -04:00
Ryan Hughes ca2b58fa4d Install omarchy-installer + omarchy-settings into the live ISO
Two motivations:

1. Plymouth: the omarchy plymouth theme + /etc/plymouth/plymouthd.conf
   ship from omarchy-settings (theme at /usr/share/plymouth/themes/omarchy,
   plymouthd.conf via the package's post_install etc-overrides hook).
   Without omarchy-settings on the live ISO, mkarchiso builds the initrd
   with plymouth pointing at the default theme instead of Omarchy's. Now
   it picks up the Omarchy theme on boot like the installed system does.

2. Drops the .pkg.tar.zst extraction hack in .automated_script.sh:
   OMARCHY_PATH=/usr/share/omarchy is the real package install path, so
   use_omarchy_helpers no longer needs to bsdtar pieces out of the
   offline mirror into /tmp/omarchy-iso.
2026-05-21 00:13:05 -04:00