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

nordisk/holger

docs/v1/HOLGER-V2.md

Holger v2.0

Holger v2.0 is the secure artifact gateway for the usual case: packages come from the internet, are checked once outside, and are served inside an air-gapped site to its build systems. This page is the plan. The decisions behind it are recorded in A31-holger-slim-plan.md §6 (2026-10-25).

What has landed, row by row with status and commit, is in FEATURES.md.

The usual case: internet to on-prem

 INTERNET                      DMZ / OUTSIDE                         ON-PREM (air-gapped)
 ─────────                     ─────────────                         ────────────────────

 Chainguard ──┐
 crates.io  ──┤
 PyPI       ──┤      pull     ┌───────────────────────────┐
 npm        ──┼──────────────►│  HOLGER UPSTREAM           │
 Maven      ──┤               │                           │
 OCI        ──┘               │  1. pull                  │
                              │  2. verify  DSSE · SLSA · │
                              │     in-toto · Sigstore ·  │
                              │     PEP 740 · PGP         │
                              │  3. scan in the sandbox   │
                              │     ClamAV · YARA · CVE · │
                              │     secrets               │
                              │  4. two-person approval   │
                              │  5. sign the sync         │        pull  (when a line exists)
                              │     + invalidations       │ ──────────────────────────────────►┐
                              │                           │                                    │
                              │                           │ ═══ signed znippy bundle (USB) ═══►│
                              │  board: per downstream    │                                    ▼
                              │  · last sync applied      │                        ┌───────────────────────────┐
                              │  · lag                    │                        │  HOLGER DOWNSTREAM         │
                              │  · invalidation reached?  │   ack: direct, or a    │                           │
                              │  · ALARM if not acked     │◄─ signed ack file ─────│  · trusts the upstream's  │
                              │    in time                │   back on the USB      │    signed scan results    │
                              └─────────────┬─────────────┘                        │  · applies each sync      │
                                            │ data (JSON)                          │  · blocks invalidated     │
                              ┌─────────────▼─────────────┐                        │    packages at once       │
                              │  holger-ui (own package)  │                        │    (quarantine, audited)  │
                              │  approve · board · audit  │                        │  · zero egress except its │
                              │  (draws the board)        │                        │    upstream               │
                              └───────────────────────────┘                        └─────────────┬─────────────┘
                                                                                                 │ serves
                                                                                   ┌─────────────▼─────────────┐
                                                                                   │  on-prem build systems    │
                                                                                   │  Maven (Java) · cargo     │
                                                                                   │  modern Python (uv, pip,  │
                                                                                   │  poetry, pdm) · OCI       │
                                                                                   │  containers · Go modules  │
                                                                                   └───────────────────────────┘

What each product does

Capability upstream downstream holger-ui
Pull from the internet (Chainguard, crates.io, PyPI, npm, Maven, OCI) yes no —
Pull from an upstream Holger yes (chain) yes —
Import a signed znippy bundle (USB) yes yes —
Export a signed znippy bundle yes no —
Verify DSSE / SLSA / in-toto / Sigstore / PEP 740 / PGP all verify only —
Scan in the sandbox (ClamAV, YARA, CVE, secrets) yes no: trusts the upstream —
Produce and sign sync metadata, incl. invalidations yes — —
Apply syncs, block invalidated packages — yes —
Acknowledge every sync receives sends shows
Serve build systems: Maven, cargo, modern Python, OCI, Go yes yes, its main job —
Two-person approval, audit yes yes shows
Board (upstreams, downstreams, lag, invalidation reach, alarm) serves data serves data draws it
Web UI inside the server never never the UI is its own package
Outbound network by default only to configured sources zero, except its upstream none

Modern Python means the standard indexes (PEP 503 simple, PEP 691 JSON), wheels and sdists, separate metadata files (PEP 658/714) and attestations (PEP 740), so uv, pip, poetry and pdm all work.

Run it: three flavours

One holger-server, three ways to run it (A31-holger-slim-plan.md §7). The default build dials nothing in all three.

A Linux host with systemd (the exe)

cargo install holger                      # the default build, with the systemd lifecycle
sudo ~/.cargo/bin/holger install add      # the holger user, /usr/local/bin/holger-server, holger.service
systemctl status holger                   # active, enabled at boot
curl http://127.0.0.1:18444/healthz

The first install writes /home/holger/.holger/holger.ron (never over an existing one): two local cargo registries on loopback, /sparring (writable) and /cache. Point cargo at sparse+http://127.0.0.1:18444/sparring/index/ to publish and pull. Edit the RON (ron_http_url to serve the network, more repositories), then sudo systemctl restart holger. Without root, holger install add --scope user installs a systemctl --user unit (loginctl enable-linger $USER keeps it across reboots). sudo ~/.cargo/bin/holger install remove stops and removes the unit; --purge also erases the data and keeps the keys. Logs: journalctl -u holger. Proof: scripts/flavour-systemd-vm.sh (a fresh Ubuntu VM: install, a real cargo publish and pull, a reboot, and an egress log that shows the service opened no outbound connection).

Kubernetes (the container)

cargo xtask k8s image     # holger.rs/nordisk/holger and holger-scan-worker, static musl, FROM scratch
cargo xtask k8s chart     # helm lint + template, the hardened pod checked
helm install holger deploy/helm/holger -n holger --create-namespace \
  --set-file tls.cert=tls.crt --set-file tls.key=tls.key
kubectl -n holger get pods                # 2/2: holger + the scan-worker sidecar

The image is the default build without systemd: no Skidbladnir crate, no D-Bus (server/cli/tests/systemd_flavour.rs). Every scan runs in the sidecar over a unix socket in a shared emptyDir; the sidecar has no network client, no token and no volume but its scratch. /readyz is ready only while the sidecar answers. The default values are an upstream intake chain (incoming → quarantine → approved → production) with no source, behind a NetworkPolicy with no egress. Your own config: --set-file config.ron=holger.ron. A downstream does not scan: --set scanWorker.enabled=false. Until the images are on holger.rs, load them into the cluster yourself (kind load docker-image …, or podman save … | ctr -n k8s.io images import - on a k3s node). GET /-/sandbox/probe (admin) shows the sidecar from inside: its run count, uid, no new privileges, and a deliberate dial that is refused. Proof: scripts/flavour-k8s-cluster.sh (k3s in podman: the default values go Ready, a wheel is inspected in the sidecar, walked to AVAILABLE and pulled byte for byte, and neither pod opens a connection).

The appliance (an estate box)

Holger is PID 1 on its own OS-less box, installed from the Build Thing's medium (the suite-mvp row below) and updated by the ONE box. There is no install command to type.

The plan

Phase What Done when
H1 Clean build A separate estate/ workspace, so the root workspace no longer pulls estate crates. The missing crates are published to crates.io. Tree guards in CI. cargo install holger works from crates.io in a lone clone.
H2 Slim upstream Estate code (about 15,000 lines: twin, grow, peers, replication, recharge, egress) out of the default, behind a default-off feature; the code shared with Gunnar moves to the common crates. The board becomes data; holger-ui draws it. A small built-in allow-meter replaces wasmtime in the open rows. Features untangled (online without PGP, mini = embedded merged into downstream, jobs without supply-chain). The scan worker is installed with the server and its image built locally. The default is clearly smaller, and scans run right after cargo install. The estate cut (HCUT 2026-10-26): server-lib estate (default-off) carries the twin over mutual TLS, certify, the recharge lane, the grow plane and the admin view's disks; the CLI's appliance, paired, metered and full turn it on. Without it a declared replication_door refuses the start, GrowService refuses by name and the Disks pane says it is not measured. The airgap defaults (B2, HCUT): an omitted network: is Offline unless the config names an upstream (the boot says which); a rust-remote with no ron_base_url is refused (holger-offline-3) instead of falling back to crates.io; the_default_build_dials_nothing runs the default binary under the connect trap. A container image is never auto-approved (A29 row 11). Left: the scan worker installed with the server and the two optional estate edges (metered → holger-fleet, paired → holger-live).
H3 Downstream A downstream build: pull from an upstream or import a bundle, verify only, serve Maven, cargo, modern Python, OCI and Go, zero egress. The on-prem gateway runs from one install. Landed 2026-10-26 on H2's names: cargo install --locked --path server/cli --no-default-features --features downstream --bin holger-downstream (the Build Thing's downstream row; the small core downstream-core plus server-lib gateway, intake, bundle-import, sync and the formats); suite-check downstream:*. H2's estate cut landed 2026-10-26 (HCUT): nordisk-twin, appliance-grow and disk-snapshot are behind server-lib estate, so the default is 332 crates (was 338) and the downstream 329 (was 335); the estate rows are unchanged. The downstream is smaller than the default, but not 90% of it.
H4 Sync protocol Signed sync metadata with invalidations; quarantine on the downstream; acknowledgements direct or as a signed file on the USB; the board shows lag and invalidation reach; the alarm when an invalidation is not acknowledged in time. A bad package found later is blocked on every downstream, and the upstream knows it. Landed 2026-10-25: docs/sync.md.
H5 Rows = cargo install The Build Thing rows upstream, downstream, ui and suite-mvp produce exactly what the matching cargo install produces; each row shows its crate count, binary size and what it adds over the default. The products are reproducible both ways. Landed 2026-10-26: the OPEN delivery (upstream, downstream; holger-ui's ui), suite-mvp = the MVP suite's server row mvp-server; holger_recipe::twins::OPEN_ROWS holds each to one cargo install --locked line (the README's table), cargo xtask twin builds both in the pinned image and compares the bytes, the lone clone installs every OPEN row.
H6 Proof An end-to-end gate: internet → upstream → USB → downstream → a real mvn, cargo, uv and docker pull build, plus an invalidation that reaches the downstream and blocks. The demo and the test of the whole chain. Landed 2026-10-26: cargo xtask e2e (server/cli/tests/e2e_internet_to_build_systems.rs in the holger-e2e-clients image, --network none). A fake internet (PyPI, crates, Maven, OCI, GOPROXY) → the upstream pulls, scans and waits for two approvers → holger export, holger-bundle approve ×2 and finalize onto a USB directory → holger-downstream imports and serves → real pip, uv, cargo, mvn, skopeo and go build; then sync invalidate → sync apply holds the wheel, pip and uv fail naming demo-pkg, and the ack shows on the upstream's board. The downstream dials nothing (a connect trap). Receipt: target/e2e/receipt.json. The gate found the Go pull missing: a kind: go source with a url now pulls from a GOPROXY. A29-11 (2026-10-26): ClamAV (a clamd sidecar with only the pinned signature set in distribution/e2e-clients/clamav/) and YARA (the pinned rule in distribution/e2e-clients/yara/) are required at the upstream's intake; an EICAR wheel and a YARA-matching crate are refused at INGESTED by name, and the gate is red if either passes.

H1 and H2 run beside monetize v2.0, because they do not touch the monetize seam. H3 to H6 follow.

What is built through the Build Thing, not by plain cargo

Plain cargo install gives the three open products above: holger (upstream, cargo install --locked --path server/cli), the downstream (cargo install --locked --path server/cli --no-default-features --features downstream --bin holger-downstream) and holger-ui, and beside them the thin client holger-cli (every console action as a command over the same gRPC services; it dials only --at; .nornir/SUITES.md, the cli section). Everything below is built through the Build Thing (cargo xtask / dwarves), because it needs signing, media or monetize.

Row What it is Why the Build Thing
upstream, downstream, ui The same three products as cargo install Reproducible, pinned toolchain, signed release artifacts
suite-mvp An estate: front + main + twin, holger-ui, the board, the estate media Media and installer images, the toolchain pin, signatures; the estate crates (twin, replication, WireGuard pairing) live only here
suite-mvp-metered The sold estate Monetize support: the signed Holger product operator (guest) for the ONE box, the real meter (wasmtime), the estate front's pairing with the ONE box, the buy pages from monetize-site-buy. Never in an open row.
micro (extreme) The smallest air-gap gatekeeper: signed-bundle import, verify, intake and serve, one static musl binary Static musl cross-build and its size guard; kept clearly smaller than downstream
dev suite The old ORIGINAL rows and dev-narrate builds Development only; not a product

Rules that hold for every row: