The installer is a Build Thing: estate shapes are gunnar variants
Owner, 2026-09-24:
- "The mock upcloud should run the installer EXACTLY as the installer runs in upcloud."
- "The installer is supposed to be an installer."
- "Nuke all xtask except one line."
- "vm1 vm2 vm3 is a variant."
- "One variant would be plain gunnar."
- "Another variant would be gunnar without monetize, or monetize with open source only."
What is wrong today
private-gunnar-ops/xtask is half the installer. After the boxes pair themselves through the door, estate rebuild's pairing step still does work:
pair()enrols the operator, monetize and gunnar-guest on the primary.appliance_key::driveputs the primary's ticket key into monetize.front::runre-renders the front with the elected roles.
The mock (rebuild_mock.rs) is a second copy of those steps. It refuses the pairing step and pairs from the laptop (--xtask-pairs), so a mock PASS says nothing about the real installer.
The variants (owner's names, 2026-09-24)
| variant | what it installs |
solo |
one gunnar box |
gunnar-twin |
main + twin, paired through the door; no monetize |
gunnar-twin-one |
main + twin METERED, booking in the ONE monetize (the estate enrols there first); the front runs no monetize |
There is exactly ONE monetize: the ONE box (monetize.ignalina.dk; owner 2026-10-12). It is where the products are shown and sold. Monetize never runs on Gunnar's front or UI server: gunnar-twin-monetize and gunnar-twin-monetize-provided are retired, and the kit refuses them by name ([variant-retired]).
Open source is the default. Pro is the suite (owner 2026-09-24): "the Build Thing that uses plugins from vetra/monetize-impl is pro = suite". Pro is not a gunnar feature. It is the COMMERCIAL suite that monetize-impl-build-thing declares (--suite COMMERCIAL --build njord --build holger, monetize-impl branch agent/monetize-build-thing 14fb45b, not yet on main). No variant carries a monetize: the ONE box runs it, open or from the pro suite. There may be more variants.
Model: variant → features → settings → typ. where is a setting on every variant, not a variant of its own:
| setting | values | meaning |
where |
kvm · upcloud (later aws, azure) |
where the VMs run. kvm is what "the mock" was |
The same variant runs on kvm or on upcloud. Only the place the VM lives changes. The medium, the seed, the door, the join, the election, the certificates, the tunnel and the follow are the same code with the same bytes. That makes the mock exact by construction.
Each variant carries an SVG of its system
A Build Thing can carry an SVG of the system it installs (owner 2026-09-24). Each variant gets one: which VMs, the door, the tunnel, and (the ONE variant) the ONE monetize outside the estate. It lives at .nornir/assets/<variant>.svg, the way dwarves' own .nornir/assets/*.svg do. dwarves has no field on a variant for it yet (today its SVGs are doc pictures only), so that is a dwarves gap.
The pictures are generated by cargo xtask svg from xtask/src/variant_svg.rs, with the same renderer and layout checks as the quick start's pictures. A test pins two rules: main and twin boot the SAME medium, and both of them JOIN the door.
vm1, vm2, vm3
- vm1, the front: its OWN medium,
gunnar-front-installer-open(see below): relay, the twin's certification, clock and the UI. Never monetize. - vm2, main and vm3, the twin: THE SAME installer medium, byte for byte (ruling of 2026-09-21). They differ only in the VM's settings (cores, disks). The door decides which one is the twin. No role is baked in.
Two media per estate (owner 2026-09-25)
The owner, 2026-09-25: "twin/main gunnar share ISO, but NOT the ui!! BUILD an iso for them using build thing!!!" (monetize is the ONE box's, owner 2026-10-12.)
| medium | Build Thing variant | payload | boots on |
| the gunnar medium | gunnar-installer-open (or -metered) |
gunnar-server |
vm2 main, vm3 twin |
| the front medium | gunnar-front-installer-open |
gunnar-front |
vm1 front |
Build the front medium like the other one:
cargo xtask iso-installer --variant gunnar-front-installer-open --build kernel \
--param pairing_seed=@<seed.toml of kind front> --param clock=<the front's own sources>
The front medium is derived from the open row in code (gunnar-recipe, front_installer). It has the same kernel, the same machine and the same pairing seed parameter. It lays gunnar-front instead of the git server, and it has no git server argument.
What gunnar-front does at boot, by itself:
- It brings both network cards up and opens the installer's WireGuard link.
- It presents itself to the installer as kind
front. The door only takes a front on the FRONT slot. - It is fed, and it keeps what it was fed in
/data/pairing/joined.toml. - It runs the relay on udp/51820 with its own tunnel key.
- It certifies the twin against the primary.
- It keeps its own clock from the
clocksources and serves signed time on its utility address, port 50123.
What it makes itself at first boot and OFFERS the installer (the installer passes each on as offer.<vm>.<key>):
| offer | value | who takes it |
version |
the build it runs | the install check |
certify_pubkey |
its certify key | the primary enrols it as front-certify |
clock_key |
its time key | every box's clock |
clock_peer |
peer://<utility>:50123 |
every box's clock |
The installer feeds only the operator key, the CA and the estate identity.
The gunnar medium is never the front. If it is fed role front, it refuses by name ([gunnar-medium-is-not-the-front]). The front medium takes no other role ([front-medium-is-only-the-front]).
The front medium also carries the estate's web face (2026-09-25):
- It serves HTTPS itself on tcp/80 and tcp/443 of its public address. There is no Caddy. The certificate is one the installer feeds (
tls.<host>.certandtls.<host>.key), else the one kept on its data disk, else a new one from Let's Encrypt. It asks Let's Encrypt in its own process (HTTP-01 on :80) only when nothing valid has 30 days left. After a failed attempt it waits 15 minutes, doubling up to 6 hours, and the wait survives a reboot. A new certificate is served from the next handshake, with no restart. - It serves the console: gunnar-ui-server linked in, and the built console (gunnar-ui-web
dist/) embedded in the binary. It dials the fed primary's :50051. When the feed names another primary, it is rebuilt and swapped in live. - The metered medium
gunnar-front-installer-metered(the front ofgunnar-twin-one) runs NO monetize: its console sells through the ONE box with the estate's own bearer, and the boxes pull from the ONE box.
The feed keys the front reads (all optional). They go in the FRONT VM's own feed (edda nordisk-pairing 0.5.0 PlanVm::feed): tls.*, plugin.*, plugin_root.* and console_admin_token are refused in the shared feed and on a box, so main and twin never receive them.
| feed key | value |
web_hosts |
the served names, comma-separated; the first names the certificate (default: the estate) |
tls.<host>.cert, tls.<host>.key |
a PEM chain and its key, to carry a certificate over a rebuild |
acme_directory, acme_contact |
the ACME directory (default Let's Encrypt production) and an e-mail |
console_admin_token |
the console's break-glass bearer for the first passkey (at least 32 random characters) |
What the front offers besides (the primary enrols each from the offer):
| offer | value | who takes it |
console_pubkey |
the console's key | principal gunnar-ui, machine, gunnar-admin |
guest_pubkey |
the console's guest key | principal gunnar-guest, machine, no grant |
What the front HANDS OVER at the end of an install (the installer's secret offer, edda nordisk-pairing 0.5.0, asked right before the close): every valid certificate it keeps on its data disk and its private key, as tls.<host>.cert + tls.<host>.key. The installer keeps them in the operator's run folder (secret-offers.toml, 0600) and nowhere else; the next rebuild feeds them to the new front (installer::successor_feed), which serves them at once and asks Let's Encrypt nothing (five certificates per week for the same names).
The installer does the whole install
What xtask does today moves to the door and the boxes:
| today (xtask, after the read) | moves to |
| operator, monetize and gunnar-guest enrolled on the primary | door FEED keys, installed by the primary itself, the same way admin_pubkey already is |
| the primary's ticket key into the ONE monetize | the install's notice to the ONE box carries it (attach::tell_installed) |
| the front re-rendered with the elected roles | the front reads the door's verdict itself |
xtask afterwards
One line: build the chosen estate variant with where=upcloud. Everything else under private-gunnar-ops/xtask/src that ran the install (rebuild.rs, rebuild_mock.rs, pair, elect, twin, appliance_key, the front re-render) is deleted. That happens only once the variant has passed on where=kvm and on where=upcloud.
Open
- DNS switch (grey, orange) and tearing down the previous installation: do they stay as xtask's one line, or become part of the estate variant?
gunnar-twinhas no monetize: does it still have a vm1 front for the door, clock and relay, or does the door live on one of the two boxes?Retired 2026-10-12: there is exactly ONE monetize, the ONE box;gunnar-twin-monetize-providedgunnar-twin-oneenrols there.vm1 as an OS-less tunnr image, or first as it is today?Answered 2026-09-25: vm1 boots its own OS-less medium,gunnar-front-installer-open(see "Two media per estate"). The UI runs on it (never monetize), and it holds its own web certificate (points 7 to 9 there).