gunnar-server 2.0.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
//! **`Admin.Topology`'s facts from this server** (2026-10-04): what only
//! `gunnar-server` holds — the installer's pairing feed (`joined.toml`) with
//! the ESTATE RECORD it carries (the `estate.*` keys: every box as the kit laid
//! it), which principals run on the front, the plugin registration, this box's
//! own data volume. The assembly and every verdict are `gunnar_grpc::topology`'s; this
//! module only reads.

use std::path::Path;

use gunnar_grpc::topology::Facts;

/// The front's principals, as the pairing feed offers them, and what each one
/// is on the front. `front-certify` is the front itself (no service word of
/// its own: it signs the twin's certificate).
#[cfg(feature = "wg-client")]
fn front_principals() -> std::collections::BTreeMap<String, String> {
    crate::fed_principals::OFFERED_PRINCIPALS
        .iter()
        .map(|(offer, id, _)| {
            let service = match *offer {
                crate::fed_principals::OFFER_MONETIZE_IDENTITY => gunnar_grpc::topology::SERVICE_MONETIZE,
                crate::fed_principals::OFFER_CONSOLE_PUBKEY | crate::fed_principals::OFFER_GUEST_PUBKEY => gunnar_grpc::topology::SERVICE_CONSOLE,
                _ => gunnar_grpc::topology::ROLE_FRONT,
            };
            (id.to_string(), service.to_string())
        })
        .collect()
}

/// **The pairing feed's view of the estate**, from `joined.toml` — pure over
/// the reply, so a test hands it one.
#[cfg(feature = "wg-client")]
pub fn from_reply(reply: &nordisk_pairing::join::JoinReply) -> Facts {
    use nordisk_pairing::Role;
    let me = reply.me.name.clone();
    let address = match (reply.role, reply.primary_utility) {
        (Some(Role::Primary), Some(utility)) => utility.to_string(),
        _ => reply.me.overlay.to_string(),
    };
    let peers = reply
        .peers
        .iter()
        .filter(|p| p.name != me)
        .map(|p| {
            let mut hosts = Vec::new();
            if let Some(u) = p.utility {
                hosts.push(u.to_string());
            }
            // The primary's control plane is its utility address, which the
            // reply carries apart from the peer list.
            if p.role == Role::Primary {
                if let Some(u) = reply.primary_utility {
                    hosts.push(u.to_string());
                }
            }
            hosts.push(p.overlay.to_string());
            if let Some(e) = &p.endpoint {
                hosts.push(gunnar_grpc::topology::host_of(e));
            }
            hosts.dedup();
            gunnar_grpc::topology::Peer { name: p.name.clone(), role: p.role.as_str().to_owned(), hosts }
        })
        .collect();
    Facts {
        estate: reply.estate.clone(),
        me,
        address,
        peers,
        front_principals: front_principals(),
        // The installer's record of the estate, from the same feed; renewed by
        // every re-admission (upgrade, recharge), which rewrites joined.toml.
        record: gunnar_grpc::topology::Record::from_feed(&reply.feed),
        ..Facts::default()
    }
}

/// **Everything this server knows**, read at the moment of the call.
pub fn facts(root: &Path) -> Facts {
    #[cfg(feature = "wg-client")]
    let mut facts = crate::fed_at_rest::read(root, crate::pairing::JOINED_REL)
        .and_then(|t| nordisk_pairing::join::JoinReply::parse(&t).ok())
        .map(|reply| from_reply(&reply))
        .unwrap_or_else(|| Facts { front_principals: front_principals(), ..Facts::default() });
    #[cfg(not(feature = "wg-client"))]
    let mut facts = Facts::default();

    let fs = crate::stores::filesystem_use("data root", root);
    if fs.measured && fs.total_bytes > 0 {
        facts.disk = Some((fs.total_bytes, fs.total_bytes.saturating_sub(fs.free_bytes)));
    }
    facts
}

#[cfg(all(test, feature = "wg-client"))]
mod tests {
    use super::*;
    use nordisk_pairing::join::{Assigned, JoinReply, Peer as PairPeer};
    use nordisk_pairing::Role;
    use std::net::Ipv4Addr;

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

    /// ★ **The pairing feed names the estate**: this box by its name and
    /// utility address, every OTHER peer with every address it can be
    /// matched on, and the front's offered principals with what each is.
    #[test]
    fn the_feed_names_this_box_and_its_peers() {
        let reply = JoinReply {
            estate: "test2.gunnar.rs".into(),
            me: Assigned { name: "vm2".into(), overlay: Ipv4Addr::new(10, 99, 0, 2) },
            cert_pem: None,
            role: Some(Role::Primary),
            primary_overlay: Some(Ipv4Addr::new(10, 99, 0, 2)),
            primary_utility: Some(Ipv4Addr::new(10, 13, 8, 121)),
            peers: vec![
                peer("front", Role::Front, [10, 99, 0, 1], None, Some("95.111.211.189:51820")),
                peer("vm2", Role::Primary, [10, 99, 0, 2], None, None),
                peer("vm3", Role::Twin, [10, 99, 0, 3], Some([10, 13, 8, 122]), None),
            ],
            feed: Default::default(),
        };
        let f = from_reply(&reply);
        assert_eq!((f.estate.as_str(), f.me.as_str(), f.address.as_str()), ("test2.gunnar.rs", "vm2", "10.13.8.121"));
        assert_eq!(f.record, None, "a feed with no estate record is no record");
        let names: Vec<_> = f.peers.iter().map(|p| (p.name.as_str(), p.role.as_str())).collect();
        assert_eq!(names, [("front", "front"), ("vm3", "twin")], "this box is not its own peer");
        assert_eq!(f.peers[0].hosts, ["10.99.0.1", "95.111.211.189"]);
        assert_eq!(f.peers[1].hosts, ["10.13.8.122", "10.99.0.3"]);
        assert_eq!(f.front_principals.get("gunnar-ui").map(String::as_str), Some("console"));
        assert_eq!(f.front_principals.get("monetize").map(String::as_str), Some("monetize"));
    }

    /// Unpaired (a solo gunnar, the uitest rig): no estate, no peers — and
    /// the data volume is still measured.
    #[test]
    fn an_unpaired_box_knows_itself_and_its_disk() {
        let dir = tempfile::tempdir().unwrap();
        let f = facts(dir.path());
        assert!(f.estate.is_empty() && f.peers.is_empty());
        assert!(f.disk.is_some_and(|(total, _)| total > 0));
    }

    /// ★ **The installer's record rides the feed the pairing persisted**: a
    /// box fed `estate.*` keys reads back every laid box, as the kit wrote it.
    #[test]
    fn the_feed_carries_the_installers_estate_record() {
        use gunnar_grpc::topology::{Record, RecordBox};
        let record = Record {
            variant: "gunnar-twin".into(),
            versions: vec![("gunnar".into(), "abc".into())],
            boxes: ["front", "main", "twin"]
                .iter()
                .map(|n| RecordBox { name: n.to_string(), laid_as: n.to_string(), utility: format!("10.0.0.{}", n.len()), ..Default::default() })
                .collect(),
        };
        let reply = JoinReply {
            estate: "e".into(),
            me: Assigned { name: "main".into(), overlay: Ipv4Addr::new(10, 99, 0, 2) },
            cert_pem: None,
            role: Some(Role::Primary),
            primary_overlay: None,
            primary_utility: None,
            peers: vec![],
            feed: record.to_feed(),
        };
        // Through the persisted text, as the server reads it at each call.
        let reply = JoinReply::parse(&reply.render().unwrap()).unwrap();
        assert_eq!(from_reply(&reply).record, Some(record));
    }
}