draupnir 0.1.9

Draupnir — the nordisk boot/provisioning library: fire up a runtime from one BootSpec across three backends (KVM via tunnr · OCI container · Redfish bare-metal virtual-media) and drive its power lifecycle. Odin's ring that drips eight identical copies → boot a fleet of identical machines from one ISO.
Documentation
# Draupnir — the low-level BOOT / PROVISIONING library of the nordisk
# constellation. One crate, three boot backends: KVM (drives tunnr), OCI
# containers, and Redfish bare-metal virtual-media boot.
#
# Standalone workspace whose root IS the `draupnir` crate (the tunnr /
# Skidbladnir house pattern): this pins it as its own workspace root so a
# parent directory's workspace cannot accidentally absorb it, and so the two
# high-level consumers (jera, Skidbladnir) can path/version-dep it as a leaf
# without dragging a wider tree.
[workspace]
resolver = "2"

[package]
name = "draupnir"
version = "0.1.9"
edition = "2021"
rust-version = "1.85"
description = "Draupnir — the nordisk boot/provisioning library: fire up a runtime from one BootSpec across three backends (KVM via tunnr · OCI container · Redfish bare-metal virtual-media) and drive its power lifecycle. Odin's ring that drips eight identical copies → boot a fleet of identical machines from one ISO."
license = "MIT OR Apache-2.0"
repository = "https://codeberg.org/nordisk/draupnir"
keywords = ["boot", "provisioning", "redfish", "kvm", "container"]
categories = ["virtualization", "os"]

[lib]
name = "draupnir"
path = "src/lib.rs"

[features]
# Lean by design: the default build is PURE std — the trait surface, the
# BootSpec/Machine types, validation and the fleet fan-out all compile with no
# dependency at all. Each live backend sits behind a feature so a lean consumer
# pulls only what it drives.
default = []
# The REAL KVM backend over tunnr's `tunnr_vm::boot_test(BootSpec) -> BootHandle`
# primitive. The `kvm` module + its `Boot`/`Lifecycle` seam always compile (an
# honest, un-feature-gated stub); this feature adds the live tunnr wire. tunnr-vm
# is not yet on crates.io, so a PATH dep is the correct interim (exactly how
# `jera` wires it) — switch to a version once tunnr publishes. Pulls `seed` so the
# KVM adapter can author the cloud-init NoCloud seed *image* it attaches to a VM.
backend-tunnr = ["dep:tunnr-vm", "seed"]
# The **NoCloud cloud-init seed** builder ([`seed`]) — draupnir owns it (ported
# from Skidbladnir). The file-set render + the plain **seed directory** output are
# pure std (always compiled, no dep); THIS feature adds the pure-Rust `fatfs`
# **vfat seed image** authoring (volume label `cidata`, no `genisoimage`/`mkisofs`
# subprocess). Split out from `backend-tunnr` so a consumer can author a seed
# image without dragging the KVM boot path.
seed = ["dep:fatfs"]
# Route this crate's `functional_status` emit markers into real nornir test-matrix
# rows (the constellation-wide introspection-coverage gate). OFF by default so the
# lean core pulls no nornir dep — then `functional_status` is a compiled-out no-op.
testmatrix = ["dep:nornir-testmatrix", "nornir-testmatrix/testmatrix"]
# The REAL OCI container backend over the podman/Docker REST API (`bollard`) — the
# same zero-shell container engine `jera` drives, reused rather than re-rolled. A
# dedicated tokio runtime drives the async API from the synchronous `Boot`/
# `Lifecycle` seam. OFF by default so a plain `cargo build` stays pure-std.
#
# Also carries the image ARCHIVE path (`export_image`/`import_image`): `bytes` for
# bollard's streaming load body, and `serde_json` — already in this manifest for
# Redfish — to read the `manifest.json` at the root of a docker-archive, which is
# how an exported tar names the tags and config digest it actually contains.
backend-oci = ["dep:bollard", "dep:tokio", "dep:futures", "dep:tar", "dep:bytes", "dep:serde_json"]
# The DAEMON-LESS OCI runtime backend over **youki-core** (`libcontainer`): a
# pure-Rust runc alternative that runs containers directly on Linux namespaces +
# cgroups-v2, NO daemon, NO CLI (the in-appliance engine korp-server links). OFF by
# default so a plain `cargo build` stays pure-std and the `bollard` socket path is
# the default container engine. Pulls `tar` (shared with `backend-oci`) for the
# layer→rootfs unpack. Alias-worthy as `oci-native`.
backend-youki = ["dep:libcontainer", "dep:rustix", "dep:flate2", "dep:tar"]
# The REAL bare-metal Redfish backend: a pure-Rust `ureq` (rustls, no
# openssl/native-tls) client over the handful of DMTF Redfish endpoints we drive
# (VirtualMedia insert/eject, the ComputerSystem `Boot` override, and
# ComputerSystem.Reset). OFF by default so a plain `cargo build` stays pure-std
# and offline-green; the request shaping is pure + fixture-tested with no BMC.
backend-redfish = ["dep:ureq", "dep:serde_json"]
# The Redfish **SERVER** — draupnir's own BMC, fronting KVM ([`redfish_server`]).
#
# The other end of `backend-redfish`: a pure-Rust Redfish service that answers the
# six endpoints draupnir's client drives and translates them onto the vocabulary
# that already exists — `InsertMedia` → [`BootSpec::medium`], the `Boot` override →
# [`BootOrder`], `ComputerSystem.Reset` → the [`Boot`]/[`Lifecycle`] seam. That
# turns any Linux box with `/dev/kvm` into a Redfish-controllable machine, which is
# squarely this crate's charter, and it gives the `iso-redfish-sim` matrix form a
# BMC to burn against on a box that has none.
#
# OFF by default and NOT implied by `backend-redfish`: a client-only build must not
# grow a listening socket. Pulls `rustls` (pure Rust, `ring` — the same 0.23 line
# `ureq` already resolves to, so no second TLS stack enters the graph) and `rcgen`
# for the server's TLS identity, because draupnir's own client PINS the BMC
# certificate and a pinned client needs a real cert to pin. `rcgen` — not
# korp-installer's RSA/`x509-cert` `SigningIdentity`, which mints an *Authenticode
# code-signing* identity — is the constellation's TLS-identity crate: it is exactly
# what Skidbladnir's `src/pki.rs` uses (`rcgen 0.13`, `default-features = false`,
# `["pem","ring"]`, proven to load into a real `rustls::ServerConfig`). That code
# cannot simply be called from here: Skidbladnir path-deps *this* crate
# (`Skidbladnir/Cargo.toml:87`), so a dep back onto its `pki` module would be a
# cycle. Same crate, same features, same pattern — one cert story, not two.
redfish-server = ["dep:rustls", "dep:rcgen", "dep:serde_json"]

[dependencies]
# ── threading (always): the ONE sanctioned home for concurrency ──
# ROOT-LAW #0 ("rayon-free" / core-saturation): every thread in the constellation
# originates in gatling, never a bare `std::thread::spawn`. The parallel fleet
# fan-out (`boot_fleet_and_await_parallel`) routes each member's blocking boot
# through `gatling::gatling_forkjoin::gatling_for_each` (one worker per member).
# Published as `rotaryengine`; the library identity stays `gatling`. Path dep
# (offline workspace, all repos are siblings under git/); already in the tree
# transitively via tunnr-vm, so a direct dep adds no new leaf.
gatling = { path = "../znippy-zoomies/gatling", version = "0.1.9", package = "rotaryengine" }
# ── real KVM backend (feature `backend-tunnr`) ──
# The path resolves to the sibling tunnr workspace root whose root crate is
# `tunnr-vm` (lib `tunnr_vm`); OFF by default so a plain `cargo build` has no
# dep and the KVM `boot()` is a compiled-out honest stub.
tunnr-vm = { path = "../tunnr", version = "0.1", optional = true }
# Pure-Rust FAT12/16 authoring for the cloud-init NoCloud seed image (volume label
# `cidata`) the KVM guest's cloud-init reads at first boot — the same `fatfs` crate
# Skidbladnir uses, so there is no `genisoimage`/`mkisofs` subprocess (zero-shell).
# Rides the `seed` feature (which `backend-tunnr` pulls).
fatfs = { version = "0.3", optional = true }
# ── the nornir test-matrix emit sink (feature `testmatrix`) ──
# Lean serde-only crate: turns `functional_status` markers into real matrix rows.
# PATH added 2026-08-01 for `tests/silenced_tests_guard.rs`, which needs the
# `gatedtests` module — that module exists only in edda's unpublished 0.2.8, and
# cargo demands ONE canonical source path for a package across [dependencies] and
# [dev-dependencies]. `version` is kept so `cargo publish` still resolves it from
# the registry for external consumers; locally the sibling path wins (the same
# path+version shape the `gatling` dep above uses).
nornir-testmatrix = { path = "../edda/crates/nornir-testmatrix", version = "0.2", default-features = false, optional = true }
# ── real OCI container backend (feature `backend-oci`) ──
# bollard is the async podman/Docker REST client; pinned to the same 0.20 line
# jera uses so there is one container engine across the constellation. tokio drives
# its async API from draupnir's synchronous seam; futures for the pull/log streams.
bollard = { version = "0.20", optional = true }
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
futures = { version = "0.3", optional = true }
# Pure-Rust tar for the OCI image-build context (tar the build dir → `build_image`)
# and the `create`+`cp` artifact extract (unpack `download_from_container`'s tar) —
# so Skidbladnir's `pack.rs` `podman build` + `podman cp` twin routes through the
# ONE OCI engine. Rides `backend-oci`; the lean default pulls nothing.
tar = { version = "0.4", optional = true }
# `bytes::Bytes` is the chunk type bollard's STREAMING import (`import_image_stream`)
# demands (`Stream<Item = Result<Bytes, E>>`); the non-streaming `import_image` wants
# the whole archive as one `Bytes`, which for a 1.4 GB Spark image is exactly the
# gigabyte-in-RAM we refuse. Not a new crate in the build graph — bollard already
# pulls `bytes` (1.12.1 in `Cargo.lock`); naming it here only makes the type
# constructible. Pinned to bollard's own 1.x line so the types unify.
bytes = { version = "1", optional = true }
# ── the DAEMON-LESS OCI runtime backend (feature `backend-youki`, aka oci-native) ──
# youki-core (`libcontainer`) is the pure-Rust LOW-LEVEL OCI runtime — a runc
# alternative that runs a container DIRECTLY over Linux namespaces + cgroups-v2 with
# NO daemon and NO CLI. This is the in-appliance container engine korp-server (static
# PID-1) links, per Gemini's revised design: mount cgroups, set up namespaces, unpack
# an OCI image's layers into a bundle rootfs, generate `config.json`, and drive the
# create/start/kill/delete lifecycle — all in-process. Zero-shell: single-line rootless
# uid/gid maps are written straight to `/proc/self/uid_map` (no `newuidmap` exec), and
# any boot-path shell-out into a `scratch` bundle is an instant ENOENT.
#   - `v2`      → cgroups-v2 (Unified) only; no cgroup-v1.
#   - `systemd` → the pure-Rust **native-dbus** systemd cgroup manager. libcontainer
#     FORCES the systemd cgroup path whenever a user namespace is present (rootless),
#     so this is required for a rootless run: it creates a transient scope under the
#     caller's delegated `user@UID.service` slice over the session bus — still zero
#     external process (a hand-rolled dbus client, not the `systemctl`/`busctl` CLI).
libcontainer = { version = "0.6", default-features = false, features = ["v2", "systemd"], optional = true }
# geteuid/getegid for the single-line rootless id-map (map container-root → the
# caller's own uid, so youki writes uid_map directly and never execs `newuidmap`).
rustix = { version = "1", default-features = false, features = ["process"], optional = true }
# gzip decode for the OCI-image layer tarballs when unpacking them into a bundle rootfs.
flate2 = { version = "1", optional = true }
# ── real Redfish backend (feature `backend-redfish`) ──
# Pure-Rust HTTP: ureq over rustls (ring) — NO openssl/native-tls (pure-Rust
# charter). `disable_verification` handles the self-signed certs BMCs ship with.
# serde_json shapes the Redfish action bodies.
ureq = { version = "3", default-features = false, features = ["rustls", "json", "gzip"], optional = true }
serde_json = { version = "1", optional = true }
# ── the Redfish SERVER (feature `redfish-server`) ──
# TLS for draupnir's own BMC. rustls 0.23 over **ring** — the exact provider `ureq`
# already resolves to in this tree (`Cargo.lock`: rustls 0.23.42 → ring 0.17.14), so
# the server side adds no second crypto backend and client + server share one stack.
# `std` for the blocking `StreamOwned` seam the accept loop reads/writes through.
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12", "logging"], optional = true }
# The server's TLS identity — a genuine self-signed X.509 with SANs, minted per run
# and held **in memory only** (never written to disk, so no `.key`/`.pem`/`.der` can
# be committed). Same crate, same feature set and same call shape as Skidbladnir's
# `pki::issue_leaf` (`Skidbladnir/Cargo.toml:116`); see the `redfish-server` feature
# note for why the code is mirrored in crate choice rather than imported.
rcgen = { version = "0.13", default-features = false, features = ["pem", "ring"], optional = true }

# ── dev-only: the fleet-boot bench (does NOT affect the lib's runtime deps) ──
# criterion drives the LIGHT serial-vs-parallel fleet-boot arm (`benches/fleet_boot.rs`);
# it is a dev-dependency only, so a consumer's `cargo build` still pulls the pure-std
# lib with no criterion in its tree. The Loki bencher runs the authoritative HEAVY pass.
[dev-dependencies]
criterion = { version = "0.5", default-features = false }
# The SILENCED-TEST guard (`tests/silenced_tests_guard.rs`): edda's
# `nornir_testmatrix::gatedtests::audit_repo`, which fails this repo's own
# `cargo test` the moment a `tests/*.rs` goes dark behind an off-by-default
# feature. Deliberately NO `version` — crates.io's newest nornir-testmatrix
# (0.2.7) has no `gatedtests` module, so a version requirement would be a claim
# the registry cannot honour. A path-only dev-dep is stripped by `cargo publish`,
# so no consumer ever sees this edge.
nornir-testmatrix = { path = "../edda/crates/nornir-testmatrix", default-features = false }
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
# `tests/redfish_server_conformance.rs` re-hashes every vendored DMTF fixture against
# the digests recorded in `tests/fixtures/dmtf/PROVENANCE.md` before it reads them.
# Without that, a fixture quietly edited to make a conformance assertion pass would
# be indistinguishable from conformance — the anchor has to be tamper-evident or it
# is not an anchor. Pure-Rust `sha2` (no `cc`, no openssl); dev-only, so no consumer
# sees it.
sha2 = "0.10"
# The conformance suite's HTTP transport. Deliberately a THIRD-PARTY client rather
# than anything draupnir wrote: if the server emits malformed HTTP, an independent
# implementation is what notices. Same crate/features the `backend-redfish` optional
# dep resolves to, so no second HTTP stack enters the graph.
ureq = { version = "3", default-features = false, features = ["rustls", "json", "gzip"] }
serde_json = "1"

# The fleet-boot bench: serial `boot_fleet_and_await` vs the concurrent
# `boot_fleet_and_await_parallel` over a fake fast backend with a small per-member
# status latency (models a BMC/QEMU/container readback round-trip). `harness = false`
# → criterion owns `main`.
[[bench]]
name = "fleet_boot"
harness = false