Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

docs/v2/09-estate.md

9. The estate

An estate is Holger on its own OS-less boxes, laid and sold through the ONE box. Holger is PID 1; there is no shell and no install command to type.

The boxes

The ONE box lays and pairs them (chapter 6). The builds of the boxes, the front and the media are rows of the MVP suite (chapter 7).

The declaration

Every box boots the same holger.ron, made by one generator, holger_recipe::estate_ron (holger-recipe/src/estate_ron.rs), and staged on the media by cargo xtask estate-media. It turns on, from the first boot:

The estate's name is never a constant. One medium serves every estate: the declaration says https://repo.{estate}, and the box fills in the name the ONE box's installer feeds it.

How build tools reach the estate: chapter 3, with https://repo.<estate> as the address.

Who approves

An artifact reaches APPROVED only after two different people approve it, and neither of them may be the principal that imported it. The duties (group_duties and CONSOLE_DUTIES in estate_ron.rs):

who role duties
console (the bearer each box derives from the front's console key) Admin Importer, RepositoryAdmin, Auditor
group holger-admins (the first passkey admin) Admin Developer, SecurityApprover, RepositoryAdmin, Auditor. Never Importer
group holger-writers Writer Developer, Importer
group holger-readers Reader Developer
group holger-security-approvers Reader Developer, SecurityApprover
group holger-security-auditors Reader Developer, Auditor

The console principal never approves: it holds no SecurityApprover duty, and it imports everything it uploads. Two approvals need two people.

Add a second approver. An admin invites into the approver group. The invite needs a stepped-up passkey session (holger-auth-6, chapter 10):

curl -sS -X POST "https://<estate>/-/auth/passkey/invites" \
  -H "Authorization: Bearer <admin session>" \
  -H 'Content-Type: application/json' \
  -d '{"display": "bert", "duty": "security_approver"}'

The answer carries the invite secret once; it is single use and expires. The invitee opens the console's invite link and registers a passkey. The invite decides the group: "duty": "security_approver" lands in holger-security-approvers, "duty": "importer" in holger-writers. An invite never grants admin. The proof is server/lib/tests/an_estate_admin_invites_its_importer_and_approver.rs.

A RON that grants no duty at all (written before duties existed, or by hand) gets the estate's table at boot: an Admin holds RepositoryAdmin and SecurityApprover and never Importer, and the groups holger-importers, holger-approvers and holger-auditors are added for invites by duty (server_lib::auth::estate_duties, server/lib/src/auth.rs). A declaration that grants any duty is left as written.

Public repositories and the public site

One medium makes no repository public. The ONE box feeds each estate its own list (public_repositories = "crates,npm"), and the box makes exactly those visibility: Public: read with no sign-in while every other read stays gated ([read-gated]). A name the box does not declare is refused by name (server/lib/tests/the_fed_public_repositories_are_read_with_no_sign_in.rs).

The console's public-site switch is the setting console.public-site. The ONE box's signed refeed carries console_public_site = on | off; an admin's flip through /-/settings/console.public-site is kept and audited the same way (server/lib/tests/a_refeed_keeps_the_consoles_public_site_switch.rs).

A dead main

A dead main is rebuilt from its twin. The ONE box lays a fresh main with recharge_from naming the twin's door. The fresh main serves nothing until the recharge (a read or write is holger-twin-15). The recharge refuses before a byte moves when no twin is named, the main is not fresh or the twin holds nothing (holger-twin-16, server/lib/src/recharge_lane.rs). It pulls every artifact and every estate file over mutual TLS (holger.ron byte for byte), and then the twin follows the new main (server/lib/tests/a_dead_main_is_recharged_from_its_remote_twin.rs; holger recharge by hand). What a rebuild keeps: chapter 6.

Growth

The grow plane (holger.grow.v1.GrowService, server/lib/src/grow.rs) is in the estate build (server-lib feature estate); without it every grow call is refused by name.

Health words that need no action

repositories_wanted (server/lib/src/fleet_box.rs) names a repository a paid signup made that this start does not serve yet. Nothing acts on the word. The box's next start (an UpdateEstate from the ONE box, a recharge or a reboot) serves it, and the word clears (server/lib/tests/repositories_wanted_clears_at_the_next_start.rs).

Moved from v1