crates/gunnar-front/build.rs
//! **What the front medium carries besides its own code**, embedded at build
//! time so the medium stays one binary (korp-start execs exactly one file):
//!
//! * the built console (gunnar-ui-web's `dist/`: `index.html`, the wasm, its
//! loader, the stylesheet, the pictures) under the `console` feature — the
//! community build (`dist/`), or the paid one (`dist-metered/`) under
//! `metered`;
//!
//! Where from: `GUNNAR_FRONT_CONSOLE_DIST`, else the declared sibling beside
//! this checkout (`../gunnar-ui/crates/gunnar-ui-web/dist[-metered]`). An
//! absent console is NOT a build failure — tests build without it — but the
//! binary then knows it (`console::DIST_SOURCE` is `None`), says so by name
//! when it runs, and a `cargo:warning` says so here.
//!
//! * the SIGNED `gunnar` CLI the estate's own site offers under `/download/`
//! (E4, 2026-10-11; `downloads` module): the Build Thing's `gunnar-musl`
//! row's `gunnar` with the forge's `gunnar.p7s` + `gunnar.blake3`, served as
//! `gunnar-x86_64-unknown-linux-musl[.p7s|.blake3]`, and the published trust
//! root `korp-root-ca.der`. From `GUNNAR_FRONT_CLI_DIR`, else `<gunnar>/.cli-out/`
//! (staged by `cargo xtask iso-installer`, `xtask/src/cli_stage.rs`); the root
//! from `GUNNAR_FRONT_CLI_ROOT_CA`, else the staged one, else the sibling
//! `dwarves/trust/`. An absent CLI is not a build failure either: the
//! manifest says so by name.
//!
//! **No monetize plugin is embedded** (owner 2026-09-25): the front's monetize
//! is bare and RECEIVES its plugins in the feed (`monetize` module).
use std::fmt::Write as _;
use std::path::{Path, PathBuf};
fn walk(dir: &Path, base: &Path, out: &mut Vec<(String, PathBuf)>) {
let Ok(rd) = std::fs::read_dir(dir) else {
return;
};
let mut entries: Vec<_> = rd.flatten().collect();
entries.sort_by_key(|e| e.file_name());
for e in entries {
let p = e.path();
if p.is_dir() {
walk(&p, base, out);
} else if let Ok(rel) = p.strip_prefix(base) {
out.push((
rel.to_string_lossy().replace('\\', "/"),
p.canonicalize().unwrap_or(p.clone()),
));
}
}
}
fn main() {
let manifest =
PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("cargo sets CARGO_MANIFEST_DIR"));
let out = PathBuf::from(std::env::var("OUT_DIR").expect("cargo sets OUT_DIR"));
let console = std::env::var_os("CARGO_FEATURE_CONSOLE").is_some();
let metered = std::env::var_os("CARGO_FEATURE_METERED").is_some();
println!("cargo:rerun-if-env-changed=GUNNAR_FRONT_CONSOLE_DIST");
let siblings = manifest.join("../../..");
// ── the console ─────────────────────────────────────────────────────────
let mut code = String::new();
let dist = std::env::var_os("GUNNAR_FRONT_CONSOLE_DIST")
.map(PathBuf::from)
.unwrap_or_else(|| {
siblings
.join("gunnar-ui/crates/gunnar-ui-web")
.join(if metered { "dist-metered" } else { "dist" })
});
let mut files = Vec::new();
if console {
// The PARENT too: a dist that does not exist yet has nothing for cargo
// to watch, so a console built AFTER a first build never reached the
// binary (MEASURED loki 2026-09-26, prod: the front medium was composed
// twice with the same bytes and served 503 [console-not-on-medium]).
if let Some(parent) = dist.parent() {
println!("cargo:rerun-if-changed={}", parent.display());
}
println!("cargo:rerun-if-changed={}", dist.display());
if dist.join("index.html").is_file() {
walk(&dist, &dist, &mut files);
for (_, abs) in &files {
println!("cargo:rerun-if-changed={}", abs.display());
}
} else {
println!("cargo:warning=gunnar-front: no built console at {} (index.html absent) — this binary serves a named refusal instead of the console", dist.display());
}
}
let _ = writeln!(
code,
"/// The built console's files, `(path, bytes)`.\npub static FILES: &[(&str, &[u8])] = &["
);
for (rel, abs) in &files {
let _ = writeln!(
code,
" ({rel:?}, include_bytes!({:?})),",
abs.display().to_string()
);
}
let _ = writeln!(code, "];");
let source = (!files.is_empty()).then(|| {
dist.canonicalize()
.unwrap_or(dist.clone())
.display()
.to_string()
});
let _ = writeln!(code, "/// Where they were read from at build time; `None` = not embedded.\npub const DIST_SOURCE: Option<&str> = {source:?};");
std::fs::write(out.join("console_dist.rs"), code).expect("write console_dist.rs");
// ── the signed gunnar CLI ───────────────────────────────────────────────
println!("cargo:rerun-if-env-changed=GUNNAR_FRONT_CLI_DIR");
println!("cargo:rerun-if-env-changed=GUNNAR_FRONT_CLI_ROOT_CA");
const TRIPLE: &str = "x86_64-unknown-linux-musl";
// Staged by `cargo xtask iso-installer` for a front medium (xtask/src/cli_stage.rs),
// as `.plugin-out/` is for gunnar-server: the signed binary, its sidecars, the root.
let cli_dir = std::env::var_os("GUNNAR_FRONT_CLI_DIR")
.filter(|v| !v.is_empty())
.map(PathBuf::from)
.unwrap_or_else(|| manifest.join("../../.cli-out"));
let root_ca = std::env::var_os("GUNNAR_FRONT_CLI_ROOT_CA")
.filter(|v| !v.is_empty())
.map(PathBuf::from)
.unwrap_or_else(|| {
let staged = cli_dir.join("korp-root-ca.der");
if staged.is_file() {
staged
} else {
siblings.join("dwarves/trust/korp-root-ca.der")
}
});
let served = format!("gunnar-{TRIPLE}");
let mut cli: Vec<(String, PathBuf)> = Vec::new();
for (from, to) in [
("gunnar".to_string(), served.clone()),
("gunnar.p7s".to_string(), format!("{served}.p7s")),
("gunnar.blake3".to_string(), format!("{served}.blake3")),
] {
let p = cli_dir.join(&from);
if p.is_file() {
println!("cargo:rerun-if-changed={}", p.display());
cli.push((to, p));
}
}
// The DIRECTORY too, as the console's parent is: a CLI staged AFTER a first
// build must reach the binary. `.cli-out/` is in every checkout (its
// `.gitkeep`), and a path watched while ABSENT would rerun this on every build.
if cli_dir.is_dir() {
println!("cargo:rerun-if-changed={}", cli_dir.display());
}
if root_ca.is_file() {
println!("cargo:rerun-if-changed={}", root_ca.display());
cli.push(("korp-root-ca.der".to_string(), root_ca.clone()));
}
if !cli.iter().any(|(n, _)| *n == served) {
println!(
"cargo:warning=gunnar-front: no gunnar CLI at {} — /download/ says [cli-not-on-medium]",
cli_dir.display()
);
}
let mut code = String::new();
let _ = writeln!(
code,
"/// The CLI downloads, `(served name, bytes)`.\npub static FILES: &[(&str, &[u8])] = &["
);
for (name, abs) in &cli {
let _ = writeln!(
code,
" ({name:?}, include_bytes!({:?})),",
abs.display().to_string()
);
}
let _ = writeln!(code, "];");
let source = cli
.iter()
.any(|(n, _)| *n == served)
.then(|| cli_dir.display().to_string());
let _ = writeln!(code, "/// Where the CLI was read from at build time; `None` = not embedded.\npub const CLI_SOURCE: Option<&str> = {source:?};");
std::fs::write(out.join("cli_downloads.rs"), code).expect("write cli_downloads.rs");
}