gunnar-server 1.1.0

The gunnar git server: SSH and smart-HTTP git transports and the gRPC control plane. The appliance half, extracted from gunnar-cli so the server code lives in its own crate.
Documentation
//! Writes the build-thing RECEIPT for the appliance binary.
//!
//! The receipt says which **recipe** built this artifact — `gunnar-build-thing`,
//! its version, its content hash, and every resolved setting of the variant this
//! compilation is. It goes into the binary's `.nordisk.buildthing` section via
//! the `include!` in `src/main.rs`, and `dwarves` reads it back out with an
//! object parser without ever running the artifact.
//!
//! Until 2026-08-10 this crate had no `build.rs` and the `gunnar-server` variant
//! still named package `gunnar-cli`, where the bin lived before the extraction
//! The variant follows the code now, and so does the writer.
//!
//! This is the RECEIPT half only. The `GUNNAR_BUILD_REF`/`GUNNAR_BUILD_DATE`
//! version stamps come from `gunnar-common`'s own `build.rs` — two copies of
//! the receipt static in one artifact would concatenate and be reported as
//! "more than one receipt", which is why each binary crate writes exactly its
//! own. The long story of what a receipt is and why a refused one PANICS lives
//! in `crates/gunnar-cli/build.rs`.

use std::process::Command;

fn main() {
    // ONE `git describe`, as the receipt's `source-commit` input. NOT the build
    // date: two identical builds would differ by it and by nothing else, and
    // `nornir_build_thing::receipt::audit_receipt` refuses a timestamp outright.
    // The four `-c` overrides are the SAME ones `gunnar-cli/build.rs` carries and
    // for the same measured reason: a git configured for git-lfs
    // (`filter.lfs.required = true`, as this box's /etc/gitconfig is) runs the
    // lfs filter for `--dirty`'s status walk, and inside the pinned cargo
    // toolchain image — where `git-lfs` is not on PATH — `describe` then dies
    // with `git-lfs: not found`. `gunnar-cli` and `gunnar-common` were fixed on
    // 2026-09-05; THIS script was left behind, and it runs the same `--dirty`
    // walk. The cost here is quieter than a bare SSH banner and no less real:
    // `source-commit` simply drops out of the build receipt, so a
    // container-built gunnar-server ships a receipt that does not name the
    // source it was built from. Describing a commit needs no filter.
    // MEASURED 2026-09-15 and recorded in `gunnar-common/build.rs`, which carries
    // both of these and which this script did not: inside the pinned toolchain
    // container the checkout is bind-mounted and the building uid does not own
    // it, so git answers `fatal: detected dubious ownership` and the stamp falls
    // to nothing — there, `source-commit` simply leaves the receipt.
    //
    // `core.checkStat=minimal` is the one that bit THIS script, MEASURED
    // 2026-09-16 on the medium composed from a clean clone at b39da6c: the
    // binary carried BOTH `v1.4.0-279-gb39da6c` (the banner, from
    // gunnar-common, which has this override) and
    // `v1.4.0-279-gb39da6c-modified` (this receipt, which did not). The tree was
    // clean on both sides of the mount. Rootless podman remaps uid/gid and git's
    // default stat check compares uid, gid, inode and ctime against the index,
    // so every tracked file reads as changed — and `--dirty=-modified`, which
    // only this script passes, turns that into a receipt claiming a modified
    // source that never was. `minimal` compares size and mtime, which a bind
    // mount preserves.
    //
    // Reading a version is not a trust decision: nothing here runs the
    // repository's hooks or config.
    let describe = Command::new("git")
        .args([
            "-c", "safe.directory=*",
            "-c", "core.checkStat=minimal",
            "-c", "filter.lfs.required=false",
            "-c", "filter.lfs.process=",
            "-c", "filter.lfs.clean=",
            "-c", "filter.lfs.smudge=",
            "describe",
            "--tags",
            "--always",
            "--dirty=-modified",
            "--abbrev=7",
        ])
        .output()
        .ok()
        .filter(|o| o.status.success())
        .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_owned())
        .filter(|s| !s.is_empty());

    self_plugin(describe.as_deref());

    let mut inputs: Vec<(&str, String)> = Vec::new();
    if let Some(d) = &describe {
        inputs.push(("source-commit", d.clone()));
    }

    match gunnar_recipe::emit::write_receipt(&inputs) {
        Ok(emitted) => {
            if let Some(variant) = emitted.attribution.variant() {
                println!("cargo:rustc-env=GUNNAR_BUILD_VARIANT={}", variant.name);
            }
        }
        Err(e) => panic!(
            "the build-thing receipt was refused: {e:#}\n\
             This is a defect in gunnar-build-thing's declaration, not in this build. \
             A binary that ships with a receipt its own audit would reject is worse than \
             one with none, so the build stops here."
        ),
    }

    // Re-run when HEAD moves, so the receipt's source-commit cannot go stale in
    // an incremental build. `--git-path` rather than `../../.git/…` because in a
    // git WORKTREE `.git` is a file and naming paths under it triggers cargo's
    // always-dirty behaviour — see gunnar-cli/build.rs for the full account.
    for what in ["HEAD", "refs"] {
        let path = Command::new("git")
            .args(["rev-parse", "--git-path", what])
            .output()
            .ok()
            .filter(|o| o.status.success())
            .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_owned())
            .filter(|s| !s.is_empty());
        if let Some(path) = path {
            if std::path::Path::new(&path).exists() {
                println!("cargo:rerun-if-changed={path}");
            }
        }
    }
}

/// **gunnar's own monetize plugin, carried by the METERED server** (owner
/// 2026-09-25): the signed `gunnar_monetize_guest.wasm` + `.wasm.p7s`, staged by
/// `cargo xtask iso-installer` for a metered medium into `<gunnar>/.plugin-out/`
/// (or `GUNNAR_SERVER_PLUGIN_DIR`), embedded here as `self_plugin.rs`
/// (`crate::self_register`). The open build embeds nothing and says nothing.
///
/// **One commit or no plugin**: the module carries its build reference in the
/// custom section `gunnar_build_ref` (the same `git describe` as this binary).
/// A staged plugin of ANOTHER build is refused here, by name — a medium that
/// would never register its plugin is worse than a build that stops. An absent
/// plugin is not a failure (every dev and test build): the binary knows it has
/// none and says so at run time.
fn self_plugin(describe: Option<&str>) {
    use std::fmt::Write as _;
    let out = std::path::PathBuf::from(std::env::var("OUT_DIR").expect("cargo sets OUT_DIR"));
    let manifest = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("cargo sets CARGO_MANIFEST_DIR"));
    println!("cargo:rerun-if-env-changed=GUNNAR_SERVER_PLUGIN_DIR");
    let dir = std::env::var_os("GUNNAR_SERVER_PLUGIN_DIR").map(std::path::PathBuf::from).unwrap_or_else(|| manifest.join("../../.plugin-out"));
    let module = dir.join("gunnar_monetize_guest.wasm");
    let sig = dir.join("gunnar_monetize_guest.wasm.p7s");
    println!("cargo:rerun-if-changed={}", module.display());
    println!("cargo:rerun-if-changed={}", sig.display());
    let metered = std::env::var_os("CARGO_FEATURE_METERED").is_some();
    let mut code = String::new();
    let staged = metered && module.is_file() && sig.is_file();
    if staged {
        let bytes = std::fs::read(&module).expect("read the staged plugin");
        let stamp = custom_section(&bytes, "gunnar_build_ref").map(|s| String::from_utf8_lossy(s).to_string());
        match (stamp.as_deref(), describe) {
            (Some(s), Some(d)) if s != d => panic!(
                "REFUSED [plugin-other-build] the staged plugin {} is build `{s}`, and this gunnar-server is `{d}`: \
                 the metered medium carries its OWN plugin, built and signed from the same commit. Re-stage it \
                 (`cargo xtask iso-installer` does, for a metered variant) or remove {}",
                module.display(),
                dir.display()
            ),
            (None, _) => panic!("REFUSED [plugin-no-build-ref] the staged plugin {} carries no `gunnar_build_ref` section: it is not gunnar's own plugin", module.display()),
            _ => {}
        }
        let _ = writeln!(code, "/// The signed module (`gunnar_monetize_guest.wasm`).\npub static MODULE: &[u8] = include_bytes!({:?});", module.canonicalize().unwrap_or(module.clone()).display().to_string());
        let _ = writeln!(code, "/// Its detached CMS signature.\npub static SIGNATURE: &[u8] = include_bytes!({:?});", sig.canonicalize().unwrap_or(sig.clone()).display().to_string());
        let _ = writeln!(code, "/// Where it was staged from at build time.\npub const SOURCE: Option<&str> = Some({:?});", dir.display().to_string());
    } else {
        if metered {
            println!("cargo:warning=gunnar-server: no staged plugin in {} (gunnar_monetize_guest.wasm + .p7s) — this metered binary carries none and will not register one", dir.display());
        }
        let _ = writeln!(code, "/// No plugin was staged for this build.\npub static MODULE: &[u8] = &[];\npub static SIGNATURE: &[u8] = &[];\npub const SOURCE: Option<&str> = None;");
    }
    std::fs::write(out.join("self_plugin.rs"), code).expect("write self_plugin.rs");
}

/// A WebAssembly custom section by name (the same reader as
/// `plugin_door_wire::custom_section`; a build script cannot link it).
fn custom_section<'a>(m: &'a [u8], name: &str) -> Option<&'a [u8]> {
    fn leb(b: &[u8], at: &mut usize) -> Option<usize> {
        let (mut n, mut shift) = (0usize, 0u32);
        loop {
            let x = *b.get(*at)?;
            *at += 1;
            n |= ((x & 0x7f) as usize).checked_shl(shift)?;
            if x & 0x80 == 0 {
                return Some(n);
            }
            shift += 7;
            if shift > 35 {
                return None;
            }
        }
    }
    if m.len() < 8 || &m[..4] != b"\0asm" {
        return None;
    }
    let mut at = 8;
    while at < m.len() {
        let id = m[at];
        at += 1;
        let len = leb(m, &mut at)?;
        let end = at.checked_add(len).filter(|e| *e <= m.len())?;
        if id == 0 {
            let mut p = at;
            let n = leb(m, &mut p)?;
            let name_end = p.checked_add(n).filter(|e| *e <= end)?;
            if &m[p..name_end] == name.as_bytes() {
                return Some(&m[name_end..end]);
            }
        }
        at = end;
    }
    None
}