PR #72 targeted the pre-quattro tree and can't be rebased -- the bash seams it hooked into are gone. This plans the same feature against the configurator/dashboard/orchestrator split, and drops the three parts of that PR that were flagged as questionable. Claude-Session: https://claude.ai/code/session_01Sy3HeoBqb13ahLuaiP2UkM
11 KiB
Autoinstall Plan
Goal
Let the standard Omarchy ISO install itself with no keyboard present, driven by config files handed to the VM on a second drive. This makes Omarchy usable as a Packer/Proxmox base image for disposable dev environments: create VM, boot, walk away, SSH in.
Product Requirements
- No ISO rebuild. The shipped ISO autoinstalls when config is present and runs the configurator when it is not.
- No new menu entry, no new boot parameter, no separate ISO flavor.
- Config format is exactly what the configurator already writes — no second schema to maintain.
- The installed machine is reachable over SSH with a key supplied at install time.
- Interactive installs behave identically to today.
Chosen Architecture
Trigger: config presence, not a mode flag
The installer looks for a drive labeled cidata at startup. If it is there and carries the configurator's output files, those files are copied into /root and ./configurator is skipped — the rest of the install runs the ordinary path against ordinary inputs. If it is not there, nothing changes.
This is Ryan Hughes' suggestion from PR #72 and it is what keeps the diff small: the install pipeline can be rewritten freely as long as it keeps reading its five files out of /root, and autoinstall follows along for free.
cidata is the cloud-init NoCloud label, so Proxmox, libvirt, and Packer all already know how to attach one.
What the ISO already does for us
Two things that would otherwise need building are already true on a stock quattro install, per manifests/fresh-4-semantic.json:
sshd.serviceis enabled.NetworkManager.serviceis enabled and active, with DHCP by default.
So remote access needs only an authorized_keys file. No systemd-networkd unit (it would contend with NetworkManager), no systemctl enable sshd, and specifically no loosening of PasswordAuthentication — key auth is the only thing autoinstall adds.
Non-interactivity belongs to the dashboard
omarchy-install-dashboard owns every prompt that survives to the end of an install: the reboot confirm and, on failure, the action menu. Both are gum calls reading from the TTY, and both hang forever on a headless VM. Autoinstall therefore sets one env var that the dashboard reads, rather than patching prompts out of scripts after the fact — which is what PR #72 did to finished.sh, and what everyone correctly disliked.
Non-Goals
- No config generator, validator, or schema doc. The configurator is the source of truth for the format; users copy its output.
- No secret management. Whoever builds the
cidatadrive owns the password hash and the public key on it. - No PXE, no network-fetched config, no cloud-init compatibility beyond reusing the drive label.
- No changes to disk selection, encryption, or any install phase behavior.
Repo Implementation Plan
1) Detect the cidata drive and skip the configurator
File
configs/airootfs/root/.automated_script.sh
Changes
- Add a
load_cidatafunction above the existingcd /root(:93):- Look for
/dev/disk/by-label/cidatathen/dev/disk/by-label/CIDATA; return non-zero if neither exists. - Mount read-only on a temp mountpoint; return non-zero if the mount fails.
- Require both
user_configuration.jsonanduser_credentials.jsonon the drive — this is the same pairInstallContext.from_envtreats as mandatory. Missing either means the drive is not an autoinstall drive; unmount and return non-zero. - Copy the required pair plus, when present,
user_full_name.txt,user_email_address.txt,user_encrypt_installation.txt, andssh.jsoninto/root. - Unmount before returning. Leave nothing mounted for the install to trip over.
- Look for
- Replace the bare
./configuratorcall (:94) with: runload_cidata; on success exportOMARCHY_UI_INTERACTIVE=no, on failure run./configurator. - Pass
--ssh-keys-file /root/ssh.jsontoomarchy-iso-installin the dashboard invocation (:101–:110), alongside the existing five flags. Always pass it; the orchestrator no-ops when the file is absent, which keeps the interactive path on the identical command line.
Notes
- The three text files are optional by construction:
_read_textincontext.pyreturns""for a missing path andencryptfalls back to false. Do not add stricter checks here than the orchestrator itself applies. load_cidatamust not run beforewarm_offline_mirroris backgrounded — the mirror warm should keep its head start.
Acceptance criteria
- ISO with no
cidatadrive attached: configurator runs, byte-identical UX to today. - ISO with a
cidatadrive carrying the five files: configurator never appears; install starts within seconds of boot. - ISO with a drive labeled
cidatathat is empty or carries only one of the two required files: falls back to the configurator rather than failing the boot. - Nothing remains mounted from
/dev/disk/by-label/cidataonce the install begins.
2) Non-interactive dashboard
File
configs/airootfs/usr/local/bin/omarchy-install-dashboard
Changes
- Read
OMARCHY_UI_INTERACTIVEonce near the other env reads at the top. - In
reboot_prompt()(:474): when non-interactive, return 0 immediately instead of callinggum confirm. The existingOMARCHY_UI_AUTO_REBOOT=nocheck at:737stays in front of the actualreboot, so a debug run can set both and stop on the finish screen without a keyboard. - In
failure_menu()(:597): treat non-interactive as equivalent to the existingOMARCHY_UI_FAILURE_ACTION=exitearly return (:600), so a failed autoinstall exits with the installer's status instead of blocking ongum chooseforever. The failure screen and log tail still render, and the serial console still captures them.
Why here and not in the caller
The dashboard is the only process that owns the visible UI. Suppressing its prompts anywhere else means editing scripts it invokes — the finished.sh sed from PR #72 — which breaks the moment those scripts move.
Acceptance criteria
- Autoinstall run reaches the finish screen and reboots with no input.
- Autoinstall run whose install fails renders the failure screen, exits non-zero, and does not hang.
- Interactive run still prompts for reboot and still offers the failure menu.
OMARCHY_UI_INTERACTIVE=noplusOMARCHY_UI_AUTO_REBOOT=nostops at the finish screen without prompting.
3) SSH keys as an orchestrator phase
Files
configs/airootfs/usr/local/bin/omarchy-iso-installconfigs/airootfs/usr/share/omarchy-iso/orchestrator/context.pyconfigs/airootfs/usr/share/omarchy-iso/orchestrator/main.pyconfigs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py
Changes
omarchy-iso-install: add--ssh-keys-fileto the arg loop, exportingOMARCHY_INSTALL_SSH_KEYS_FILE. Same shape as the five existing flags, ahead of theunknown argcatch-all (:19).context.py: add anssh_keys_path: Path | Nonefield populated from that variable infrom_env,Nonewhen unset or when the file does not exist.phases_impl.py: addconfigure_remote_access(ctx):- Return immediately when
ctx.ssh_keys_pathisNone. - Parse the file as a JSON array of public key strings; write them one per line to
<target>/home/<user>/.ssh/authorized_keys. .sshat0700,authorized_keysat0600, both owned by the user viaarch-chroot <target> chown -R <user>:<user>. Do not hardcode uid 1000 as PR #72 did — ask the target for it.- A malformed or empty
ssh.jsonmust fail the phase loudly. A machine that installs "successfully" and is then unreachable is worse than one that stops with an error on screen.
- Return immediately when
main.py: register("Configuring remote access", configure_remote_access)inbuild_phasesafterconfigure_login(:52) and beforevalidate_boot.
Model to follow
configure_login (phases_impl.py:1251) — direct writes against ctx.target, arch-chroot reserved for ownership and systemctl. Match its structure.
Acceptance criteria
- Install with
ssh.json:ssh <user>@<vm>works with the corresponding private key on first boot, no password. - Install without
ssh.json: phase runs, does nothing, install unaffected;~/.sshis not created. authorized_keysis0600and owned by the install user, not root.- Password SSH auth is left at the distro default — autoinstall does not enable it.
- Interactive installs are unaffected: the flag is passed but the file never exists.
4) Documentation
File
README.md
Changes
- New "Autoinstall" section covering: what the
cidatadrive is, the file table, how to produce the drive, and a Proxmoxqm createexample with disk-first boot order so the empty disk falls through to the ISO on the first boot and boots the installed system afterward. - Show
openssl passwd -6for the credential hash and thessh.jsonarray format. - State plainly that the config files are the configurator's own output — the documented way to get a starting
user_configuration.jsonis to run one interactive install and copy what it wrote. - Do not repeat PR #72's networking claims. Say that sshd and NetworkManager come enabled on a stock install and that autoinstall only adds the key.
5) Validation
Manual matrix
- Build the ISO from this branch.
- Interactive control run: boot with no
cidatadrive, install normally, confirm nothing changed. - Autoinstall run on Proxmox: attach ISO +
cidatadrive, boot, no input; confirm the configurator never renders, the install completes, the machine reboots on its own, and SSH with the key works against the booted system. - Encrypted autoinstall:
user_encrypt_installation.txtset true; confirm the LUKS prompt appears on boot (this is the one autoinstall case that still needs a human at first boot — document it). - Full Packer cycle: build, destroy, rebuild from the same
cidatadrive; confirm reproducibility.
Negative tests
- Drive labeled
cidatawith no files → configurator runs. - Drive with
user_configuration.jsononly → configurator runs. - Malformed
ssh.json→ install stops in the remote-access phase with a visible error, does not reboot into an unreachable machine. - Install failure under autoinstall (e.g. a disk target that does not exist) → failure screen renders, process exits non-zero, no hang.
Regression surface
The interactive path shares every line of this change except the load_cidata branch, so run the standard acceptance harness (./bin/omarchy-iso-test) on the built ISO before proposing the PR.
Relationship to PR #72
PR #72 targeted the pre-quattro tree and cannot be rebased: use_omarchy_helpers, run_configurator, install_arch, install_omarchy, and chroot_bash no longer exist. This plan reimplements the same feature against the current tree and drops three things that PR deliberately flagged as questionable:
- The
finished.shsed— replaced by section 2. The dashboard renders the reboot prompt now. - The
systemd-networkdDHCP unit — unnecessary; NetworkManager is already enabled and would contend with it. - The
PasswordAuthentication yesrewrite — unnecessary and a security downgrade.
load_cidata is the one piece that carries over close to intact.