Installer GUI #4

Open
opened 2026-08-21 08:01:45 +00:00 by jmarya · 2 comments
Owner

Implement opinionated installer GUI and improve live image

Implement opinionated installer GUI and improve live image
Author
Owner

Design idea: the installer generates artifacts, it does not install

The title is a bit misleading — this shouldn't be a Calamares-style installer. A classic installer performs an installation (partition, unpack, chroot) and the machine's identity afterwards is whatever residue that left behind; nothing about the result is re-derivable.

The declarative version inverts it: the GUI's only output is files. It writes a machines/<host>/ directory, and a separate, boring, replayable step consumes it.

GUI  →  machines/<host>/{configuration,disk,fleet}.nix + facter.json
                          ↓
              disko-install --flake .#<host>        (local)
              nixos-anywhere --flake .#<host>       (remote)

If the GUI crashes halfway, nothing is lost but keystrokes. Anyone can skip the GUI and hand-write the directory — the escape hatch the README already promises.

Wizard flow

1. Target        local / remote (ssh user@host)
2. Probe         run facter on the target → disks, CPU, GPU, network
3. Disk          pick device + named layout, show the plan
4. Identity      hostname, user, timezone/locale/keymap (defaulted from probe)
5. Device class  desktop / server / tv / console / router / tablet / phone
6. Features      checkboxes over the feature modules
7. Flake         new at /etc/nixos, or an existing one (path or git URL)
8. Review        show the generated machines/<host>/ verbatim
9. Apply         disko-install / nixos-anywhere
10. Register     print host pubkey, enable comin

"local or remote" as step 1 is nearly free once the pipeline is spec → artifacts → apply: same spec, same generated directory, different applier. It also means the ISO isn't always in the picture — a remote install can target any SSH-reachable box in any installer or kexec image.

Probe-before-disk is why this can't be a pure form: step 2 has to actually execute somewhere before step 3 has anything to offer.

Screen 8 is what makes this opinionated rather than magic. Eight questions replace dozens of NixOS decisions, so the payment is showing exactly what Nix was generated before running it. It's also the free QA path — the generator's output can be reviewed without installing anything.

Prerequisites (the actual work)

  1. disko — not an input anywhere in the tree today. Without it the partitioning step is imperative and the artifact set is incomplete: a machine could not be reinstalled from its own files. Biggest gap, and worth doing on its own merits regardless of whether the GUI ever ships.
  2. Hardware capturenixos-facter checks in a JSON hardware report and derives config at eval time; the artifact is a fact about the machine rather than a guess about its config. hardware-configuration.nix as fallback.
  3. The GUI — once 1 and 2 exist, largely a form over profiles.*.

Opinionated disks

"Opinionated" should mean no partition editor ships. Named layouts as a myverse module:

myverse.disk = {
  layout = "btrfs-luks";   # plain | btrfs | btrfs-luks | btrfs-luks-impermanence
  device = "/dev/nvme0n1";
  swap = "16G";
};

The GUI asks two questions instead of exposing a partition table. Advanced users write raw disko in disk.nix and the module gets out of the way. The opinion lives here, not in the UI.

Registering into a flake

If the target flake uses myverse.fleet, adding a machine is dropping a directoryautoDiscover reads machines/ and picks it up with zero Nix edits. Already implemented, and it's a genuinely clean path.

If the flake doesn't use fleet, registering would mean parsing and rewriting arbitrary Nix. Don't. Write the directory, print the snippet for the user to paste. Honest boundary, and it quietly makes myverse.fleet the paved road.

Friction points where "mostly declarative" needs a decision

  • Where the flake lives. /etc/nixos is root-owned — fine for the local-only exit, hostile the moment the artifacts are meant to be pushed to a git remote (no git push without sudo, and comin wants its own checkout). Local-only → /etc/nixos; fleet exits → somewhere user-owned with --flake pointed at it.
  • Passwords. A hashed password in configuration.nix is fine until step 7 says "push to your remote". Use hashedPasswordFile written out-of-band, or initialPassword + forced change. Rule to hold: the generated artifact set contains zero secrets, so it is always safe to push.
  • Remote + /etc/nixos. For a remote install the artifacts belong on the operator's machine, not the target — the target only receives a closure. Screen 7 means something different in each branch and the UI should say so.
  • Host key bootstrap. The one genuinely imperative step: generate the target's SSH host key during install so its pubkey can be shown on screen 10 and registered as a deploy key while the user is still sitting there. Everything else in the flow is file-writing.

Exits

  • Local — artifacts in /etc/nixos, rebuild by hand. No git, no remote.
  • New fleet — init a fleet repo containing machines/<host>/, enable profiles.comin, print the host pubkey to register as a deploy key. Machine self-updates from then on.
  • Join fleet — clone an existing fleet repo, add machines/<host>/, push a branch. Adding a machine becomes a commit, and the installer's whole job was to author it.

Suggested build order

disko module with named layouts → facter probe → a myverse-install CLI taking a spec JSON → a Dioxus GUI over it.

Making the CLI the real interface (GUI as a skin that builds the spec and shells out) means unattended installs, VM-tested installs, and myverse-install --flake <fleet>#<host> re-installs all come free, instead of GUI and headless being two implementations. The first three steps are independently useful and testable in a VM; the GUI is not.

## Design idea: the installer generates artifacts, it does not install The title is a bit misleading — this shouldn't be a Calamares-style installer. A classic installer *performs* an installation (partition, unpack, chroot) and the machine's identity afterwards is whatever residue that left behind; nothing about the result is re-derivable. The declarative version inverts it: **the GUI's only output is files.** It writes a `machines/<host>/` directory, and a separate, boring, replayable step consumes it. ``` GUI → machines/<host>/{configuration,disk,fleet}.nix + facter.json ↓ disko-install --flake .#<host> (local) nixos-anywhere --flake .#<host> (remote) ``` If the GUI crashes halfway, nothing is lost but keystrokes. Anyone can skip the GUI and hand-write the directory — the escape hatch the README already promises. ### Wizard flow ``` 1. Target local / remote (ssh user@host) 2. Probe run facter on the target → disks, CPU, GPU, network 3. Disk pick device + named layout, show the plan 4. Identity hostname, user, timezone/locale/keymap (defaulted from probe) 5. Device class desktop / server / tv / console / router / tablet / phone 6. Features checkboxes over the feature modules 7. Flake new at /etc/nixos, or an existing one (path or git URL) 8. Review show the generated machines/<host>/ verbatim 9. Apply disko-install / nixos-anywhere 10. Register print host pubkey, enable comin ``` "local or remote" as step 1 is nearly free once the pipeline is spec → artifacts → apply: same spec, same generated directory, different applier. It also means the ISO isn't always in the picture — a remote install can target any SSH-reachable box in any installer or kexec image. Probe-before-disk is why this can't be a pure form: step 2 has to actually execute somewhere before step 3 has anything to offer. Screen 8 is what makes this *opinionated* rather than *magic*. Eight questions replace dozens of NixOS decisions, so the payment is showing exactly what Nix was generated before running it. It's also the free QA path — the generator's output can be reviewed without installing anything. ### Prerequisites (the actual work) 1. **`disko`** — not an input anywhere in the tree today. Without it the partitioning step is imperative and the artifact set is incomplete: a machine could not be reinstalled from its own files. Biggest gap, and worth doing on its own merits regardless of whether the GUI ever ships. 2. **Hardware capture** — `nixos-facter` checks in a JSON hardware *report* and derives config at eval time; the artifact is a fact about the machine rather than a guess about its config. `hardware-configuration.nix` as fallback. 3. **The GUI** — once 1 and 2 exist, largely a form over `profiles.*`. ### Opinionated disks "Opinionated" should mean **no partition editor ships**. Named layouts as a myverse module: ```nix myverse.disk = { layout = "btrfs-luks"; # plain | btrfs | btrfs-luks | btrfs-luks-impermanence device = "/dev/nvme0n1"; swap = "16G"; }; ``` The GUI asks two questions instead of exposing a partition table. Advanced users write raw disko in `disk.nix` and the module gets out of the way. The opinion lives here, not in the UI. ### Registering into a flake If the target flake uses `myverse.fleet`, adding a machine is **dropping a directory** — `autoDiscover` reads `machines/` and picks it up with zero Nix edits. Already implemented, and it's a genuinely clean path. If the flake doesn't use fleet, registering would mean parsing and rewriting arbitrary Nix. Don't. Write the directory, print the snippet for the user to paste. Honest boundary, and it quietly makes `myverse.fleet` the paved road. ### Friction points where "mostly declarative" needs a decision - **Where the flake lives.** `/etc/nixos` is root-owned — fine for the local-only exit, hostile the moment the artifacts are meant to be pushed to a git remote (no `git push` without sudo, and comin wants its own checkout). Local-only → `/etc/nixos`; fleet exits → somewhere user-owned with `--flake` pointed at it. - **Passwords.** A hashed password in `configuration.nix` is fine until step 7 says "push to your remote". Use `hashedPasswordFile` written out-of-band, or `initialPassword` + forced change. Rule to hold: **the generated artifact set contains zero secrets**, so it is always safe to push. - **Remote + `/etc/nixos`.** For a remote install the artifacts belong on the *operator's* machine, not the target — the target only receives a closure. Screen 7 means something different in each branch and the UI should say so. - **Host key bootstrap.** The one genuinely imperative step: generate the target's SSH host key during install so its pubkey can be shown on screen 10 and registered as a deploy key while the user is still sitting there. Everything else in the flow is file-writing. ### Exits - **Local** — artifacts in `/etc/nixos`, rebuild by hand. No git, no remote. - **New fleet** — init a fleet repo containing `machines/<host>/`, enable `profiles.comin`, print the host pubkey to register as a deploy key. Machine self-updates from then on. - **Join fleet** — clone an existing fleet repo, add `machines/<host>/`, push a branch. Adding a machine becomes a commit, and the installer's whole job was to author it. ### Suggested build order `disko` module with named layouts → facter probe → a `myverse-install` CLI taking a spec JSON → a Dioxus GUI over it. Making the CLI the real interface (GUI as a skin that builds the spec and shells out) means unattended installs, VM-tested installs, and `myverse-install --flake <fleet>#<host>` re-installs all come free, instead of GUI and headless being two implementations. The first three steps are independently useful and testable in a VM; the GUI is not.
Author
Owner

Scope: the CLI is a first-class front end, not a fallback

Making it explicit, since it changes what "done" means for this issue. Three front ends, one engine, and none of them may be a second-class citizen:

  1. myverse-install — interactive prompts in the terminal. Same questions, same generated machines/<host>/, same review step. No desktop, no Wayland, works over SSH into the live image. This is the reference implementation.
  2. GUI — a skin that collects answers into the spec and shells out. Zero install logic of its own.
  3. Unattendedmyverse-install --spec host.toml, no prompts at all. Boot the ISO with a spec (kernel cmdline, a file baked into a custom image, or fetched over the network) and the machine installs itself.

The unattended mode is not a fourth code path. Once the pipeline is spec -> artifacts -> apply, "autoinstall" is just supplying the spec up front instead of typing it, so it costs roughly nothing beyond a serializable spec format and a --spec flag. Worth designing the spec file for that from the start rather than retrofitting.

Practical payoffs beyond convenience: installs become testable in CI (feed a spec, boot a VM, assert the result), a reinstall is re-running the same spec against the existing host dir, and provisioning several machines stops being several sittings.

Acceptance for this issue should be that the CLI can do everything the GUI can, and that at least one machine class can install with no interaction at all.

### Scope: the CLI is a first-class front end, not a fallback Making it explicit, since it changes what "done" means for this issue. Three front ends, one engine, and none of them may be a second-class citizen: 1. **`myverse-install`** — interactive prompts in the terminal. Same questions, same generated `machines/<host>/`, same review step. No desktop, no Wayland, works over SSH into the live image. This is the reference implementation. 2. **GUI** — a skin that collects answers into the spec and shells out. Zero install logic of its own. 3. **Unattended** — `myverse-install --spec host.toml`, no prompts at all. Boot the ISO with a spec (kernel cmdline, a file baked into a custom image, or fetched over the network) and the machine installs itself. The unattended mode is not a fourth code path. Once the pipeline is `spec -> artifacts -> apply`, "autoinstall" is just supplying the spec up front instead of typing it, so it costs roughly nothing beyond a serializable spec format and a `--spec` flag. Worth designing the spec file for that from the start rather than retrofitting. Practical payoffs beyond convenience: installs become testable in CI (feed a spec, boot a VM, assert the result), a reinstall is re-running the same spec against the existing host dir, and provisioning several machines stops being several sittings. Acceptance for this issue should be that the CLI can do everything the GUI can, and that at least one machine class can install with no interaction at all.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
jmarya/myverse#4
No description provided.