From 7d8a5864014a9f06c13594604ccc39996edba25f Mon Sep 17 00:00:00 2001 From: Dorian Date: Sun, 19 Apr 2026 15:19:56 -0400 Subject: [PATCH] fix(fips,iso): match upstream fips schema + guard ISO against stale binary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. FIPS daemon config schema drifted: upstream jmcorgan/fips now takes `node.identity.persistent: true` (keys read from config-dir/fips.key) and `transports.udp.bind_addr: "0.0.0.0:PORT"` instead of `identity.key_file/pub_file` + `transports.udp.enabled/port`. The `tor:` transport was dropped entirely; archipelago handles Tor fallback itself. fips.yaml generated by archipelago::fips::config now matches the upstream schema, and archipelago-fips.service stops crashlooping on Activate. Observed on .198: 52 restarts with "data did not match any variant of untagged enum TransportInstances at line 7 column 3". 2. ISO backend-binary capture didn't verify that the captured binary matched the checked-out Cargo.toml version. Today's 14:40 ISO shipped a stale 1.4.0 binary because `core/target/release/archipelago` pre-dated the 1.5.0-alpha bump — the build grabbed it via the first-priority "local release build" path without looking at it. All four capture sources now go through verify_backend_version() which greps the binary for the expected version string; mismatches are skipped so the build falls through to the source-build path. Co-Authored-By: Claude Opus 4.7 (1M context) --- core/archipelago/src/fips/config.rs | 40 ++++++++++++---------- image-recipe/build-auto-installer-iso.sh | 42 ++++++++++++++++++++---- 2 files changed, 58 insertions(+), 24 deletions(-) diff --git a/core/archipelago/src/fips/config.rs b/core/archipelago/src/fips/config.rs index 62007903..74c63fd3 100644 --- a/core/archipelago/src/fips/config.rs +++ b/core/archipelago/src/fips/config.rs @@ -24,26 +24,28 @@ use super::{DAEMON_CONFIG_PATH, DAEMON_KEY_PATH, DAEMON_PUB_PATH, DEFAULT_UDP_PO /// IPv6 TUN + DNS on defaults. Static peer list is empty — archipelago /// feeds peers dynamically via federation updates. pub fn render_config_yaml() -> String { + // Schema matches upstream jmcorgan/fips as of 2026-04. With + // `node.identity.persistent: true` the daemon reuses the key file at + // config-dir/fips.key (= DAEMON_KEY_PATH). Transports take `bind_addr` + // rather than `enabled: true / port: N`, and the upstream no longer + // has a `tor:` transport — archipelago's own Tor fallback handles that. format!( "# Generated by archipelago — do not edit by hand.\n\ # Regenerated on every key change and daemon upgrade.\n\ - identity:\n \ - key_file: {key_path}\n \ - pub_file: {pub_path}\n\ - transports:\n \ - udp:\n \ - enabled: true\n \ - port: {port}\n \ - tor:\n \ - enabled: true\n\ + node:\n \ + identity:\n \ + persistent: true\n\ tun:\n \ - enabled: true\n\ + enabled: true\n \ + name: fips0\n \ + mtu: 1280\n\ dns:\n \ enabled: true\n \ - suffix: .fips\n\ + bind_addr: \"127.0.0.1\"\n\ + transports:\n \ + udp:\n \ + bind_addr: \"0.0.0.0:{port}\"\n\ peers: []\n", - key_path = DAEMON_KEY_PATH, - pub_path = DAEMON_PUB_PATH, port = DEFAULT_UDP_PORT, ) } @@ -125,14 +127,16 @@ mod tests { use super::*; #[test] - fn test_rendered_yaml_contains_paths_and_port() { + fn test_rendered_yaml_matches_upstream_schema() { let yaml = render_config_yaml(); - assert!(yaml.contains(DAEMON_KEY_PATH)); - assert!(yaml.contains(DAEMON_PUB_PATH)); - assert!(yaml.contains(&DEFAULT_UDP_PORT.to_string())); + assert!(yaml.contains("persistent: true")); + assert!(yaml.contains(&format!("0.0.0.0:{}", DEFAULT_UDP_PORT))); assert!(yaml.contains("udp:")); - assert!(yaml.contains("tor:")); assert!(yaml.contains("tun:")); + assert!(yaml.contains("name: fips0")); + // Upstream fips dropped the `tor:` transport variant; archipelago + // handles Tor fallback itself. Make sure we didn't regress. + assert!(!yaml.contains("tor:")); } #[tokio::test] diff --git a/image-recipe/build-auto-installer-iso.sh b/image-recipe/build-auto-installer-iso.sh index 14519f2f..777fafff 100755 --- a/image-recipe/build-auto-installer-iso.sh +++ b/image-recipe/build-auto-installer-iso.sh @@ -1013,19 +1013,47 @@ fi # Try to get backend binary: local release build → local install → remote → container build BACKEND_CAPTURED=0 +# The captured binary MUST report the same version as the checked-out +# core/archipelago/Cargo.toml, otherwise we're shipping a stale binary +# from an earlier version bump (which is what happened with the 14:40 +# ISO — it grabbed an Apr-18 1.4.0 binary and the fleet rejected the +# fips.yaml it wrote out on Activate). The expected version is the one +# compiled into this build run. +EXPECTED_VERSION="$(grep '^version' "$(cd "$SCRIPT_DIR/.." && pwd)/core/archipelago/Cargo.toml" | head -1 | sed 's/.*"\(.*\)".*/\1/')" +echo " Expected backend version (from Cargo.toml): $EXPECTED_VERSION" + +verify_backend_version() { + local bin="$1" + local embedded + # CARGO_PKG_VERSION is compiled into the binary as a string literal; + # the easiest way to recover it without running the daemon is to grep + # the binary for an anchored version string. This is cheap and safe. + embedded=$(strings "$bin" 2>/dev/null | grep -E "^${EXPECTED_VERSION}$" | head -1) + if [ -z "$embedded" ]; then + echo " ⚠️ Captured binary does NOT contain expected version $EXPECTED_VERSION — it is stale" + return 1 + fi + echo " ✅ Version match: binary contains $EXPECTED_VERSION" + return 0 +} + # Check for local release binary first (works for both BUILD_FROM_SOURCE and normal mode) LOCAL_RELEASE="$(cd "$SCRIPT_DIR/.." && pwd)/core/target/release/archipelago" if [ -f "$LOCAL_RELEASE" ]; then - cp "$LOCAL_RELEASE" "$ARCH_DIR/bin/archipelago" - chmod +x "$ARCH_DIR/bin/archipelago" - echo " ✅ Backend from local release build ($(du -h "$ARCH_DIR/bin/archipelago" | cut -f1))" - BACKEND_CAPTURED=1 + if verify_backend_version "$LOCAL_RELEASE"; then + cp "$LOCAL_RELEASE" "$ARCH_DIR/bin/archipelago" + chmod +x "$ARCH_DIR/bin/archipelago" + echo " ✅ Backend from local release build ($(du -h "$ARCH_DIR/bin/archipelago" | cut -f1))" + BACKEND_CAPTURED=1 + else + echo " Skipping stale local release binary; trying next source" + fi fi if [ "$BACKEND_CAPTURED" = "0" ] && [ "$BUILD_FROM_SOURCE" != "1" ]; then # Direct copy from ARCHIPELAGO_BIN env or local install BIN="${ARCHIPELAGO_BIN:-/usr/local/bin/archipelago}" - if [ -f "$BIN" ]; then + if [ -f "$BIN" ] && verify_backend_version "$BIN"; then cp "$BIN" "$ARCH_DIR/bin/archipelago" chmod +x "$ARCH_DIR/bin/archipelago" echo " ✅ Backend captured from local system ($(du -h "$ARCH_DIR/bin/archipelago" | cut -f1))" @@ -1033,10 +1061,12 @@ if [ "$BACKEND_CAPTURED" = "0" ] && [ "$BUILD_FROM_SOURCE" != "1" ]; then fi # Remote copy via SCP if local failed if [ "$BACKEND_CAPTURED" = "0" ] && [ "$DEV_SERVER" != "localhost" ] && [ "$DEV_SERVER" != "127.0.0.1" ]; then - if scp "$DEV_SERVER:/usr/local/bin/archipelago" "$ARCH_DIR/bin/archipelago" 2>/dev/null; then + if scp "$DEV_SERVER:/usr/local/bin/archipelago" "$ARCH_DIR/bin/archipelago" 2>/dev/null && verify_backend_version "$ARCH_DIR/bin/archipelago"; then chmod +x "$ARCH_DIR/bin/archipelago" echo " ✅ Backend captured from remote server ($(du -h "$ARCH_DIR/bin/archipelago" | cut -f1))" BACKEND_CAPTURED=1 + else + rm -f "$ARCH_DIR/bin/archipelago" fi fi fi