Compare commits
3 Commits
v1.7.21-al
...
v1.7.24-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41474047bf | ||
|
|
005bbd9a9a | ||
|
|
d0c50bc9ce |
2
core/Cargo.lock
generated
2
core/Cargo.lock
generated
@@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "archipelago"
|
||||
version = "1.7.21-alpha"
|
||||
version = "1.7.24-alpha"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"archipelago-container",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "archipelago"
|
||||
version = "1.7.21-alpha"
|
||||
version = "1.7.24-alpha"
|
||||
edition = "2021"
|
||||
description = "Archipelago Bitcoin Node OS - Native backend"
|
||||
authors = ["Archipelago Team"]
|
||||
|
||||
@@ -12,8 +12,7 @@ use anyhow::Result;
|
||||
|
||||
impl RpcHandler {
|
||||
pub(super) async fn handle_fips_status(&self) -> Result<serde_json::Value> {
|
||||
let identity_dir = fips::identity_dir_from(&self.config.data_dir);
|
||||
let status = fips::FipsStatus::query(&identity_dir).await;
|
||||
let status = fips::FipsStatus::query(&self.config.data_dir).await;
|
||||
Ok(serde_json::to_value(status)?)
|
||||
}
|
||||
|
||||
@@ -36,13 +35,19 @@ impl RpcHandler {
|
||||
let identity_dir = fips::identity_dir_from(&self.config.data_dir);
|
||||
fips::config::install(&identity_dir).await?;
|
||||
fips::service::activate(fips::SERVICE_UNIT).await?;
|
||||
let status = fips::FipsStatus::query(&identity_dir).await;
|
||||
let status = fips::FipsStatus::query(&self.config.data_dir).await;
|
||||
Ok(serde_json::to_value(status)?)
|
||||
}
|
||||
|
||||
/// Restart whichever fips unit is supervising the daemon on this host.
|
||||
/// Nodes installed from the archipelago ISO use `archipelago-fips.service`;
|
||||
/// nodes that had the upstream debian package set up first may only have
|
||||
/// `fips.service`. We resolve the active one via `service::active_unit()`
|
||||
/// so the UI button is never a no-op.
|
||||
pub(super) async fn handle_fips_restart(&self) -> Result<serde_json::Value> {
|
||||
fips::service::restart(fips::SERVICE_UNIT).await?;
|
||||
Ok(serde_json::json!({ "restarted": true }))
|
||||
let unit = fips::service::active_unit().await;
|
||||
fips::service::restart(unit).await?;
|
||||
Ok(serde_json::json!({ "restarted": true, "unit": unit }))
|
||||
}
|
||||
|
||||
/// Full reconnect: stop the daemon, bring it back, wait for the DHT
|
||||
@@ -53,7 +58,7 @@ impl RpcHandler {
|
||||
/// Runtime: ~20s. Needs an RPC timeout ≥ 45s on the client.
|
||||
pub(super) async fn handle_fips_reconnect(&self) -> Result<serde_json::Value> {
|
||||
let identity_dir = fips::identity_dir_from(&self.config.data_dir);
|
||||
let before = fips::FipsStatus::query(&identity_dir).await;
|
||||
let before = fips::FipsStatus::query(&self.config.data_dir).await;
|
||||
|
||||
// Heal the pre-fix bech32-text fips_key.pub → 32-raw-bytes
|
||||
// mismatch. The daemon silently authenticates with a garbage
|
||||
@@ -70,12 +75,26 @@ impl RpcHandler {
|
||||
let _ = fips::config::install(&identity_dir).await;
|
||||
}
|
||||
|
||||
// Clean stop+start rather than `restart`, so a daemon that
|
||||
// fails to come back up surfaces as service_active=false
|
||||
// instead of quietly sticking with the old process.
|
||||
let _ = fips::service::stop(fips::SERVICE_UNIT).await;
|
||||
// Operate on whichever fips unit is actually up — nodes that
|
||||
// have the upstream `fips.service` rather than the
|
||||
// archipelago-managed `archipelago-fips.service` used to see
|
||||
// Reconnect silently fail because we stopped a unit that
|
||||
// didn't exist. Clean stop+start rather than `restart` so a
|
||||
// daemon that fails to come back up surfaces as
|
||||
// service_active=false instead of quietly sticking with the
|
||||
// old process.
|
||||
let unit = fips::service::active_unit().await;
|
||||
let _ = fips::service::stop(unit).await;
|
||||
tokio::time::sleep(std::time::Duration::from_millis(800)).await;
|
||||
fips::service::activate(fips::SERVICE_UNIT).await?;
|
||||
fips::service::activate(unit).await?;
|
||||
|
||||
// Re-push seed anchors after restart so freshly-bound daemons
|
||||
// don't have to wait 5 min for the periodic apply loop.
|
||||
if let Ok(list) = fips::anchors::load(&self.config.data_dir).await {
|
||||
if !list.is_empty() {
|
||||
let _ = fips::anchors::apply(&list).await;
|
||||
}
|
||||
}
|
||||
|
||||
// Anchor bootstrap window: poll the status every ~3s for up to
|
||||
// 20s. Bail as soon as the anchor is connected.
|
||||
@@ -83,7 +102,7 @@ impl RpcHandler {
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(20);
|
||||
loop {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
||||
let s = fips::FipsStatus::query(&identity_dir).await;
|
||||
let s = fips::FipsStatus::query(&self.config.data_dir).await;
|
||||
if s.anchor_connected {
|
||||
last_status = Some(s);
|
||||
break;
|
||||
@@ -111,13 +130,13 @@ impl RpcHandler {
|
||||
"peers_but_no_anchor"
|
||||
};
|
||||
let hint = match likely_cause {
|
||||
"connected" => "Anchor is reachable.",
|
||||
"daemon_down" => "The FIPS daemon didn't come back up — check archipelago-fips.service.",
|
||||
"connected" => "An anchor is reachable.",
|
||||
"daemon_down" => "The FIPS daemon didn't come back up — check the FIPS service on this host.",
|
||||
"no_seed_key" => "No seed-derived FIPS key on disk. Re-run the onboarding unlock step.",
|
||||
"no_outbound_udp_or_anchor_down" =>
|
||||
"Daemon is running but no peers handshook. Your router / ISP might be blocking outbound UDP 8668, or the anchor (fips.v0l.io) could be down.",
|
||||
"Daemon is running but no peers handshook. Your router / ISP might be blocking outbound UDP 8668, or every configured anchor could be down. Add a reachable peer in Seed Anchors.",
|
||||
"peers_but_no_anchor" =>
|
||||
"Mesh has peers but the anchor hasn't been seen yet. Give it a minute and re-check.",
|
||||
"Mesh has peers but none of them are anchors we recognise. Add your cluster's anchor in Seed Anchors.",
|
||||
_ => "",
|
||||
};
|
||||
|
||||
|
||||
@@ -99,7 +99,13 @@ pub struct FipsStatus {
|
||||
|
||||
impl FipsStatus {
|
||||
/// Snapshot the current state across package, key, and service.
|
||||
pub async fn query(identity_dir: &Path) -> Self {
|
||||
///
|
||||
/// `data_dir` is the archipelago data-dir (used to load the
|
||||
/// operator-configured seed-anchor list so "anchor_connected" means
|
||||
/// "at least one authenticated peer matches a public or configured
|
||||
/// seed anchor", not just "fips.v0l.io specifically").
|
||||
pub async fn query(data_dir: &Path) -> Self {
|
||||
let identity_dir = identity_dir_from(data_dir);
|
||||
let installed = service::package_installed().await;
|
||||
let version = if installed {
|
||||
service::daemon_version().await.ok()
|
||||
@@ -110,17 +116,24 @@ impl FipsStatus {
|
||||
let upstream_service_state = service::unit_state(UPSTREAM_SERVICE_UNIT).await;
|
||||
let service_active =
|
||||
service_state == "active" || upstream_service_state == "active";
|
||||
let key_present = crate::identity::fips_key_exists(identity_dir);
|
||||
let key_present = crate::identity::fips_key_exists(&identity_dir);
|
||||
|
||||
// Prefer the seed-derived npub; otherwise read the daemon's own
|
||||
// key file at /etc/fips/fips.pub (world-readable per debian pkg).
|
||||
let npub = match crate::identity::fips_npub(identity_dir).await {
|
||||
let npub = match crate::identity::fips_npub(&identity_dir).await {
|
||||
Ok(Some(n)) => Some(n),
|
||||
_ => service::read_upstream_npub().await.ok().flatten(),
|
||||
};
|
||||
|
||||
let (authenticated_peer_count, anchor_connected) = if service_active {
|
||||
service::peer_connectivity_summary().await
|
||||
// Build the anchor-candidate list: hardcoded public anchor
|
||||
// plus every entry in the operator's seed-anchors.json.
|
||||
// The card lights up if any of them is authenticated.
|
||||
let mut anchor_npubs = vec![service::PUBLIC_ANCHOR_NPUB.to_string()];
|
||||
if let Ok(seed) = anchors::load(data_dir).await {
|
||||
anchor_npubs.extend(seed.into_iter().map(|a| a.npub));
|
||||
}
|
||||
service::peer_connectivity_summary(&anchor_npubs).await
|
||||
} else {
|
||||
(0, false)
|
||||
};
|
||||
@@ -153,10 +166,11 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_status_reports_no_key_pre_onboarding() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let id_dir = dir.path().join("identity");
|
||||
tokio::fs::create_dir_all(&id_dir).await.unwrap();
|
||||
|
||||
let status = FipsStatus::query(&id_dir).await;
|
||||
// query() now takes a data_dir (parent) rather than identity_dir,
|
||||
// since it also reads seed-anchors.json for the anchor check.
|
||||
// No identity/ subdir → no key; no seed-anchors.json → public
|
||||
// anchor is the only candidate.
|
||||
let status = FipsStatus::query(dir.path()).await;
|
||||
assert!(!status.key_present, "no key before onboarding");
|
||||
assert!(status.npub.is_none());
|
||||
// `installed`, `service_state`, `version` depend on the host and are
|
||||
|
||||
@@ -97,6 +97,27 @@ pub async fn restart(unit: &str) -> Result<()> {
|
||||
sudo_systemctl("restart", unit).await
|
||||
}
|
||||
|
||||
/// Resolve which systemd unit is actually supervising the fips daemon
|
||||
/// on this host. Nodes installed from the archipelago ISO run
|
||||
/// `archipelago-fips.service`; nodes that were apt-installed (or had
|
||||
/// fips running before archipelago took over) may only have the
|
||||
/// upstream `fips.service`. Restart/Reconnect must operate on whichever
|
||||
/// one is running, otherwise the UI button is a silent no-op.
|
||||
///
|
||||
/// Returns the archipelago-managed unit name if it's active,
|
||||
/// else the upstream unit name if that's active,
|
||||
/// else the archipelago-managed name as a default (so activate() can
|
||||
/// bring it up).
|
||||
pub async fn active_unit() -> &'static str {
|
||||
if unit_state(super::SERVICE_UNIT).await == "active" {
|
||||
return super::SERVICE_UNIT;
|
||||
}
|
||||
if unit_state(super::UPSTREAM_SERVICE_UNIT).await == "active" {
|
||||
return super::UPSTREAM_SERVICE_UNIT;
|
||||
}
|
||||
super::SERVICE_UNIT
|
||||
}
|
||||
|
||||
pub async fn mask(unit: &str) -> Result<()> {
|
||||
let _ = sudo_systemctl("stop", unit).await;
|
||||
let _ = sudo_systemctl("disable", unit).await;
|
||||
@@ -108,12 +129,19 @@ pub async fn mask(unit: &str) -> Result<()> {
|
||||
pub const PUBLIC_ANCHOR_NPUB: &str =
|
||||
"npub1zv58cn7v83mxvttl70w5fwjwuclfmntv9cnmv5wmz2nzz88u5urqvdx96n";
|
||||
|
||||
/// Summarise peer connectivity from `fipsctl show peers` + `identity-cache`.
|
||||
/// Returns `(authenticated_peer_count, anchor_connected)`. Shells out rather
|
||||
/// than embedding a fips client because fipsctl is the daemon's own ground
|
||||
/// truth — the daemon can always rewrite its internal routing and we'd
|
||||
/// rather be consistent with `fipsctl` than snapshot it ourselves.
|
||||
pub async fn peer_connectivity_summary() -> (u32, bool) {
|
||||
/// Summarise peer connectivity from `fipsctl show peers`. Returns
|
||||
/// `(authenticated_peer_count, anchor_connected)`.
|
||||
///
|
||||
/// `anchor_candidates` is the operator-controlled list of npubs this
|
||||
/// node considers a valid mesh anchor — always includes the hard-coded
|
||||
/// public anchor, plus any entries from `seed-anchors.json`. A node is
|
||||
/// "anchor connected" when at least one currently-authenticated peer
|
||||
/// matches one of these npubs. We used to check the identity cache
|
||||
/// (which includes transient hearsay from other peers), but a cache
|
||||
/// hit on `fips.v0l.io` didn't mean we could actually route through
|
||||
/// it, and the card lied to users whose mesh was federated through
|
||||
/// their own seed anchors instead.
|
||||
pub async fn peer_connectivity_summary(anchor_candidates: &[String]) -> (u32, bool) {
|
||||
let peers_json = match Command::new("sudo")
|
||||
.args(["-n", "fipsctl", "show", "peers"])
|
||||
.output()
|
||||
@@ -122,39 +150,26 @@ pub async fn peer_connectivity_summary() -> (u32, bool) {
|
||||
Ok(o) if o.status.success() => o.stdout,
|
||||
_ => return (0, false),
|
||||
};
|
||||
let authenticated_peer_count =
|
||||
match serde_json::from_slice::<serde_json::Value>(&peers_json) {
|
||||
Ok(v) => v
|
||||
.get("peers")
|
||||
.and_then(|p| p.as_array())
|
||||
.map(|a| a.len() as u32)
|
||||
.unwrap_or(0),
|
||||
Err(_) => 0,
|
||||
let parsed: serde_json::Value =
|
||||
match serde_json::from_slice(&peers_json) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return (0, false),
|
||||
};
|
||||
|
||||
// Anchor check: look in identity-cache (known node pubkeys the daemon
|
||||
// has heard about) rather than authenticated peers — the anchor may be
|
||||
// in the cache but not currently at session depth.
|
||||
let cache_json = match Command::new("sudo")
|
||||
.args(["-n", "fipsctl", "show", "identity-cache"])
|
||||
.output()
|
||||
.await
|
||||
{
|
||||
Ok(o) if o.status.success() => o.stdout,
|
||||
_ => return (authenticated_peer_count, false),
|
||||
};
|
||||
let anchor_connected = match serde_json::from_slice::<serde_json::Value>(&cache_json) {
|
||||
Ok(v) => v
|
||||
.get("entries")
|
||||
.and_then(|e| e.as_array())
|
||||
.map(|entries| {
|
||||
entries
|
||||
.iter()
|
||||
.any(|e| e.get("npub").and_then(|n| n.as_str()) == Some(PUBLIC_ANCHOR_NPUB))
|
||||
})
|
||||
.unwrap_or(false),
|
||||
Err(_) => false,
|
||||
};
|
||||
let peers = parsed
|
||||
.get("peers")
|
||||
.and_then(|p| p.as_array())
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let authenticated_peer_count = peers.len() as u32;
|
||||
let anchor_connected = peers.iter().any(|p| {
|
||||
let npub = p.get("npub").and_then(|n| n.as_str()).unwrap_or_default();
|
||||
let connected = p
|
||||
.get("connectivity")
|
||||
.and_then(|c| c.as_str())
|
||||
.map(|s| s == "connected")
|
||||
.unwrap_or(true);
|
||||
connected && anchor_candidates.iter().any(|a| a == npub)
|
||||
});
|
||||
(authenticated_peer_count, anchor_connected)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,15 +9,57 @@
|
||||
<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. -->
|
||||
<Teleport to="body">
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="showAnchorsModal"
|
||||
class="fixed inset-0 z-[3000] flex items-center justify-center p-4"
|
||||
@click.self="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">
|
||||
<div class="flex justify-end mb-2">
|
||||
<button
|
||||
type="button"
|
||||
class="p-2 rounded-md bg-white/10 hover:bg-white/20 text-white/70 hover:text-white transition-colors"
|
||||
aria-label="Close"
|
||||
@click="showAnchorsModal = false"
|
||||
>
|
||||
<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>
|
||||
<FipsSeedAnchorsCard />
|
||||
</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>
|
||||
@@ -50,7 +92,7 @@
|
||||
<div class="flex items-center justify-between gap-3 flex-wrap">
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<span class="w-2 h-2 rounded-full" :class="status.anchor_connected ? 'bg-cyan-400' : 'bg-orange-400'"></span>
|
||||
<span class="text-white/70">Anchor (fips.v0l.io):</span>
|
||||
<span class="text-white/70">Anchor:</span>
|
||||
<span :class="status.anchor_connected ? 'text-cyan-300' : 'text-orange-300'">
|
||||
{{ status.anchor_connected ? 'connected' : 'not reached' }}
|
||||
</span>
|
||||
@@ -68,7 +110,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<p v-if="!status.anchor_connected" class="mt-2 text-[11px] text-white/40 leading-snug">
|
||||
Without the anchor, DHT routing to unknown npubs can't bootstrap; federation and messaging fall back to Tor until it reconnects. Reconnect restarts the FIPS daemon, which usually clears a stale identity cache.
|
||||
No known anchor is currently an authenticated peer. DHT routing to unknown npubs can't bootstrap; federation and messaging fall back to Tor until one reconnects. Reconnect restarts the FIPS daemon, which usually clears a stale identity cache. Add a cluster-local anchor in Seed Anchors if the public one is unreachable.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -84,6 +126,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 +156,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
|
||||
|
||||
@@ -180,6 +180,40 @@ init()
|
||||
</button>
|
||||
</div>
|
||||
<div class="overflow-y-auto flex-1 min-h-0 space-y-6 pr-1">
|
||||
<!-- 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">
|
||||
<span class="text-xs font-mono px-2 py-0.5 rounded bg-orange-500/20 text-orange-300">v1.7.22-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>The 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 that were running the upstream unit instead saw the buttons silently do nothing. Both paths now auto-detect which unit is up and act on that one.</p>
|
||||
<p>The FIPS anchor status no longer shows red just because one specific public anchor is unreachable. It now lights green whenever any authenticated peer is a recognised anchor — that's either the public anchor or something you added under Seed Anchors. A federated cluster that routes through its own seed anchor finally reports the truth.</p>
|
||||
<p>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.</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- v1.7.21-alpha -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
{
|
||||
"version": "1.7.21-alpha",
|
||||
"version": "1.7.24-alpha",
|
||||
"release_date": "2026-04-21",
|
||||
"changelog": [
|
||||
"FIPS bootstrap no longer depends on a single public anchor. You can add your own anchors — other archipelago nodes or a VPS you control — and the node dials every one on startup to join the mesh. If one anchor is down, the next seeds the routing layer instead, so a flaky public anchor no longer strands a fresh install.",
|
||||
"Anchors persist across restarts and are re-applied every five minutes, so a daemon that got temporarily isolated reconnects on its own without anyone having to SSH in. Each anchor carries an operator-editable label.",
|
||||
"No behavior change if you don't configure any — the upstream daemon's defaults keep working as before. This purely adds an operator-controlled list on top."
|
||||
"Frontend updates actually ship again. 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 the TypeScript compile failed before vite ever ran, so every published tarball carried the same frozen v1.7.9-era UI. The backend moved forward; the UI didn't.",
|
||||
"Once this lands, your node gets the real current frontend: the FIPS Seed Anchors modal (gear icon on the FIPS Mesh card), the cancel-download button, the anchor-status fix, and every What's New entry for releases in between.",
|
||||
"The build pipeline now grep-verifies the packaged tarball actually contains the new version string before any commit or push, so a silently-stale bundle can't slip through again."
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"name": "archipelago",
|
||||
"current_version": "1.7.20-alpha",
|
||||
"new_version": "1.7.21-alpha",
|
||||
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.21-alpha/archipelago",
|
||||
"sha256": "47e5ddff3a2eeb3ff7117bfccb1799a72932e77afefb4f03b17679c21858f21c",
|
||||
"size_bytes": 40799840
|
||||
"current_version": "1.7.23-alpha",
|
||||
"new_version": "1.7.24-alpha",
|
||||
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.24-alpha/archipelago",
|
||||
"sha256": "a90428a6486d90c34e7e3dd9e1ac6d3dee171855f4cdae9680400e2b7dab200a",
|
||||
"size_bytes": 40817488
|
||||
},
|
||||
{
|
||||
"name": "archipelago-frontend-1.7.21-alpha.tar.gz",
|
||||
"current_version": "1.7.20-alpha",
|
||||
"new_version": "1.7.21-alpha",
|
||||
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.21-alpha/archipelago-frontend-1.7.21-alpha.tar.gz",
|
||||
"sha256": "f8fd8f7d07f99fd227c6d3f3a188154b51e20e19b0f2f303175ea46095ae30d9",
|
||||
"size_bytes": 162082809
|
||||
"name": "archipelago-frontend-1.7.24-alpha.tar.gz",
|
||||
"current_version": "1.7.23-alpha",
|
||||
"new_version": "1.7.24-alpha",
|
||||
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.24-alpha/archipelago-frontend-1.7.24-alpha.tar.gz",
|
||||
"sha256": "60cd9cc391faffe11b8b07982a416051977913831703474d2a433bd4fb81d5e9",
|
||||
"size_bytes": 162085926
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
BIN
releases/v1.7.22-alpha/archipelago
Executable file
BIN
releases/v1.7.22-alpha/archipelago
Executable file
Binary file not shown.
BIN
releases/v1.7.22-alpha/archipelago-frontend-1.7.22-alpha.tar.gz
Normal file
BIN
releases/v1.7.22-alpha/archipelago-frontend-1.7.22-alpha.tar.gz
Normal file
Binary file not shown.
BIN
releases/v1.7.23-alpha/archipelago
Executable file
BIN
releases/v1.7.23-alpha/archipelago
Executable file
Binary file not shown.
BIN
releases/v1.7.23-alpha/archipelago-frontend-1.7.23-alpha.tar.gz
Normal file
BIN
releases/v1.7.23-alpha/archipelago-frontend-1.7.23-alpha.tar.gz
Normal file
Binary file not shown.
BIN
releases/v1.7.24-alpha/archipelago
Executable file
BIN
releases/v1.7.24-alpha/archipelago
Executable file
Binary file not shown.
BIN
releases/v1.7.24-alpha/archipelago-frontend-1.7.24-alpha.tar.gz
Normal file
BIN
releases/v1.7.24-alpha/archipelago-frontend-1.7.24-alpha.tar.gz
Normal file
Binary file not shown.
Reference in New Issue
Block a user