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

nordisk/gunnar

crates/gunnar-front/src/downloads.rs

//! **The `gunnar` CLI, downloadable from the estate's own site** (E4, owner
//! 2026-10-11): every encrypted-repository flow and the browser decrypt need
//! the CLI, so every Gunnar site serves it — gunnar.rs, test2, every sold
//! `<company>.gunnar.rs` — from the SAME release build that made the estate.
//!
//! **Where the bytes come from.** The Build Thing's `gunnar-musl` row builds the
//! client-only `gunnar` for `x86_64-unknown-linux-musl` and declares `sign`, so
//! the forge's signing step writes `gunnar.p7s` (a detached CMS over the
//! BLAKE3 digest, under the plugin-signing certificate issued by the owner's
//! root key) and `gunnar.blake3` beside it. `build.rs` embeds the three files
//! and the published trust root (`korp-root-ca.der`) into this binary — the
//! front medium stays ONE file — and this module serves them:
//!
//! | path | what |
//! |---|---|
//! | `/download/` | a 303 to the console's page (`/cli`), which renders the manifest |
//! | `/download/index.json` | the [`Manifest`]: sizes, sha256, blake3, the verify line, `cargo install` |
//! | `/download/<file>` | the binary, `<file>.p7s`, `<file>.blake3`, `<file>.sha256`, the root |
//!
//! **Only a SIGNED binary is offered**: a binary the build carries without its
//! `.p7s` is left out, and a medium without one says so by name in the
//! manifest (`absent`) instead of serving an unsigned file.

use std::sync::Arc;

use axum::extract::Path as UrlPath;
use axum::http::{header, StatusCode};
use axum::response::IntoResponse;
use axum::routing::get;
use axum::Router;
use serde::Serialize;

mod embedded {
    include!(concat!(env!("OUT_DIR"), "/cli_downloads.rs"));
}
pub use embedded::{CLI_SOURCE, FILES};

/// The path every download sits under.
pub const PREFIX: &str = "/download";
/// The console's page that renders the manifest (gunnar-ui `/cli`).
pub const PAGE: &str = "/cli";
/// The published trust root's file name (dwarves `trust/korp-root-ca.der`).
pub const ROOT_CA: &str = "korp-root-ca.der";
/// **The Gunnar mark, at a stable public path on every site** — what the crates'
/// READMEs (crates.io) and `#![doc(html_logo_url, html_favicon_url)]` (docs.rs)
/// point at, absolute: `https://gunnar.rs/download/gunnar.webp`. Copies of the
/// console's own (gunnar-ui-web `assets/gunnar_light.webp`, `styles/favicon.svg`).
pub const BRAND: &[(&str, &[u8], &str)] = &[
    (
        "gunnar.webp",
        include_bytes!("../assets/gunnar.webp"),
        "image/webp",
    ),
    (
        "favicon.svg",
        include_bytes!("../assets/favicon.svg"),
        "image/svg+xml",
    ),
];

/// The second way to get the CLI (owner 2026-10-11, decision (c)): crates.io.
pub const CARGO_INSTALL: &str =
    "cargo install gunnar-cli --locked --no-default-features --features client";

/// One signed binary, as the page shows it.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Binary {
    /// The file name served under [`PREFIX`].
    pub file: String,
    /// The target triple it runs on.
    pub target: String,
    pub bytes: u64,
    pub sha256: String,
    pub blake3: String,
    /// `<file>.p7s`.
    pub signature: String,
    /// The one line that checks the sha256 AND the signature against the root.
    pub verify: String,
}

/// The trust root a signature chains to.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Root {
    pub file: String,
    pub sha256: String,
}

/// **What `/download/index.json` says.**
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Manifest {
    /// The release these binaries are from.
    pub version: String,
    pub binaries: Vec<Binary>,
    pub root: Option<Root>,
    /// The crates.io way.
    pub cargo_install: String,
    /// Why nothing is offered, by name; `None` when a signed binary is.
    pub absent: Option<String>,
}

fn hex(b: &[u8]) -> String {
    b.iter().map(|x| format!("{x:02x}")).collect()
}

/// Pure: the sha256 of `bytes`, lowercase hex.
pub fn sha256_hex(bytes: &[u8]) -> String {
    use sha2::Digest;
    hex(&sha2::Sha256::digest(bytes))
}

/// Pure: **the one verify line** for `file` — the sha256, then the detached CMS
/// over the BLAKE3 digest against the published root (openssl ≥ 3, b3sum).
pub fn verify_line(file: &str) -> String {
    format!(
        "sha256sum -c {file}.sha256 && b3sum --no-names {file} | xxd -r -p > {file}.b3 && openssl cms -verify -binary -inform DER -in {file}.p7s -content {file}.b3 -CAfile <(openssl x509 -inform DER -in {ROOT_CA}) -purpose any -out /dev/null"
    )
}

/// The target a served binary's name ends in (`gunnar-<target>`).
fn target_of(file: &str) -> Option<&str> {
    file.strip_prefix("gunnar-")
        .filter(|t| !t.is_empty() && !t.contains('.'))
}

/// Pure: **the manifest of `files`** (`(name, bytes)` as [`FILES`] holds them).
pub fn manifest(files: &[(&str, &[u8])], version: &str) -> Manifest {
    let get = |n: &str| files.iter().find(|(f, _)| *f == n).map(|(_, b)| *b);
    let mut binaries = Vec::new();
    let mut unsigned = Vec::new();
    for (name, bytes) in files {
        let Some(target) = target_of(name) else {
            continue;
        };
        if get(&format!("{name}.p7s")).is_none() {
            unsigned.push(name.to_string());
            continue;
        }
        let blake3 = get(&format!("{name}.blake3"))
            .map(|b| String::from_utf8_lossy(b).trim().to_string())
            .unwrap_or_else(|| blake3::hash(bytes).to_hex().to_string());
        binaries.push(Binary {
            file: name.to_string(),
            target: target.to_string(),
            bytes: bytes.len() as u64,
            sha256: sha256_hex(bytes),
            blake3,
            signature: format!("{name}.p7s"),
            verify: verify_line(name),
        });
    }
    let root = get(ROOT_CA).map(|b| Root {
        file: ROOT_CA.to_string(),
        sha256: sha256_hex(b),
    });
    let absent = if binaries.is_empty() {
        Some(if unsigned.is_empty() {
            "REFUSED [cli-not-on-medium] this medium was composed without the signed Gunnar CLI (the Build Thing's `gunnar-musl` row); compose it again with it".to_string()
        } else {
            format!(
                "REFUSED [cli-unsigned] {} came without a signature and is not offered",
                unsigned.join(", ")
            )
        })
    } else if root.is_none() {
        binaries.clear();
        Some(format!("REFUSED [cli-no-root] the trust root `{ROOT_CA}` is not on this medium: a signature nobody can check is not offered"))
    } else {
        None
    };
    Manifest {
        version: version.to_string(),
        binaries,
        root,
        cargo_install: CARGO_INSTALL.to_string(),
        absent,
    }
}

/// Pure: the bytes served at `/download/<name>` — an embedded file (only those
/// the manifest offers, their sidecars and the root), or `<binary>.sha256`
/// made from it in the `sha256sum -c` form.
pub fn file(files: &[(&str, &[u8])], m: &Manifest, name: &str) -> Option<Vec<u8>> {
    if let Some(b) = name
        .strip_suffix(".sha256")
        .and_then(|bin| m.binaries.iter().find(|x| x.file == bin))
    {
        return Some(format!("{}  {}\n", b.sha256, b.file).into_bytes());
    }
    let offered = m.root.as_ref().is_some_and(|r| r.file == name)
        || m.binaries
            .iter()
            .any(|b| name == b.file || name == b.signature || name == format!("{}.blake3", b.file));
    if !offered {
        return None;
    }
    files
        .iter()
        .find(|(f, _)| *f == name)
        .map(|(_, b)| b.to_vec())
}

/// **The `/download` router** over `files`.
pub fn router_over(files: &'static [(&'static str, &'static [u8])], version: &str) -> Router {
    let m = Arc::new(manifest(files, version));
    let index = m.clone();
    Router::new()
        .route(&format!("{PREFIX}/"), get(|| async { (StatusCode::SEE_OTHER, [(header::LOCATION, PAGE)]).into_response() }))
        .route(PREFIX, get(|| async { (StatusCode::SEE_OTHER, [(header::LOCATION, PAGE)]).into_response() }))
        .route(&format!("{PREFIX}/index.json"), get(move || async move { axum::Json((*index).clone()).into_response() }))
        .route(
            &format!("{PREFIX}/{{name}}"),
            get(move |UrlPath(name): UrlPath<String>| async move {
                if let Some((_, bytes, ct)) = BRAND.iter().find(|(n, _, _)| *n == name) {
                    return ([(header::CONTENT_TYPE, *ct), (header::CACHE_CONTROL, "public, max-age=86400")], *bytes).into_response();
                }
                match file(files, &m, &name) {
                    Some(b) => {
                        let ct = if name.ends_with(".sha256") || name.ends_with(".blake3") { "text/plain; charset=utf-8" } else { "application/octet-stream" };
                        ([(header::CONTENT_TYPE, ct.to_string()), (header::CONTENT_DISPOSITION, format!("attachment; filename=\"{name}\""))], b).into_response()
                    }
                    None => (StatusCode::NOT_FOUND, format!("REFUSED [cli-download-unknown] {name:?} is not a download of this medium\n")).into_response(),
                }
            }),
        )
}

/// The router over THIS medium's embedded files.
pub fn router() -> Router {
    router_over(FILES, env!("CARGO_PKG_VERSION"))
}

/// **`face`, with `/download/…` served by this module** and every other path
/// left to it (the console).
pub fn serving(face: Router) -> Router {
    router().fallback_service(face)
}

#[cfg(test)]
mod tests {
    use super::*;
    use axum::body::Body;
    use tower::ServiceExt;

    const BIN: &[u8] = b"\x7fELF a gunnar";
    const SIG: &[u8] = b"0\x82 cms";
    const ROOT: &[u8] = b"0\x82 root";

    fn signed() -> &'static [(&'static str, &'static [u8])] {
        &[
            ("gunnar-x86_64-unknown-linux-musl", BIN),
            ("gunnar-x86_64-unknown-linux-musl.p7s", SIG),
            ("korp-root-ca.der", ROOT),
        ]
    }

    #[test]
    fn a_signed_binary_is_offered_with_its_sha256_blake3_signature_and_verify_line() {
        let m = manifest(signed(), "1.5.0");
        assert_eq!(m.absent, None);
        assert_eq!(m.binaries.len(), 1);
        let b = &m.binaries[0];
        assert_eq!(b.target, "x86_64-unknown-linux-musl");
        assert_eq!(b.bytes, BIN.len() as u64);
        assert_eq!(b.sha256, sha256_hex(BIN));
        assert_eq!(b.sha256.len(), 64);
        assert_eq!(b.blake3, blake3::hash(BIN).to_hex().to_string());
        assert_eq!(b.signature, "gunnar-x86_64-unknown-linux-musl.p7s");
        assert!(
            b.verify
                .contains("sha256sum -c gunnar-x86_64-unknown-linux-musl.sha256"),
            "{}",
            b.verify
        );
        assert!(
            b.verify.contains("openssl cms -verify") && b.verify.contains(ROOT_CA),
            "{}",
            b.verify
        );
        assert_eq!(
            m.root,
            Some(Root {
                file: ROOT_CA.into(),
                sha256: sha256_hex(ROOT)
            })
        );
        assert!(
            m.cargo_install.starts_with("cargo install gunnar-cli"),
            "{}",
            m.cargo_install
        );
    }

    #[test]
    fn an_unsigned_binary_is_never_offered() {
        let files: &[(&str, &[u8])] = &[
            ("gunnar-x86_64-unknown-linux-musl", BIN),
            ("korp-root-ca.der", ROOT),
        ];
        let m = manifest(files, "1.5.0");
        assert!(m.binaries.is_empty());
        assert!(
            m.absent.as_deref().unwrap().contains("[cli-unsigned]"),
            "{:?}",
            m.absent
        );
        assert_eq!(file(files, &m, "gunnar-x86_64-unknown-linux-musl"), None);
    }

    #[test]
    fn a_medium_without_the_cli_or_its_root_says_so_by_name() {
        assert!(manifest(&[], "1.5.0")
            .absent
            .unwrap()
            .contains("[cli-not-on-medium]"));
        let no_root: &[(&str, &[u8])] = &[
            ("gunnar-x86_64-unknown-linux-musl", BIN),
            ("gunnar-x86_64-unknown-linux-musl.p7s", SIG),
        ];
        let m = manifest(no_root, "1.5.0");
        assert!(m.binaries.is_empty() && m.absent.unwrap().contains("[cli-no-root]"));
    }

    #[test]
    fn the_sha256_file_is_in_the_form_sha256sum_checks() {
        let m = manifest(signed(), "1.5.0");
        let got = file(signed(), &m, "gunnar-x86_64-unknown-linux-musl.sha256").unwrap();
        assert_eq!(
            String::from_utf8(got).unwrap(),
            format!("{}  gunnar-x86_64-unknown-linux-musl\n", sha256_hex(BIN))
        );
    }

    async fn get_path(r: Router, p: &str) -> (StatusCode, Option<String>, Vec<u8>) {
        let res = r
            .oneshot(axum::http::Request::get(p).body(Body::empty()).unwrap())
            .await
            .unwrap();
        let loc = res
            .headers()
            .get(header::LOCATION)
            .map(|v| v.to_str().unwrap().to_string());
        let st = res.status();
        let body = axum::body::to_bytes(res.into_body(), usize::MAX)
            .await
            .unwrap()
            .to_vec();
        (st, loc, body)
    }

    #[tokio::test]
    async fn the_download_paths_are_served_ahead_of_the_console() {
        let console = Router::new().fallback(|| async { "console" });
        let r = router_over(signed(), "1.5.0").fallback_service(console);
        let (st, _, body) = get_path(r.clone(), "/download/gunnar-x86_64-unknown-linux-musl").await;
        assert_eq!((st, body.as_slice()), (StatusCode::OK, BIN));
        let (st, _, body) =
            get_path(r.clone(), "/download/gunnar-x86_64-unknown-linux-musl.p7s").await;
        assert_eq!((st, body.as_slice()), (StatusCode::OK, SIG));
        let (st, _, body) = get_path(r.clone(), "/download/korp-root-ca.der").await;
        assert_eq!((st, body.as_slice()), (StatusCode::OK, ROOT));
        let (st, _, body) = get_path(r.clone(), "/download/index.json").await;
        assert_eq!(st, StatusCode::OK);
        let v: serde_json::Value = serde_json::from_slice(&body).unwrap();
        assert_eq!(v["binaries"][0]["file"], "gunnar-x86_64-unknown-linux-musl");
        assert_eq!(v["version"], "1.5.0");
        let (st, loc, _) = get_path(r.clone(), "/download/").await;
        assert_eq!((st, loc.as_deref()), (StatusCode::SEE_OTHER, Some(PAGE)));
        // The mark the crates' READMEs and docs.rs name, absolute, on every site.
        let (st, _, body) = get_path(r.clone(), "/download/gunnar.webp").await;
        assert_eq!(
            (st, &body[..4], &body[8..12]),
            (StatusCode::OK, &b"RIFF"[..], &b"WEBP"[..])
        );
        let (st, _, body) = get_path(r.clone(), "/download/favicon.svg").await;
        assert!(st == StatusCode::OK && String::from_utf8_lossy(&body).contains("<svg"));
        let (st, _, _) = get_path(r.clone(), "/download/etc-passwd").await;
        assert_eq!(st, StatusCode::NOT_FOUND);
        let (st, _, body) = get_path(r, "/repos").await;
        assert_eq!((st, body.as_slice()), (StatusCode::OK, &b"console"[..]));
    }
}