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.
- Without
online, no internet client is linked. A source that needs one (for example acratessource, or arust-remoterepository withoutron_base_url) is refused at start, by name. - An omitted
network:inholger.ronisOfflineunless the config names an upstream.Offlinerefuses a config that needs the network and every outbound dial (server/lib/src/egress.rs). server/cli/tests/the_default_build_dials_nothing.rsruns the default binary under a connect trap and fails on any dial.- The one-way downstream (
holger-downstream, default) has no fetch code and dials nothing (server/cli/tests/one_way_ships_no_client.rs). Withfetchit dials only its one upstream.
First start
The first holger writes ~/.holger/holger.ron (or $HOLGER_DATA/holger.ron) and serves on loopback:
- health:
http://127.0.0.1:18444/healthz, - cargo:
sparse+http://127.0.0.1:18444/cache/index/to read,/sparringto publish.
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
- v1/README-v1.md: the estate build rows and their install lines.
- v1/HOLGER-V2.md: systemd, Kubernetes and the appliance, step by step.