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

nordisk/holger

.nornir/agent-design.md

The agent — design

Class: design · Function: agent (a sibling process to the server; produces what storage serves). The whole-system view is design.md.

What it is

The agent is the internet-side half of the airgap split. It runs where there is connectivity, pulls the packages a secure zone needs, and packs them into one signed .znippy archive that an operator carries across the boundary. The secure-side server never needs connectivity — the write happens outside the wall. Binary holger-agent (package holger-agent-cli, agent/cli); logic in agent/lib.

The connector seam — ConnectorTrait

Every source and target implements one trait (traits/src/lib.rs):

method purpose
list_repositories() -> Vec<RemoteRepository> enumerate repos at a source
list_assets(repo) -> Vec<RemoteAsset> enumerate a repo's artifacts
download_asset(asset) -> Vec<u8> pull one artifact's bytes
upload_asset(repo, path, data) push bytes to a writable target

Each connector carries a ConnectorAuth = None | Basic { username, password } | OidcToken { token } | Mtls. Concrete connectors ship as their own crates: agent/connector/nexus (Basic auth) and agent/connector/world (crates.io / PyPI, the external-registry connector). The same ConnectorTrait symmetry means "pull from Nexus, pack to znippy" and "pull from crates.io, pack to znippy" are the same code with different connectors.

The flow

Source ──▶ [agent: list → download → pack + sign] ──▶ .znippy ──(USB/optical)──▶ server
  1. list_repositories / list_assets enumerate what to pull;
  2. download_asset fetches each artifact's bytes;
  3. the set is packed into an immutable, content-addressed (blake3) .znippy — one archive per repository version;
  4. the operator carries the .znippy across the airgap; the server points a read-only repo at it and serves every ecosystem's native protocol out of it.

Hardening (the internet-side attack surface)

The agent is the component with egress, so its download path is guarded: