gunnar-server 3.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));
    }
}