crates/installer/nordisk-pairing/src/lib.rs
//! **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;
/// A box's own background duty, supervised (A28 M18).
pub mod duty;
pub mod frontside;
pub mod installer;
pub mod installer_link;
pub mod elect;
/// The estate's way out through its front: the lean estate's egress door (2026-10-23).
pub mod egress;
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);
}
}