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
//! The console dashboard, end to end into a buffer: real `/proc` facts on
//! this box, real tapped log lines, one rendered frame — asserted on content,
//! not on "it did not panic".

use std::path::PathBuf;

use gunnar_common::logtap::{self, TapLayer};
use gunnar_server::dashboard::{Facts, Screen, DEFAULT_COLS, DEFAULT_ROWS, EVENT_ROWS};
use tracing_subscriber::layer::SubscriberExt as _;

#[test]
fn a_frame_carries_this_box_and_the_lines_that_were_logged() {
    let subscriber = tracing_subscriber::registry().with(TapLayer::new());
    let rx = tracing::subscriber::with_default(subscriber, || {
        let rx = logtap::take_receiver().expect("the tap's receiver, once");
        let span = tracing::info_span!(
            "ssh.connection",
            peer = "10.0.2.2:40000",
            principal = tracing::field::Empty,
            repo = tracing::field::Empty,
            service = tracing::field::Empty
        );
        span.record("principal", "alice");
        span.record("repo", "ns/demo");
        span.record("service", "git-receive-pack");
        let _e = span.enter();
        tracing::info!(target: "gunnar::ssh", event = "ssh.auth.accepted", fingerprint = "SHA256:ZZZZZZZZZZZZZZZZZZZZ", "publickey accepted");
        tracing::info!(target: "gunnar::git", event = "git.transfer.finished", status = "ok", elapsed_ms = 812u64, "transfer finished");
        tracing::info!(target: "gunnar::git", event = "git.upload_pack.advertised", bytes_out = 10u64, "not a row");
        rx
    });

    let mut screen = Screen::new(Facts {
        ssh_bind: "0.0.0.0:2222".parse().unwrap(),
        grpc_bind: None,
        data_root: PathBuf::from("/"),
        build: "test-build".into(),
        engine: None,
        replication: gunnar_server::replication_view::ReplicationView::new(),
        tenants: gunnar_server::dashboard::TenantView::new(),
        flow: gunnar_server::dashboard::FlowView::new(),
    });
    screen.sample();
    for line in rx.try_iter() {
        screen.take(&line);
    }
    // A second sample so the CPU percentage has an interval to measure.
    std::thread::sleep(std::time::Duration::from_millis(30));
    screen.sample();

    let frame = screen.render_frame(DEFAULT_COLS, DEFAULT_ROWS);
    // Every row closes with erase-to-EOL (clears column 80 without touching
    // the VT's wrap flag); strip it before measuring the padded width.
    let raw: Vec<&str> = frame.trim_start_matches("\x1b[H").split("\r\n").collect();
    assert!(
        raw.iter().all(|l| l.ends_with("\x1b[K")),
        "every row ends with ESC[K"
    );
    let lines: Vec<&str> = raw
        .iter()
        .map(|l| l.strip_suffix("\x1b[K").unwrap())
        .collect();
    assert_eq!(lines.len(), DEFAULT_ROWS);
    assert!(lines.iter().all(|l| l.chars().count() == DEFAULT_COLS - 1));

    // The band: the facts this kernel gave.
    assert!(
        lines[0].contains("gunnar test-build")
            && lines[0].contains(" up ")
            && !lines[0].contains("up -"),
        "{}",
        lines[0]
    );
    assert!(
        lines[2].contains("ram ") && !lines[2].contains("ram -"),
        "{}",
        lines[2]
    );
    assert!(
        lines[3].contains("disk  / ")
            && !lines[3].contains("disk  / -")
            && lines[3].contains("ssh *:2222")
            && !lines[3].contains("grpc"),
        "{}",
        lines[3]
    );
    // What `/` IS on this box, read off /proc/mounts the same way the cell
    // did: the filesystem name is on the row (an md root adds its shape).
    let mounts = std::fs::read_to_string("/proc/mounts").unwrap();
    let root = gunnar_server::dashboard::probe::parse_mounts(&mounts, "/").expect("/ is a mount");
    let fs = if root.fstype == "vfat" {
        "fat"
    } else {
        root.fstype.as_str()
    };
    assert!(
        lines[3].contains(&format!(" {fs}   ssh")),
        "the disk cell names the root's filesystem ({fs}): {}",
        lines[3]
    );
    assert!(screen.cpus >= 1 && screen.uptime_secs.is_some());

    // The rows: the two lines that are events, in order, and not the third.
    let body: Vec<&str> = lines[5..]
        .iter()
        .copied()
        .filter(|l| !l.trim().is_empty())
        .collect();
    assert_eq!(body.len(), 2, "{body:?}");
    assert!(
        body[0].contains("login   alice  ZZZZZZZZZZZZ\u{2026}  10.0.2.2:40000"),
        "{}",
        body[0]
    );
    assert!(
        body[1].contains("push    ns/demo  alice  ok  812 ms"),
        "{}",
        body[1]
    );
    assert!(!frame.contains("not a row"));
    assert_eq!(
        EVENT_ROWS + 5,
        DEFAULT_ROWS,
        "the ring and the band fill exactly one VGA screen"
    );
}

// The device-seam test (`GUNNAR_DASHBOARD=<path>`) lives in its own binary,
// `tests/dashboard_seam.rs`, since 2026-09-04: it and the test above both need
// the log tap's process-global one-shot receiver, and sharing a process made
// whichever ran second take a degenerate arm — or fail on the race between
// `spawn` looking for the tap and this file installing it.