crates/gunnar-ui-server/src/lib.rs
//! The gunnar console's backend-for-frontend.
//!
//! The browser cannot reach gunnar directly. gunnar's control plane is native
//! gRPC over plaintext HTTP/2 on loopback, with no grpc-web layer and no CORS,
//! and its every call carries a single-use `sshsig` credential signed by an SSH
//! private key. gunnar's own build guard bans HTTP frameworks outright, so that
//! is not going to change. This crate is the seam: it holds the operator key and
//! signs, terminates passkey sessions for humans, and re-publishes what gunnar
//! answers as same-origin REST and SSE.
//!
//! What it also does, because gunnar does not, is slice unbounded list
//! responses, remember the event sequence across a dropped stream, and hold
//! read-heavy answers briefly so a page of panels does not spend gunnar's
//! replay-guard budget on questions it already asked.
/// **The anonymous viewer, driven end to end.** Tests only, and its own module
/// because what it asserts is a property of the seam between the console's
/// admin credential and the guest one — not of any single handler, so it does
/// not belong inside one.
#[cfg(test)]
mod anonymous;
/// **The console's one address scheme** (GUN-GUEST): `/{ns}/{name}/…`, and the 301 from
/// the old `/repo/…`.
pub mod address;
pub mod api;
pub mod app;
pub mod auth;
/// **`/etc/gunnar-ui.env`, declared.** Which variables this console reads,
/// which of them it cannot start correctly without, and what each one is for —
/// as a typed table rather than a heredoc in a README. `--env-template` renders
/// it; `tests/env_declaration.rs` fails when the binary grows a `GUNNAR_UI_*`
/// the table does not know about.
pub mod envfile;
pub mod forge;
/// The front's certificates for the Administration page (owner 2026-10-25).
pub mod front_certs;
/// **Somebody else's forge**, and the one place a forge token lives in this
/// process. Not [`forge`], which is gunnar's own review model — see the module
/// docs for why the two names are a hair apart and the things are not.
/// Behind `forges` (AIRGAP: it dials the forges); without it, a stand-in that refuses by name.
#[cfg(feature = "forges")]
pub mod forges;
#[cfg(not(feature = "forges"))]
#[path = "forges_off.rs"]
pub mod forges;
/// True when this binary carries forge import (`forges`).
pub const FORGES: bool = cfg!(feature = "forges");
pub mod git;
pub mod grpc;
/// **The systemd unit, declared** — the skidbladnir `ServiceSpec` this console
/// is installed from (`--unit`, `--install`, `--uninstall`, …). It replaces
/// `deploy/gunnar-ui.service`, which was hand-written and copied into place by
/// a shell script.
pub mod lifecycle;
/// **A console switch kept as a generic gunnar setting** (`public-site` and `mcp` builds
/// only): the administrator's switch for the public site and the MCP connector. A default
/// build carries none of it. See the module.
#[cfg(any(feature = "public-site", feature = "mcp"))]
pub mod console_setting;
/// **Gunnar's MCP connector** (feature `mcp`, off by default): the
/// `gunnar-mcp` crate's backend and mount. Nothing else in this crate knows
/// it exists. See the module.
#[cfg(feature = "mcp")]
pub mod mcp;
/// True when this binary carries the MCP connector (`mcp`).
pub const MCP: bool = cfg!(feature = "mcp");
/// **Importing repositories, driven end to end.** Tests only, and its own
/// module for the reason [`anonymous`] is: what it asserts is a property of the
/// path a forge token takes through this process — browser to session store to
/// `Mirror.SetCredential` — and not of any single handler.
/// The log capture the tests share (tests only).
#[cfg(test)]
mod captured;
#[cfg(all(test, feature = "forges"))]
mod mirroring;
/// **An ordinary user imports into their OWN namespace**, driven end to end:
/// grants, room, limits, and the credential's one way. Tests only.
#[cfg(all(test, feature = "forges"))]
mod tenant_import;
/// **The owner's account model** (2026-10-03): a repository's visibility and
/// the account's settings are its ACCOUNT admin's, an account's namespace is
/// private until set, a private namespace shows a visitor only its public
/// repositories, and the account admin enrolls NEW employees. Driven end to
/// end against a fake gunnar. Tests only.
#[cfg(test)]
mod account_model;
/// **Passkeys, invites and the passkey↔principal pin are gunnar's** (thin
/// layer L): the login verified by gunnar, the session signed with the key it
/// enrols, invites made and redeemed at gunnar, passkeys from before moved
/// there. Tests only.
#[cfg(test)]
mod passkeys_at_gunnar;
/// **The first admin's passkey IS the estate's operator** — registered with the
/// break-glass token on test2's gunnar of 2026-10-04, then logged in, then
/// reading `You → Account` and adding SSH keys, with no operator step. Tests
/// only.
#[cfg(test)]
mod first_admin_login;
/// **The console is a thin layer** (owner, 2026-10-04): a person's reads are
/// asked of gunnar as that person, and a second tenant is refused another
/// account's data by gunnar on their own key. Tests only.
#[cfg(test)]
mod thin_layer;
/// **The console relays the ONE identity core** (IDENTITY-HOST): login, step-up,
/// logout, invites and the first admin through `nornir.identity.v1.IdentityService`,
/// against edda's real core. Tests only.
#[cfg(test)]
mod identity_core;
/// **The seam to monetize**, and nothing else in this crate knows it exists.
///
/// Compiled only under `metered`. The reasoning for the feature name and for
/// the type being distinct from [`grpc::Gunnar`] is in the module itself.
#[cfg(feature = "metered")]
pub mod monetize;
/// **What does this session get?** The one place the logged-in / anonymous fork
/// is decided — which view of a markdown file opens, whether the choice is
/// remembered, whether the PDF is offered — and where a person's answer is
/// kept. The owner expects more such separations; this is where they go.
pub mod reading;
/// **gunnar.rs's public face** (feature `public-site`, off by default): the
/// `gunnar-public-site` crate's source and mount. Nothing else in this crate
/// knows it exists. See the module.
#[cfg(feature = "public-site")]
pub mod public_site;
/// **A page's content as plain HTML for the console's document** (GUN-GUEST, `public-site`):
/// the README through the console's own render, the files, the commits, the issues, read as
/// the grantless guest. See the module.
#[cfg(feature = "public-site")]
pub mod prerender;
/// True when this binary carries the public site (`public-site`).
pub const PUBLIC_SITE: bool = cfg!(feature = "public-site");
/// **A repository's README, typeset**: markdown -> typst -> SVG, all in this
/// process and with no network under it. The module docs carry the measured
/// render times and the one honest cost (the glyphs are paths, so the page is
/// not selectable text).
pub mod readme;
/// **The release layout on the host** — `current`/`previous`/`releases/<tag>`,
/// the atomic symlink swap, the rollback and the prune, ported out of
/// `deploy/deploy.sh`'s remote heredocs into `std::fs`. The reasoning in that
/// script is good and it survives whole; what it lacked was a way to be tested.
pub mod release;
/// **`reset-state`**: getting back into a console nobody can sign in to, in
/// two scopes — the passkeys alone, or the whole store — with the blast radius
/// printed before anything is destroyed. See the module for why `rm
/// state.redb` was worse than it looked.
/// **The console's program** (`gunnar-ui` and `gunnar-ui-server` both run it).
pub mod program;
pub mod reset;
pub mod reviewer;
pub mod role;
pub mod security;
pub mod serve;
pub mod signer;
pub mod sse;
pub mod state;
/// **The per-tenant read credential**: one ed25519 key per console user,
/// enrolled on that person's own gunnar principal, so a session's reads carry
/// that principal's grants and nobody else's. See the module.
pub mod tenant;
/// **The one throttle every request passes** (GUN-GUEST).
pub mod throttle;
/// The crate version, as the manifest spells it.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
/// `git describe` of the source this binary was built from (`-modified` for a
/// dirty tree, `unknown` without a `.git`) — stamped by `build.rs`.
pub const BUILD_REF: &str = env!("GUNNAR_UI_BUILD_REF");
/// The SOURCE's date (`SOURCE_DATE_EPOCH` or HEAD's committer date), UTC.
pub const BUILD_DATE: &str = env!("GUNNAR_UI_BUILD_DATE");
/// **Which half this binary is**, in one word: `metered` or `community`.
///
/// A COMPILED-IN fact, and that is the whole point of it. `metered` is a
/// non-default feature, so `cargo build --release -p gunnar-ui-server` produces
/// a console that cannot sell — and until 2026-09-13 nothing about that console
/// said so. It started, answered `/health`, served every page, read
/// `GUNNAR_UI_MONETIZE` out of its environment file, and answered a paying
/// stranger at `/welcome` with 401 "log in to use the console". Two live card
/// payments were taken on 2026-09-11 and refunded.
///
/// The lesson is written into [`crate::lifecycle::refusals`] as a refusal and
/// into this constant as the fact the refusal reads: **a feature that silently
/// did not apply looks exactly like one that did**, so the artifact has to be
/// able to say which one it is. A command line, a build log and an operator's
/// memory are all claims ABOUT the binary; this is the binary.
#[cfg(feature = "metered")]
pub const HALF: &str = "metered";
#[cfg(not(feature = "metered"))]
pub const HALF: &str = "community";
/// [`HALF`] as the question the code actually asks. Not `cfg!` at every call
/// site: [`lifecycle::refusals`](crate::lifecycle::refusals) takes it as an
/// argument so that one test binary can drive both halves, which is the only
/// way the refusal below can be seen to go red.
pub const METERED: bool = cfg!(feature = "metered");
/// What `--version` prints and `GET /api/version` carries as `version`:
/// `gunnar-ui-server 0.1.0 (v0.1.0-12-gabcdef0, built 2026-08-29T10:00:00Z,
/// metered)` — the same shape as `gunnar --version`, so a rig can hold both
/// binaries to the commit they came from, plus [`HALF`], so it can also hold
/// this one to the half it was asked for. `deploy/deploy.sh` reads that last
/// word off the artifact it is about to ship rather than trusting the `cargo
/// build` line it just ran.
#[cfg(feature = "metered")]
pub const VERSION_LINE: &str = concat!(
"gunnar-ui-server ",
env!("CARGO_PKG_VERSION"),
" (",
env!("GUNNAR_UI_BUILD_REF"),
", built ",
env!("GUNNAR_UI_BUILD_DATE"),
", metered)"
);
/// See the `metered` twin above. Two definitions rather than one, because
/// `concat!` takes literals and not constants.
#[cfg(not(feature = "metered"))]
pub const VERSION_LINE: &str = concat!(
"gunnar-ui-server ",
env!("CARGO_PKG_VERSION"),
" (",
env!("GUNNAR_UI_BUILD_REF"),
", built ",
env!("GUNNAR_UI_BUILD_DATE"),
", community)"
);
/// Generated clients for gunnar's control plane, package `gunnar.v1`.
///
/// This process consumes gunnar's services and never serves them. The generated
/// SERVER traits are here all the same, and only the tests use them: they are
/// how `api::usage` stands a gunnar up in-process and makes it refuse on
/// demand, which is the only way the console's refusal path can be seen to work
/// without a running server (see `build.rs`).
// Generated code: its doc comments are the protos', as written there.
#[allow(clippy::doc_overindented_list_items, clippy::doc_lazy_continuation)]
pub mod pb {
tonic::include_proto!("gunnar.v1");
}
/// Generated clients for **monetize's** control plane, package `monetize.v1`.
///
/// A second module and not more of [`pb`], because they are two different
/// servers with two different credentials and one namespace holding both would
/// make `pb::Money` read as something gunnar knows about. gunnar does not know
/// what a byte is worth and this crate must keep saying so.
///
/// It is monetize-proto (RA3-4): every monetize message and service, clients and servers.
/// Servers are generated as well as clients, and only the tests use them: it is
/// how `api::increase` stands a monetize up in-process and makes it refuse on
/// demand, which is the only way the refusal paths here can be seen to work
/// without a running monetize.
///
/// RA3-4 (2026-10-26): this is monetize's own `monetize-proto` (0.3.0 on crates.io), not a
/// hand copy of five of its protos compiled here.
#[cfg(feature = "metered")]
pub mod mpb {
pub use monetize_proto::*;
}