From 1c1416cc1a1d3351f2eb33aec904c77c1ae99acc Mon Sep 17 00:00:00 2001 From: Dorian Date: Tue, 21 Apr 2026 09:25:53 -0400 Subject: [PATCH] release(v1.7.25-alpha): TCP transport for public FIPS mesh + modal cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-adds the TCP transport (`0.0.0.0:8443`) to the rendered fips.yaml alongside UDP. Upstream factory default enables both; we had inadvertently narrowed to UDP-only when the yaml rewriter was last touched, which left nodes unable to reach fips.v0l.io (the public anchor only answers on TCP right now) or talk across networks that block UDP. Backend startup now compares the installed yaml against the current rendered schema and restarts whichever fips unit is active when they differ — so OTA-upgrading nodes pick up the new transport without anyone having to click Reconnect. Dropped the earlier plan to auto-add federated peers as seed anchors: invites don't carry a FIPS-reachable IP:port, and once TCP reconnects the public mesh, federated peers become npub-routable without needing a seed entry. Seed Anchors modal cleanup: replaced malformed header icon with a three-arc broadcast glyph, and the close button now matches the What's New modal (embedded in the card header, same icon + hover style) instead of the earlier floating off-design placeholder. Co-Authored-By: Claude Opus 4.7 (1M context) --- core/Cargo.lock | 2 +- core/archipelago/Cargo.toml | 2 +- core/archipelago/src/fips/config.rs | 26 ++++++++++++------ core/archipelago/src/fips/mod.rs | 8 ++++++ core/archipelago/src/server.rs | 27 +++++++++++++++++++ neode-ui/src/views/server/FipsNetworkCard.vue | 24 +++++++---------- .../src/views/server/FipsSeedAnchorsCard.vue | 23 +++++++++++++--- .../src/views/settings/AccountInfoSection.vue | 12 +++++++++ 8 files changed, 97 insertions(+), 27 deletions(-) diff --git a/core/Cargo.lock b/core/Cargo.lock index 86dcc38d..3949d227 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "archipelago" -version = "1.7.24-alpha" +version = "1.7.25-alpha" dependencies = [ "anyhow", "archipelago-container", diff --git a/core/archipelago/Cargo.toml b/core/archipelago/Cargo.toml index 2c48ef8c..cd3464a8 100644 --- a/core/archipelago/Cargo.toml +++ b/core/archipelago/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "archipelago" -version = "1.7.24-alpha" +version = "1.7.25-alpha" edition = "2021" description = "Archipelago Bitcoin Node OS - Native backend" authors = ["Archipelago Team"] diff --git a/core/archipelago/src/fips/config.rs b/core/archipelago/src/fips/config.rs index b6083dec..af17d2e7 100644 --- a/core/archipelago/src/fips/config.rs +++ b/core/archipelago/src/fips/config.rs @@ -13,22 +13,27 @@ use anyhow::{Context, Result}; use std::path::Path; use tokio::process::Command; -use super::{DAEMON_CONFIG_PATH, DAEMON_KEY_PATH, DAEMON_PUB_PATH, DEFAULT_UDP_PORT}; +use super::{DAEMON_CONFIG_PATH, DAEMON_KEY_PATH, DAEMON_PUB_PATH, DEFAULT_TCP_PORT, DEFAULT_UDP_PORT}; /// Write the FIPS daemon config based on the local npub and default /// transports. Overwrites any existing file — callers are expected to /// re-run this whenever the key or daemon version changes. /// /// Schema is intentionally minimal: node identity comes from the key -/// file on disk (the daemon handles it), transports enable UDP + Tor, -/// IPv6 TUN + DNS on defaults. Static peer list is empty — archipelago -/// feeds peers dynamically via federation updates. +/// file on disk (the daemon handles it), transports enable UDP + TCP +/// (matching upstream factory default), IPv6 TUN + DNS on defaults. +/// Static peer list is empty — archipelago feeds peers dynamically via +/// the seed-anchors apply loop and federation-invite hooks. 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. + // rather than `enabled: true / port: N`. Both UDP and TCP are + // enabled by default because the public anchor (fips.v0l.io) + // currently answers on TCP/8443 only, and networks that block UDP + // outbound can still bootstrap via TCP. Upstream fips no longer + // has a `tor:` transport variant — archipelago's own Tor fallback + // handles that layer. format!( "# Generated by archipelago — do not edit by hand.\n\ # Regenerated on every key change and daemon upgrade.\n\ @@ -44,9 +49,12 @@ pub fn render_config_yaml() -> String { bind_addr: \"127.0.0.1\"\n\ transports:\n \ udp:\n \ - bind_addr: \"0.0.0.0:{port}\"\n\ + bind_addr: \"0.0.0.0:{udp}\"\n \ + tcp:\n \ + bind_addr: \"0.0.0.0:{tcp}\"\n\ peers: []\n", - port = DEFAULT_UDP_PORT, + udp = DEFAULT_UDP_PORT, + tcp = DEFAULT_TCP_PORT, ) } @@ -185,7 +193,9 @@ mod tests { let yaml = render_config_yaml(); assert!(yaml.contains("persistent: true")); assert!(yaml.contains(&format!("0.0.0.0:{}", DEFAULT_UDP_PORT))); + assert!(yaml.contains(&format!("0.0.0.0:{}", DEFAULT_TCP_PORT))); assert!(yaml.contains("udp:")); + assert!(yaml.contains("tcp:")); assert!(yaml.contains("tun:")); assert!(yaml.contains("name: fips0")); // Upstream fips dropped the `tor:` transport variant; archipelago diff --git a/core/archipelago/src/fips/mod.rs b/core/archipelago/src/fips/mod.rs index a32c0d18..105aad4a 100644 --- a/core/archipelago/src/fips/mod.rs +++ b/core/archipelago/src/fips/mod.rs @@ -53,6 +53,14 @@ pub const UPSTREAM_REPO: &str = "jmcorgan/fips"; /// Default UDP port the daemon listens on. pub const DEFAULT_UDP_PORT: u16 = 8668; +/// Default TCP port the daemon listens on. Used as a fallback when a +/// peer can't be reached over UDP — common on networks that block UDP +/// (corporate/guest wifi) and the path the public fips.v0l.io anchor +/// currently accepts. Upstream factory default enables both transports +/// and archipelago intentionally matches that baseline so fresh nodes +/// can reach the broader FIPS mesh without operator config. +pub const DEFAULT_TCP_PORT: u16 = 8443; + /// Upstream systemd unit shipped by the `fips` debian package. Archipelago /// prefers its own supervision (`archipelago-fips.service`) but respects an /// already-running upstream unit so legacy/dev nodes — where no seed-derived diff --git a/core/archipelago/src/server.rs b/core/archipelago/src/server.rs index 8f9f73d4..cebf2c75 100644 --- a/core/archipelago/src/server.rs +++ b/core/archipelago/src/server.rs @@ -510,10 +510,37 @@ impl Server { tracing::warn!("FIPS key load/migrate failed: {}", e); return; } + // Check if the installed fips.yaml matches what we'd + // render now. If not, we need to restart the daemon after + // reinstalling so it picks up schema changes (e.g. the + // v1.7.25 re-addition of the TCP transport). Without this, + // OTA'd nodes would be stuck on the old UDP-only config + // until someone manually clicked Reconnect. + let expected = crate::fips::config::render_config_yaml(); + let installed = tokio::fs::read_to_string("/etc/fips/fips.yaml") + .await + .ok(); + let config_changed = installed.as_deref() != Some(expected.as_str()); + if let Err(e) = crate::fips::config::install(&identity_dir).await { tracing::warn!("FIPS config install failed on startup: {}", e); return; } + if config_changed { + tracing::info!( + "FIPS config schema changed on disk — restarting daemon to pick up new transports" + ); + // Restart whichever unit is actually supervising + // the daemon (archipelago-fips vs upstream fips). + let unit = crate::fips::service::active_unit().await; + if let Err(e) = crate::fips::service::restart(unit).await { + tracing::warn!( + "FIPS restart after config migration failed on {}: {} — user can retry via fips.reconnect", + unit, + e + ); + } + } if let Err(e) = crate::fips::service::activate(crate::fips::SERVICE_UNIT).await { tracing::warn!( "archipelago-fips activate failed on startup: {} — user can retry via fips.install RPC", diff --git a/neode-ui/src/views/server/FipsNetworkCard.vue b/neode-ui/src/views/server/FipsNetworkCard.vue index c590d382..171554a5 100644 --- a/neode-ui/src/views/server/FipsNetworkCard.vue +++ b/neode-ui/src/views/server/FipsNetworkCard.vue @@ -34,27 +34,23 @@ + doesn't crowd the card but is still one click away. Close + button and layout mirror the What's New modal (and the rest + of the app) so it feels like a first-class modal. -->
-
-
- -
- +
+
diff --git a/neode-ui/src/views/server/FipsSeedAnchorsCard.vue b/neode-ui/src/views/server/FipsSeedAnchorsCard.vue index 0941f5fb..9c218ae5 100644 --- a/neode-ui/src/views/server/FipsSeedAnchorsCard.vue +++ b/neode-ui/src/views/server/FipsSeedAnchorsCard.vue @@ -1,9 +1,23 @@