xtask/src/installtest.rs
//! **`cargo xtask installtest`** — Holger installed in KVM from its own DEV medium, booted from
//! the disk it laid, and proven to SERVE by a push and a pull of one artifact for each of cargo,
//! pip and npm (A9 gap 2, lane B2, 2026-10-21).
//!
//! ```text
//! cargo xtask installtest [--iso <medium>] [--work <dir>] [--install-min N] [--serve-min N]
//! [--keep-disks] [--plan]
//! ```
//!
//! What it does, in order, and every step that misses makes the verb RED (exit 1):
//!
//! 1. **the DEV medium** — the main's medium (`holger-installer-metered`), composed through the
//! Build Thing as `estate-media` composes it (`--opt dev-narrate=on`, `--build kernel`, the korp
//! root, `live_app=on`, and an intel trust policy in the `intel_trust_policy` slot as an airgap
//! estate's operator stages one: here the policy of signers minted for the run), with two
//! differences. It carries NO pairing seed marker: no metadata
//! service and no installer exist here, and a box whose marker finds no metadata refuses to
//! start by name (`REFUSED [pairing-seed-metadata]`, measured on the R10 medium 2026-10-21) —
//! the paired path is the ONE box gate's (`gate holger`). And its `holger_ron` is [`declaration`] — the estate's own declaration (`estate_ron`, the sold, online
//! tier, estate `installtest.invalid`) with the console's bearer and two approvers written in,
//! because no installer feeds this box. The composed medium is moved out of `.iso-out` into
//! the run's directory, so no test medium is ever uploaded by `estate-media --composed`.
//! `--iso <medium>` installs a medium composed elsewhere instead (its record must name this
//! checkout's HEAD for a green receipt);
//! 2. **the install** — SeaBIOS (UpCloud's firmware), the medium as an IDE cdrom, a 30 GiB boot
//! disk and a 20 GiB data disk, `-no-reboot`. The leg ends when the GUEST ends it (the
//! installer's own reboot), never at `INSTALL-OK`: the data set is laid after that marker;
//! 3. **the boot of the laid disks** — no medium, two NICs (UpCloud's public and utility; the
//! medium names `eth1`), the HTTP door (8443) forwarded on the first;
//! 4. **serve** — the leg waits for the door to answer at all, then:
//! 5. **push and pull**, each compared byte for byte by sha256:
//! * **cargo** — `cargo package` of a one-file crate, the publish call `cargo publish` makes
//! (`PUT /crates/api/v1/crates/new`) to the estate's cargo door, walked through the intake
//! chain to AVAILABLE (the coordinate `crates/<name>/<name>-<version>.crate`, lane
//! HOL-CARGO-PUBLISH-DOOR), then the sparse index line (its `cksum`) and the download;
//! * **pip** — a wheel uploaded the way twine uploads it (`POST /incoming/legacy/`), walked
//! through the estate's intake chain to AVAILABLE (`POST /-/intake/advance`: the console
//! imports and seals, two approvers approve), then the PEP 503 page
//! `/production/simple/<project>/` and the file it links;
//! * **npm** — the document `npm publish` sends (`PUT /npm/<package>`) to the estate's npm
//! door, walked through the intake chain to AVAILABLE as the wheel is (the coordinate is
//! the tarball's registry path `<package>/-/<file>.tgz`), then the packument and the
//! tarball it names.
//!
//! One red fault runs first: a pull of a crate nobody pushed must NOT answer 200, so the pull
//! check is proven able to say no (an empty registry is never a pass).
//! 6. **the scan sandbox** (lane HOLGER-MEDIA): the pip walk must have passed through SCANNED (the
//! wheel's INGEST walk and its SCANNED scanners ran in the sandbox the medium carried: crun over
//! the laid worker rootfs, `rules: (sandbox: required)`), and `GET /-/sandbox/probe` (the
//! worker's `probe` mode through that same sandbox) must answer what `holger-sandbox`'s live test
//! asserts: no network, no name resolves, a read-only root, the scratch cap, uid 65532, no
//! capability, no new privileges, a seccomp filter, and a run count above zero.
//!
//! The receipt `.installtest-receipts/holger-installtest-<holger12>.json` names the commits
//! (holger, tunnr, edda, Skidbladnir, dwarves), the medium's sha256, every leg and every probe.
//! It is GREEN only when every leg passed and the medium is this checkout's. The ONE box kit's
//! `cargo xtask gate holger` (private-monetize-ops) reads it: no green gate without a serving main.
//!
//! Gunnar's `installtest` is the pattern (install in KVM, boot the laid disk, push and clone with
//! the product's own protocol, refuse an empty graph); the code is Holger's own. The KVM half is
//! the shared `korp_installer::isoproof::serve` (tunnr).
//!
//! Runs on the machine with `/dev/kvm` and `qemu-system-x86_64`, at nice 19, one at a time
//! (`~/locks/holger-installtest.lock`).
use std::collections::BTreeMap;
use std::io::Write as _;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Duration;
use anyhow::{bail, Context, Result};
use korp_installer::isoproof::serve::{Disk, InstallThenServe, Leg, MediumAttach, Probe};
use korp_installer::isoproof::SlirpNic;
use korp_installer::kvmproof::{self, Machine};
use nornir_build_thing::variant::ArtifactKind;
use sha2::Digest as _;
use crate::{estate_media, forge_door};
/// The medium under test: the main's (and the twin's) DEV medium.
pub const VARIANT: &str = holger_recipe::packaging::METERED_INSTALLER;
/// The estate name the test declaration carries (never a real estate).
pub const ESTATE: &str = "installtest.invalid";
/// The box's HTTP door (`estate_ron::EstateRon::on_a_box`'s `http_bind`).
pub const HTTP_DOOR: u16 = 8443;
/// The receipts' directory in the holger checkout.
pub const RECEIPTS: &str = ".installtest-receipts";
/// The repositories a receipt names (siblings of the holger checkout).
pub const NAMED_BY: [&str; 5] = ["holger", "tunnr", "edda", "Skidbladnir", "dwarves"];
/// The two approvers written into the test declaration.
pub const APPROVERS: [&str; 2] = ["installtest-approver-a", "installtest-approver-b"];
/// The probe artifacts.
pub const CRATE: &str = "holger-installtest-probe";
pub const CRATE_VERSION: &str = "0.1.0";
pub const PIP_PROJECT: &str = "holger-installtest-probe";
pub const PIP_VERSION: &str = "0.1.0";
pub const NPM_PACKAGE: &str = "holger-installtest-probe";
pub const NPM_VERSION: &str = "0.1.0";
/// The disks of the machine (R10's main shape).
pub const BOOT_DISK_GIB: u64 = 30;
pub const DATA_DISK_GIB: u64 = 20;
/// The trust policy's path on the box: the shipped declaration's `intel.trust_policy`, which
/// the medium's `intel_trust_policy` slot fills on the release (lane P6). The tests' own.
#[cfg(test)]
pub fn intel_trust_on_box() -> String {
format!(
"{}/{}",
holger_recipe::estate_ron::RELEASE_ROOT,
holger_recipe::estate_ron::INTEL_TRUST_STAGED
)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Opts {
pub iso: Option<PathBuf>,
pub work: Option<PathBuf>,
pub install_min: u64,
pub serve_min: u64,
pub keep_disks: bool,
pub plan: bool,
}
/// Pure: the verb's words; a stray word is refused, never dropped.
pub fn parse(args: &[String]) -> Result<Opts> {
let mut o = Opts {
iso: None,
work: None,
install_min: 20,
serve_min: 20,
keep_disks: false,
plan: false,
};
let mut it = args.iter();
while let Some(a) = it.next() {
let mut val = |what: &str| {
it.next()
.cloned()
.with_context(|| format!("installtest: {a} takes {what}"))
};
match a.as_str() {
"--iso" => o.iso = Some(PathBuf::from(val("a medium")?)),
"--work" => o.work = Some(PathBuf::from(val("a directory")?)),
"--install-min" => o.install_min = val("minutes")?.parse().context("--install-min is minutes")?,
"--serve-min" => o.serve_min = val("minutes")?.parse().context("--serve-min is minutes")?,
"--keep-disks" => o.keep_disks = true,
"--plan" => o.plan = true,
other => bail!("installtest: `{other}`: cargo xtask installtest [--iso <medium>] [--work <dir>] [--install-min N] [--serve-min N] [--keep-disks] [--plan]"),
}
}
Ok(o)
}
// ── the declaration (pure) ──────────────────────────────────────────────────
/// The principals' bearers of one run (never printed, never in the receipt).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Bearers {
pub console: String,
pub approvers: [String; 2],
}
impl Bearers {
/// Fresh random bearers.
pub fn mint() -> Result<Self> {
let mut f = std::fs::File::open("/dev/urandom").context("/dev/urandom")?;
let mut one = || -> Result<String> {
let mut b = [0u8; 24];
std::io::Read::read_exact(&mut f, &mut b)?;
Ok(format!("installtest-{}", hex(&b)))
};
Ok(Self {
console: one()?,
approvers: [one()?, one()?],
})
}
}
fn hex(b: &[u8]) -> String {
b.iter().map(|x| format!("{x:02x}")).collect()
}
/// Replace `from` by `to` in `s`, exactly once, or refuse by name: the estate declaration
/// changed under the test and the test must be taught the new shape, never guess.
fn once(s: &str, from: &str, to: &str) -> Result<String> {
match s.matches(from).count() {
1 => Ok(s.replacen(from, to, 1)),
n => bail!("[installtest-declaration-drift] the estate declaration carries `{from}` {n} times, the test expects it once: teach installtest::declaration the new shape"),
}
}
/// Pure: **the test medium's declaration** — the estate's own (`estate_ron`, the sold main's
/// online form, for
/// [`ESTATE`]) with what the installer would feed written in: the console's bearer (the shop
/// derives from the front's console key), and two approvers (people an admin would invite). Nothing
/// else differs from what a sold main boots.
pub fn declaration(b: &Bearers) -> Result<String> {
use holger_recipe::estate_ron as er;
// The airgap tier: no egress. Its scan blocks every artifact until an advisory database
// is installed (`no advisory database is installed`, measured on B1's run of e42850ea,
// 2026-10-21), and an airgap box takes one only as a signed bundle verified against
// `intel.trust_policy`. The shipped declaration names it on the release, and the test fills
// that slot as an airgap estate's operator does: its medium is composed with the policy of
// signers minted for the run (`intel_trust_policy`, [`compose`]), and it imports a bundle
// signed by them ([`IntelFeed`]). The intel line is the estate's own, and the scan is
// untouched. (The online form was tried on 2026-10-21: the box's first `intel.refresh` of
// the public feeds had not landed after 40 minutes.)
let base = er::render(&er::EstateRon::on_a_box(Some(ESTATE)).airgapped());
let file = format!(
"StaticToken(subject: \"{}\", token_file: Some(\"{}/{}\"))",
er::CONSOLE_PRINCIPAL,
er::BOX_APPLIANCE_ROOT,
er::CONSOLE_TOKEN_FILE
);
let mut methods = vec![format!(
"StaticToken(subject: \"{}\", token: Some(\"{}\"))",
er::CONSOLE_PRINCIPAL,
b.console
)];
methods.extend(
APPROVERS
.iter()
.zip(&b.approvers)
.map(|(who, t)| format!("StaticToken(subject: \"{who}\", token: Some(\"{t}\"))")),
);
let s = once(
&base,
&format!("methods: [{file}]"),
&format!("methods: [{}]", methods.join(", ")),
)?;
let roles = APPROVERS
.iter()
.map(|a| format!(", \"{a}\": Reader"))
.collect::<String>();
let s = once(
&s,
&format!("roles: {{\"{}\": Admin}}", er::CONSOLE_PRINCIPAL),
&format!("roles: {{\"{}\": Admin{roles}}}", er::CONSOLE_PRINCIPAL),
)?;
let console_duties = format!(
"\"{}\": [{}]",
er::CONSOLE_PRINCIPAL,
er::CONSOLE_DUTIES.join(", ")
);
let approver_duties = APPROVERS
.iter()
.map(|a| format!(", \"{a}\": [Developer, SecurityApprover]"))
.collect::<String>();
once(
&s,
&format!("duties: {{{console_duties}}}"),
&format!("duties: {{{console_duties}{approver_duties}}}"),
)
}
// ── the artifacts (pure, but cargo's package) ───────────────────────────────
/// The publish body `cargo publish` sends: `u32le json | json | u32le crate | crate`.
pub fn cargo_publish_body(name: &str, vers: &str, crate_bytes: &[u8]) -> Vec<u8> {
let meta = serde_json::json!({
"name": name, "vers": vers, "deps": [], "features": {}, "authors": ["holger installtest"],
"description": "Holger's install test probe", "license": "AGPL-3.0-only", "readme": null,
"keywords": [], "categories": [], "links": null,
})
.to_string();
let mut body = Vec::new();
body.extend_from_slice(&(meta.len() as u32).to_le_bytes());
body.extend_from_slice(meta.as_bytes());
body.extend_from_slice(&(crate_bytes.len() as u32).to_le_bytes());
body.extend_from_slice(crate_bytes);
body
}
/// The sparse index path of a crate name (cargo's layout).
pub fn sparse_path(name: &str) -> String {
let n = name.to_ascii_lowercase();
match n.len() {
1 => format!("1/{n}"),
2 => format!("2/{n}"),
3 => format!("3/{}/{n}", &n[..1]),
_ => format!("{}/{}/{n}", &n[..2], &n[2..4]),
}
}
/// Pure: the `cksum` of version `vers` in a sparse index file.
pub fn index_cksum(index: &str, vers: &str) -> Option<String> {
index
.lines()
.filter_map(|l| serde_json::from_str::<serde_json::Value>(l).ok())
.find(|v| v["vers"] == vers)
.and_then(|v| v["cksum"].as_str().map(str::to_string))
}
/// Package a one-file crate with stock `cargo package` and return the `.crate` bytes.
pub fn package_crate(dir: &Path) -> Result<Vec<u8>> {
let src = dir.join("crate");
std::fs::create_dir_all(src.join("src"))?;
std::fs::write(
src.join("Cargo.toml"),
format!("[package]\nname = \"{CRATE}\"\nversion = \"{CRATE_VERSION}\"\nedition = \"2021\"\nlicense = \"AGPL-3.0-only\"\ndescription = \"Holger's install test probe\"\n\n[workspace]\n"),
)?;
std::fs::write(src.join("src/lib.rs"), "//! Holger's install test probe.\npub fn probe() -> &'static str { \"holger-installtest\" }\n")?;
let cargo = std::env::var_os("CARGO").unwrap_or_else(|| "cargo".into());
let target = dir.join("crate-target");
let st = Command::new(&cargo)
.args(["package", "--no-verify", "--allow-dirty", "--offline"])
.env("CARGO_TARGET_DIR", &target)
.current_dir(&src)
.status()
.context("spawn cargo package")?;
if !st.success() {
bail!("[installtest-cargo-package] `cargo package` exited {st}");
}
let file = target
.join("package")
.join(format!("{CRATE}-{CRATE_VERSION}.crate"));
std::fs::read(&file).with_context(|| format!("[installtest-cargo-package] {}", file.display()))
}
fn crc32(data: &[u8]) -> u32 {
let mut crc = 0xFFFF_FFFFu32;
for b in data {
crc ^= *b as u32;
for _ in 0..8 {
crc = if crc & 1 != 0 {
(crc >> 1) ^ 0xEDB8_8320
} else {
crc >> 1
};
}
}
!crc
}
fn b64(data: &[u8], url: bool) -> String {
let abc: &[u8; 64] = if url {
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
} else {
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
};
let mut out = String::new();
for c in data.chunks(3) {
let n = (c[0] as u32) << 16
| (*c.get(1).unwrap_or(&0) as u32) << 8
| *c.get(2).unwrap_or(&0) as u32;
for i in 0..4 {
if i <= c.len() {
out.push(abc[(n >> (18 - 6 * i) & 63) as usize] as char);
} else if !url {
out.push('=');
}
}
}
out
}
/// A stored (uncompressed) zip of `members`.
pub fn zip_stored(members: &[(String, Vec<u8>)]) -> Vec<u8> {
let (mut out, mut central) = (Vec::new(), Vec::new());
for (name, body) in members {
let crc = crc32(body);
let offset = out.len() as u32;
out.extend_from_slice(&0x0403_4b50u32.to_le_bytes());
out.extend_from_slice(&[20, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
out.extend_from_slice(&crc.to_le_bytes());
out.extend_from_slice(&(body.len() as u32).to_le_bytes());
out.extend_from_slice(&(body.len() as u32).to_le_bytes());
out.extend_from_slice(&(name.len() as u16).to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out.extend_from_slice(name.as_bytes());
out.extend_from_slice(body);
central.extend_from_slice(&0x0201_4b50u32.to_le_bytes());
central.extend_from_slice(&[20, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
central.extend_from_slice(&crc.to_le_bytes());
central.extend_from_slice(&(body.len() as u32).to_le_bytes());
central.extend_from_slice(&(body.len() as u32).to_le_bytes());
central.extend_from_slice(&(name.len() as u16).to_le_bytes());
central.extend_from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
central.extend_from_slice(&offset.to_le_bytes());
central.extend_from_slice(name.as_bytes());
}
let (cd_at, cd_len, n) = (out.len() as u32, central.len() as u32, members.len() as u16);
out.extend_from_slice(¢ral);
out.extend_from_slice(&0x0605_4b50u32.to_le_bytes());
out.extend_from_slice(&[0, 0, 0, 0]);
out.extend_from_slice(&n.to_le_bytes());
out.extend_from_slice(&n.to_le_bytes());
out.extend_from_slice(&cd_len.to_le_bytes());
out.extend_from_slice(&cd_at.to_le_bytes());
out.extend_from_slice(&0u16.to_le_bytes());
out
}
/// The wheel file name of the pip probe.
pub fn wheel_name() -> String {
format!(
"{}-{PIP_VERSION}-py3-none-any.whl",
PIP_PROJECT.replace('-', "_")
)
}
/// Pure: a valid pure-Python wheel (METADATA, WHEEL, RECORD).
pub fn wheel_bytes() -> Vec<u8> {
let pkg = PIP_PROJECT.replace('-', "_");
let info = format!("{pkg}-{PIP_VERSION}.dist-info");
let mut m: Vec<(String, Vec<u8>)> = vec![
(format!("{pkg}/__init__.py"), format!("__version__ = \"{PIP_VERSION}\"\n").into_bytes()),
(format!("{info}/METADATA"), format!("Metadata-Version: 2.1\nName: {PIP_PROJECT}\nVersion: {PIP_VERSION}\nLicense-Expression: MIT\n").into_bytes()),
(format!("{info}/WHEEL"), b"Wheel-Version: 1.0\nGenerator: holger-installtest\nRoot-Is-Purelib: true\nTag: py3-none-any\n".to_vec()),
];
let mut record = String::new();
for (name, body) in &m {
record.push_str(&format!(
"{name},sha256={},{}\n",
b64(&sha2::Sha256::digest(body), true),
body.len()
));
}
record.push_str(&format!("{info}/RECORD,,\n"));
m.push((format!("{info}/RECORD"), record.into_bytes()));
zip_stored(&m)
}
/// Pure: the multipart body twine sends to `/legacy/`.
pub fn twine_form(file: &str, content: &[u8]) -> (String, Vec<u8>) {
let boundary = "----holger-installtest-twine";
let mut body = Vec::new();
let mut field = |name: &str, value: &str| {
body.extend_from_slice(format!("--{boundary}\r\nContent-Disposition: form-data; name=\"{name}\"\r\n\r\n{value}\r\n").as_bytes())
};
for (k, v) in [
(":action", "file_upload"),
("protocol_version", "1"),
("metadata_version", "2.1"),
("name", PIP_PROJECT),
("version", PIP_VERSION),
("filetype", "bdist_wheel"),
("pyversion", "py3"),
] {
field(k, v);
}
field("sha256_digest", &sha256_hex(content));
body.extend_from_slice(format!("--{boundary}\r\nContent-Disposition: form-data; name=\"content\"; filename=\"{file}\"\r\nContent-Type: application/octet-stream\r\n\r\n").as_bytes());
body.extend_from_slice(content);
body.extend_from_slice(format!("\r\n--{boundary}--\r\n").as_bytes());
(format!("multipart/form-data; boundary={boundary}"), body)
}
/// Pure: an npm package tarball (`package/package.json`, `package/index.js`), gzip-stored.
pub fn npm_tarball() -> Vec<u8> {
fn header(name: &str, size: usize) -> [u8; 512] {
let mut h = [0u8; 512];
h[..name.len()].copy_from_slice(name.as_bytes());
h[100..107].copy_from_slice(b"0000644");
h[108..115].copy_from_slice(b"0000000");
h[116..123].copy_from_slice(b"0000000");
h[124..135].copy_from_slice(format!("{size:011o}").as_bytes());
h[136..147].copy_from_slice(b"00000000000");
h[156] = b'0';
h[257..263].copy_from_slice(b"ustar\0");
h[263..265].copy_from_slice(b"00");
h[148..156].copy_from_slice(b" ");
let sum: u32 = h.iter().map(|b| *b as u32).sum();
h[148..155].copy_from_slice(format!("{sum:06o}\0").as_bytes());
h
}
let files = [
("package/package.json", format!("{{\"name\":\"{NPM_PACKAGE}\",\"version\":\"{NPM_VERSION}\",\"main\":\"index.js\",\"license\":\"MIT\"}}\n").into_bytes()),
("package/index.js", b"module.exports = 'holger-installtest';\n".to_vec()),
];
let mut tar = Vec::new();
for (name, body) in &files {
tar.extend_from_slice(&header(name, body.len()));
tar.extend_from_slice(body);
tar.resize(tar.len().div_ceil(512) * 512, 0);
}
tar.resize(tar.len() + 1024, 0);
// gzip with stored deflate blocks (no compressor needed, any gunzip reads it).
let mut gz = vec![0x1f, 0x8b, 8, 0, 0, 0, 0, 0, 0, 255];
for (i, c) in tar.chunks(65535).enumerate() {
let last = (i + 1) * 65535 >= tar.len();
gz.push(u8::from(last));
gz.extend_from_slice(&(c.len() as u16).to_le_bytes());
gz.extend_from_slice(&(!(c.len() as u16)).to_le_bytes());
gz.extend_from_slice(c);
}
gz.extend_from_slice(&crc32(&tar).to_le_bytes());
gz.extend_from_slice(&(tar.len() as u32).to_le_bytes());
gz
}
/// Pure: the document `npm publish` PUTs.
pub fn npm_publish_doc(tgz: &[u8]) -> String {
let file = format!("{NPM_PACKAGE}-{NPM_VERSION}.tgz");
let sha1 = sha1_hex(tgz);
let integrity = format!("sha512-{}", b64(&sha2::Sha512::digest(tgz), false));
serde_json::json!({
"_id": NPM_PACKAGE, "name": NPM_PACKAGE, "dist-tags": {"latest": NPM_VERSION},
"versions": {NPM_VERSION: {"name": NPM_PACKAGE, "version": NPM_VERSION, "main": "index.js", "license": "MIT",
"dist": {"shasum": sha1, "integrity": integrity, "tarball": format!("/npm/{NPM_PACKAGE}/-/{file}")}}},
"_attachments": {file: {"content_type": "application/octet-stream", "data": b64(tgz, false), "length": tgz.len()}},
})
.to_string()
}
/// SHA-1 (npm's `shasum`), small and self-contained.
pub fn sha1_hex(data: &[u8]) -> String {
let mut h: [u32; 5] = [
0x6745_2301,
0xEFCD_AB89,
0x98BA_DCFE,
0x1032_5476,
0xC3D2_E1F0,
];
let mut m = data.to_vec();
let bits = (data.len() as u64) * 8;
m.push(0x80);
while m.len() % 64 != 56 {
m.push(0);
}
m.extend_from_slice(&bits.to_be_bytes());
for block in m.chunks(64) {
let mut w = [0u32; 80];
for i in 0..16 {
w[i] = u32::from_be_bytes([
block[4 * i],
block[4 * i + 1],
block[4 * i + 2],
block[4 * i + 3],
]);
}
for i in 16..80 {
w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1);
}
let [mut a, mut b, mut c, mut d, mut e] = h;
for (i, wi) in w.iter().enumerate() {
let (f, k) = match i {
0..=19 => ((b & c) | (!b & d), 0x5A82_7999),
20..=39 => (b ^ c ^ d, 0x6ED9_EBA1),
40..=59 => ((b & c) | (b & d) | (c & d), 0x8F1B_BCDC),
_ => (b ^ c ^ d, 0xCA62_C1D6),
};
let t = a
.rotate_left(5)
.wrapping_add(f)
.wrapping_add(e)
.wrapping_add(k)
.wrapping_add(*wi);
e = d;
d = c;
c = b.rotate_left(30);
b = a;
a = t;
}
for (x, y) in h.iter_mut().zip([a, b, c, d, e]) {
*x = x.wrapping_add(y);
}
}
h.iter().map(|x| format!("{x:08x}")).collect()
}
pub fn sha256_hex(b: &[u8]) -> String {
hex(&sha2::Sha256::digest(b))
}
// ── the probe (HTTP against the forwarded door) ─────────────────────────────
/// One HTTP answer: status and body. `Err` is a transport failure (nothing answered).
type Answer = std::result::Result<(u16, Vec<u8>), String>;
struct Door {
base: String,
agent: ureq::Agent,
}
impl Door {
fn new(port: u16) -> Self {
let agent: ureq::Agent = ureq::Agent::config_builder()
.http_status_as_error(false)
.timeout_global(Some(Duration::from_secs(60)))
.build()
.into();
Self {
base: format!("http://127.0.0.1:{port}"),
agent,
}
}
fn call(
&self,
method: &str,
path: &str,
bearer: Option<&str>,
ctype: Option<&str>,
body: Option<&[u8]>,
) -> Answer {
let url = format!("{}{path}", self.base);
let read =
|r: std::result::Result<ureq::http::Response<ureq::Body>, ureq::Error>| -> Answer {
let mut r = r.map_err(|e| e.to_string())?;
let st = r.status().as_u16();
let b = r
.body_mut()
.with_config()
.limit(64 << 20)
.read_to_vec()
.map_err(|e| e.to_string())?;
Ok((st, b))
};
match method {
"GET" => {
let mut q = self.agent.get(&url);
if let Some(t) = bearer {
q = q.header("Authorization", format!("Bearer {t}"));
}
read(q.call())
}
_ => {
let mut q = if method == "PUT" {
self.agent.put(&url)
} else {
self.agent.post(&url)
};
if let Some(t) = bearer {
q = q.header("Authorization", format!("Bearer {t}"));
}
if let Some(c) = ctype {
q = q.header("Content-Type", c);
}
read(q.send(body.unwrap_or_default()))
}
}
}
}
fn said(a: &Answer) -> String {
match a {
Ok((st, b)) => format!(
"http {st}: {}",
String::from_utf8_lossy(&b[..b.len().min(200)]).trim()
),
Err(e) => format!("no answer: {e}"),
}
}
/// What the probe carries in: the artifacts and the bearers.
pub struct Artifacts {
pub crate_bytes: Vec<u8>,
pub wheel: Vec<u8>,
pub tgz: Vec<u8>,
}
/// One probe's result.
#[derive(Debug, Clone)]
pub struct ProbeRow {
pub name: String,
pub pass: bool,
pub detail: String,
}
fn row(name: &str, r: std::result::Result<String, String>) -> ProbeRow {
match r {
Ok(d) => ProbeRow {
name: name.into(),
pass: true,
detail: d,
},
Err(e) => ProbeRow {
name: name.into(),
pass: false,
detail: e,
},
}
}
/// **The estate board on the laid box** (lane E3; A18 §4 #8): `GET /-/estate.json` is admin-read
/// (anonymous 401, an approver — a Reader with duties — 403), and the console's bearer is handed
/// the board's rows (data; holger-ui draws them, H2 #1): this box first, as the main, up, and the
/// consumers counted (the metered row carries `admission`, so the book is kept). The install
/// test is ONE main: the twin and a downstream on the board are proven in `server/lib/tests`
/// (the twin follow, the A13 board test).
fn board_probe(d: &Door, b: &Bearers) -> std::result::Result<String, String> {
let anon = d.call("GET", BOARD_PATH, None, None, None);
if !matches!(anon, Ok((401, _))) {
return Err(format!(
"[installtest-board-anonymous] anonymous GET {BOARD_PATH} must be 401, got {}",
said(&anon)
));
}
let approver = d.call("GET", BOARD_PATH, Some(&b.approvers[0]), None, None);
if !matches!(approver, Ok((403, _))) {
return Err(format!(
"[installtest-board-approver] an approver (Reader) on {BOARD_PATH} must be 403, got {}",
said(&approver)
));
}
let (st, body) = d.call("GET", BOARD_PATH, Some(&b.console), None, None)?;
if st != 200 {
return Err(format!(
"[installtest-board-console] the console's GET {BOARD_PATH}: http {st}: {}",
String::from_utf8_lossy(&body[..body.len().min(200)])
));
}
let text = String::from_utf8_lossy(&body);
let v: serde_json::Value = serde_json::from_slice(&body).map_err(|e| {
format!(
"[installtest-board-data] the console's board is not JSON ({e}): {}",
first_chars(&text, 200)
)
})?;
let me = &v["boxes"][0];
if me["this_box"].as_bool() != Some(true) {
return Err(format!(
"[installtest-board-data] the board's first row is not this box: {}",
first_chars(&text, 300)
));
}
if me["state"].as_str() != Some("up") {
return Err(format!(
"[installtest-board-state] this box is not up: {}",
first_chars(&me.to_string(), 300)
));
}
if v["consumers"]["counted"].as_bool() != Some(true) {
return Err(format!("[installtest-board-consumers] the metered main keeps the admission book, and its board does not count consumers: {}", v["consumers"]));
}
let summary = v["summary"].as_str().unwrap_or_default();
let consumers = summary
.find("consumers: ")
.map(|at| &summary[at..])
.unwrap_or(summary);
Ok(format!(
"401 anonymous, 403 approver, 200 console: {} bytes of JSON, this box up, {}",
body.len(),
consumers.trim()
))
}
/// The estate board's HTTP path (server-lib `exposed::http::ESTATE_JSON_PATH`).
const BOARD_PATH: &str = "/-/estate.json";
/// The first `n` characters of `s`, on a character boundary (the board's words carry `·`).
fn first_chars(s: &str, n: usize) -> &str {
match s.char_indices().nth(n) {
Some((i, _)) => &s[..i],
None => s,
}
}
/// **The scan sandbox, from inside the laid box** (lane HOLGER-MEDIA): the same assertions as
/// `holger-sandbox`'s `live_podman` probe, over the medium's own engine. `walk` is the pip walk
/// (it must show SCANNED: the scan ran in the sandbox, or the chain would have refused by name).
fn sandbox_probe(d: &Door, b: &Bearers, walk: &[String]) -> std::result::Result<String, String> {
if !walk.iter().any(|w| w.ends_with("→SCANNED")) {
return Err(format!(
"[installtest-sandbox-walk] the pip walk never reached SCANNED: {}",
walk.join(", ")
));
}
let v: serde_json::Value = match d.call("GET", "/-/sandbox/probe", Some(&b.console), None, None)
{
Ok((200, body)) => serde_json::from_slice(&body)
.map_err(|e| format!("[installtest-sandbox-probe] not JSON: {e}"))?,
other => {
return Err(format!(
"[installtest-sandbox-probe] GET /-/sandbox/probe: {}",
said(&other)
))
}
};
let s = |k: &str| v[k].as_str().unwrap_or("").to_string();
let mut red = Vec::new();
for k in ["connect_public", "connect_gateway"] {
if !s(k).starts_with("refused") {
red.push(format!("{k}={}", s(k)));
}
}
if s("resolve").starts_with("Ok(") {
red.push(format!("resolve={}", s("resolve")));
}
for k in ["write_root", "write_etc", "write_in", "write_traversal"] {
if !s(k).starts_with("refused") {
red.push(format!("{k}={}", s(k)));
}
}
if s("write_scratch") != "written" {
red.push(format!("write_scratch={}", s("write_scratch")));
}
if !s("write_past_scratch_cap").starts_with("refused") {
red.push(format!(
"write_past_scratch_cap={}",
s("write_past_scratch_cap")
));
}
if !s("uid").starts_with("65532") {
red.push(format!("uid={}", s("uid")));
}
if s("cap_eff") != "0000000000000000" {
red.push(format!("cap_eff={}", s("cap_eff")));
}
if s("no_new_privs") != "1" {
red.push(format!("no_new_privs={}", s("no_new_privs")));
}
if s("seccomp") != "2" {
red.push(format!("seccomp={}", s("seccomp")));
}
if v["runs"].as_u64().unwrap_or(0) == 0 {
red.push("runs=0".into());
}
if !red.is_empty() {
return Err(format!(
"[installtest-sandbox-limits] the sandbox's limits do not hold from inside: {} ({v})",
red.join(", ")
));
}
Ok(format!(
"the pip walk passed SCANNED in the sandbox; from inside: no network, read-only root, scratch cap holds, uid {}, CapEff {}, NoNewPrivs {}, Seccomp {}; engine: {}; {} runs",
s("uid").split_whitespace().next().unwrap_or(""),
s("cap_eff"),
s("no_new_privs"),
s("seccomp"),
s("engine"),
v["runs"]
))
}
/// Signed in (HHUNT-1): on a gated estate an anonymous pull is 401 whatever is there, so only a
/// signed-in pull of a crate nobody pushed shows that the pull check can say no.
fn red_fault(d: &Door, b: &Bearers) -> std::result::Result<String, String> {
let path = format!("/crates/crates/{CRATE}/{CRATE_VERSION}/download");
match d.call("GET", &path, Some(&b.console), None, None) {
Ok((200, b)) => Err(format!("[installtest-red-fault] a pull of a crate nobody pushed answered 200 ({} bytes): the pull check could never say no", b.len())),
Ok((st, _)) => Ok(format!("a pull before any push is refused (http {st})")),
Err(e) => Err(format!("[installtest-red-fault] the door did not answer the empty pull: {e}")),
}
}
/// The probes pull SIGNED IN: the estate medium gates reads (`access: (reads: Gated)`, HCON-5),
/// so a pull carries the console's bearer as a user's client carries the estate's token. The
/// anonymous refusal is the `red-fault-empty-pull` row's.
fn cargo_probe(d: &Door, b: &Bearers, a: &Artifacts) -> std::result::Result<String, String> {
let want = sha256_hex(&a.crate_bytes);
let put = d.call(
"PUT",
"/crates/api/v1/crates/new",
Some(&b.console),
Some("application/octet-stream"),
Some(&cargo_publish_body(CRATE, CRATE_VERSION, &a.crate_bytes)),
);
if !matches!(put, Ok((200..=299, _))) {
return Err(format!(
"[installtest-cargo-push] cargo publish: {}",
said(&put)
));
}
// The publish is INGESTED at the cargo door; Holger lists it only once it is AVAILABLE.
let walk = walk_to_available(
d,
b,
&format!("crates/{CRATE}/{CRATE}-{CRATE_VERSION}.crate"),
"cargo",
)?;
let idx = d.call(
"GET",
&format!("/crates/index/{}", sparse_path(CRATE)),
Some(&b.console),
None,
None,
);
let cksum = match &idx {
Ok((200, body)) => index_cksum(&String::from_utf8_lossy(body), CRATE_VERSION),
_ => None,
};
if cksum.as_deref() != Some(want.as_str()) {
return Err(format!("[installtest-cargo-index] the sparse index names {CRATE} {CRATE_VERSION} with cksum {cksum:?}, the crate is {want}: {}", said(&idx)));
}
match d.call(
"GET",
&format!("/crates/crates/{CRATE}/{CRATE_VERSION}/download"),
Some(&b.console),
None,
None,
) {
Ok((200, got)) if sha256_hex(&got) == want => Ok(format!(
"published, walked ({}) and pulled back {} bytes, sha256 {}",
walk.join(", "),
got.len(),
&want[..16]
)),
other => Err(format!(
"[installtest-cargo-pull] the download is not the pushed crate: {}",
said(&other)
)),
}
}
fn pip_probe(d: &Door, b: &Bearers, a: &Artifacts) -> std::result::Result<String, String> {
let file = wheel_name();
let want = sha256_hex(&a.wheel);
let (ctype, body) = twine_form(&file, &a.wheel);
let up = d.call(
"POST",
"/incoming/legacy/",
Some(&b.console),
Some(&ctype),
Some(&body),
);
if !matches!(up, Ok((200..=299, _))) {
return Err(format!(
"[installtest-pip-push] twine upload: {}",
said(&up)
));
}
let walk = walk_to_available(d, b, &file, "pip")?;
let page = d.call(
"GET",
&format!("/production/simple/{PIP_PROJECT}/"),
Some(&b.console),
None,
None,
);
let html = match &page {
Ok((200, b)) => String::from_utf8_lossy(b).to_string(),
_ => {
return Err(format!(
"[installtest-pip-index] the simple page: {}",
said(&page)
))
}
};
if !html.contains(&file) || !html.contains(&want) {
return Err(format!(
"[installtest-pip-index] the simple page does not list {file} with sha256 {}: {}",
&want[..16],
&html[..html.len().min(300)]
));
}
match d.call(
"GET",
&format!("/production/{file}"),
Some(&b.console),
None,
None,
) {
Ok((200, got)) if sha256_hex(&got) == want => Ok(format!(
"uploaded, walked ({}), listed and pulled back {} bytes, sha256 {}",
walk.join(", "),
got.len(),
&want[..16]
)),
other => Err(format!(
"[installtest-pip-pull] the linked file is not the pushed wheel: {}",
said(&other)
)),
}
}
/// The walk a pip probe's detail names (`walked (console→SCANNED, …)`), for the sandbox row.
fn walk_of(detail: &str) -> Vec<String> {
detail
.split_once("walked (")
.and_then(|(_, rest)| rest.split_once(')'))
.map(|(w, _)| w.split(", ").map(str::to_string).collect())
.unwrap_or_default()
}
/// The intake walk: whoever may take the next step takes it, until AVAILABLE. `name` is the
/// coordinate (the wheel's file name, the npm tarball's registry path); `leg` names the code.
fn walk_to_available(
d: &Door,
b: &Bearers,
name: &str,
leg: &str,
) -> std::result::Result<Vec<String>, String> {
let actors: Vec<&str> = std::iter::once(b.console.as_str())
.chain(b.approvers.iter().map(String::as_str))
.collect();
let mut walk = Vec::new();
let mut state = String::from("INGESTED");
'walk: for _ in 0..12 {
if state == "AVAILABLE" {
break;
}
let mut refusals = Vec::new();
for (i, t) in actors.iter().enumerate() {
let r = d.call(
"POST",
&format!("/-/intake/advance?name={name}&verb=advance"),
Some(t),
None,
Some(b""),
);
match &r {
Ok((200, body)) => {
let v: serde_json::Value = serde_json::from_slice(body).unwrap_or_default();
state = v["state"].as_str().unwrap_or("?").to_string();
walk.push(format!(
"{}→{state}",
if i == 0 { "console" } else { APPROVERS[i - 1] }
));
continue 'walk;
}
_ => refusals.push(said(&r)),
}
}
return Err(format!(
"[installtest-{leg}-walk] held at {state}; nobody may advance it ({}); walk so far: {}",
refusals.join(" | "),
walk.join(", ")
));
}
if state != "AVAILABLE" {
return Err(format!(
"[installtest-{leg}-walk] never AVAILABLE: {}",
walk.join(", ")
));
}
Ok(walk)
}
/// **The advisory database the airgap box imports**: three ed25519 signers minted for this
/// run (two approvers and a release signer), the trust policy that pins exactly them for
/// `holger.intel/v1`, and a signed bundle of a fresh advisory set.
pub struct IntelFeed {
pub policy_ron: String,
pub bundle: Vec<u8>,
/// The bundle's sequence: the one the admin's first-import authorization names.
pub sequence: u64,
}
impl IntelFeed {
pub fn mint(work: &Path) -> Result<Self> {
use holger_intel::chrono::{Duration as Days, Utc};
use update_bundle::policy::{
ApprovedSigner, CertificateValidity, ExpiredPosture, TrustMode, TrustPolicy,
};
let mint = |role: &str| -> Result<(holger_intel::SignerFiles, String)> {
let kp = rcgen::KeyPair::generate_for(&rcgen::PKCS_ED25519)
.context("mint an ed25519 key")?;
let mut params = rcgen::CertificateParams::new(vec![]).context("cert params")?;
params.distinguished_name.push(
rcgen::DnType::CommonName,
format!("holger installtest intel {role}"),
);
let cert = params.self_signed(&kp).context("self-sign")?;
let sf = holger_intel::SignerFiles {
cert_der: cert.der().to_vec(),
key_pkcs8: kp.serialize_der(),
};
let fp = update_bundle::digest::sha256_hex(&sf.cert_der);
Ok((sf, fp))
};
let (reviewer, reviewer_fp) = mint("security-reviewer")?;
let (owner, owner_fp) = mint("system-owner")?;
let (release, release_fp) = mint("release")?;
let policy = TrustPolicy {
schema_version: update_bundle::schema::POLICY_SCHEMA.into(),
trust_mode: Some(TrustMode::PinnedSigners),
trusted_roots: vec![],
certificate_validity: CertificateValidity::Enforce,
approved_signers: vec![
ApprovedSigner {
fingerprint: reviewer_fp,
subject: None,
roles: vec!["security-reviewer".into()],
},
ApprovedSigner {
fingerprint: owner_fp,
subject: None,
roles: vec!["system-owner".into()],
},
],
required_approval_roles: vec!["security-reviewer".into(), "system-owner".into()],
minimum_distinct_approvers: 2,
trusted_release_signers: vec![release_fp],
release_signer_must_be_distinct: true,
maximum_bundle_age_secs: None,
require_expiry: true,
on_expired_metadata: Some(ExpiredPosture::Refuse),
require_sbom: false,
require_vex: false,
require_scan: false,
allowed_products: vec![holger_intel::INTEL_PRODUCT.into()],
allowed_models: vec![],
};
let now = Utc::now();
let set = holger_intel::AdvisorySet {
schema: holger_intel::set::SET_SCHEMA.into(),
generated_at: now,
sources: vec![],
advisories: vec![],
};
let out = work.join("installtest-intel.holger.znippy");
let sequence = holger_intel::bundle::sequence_for(now);
holger_intel::build_bundle(
&set,
holger_intel::BuildOptions {
sequence,
created_at: now,
expires_at: Some(now + Days::days(30)),
supplier: holger_intel::DEFAULT_SUPPLIER.into(),
producer: "holger cargo xtask installtest".into(),
policy: policy.clone(),
approvals: vec![
("security-reviewer".into(), reviewer),
("system-owner".into(), owner),
],
release,
out: out.clone(),
},
)
.context("[installtest-intel] sign the advisory set")?;
let policy_ron = policy
.to_ron()
.map_err(|e| anyhow::anyhow!("[installtest-intel] the trust policy: {e}"))?;
Ok(Self {
policy_ron,
bundle: std::fs::read(&out).with_context(|| format!("read {}", out.display()))?,
sequence,
})
}
}
/// The airgap box's intel, the way an airgap estate takes it: the trust policy rode the medium
/// onto the release; the console (RepositoryAdmin) authorizes its first import
/// (`POST /-/bundle/first-import`, lane HOL-CDS) and the signed bundle goes to
/// `POST /-/intel/import`.
fn intel_probe(d: &Door, b: &Bearers, feed: &IntelFeed) -> std::result::Result<String, String> {
let grant = format!(
"/-/bundle/first-import?vendor={}&product={}&sequence={}",
holger_intel::DEFAULT_SUPPLIER,
holger_intel::INTEL_PRODUCT.replace('/', "%2F"),
feed.sequence
);
match d.call("POST", &grant, Some(&b.console), None, Some(b"")) {
Ok((200, _)) => {}
other => {
return Err(format!(
"[installtest-intel-import] POST /-/bundle/first-import: {}",
said(&other)
))
}
}
let imp = d.call(
"POST",
"/-/intel/import",
Some(&b.console),
Some("application/octet-stream"),
Some(&feed.bundle),
);
match &imp {
Ok((200, body)) => {
let v: serde_json::Value = serde_json::from_slice(body).unwrap_or_default();
Ok(format!(
"a signed intel bundle imported ({} seq {}, {} advisories)",
v["bundle_id"].as_str().unwrap_or("?"),
v["sequence"],
v["advisories"]
))
}
_ => Err(format!(
"[installtest-intel-import] POST /-/intel/import: {}",
said(&imp)
)),
}
}
/// The npm intake coordinate: the tarball's registry path below the npm repository.
pub fn npm_coordinate() -> String {
format!("{NPM_PACKAGE}/-/{NPM_PACKAGE}-{NPM_VERSION}.tgz")
}
fn npm_probe(
d: &Door,
b: &Bearers,
a: &Artifacts,
declaration: &str,
) -> std::result::Result<String, String> {
let want = sha256_hex(&a.tgz);
let undeclared = if declaration.contains("ron_repo_type: \"npm\"") {
""
} else {
" (the estate declaration names no npm repository)"
};
let put = d.call(
"PUT",
&format!("/npm/{NPM_PACKAGE}"),
Some(&b.console),
Some("application/json"),
Some(npm_publish_doc(&a.tgz).as_bytes()),
);
if !matches!(put, Ok((200..=299, _))) {
return Err(format!(
"[installtest-npm-push] npm publish: {}{undeclared}",
said(&put)
));
}
// The publish is INGESTED; Holger serves it only once it is AVAILABLE (the pip walk).
let walk = walk_to_available(d, b, &npm_coordinate(), "npm")?;
let doc = d.call(
"GET",
&format!("/npm/{NPM_PACKAGE}"),
Some(&b.console),
None,
None,
);
let tarball = match &doc {
Ok((200, body)) => serde_json::from_slice::<serde_json::Value>(body)
.ok()
.and_then(|v| {
v["versions"][NPM_VERSION]["dist"]["tarball"]
.as_str()
.map(str::to_string)
}),
_ => None,
};
let Some(tarball) = tarball else {
return Err(format!(
"[installtest-npm-packument] no tarball for {NPM_VERSION}: {}",
said(&doc)
));
};
// The packument may name the public host: the path is what this door serves.
let path = tarball
.find("/npm/")
.map(|i| tarball[i..].to_string())
.unwrap_or(tarball);
match d.call("GET", &path, Some(&b.console), None, None) {
Ok((200, got)) if sha256_hex(&got) == want => Ok(format!(
"published, walked ({}) and pulled back {} bytes, sha256 {}",
walk.join(", "),
got.len(),
&want[..16]
)),
other => Err(format!(
"[installtest-npm-pull] {path} is not the pushed tarball: {}",
said(&other)
)),
}
}
// ── the receipt (pure) ──────────────────────────────────────────────────────
/// The receipt's file name for this holger HEAD.
pub fn receipt_name(holger_head: &str) -> String {
format!(
"holger-installtest-{}.json",
holger_head.chars().take(12).collect::<String>()
)
}
/// Pure: the receipt. Green only with every leg and probe passed and the medium this HEAD's.
pub fn receipt(
commits: &BTreeMap<String, String>,
medium: &serde_json::Value,
legs: &[Leg],
probes: &[ProbeRow],
medium_is_head: bool,
at_unix: u64,
) -> serde_json::Value {
let legs_ok = legs.len() >= 2 && legs.iter().all(|l| l.pass);
let probes_ok = probes.len() >= 5 && probes.iter().all(|p| p.pass);
serde_json::json!({
"test": "holger-installtest",
"green": legs_ok && probes_ok && medium_is_head,
"medium_is_head": medium_is_head,
"commits": commits,
"medium": medium,
"legs": legs.iter().map(|l| serde_json::json!({"name": l.name, "pass": l.pass, "ms": l.ms as u64, "detail": l.detail, "serial_tail": l.serial_tail})).collect::<Vec<_>>(),
"probes": probes.iter().map(|p| serde_json::json!({"name": p.name, "pass": p.pass, "detail": p.detail})).collect::<Vec<_>>(),
"at_unix": at_unix,
})
}
// ── the run ─────────────────────────────────────────────────────────────────
fn head(dir: &Path) -> String {
Command::new("git")
.arg("-C")
.arg(dir)
.args(["rev-parse", "HEAD"])
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.unwrap_or_default()
}
/// The full HEADs of [`NAMED_BY`] (holger here, the rest beside it).
pub fn commits(root: &Path) -> BTreeMap<String, String> {
NAMED_BY
.iter()
.map(|r| {
(
r.to_string(),
head(&if *r == "holger" {
root.to_path_buf()
} else {
root.parent().unwrap_or(root).join(r)
}),
)
})
.collect()
}
struct Lock(PathBuf);
impl Lock {
fn take() -> Result<Self> {
let dir = PathBuf::from(std::env::var_os("HOME").context("$HOME")?).join("locks");
std::fs::create_dir_all(&dir)?;
let p = dir.join("holger-installtest.lock");
if let Ok(pid) = std::fs::read_to_string(&p) {
if Path::new(&format!("/proc/{}", pid.trim())).exists() {
bail!(
"[installtest-busy] another install test runs (pid {}, {}): one at a time",
pid.trim(),
p.display()
);
}
let _ = std::fs::remove_file(&p);
}
let mut f = std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&p)
.with_context(|| format!("{}", p.display()))?;
write!(f, "{}", std::process::id())?;
Ok(Self(p))
}
}
impl Drop for Lock {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.0);
}
}
/// Compose the DEV main medium with the test declaration and move it out of `.iso-out`.
fn compose(root: &Path, work: &Path, ron: &str, intel_policy: &str) -> Result<PathBuf> {
let suite = forge_door::declared_medium(
VARIANT,
ArtifactKind::Installer,
"cargo xtask installer-iso",
)?;
let stage = work.join("stage");
std::fs::create_dir_all(&stage)?;
let staged = estate_media::Staged {
marker: stage.join("seed-from-metadata.toml"),
ron: stage.join("installtest-holger.ron"),
korp_root: stage.join(estate_media::KORP_ROOT_FILE),
intel_trust: Some(stage.join(estate_media::INTEL_TRUST_FILE)),
bundles_trust: None,
};
std::fs::write(&staged.marker, estate_media::SEED_FROM_METADATA)?;
std::fs::write(&staged.ron, ron)?;
std::fs::write(&staged.korp_root, holger_recipe::estate_ron::KORP_ROOT_PEM)?;
if let Some(why) = estate_media::intel_trust_refusal(intel_policy) {
bail!("{why}");
}
std::fs::write(
staged.intel_trust.as_ref().expect("staged above"),
intel_policy,
)?;
// No seed marker: see the module (step 1).
let mut params: Vec<String> = estate_media::forge_params(VARIANT, &staged)
.into_iter()
.filter(|p| !p.starts_with("pairing_seed="))
.collect();
params.extend(estate_media::live_update_params(true));
let ask = forge_door::Ask {
variant: Some(VARIANT.to_string()),
params,
opts: estate_media::forge_opts(estate_media::KIND_DEV),
builds: vec!["kernel".to_string()],
..Default::default()
};
let iso = forge_door::run("installtest", root, &suite, VARIANT, &ask)?;
// Out of `.iso-out`: a medium with the test's bearers is never uploaded.
let (rec, app) = estate_media::live_companions(&iso);
let mut moved = None;
// The live release's signed bundle and its signature carry the test declaration too.
let (bundle, sig) = (
iso.with_extension("live-release.bundle"),
iso.with_extension("live-release.sig"),
);
for from in [
iso.clone(),
iso.with_extension("medium.json"),
rec,
app,
bundle,
sig,
] {
if let Some(name) = from.file_name() {
let to = work.join(name);
if from.is_file() {
std::fs::rename(&from, &to)
.or_else(|_| {
std::fs::copy(&from, &to)
.map(|_| ())
.and_then(|_| std::fs::remove_file(&from))
})
.with_context(|| format!("move {} out of .iso-out", from.display()))?;
if from == iso {
moved = Some(to);
}
}
}
}
moved.context("the composed medium vanished")
}
/// Whether the medium's record (`<iso>.medium.json` beside it) names this HEAD.
fn medium_is_head(iso: &Path, holger_head: &str) -> (bool, serde_json::Value) {
let rec = std::fs::read_to_string(iso.with_extension("medium.json"))
.ok()
.and_then(|s| serde_json::from_str::<serde_json::Value>(&s).ok())
.unwrap_or_default();
let named = rec["head"].as_str().unwrap_or("").to_string();
let sha = std::fs::read(iso)
.map(|b| sha256_hex(&b))
.unwrap_or_default();
(
named.len() >= 7 && holger_head.starts_with(&named),
serde_json::json!({"path": iso.display().to_string(), "sha256": sha, "record_head": named, "variant": VARIANT, "kind": estate_media::KIND_DEV}),
)
}
pub fn run() -> Result<()> {
let args: Vec<String> = std::env::args().skip(2).collect();
run_with(&parse(&args)?, &crate::workspace_root())
}
fn run_with(o: &Opts, root: &Path) -> Result<()> {
let c = commits(root);
let holger_head = c.get("holger").cloned().unwrap_or_default();
let work = o.work.clone().unwrap_or_else(|| {
PathBuf::from(std::env::var_os("HOME").unwrap_or_default())
.join("scratch/holger-installtest")
.join(holger_head.chars().take(12).collect::<String>())
});
println!("installtest: holger {} — {} → KVM (SeaBIOS, {BOOT_DISK_GIB}+{DATA_DISK_GIB} GiB, -no-reboot) → boot the laid disks → serve → cargo, pip, npm push and pull", &holger_head[..holger_head.len().min(12)], o.iso.as_ref().map(|p| p.display().to_string()).unwrap_or_else(|| format!("compose {VARIANT} (DEV)")));
println!(
" work: {}\n receipt: {}/{}",
work.display(),
root.join(RECEIPTS).display(),
receipt_name(&holger_head)
);
if o.plan {
println!(" PLAN ONLY.");
return Ok(());
}
let _lock = Lock::take()?;
let qemu = kvmproof::which("qemu-system-x86_64")
.context("[installtest-no-qemu] no qemu-system-x86_64 on PATH")?;
if !Path::new("/dev/kvm").exists() {
bail!("[installtest-no-kvm] /dev/kvm is absent: the install test runs on a KVM host");
}
std::fs::create_dir_all(&work)?;
let bearers = Bearers::mint()?;
let ron = declaration(&bearers)?;
// The run's intel signers before the medium: their policy rides it (`intel_trust_policy`).
let intel_feed = IntelFeed::mint(&work)?;
let iso = match &o.iso {
Some(p) => p.clone(),
None => compose(root, &work, &ron, &intel_feed.policy_ron)?,
};
let (is_head, medium) = medium_is_head(&iso, &holger_head);
let declaration_used = if o.iso.is_some() {
None
} else {
Some(ron.clone())
};
let artifacts = Artifacts {
crate_bytes: package_crate(&work)?,
wheel: wheel_bytes(),
tgz: npm_tarball(),
};
let port = korp_installer::isoproof::free_port();
let mut machine = Machine::seabios();
// 4 GiB: the laid box runs clamd in its sandbox (about 1.3 GiB with the full database) and a scan
// worker with a 1 GiB ceiling beside the server (lane HOLGER-MEDIA).
machine.mem_mb = 4096;
machine.smp = 2;
let proof = InstallThenServe {
product: "holger".into(),
qemu,
machine,
iso: iso.clone(),
attach: MediumAttach::Cdrom,
disks: vec![
Disk {
path: work.join("sys.img"),
bytes: BOOT_DISK_GIB << 30,
},
Disk {
path: work.join("data.img"),
bytes: DATA_DISK_GIB << 30,
},
],
workdir: work.clone(),
install_budget: Duration::from_secs(o.install_min * 60),
serve_budget: Duration::from_secs(o.serve_min * 60),
nics: vec![
SlirpNic {
net: None,
hostfwds: vec![(port, HTTP_DOOR)],
},
SlirpNic {
net: Some("10.0.3.0/24".into()),
hostfwds: vec![],
},
],
nic_model: "virtio-net-pci".into(),
refuse_serial: Vec::new(),
probe_every: Duration::from_secs(5),
};
let mut legs = Vec::new();
let mut probes: Vec<ProbeRow> = Vec::new();
println!("── install ({} min budget) ──", o.install_min);
let install = proof.install();
println!(
" {} install {} ms {}",
if install.pass { "PASS" } else { "FAIL" },
install.ms,
install.detail
);
let installed = install.pass;
legs.push(install);
if installed {
println!("── boot the laid disks, wait for http://127.0.0.1:{port} (guest :{HTTP_DOOR}), then push and pull ──");
let door = Door::new(port);
let ron_for_npm = declaration_used.clone().unwrap_or_default();
let mut collected: Vec<ProbeRow> = Vec::new();
let serve = proof.serve(&mut |_serial| {
// Serving at all: the index root answers with any status.
match door.call("GET", "/crates/index/config.json", None, None, None) {
Err(_) => return Probe::Wait,
Ok((st, _)) => println!(" the door answers (http {st} on /crates/index/config.json)"),
}
collected.push(row("red-fault-empty-pull", red_fault(&door, &bearers)));
// The intel bundle first: since the cargo door (HOL-CARGO-PUBLISH-DOOR) a crate walks
// the intake chain like pip and npm, and a box with no advisory database blocks every
// scan (HOLGER-INTEL-MISSING), so a crate pushed before the import is held at
// INGESTED (the 3.1.1 KVM gate on dwarf, 2026-11-02).
collected.push(row("intel", intel_probe(&door, &bearers, &intel_feed)));
collected.push(row("cargo", cargo_probe(&door, &bearers, &artifacts)));
let pip = pip_probe(&door, &bearers, &artifacts);
let pip_walk: Vec<String> = pip.as_ref().ok().map(|d| walk_of(d)).unwrap_or_default();
collected.push(row("pip", pip));
collected.push(row("npm", npm_probe(&door, &bearers, &artifacts, &ron_for_npm)));
collected.push(row("sandbox", sandbox_probe(&door, &bearers, &pip_walk)));
collected.push(row("board", board_probe(&door, &bearers)));
let red: Vec<String> = collected.iter().filter(|p| !p.pass).map(|p| format!("{}: {}", p.name, p.detail)).collect();
Probe::Done(if red.is_empty() { Ok("cargo, pip and npm pushed and pulled back byte for byte; the sandbox probed; the board drawn".into()) } else { Err(red.join(" | ")) })
});
probes = collected;
println!(
" {} serve {} ms {}",
if serve.pass { "PASS" } else { "FAIL" },
serve.ms,
serve.detail
);
if !serve.pass && !serve.serial_tail.is_empty() {
println!(
" serial tail:\n{}",
serve
.serial_tail
.lines()
.map(|l| format!(" | {l}"))
.collect::<Vec<_>>()
.join("\n")
);
}
legs.push(serve);
} else if !legs[0].serial_tail.is_empty() {
println!(
" serial tail:\n{}",
legs[0]
.serial_tail
.lines()
.map(|l| format!(" | {l}"))
.collect::<Vec<_>>()
.join("\n")
);
}
for p in &probes {
println!(
" {} {:<22} {}",
if p.pass { "PASS" } else { "FAIL" },
p.name,
p.detail
);
}
let at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let r = receipt(&c, &medium, &legs, &probes, is_head, at);
let dir = root.join(RECEIPTS);
std::fs::create_dir_all(&dir)?;
let path = dir.join(receipt_name(&holger_head));
std::fs::write(&path, serde_json::to_string_pretty(&r)?)?;
std::fs::write(
work.join(receipt_name(&holger_head)),
serde_json::to_string_pretty(&r)?,
)?;
if !o.keep_disks {
for d in &proof.disks {
let _ = std::fs::remove_file(&d.path);
}
}
let green = r["green"].as_bool() == Some(true);
println!(
"\ninstalltest: {}\nreceipt: {}",
if green { "GREEN" } else { "RED" },
path.display()
);
if !is_head {
println!(
" note: the medium's record does not name this HEAD ({}): never green",
medium["record_head"]
);
}
if !green {
bail!("installtest RED: see {}", path.display());
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn s(v: &str) -> Vec<String> {
v.split_whitespace().map(str::to_string).collect()
}
#[test]
fn the_words_are_understood_and_strays_refused() {
let o = parse(&s("--iso /m.iso --serve-min 5 --keep-disks")).unwrap();
assert_eq!(
(o.iso, o.serve_min, o.keep_disks, o.install_min),
(Some(PathBuf::from("/m.iso")), 5, true, 20)
);
assert!(parse(&s("--wat")).is_err());
assert!(parse(&s("--iso")).is_err());
}
fn bearers() -> Bearers {
Bearers {
console: "installtest-c0".into(),
approvers: ["installtest-a1".into(), "installtest-a2".into()],
}
}
/// A door that gates reads as the estate medium declares (`access: (reads: Gated)`,
/// HCON-5): every GET without the console's bearer is `401 [read-gated]`. It serves one
/// published crate: the push, its walk through the chain (the console takes it to
/// AVAILABLE), its sparse index line and its download.
fn gated_door(crate_bytes: Vec<u8>) -> u16 {
use std::io::{BufRead as _, Read as _, Write as _};
let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let port = l.local_addr().unwrap().port();
std::thread::spawn(move || {
for c in l.incoming() {
let Ok(c) = c else { continue };
let mut r = std::io::BufReader::new(c.try_clone().unwrap());
let mut line = String::new();
r.read_line(&mut line).unwrap();
let (mut len, mut bearer) = (0usize, false);
loop {
let mut h = String::new();
r.read_line(&mut h).unwrap();
let lower = h.to_ascii_lowercase();
if let Some(v) = lower.strip_prefix("content-length:") {
len = v.trim().parse().unwrap_or(0);
}
if lower.starts_with("authorization:") && h.contains("Bearer installtest-c0") {
bearer = true;
}
if h == "\r\n" || h.is_empty() {
break;
}
}
let mut body = vec![0u8; len];
r.read_exact(&mut body).unwrap();
let (st, out): (u16, Vec<u8>) = if line.starts_with("PUT /crates/api/v1/crates/new")
{
(if bearer { 200 } else { 401 }, b"{}".to_vec())
} else if bearer && line.starts_with(&format!(
"POST /-/intake/advance?name=crates/{CRATE}/{CRATE}-{CRATE_VERSION}.crate&verb=advance "
)) {
(200, br#"{"state":"AVAILABLE"}"#.to_vec())
} else if !bearer {
(401, b"[read-gated] reading `crates` on this Holger needs a sign-in (it is not a public repository)".to_vec())
} else if line.starts_with(&format!("GET /crates/index/{} ", sparse_path(CRATE))) {
(200, format!("{{\"name\":\"{CRATE}\",\"vers\":\"{CRATE_VERSION}\",\"cksum\":\"{}\"}}\n", sha256_hex(&crate_bytes)).into_bytes())
} else if line.starts_with(&format!(
"GET /crates/crates/{CRATE}/{CRATE_VERSION}/download "
)) {
(200, crate_bytes.clone())
} else {
(404, b"no".to_vec())
};
let mut c = c;
let _ = write!(
c,
"HTTP/1.1 {st} X\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
out.len()
);
let _ = c.write_all(&out);
}
});
port
}
/// ★ **HHUNT-1: on a gated estate the probes pull signed in.** The estate medium declares
/// `access: (reads: Gated)` since HCON-5 (c57ba731), so an anonymous pull is `401
/// [read-gated]`, as a user's cargo without a token sees it. The installtest's client is
/// the console (the shop's bearer): its pulls carry that bearer, as `cargo` with the
/// estate's token does.
#[test]
fn on_a_gated_estate_the_probes_pull_signed_in() {
let crate_bytes = b"the crate".to_vec();
let a = Artifacts {
crate_bytes: crate_bytes.clone(),
wheel: Vec::new(),
tgz: Vec::new(),
};
let d = Door::new(gated_door(crate_bytes));
let got = cargo_probe(&d, &bearers(), &a);
assert!(
got.as_ref()
.is_ok_and(|s| s
.starts_with("published, walked (console→AVAILABLE) and pulled back 9 bytes")),
"{got:?}"
);
}
#[test]
fn the_declaration_is_the_estates_with_the_fed_bearer_and_two_approvers() {
let r = declaration(&bearers()).unwrap();
assert!(
r.contains("StaticToken(subject: \"console\", token: Some(\"installtest-c0\"))"),
"{r}"
);
assert!(
!r.contains("token_file"),
"no bearer file a feed would write: {r}"
);
assert!(
r.contains("\"installtest-approver-b\": [Developer, SecurityApprover]"),
"{r}"
);
assert!(
r.contains("\"console\": [Importer, RepositoryAdmin, Auditor]"),
"the console's duties are the estate's: {r}"
);
// Everything else is the estate's own.
let base = holger_recipe::estate_ron::render(
&holger_recipe::estate_ron::EstateRon::on_a_box(Some(ESTATE)).airgapped(),
);
assert!(
r.contains(&format!(
"offline: true, trust_policy: Some(\"{}\")",
intel_trust_on_box()
)),
"the airgap box verifies intel against the policy its medium carries: {r}"
);
assert_eq!(intel_trust_on_box(), "/run/korp/release/intel-trust.ron");
for l in base.lines().filter(|l| {
!l.contains("methods:")
&& !l.contains("roles: {\"console\"")
&& !l.contains("duties: {\"console\"")
}) {
assert!(
r.contains(l),
"the test declaration lost an estate line: {l}"
);
}
}
#[test]
fn the_artifacts_are_what_the_clients_send() {
let body = cargo_publish_body("x", "0.1.0", b"crate");
let n = u32::from_le_bytes(body[..4].try_into().unwrap()) as usize;
let meta: serde_json::Value = serde_json::from_slice(&body[4..4 + n]).unwrap();
assert_eq!(
(meta["name"].as_str(), meta["vers"].as_str()),
(Some("x"), Some("0.1.0"))
);
assert_eq!(&body[8 + n..], b"crate");
assert_eq!(sparse_path(CRATE), "ho/lg/holger-installtest-probe");
assert_eq!(sparse_path("ab"), "2/ab");
assert_eq!(sparse_path("abc"), "3/a/abc");
assert_eq!(
index_cksum("{\"vers\":\"0.1.0\",\"cksum\":\"ff\"}\n", "0.1.0").as_deref(),
Some("ff")
);
let w = wheel_bytes();
assert!(w.starts_with(b"PK\x03\x04") && w.windows(4).any(|x| x == b"PK\x05\x06"));
assert_eq!(
wheel_name(),
"holger_installtest_probe-0.1.0-py3-none-any.whl"
);
assert_eq!(
walk_of(
"uploaded, walked (console→SCANNED, alice→APPROVED, console→AVAILABLE), listed"
),
["console→SCANNED", "alice→APPROVED", "console→AVAILABLE"]
);
assert!(walk_of("no walk here").is_empty());
let t = npm_tarball();
assert_eq!(&t[..3], &[0x1f, 0x8b, 8]);
assert_eq!(sha1_hex(b"abc"), "a9993e364706816aba3e25717850c26c9cd0d89d");
assert_eq!(b64(b"hello", false), "aGVsbG8=");
assert_eq!(b64(b"hello", true), "aGVsbG8");
let doc: serde_json::Value = serde_json::from_str(&npm_publish_doc(&t)).unwrap();
assert_eq!(doc["versions"][NPM_VERSION]["name"], NPM_PACKAGE);
}
#[test]
fn a_receipt_is_green_only_with_every_leg_and_probe_and_this_heads_medium() {
let leg = |p: bool| Leg {
name: "l".into(),
pass: p,
ms: 1,
detail: String::new(),
serial_tail: String::new(),
};
let probe = |p: bool| ProbeRow {
name: "p".into(),
pass: p,
detail: String::new(),
};
let c = BTreeMap::new();
let m = serde_json::json!({});
let all = vec![probe(true); 5];
assert_eq!(
receipt(&c, &m, &[leg(true), leg(true)], &all, true, 0)["green"],
true
);
assert_eq!(
receipt(&c, &m, &[leg(true), leg(true)], &all, false, 0)["green"],
false,
"a medium of another HEAD"
);
assert_eq!(
receipt(&c, &m, &[leg(true)], &[], true, 0)["green"],
false,
"an install that never served"
);
assert_eq!(
receipt(
&c,
&m,
&[leg(true), leg(true)],
&[probe(true), probe(true), probe(true), probe(false)],
true,
0
)["green"],
false
);
assert_eq!(
receipt(&c, &m, &[], &[], true, 0)["green"],
false,
"nothing run is never green"
);
assert_eq!(
receipt_name(&"a".repeat(40)),
format!("holger-installtest-{}.json", "a".repeat(12))
);
}
}