From 52cfc3ea1c6e9508c05d422462415cb9c1178868 Mon Sep 17 00:00:00 2001 From: damocles Date: Sun, 31 May 2026 23:29:30 +0200 Subject: [PATCH] reshape swarm peers: domain-as-key, certFingerprint field --- hive-ag3nt/src/identity.rs | 8 ++-- hive-c0re/src/dashboard.rs | 28 ++++++-------- nix/modules/hive-c0re.nix | 75 +++++++++++++++----------------------- 3 files changed, 45 insertions(+), 66 deletions(-) diff --git a/hive-ag3nt/src/identity.rs b/hive-ag3nt/src/identity.rs index 97f511df..14acb7f2 100644 --- a/hive-ag3nt/src/identity.rs +++ b/hive-ag3nt/src/identity.rs @@ -51,14 +51,14 @@ pub fn swarm_name() -> Option { /// One peer hive in the same swarm. Parsed from `HYPERHIVE_PEERS`. #[derive(Debug, Clone, serde::Deserialize)] pub struct PeerHive { - pub label: String, pub domain: String, + pub cert_fingerprint: Option, } /// Peer hives in the same swarm, parsed from `HYPERHIVE_PEERS` env var -/// (JSON array of `{label,domain}` objects, emitted by the c0re NixOS -/// module from `services.hyperhive.peers`). Returns empty vec on -/// single-hive deploys (env var absent). +/// (JSON array of `{domain, cert_fingerprint}` objects, emitted by the +/// c0re NixOS module from `services.hyperhive.swarm.peers`). Returns +/// empty vec on single-hive deploys (env var absent). #[must_use] pub fn peers() -> Vec { env::var("HYPERHIVE_PEERS") diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index 7192d64a..15794ef6 100644 --- a/hive-c0re/src/dashboard.rs +++ b/hive-c0re/src/dashboard.rs @@ -267,19 +267,14 @@ struct StateSnapshot { /// unset — chrome omits the swarm segment of the breadcrumb. swarm_name: Option, /// Peer hives in the same swarm. Parsed from `HYPERHIVE_PEERS` - /// (JSON array of `{label,domain}` objects, emitted by the c0re - /// NixOS module from `services.hyperhive.peers`). Empty on - /// single-hive deploys. Feeds the P33RS dashboard tab. Each entry - /// exposes `name` (the operator-chosen label) and `url` (dashboard - /// link derived as `http:///`; HTTP-only until the gateway - /// has TLS and the scheme is threaded through). + /// (JSON array of `{domain,cert_fingerprint}` objects, emitted by + /// the c0re NixOS module from `services.hyperhive.swarm.peers`). + /// Empty on single-hive deploys. Feeds the P33RS dashboard tab. peer_hives: Vec, } /// One peer hive for the P33RS dashboard tab. Derived from -/// `HYPERHIVE_PEERS` env; `url` is the peer's dashboard root so the -/// tab can render a clickable card without knowing the remote port. -/// HTTP-only until per-peer TLS is wired through the gateway layer. +/// `HYPERHIVE_PEERS` env; `url` is the peer's HTTPS dashboard root. #[derive(Serialize)] struct PeerHiveView { name: String, @@ -498,15 +493,16 @@ async fn api_state(headers: HeaderMap, State(state): State) -> axum::J } /// Parse `HYPERHIVE_PEERS` env var into dashboard-ready `PeerHiveView` -/// entries. The env var is a JSON array of `{label, domain}` objects -/// emitted by the c0re NixOS module from `services.hyperhive.peers`. -/// Each entry becomes `{ name: label, url: "http://domain/" }` for the -/// P33RS tab. Returns empty vec when unset (single-hive deploy). +/// entries. The env var is a JSON array of `{domain, cert_fingerprint}` +/// objects emitted by the c0re NixOS module from +/// `services.hyperhive.swarm.peers`. Each entry becomes +/// `{ name: domain, url: "https://domain/" }` for the P33RS tab. +/// Returns empty vec when unset (single-hive deploy). fn parse_peer_hives() -> Vec { #[derive(serde::Deserialize)] struct Raw { - label: String, domain: String, + cert_fingerprint: Option, } let Ok(json) = std::env::var("HYPERHIVE_PEERS") else { return Vec::new(); @@ -517,8 +513,8 @@ fn parse_peer_hives() -> Vec { }; raw.into_iter() .map(|r| PeerHiveView { - name: r.label, - url: format!("http://{}/", r.domain), + name: r.domain.clone(), + url: format!("https://{}/", r.domain), }) .collect() } diff --git a/nix/modules/hive-c0re.nix b/nix/modules/hive-c0re.nix index 2565e27e..df54426f 100644 --- a/nix/modules/hive-c0re.nix +++ b/nix/modules/hive-c0re.nix @@ -99,48 +99,31 @@ in # containers via `meta.rs::FORWARDED_VARS`. Consumed by # `identity.rs::peers()` + the dashboard's `peer_hives` state field # (feeds the P33RS dashboard tab). - options.services.hyperhive.peers = lib.mkOption { - type = lib.types.attrsOf ( - lib.types.submodule { - options = { - domain = lib.mkOption { - type = lib.types.str; - example = "lab.example.com"; - description = '' - DNS domain of the peer hive. Used to construct the peer's - dashboard URL (`http://''${domain}/`) and for Matrix - federation auto-discovery (`matrix.''${domain}`). - Must be reachable from this host. - ''; - }; - tlsCertFile = lib.mkOption { - type = lib.types.nullOr lib.types.path; - default = null; - description = '' - Optional path to a PEM cert/bundle to trust for this peer's - TLS. Null = system CA bundle (for Let's Encrypt peers). Set - to the peer's self-signed cert for `selfSignedTls = true` - peers. Forward-compat slot; not yet used in v0. - ''; - }; + options.services.hyperhive.swarm.peers = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule { + options = { + certFingerprint = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "sha256:abc123..."; + description = '' + Expected TLS certificate fingerprint for this peer's HTTPS + endpoint. Null = trust the system CA bundle (for Let's + Encrypt peers). Set to pin a self-signed cert. + ''; }; - } - ); + }; + }); default = { }; example = { - lab = { - domain = "lab.example.com"; - }; - edge = { - domain = "edge.corp"; - }; + "lab.example.com" = { certFingerprint = "sha256:abc123"; }; + "edge.corp" = { }; }; description = '' - Peer hives in the same swarm. The attrset key is a short label - used in dashboard links and log messages -- it does not need to - match the remote hive's `hiveName`. Null `tlsCertFile` uses the - system CA bundle; set it for self-signed TLS peers (forward-compat - slot, not yet used in v0). + Peer hives in the same swarm. The attrset key is the peer's DNS + domain — used for dashboard links and Matrix federation discovery. + Null `certFingerprint` trusts the system CA bundle; set it to pin + a self-signed TLS cert. ''; }; @@ -365,16 +348,16 @@ in # falls back to `:3000`. HIVE_FORGE_PUBLIC_URL = "https://${config.services.hyperhive.forge.domain}"; } - // lib.optionalAttrs (config.services.hyperhive.peers != { }) { - # Peer hives serialised as a JSON array of {label, domain} objects. - # Consumed by hive-ag3nt::identity::peers() + the dashboard's - # peer_hives StateSnapshot field (P33RS tab). tlsCertFile is - # nix-side-only (host nginx/trust config); rust never needs the path. + // lib.optionalAttrs (config.services.hyperhive.swarm.peers != { }) { + # Peer hives serialised as a JSON array of {domain, cert_fingerprint} + # objects. Consumed by hive-ag3nt::identity::peers() + the dashboard's + # peer_hives StateSnapshot field (P33RS tab). Domain is the attrset key; + # cert_fingerprint is null for CA-trusted peers. HYPERHIVE_PEERS = builtins.toJSON ( - lib.mapAttrsToList (label: p: { - inherit label; - inherit (p) domain; - }) config.services.hyperhive.peers + lib.mapAttrsToList (domain: p: { + inherit domain; + cert_fingerprint = p.certFingerprint; + }) config.services.hyperhive.swarm.peers ); }; serviceConfig = {