xtask/src/appliance_pack.rs
//! **`appliance-pack`** — pack a built holger server binary into the
//! app-`.znippy` the appliance boot chain execs, plus its blake3 pin file.
//!
//! This is the packer the installer-cell wall report named as holger's missing
//! input #2 — and it is deliberately THIN: the archive writer is the shared
//! znippy codec (`znippy_common::create_archive_to_vec`, the exact function
//! `korp_installer::payload::pack_znippy` wraps), and the entry name is the
//! `korp.entry=` selector the boot chain resolves (`for_product("holger")`
//! bakes `korp.entry=holger`; `korp-start` does `get_file(archive, entry)` and
//! `memfd`+`fexecve`s the result). One codec, one entry contract, no dialect.
//!
//! Outputs, named for the installer medium's vocabulary:
//!
//! ```text
//! <out>/holger-app.znippy the app payload (streamed raw onto P2 at install)
//! <out>/holger-app.blake3 blake3 hex of the ARCHIVE bytes — the pin the
//! ESP carries and every boot re-checks
//! ```
//!
//! The verdict is APPLIED OUTPUT: after writing, the archive is read back
//! through `get_file` and the extracted entry must be byte-identical to the
//! input binary — the packer proves its own product, never just its exit code.
//!
//! Honest wall, stated where the operator will meet it: the OS-less installed
//! shape execs the entry with NO loader and NO /lib, so a production payload
//! must be STATICALLY linked. The packer warns (loudly, by PT_INTERP) rather
//! than refusing, because the container/lab shapes run a dynamic payload today
//! — but a dynamic binary on real metal refuses at exec, which is why the
//! warning is the loud kind.
//!
//! The musl wall this paragraph used to describe HAS FALLEN (2026-09-17). It
//! said holger's musl-static build was blocked by openzl-sys-rs's cross-C++
//! need and cited `.nornir/build-variants.toml` as the measurement. That
//! citation was dangling: the emitted declaration names no wall and no
//! openzl — it declares `mvp-server-musl` as a live variant
//! (`target = "x86_64-unknown-linux-musl"`) and the installer row consumes it
//! by name, as `installer_iso.rs` already says ("the STATIC `mvp-server-musl`
//! payload"). A stale wall in a doc comment is worse than no comment: it tells
//! the next operator to stop reaching for the thing that now works.
use std::path::PathBuf;
use anyhow::{bail, Context, Result};
/// Defaults: the MVP gateway binary, the entry `for_product("holger")` bakes.
const DEFAULT_BIN: &str = "target/release/holger-mvp";
const DEFAULT_ENTRY: &str = "holger";
const DEFAULT_OUT: &str = ".znippy-out";
/// zstd level for the release artifact — dense over fast (the archive is
/// written once and streamed to disks many times).
const DEFAULT_LEVEL: i32 = 19;
/// Does this ELF ask for an interpreter (`PT_INTERP`)? A dynamically linked
/// payload cannot exec on the OS-less installed shape.
fn wants_interpreter(elf: &[u8]) -> bool {
// ELF64: e_phoff at 0x20 (u64), e_phentsize at 0x36 (u16), e_phnum at 0x38
// (u16); a program header's p_type is its first u32; PT_INTERP = 3.
if elf.len() < 0x40 || &elf[..4] != b"\x7fELF" {
return false;
}
let phoff = u64::from_le_bytes(elf[0x20..0x28].try_into().unwrap()) as usize;
let phentsize = u16::from_le_bytes(elf[0x36..0x38].try_into().unwrap()) as usize;
let phnum = u16::from_le_bytes(elf[0x38..0x3a].try_into().unwrap()) as usize;
(0..phnum).any(|i| {
let off = phoff + i * phentsize;
elf.get(off..off + 4)
.map(|t| u32::from_le_bytes(t.try_into().unwrap()) == 3)
.unwrap_or(false)
})
}
/// Run the verb. Args: `--bin <path>` `--entry <name>` `--out <dir>`
/// `--level <zstd>` — every default spelled above.
pub fn run() -> Result<()> {
let mut bin = PathBuf::from(DEFAULT_BIN);
let mut entry = DEFAULT_ENTRY.to_string();
let mut out_dir = PathBuf::from(DEFAULT_OUT);
let mut level = DEFAULT_LEVEL;
let mut args = std::env::args().skip(2);
while let Some(a) = args.next() {
let mut val = |flag: &str| args.next().with_context(|| format!("{flag} takes a value"));
match a.as_str() {
"--bin" => bin = PathBuf::from(val("--bin")?),
"--entry" => entry = val("--entry")?,
"--out" => out_dir = PathBuf::from(val("--out")?),
"--level" => level = val("--level")?.parse().context("--level is a zstd level")?,
other => bail!(
"unknown argument '{other}' (appliance-pack takes --bin/--entry/--out/--level)"
),
}
}
let bytes = std::fs::read(&bin).with_context(|| {
// ⚠ THE ADVICE NAMES THE DECLARATION, NOT A FEATURE LIST (2026-09-21).
//
// It used to spell `--features mvp-server,appliance`, which is a SUBSET
// of the declared row and produces a payload that serves NO FORMATS —
// the appliance then dies at first boot with `holger-mvp-1` and, being
// PID 1, takes the machine with it. Measured through the fleet proof,
// which had copied the same short list. A build line in a message is a
// second writer like any other; this one now points at the one writer.
format!(
"read {} — build it first through the declaration: \
`cargo xtask build --suite MVP --variant mvp-server-musl`, then pass the result \
with --bin. Do NOT hand-spell a feature list: the declared row carries \
auth-passkey, sign, allow-meter, the twelve fmt-* formats, sync and jobs \
beside mvp-server+appliance, and a payload missing the fmt-* features serves no \
repository at all",
bin.display()
)
})?;
if wants_interpreter(&bytes) {
eprintln!(
"⚠ {} is DYNAMICALLY linked (PT_INTERP present): it packs and verifies, but the \
OS-less installed shape has no loader and will refuse it at exec — the musl wall \
recorded in .nornir/build-variants.toml is what stands between this payload and \
real metal",
bin.display()
);
}
let (archive, _report) =
znippy_common::create_archive_to_vec(&[(entry.clone(), bytes.clone())], level)
.context("pack the app .znippy")?;
std::fs::create_dir_all(&out_dir).with_context(|| format!("create {}", out_dir.display()))?;
let archive_path = out_dir.join("holger-app.znippy");
let pin_path = out_dir.join("holger-app.blake3");
std::fs::write(&archive_path, &archive)
.with_context(|| format!("write {}", archive_path.display()))?;
let pin = blake3::hash(&archive).to_hex().to_string();
std::fs::write(&pin_path, format!("{pin}\n"))
.with_context(|| format!("write {}", pin_path.display()))?;
// APPLIED OUTPUT: the archive on disk gives the binary back, byte for byte.
let roundtrip = znippy_common::get_file(&archive_path, &entry).with_context(|| {
format!(
"read the entry '{entry}' back out of {}",
archive_path.display()
)
})?;
if roundtrip != bytes {
bail!(
"the packed entry read back {} bytes but the input was {} — refusing to bless \
an archive that does not return its payload",
roundtrip.len(),
bytes.len()
);
}
println!(
"✅ appliance-pack: {} ({} bytes, entry '{entry}') → {} ({} bytes, zstd {level})",
bin.display(),
bytes.len(),
archive_path.display(),
archive.len(),
);
println!(" pin {} → {}", &pin[..12], pin_path.display());
Ok(())
}