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

nordisk/holger

docs/v2/02-install.md

2. Install

Holger is four products, one crate each:

cargo install holger                 # the server
cargo install holger-downstream      # the airgapped downstream
cargo install holger-ui              # the web console
cargo install holger-cli             # the command-line tool (with bundle, verify and agent)

From a clone: cargo install --locked --path server/cli, --path holger-downstream and --path holger-cli; the console from a clone of nordisk/holger-ui (cargo xtask console && cargo install --locked --path crates/holger-ui, which carries the console bundle in the program). The README's guide "Three products by hand" sets the four up together.

A clone needs no registered key: git clone ssh://anonymous@git.gunnar.rs:2222/nordisk/holger.git. A plain cargo build --release in it builds target/release/holger only; every crate resolves from crates.io.

The default and the extras

The default features of holger (server/cli) are downstream-core, the twelve formats of chapter 1, secure_update (the intake chain with its scanners), fetch and export-push (it fetches from another Holger, and pushes signed bundles into a drop folder when its RON says so), sign (signed scan evidence, which an airgap export needs), sync (the protocol a downstream enrols with) and systemd. holger-downstream's default is the server's downstream feature: the twelve formats, no scanner, and one-way (packages come in only through its inbox); --features fetch lets it fetch from its one upstream (boundary: Fetch), and downstream-wg adds the WireGuard link.

Every product names the same four extras: all-formats, online, federation and full.

feature what it adds
fetch pulls from another Holger: the mirror timer, the gRPC client, the sync pull and the signed sync receipt. In the default; not in downstream
export-push pushes signed bundles into a drop folder (export: in the RON). In the default
online pulls from internet sources named in holger.ron: crates.io, PyPI, npm, Maven, OCI registries, GOPROXY, APK repositories
all-formats NuGet, gems, conda and Composer beside the twelve (fmt-all is its old name)
federation Artifactory, Nexus, AWS and Azure upstreams through Skidbladnir, and the migration from Nexus and Artifactory
maven-pgp OpenPGP .asc checks for Maven
auth-oidc, auth-oauth2, auth-passkey the other sign-in methods (bearer token and mTLS are in the default)
front the front page at /
full all-formats, online and federation, with maven-pgp and everything of server-lib full

Zero egress by default

The default build opens no outbound connection.

First start

The first holger writes ~/.holger/holger.ron (or $HOLGER_DATA/holger.ron) and serves on loopback:

A later start keeps your edited file. holger start --config <file> runs another config; holger --help lists every verb. As a service: sudo ~/.cargo/bin/holger install add (systemd), or the Helm chart in deploy/helm/holger with its scan-worker sidecar. The chart turns authentication on: an operator token in the Secret <release>-auth, mounted at /etc/holger/auth/token, which its default config names.

To serve beyond loopback, an endpoint needs an authentication method (else holger-listen-1) and TLS (ron_tls), or insecure: true on the endpoint where something else encrypts the wire, such as WireGuard (else holger-listen-2). See chapter 10.

Every scan and every bundle import runs in a container, on the server and on the downstream (chapter 8). Make the scan-worker image with rootless podman: cargo xtask scan-worker image, tagged localhost/holger-scan-worker:<Holger release> (:3.1.0 for Holger 3.1.0). Make it again after every upgrade: the server refuses a worker that is not its build (holger-sandbox-5, chapter 5). Without a container engine (holger-sandbox-1) or without the image (holger-sandbox-8) Holger refuses every scan by name.

Memory

Holger serves a download from its store in pieces, never as one whole body in memory (ZnippyBase::stream_get, file_pieces; every znippy format: pip, npm, Maven, apt, dnf, Helm, gems, Go, conda, Rust, Composer, Docker blobs and NuGet; tests server/lib/tests/a_streamed_download_is_the_whole_body_answer.rs and every_znippy_download_streams.rs). On glibc, holger caps malloc's arenas at 4 at start (server/lib/src/memory.rs); a set MALLOC_ARENA_MAX wins.

Peak RSS per package set, measured by holperf on real packages (the README's speed table; holperf/results/):

run machine holger peak RSS, plain store peak RSS, intake chain
2026-10-31 oden (32 threads, 499 GiB RAM) 2103f732 3643 to 8449 MiB 7373 to 7872 MiB
2026-11-01 oden (32 threads, 499 GiB RAM) ad6d66db 139 to 180 MiB 166 to 191 MiB

cargo xtask bench-readme check fails a set above 512 MiB, half the lean box's 1 GB.

Still in v1