From 7e782bb7cf4b3ca3698960eb817e99aa1f376b20 Mon Sep 17 00:00:00 2001 From: Jeffrey Guenther Date: Tue, 28 Jul 2026 13:51:32 -0700 Subject: [PATCH] Document autoinstall Claude-Session: https://claude.ai/code/session_01Sy3HeoBqb13ahLuaiP2UkM --- README.md | 57 ++++++++++++++++++++++++++++++++++++++++++++ plans/autoinstall.md | 22 +++++++++++------ 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c80685e..b354747 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,63 @@ Despite the local folder name, the first argument is the Omarchy source checkout Use `--dev` or `--rc` to build against those package channels. Both `--dev` and `--edge` select the dev packages from the edge mirror. +## Autoinstall + +The shipped ISO installs itself with no keyboard when it finds its configuration on a second drive. Attach a drive labeled `cidata` alongside the ISO and the installer copies the config off it and skips the configurator; with no such drive, nothing changes and the wizard runs as usual. No rebuild, no extra boot entry. + +`cidata` is the cloud-init `NoCloud` label, so Proxmox, libvirt, and Packer already know how to attach one. + +### Configuration files + +These are the configurator's own output files, so the way to get a starting set is to run one interactive install and copy what it wrote into `/root`. + +| File | Required | Purpose | +|------|----------|---------| +| `user_configuration.json` | Yes | archinstall config: disk, hostname, timezone, keyboard | +| `user_credentials.json` | Yes | Username and password hash | +| `user_full_name.txt` | No | Git full name | +| `user_email_address.txt` | No | Git email | +| `user_encrypt_installation.txt` | No | `true` to encrypt the install; defaults to false | +| `ssh.json` | No | JSON array of SSH public keys | + +Both required files must be present or the installer falls back to the configurator. Generate the password hash for `user_credentials.json` with `openssl passwd -6 "yourpassword"`. + +`ssh.json` is a JSON array of public keys: + +```json +["ssh-ed25519 AAAA... you@host"] +``` + +When `ssh.json` is present, autoinstall writes the keys to the user's `~/.ssh/authorized_keys`, enables `sshd`, and adds a `ufw allow ssh` rule — a stock Omarchy install ships openssh with the service disabled and its firewall opens neither port 22 nor anything else beyond LocalSend. Networking needs nothing extra; NetworkManager is already enabled with DHCP. Password SSH authentication is left at the distro default. An unparseable or empty `ssh.json` fails the install rather than producing a machine nobody can reach. + +### Building the drive + +```bash +mkdir cidata +cp user_configuration.json user_credentials.json ssh.json cidata/ +genisoimage -output cidata.iso -volid cidata -joliet -rock cidata/ +``` + +### Proxmox example + +```bash +qm create 101 --name my-omarchy \ + --bios ovmf --machine q35 --cpu host --cores 4 --memory 8192 \ + --ostype l26 --scsihw virtio-scsi-single \ + --efidisk0 local-lvm:0,efitype=4m,pre-enrolled-keys=0 \ + --scsi0 local-lvm:40,discard=on,iothread=1 \ + --net0 virtio,bridge=vmbr0 --vga virtio --serial0 socket \ + --ide2 local:iso/omarchy.iso,media=cdrom \ + --ide3 local:iso/cidata.iso,media=cdrom \ + --boot order='scsi0;ide2' + +qm start 101 +``` + +Boot order is disk first: the empty disk falls through to the ISO on the first boot, and the installed system boots from disk afterwards. The machine reboots into Omarchy on its own when the install finishes. + +Encrypted autoinstalls are not fully unattended — the LUKS passphrase prompt still needs someone at the first boot. + ## Testing the ISO Run `./bin/omarchy-iso-boot [release/omarchy.iso]`. diff --git a/plans/autoinstall.md b/plans/autoinstall.md index 18700ad..023621d 100644 --- a/plans/autoinstall.md +++ b/plans/autoinstall.md @@ -26,12 +26,17 @@ This is Ryan Hughes' suggestion from PR #72 and it is what keeps the diff small: ### 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`: +One thing that would otherwise need building is already true on a stock install: `NetworkManager.service` is enabled and active with DHCP by default, so autoinstall needs no `systemd-networkd` unit — one would contend with NetworkManager. -- `sshd.service` is enabled. -- `NetworkManager.service` is enabled and active, with DHCP by default. +Remote access needs three things, all verified against a real install rather than inferred: -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. +- `authorized_keys` for the user. +- `systemctl enable sshd.service`. Omarchy ships `openssh` but leaves the service disabled. +- `ufw allow ssh`. `install/config/firewall.sh` opens LocalSend (53317) and docker DNS and nothing else, and ufw runs default-deny incoming. + +Still no loosening of `PasswordAuthentication` — key auth only. + +Do not take `manifests/fresh-4-semantic.json` as the authority on any of this. It claims `sshd.service: enabled` and carries an `allow tcp 22` ufw rule; a real install of this ISO has neither. It is a snapshot of one machine's state, not a contract about stock behavior, and trusting it cost two test cycles. ### Non-interactivity belongs to the dashboard @@ -109,12 +114,14 @@ The dashboard is the only process that owns the visible UI. Suppressing its prom ### Changes - `omarchy-iso-install`: add `--ssh-keys-file` to the arg loop, exporting `OMARCHY_INSTALL_SSH_KEYS_FILE`. Same shape as the five existing flags, ahead of the `unknown arg` catch-all (`:19`). - `context.py`: add an `ssh_keys_path: Path | None` field populated from that variable in `from_env`, `None` when unset or when the file does not exist. -- `phases_impl.py`: add `configure_remote_access(ctx)`: +- `phases_impl.py`: add `configure_ssh_access(ctx)`: - Return immediately when `ctx.ssh_keys_path` is `None`. - Parse the file as a JSON array of public key strings; write them one per line to `/home//.ssh/authorized_keys`. - `.ssh` at `0700`, `authorized_keys` at `0600`, both owned by the user via `arch-chroot chown -R :`. Do not hardcode uid 1000 as PR #72 did — ask the target for it. + - `arch-chroot systemctl enable sshd.service`. Works cleanly in the chroot. + - `arch-chroot ufw allow ssh` when the target has ufw. This one exits **non-zero** in a chroot (`ERROR: problem running`) because ufw cannot reach netfilter, but it writes the rule to `/etc/ufw/user.rules` before failing, and that file is what `ufw.service` loads on first boot. So ignore the exit status and assert the rule landed in the file instead. - A malformed or empty `ssh.json` must fail the phase loudly. A machine that installs "successfully" and is then unreachable is worse than one that stops with an error on screen. -- `main.py`: register `("Configuring remote access", configure_remote_access)` in `build_phases` after `configure_login` (`:52`) and before `validate_boot`. +- `main.py`: register `("Configuring SSH access", configure_ssh_access)` in `build_phases` after `configure_login` (`:52`) and before `validate_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. @@ -123,6 +130,7 @@ The dashboard is the only process that owns the visible UI. Suppressing its prom - Install with `ssh.json`: `ssh @` works with the corresponding private key on first boot, no password. - Install without `ssh.json`: phase runs, does nothing, install unaffected; `~/.ssh` is not created. - `authorized_keys` is `0600` and owned by the install user, not root. +- Port 22 is open in ufw on the installed system. Test this by connecting, not by reading the phase log — the rule is written from a chroot that cannot verify it. - 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. @@ -169,4 +177,4 @@ PR #72 targeted the pre-`quattro` tree and cannot be rebased: `use_omarchy_helpe - The `systemd-networkd` DHCP unit — unnecessary; NetworkManager is already enabled and would contend with it. - The `PasswordAuthentication yes` rewrite — unnecessary and a security downgrade. -`load_cidata` is the one piece that carries over close to intact. +`load_cidata` is the one piece that carries over close to intact. PR #72's `ufw allow ssh` also carries over: it was dropped from the first draft of this plan on the reasoning that an enabled sshd is a reachable sshd, and a test install proved otherwise. That PR was right to include it.