From c086ee3edfde707606b00e42e2d0fcfbab6e01ba Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Sun, 8 Mar 2026 02:19:33 +0000 Subject: [PATCH] Add build version metadata, changelog, and version display Embed git commit hash, dirty flag, and target triple in all binaries via a zero-dependency build.rs. Wire clap short/long version output so -V shows "0.1.0 (rev abc1234)" and --version adds the target triple. Log version at daemon startup. Add version field to show_status control socket API response. Show the daemon's version in fipstop's tab bar title and Runtime section. Add CHANGELOG.md in Keep a Changelog format with the 0.1.0-alpha release (2026-02-24) and unreleased work since then. --- CHANGELOG.md | 59 +++++++++++++++++++++++++++++++++ build.rs | 39 ++++++++++++++++++++++ src/bin/fips.rs | 10 ++++-- src/bin/fipsctl.rs | 8 ++++- src/bin/fipstop/main.rs | 8 ++++- src/bin/fipstop/ui/dashboard.rs | 5 ++- src/bin/fipstop/ui/mod.rs | 3 +- src/control/queries.rs | 1 + src/lib.rs | 1 + src/version.rs | 38 +++++++++++++++++++++ 10 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 build.rs create mode 100644 src/version.rs diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..685c813 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,59 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Ethernet transport with beacon discovery and auto-connect +- TCP transport with configurable bind address and static peer support +- Docker sidecar deployment for containerized services +- Comprehensive node and transport statistics via control socket +- fipstop TUI monitoring tool with smoothed metrics and quality indices +- ECN congestion signaling and transport congestion detection +- Persistent identity with key file management (`fipsctl keygen`) +- Periodic Noise rekey with fresh DH for forward secrecy (FMP + FSP) +- Systemd service packaging with tarball installer +- Build version metadata: git commit hash, dirty flag, and target triple + embedded in all binaries via `--version` +- CHANGELOG.md following Keep a Changelog format + +### Fixed + +- Spanning tree coordinate loop: reject parents whose ancestry contains us +- PMTUD per-destination path MTU check and ICMPv6 MTU field width +- Ethernet AEAD decryption failures caused by minimum-frame padding +- Link-dead detection skipping peers that never send data +- FMP version check added to TCP stream reader + +### Changed + +- GitHub repository moved from `jmcorgan/fips` to `fips-network/fips` + +## [0.1.0-alpha] - 2026-02-24 + +### Added + +- Core mesh routing protocol with greedy coordinate-based forwarding +- Noise IK handshake at FMP (link layer) and Noise XK at FSP (session layer) +- UDP transport with configurable bind address and static peers +- TUN-based virtual network interface (fips0) with ICMPv6 Packet Too Big +- DNS resolver for .fips domain names (port 5354) +- Spanning tree construction with cost-based parent selection and + flap dampening +- Bloom filter-based identity discovery protocol with reverse-path routing +- MMP (Mesh Management Protocol) for link and session layer measurement +- Hybrid coordinate warmup (CoordsWarmup message and proactive fallback) +- Handshake retry with exponential backoff (link and session layer) +- Link-layer heartbeat and liveness timeout for dead peer detection +- Epoch-based peer restart detection +- Per-link MTU support and reactive MtuExceeded error signal +- Session idle timeout and identity cache expiry +- Unix domain control socket for runtime observability (fipsctl) +- Docker test harness with static and stochastic topologies +- CI with GitHub Actions (x86_64 and aarch64, unit and integration tests) +- Design documentation suite covering all protocol layers diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..ad14543 --- /dev/null +++ b/build.rs @@ -0,0 +1,39 @@ +use std::process::Command; + +fn main() { + // Git commit hash (short) + let git_hash = Command::new("git") + .args(["rev-parse", "--short=10", "HEAD"]) + .output() + .ok() + .filter(|o| o.status.success()) + .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) + .unwrap_or_default(); + println!("cargo:rustc-env=FIPS_GIT_HASH={git_hash}"); + + // Dirty working tree + let dirty = Command::new("git") + .args(["status", "--porcelain"]) + .output() + .ok() + .filter(|o| o.status.success()) + .map(|o| !o.stdout.is_empty()) + .unwrap_or(false); + if dirty { + println!("cargo:rustc-env=FIPS_GIT_DIRTY=-dirty"); + } else { + println!("cargo:rustc-env=FIPS_GIT_DIRTY="); + } + + // Build target triple + if let Ok(target) = std::env::var("TARGET") { + println!("cargo:rustc-env=FIPS_TARGET={target}"); + } + + // Rebuild when commits change + println!("cargo:rerun-if-changed=.git/HEAD"); + println!("cargo:rerun-if-changed=.git/refs/"); + + // Support reproducible builds (Debian packaging) + println!("cargo:rerun-if-env-changed=SOURCE_DATE_EPOCH"); +} diff --git a/src/bin/fips.rs b/src/bin/fips.rs index be28cc6..f6b11ab 100644 --- a/src/bin/fips.rs +++ b/src/bin/fips.rs @@ -4,6 +4,7 @@ use clap::Parser; use fips::config::{resolve_identity, IdentitySource}; +use fips::version; use fips::{Config, Node}; use std::path::PathBuf; use tracing::{error, info, warn, Level}; @@ -11,7 +12,12 @@ use tracing_subscriber::{fmt, EnvFilter}; /// FIPS mesh network daemon #[derive(Parser, Debug)] -#[command(name = "fips", version, about)] +#[command( + name = "fips", + version = version::short_version(), + long_version = version::long_version(), + about +)] struct Args { /// Path to configuration file (overrides default search paths) #[arg(short, long, value_name = "FILE")] @@ -32,7 +38,7 @@ async fn main() { let args = Args::parse(); - info!("FIPS starting"); + info!("FIPS {} starting", version::short_version()); // Load configuration info!("Loading configuration"); diff --git a/src/bin/fipsctl.rs b/src/bin/fipsctl.rs index 6fe90a2..327c973 100644 --- a/src/bin/fipsctl.rs +++ b/src/bin/fipsctl.rs @@ -5,6 +5,7 @@ use clap::{Parser, Subcommand}; use fips::config::{write_key_file, write_pub_file}; +use fips::version; use fips::{encode_nsec, Identity}; use std::io::{BufRead, BufReader, Write}; use std::os::unix::net::UnixStream; @@ -13,7 +14,12 @@ use std::time::Duration; /// FIPS control client #[derive(Parser, Debug)] -#[command(name = "fipsctl", version, about = "Query a running FIPS daemon")] +#[command( + name = "fipsctl", + version = version::short_version(), + long_version = version::long_version(), + about = "Query a running FIPS daemon" +)] struct Cli { /// Control socket path override #[arg(short = 's', long)] diff --git a/src/bin/fipstop/main.rs b/src/bin/fipstop/main.rs index d107471..b20cb58 100644 --- a/src/bin/fipstop/main.rs +++ b/src/bin/fipstop/main.rs @@ -7,13 +7,19 @@ use app::{App, ConnectionState, SelectedTreeItem, Tab}; use clap::Parser; use client::ControlClient; use event::{Event, EventHandler}; +use fips::version; use ratatui::crossterm::event::{KeyCode, KeyModifiers}; use std::path::{Path, PathBuf}; use std::time::Duration; /// FIPS mesh monitoring TUI #[derive(Parser, Debug)] -#[command(name = "fipstop", version, about = "Monitor a running FIPS daemon")] +#[command( + name = "fipstop", + version = version::short_version(), + long_version = version::long_version(), + about = "Monitor a running FIPS daemon" +)] struct Cli { /// Control socket path override #[arg(short = 's', long)] diff --git a/src/bin/fipstop/ui/dashboard.rs b/src/bin/fipstop/ui/dashboard.rs index acf2875..8662e46 100644 --- a/src/bin/fipstop/ui/dashboard.rs +++ b/src/bin/fipstop/ui/dashboard.rs @@ -41,6 +41,7 @@ fn draw_runtime(frame: &mut Frame, data: &serde_json::Value, area: Rect) { let inner = block.inner(area); frame.render_widget(block, area); + let version = helpers::str_field(data, "version"); let pid = helpers::u64_field(data, "pid"); let exe = helpers::str_field(data, "exe_path"); let uptime_secs = data.get("uptime_secs").and_then(|v| v.as_u64()).unwrap_or(0); @@ -52,7 +53,9 @@ fn draw_runtime(frame: &mut Frame, data: &serde_json::Value, area: Rect) { let lines = vec![ Line::from(vec![ - Span::styled(" pid: ", label), + Span::styled(" ver: ", label), + Span::raw(version.to_string()), + Span::styled(" pid: ", label), Span::raw(pid), Span::styled(" uptime: ", label), Span::raw(uptime), diff --git a/src/bin/fipstop/ui/mod.rs b/src/bin/fipstop/ui/mod.rs index 35d6c8a..2bedfb0 100644 --- a/src/bin/fipstop/ui/mod.rs +++ b/src/bin/fipstop/ui/mod.rs @@ -30,7 +30,8 @@ pub fn draw(frame: &mut Frame, app: &mut App) { } fn draw_tab_bar(frame: &mut Frame, app: &App, area: Rect) { - let block = Block::default().borders(Borders::ALL).title(" fipstop "); + let title = format!(" fipstop {} ", fips::version::short_version()); + let block = Block::default().borders(Borders::ALL).title(title); let inner = block.inner(area); frame.render_widget(block, area); diff --git a/src/control/queries.rs b/src/control/queries.rs index 89451d2..de2c339 100644 --- a/src/control/queries.rs +++ b/src/control/queries.rs @@ -40,6 +40,7 @@ pub fn show_status(node: &Node) -> Value { let fwd = node.stats().snapshot().forwarding; json!({ + "version": crate::version::short_version(), "npub": node.npub(), "node_addr": hex::encode(node.node_addr().as_bytes()), "ipv6_addr": format!("{}", node.identity().address()), diff --git a/src/lib.rs b/src/lib.rs index 3fda2c3..7273f8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ //! A distributed, decentralized network routing protocol for mesh nodes //! connecting over arbitrary transports. +pub mod version; pub mod bloom; pub mod cache; pub mod config; diff --git a/src/version.rs b/src/version.rs new file mode 100644 index 0000000..9ffd92e --- /dev/null +++ b/src/version.rs @@ -0,0 +1,38 @@ +//! Build version information for FIPS binaries. + +use std::sync::LazyLock; + +/// Package version from Cargo.toml. +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + +/// Short git commit hash (empty if not available). +const GIT_HASH: &str = env!("FIPS_GIT_HASH"); + +/// Dirty flag ("-dirty" or empty). +const GIT_DIRTY: &str = env!("FIPS_GIT_DIRTY"); + +/// Build target triple. +const TARGET: &str = env!("FIPS_TARGET"); + +/// Short version string for `-V`: `0.1.0 (rev abc1234567)` +#[allow(clippy::const_is_empty)] +static SHORT_VERSION: LazyLock = LazyLock::new(|| { + if GIT_HASH.is_empty() { + VERSION.to_string() + } else { + format!("{VERSION} (rev {GIT_HASH}{GIT_DIRTY})") + } +}); + +/// Long version string for `--version` with build metadata. +static LONG_VERSION: LazyLock = LazyLock::new(|| { + format!("{}\ntarget: {TARGET}", *SHORT_VERSION) +}); + +pub fn short_version() -> &'static str { + &SHORT_VERSION +} + +pub fn long_version() -> &'static str { + &LONG_VERSION +}