Compare commits

...

3 Commits

Author SHA1 Message Date
Dorian
5c634baa6d release(v1.7.25-alpha): TCP transport for public FIPS mesh + modal cleanup
All checks were successful
Build Archipelago ISO (dev) / build-iso (push) Successful in 34m25s
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) <noreply@anthropic.com>
2026-04-21 09:25:53 -04:00
Dorian
41474047bf release(v1.7.24-alpha): unbreak frontend pipeline — fresh UI for the first time since v1.7.17
All checks were successful
Build Archipelago ISO (dev) / build-iso (push) Successful in 12m24s
The npm run build step in the release ritual had been silently failing
for roughly seven releases. vue-tsc died with EACCES on a root-owned
node_modules/.tmp, exited non-zero, and my `tail -5` of the build
output happened to only show vite's precache summary — which makes
vite look successful even when the typecheck that precedes it failed.
The resulting archipelago-frontend-*.tar.gz files were rebuilds from
whatever content happened to live in web/dist/neode-ui/ at the moment
(files left over from v1.7.9, owned root:root from an earlier sudo'd
operation, unchanged since).

Fixed by chowning both paths back to the archipelago user and
rebuilding. Every published frontend tarball from v1.7.17 through
v1.7.23 therefore shipped the same frozen UI; v1.7.24 is the first
release in that stretch whose frontend actually matches its backend.

Recorded the build-verification rule as a persistent feedback memory
(feedback_frontend_build_verify.md) — future ships must grep the
packaged tarball for the new version string before push.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 08:53:00 -04:00
Dorian
005bbd9a9a release(v1.7.23-alpha): FIPS Seed Anchors reachable via gear icon
All checks were successful
Build Archipelago ISO (dev) / build-iso (push) Successful in 21m24s
Adds a gear button next to the FIPS Mesh card's status pill that
opens a Teleport-ed modal containing FipsSeedAnchorsCard. The card
was landed on disk in v1.7.21 but never wired into a UI entry point
per the entry-point convention, so users couldn't access the
Add/Remove/Apply controls at all. One gear click now opens them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 08:17:26 -04:00
15 changed files with 168 additions and 31 deletions

2
core/Cargo.lock generated
View File

@@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "archipelago"
version = "1.7.22-alpha"
version = "1.7.25-alpha"
dependencies = [
"anyhow",
"archipelago-container",

View File

@@ -1,6 +1,6 @@
[package]
name = "archipelago"
version = "1.7.22-alpha"
version = "1.7.25-alpha"
edition = "2021"
description = "Archipelago Bitcoin Node OS - Native backend"
authors = ["Archipelago Team"]

View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -9,15 +9,53 @@
<div class="flex-1">
<div class="flex items-start justify-between gap-4 mb-2">
<h2 class="text-xl font-semibold text-white">FIPS Mesh</h2>
<div class="flex items-center gap-2" :title="statusLabel">
<span class="w-2 h-2 rounded-full" :class="statusDotColor"></span>
<span class="text-sm font-medium" :class="statusTextColor">{{ statusLabel }}</span>
<div class="flex items-center gap-3">
<div class="flex items-center gap-2" :title="statusLabel">
<span class="w-2 h-2 rounded-full" :class="statusDotColor"></span>
<span class="text-sm font-medium" :class="statusTextColor">{{ statusLabel }}</span>
</div>
<button
type="button"
class="p-1.5 rounded-md text-white/50 hover:text-white hover:bg-white/10 transition-colors"
title="Seed anchors"
aria-label="Open FIPS seed anchors settings"
@click="showAnchorsModal = true"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</button>
</div>
</div>
<p class="text-white/70 text-sm mb-4">Fast Nostr-keyed mesh routing</p>
</div>
</div>
<!-- Seed anchors modal operator-editable list of peers this node
dials to bootstrap the mesh. Tucked behind the gear so it
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. -->
<Teleport to="body">
<Transition name="fade">
<div
v-if="showAnchorsModal"
class="fixed inset-0 z-[3000] flex items-center justify-center p-4"
@click="showAnchorsModal = false"
>
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm"></div>
<div
class="relative z-10 max-w-xl w-full"
style="max-height: 90vh; overflow-y: auto"
@click.stop
>
<FipsSeedAnchorsCard closable @close="showAnchorsModal = false" />
</div>
</div>
</Transition>
</Teleport>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-3 shrink-0">
<div class="p-3 bg-white/5 rounded-lg">
<p class="text-xs text-white/60 mb-1">Daemon version</p>
@@ -84,6 +122,7 @@
import { computed, onMounted, ref } from 'vue'
import { rpcClient } from '@/api/rpc-client'
import { safeClipboardWrite } from '@/views/web5/utils'
import FipsSeedAnchorsCard from './FipsSeedAnchorsCard.vue'
interface FipsStatus {
installed: boolean
@@ -113,6 +152,7 @@ const reconnecting = ref(false)
const statusMessage = ref('')
const statusIsError = ref(false)
const copied = ref(false)
const showAnchorsModal = ref(false)
async function copyNpub() {
if (!status.value.npub) return

View File

@@ -1,9 +1,23 @@
<template>
<div data-controller-container tabindex="0" class="glass-card p-6 flex flex-col transition-all hover:-translate-y-1">
<div class="flex items-start gap-4 mb-4 shrink-0">
<div data-controller-container tabindex="0" class="glass-card p-6 flex flex-col transition-all hover:-translate-y-1 relative">
<button
v-if="closable"
type="button"
class="absolute top-4 right-4 p-2 rounded-lg hover:bg-white/10 text-white/70 hover:text-white transition-colors z-10"
aria-label="Close"
@click="$emit('close')"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<div class="flex items-start gap-4 mb-4 shrink-0" :class="{ 'pr-10': closable }">
<div class="flex-shrink-0 w-12 h-12 rounded-lg bg-white/10 flex items-center justify-center">
<!-- Radio/broadcast icon three concentric arcs radiating from a
dot. Reads as mesh, signal, anchor-reaching-peers. -->
<svg class="w-6 h-6 text-white/80" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 11c0 3.517-1.009 6.799-2.753 9.571m-3.44-2.04.054-.09A13.916 13.916 0 0 0 8 11a4 4 0 1 1 8 0c0 1.017-.07 2.019-.203 3M9.497 10.997 14 18m-9.41-3.41L4 18.5" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.288 15.038a5.25 5.25 0 017.424 0M5.106 11.856c3.807-3.808 9.98-3.808 13.788 0M1.924 8.674c5.565-5.565 14.587-5.565 20.152 0" />
<circle cx="12" cy="18" r="1.25" fill="currentColor" stroke="none" />
</svg>
</div>
<div class="flex-1">
@@ -70,6 +84,9 @@
import { onMounted, reactive, ref } from 'vue'
import { rpcClient } from '@/api/rpc-client'
defineProps<{ closable?: boolean }>()
defineEmits<{ (e: 'close'): void }>()
interface SeedAnchor {
npub: string
address: string

View File

@@ -180,6 +180,40 @@ init()
</button>
</div>
<div class="overflow-y-auto flex-1 min-h-0 space-y-6 pr-1">
<!-- v1.7.25-alpha -->
<div>
<div class="flex items-center gap-2 mb-3">
<span class="text-xs font-mono px-2 py-0.5 rounded bg-orange-500/20 text-orange-300">v1.7.25-alpha</span>
<span class="text-xs text-white/40">Apr 21, 2026</span>
</div>
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
<p>Your node can now reach the broader FIPS public mesh, not just your own federated cluster. The FIPS daemon now binds both UDP (fast mesh forwarding) and TCP (NAT-friendly bootstrap) transports matching the upstream factory default. The public anchor currently answers on TCP, so UDP-only nodes couldn't reach it; this fixes that without any action needed on your end.</p>
<p>Upgrading the config happens automatically. On next startup, if the installed FIPS yaml doesn't match the new two-transport schema, the node reinstalls and restarts the daemon so the TCP transport comes online. No manual Reconnect required.</p>
<p>Side benefit: TCP also helps on networks that block outbound UDP (corporate, some guest wifi) your node falls back to TCP/8443 automatically and still joins the mesh.</p>
</div>
</div>
<!-- v1.7.24-alpha -->
<div>
<div class="flex items-center gap-2 mb-3">
<span class="text-xs font-mono px-2 py-0.5 rounded bg-orange-500/20 text-orange-300">v1.7.24-alpha</span>
<span class="text-xs text-white/40">Apr 21, 2026</span>
</div>
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
<p>Frontend updates now actually ship. Since roughly v1.7.17 the release pipeline had been rebuilding the backend every version but silently skipping the frontend bundle a permissions issue on the build server meant vue-tsc failed before vite ever ran, and nobody noticed because the published tarballs still extracted cleanly. The result was the backend moving forward while the UI stayed frozen at its v1.7.9-era state, which is why the FIPS gear icon and the What's New entries for every release since then had been missing on your node.</p>
<p>Once this update applies, your node gets the real v1.7.24 frontend: the FIPS Seed Anchors modal (gear icon on the FIPS Mesh card), the current What's New history, the cancel-download button, and every other UI touch from the releases in between.</p>
</div>
</div>
<!-- v1.7.23-alpha -->
<div>
<div class="flex items-center gap-2 mb-3">
<span class="text-xs font-mono px-2 py-0.5 rounded bg-orange-500/20 text-orange-300">v1.7.23-alpha</span>
<span class="text-xs text-white/40">Apr 21, 2026</span>
</div>
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
<p>FIPS Seed Anchors are now one click away. A small gear icon sits next to the status pill on the FIPS Mesh card click it to open a modal where you can add, remove, and re-apply anchors. No more needing to go digging for the card or editing JSON by hand.</p>
<p>The modal lists each anchor with its label, truncated npub, address, and transport, plus an Apply button to force-redial the full list and a Remove button per entry. The add form right below validates that the address is host:port and the npub is bech32 before saving.</p>
</div>
</div>
<!-- v1.7.22-alpha -->
<div>
<div class="flex items-center gap-2 mb-3">

View File

@@ -1,27 +1,28 @@
{
"version": "1.7.22-alpha",
"version": "1.7.25-alpha",
"release_date": "2026-04-21",
"changelog": [
"FIPS Reconnect and Restart buttons now work on every node, regardless of which systemd unit is actually supervising the daemon. Previously they targeted only the archipelago-managed unit — nodes on the upstream unit saw the buttons silently do nothing. Both paths auto-detect which unit is up and act on that one.",
"FIPS anchor status no longer shows red just because a specific public anchor is unreachable. It now lights green whenever any authenticated peer is a recognised anchor — public or one you added under Seed Anchors. Federated clusters that route through their own seed anchors finally report the truth.",
"Reconnect also re-pushes your seed anchors after the restart, so you don't have to wait five minutes for the background apply loop to re-dial them."
"Your node can now reach the broader FIPS public mesh, not just your own federated cluster. The FIPS daemon now binds both UDP (fast mesh forwarding) and TCP (NAT-friendly bootstrap) transports — matching the upstream factory default. The public anchor currently answers on TCP, so UDP-only nodes couldn't reach it; this fixes that without any action needed on your end.",
"Config upgrade is automatic. On next startup, if the installed FIPS yaml doesn't match the new two-transport schema, the node reinstalls and restarts the daemon so the TCP transport comes online. No manual Reconnect required.",
"Side benefit: TCP also helps on networks that block outbound UDP (corporate, some guest wifi) — your node falls back to TCP/8443 automatically and still joins the mesh.",
"The FIPS Seed Anchors modal got a design cleanup: new radio/mesh icon in the card header, and a matching close button styled like the rest of the app's modals instead of the earlier off-design placeholder."
],
"components": [
{
"name": "archipelago",
"current_version": "1.7.21-alpha",
"new_version": "1.7.22-alpha",
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.22-alpha/archipelago",
"sha256": "43d632c7de75b5619b20dcb59ca5e561708fe7f8d0138b3bda7accb109a61ef6",
"size_bytes": 40818136
"current_version": "1.7.24-alpha",
"new_version": "1.7.25-alpha",
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.25-alpha/archipelago",
"sha256": "912e3baa0c97672e775e256adef97cf62be4873132bcd339738c6ee44cf940a0",
"size_bytes": 40833768
},
{
"name": "archipelago-frontend-1.7.22-alpha.tar.gz",
"current_version": "1.7.21-alpha",
"new_version": "1.7.22-alpha",
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.22-alpha/archipelago-frontend-1.7.22-alpha.tar.gz",
"sha256": "402ba9af8d6bbbfc649a1cf62c4df4f44d9e48c78136dae7c29721d816e478e9",
"size_bytes": 162082789
"name": "archipelago-frontend-1.7.25-alpha.tar.gz",
"current_version": "1.7.24-alpha",
"new_version": "1.7.25-alpha",
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.25-alpha/archipelago-frontend-1.7.25-alpha.tar.gz",
"sha256": "5d8aebfc72a3eacd76d74a3bd258cbadadaaf8079c590218186013de77139eb8",
"size_bytes": 162089259
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.