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>
7.7 KiB
OEM Install + Factory Reset Plan
Goal
An install mode that installs the entire system but defers user account setup to first boot. On first power-on the machine asks for the user, creates it, and lands in the already-installed system — the classic OEM out-of-box experience. The same machinery doubles as a "reset computer" option for wiping a machine before selling it.
Core idea: "OEM state" as a first-class system state
Define one canonical state — fully installed, no user, setup pending — and make both paths produce it:
- ISO OEM mode ends the install in OEM state instead of creating the user.
- Reset returns a running machine to OEM state.
OEM state is: a fully installed system plus /var/lib/omarchy/oem/ containing:
| File | Purpose |
|---|---|
pending |
Flag that arms the first-boot setup service |
packages/node-v*.tar.gz |
Node tarball copied off the ISO's /opt/packages (~30MB) so omarchy-finalize-user works offline at first boot |
groups |
Deferred usermod -aG groups written by system setup (see below) |
luks-key |
Throwaway LUKS passphrase for encrypted installs, staged for auto-unlock until first-boot re-key |
Why the seam is nearly clean already
Everything in the orchestrator through finalize_limine_boot is
user-independent except five touch points:
installer.create_users— useradd + password (root gets the same password).- Three
usermod -aGcalls insideomarchy-setup-system's scripts (config/docker.sh,hardware/input-group.sh,hardware/apple/fix-t2.sh— docker/input/video). This is the only reasonomarchy-setup-systemrequires the user to exist (its getent check). run_chroot_finalizer→omarchy-finalize-useras the user. Already offline-safe:/etc/skeldoes the heavy seeding at useradd time,omarchy-mise-installwrites lazy wrappers, and the only external artifact is the bundled Node tarball from/opt/packages(install/user/mise.sh).configure_login— SDDMstate.confand the encrypted-install autologin.- Encryption — the user's password is the LUKS passphrase.
Nothing in hardware setup, Limine, hibernation, snapper, or package installation cares who the user is.
First boot: omarchy-oem-setup.service
Ships in the omarchy runtime package (so reset can arm it without the ISO):
ConditionPathExists=/var/lib/omarchy/oem/pendingBefore=display-manager.service, conflictsgetty@tty1, TUI on tty1.- Runs the user form extracted from the configurator (Step 2: username,
password, full name, email). The configurator is bash+gum, so the form moves
into a runtime-shipped
omarchy-oem-setupscript that reuses the exact ISO look (logo, Tokyo Night VT palette).
Then it does what the ISO would have done:
useraddwith wheel + the groups from/var/lib/omarchy/oem/groups, set user + root password.omarchy-finalize-useras the user, with a setup context that points at the stashed Node tarball (a sibling of the existingiso-chrootcontext).- The
configure_loginwrites: SDDMstate.conf, autologin for encrypted installs. - If
luks-keyexists:cryptsetup luksChangeKeyto the user's password, delete the stash, remove the auto-unlock keyfile, rebuild initramfs. - Git identity from the form (the
OMARCHY_USER_NAME/OMARCHY_USER_EMAILpath ininstall/user/git.sh). - Remove
/var/lib/omarchy/oem/, hand off to SDDM.
The user lands in their session exactly like a normal install.
Encryption: keep it, Pop!_OS-style
Do not force OEM installs unencrypted. Install encrypted with a generated throwaway passphrase, stage a keyfile so boot auto-unlocks during the OEM window, and have first-boot setup re-key to the user's chosen password (step 4 above). Limine and mkinitcpio don't care what the passphrase is. This preserves encrypted-by-default without the machine builder ever knowing the end user's password.
ISO-side changes (this repo)
- Configurator: an OEM choice in the install-mode step that skips Step 2
(user), plus a cidata
oemmarker file that replaces theuser_credentials.jsonrequirement — that's what actual OEM imaging rigs will use via autoinstall. - Orchestrator:
oem: truein theomarchy_installJSON. Swaps the three user phases (create_usersinsidearch_install_system,run_chroot_finalizer, the user parts ofconfigure_login) for a "stage OEM state" phase that writes/var/lib/omarchy/oem/and enablesomarchy-oem-setup.servicein the target.InstallContext.usernameneeds an OEM-safe fallback (creds are absent). - Factory snapshot: at the end of every install (not just OEM), take a
read-only snapshot of
@kept at the top level as@factory— outside snapper's.snapshots, so cleanup timers and the Limine snapshot menu never touch it. Zero bytes at creation; grows only with drift. This is what makes reset a true factory reset.
Runtime-side changes (omarchy repo)
omarchy-oem-setup+omarchy-oem-setup.service(the OOBE above).omarchy-system-factory-reset(below).- Relax
omarchy-setup-system: when the install user doesn't exist yet, the threeusermod -aGscripts append to/var/lib/omarchy/oem/groupsinstead of failing the getent check.
Reset: omarchy-system-factory-reset
Behind a very explicit confirmation (typed, not y/n):
- Point the default subvolume at a writable snapshot of
@factory; stage a next-boot wipe unit. - On reboot: recreate
@home(subvolume delete/create — instant, no rm -rf), clear@log, delete snapper snapshots, reset machine-id, SSH host keys, NetworkManager connections, Tailscale state, SDDM state; runfstrim. - Re-arm OEM state:
pendingflag,groups, Node tarball (kept from install or re-fetched into@factoryat install time), and on encrypted machines a throwaway LUKS re-key — the buyer never needs the seller's passphrase.
Next boot is the same OOBE as a fresh OEM install. User-installed packages and /etc drift are gone, which is what "wipe and sell" actually promises.
Machines installed before @factory existed get a degraded reset — keep the
current system, wipe users/state only — with that caveat surfaced in the
confirmation.
Honest limitations (document these)
- On unencrypted disks this is deletion, not forensic erasure.
fstrimhelps on SSDs; offercryptsetup reencryptorblkdiscard+ reinstall for the paranoid. luksChangeKeychanges the passphrase but not the volume key; freed extents remain readable to someone with raw-device access and the new passphrase. Same remedy as above.- A reset machine boots with day-one packages until it updates — same as a fresh install.
Sequencing
The OOBE lives in the omarchy runtime package, but the ISO's OEM phase depends on it existing. So:
- Ship the runtime side first (
omarchy-oem-setup, service, reset command,omarchy-setup-systemrelaxation). - ISO OEM mode then asserts the service file exists in the target before offering the mode.
- Factory snapshot can land with either half; reset requires both.
Validation
- VM: OEM install (encrypted + unencrypted) → reboot → OOBE → session; verify
LUKS passphrase is the user's, throwaway key gone, groups applied,
omarchy-finalize-userstate marker present. - VM: normal install → use system →
omarchy-system-factory-reset→ OOBE as new user; verify no trace of prior user (home, NM connections, tailscale, machine-id, host keys, snapper snapshots). - Autoinstall: cidata drive with
oemmarker and no credentials → unattended OEM install. - Negative: OEM ISO built against a runtime package lacking the service fails the install with a clear error, not a user-less boot loop.