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

nordisk/holger

holger-estate-front/src/lib.rs

//! # holger-estate-front — vm1 of holger's estate, holger's OWN front program
//!
//! holger's estate is three VMs, the pattern gunnar's estate set: **vm1 the
//! front**, vm2 holger's primary, vm3 its twin. vm2 and vm3 boot the holger
//! medium (`holger-installer-metered`, one ISO, the role elected by the
//! installer); vm1 boots THIS program's medium, `holger-front-installer-metered`,
//! as PID 1. Like every box it reaches the installer over the installer's
//! WireGuard link, is fed, and carries on by itself (owner 2026-09-27: follow
//! gunnar's pattern, never gunnar's code — nothing here comes from the gunnar
//! repository; see the crate's Cargo.toml for which shared crate does what):
//!
//! * [`front`] — the installer's view of vm1 (Skidbladnir
//!   `nordisk-pairing::frontside`, kind `front`): what holger's front offers
//!   (its build, where its plugin door is, monetize's key) and hands its
//!   successor (the web certificate);
//! * [`role`] — the estate's WireGuard relay (edda `estate-front::relay`) on the
//!   front's own tunnel key, and the twin's certification by the front
//!   ([`certify`]: `nordisk-twin`'s state machine over holger's own
//!   AdminService, with the fed console bearer);
//! * the web face (edda `estate-front::web`): :80/:443 with the front's own
//!   certificate, the repository route (`repo_hosts` → the primary's HTTP
//!   :8443 and gRPC :50051 over the utility network) and — paid — the payment
//!   vendors' `/callback/<vendor>` to monetize;
//! * the estate's clock (edda `estate-clock`, started by `main`): this front
//!   keeps its own clock from the medium's `clock` sources and serves signed
//!   time on `<utility>:50123`; [`front`] offers `clock_key` + `clock_peer`,
//!   and every holger box follows it (holger `server_lib::clock`);
//! * [`console`] — holger's console (holger-ui's thin server, linked in): the
//!   bundle and a pass-through to the primary's two doors;
//! * [`shop_door`] + [`sold`] + [`update`] (feature `metered`) — NO monetize runs here
//!   (G16, ONE monetize): the front calls the ONE monetize's `SoldEstate` door,
//!   signed by the estate's client key — the installer material kept in the
//!   primary's sealed slot ([`sealed`]), the update the operator opened pulled box
//!   by box; it relays the ONE monetize's door and key to every box, whose own pull
//!   channel (holger-fleet) reports and takes orders.
//!
//! **A lost front comes back as Gunnar's does** (`EstateOrder.RestoreFront`, S8): the ONE
//! shop restores the provider's daily backup of the front's one disk, in place or onto a
//! new server, driven by the estate's lifecycle (`lifecycle::Holger`, front role `front`).
//! Nothing Holger-specific is needed: everything this front is keeps on that disk under its
//! data root (its tunnel key `wg/`, its clock key, the twin's certification record, the web
//! certificates), so a restored front keeps every key and nothing is re-trusted; the
//! installer material is in the primary's sealed slot, not on the front; and main and twin
//! follow a moved front through their own installer listener's re-feed
//! (`nordisk_pairing::boxside`, the same code Gunnar's boxes run).

/// A re-laid main filled from its twin by this front (Gunnar's autofill, lane G11).
pub mod autofill;
/// Holger's site in the shared buy flow (monetize-site-buy, A12): the relay on the front.
#[cfg(all(feature = "console", feature = "metered"))]
pub mod buy;
pub mod certify;
/// This front's certificates for holger-ui's admin page (owner 2026-10-25).
#[cfg(feature = "console")]
pub mod certs_page;
#[cfg(feature = "console")]
pub mod console;
pub mod console_key;
pub mod front;
/// The front's own live slot on trial (L20).
pub mod live;
/// The front proves the tunnel is the only path to its boxes (lane W9, Gunnar's mesh probe).
pub mod mesh_probe;
/// A lost main or twin re-admitted by the front (feature `metered`); the pulled order fills a main.
#[cfg(feature = "metered")]
pub mod recharge;
/// Every paired link renewed before it ends, as jera jobs (lane E2).
pub mod renew;
pub mod role;
/// The primary's sealed slot (feature `metered`).
#[cfg(feature = "metered")]
pub mod sealed;
/// The ONE monetize's `SoldEstate` door (feature `metered`).
#[cfg(feature = "metered")]
pub mod shop_door;
/// The sold front's duties as jera jobs (feature `metered`).
#[cfg(feature = "metered")]
pub mod sold;
/// The update the ONE box opened, decided box by box.
pub mod update;
/// The front keeps the certificate its installer renews (lane E2): the shared box half,
/// Skidbladnir `nordisk_pairing::boxside::renewing` (P4c, A28 M11).
pub use nordisk_pairing::boxside::renewing;
/// The front's own WireGuard interface for the downstreams declared `via: Wireguard`, and
/// the sync door on it (lane HOL-WGDS).
pub mod downstreams;
/// The front tells main its doors, the names it serves and where each goes (lane
/// HOL-FLOW-FRONT): main's flow unfolds the front to it.
pub mod front_report;
/// A panic on the front is a fault with Holger's code (lane G6, Gunnar's GN-99 hook).
pub mod pid1_fault;
/// The front reaches its boxes over the tunnel only (lane E1).
pub mod wg_only;

use std::net::Ipv4Addr;
use std::path::PathBuf;

use nordisk_pairing::join::JoinReply;

/// The product this front fronts.
pub const PRODUCT: &str = "holger";
/// The build this binary is (the short commit, `unknown` outside a checkout).
pub const GIT_HASH: &str = env!("HOLGER_FRONT_GIT_HASH");
/// Where the medium stages the seed, under the data root (as on a holger box).
pub const SEED_REL: &str = "pairing/seed.toml";
/// The data root's variable and default — the same as a holger box's.
pub const DATA_ENV: &str = "HOLGER_APPLIANCE_DATA";
/// The interface the front's public address is on (`HOLGER_NET`'s first entry): where a
/// WireGuard downstream reaches the front's downstream interface (lane HOL-WGDS).
pub const PUBLIC_INTERFACE: &str = "eth0";
/// The interface the front's utility address is on (`HOLGER_NET`'s second
/// entry, `eth1` — the network the boxes' control planes are on).
pub const UTILITY_INTERFACE: &str = "eth1";
/// holger's control plane (gRPC) on a box's utility address.
pub const CONTROL_PORT: u16 = 50051;
/// holger's repository HTTP door on a box's utility address (the estate's RON:
/// `ron_http_url: 0.0.0.0:8443`, the firewall admits it from vm1 only).
pub const HTTP_PORT: u16 = 8443;
/// **The gRPC services the front carries to the primary on the repository host**
/// (RA5-1, edda `Routed::grpc_services`): every `holger.*` service (each checks its
/// own caller: a bearer, a signed-in person, a repository token). Nothing else: the
/// identity core's `nornir.identity.v1.IdentityService` (sign-up, invites) is the
/// primary's own and is never reached from the internet through the front.
pub const CARRIED_GRPC: &[&str] = &["holger."];

/// **Where the front keeps its own copy of its boot log**, under its data root (Gunnar
/// 6dcbbe38, `installer_listener::KEEP_REL`): bounded (nordisk-pairing `KEEP_BYTES`,
/// rotated to `.1`), so a crash leaves its last lines on the disk and the next boot serves
/// them through the log door.
pub const KEEP_REL: &str = "log/door.log";

/// **Keep `log` on the front's disk** for the life of the process: the previous boot's last
/// lines carried in (marked `(previous boot) `), then every line written to
/// `<data_root>/`[`KEEP_REL`] within a second. Answers how many lines were carried.
pub fn keep_on_disk(
    log: &'static nordisk_pairing::bootlog::BootLog,
    data_root: &std::path::Path,
) -> usize {
    log.keep_on_disk(
        data_root.join(KEEP_REL),
        nordisk_pairing::bootlog::KEEP_BYTES,
    )
}

/// The data root: `HOLGER_APPLIANCE_DATA`, else `/data`.
pub fn data_root() -> PathBuf {
    std::env::var(DATA_ENV)
        .ok()
        .filter(|v| !v.trim().is_empty())
        .map(PathBuf::from)
        .unwrap_or_else(|| PathBuf::from("/data"))
}

/// The primary's control plane (`<overlay>:50051` on an estate, else `<utility>:50051`)
/// by the verdict, under the process posture ([`wg_only::wg_only`]).
pub fn primary_control(reply: &JoinReply) -> Option<String> {
    primary_control_in(reply, wg_only::wg_only())
}

/// Pure: [`primary_control`] under `wg`.
pub fn primary_control_in(reply: &JoinReply, wg: bool) -> Option<String> {
    wg_only::box_addr(reply, nordisk_pairing::Role::Primary, wg)
        .map(|ip| format!("{ip}:{CONTROL_PORT}"))
}

/// The primary's address the front reaches (overlay on an estate, else utility).
pub fn primary_addr(reply: &JoinReply) -> Option<Ipv4Addr> {
    wg_only::box_addr(reply, nordisk_pairing::Role::Primary, wg_only::wg_only())
}

/// Pure: **the primary's HTTP door** (`<utility>:8443`) — where holger serves
/// its passkey plane (`/-/auth/passkey/*`), which the console carries every
/// person's login, invite and session to (holger-ui `door`, the thin-layer
/// move 2026-10-04). `None` until the verdict names a primary.
pub fn primary_door(reply: &JoinReply) -> Option<String> {
    primary_door_in(reply, wg_only::wg_only())
}

/// Pure: [`primary_door`] under `wg`.
pub fn primary_door_in(reply: &JoinReply, wg: bool) -> Option<String> {
    wg_only::box_addr(reply, nordisk_pairing::Role::Primary, wg)
        .map(|ip| format!("{ip}:{HTTP_PORT}"))
}

/// The twin's address the front reaches (overlay on an estate, else utility), when
/// the verdict names one.
pub fn twin_addr(reply: &JoinReply) -> Option<Ipv4Addr> {
    twin_addr_in(reply, wg_only::wg_only())
}

/// Pure: [`twin_addr`] under `wg`.
pub fn twin_addr_in(reply: &JoinReply, wg: bool) -> Option<Ipv4Addr> {
    wg_only::box_addr(reply, nordisk_pairing::Role::Twin, wg)
}

/// The front, as the installer sees it.
pub type FrontVm = nordisk_pairing::frontside::Front<front::HolgerFront>;

/// **The pairing files a fresh installation of this front must not carry**: the shared ones
/// (Skidbladnir `nordisk_pairing::boxside::PAIRING_STATE`), the twin's certification record
/// and the renewal state.
pub fn pairing_state() -> Vec<&'static str> {
    let mut state: Vec<&str> = nordisk_pairing::boxside::PAIRING_STATE.to_vec();
    state.push(certify::RECORD_REL);
    state.push(renew::STATE_REL);
    state
}

/// **The first boot after an installation** (`main`): the previous installation's pairing
/// state ([`pairing_state`]) is cleared so a front installed for a NEW estate starts
/// unpaired — and a KEEP boot of the SAME estate clears nothing (lane G5, A7 row 6; Gunnar
/// ff58dad1 `vm::first_boot`): the shop keep-boots the front from the release's keep medium,
/// its data kept, and nobody feeds a front again (Skidbladnir
/// `nordisk_pairing::boxside::reset_if_fresh_install_of`). `Some(vec![])`: a keep boot, kept.
pub fn first_boot(
    esp: &std::path::Path,
    data: &std::path::Path,
    seed: &nordisk_pairing::seed::Seed,
    mark: &dyn Fn(&std::path::Path) -> bool,
) -> Option<Vec<String>> {
    nordisk_pairing::boxside::reset_if_fresh_install_of(esp, data, &pairing_state(), seed, mark)
}

/// **Holger's TLS on :443** (edda `Hooks::tls`, lane WG-PSK 2026-10-30): the provider the web
/// face is served with — Gunnar's, X25519MLKEM768 offered FIRST, the classical groups after it.
pub fn tls() -> std::sync::Arc<rustls::crypto::CryptoProvider> {
    std::sync::Arc::new(nordisk_pairing::pqtls::provider())
}

#[cfg(test)]
pub(crate) mod tests {
    use super::*;
    use nordisk_pairing::join::{Assigned, Peer};
    use nordisk_pairing::Role;

    const TOKEN: &str = "c0nsole-bearer-0123456789abcdefghij";

    /// ★ **The front's :443 offers X25519MLKEM768 first** (lane WG-PSK, 2026-10-30: holger.rs
    /// negotiated plain X25519, gunnar.rs the hybrid): the same groups, in the same order, as
    /// Gunnar's front (`gunnar_front::web::tls`), so an older client still connects on X25519.
    #[test]
    fn the_fronts_tls_offers_x25519mlkem768_first_and_keeps_the_classical_groups() {
        let names: Vec<rustls::NamedGroup> = tls().kx_groups.iter().map(|g| g.name()).collect();
        assert_eq!(
            names,
            vec![
                rustls::NamedGroup::X25519MLKEM768,
                rustls::NamedGroup::X25519,
                rustls::NamedGroup::secp256r1,
                rustls::NamedGroup::secp384r1
            ]
        );
    }

    fn peer(name: &str, role: Role, utility: Option<[u8; 4]>) -> Peer {
        Peer {
            name: name.into(),
            role,
            tunnel_pubkey: String::new(),
            overlay: nordisk_pairing::OVERLAY_BOX_A,
            endpoint: None,
            utility: utility.map(Ipv4Addr::from),
        }
    }

    pub(crate) fn fed(feed: &[(&str, &str)], peers: Vec<Peer>) -> JoinReply {
        JoinReply {
            estate: "holger.test".into(),
            me: Assigned {
                name: "vm1".into(),
                overlay: nordisk_pairing::OVERLAY_FRONT,
            },
            cert_pem: None,
            role: Some(Role::Front),
            primary_overlay: None,
            primary_utility: None,
            peers,
            feed: feed
                .iter()
                .map(|(k, v)| (k.to_string(), v.to_string()))
                .collect(),
        }
    }

    fn estate() -> Vec<Peer> {
        vec![
            peer("vm1", Role::Front, None),
            peer("vm2", Role::Primary, Some([10, 13, 8, 2])),
            peer("vm3", Role::Twin, Some([10, 13, 8, 3])),
        ]
    }

    /// **The feed, read**: the console bearer (a weak one refused by name, never
    /// presented), the primary's control plane and the twin by the verdict, the
    /// certification target (the twin reads the primary's door; no second
    /// election), and the repository route to the primary's 8443/50051.
    #[test]
    fn the_front_reads_its_feed_the_verdict_the_bearer_the_certification_and_the_route() {
        // No shared bearer is fed any more (A7 row 14): an old feed's token is not read.
        assert_eq!(
            console_key::bearers_with(
                Some([0x11; 32]),
                &fed(&[("holger_console_token", TOKEN)], estate())
            ),
            Ok(None)
        );
        let r = fed(
            &[
                ("web_hosts", "holger.test,repo.holger.test"),
                ("repo_hosts", "repo.holger.test"),
            ],
            estate(),
        );
        assert_eq!(
            primary_control_in(&r, false).as_deref(),
            Some("10.13.8.2:50051")
        );
        assert_eq!(twin_addr_in(&r, false), Some(Ipv4Addr::new(10, 13, 8, 3)));
        assert_eq!(
            primary_control_in(&fed(&[], vec![peer("vm1", Role::Front, None)]), false),
            None,
            "no verdict yet"
        );

        let t = certify::target_in(&r, false).expect("the verdict names both");
        assert_eq!(
            (
                t.reader_ctl.as_str(),
                t.source_ctl.as_str(),
                t.become_twin_of.is_none(),
                t.principal.as_str(),
                t.purpose.as_str()
            ),
            (
                "10.13.8.3:50051",
                "10.13.8.2:50051",
                true,
                "holger-twin",
                "twin"
            )
        );
        assert!(
            certify::target_in(
                &fed(
                    &[],
                    vec![
                        peer("vm2", Role::Primary, Some([10, 13, 8, 2])),
                        peer("vm3", Role::Twin, None)
                    ]
                ),
                false
            )
            .is_none(),
            "a twin with no utility address is not dialled"
        );

        let web = estate_front::web::WebConfig::from("holger.test", &r.feed);
        let route = estate_front::route::RepoRoute::of(
            &r.feed,
            &web,
            PRODUCT,
            Some((HTTP_PORT, CONTROL_PORT)),
            CARRIED_GRPC,
        );
        let routed = route
            .routed(nordisk_pairing::frontside::primary_utility(&r))
            .expect("routed");
        assert_eq!(
            (
                routed.hosts.clone(),
                routed.http.to_string(),
                routed.grpc.to_string()
            ),
            (
                vec!["repo.holger.test".to_string()],
                "10.13.8.2:8443".to_string(),
                "10.13.8.2:50051".to_string()
            )
        );
        // RA5-1: Holger's services are carried, the identity core is not.
        for rpc in [
            "/holger.v1.RepositoryService/Get",
            "/holger.grow.v1.GrowService/Status",
            "/holger.token.v1.TokenService/Mint",
        ] {
            assert!(
                estate_front::web::serve::carries(&routed.grpc_services, rpc),
                "{rpc}"
            );
        }
        for rpc in [
            "/nornir.identity.v1.IdentityService/SignUp",
            "/nornir.identity.v1.IdentityService/CreateInvite",
            "/grpc.reflection.v1.ServerReflection/ServerReflectionInfo",
        ] {
            assert!(
                !estate_front::web::serve::carries(&routed.grpc_services, rpc),
                "{rpc} is not carried"
            );
        }
    }

    /// The console's wiring: the primary's two doors and the bearer (a weak one
    /// refused by name, never presented).
    #[cfg(feature = "console")]
    #[test]
    fn the_console_is_wired_from_the_feed() {
        // The primary (vm2) offered its console key; the front derives its bearer (A7 row 14).
        let primary_pub = wg_appliance_core::keys::encode_key(
            &wg_appliance_core::keys::public_from_private_bytes(&[0x22; 32]),
        );
        let offered = fed(
            &[("offer.vm2.console_box_pubkey", primary_pub.as_str())],
            estate(),
        );
        let w = console::Wiring::of_with(Some([0x11; 32]), &offered);
        let want = console_key::bearer(&[0x11; 32], &primary_pub).unwrap();
        assert_eq!(
            (
                w.appliance.as_deref(),
                w.door.as_deref(),
                w.token.as_deref()
            ),
            (
                Some("10.13.8.2:50051"),
                Some("10.13.8.2:8443"),
                Some(want.as_str())
            )
        );
        let unminted = console::Wiring::of_with(None, &offered);
        assert!(
            unminted.token.is_none(),
            "no console key adopted yet: no bearer"
        );
        let bad = console::Wiring::of_with(
            Some([0x11; 32]),
            &fed(&[("offer.vm2.console_box_pubkey", "short")], estate()),
        );
        assert!(
            bad.token.is_none()
                && bad
                    .refused
                    .iter()
                    .any(|l| l.contains("[holger-console-key-bad]")),
            "{:?}",
            bad.refused
        );
        assert!(
            console::Wiring::of(&fed(&[], vec![peer("vm1", Role::Front, None)]))
                .appliance
                .is_none(),
            "no verdict yet"
        );
    }

    /// ★ The shop's sale check reads `/-/releases` with the bearer it fed the front; the
    /// front passes it on with the primary's derived bearer, and touches no other bearer
    /// (measured on the mock gate 2026-10-22: the main refused the shop's own, holger-front-3).
    #[cfg(feature = "console")]
    #[test]
    fn the_shops_bearer_reaches_the_primary_as_the_primarys_own() {
        let primary_pub = wg_appliance_core::keys::encode_key(
            &wg_appliance_core::keys::public_from_private_bytes(&[0x22; 32]),
        );
        let offered = fed(
            &[
                ("offer.vm2.console_box_pubkey", primary_pub.as_str()),
                (console::SHOP_BEARER_FEED, TOKEN),
            ],
            estate(),
        );
        let w = console::Wiring::of_with(Some([0x11; 32]), &offered);
        let want = format!(
            "Bearer {}",
            console_key::bearer(&[0x11; 32], &primary_pub).unwrap()
        );
        assert_eq!(w.shop_bearer.as_deref(), Some(TOKEN));
        assert_eq!(
            console::present(&w, Some(&format!("Bearer {TOKEN}"))),
            Some(want)
        );
        assert_eq!(
            console::present(&w, Some("Bearer someone-elses-session")),
            None,
            "a person's bearer is passed on as it is"
        );
        assert_eq!(console::present(&w, None), None);
        assert_eq!(console::present(&w, Some(TOKEN)), None, "only a bearer");
        let unoffered = console::Wiring::of_with(
            Some([0x11; 32]),
            &fed(&[(console::SHOP_BEARER_FEED, TOKEN)], estate()),
        );
        assert_eq!(
            console::present(&unoffered, Some(&format!("Bearer {TOKEN}"))),
            None,
            "no derived bearer yet: nothing to swap to"
        );
        assert_eq!(
            console::present(
                &console::Wiring::of_with(
                    Some([0x11; 32]),
                    &fed(
                        &[("offer.vm2.console_box_pubkey", primary_pub.as_str())],
                        estate()
                    )
                ),
                Some(&format!("Bearer {TOKEN}"))
            ),
            None,
            "no shop bearer fed"
        );
    }

    fn a_seed() -> nordisk_pairing::seed::Seed {
        nordisk_pairing::seed::Seed {
            estate: "holger.test".into(),
            tunnel: nordisk_pairing::seed::TunnelKind::Wireguard,
            front: Default::default(),
            kind: nordisk_pairing::seed::Kind::Front,
            installer_ca_pem: String::new(),
            bootstrap_cert_pem: String::new(),
            bootstrap_key_pem: String::new(),
            peers_expected: 3,
            mullvad: None,
            tailscale: None,
            installer_link: None,
        }
    }

    /// ★ **A front KEEP-BOOTED onto its own data comes up FED** (lane G5, A7 row 6; Gunnar
    /// ff58dad1 `a_front_keep_booted_onto_its_kept_data_comes_up_fed_and_can_reach_its_shop`):
    /// a new ESP onto the kept data volume, the same estate: the feed (sealed), its own key,
    /// the twin's certification record are kept, and the boot is marked once. A front
    /// installed for ANOTHER estate onto kept data still starts unpaired.
    #[test]
    fn a_front_keep_booted_onto_its_kept_data_comes_up_fed() {
        let data = tempfile::tempdir().unwrap();
        let mut reply = fed(&[("web_hosts", "holger.test")], estate());
        reply.feed.insert(
            "shop_door.toml".into(),
            "door = \"https://one.example:443\"\n".into(),
        );
        nordisk_pairing::fed_at_rest::write(
            data.path(),
            nordisk_pairing::boxside::JOINED_REL,
            &reply.render().unwrap(),
        )
        .unwrap();
        std::fs::write(
            data.path().join(nordisk_pairing::boxside::OWN_KEY_REL),
            "-----BEGIN PRIVATE KEY-----\nk\n",
        )
        .unwrap();
        std::fs::write(data.path().join(certify::RECORD_REL), "kept = true\n").unwrap();
        let mark = |p: &std::path::Path| nordisk_pairing::boxside::mark_esp(p);
        let esp = tempfile::tempdir().unwrap();
        std::fs::create_dir_all(esp.path().join("korp")).unwrap();
        let cleared = first_boot(esp.path(), data.path(), &a_seed(), &mark);
        assert!(
            cleared.as_ref().is_none_or(|c| c.is_empty()),
            "a keep boot of the same estate clears nothing: {cleared:?}"
        );
        assert!(
            esp.path()
                .join(nordisk_pairing::boxside::ESP_CLEARED)
                .exists(),
            "decided once"
        );
        let kept =
            nordisk_pairing::fed_at_rest::read(data.path(), nordisk_pairing::boxside::JOINED_REL)
                .and_then(|t| JoinReply::parse(&t).ok())
                .expect("still fed");
        assert!(
            kept.feed.contains_key("shop_door.toml"),
            "the shop door to pull UpdateTake from"
        );
        assert!(
            data.path()
                .join(nordisk_pairing::boxside::OWN_KEY_REL)
                .exists()
                && data.path().join(certify::RECORD_REL).exists()
        );
        let esp2 = tempfile::tempdir().unwrap();
        std::fs::create_dir_all(esp2.path().join("korp")).unwrap();
        let other = nordisk_pairing::seed::Seed {
            estate: "other.test".into(),
            ..a_seed()
        };
        let cleared = first_boot(esp2.path(), data.path(), &other, &mark).expect("a first boot");
        assert!(
            cleared.contains(&nordisk_pairing::boxside::JOINED_REL.to_string())
                && cleared.contains(&certify::RECORD_REL.to_string()),
            "{cleared:?}"
        );
    }
}