nordisk-pairing 0.5.25

The boxes of an estate find each other: a seed each box is installed with, a one-time join against the front, a tunnel (WireGuard by default) they meet inside, roles decided once, a pairing loop each box runs itself, and the signal it reports. Shared by gunnar and holger; the laptop's xtask only lays machines, writes seeds and reads the signal. Design: .nornir/pairing-design.md (2026-09-24).
Documentation
//! **The boxes of an estate find each other.**
//!
//! Until 2026-09-24 the laptop's xtask was the matchmaker of the gunnar.rs
//! estate: three machines booted knowing nothing of each other, and every
//! introduction — bootstrap the primary with the front's console key, enrol
//! the principals, confer the twin's role, sign its certificate — was one ssh
//! command from a laptop, run once, in order. Every one of them was a place
//! the chain broke (a box not listening yet, a killed run, a dropped packet),
//! and a laptop in the middle of an installation is a single point of failure
//! that has nothing to do with the estate.
//!
//! The owner's order (2026-09-24): the xtask does not pair. It starts the
//! machines with enough information for them to form a tunnel — WireGuard by
//! default; Tailscale, Mullvad, or none — and the boxes pair each other inside
//! it. gunnar and holger share this crate. Design: `Skidbladnir/.nornir/pairing-design.md`.
//!
//! The pieces, in the order a box meets them:
//!
//! 1. [`seed`] — what a MEDIUM is told at compose time, the same for every
//!    lay: the estate, the tunnel kind, the join door's fixed address, the
//!    installer CA and the bootstrap certificate. Nothing per box, nothing per
//!    lay — the medium is constant (2026-09-24 evening).
//! 2. [`tunnel`] — the driver trait and the `none` driver. Only the FRONT needs
//!    a known address; the others dial it, and the overlay addresses are ours.
//! 3. [`join`] — the one-time join against the front's door over mutual TLS:
//!    the box presents its tunnel public key and its capacity; the door admits
//!    it by the address it joins from, and answers with its name and overlay
//!    address, the peers (the front's tunnel first), the role, and the FEED.
//! 4. [`elect`] — the weakest gunnar box becomes the twin, decided ONCE on the
//!    front from two capacity reports, never re-decided.
//! 5. [`pair`] — the state machine each box runs: tunnel up → joined → role
//!    known → paired, every step idempotent and retried by the box itself.
//! 6. [`signal`] — where a box is, as one line and as a value; what the xtask
//!    (and an operator) READS instead of doing.
//!
//! Pure Rust, no shell (LAW). No driver here ever answers "up" for a tunnel it
//! did not bring up: `none` and `wireguard` work, `mullvad` works as each
//! box's EXIT beside the WireGuard mesh, and `tailscale` refuses by name
//! (`[tunnel-tailscale-unimplemented]`) — see [`tunnel`].

/// A re-laid main filled from its twin by the front (Gunnar's autofill, shared: lane G11).
pub mod autofill;
pub mod bootlog;
pub mod bootstrap;
pub mod boxloop;
pub mod boxside;
pub mod door;
pub mod frontside;
pub mod installer;
pub mod installer_link;
pub mod elect;
pub mod fed_at_rest;
pub mod feedbus;
/// The pull channel's money-free rules: the bench seam and "not known yet" (lane G9).
pub mod fleet;
pub mod join;
/// The kept installer material of a sold estate (feature `kept`).
#[cfg(feature = "kept")]
pub mod kept;
pub mod live;
pub mod mtls;
pub mod narrator;
pub mod pair;
pub mod pqtls;
pub mod redact;
pub mod seed;
pub mod signal;
pub mod tunnel;
#[cfg(feature = "wireguard")]
pub mod wireguard;
#[cfg(feature = "mullvad")]
pub mod mullvad;
#[cfg(feature = "tailscale")]
pub mod tailscale;

use std::net::Ipv4Addr;

/// **The overlay addresses are a design constant, not a cloud fact.** The
/// front is `.1`; the two gunnar boxes are `.2` and `.3` and their ROLES are
/// decided by the election, so a seed names a box's address, never its role.
pub const OVERLAY_NET: (Ipv4Addr, u8) = (Ipv4Addr::new(10, 44, 0, 0), 24);
pub const OVERLAY_FRONT: Ipv4Addr = Ipv4Addr::new(10, 44, 0, 1);
pub const OVERLAY_BOX_A: Ipv4Addr = Ipv4Addr::new(10, 44, 0, 2);
pub const OVERLAY_BOX_B: Ipv4Addr = Ipv4Addr::new(10, 44, 0, 3);

/// The port the boxes' control planes listen on inside the tunnel — gunnar's
/// gRPC control port, unchanged.
pub const CONTROL_PORT: u16 = 50051;

/// The front's WireGuard relay (the estate's hub): udp/51820 on vm1.
pub const RELAY_PORT: u16 = 51820;

/// What a box IS in the estate once the election has spoken.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Role {
    Front,
    Primary,
    Twin,
}

impl Role {
    pub fn as_str(self) -> &'static str {
        match self {
            Role::Front => "front",
            Role::Primary => "primary",
            Role::Twin => "twin",
        }
    }
}

/// Is `ip` inside the overlay network?
pub fn in_overlay(ip: Ipv4Addr) -> bool {
    let (net, bits) = OVERLAY_NET;
    let mask = if bits == 0 { 0 } else { u32::MAX << (32 - bits) };
    (u32::from(ip) & mask) == (u32::from(net) & mask)
}

/// **What the tests hear** through `tracing` (0.5.18, lane G7): every line said while `f`
/// runs, as the fmt layer writes it (level and message).
#[cfg(test)]
pub(crate) fn heard<R>(f: impl FnOnce() -> R) -> (R, String) {
    #[derive(Clone, Default)]
    struct Heard(std::sync::Arc<std::sync::Mutex<Vec<u8>>>);
    impl std::io::Write for Heard {
        fn write(&mut self, b: &[u8]) -> std::io::Result<usize> {
            self.0.lock().unwrap().extend_from_slice(b);
            Ok(b.len())
        }
        fn flush(&mut self) -> std::io::Result<()> {
            Ok(())
        }
    }
    let h = Heard::default();
    let w = h.clone();
    let sub = tracing_subscriber::fmt().with_writer(move || w.clone()).with_ansi(false).finish();
    let r = tracing::subscriber::with_default(sub, f);
    let said = String::from_utf8(h.0.lock().unwrap().clone()).unwrap();
    (r, said)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn the_overlay_is_ours_and_the_three_addresses_are_in_it() {
        assert!(in_overlay(OVERLAY_FRONT) && in_overlay(OVERLAY_BOX_A) && in_overlay(OVERLAY_BOX_B));
        assert!(!in_overlay(Ipv4Addr::new(10, 13, 8, 179)), "a UpCloud utility address is not overlay");
        assert_ne!(OVERLAY_BOX_A, OVERLAY_BOX_B);
    }
}