Gunnar

nordisk/holger

holger

Holger guards your artifacts at rest

Immutable artifact repository in pure Rust.

⚡ GATLING — one all-core engine · zero rayon · zero-copy · ~11/12 cores

Build and install

Anyone can clone the repository read-only, with no registered SSH key: git clone ssh://anonymous@git.gunnar.rs:2222/nordisk/holger.git. The commands below use the git@ address, which needs a registered key; the anonymous address works the same way.

git clone ssh://git@git.gunnar.rs:2222/nordisk/holger.git && cd holger && cargo build --release
target/release/holger

A plain cargo build --release at the root builds the server package only (default-members) and links one binary, target/release/holger: the small server-local build with the intake chain (the steps and their scanners), and no bench, no wasm plugins, no online and no metered. No Build Thing and no xtask are needed, and nothing has to sit beside the clone: the root workspace is the open core, so a lone clone resolves every crate from crates.io, and cargo install --path server/cli installs the same server (cargo xtask lone-clone proves both end to end with --locked, /healthz included; until the open sibling crates it lists are on crates.io, --stage packages them from the siblings).

The estate, appliance and build-description crates (holger-fleet, holger-live, the build declaration, xtask, the monetize guest and the estate tests) are members of the separate estate/ workspace (cargo test --manifest-path estate/Cargo.toml --workspace), which builds only in an estate checkout.

In an estate checkout, with ../edda, ../Skidbladnir, ../monetize, ../tunnr, ../draupnir, ../modgunn and ../facett beside it, run cargo xtask siblings once after the clone (every other cargo xtask verb refreshes it too): it copies .cargo/estate-siblings.toml to the uncommitted .cargo/siblings.toml, which .cargo/config.toml includes when it is there, and switches the sibling patches on. cargo xtask starts in any checkout: its launcher (xtask/boot) is a detached workspace with no dependency. The tests against the real ONE box are estate-tests/, an estate/ member (cargo test --manifest-path estate-tests/Cargo.toml), which needs those siblings.

Install it instead:

cargo install --path server/cli
cargo install --git ssh://git@git.gunnar.rs:2222/nordisk/holger.git holger
cargo install holger
holger

The last cargo install line installs the release from crates.io (Holger 2.0.0 and later).

The on-prem downstream gateway (Holger v2.0 H3) is the same package with its own feature set and binary, exactly the Build Thing's downstream row: cargo install --locked --path server/cli --no-default-features --features downstream --bin holger-downstream. It takes packages only from its one upstream Holger (a signed bundle on a stick, or a pull), verifies them, serves Maven, cargo, Python (PEP 503/691), OCI and Go, blocks what its upstream invalidates, and dials nothing else.

The first holger creates ~/.holger/holger.ron (or $HOLGER_DATA/holger.ron) and starts the server on loopback:

To run it as a service: sudo ~/.cargo/bin/holger install add (systemd), or the Helm chart with its scan-worker sidecar (Kubernetes). The exact commands for each flavour are in Holger v2.0 — Run it: three flavours.

The default build is small on purpose: the server, HTTPS (rustls; set ron_tls on an endpoint), the static bearer token and mTLS, the raw and rust (cargo) repositories, and the intake chain (secure_update, which carries supply-chain). online, auth-oidc, auth-oauth2, auth-passkey, front, sign and the other formats (fmt-all, fmt-npm, …) are opt-in features.

The next start keeps your edited holger.ron. holger start --config <file> runs any other config; holger --help lists every verb. --features holger-server-alias also builds the same program under its old name, holger-server. The default build links no monetize crate (a test runs cargo tree -p holger -e normal to prove it).

The server variants

Every server variant row (holger-recipe/src/variants.rs) is a cargo feature set on the same holger package. Metered and shop variants are opt-in only.

variant rowinstall line
server-local (the default)cargo install holger
upstream (the OPEN delivery's default)cargo install holger
downstream (the on-prem gateway alone)cargo install holger --no-default-features --features downstream --bin holger-downstream
embeddedcargo install holger --no-default-features --features embedded,sign,fmt-all,jobs,supply-chain,admission,mirror --bin holger-embedded
minicargo install holger --no-default-features --features mini,appliance,sign,fmt-all,jobs,supply-chain,admission,mirror --bin holger-mikro
fullcargo install holger --no-default-features --features full,front,sign,fmt-all,jobs --bin holger-full
mvp-servercargo install holger --no-default-features --features mvp-server,appliance,paired,auth-passkey,sign,allow-meter,fmt-raw,fmt-rust,fmt-npm,jobs,supply-chain,admission,mirror --bin holger-mvp
mvp-server-muslcargo install holger --no-default-features --features mvp-server,appliance,paired,auth-passkey,sign,allow-meter,fmt-raw,fmt-rust,fmt-npm,jobs,supply-chain,admission,mirror --bin holger-mvp --target x86_64-unknown-linux-musl
mvp-server-metered (opt-in, metered)cargo install holger --no-default-features --features mvp-server,appliance,paired,auth-passkey,sign,metered,admit-share,front,wasm-plugins,meter-guests,fmt-raw,fmt-rust,fmt-npm,jobs,supply-chain,admission,intel-fetch,mirror --bin holger-mvp-metered
mvp-server-musl-metered (opt-in, metered)cargo install holger --no-default-features --features mvp-server,appliance,paired,auth-passkey,sign,metered,admit-share,front,wasm-plugins,meter-guests,fmt-raw,fmt-rust,fmt-npm,jobs,supply-chain,admission,intel-fetch,mirror --bin holger-mvp-metered --target x86_64-unknown-linux-musl
mvp-server-musl-metered-narrate (opt-in, metered, DEV boot log)cargo install holger --no-default-features --features mvp-server,appliance,paired,auth-passkey,sign,metered,admit-share,front,wasm-plugins,meter-guests,fmt-raw,fmt-rust,fmt-npm,jobs,supply-chain,admission,intel-fetch,mirror,dev-narrate --bin holger-mvp-metered --target x86_64-unknown-linux-musl
downstream-musl (a downstream's medium, OX-WGDS3)cargo install holger --no-default-features --features downstream,downstream-wg,paired --bin holger-downstream --target x86_64-unknown-linux-musl

The client side is not in this package: holger-agent-cli and holger-update-bundle.

The open rows: the Build Thing builds the cargo install line (Holger v2.0 H5)

The Build Thing rows of the open products build exactly what one plain cargo install line builds: the same package, bin, features, default switch, target and release profile (holger-recipe/src/twins.rs, held by holger-recipe/tests/rows_equal_cargo_install.rs). cargo xtask twin builds each row and its line in the pinned toolchain image and compares the two binaries: byte-identical for all three. holger-ui's ui row is cargo install --locked --path crates/holger-ui-server in holger-ui.

productBuild Thing rowcargo install linecratesbinaryadds over the default
upstreamupstreamcargo install --locked --path server/cli33250.4 MBnothing: it is the default
downstreamdownstreamcargo install --locked --path server/cli --no-default-features --features downstream --bin holger-downstream32950.1 MBthe gateway rules, sync and the Maven, Python, OCI and Go formats; it drops the scanners and the export
suite-mvpmvp-servercargo install --locked --path server/cli --no-default-features --features mvp-server,appliance,paired,auth-passkey,sign,allow-meter,fmt-raw,fmt-rust,fmt-npm,jobs,supply-chain,admission,mirror --bin holger-mvp40861.2 MBthe estate: pairing, the appliance ceremony, passkeys, the built-in allow-meter, npm, the job queue, admission and the mirror
cliclicargo install --locked --path holger-cli849.8 MBthe thin gRPC client: every console action as a command; no server, it dials only --at

Biggest gap — p50_ms: holger leads naive by 785.3× (0.15 vs 118.82).

v0.1.0 · oden · 32 cores · 2026-07-23

workloadops_per_secplans_per_secrecords_per_secp50_msp99_msp999_msitersmetadata_mbsserved_mbsmean_ms
holger181863761.953.8097M0.150.461.102000348.79108.430.17
artifactory205781.106.1137.6312.241.53
dryrun100
naive8.380.0419M118.82131.88243.84119.35
nexus158321.407.9662.6410.602.00

holger vs Nexus vs Artifactory, served over HTTP on all sides, measured identically. Fair hardware — no throttled rival: each JVM competitor gets every core on the host and ~75 % of its RAM as a fair JVM heap; holger runs in-process on the same cores. The host and core count these numbers were taken on are stamped in the table header above and in the provenance block below — never assume a number from one host is comparable with one from another. Full method + every metric: README-full.

Which rival versions these are, and what they can serve. The numbers above were taken against Sonatype Nexus 3.72.0 (released 2024-09-04) and JFrog Artifactory OSS 6.23.13 (released 2021-02-21). Two limits follow from that and are stated here rather than left for the reader to discover:

The bench suite now also runs both rivals at their current tags (Nexus 3.96.2, Artifactory OSS 7.161.26) as separate arms, so the modern comparison exists beside the historical series rather than replacing it. Method and every version fact: 10-benchmark-baseline.

Where these numbers come from

v0.1.0 · oden · 32 cores · 2026-07-23

hostoden · 32 cores
version0.1.0
measured2026-07-23
arms16
tests16/16 passed
publishableyes

The mashup and bench_status tables above are a static snapshot of the v0.1.0 run (the installed nornir predates those two docs render sections). The rest of this page is still rendered by nornir docs render holger; refresh these two by hand at release time, or restore their nornir:gen blocks once a newer nornir is on PATH.

Release provenance (SLSA Build L3)

Releases are built by dwarves, the build server, in its isolated release-build mode and carry signed SLSA v1 provenance: one in-toto statement per release, every artifact by its sha256, signed on the owner's YubiKey. The bundles are in attestations/, the release key in slsa/; how to verify (cosign, dwarves or Holger itself) and what still keeps us below L3: docs/SLSA.md.

Tier 1 in five minutes

Tier 1 is the server from a plain cargo build plus one RON file that says where packages come from (sources:), what they pass on the way in (steps:) and the rules that hold for every source (rules:). There is no UI and no SVG in tier 1.

1. Clone and build. The plain build carries the intake chain (the steps, the scanners and the offline advisory import). It does not carry online or monetize.

git clone ssh://git@git.gunnar.rs:2222/nordisk/holger.git && cd holger
cargo build --release

Every scan runs in a container, never in the server (rules: (sandbox: required), the default): mint the scan worker image once. It needs rootless podman; without it Holger refuses every scan by name (holger-sandbox-1).

cargo xtask scan-worker image     # localhost/holger-scan-worker:0.1.0, FROM scratch

2. Write holger.ron. Four raw repositories hold the steps, one upload source brings packages in, and four tokens hold the duties (an Importer, an approver, a repository admin and an auditor).

(
    exposed_endpoints: [ (ron_name: "door", ron_url: "127.0.0.1:50051", ron_http_url: Some("127.0.0.1:8080")) ],
    storage_endpoints: [ (ron_name: "disk", ron_storage_type: "znippy", ron_path: "/tmp/holger/store") ],
    repositories: [
        (ron_name: "incoming",   ron_repo_type: "raw", ron_upstreams: [], ron_in: None, ron_out: Some((ron_storage_endpoint: "disk", ron_exposed_endpoint: "door")), ron_data_dir: Some("/tmp/holger/incoming")),
        (ron_name: "quarantine", ron_repo_type: "raw", ron_upstreams: [], ron_in: None, ron_out: Some((ron_storage_endpoint: "disk", ron_exposed_endpoint: "door")), ron_data_dir: Some("/tmp/holger/quarantine")),
        (ron_name: "approved",   ron_repo_type: "raw", ron_upstreams: [], ron_in: None, ron_out: Some((ron_storage_endpoint: "disk", ron_exposed_endpoint: "door")), ron_data_dir: Some("/tmp/holger/approved")),
        (ron_name: "production", ron_repo_type: "raw", ron_upstreams: [], ron_in: None, ron_out: Some((ron_storage_endpoint: "disk", ron_exposed_endpoint: "door")), ron_data_dir: Some("/tmp/holger/production")),
    ],
    audit: (dir: "/tmp/holger/audit", enabled: true),
    auth: Some((
        methods: [
            StaticToken(subject: "ida", token: Some("ida-token-of-sufficient-length")),
            StaticToken(subject: "bob", token: Some("bob-token-of-sufficient-length")),
            StaticToken(subject: "adm", token: Some("adm-token-of-sufficient-length")),
            StaticToken(subject: "aud", token: Some("aud-token-of-sufficient-length")),
        ],
        duties: { "ida": [Importer], "bob": [SecurityApprover], "adm": [RepositoryAdmin], "aud": [Auditor] },
    )),
    sources: [
        (name: "team", kind: upload, gates: (license: (deny: ["AGPL-3.0-only"]), cve: (max: medium))),
    ],
    steps: (approvals: 1, unknown_license: Pass),
    rules: (require_signed: false, require_provenance: false, deny_licenses: ["GPL-3.0-only"]),
)

Two lines loosen the gateway's defaults for this demo, and both say so in the file: require_signed: false, require_provenance: false lets an unsigned file in (the default floor refuses anything without a verified signature), and unknown_license: Pass lets a file that declares no licence through (the default holds it for review).

3. Start it.

target/release/holger start --config holger.ron

4. Upload a package as the Importer.

echo hello > demo-1.0.txt
curl -X PUT -H "Authorization: Bearer ida-token-of-sufficient-length" \
  --data-binary @demo-1.0.txt http://127.0.0.1:8080/incoming/demo-1.0.txt     # 201 INGESTED

5. See its state, with its source and its effective policy, over HTTP and with the CLI:

curl -H "Authorization: Bearer aud-token-of-sufficient-length" \
  "http://127.0.0.1:8080/-/intake/state?name=demo-1.0.txt"
HOLGER_TOKEN=aud-token-of-sufficient-length \
  target/release/holger intake state --server 127.0.0.1:50051 --name demo-1.0.txt

Walk it through the steps: POST /-/intake/advance?name=demo-1.0.txt&verb=advance as bob to SCANNED, POLICY_CHECKED and APPROVED, then as adm to SEALED, as bob to PROMOTED and as adm to AVAILABLE. Production then serves it at /production/demo-1.0.txt. A refusal names the rule and who set it, for example rule `deny_licenses` (source 'team') for source 'team': licence AGPL-3.0-only is denied.

The RON shape

A source's gates (and steps.gates) may only tighten the floor. A gate that would loosen it, such as provenance: off under require_signed: true or cve: (max: critical) under cve_max: high, stops the load by name, with the rule it breaks. An artifact's effective policy is the floor plus its source's gates. It is fixed at ingest, kept on every line of its intake record, and shown by GetArtifactState, /-/intake/state and the audit trail. Every block refuses an unknown key by name, and so does the auth block. The old shape (promotion: with intake:, mirror:, rust-remote repositories) still loads unchanged; it has no floor. Naming steps: and promotion: together is refused.

The default floor (rules: absent, or a rule left out) is a security gateway's default: it fails closed on what this build can check.

Read the real docs

Development setup

The generated docs in this repo are kept in sync by git hooks in .githooks/. Point git at them once per clone:

git config core.hooksPath .githooks

On every commit that stages Rust source, a Cargo.toml, or .nornir/ai-context.toml, the pre-commit hook regenerates the AI-context packs (docs/ai-context.md + docs/ai-context-full.md) with kvasir and stages them, so they never drift; it also re-renders README/CHANGELOG via nornir. A missing tool warns and skips; a kvasir validation error aborts the commit. kvasir is taken from PATH (install it with cargo install --path ../kvasir) or a sibling ../kvasir release build.

Contributing, security and changes

License

AGPL-3.0-only, © 2026 Vetra Automation AB and Ignalina ApS (Rickard Ernst Björn Lundin) (jointly owned in equal shares, 50/50). Commercial terms are available from both co-owners jointly; write to gunnar@gunnar.rs. Versions published to crates.io on or before 2026-10-02 keep the license they were published with (mostly MIT). See LICENSE, NOTICE, COMMERCIAL-LICENSING.md, TRADEMARKS.md and THIRD_PARTY_LICENSES.md.

Files

.cargo/
.forgejo/
.githooks/
.nornir/
LICENSES/
agent/
attestations/
bench-scenarios/
build-space/
crates/
deploy/
distribution/
docs/
errcode/
estate/
estate-tests/
examples/
holger-build/
holger-build-thing/
holger-cli/
holger-estate-front/
holger-front/
holger-recipe/
holger-rs/
intel/
mannequin/
plugin/
proto/
provenance/
scripts/
server/
slsa/
sync/
traits/
update-bundle/
xtask/
.dockerignore
.gitattributes
.gitignore
CHANGELOG.md
CLA.md
CLAUDE.md
COMMERCIAL-LICENSING.md
CONTRIBUTING.md
Cargo.lock
Cargo.toml
LICENSE
NOTICE
README.md
SECURITY.md
THIRD_PARTY_LICENSES.md
TRADEMARKS.md
bench_baseline.builder-64.json
bench_baseline.json
bench_history.jsonl
brokkr.toml
deny.toml
rustfmt.toml

The list is cut short here. Clone the repository for every file.

Newest commits

c1d6d1a160b7Merge slsa3/dwarves-server-builder into main (lane SLSA3): the release provenance's builder is dwarves, the build server; docs/SLSA.md and the scanner test follow. Gate (ci4, dwarf): holger-provenance 47/0 (slsa_dwarf_release 4/0), no lock change2026-10-29
93f1d485c95dSLSA: the builder is dwarves, the build server (lane SLSA3; owner 2026-10-29)2026-10-29
ce4ea28b55faMerge badge-docs/2026-10-29 into main (lane BADGE-DOCS): SECURITY.md, CONTRIBUTING.md, CLA.md (draft, pending the owners' legal review), CHANGELOG.md (2.0.0, Unreleased, earlier versions; from .nornir/CHANGELOG.md), .nornir/README.md synced with README.md, README with the anonymous clone URL and the three links2026-10-29
02cb13430f76Merge origin/main into badge-docs/2026-10-292026-10-29
97696d4366d4docs: README names the anonymous clone URL, links SECURITY, CONTRIBUTING and CHANGELOG2026-10-29