Compare commits
18
Commits
@@ -1,5 +1,22 @@
|
||||
# Changelog
|
||||
|
||||
## v1.7.108-alpha (2026-07-20)
|
||||
|
||||
- Your node connects to the private mesh far more reliably. Nodes rely on a public rendezvous point to find each other, and the only one available was unreachable from many home and office networks — leaving some nodes unable to join the mesh at all. There is now a second, always-reachable rendezvous point, and your node tries every one it knows, so it joins the mesh in seconds instead of being stranded.
|
||||
- Wi-Fi setup now heals itself on older nodes. Some nodes set up before a mid-year fix couldn't connect to a Wi-Fi network from the screen — it failed with a permissions error — because the piece that lets the node manage networking on your behalf was missing. Nodes now put that piece in place automatically on startup, so "scan, pick a network, type the password, connect" works without reinstalling.
|
||||
- Your node rejoins the mesh within seconds after an update. Applying an update briefly restarts the mesh service, and previously a node could sit disconnected from other nodes for up to five minutes before it retried.
|
||||
- The TV screen now fits your television. On a large or 4K TV the interface rendered tiny with no way to zoom on a keyboard-less screen; it now sizes itself to a comfortable, readable scale automatically (and small laptop panels are left unchanged).
|
||||
- More TV-screen polish: the built-in assistant shows its dark theme instead of bright white panels, the on-screen hint for switching between the kiosk and a terminal now points at the right keys, the welcome logo no longer occasionally renders as garbled characters, and an accidental tap of the power button no longer shuts the node down — hold it to power off on purpose.
|
||||
- Behind the scenes: fixed the installer image build so it no longer stops on a component that was removed from the product, and so it correctly includes the private relay it was meant to bundle.
|
||||
|
||||
## v1.7.106-alpha (2026-07-20)
|
||||
|
||||
- Nodes on the same network now find each other directly. Your node announces itself on your local network and connects straight to other Archipelago nodes nearby, instead of every connection having to be introduced by a public rendezvous server out on the internet. Peers in the same home or office stay connected to each other even when that server is unreachable, and they reach each other faster.
|
||||
- On a phone, the peer files screen tells you how you're connected again. The badge showing whether a peer's files are arriving over the fast mesh or over Tor was only visible on desktop — on narrow screens it disappeared entirely. It now appears next to the peer name on mobile too.
|
||||
- Your node's mesh settings can no longer be written in a way that breaks the mesh. The configuration file used to be assembled as free-form text, where one wrong setting would stop the mesh service from starting and quietly drop your node off the network. It's now generated from a checked description of the file, with tests that verify the exact output.
|
||||
- When your node has trouble reaching another node, the logs now record the real reason instead of a generic summary. A failure to open a peer's files previously logged only "Failed to connect to peer" and threw away the actual cause, which made these problems very hard to diagnose. Nothing changes on screen, and no internal detail is exposed.
|
||||
- Behind the scenes: the installer image now builds its mesh component at a fixed, known version instead of whatever upstream had published that day, so two images built from the same source are identical.
|
||||
|
||||
## v1.7.105-alpha (2026-07-20)
|
||||
|
||||
- Fixed a failure loop where a node that lost power or was moved could get stuck on a blank "can't reach your node" screen forever: startup recovery no longer spends minutes retrying containers that no longer exist, and a genuinely large recovery is no longer cut off half-way and forced to start over. The node now reaches its login screen even after the messiest shutdown.
|
||||
|
||||
Generated
+1
-1
@@ -95,7 +95,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "archipelago"
|
||||
version = "1.7.104-alpha"
|
||||
version = "1.7.108-alpha"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"archipelago-container",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "archipelago"
|
||||
version = "1.7.105-alpha"
|
||||
version = "1.7.108-alpha"
|
||||
edition = "2021"
|
||||
description = "Archipelago Bitcoin Node OS - Native backend"
|
||||
authors = ["Archipelago Team"]
|
||||
|
||||
@@ -438,7 +438,13 @@ impl RpcHandler {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("RPC error on {}: {}", rpc_req.method, e);
|
||||
// `{:#}` renders the whole anyhow context chain. Logging only the
|
||||
// outermost context threw away the actual cause: a peer-files
|
||||
// failure logged just "Failed to connect to peer", with the real
|
||||
// error (Tor SOCKS failure, FIPS resolve, timeout) discarded — so
|
||||
// the logs couldn't distinguish a dead peer from a slow circuit.
|
||||
// The client-facing message below stays `{}` so internals aren't leaked.
|
||||
error!("RPC error on {}: {:#}", rpc_req.method, e);
|
||||
let user_message = sanitize_error_message(&e.to_string());
|
||||
RpcResponse {
|
||||
result: None,
|
||||
|
||||
@@ -137,6 +137,13 @@ pub async fn ensure_doctor_installed() {
|
||||
Ok(false) => debug!("/opt/archipelago/apps already populated (or no installer copy)"),
|
||||
Err(e) => warn!("Apps dir repair failed (non-fatal): {:#}", e),
|
||||
}
|
||||
match run_polkit_networkmanager_repair().await {
|
||||
Ok(true) => info!(
|
||||
"Installed NetworkManager polkit rule for the archipelago user — Wi-Fi setup enabled"
|
||||
),
|
||||
Ok(false) => debug!("NetworkManager polkit rule already present"),
|
||||
Err(e) => warn!("polkit NetworkManager repair failed (non-fatal): {:#}", e),
|
||||
}
|
||||
match run_journald_dropin().await {
|
||||
Ok(true) => info!("Installed journald log-volume policy drop-in"),
|
||||
Ok(false) => debug!("journald log-volume policy already in place"),
|
||||
@@ -442,6 +449,68 @@ exit 2
|
||||
}
|
||||
}
|
||||
|
||||
/// Self-heal Wi-Fi setup on nodes that predate the polkit fix (issue #99).
|
||||
///
|
||||
/// Archipelago drives NetworkManager from a system-level systemd service
|
||||
/// (`User=archipelago`, no logind seat), so the stock NM polkit rule — which
|
||||
/// only authorizes `subject.local && subject.active` sessions — denies it, and
|
||||
/// "connect to Wi-Fi" fails with "Insufficient privileges". Fresh ISO installs
|
||||
/// since 2026-05 ship the rule below, but nodes that reached this build over
|
||||
/// OTA never got it (OTA replaces the binary + web UI, not host system config).
|
||||
///
|
||||
/// Install the scoped rule if it is missing, and best-effort ensure `polkitd`
|
||||
/// itself is present (without the daemon the rule is inert). Both are wrapped
|
||||
/// so an offline/locked apt or a missing package can never fail startup — the
|
||||
/// rule is still written so it takes effect once polkitd arrives (e.g. after an
|
||||
/// ISO reflash). Idempotent: keyed on the rule's unique `subject.user` marker.
|
||||
async fn run_polkit_networkmanager_repair() -> Result<bool> {
|
||||
let script = r#"
|
||||
set -u
|
||||
RULE=/etc/polkit-1/rules.d/49-archipelago-networkmanager.rules
|
||||
MARKER='subject.user == "archipelago"'
|
||||
# Rule already installed — nothing to do.
|
||||
if grep -qF "$MARKER" "$RULE" 2>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
# The rule is inert without the polkit daemon. Older nodes (the ones that hit
|
||||
# issue #99) shipped without it. Try to install it, but never let apt failure
|
||||
# (offline node, locked dpkg, package unavailable) abort the heal — the rule is
|
||||
# written regardless so it activates whenever polkitd lands.
|
||||
if [ ! -d /usr/share/polkit-1 ] && ! command -v pkaction >/dev/null 2>&1; then
|
||||
timeout 240 apt-get install -y --no-install-recommends polkitd >/dev/null 2>&1 \
|
||||
|| timeout 240 sh -c 'apt-get update >/dev/null 2>&1 && apt-get install -y --no-install-recommends polkitd >/dev/null 2>&1' \
|
||||
|| true
|
||||
fi
|
||||
mkdir -p /etc/polkit-1/rules.d
|
||||
cat > "$RULE" <<'RULEEOF'
|
||||
polkit.addRule(function(action, subject) {
|
||||
if (subject.user == "archipelago" && action.id.indexOf("org.freedesktop.NetworkManager.") == 0) {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
});
|
||||
RULEEOF
|
||||
chmod 644 "$RULE"
|
||||
# Pick up the new rule. polkitd re-reads rules.d on reload; restart as a
|
||||
# fallback. Non-fatal if the unit name differs or the daemon is absent.
|
||||
systemctl reload polkit 2>/dev/null \
|
||||
|| systemctl restart polkit 2>/dev/null \
|
||||
|| systemctl restart polkit.service 2>/dev/null \
|
||||
|| true
|
||||
exit 2
|
||||
"#;
|
||||
let status = host_sudo(&["sh", "-lc", script])
|
||||
.await
|
||||
.context("install NetworkManager polkit rule")?;
|
||||
match status.code() {
|
||||
Some(0) => Ok(false),
|
||||
Some(2) => Ok(true),
|
||||
_ => {
|
||||
warn!("polkit NetworkManager repair helper exited with {}", status);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_bitcoin_rpc_repair() -> Result<bool> {
|
||||
// Older installs can have a container-owned bitcoin.conf with only rpcauth
|
||||
// and printtoconsole. Repair it at startup so OTA fixes existing nodes
|
||||
|
||||
@@ -49,9 +49,7 @@ pub const DEFAULT_PUBLIC_ANCHOR_NPUB: &str =
|
||||
pub const DEFAULT_PUBLIC_ANCHOR_ADDR: &str = "185.18.221.160:8443";
|
||||
pub const DEFAULT_PUBLIC_ANCHOR_TRANSPORT: &str = "tcp";
|
||||
|
||||
/// The default public anchor as a ready-to-apply `SeedAnchor`. Carried
|
||||
/// implicitly by `load()` on nodes that have never edited their anchor
|
||||
/// list, so every node dials it without operator action.
|
||||
/// The upstream public anchor as a ready-to-apply `SeedAnchor`.
|
||||
pub fn default_public_anchor() -> SeedAnchor {
|
||||
SeedAnchor {
|
||||
npub: DEFAULT_PUBLIC_ANCHOR_NPUB.to_string(),
|
||||
@@ -61,6 +59,38 @@ pub fn default_public_anchor() -> SeedAnchor {
|
||||
}
|
||||
}
|
||||
|
||||
// Archipelago-operated anchor on vps2 (the OTA/registry host, 146.59.87.168).
|
||||
// Every node already reaches this host for updates, so it is reachable from
|
||||
// networks that the upstream anchor is not — which is most of them (the
|
||||
// upstream anchor answers on one IPv4 that many home/office networks can't
|
||||
// reach, and its DNS resolves IPv6-first while the daemon is IPv4-only).
|
||||
// TCP because that traverses NAT/firewalls best; 8444 because 8443 on that
|
||||
// host is already taken by a container.
|
||||
pub const ARCHY_ANCHOR_NPUB: &str =
|
||||
"npub1dptaktwxv0mm245g2lqjykwm5ll0jpc6m3r4242ydfa9z7qe6urs3jvrak";
|
||||
pub const ARCHY_ANCHOR_ADDR: &str = "146.59.87.168:8444";
|
||||
pub const ARCHY_ANCHOR_TRANSPORT: &str = "tcp";
|
||||
|
||||
/// The Archipelago-operated anchor as a ready-to-apply `SeedAnchor`.
|
||||
pub fn archy_anchor() -> SeedAnchor {
|
||||
SeedAnchor {
|
||||
npub: ARCHY_ANCHOR_NPUB.to_string(),
|
||||
address: ARCHY_ANCHOR_ADDR.to_string(),
|
||||
transport: ARCHY_ANCHOR_TRANSPORT.to_string(),
|
||||
label: "Archipelago anchor (vps2)".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The default anchor set carried implicitly by `load()` on nodes that have
|
||||
/// never edited their anchor list, so every node dials them without operator
|
||||
/// action. Multiple anchors so one unreachable rendezvous host can't strand a
|
||||
/// node: `fipsctl connect` is attempted for each, and whichever the node's
|
||||
/// network can reach wins. The Archipelago-operated anchor is listed first
|
||||
/// because it is reachable from the widest set of networks.
|
||||
pub fn default_public_anchors() -> Vec<SeedAnchor> {
|
||||
vec![archy_anchor(), default_public_anchor()]
|
||||
}
|
||||
|
||||
/// One seed-anchor entry. `address` must be directly dialable (IP or
|
||||
/// resolvable hostname + UDP port); `transport` is one of "udp", "tcp",
|
||||
/// "tor", "ethernet" (the values upstream `fipsctl connect` accepts).
|
||||
@@ -94,7 +124,7 @@ fn anchors_path(data_dir: &Path) -> PathBuf {
|
||||
pub async fn load(data_dir: &Path) -> Result<Vec<SeedAnchor>> {
|
||||
let path = anchors_path(data_dir);
|
||||
if !path.exists() {
|
||||
return Ok(vec![default_public_anchor()]);
|
||||
return Ok(default_public_anchors());
|
||||
}
|
||||
let bytes = tokio::fs::read(&path)
|
||||
.await
|
||||
@@ -268,28 +298,46 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn load_missing_seeds_default_public_anchor() {
|
||||
// A node that has never edited its anchor list should still get
|
||||
// the public anchor so it can bootstrap the mesh out of the box.
|
||||
async fn load_missing_seeds_default_public_anchors() {
|
||||
// A node that has never edited its anchor list should still get the
|
||||
// full default anchor set so it can bootstrap the mesh out of the box.
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let got = load(dir.path()).await.unwrap();
|
||||
assert_eq!(got, vec![default_public_anchor()]);
|
||||
// ...and the default must be the TCP/8443 form, not the dead udp:8668.
|
||||
assert_eq!(got[0].transport, "tcp");
|
||||
assert!(got[0].address.ends_with(":8443"));
|
||||
assert_eq!(got, default_public_anchors());
|
||||
// The Archipelago-operated anchor must come first (widest reachability)
|
||||
// and the upstream anchor must remain present as a fallback.
|
||||
assert_eq!(got[0], archy_anchor());
|
||||
assert!(got.contains(&default_public_anchor()));
|
||||
// Every default must be a TCP form (traverses NAT/firewalls), never the
|
||||
// dead udp:8668 the upstream anchor never answers on.
|
||||
assert!(got.iter().all(|a| a.transport == "tcp"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn removing_default_persists_as_empty() {
|
||||
// Once the operator removes the default, a file exists and is
|
||||
// authoritative — we must not silently re-seed it on next load.
|
||||
async fn removing_one_default_persists_and_keeps_the_other() {
|
||||
// Editing the anchor list (here removing one default) makes the file
|
||||
// authoritative: the removed anchor must not be silently re-seeded on
|
||||
// next load, and the remaining default must stay.
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let list = remove(dir.path(), ARCHY_ANCHOR_NPUB).await.unwrap();
|
||||
assert!(!list.iter().any(|a| a.npub == ARCHY_ANCHOR_NPUB));
|
||||
assert!(list.contains(&default_public_anchor()));
|
||||
let got = load(dir.path()).await.unwrap();
|
||||
assert_eq!(got, list, "edited list is authoritative; no re-seed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn removing_all_defaults_persists_as_empty() {
|
||||
// Removing every default leaves an empty authoritative list that must
|
||||
// not be re-seeded on next load.
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
remove(dir.path(), ARCHY_ANCHOR_NPUB).await.unwrap();
|
||||
let list = remove(dir.path(), DEFAULT_PUBLIC_ANCHOR_NPUB)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(list.is_empty());
|
||||
let got = load(dir.path()).await.unwrap();
|
||||
assert!(got.is_empty(), "default must stay removed once edited");
|
||||
assert!(got.is_empty(), "defaults must stay removed once edited");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
//! whitelists `install` into `/etc/fips/`.
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::Serialize;
|
||||
use std::path::Path;
|
||||
use tokio::process::Command;
|
||||
|
||||
@@ -17,47 +18,145 @@ 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
|
||||
/// Header prepended to the generated YAML. serde doesn't emit comments, so
|
||||
/// this is concatenated onto the serialised body.
|
||||
const CONFIG_HEADER: &str = "# Generated by archipelago — do not edit by hand.\n\
|
||||
# Regenerated on every key change and daemon upgrade.\n";
|
||||
|
||||
/// Typed mirror of the subset of upstream `fips.yaml` that archipelago owns.
|
||||
///
|
||||
/// This was previously built by `format!`-ing a string literal. Upstream's
|
||||
/// config structs are `#[serde(deny_unknown_fields)]`, so a key we get wrong
|
||||
/// doesn't degrade gracefully — the daemon refuses to start and the node drops
|
||||
/// off the mesh. Serialising from typed structs lets the compiler and the
|
||||
/// tests below catch drift, instead of a node discovering it at boot after an
|
||||
/// upgrade.
|
||||
///
|
||||
/// Schema verified field-by-field against jmcorgan/fips **v0.4.1** (2026-07-20).
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub struct FipsConfig {
|
||||
pub node: NodeSection,
|
||||
pub tun: TunSection,
|
||||
pub dns: DnsSection,
|
||||
pub transports: TransportsSection,
|
||||
/// Static peers. Always empty: archipelago feeds peers dynamically via the
|
||||
/// seed-anchors apply loop and federation-invite hooks.
|
||||
pub peers: Vec<PeerEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub struct NodeSection {
|
||||
pub identity: IdentitySection,
|
||||
pub discovery: DiscoverySection,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub struct IdentitySection {
|
||||
/// With `persistent: true` the daemon reuses the key file at
|
||||
/// config-dir/fips.key (= `DAEMON_KEY_PATH`) instead of generating an
|
||||
/// ephemeral identity on every start.
|
||||
pub persistent: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub struct DiscoverySection {
|
||||
pub lan: LanDiscoverySection,
|
||||
}
|
||||
|
||||
/// mDNS / DNS-SD discovery on the local link (`node.discovery.lan.*`), added
|
||||
/// upstream in v0.4.0 and opt-in there (upstream default is `false`).
|
||||
///
|
||||
/// We enable it so co-located nodes peer directly instead of depending on the
|
||||
/// public anchor being reachable — an anchor blackhole on one network segment
|
||||
/// otherwise islands a node completely.
|
||||
///
|
||||
/// Emitted unconditionally rather than version-gated: v0.3.0's `DiscoveryConfig`
|
||||
/// has no `lan` field *and* no `deny_unknown_fields`, so a v0.3.0 daemon ignores
|
||||
/// this key harmlessly (verified against the v0.3.0 source). It therefore starts
|
||||
/// working on its own when a node upgrades, with no second config migration.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub struct LanDiscoverySection {
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub struct TunSection {
|
||||
pub enabled: bool,
|
||||
pub name: String,
|
||||
pub mtu: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub struct DnsSection {
|
||||
pub enabled: bool,
|
||||
pub bind_addr: String,
|
||||
}
|
||||
|
||||
/// Both UDP and TCP are enabled: the public anchor answers on TCP/8443 only,
|
||||
/// and networks that block outbound UDP can still bootstrap over TCP.
|
||||
/// Upstream dropped the `tor:` transport variant — archipelago's own Tor
|
||||
/// fallback handles that layer.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub struct TransportsSection {
|
||||
pub udp: TransportBind,
|
||||
pub tcp: TransportBind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub struct TransportBind {
|
||||
/// Upstream takes `bind_addr` ("host:port"), not `enabled` + `port`.
|
||||
pub bind_addr: String,
|
||||
}
|
||||
|
||||
/// A static peer entry. Never constructed today (see `FipsConfig::peers`), but
|
||||
/// typed so the shape is checked if static peering is ever needed.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
pub struct PeerEntry {
|
||||
pub npub: String,
|
||||
pub address: String,
|
||||
pub transport: String,
|
||||
}
|
||||
|
||||
impl Default for FipsConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
node: NodeSection {
|
||||
identity: IdentitySection { persistent: true },
|
||||
discovery: DiscoverySection {
|
||||
lan: LanDiscoverySection { enabled: true },
|
||||
},
|
||||
},
|
||||
tun: TunSection {
|
||||
enabled: true,
|
||||
name: "fips0".to_string(),
|
||||
mtu: 1280,
|
||||
},
|
||||
dns: DnsSection {
|
||||
enabled: true,
|
||||
bind_addr: "127.0.0.1".to_string(),
|
||||
},
|
||||
transports: TransportsSection {
|
||||
udp: TransportBind {
|
||||
bind_addr: format!("0.0.0.0:{DEFAULT_UDP_PORT}"),
|
||||
},
|
||||
tcp: TransportBind {
|
||||
bind_addr: format!("0.0.0.0:{DEFAULT_TCP_PORT}"),
|
||||
},
|
||||
},
|
||||
peers: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Render the FIPS daemon config. Overwrites any existing file — callers
|
||||
/// 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 + 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.
|
||||
/// Node identity comes from the key file on disk; the static peer list stays
|
||||
/// empty because peers are fed dynamically at runtime.
|
||||
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`. 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\
|
||||
node:\n \
|
||||
identity:\n \
|
||||
persistent: true\n\
|
||||
tun:\n \
|
||||
enabled: true\n \
|
||||
name: fips0\n \
|
||||
mtu: 1280\n\
|
||||
dns:\n \
|
||||
enabled: true\n \
|
||||
bind_addr: \"127.0.0.1\"\n\
|
||||
transports:\n \
|
||||
udp:\n \
|
||||
bind_addr: \"0.0.0.0:{udp}\"\n \
|
||||
tcp:\n \
|
||||
bind_addr: \"0.0.0.0:{tcp}\"\n\
|
||||
peers: []\n",
|
||||
udp = DEFAULT_UDP_PORT,
|
||||
tcp = DEFAULT_TCP_PORT,
|
||||
)
|
||||
let body = serde_yaml::to_string(&FipsConfig::default())
|
||||
.expect("FipsConfig is a plain struct tree and cannot fail to serialise");
|
||||
format!("{CONFIG_HEADER}{body}")
|
||||
}
|
||||
|
||||
/// Install the local FIPS key + rendered config into `/etc/fips/`.
|
||||
@@ -205,6 +304,60 @@ mod tests {
|
||||
assert!(!yaml.contains("tor:"));
|
||||
}
|
||||
|
||||
/// Exact-output snapshot. Upstream's config structs are
|
||||
/// `deny_unknown_fields`, so an accidental key rename/addition means the
|
||||
/// daemon won't start. Pinning the full rendering makes any such change
|
||||
/// fail here — where it's cheap — instead of on a node after an upgrade.
|
||||
/// If this fails, re-verify against the upstream schema before updating it.
|
||||
#[test]
|
||||
fn test_rendered_yaml_exact_snapshot() {
|
||||
let expected = "\
|
||||
# Generated by archipelago — do not edit by hand.
|
||||
# Regenerated on every key change and daemon upgrade.
|
||||
node:
|
||||
identity:
|
||||
persistent: true
|
||||
discovery:
|
||||
lan:
|
||||
enabled: true
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: 1280
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: 127.0.0.1
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: 0.0.0.0:8668
|
||||
tcp:
|
||||
bind_addr: 0.0.0.0:8443
|
||||
peers: []
|
||||
";
|
||||
assert_eq!(render_config_yaml(), expected);
|
||||
}
|
||||
|
||||
/// The rendered config must parse as YAML and carry the mDNS opt-in at the
|
||||
/// exact path upstream reads (`node.discovery.lan.enabled`) — a typo there
|
||||
/// would silently leave LAN discovery off rather than erroring.
|
||||
#[test]
|
||||
fn test_lan_discovery_enabled_at_upstream_path() {
|
||||
let yaml = render_config_yaml();
|
||||
let parsed: serde_yaml::Value = serde_yaml::from_str(&yaml).expect("renders valid YAML");
|
||||
assert_eq!(
|
||||
parsed["node"]["discovery"]["lan"]["enabled"],
|
||||
serde_yaml::Value::Bool(true),
|
||||
);
|
||||
}
|
||||
|
||||
/// Rendering is deterministic: the startup drift check in server.rs compares
|
||||
/// the freshly rendered config against what's on disk, so any instability
|
||||
/// here would cause an endless reinstall+restart loop of the daemon.
|
||||
#[test]
|
||||
fn test_render_is_deterministic() {
|
||||
assert_eq!(render_config_yaml(), render_config_yaml());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_install_refuses_when_key_missing() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
|
||||
@@ -688,12 +688,27 @@ impl Server {
|
||||
let fips_peer_registry = fips_peer_registry.clone();
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(300));
|
||||
// Steady cadence, but retry fast right after a daemon restart:
|
||||
// regenerating fips.yaml (this build does, once, on first boot
|
||||
// after the OTA) restarts the fips daemon, and for a few seconds
|
||||
// `/run/fips/control.sock` is gone so every `fipsctl connect`
|
||||
// fails and the node islands until the next tick. Detect that
|
||||
// exact failure and retry in 15s instead of 5 min — bounded, so a
|
||||
// node with no fips daemon falls back to the steady cadence
|
||||
// rather than busy-looping.
|
||||
const STEADY: Duration = Duration::from_secs(300);
|
||||
const FAST: Duration = Duration::from_secs(15);
|
||||
const MAX_FAST_RETRIES: u32 = 8; // ≤2 min of fast retries/episode
|
||||
let mut fast_retries: u32 = 0;
|
||||
loop {
|
||||
interval.tick().await;
|
||||
let mut daemon_restarting = false;
|
||||
match crate::fips::anchors::load(&data_dir).await {
|
||||
Ok(list) if !list.is_empty() => {
|
||||
let _ = crate::fips::anchors::apply(&list).await;
|
||||
let results = crate::fips::anchors::apply(&list).await;
|
||||
daemon_restarting = !results.is_empty()
|
||||
&& results
|
||||
.iter()
|
||||
.all(|r| !r.ok && r.message.contains("control.sock"));
|
||||
}
|
||||
Ok(_) => { /* no seed anchors configured yet */ }
|
||||
Err(e) => {
|
||||
@@ -717,6 +732,15 @@ impl Server {
|
||||
let _ = crate::fips::anchors::apply(&direct).await;
|
||||
}
|
||||
}
|
||||
|
||||
let next = if daemon_restarting && fast_retries < MAX_FAST_RETRIES {
|
||||
fast_retries += 1;
|
||||
FAST
|
||||
} else {
|
||||
fast_retries = 0;
|
||||
STEADY
|
||||
};
|
||||
tokio::time::sleep(next).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
# Handoff — 2026-07-20 — peer-files diagnosis, FIPS 0.4.1, mobile transport pill
|
||||
|
||||
Written for a fresh session that will **cut the OTA release and build the ISO**.
|
||||
Everything below is already committed and pushed to `gitea-ai/main`. Last release
|
||||
was `v1.7.105-alpha` (`e2f83c01`); the next one should be **`v1.7.106-alpha`**.
|
||||
|
||||
---
|
||||
|
||||
## 1. What this release carries (3 commits on top of v1.7.105-alpha)
|
||||
|
||||
| Commit | What | User-visible? |
|
||||
|---|---|---|
|
||||
| `9e3ac9ba` | Show the FIPS/Tor transport pill on **mobile** peer files | Yes |
|
||||
| `3ab7fb52` | Log the full anyhow error chain on RPC failures | No (diagnostics) |
|
||||
| `5fd0d6c3` | Generate `fips.yaml` from typed structs + enable **mDNS LAN discovery** | Indirectly |
|
||||
|
||||
### `9e3ac9ba` — mobile transport pill
|
||||
`PeerFiles.vue:15` wraps the peer title in `hidden md:block` (the global header
|
||||
carries the name on mobile), and the transport pill was nested inside it — so it
|
||||
vanished below 768px. Added a separate `md:hidden` pill next to the peer icon.
|
||||
Frontend was rebuilt and the class verified present in the emitted bundle.
|
||||
|
||||
Caveats worth knowing (pre-existing, not introduced here):
|
||||
- On this code path the backend only ever emits `fips` or `tor`, so the `mesh`
|
||||
and `lan` branches in `transportPill` (`PeerFiles.vue:609-627`) are dead.
|
||||
- For **received** mesh messages, `mesh/mod.rs:1519-1533` falls back to a
|
||||
hardcoded `"tor"` when the transport is unknown — that pill can genuinely lie.
|
||||
The peer-files pill does not.
|
||||
|
||||
### `3ab7fb52` — full error chain in logs
|
||||
`api/rpc/mod.rs:441` logged only the outermost anyhow context, so every
|
||||
peer-files failure read exactly `RPC error on content.browse-peer: Failed to
|
||||
connect to peer` with the real cause discarded. Now `{:#}`. The client-facing
|
||||
message still goes through `sanitize_error_message(&e.to_string())` (`{}`), so
|
||||
no internal detail leaks. **This fix applies to every RPC method, not just
|
||||
browse-peer.**
|
||||
|
||||
### `5fd0d6c3` — typed FIPS config + mDNS
|
||||
`fips/config.rs` built `/etc/fips/fips.yaml` by `format!`-ing a string literal.
|
||||
Upstream's config structs are `#[serde(deny_unknown_fields)]`, so a wrong key
|
||||
does not degrade — **the daemon refuses to start and the node leaves the mesh**.
|
||||
Now a typed serde struct tree, verified field-by-field against jmcorgan/fips
|
||||
**v0.4.1**, with 4 tests: exact-output snapshot, determinism, mDNS key path, and
|
||||
the pre-existing schema test. All pass.
|
||||
|
||||
Also enables `node.discovery.lan.enabled` (mDNS/DNS-SD, new upstream in v0.4.0)
|
||||
so co-located nodes peer directly instead of depending on the public anchor.
|
||||
|
||||
> ⚠️ **Expected one-time behaviour on first boot after this lands:** the startup
|
||||
> drift check at `server.rs:864` compares the freshly rendered config against
|
||||
> what's on disk. The render differs now, so it reinstalls the config and
|
||||
> restarts the FIPS daemon **once**. This is the intended self-healing path and
|
||||
> settles immediately. Do not mistake it for a regression.
|
||||
|
||||
Emitted unconditionally rather than version-gated: v0.3.0's `DiscoveryConfig`
|
||||
has no `lan` field **and** no `deny_unknown_fields`, so v0.3.0 daemons ignore it
|
||||
harmlessly (verified against the v0.3.0 source). It self-activates on upgrade.
|
||||
|
||||
---
|
||||
|
||||
## 2. FIPS 0.4.1 — validated, but the fleet is NOT rolled
|
||||
|
||||
Fleet was on FIPS **0.3.0 / 0.3.0-dev** (2026-05-11). Upstream is **v0.4.1**
|
||||
(2026-07-19). Verified before touching anything:
|
||||
|
||||
- **Wire-compatible** 0.3.0 → 0.4.0 → 0.4.1. Rolling upgrade, any order, no flag day.
|
||||
- **Config forward-compatible** — every key we emit exists in 0.4.1.
|
||||
- **Asset names match** what `fips/update.rs` expects (`fips_<ver>_<arch>.deb` +
|
||||
`checksums-linux.txt`), so the in-product updater should work.
|
||||
|
||||
### Upgraded so far (2 of N)
|
||||
| Node | Before | After | Result |
|
||||
|---|---|---|---|
|
||||
| OptiPlex `.198` / `100.114.134.21` | `0.3.0-dev-1` | **0.4.1** | ✅ anchor connected, `is_parent: true`, tree `depth: 4` |
|
||||
| thinkpad (this machine) | `0.3.0` | **0.4.1** | ✅ service active, but still islanded (see §4) |
|
||||
|
||||
The OptiPlex was still running the **old string-rendered config** and 0.4.1
|
||||
accepted it — empirical confirmation of the compat analysis, not just desk work.
|
||||
|
||||
### Upgrade recipe (nodes cannot reach GitHub — sideload)
|
||||
```bash
|
||||
# 1. On a host with GitHub access:
|
||||
curl -sL -o fips_0.4.1_amd64.deb \
|
||||
https://github.com/jmcorgan/fips/releases/download/v0.4.1/fips_0.4.1_amd64.deb
|
||||
curl -sL -o checksums-linux.txt \
|
||||
https://github.com/jmcorgan/fips/releases/download/v0.4.1/checksums-linux.txt
|
||||
sha256sum fips_0.4.1_amd64.deb # must match checksums-linux.txt
|
||||
# expected: 9befcc0990c7e08742b5a88f75d753a1088134b20525156688d559a317334ded
|
||||
|
||||
# 2. Sideload:
|
||||
scp fips_0.4.1_amd64.deb archipelago@<node>:/tmp/
|
||||
|
||||
# 3. On the node — the same command update.rs uses:
|
||||
sudo -n systemd-run --collect --wait --quiet --pipe -- \
|
||||
env DEBIAN_FRONTEND=noninteractive dpkg --force-confold --force-downgrade -i \
|
||||
/tmp/fips_0.4.1_amd64.deb
|
||||
|
||||
# 4. Restart the ACTIVE unit — it is archipelago-fips.service,
|
||||
# NOT fips.service (which is inactive on these nodes):
|
||||
sudo -n systemctl restart archipelago-fips.service
|
||||
|
||||
# 5. Verify:
|
||||
fipsctl --version
|
||||
sudo -n fipsctl show links # expect anchor 185.18.221.160:8443 connected
|
||||
sudo -n fipsctl show tree # expect is_root: false, depth > 0
|
||||
```
|
||||
|
||||
### ISO implication (important)
|
||||
`image-recipe/build/auto-installer/Dockerfile.rootfs:23` builds FIPS from
|
||||
**unpinned upstream main** (`git clone --depth 1`, no rev/tag/checksum, amd64
|
||||
only). So a freshly built ISO will pick up whatever main is that day — probably
|
||||
≥0.4.1, but it is not deterministic. Pinning is an open item in
|
||||
`docs/1.8.0-RELEASE-HARDENING-PLAN.md:319-322`. **Consider pinning to v0.4.1
|
||||
before building the release ISO** so the shipped version is knowable.
|
||||
|
||||
---
|
||||
|
||||
## 3. The original bug — peer cloud files not loading
|
||||
|
||||
**Status: root-caused for the thinkpad; NOT fully explained.** Being explicit
|
||||
because it would be easy to read this as closed.
|
||||
|
||||
What is established:
|
||||
- FIPS was fully down on the thinkpad: `fipsctl show peers` → `[]`, `show links`
|
||||
→ `[]`, `show tree` → `is_root: true, depth 0`. An island.
|
||||
- Cause is **network egress**, not FIPS config: the thinkpad cannot reach the
|
||||
public anchor `185.18.221.160` (`fips.v0l.io`) **at all** — 100% packet loss on
|
||||
ICMP, 443/8443/8668 all time out. `show transports` showed
|
||||
`packets_sent: 760, packets_recv: 0` on both UDP and TCP.
|
||||
- Local firewall is **not** the cause (nft/iptables policy `accept`; only stock
|
||||
Tailscale anti-spoof DROPs).
|
||||
- The OptiPlex, on the same `/24`, reaches the anchor fine → it's the thinkpad's
|
||||
WiFi segment (`wlp3s0`), which also blocks L2 to `.198` (`ip neigh` → `FAILED`).
|
||||
- With no FIPS tree, everything falls back to Tor. Every peer in
|
||||
`federation/nodes.json` reads `last_transport: "tor"`, never `"fips"`.
|
||||
- **Tor itself is healthy**: fetched the OptiPlex's `/content` over Tor 3×,
|
||||
HTTP 200 in 4.1–8.5s — well inside the 30s budget at `content.rs:349`.
|
||||
|
||||
What is **not** established: why three specific `content.browse-peer` calls
|
||||
failed today (05:25, 16:37, 16:43 UTC). Tor tested healthy and was never
|
||||
reproduced. Two hypotheses were tested and **disproved**: the Tor fallback logic
|
||||
is correct (FIPS-unreachable returns `None` and falls through in Auto mode), and
|
||||
the legs get independent timeouts (Tor gets a fresh 30s). Best remaining guess is
|
||||
cold-circuit timeouts on first fetch after idle — **a guess, not a finding.**
|
||||
`3ab7fb52` means the next occurrence will log the actual cause.
|
||||
|
||||
### Corrections to earlier claims in this session
|
||||
- "Point FIPS at the Tailscale IP" was **wrong**. FIPS routes by npub; the
|
||||
`ip:port` in `fipsctl connect` is only an underlay endpoint hint.
|
||||
- "The public anchor may be dead fleet-wide" was **wrong**. Its peer is healthy
|
||||
(`delivery_ratio` 1.0 both directions, bloom filter syncing). The
|
||||
`bytes_recv: 0` link counters are simply uninstrumented in 0.3.0.
|
||||
|
||||
---
|
||||
|
||||
## 4. Open items — decisions NOT taken
|
||||
|
||||
1. **Second FIPS anchor (user asked for this; not built).** Needs a host running
|
||||
FIPS that is reachable from the restricted WiFi. Candidate found: OVH
|
||||
**`146.59.87.168`** — pings fine from the thinkpad and general egress works
|
||||
(github 200), while the upstream anchor fails even ICMP there. But it does not
|
||||
run FIPS yet, so this means **installing FIPS on the box that hosts Gitea** —
|
||||
a production change, deliberately not made unprompted. Code side is easy after:
|
||||
`fips/anchors.rs:47-50` is a single hardcoded anchor that should become a list
|
||||
(`default_public_anchor()` → `default_public_anchors() -> Vec<SeedAnchor>`).
|
||||
2. **Fleet rollout of FIPS 0.4.1** — only 2 nodes done. `.228`
|
||||
(`100.64.204.114`) has been **offline ~20h** and could not be included.
|
||||
3. **Deploying the archipelago binary** carrying `5fd0d6c3` — no node has it yet,
|
||||
so mDNS is not actually live anywhere. That is what this OTA is for.
|
||||
4. **mDNS caveat:** on the thinkpad's WiFi, multicast may also be blocked, so
|
||||
mDNS may not rescue that particular node even after the OTA. It will help
|
||||
co-located nodes on sane networks.
|
||||
5. **Pin FIPS in the ISO build** (see §2) — recommended before the release ISO.
|
||||
|
||||
---
|
||||
|
||||
## 5. Release ritual (from prior sessions — follow exactly)
|
||||
|
||||
Working tree at handoff had pre-existing unrelated dirt: `core/Cargo.lock`,
|
||||
`release-manifest.json`, `releases/manifest.json` modified, and an untracked
|
||||
`neode-ui/vite.preview.config.mts`. **Stage explicitly by path** — another
|
||||
agent may share this tree; never `git add -A`.
|
||||
|
||||
```bash
|
||||
V=1.7.106-alpha
|
||||
|
||||
# Frontend build — MUST verify dist actually changed (build can silently no-op)
|
||||
cd neode-ui && npm run build # → web/dist/neode-ui/
|
||||
grep -r "md:hidden" ../web/dist/neode-ui/assets/PeerFiles-*.js # sanity
|
||||
|
||||
# Backend
|
||||
cd core && cargo build --release -p archipelago
|
||||
# If you hit `rust-lld: undefined hidden symbol`, it's incremental-cache
|
||||
# corruption — rebuild with CARGO_INCREMENTAL=0
|
||||
|
||||
# Tarball MUST be flat (files at root, no neode-ui/ wrapper) or every fleet UI 403s
|
||||
tar -czf releases/v$V/archipelago-frontend-$V.tar.gz -C web/dist/neode-ui .
|
||||
tar -tzf releases/v$V/archipelago-frontend-$V.tar.gz | head -3 # ./ then ./index.html
|
||||
# Exclude the ~17MB companion APK from tarballs.
|
||||
|
||||
# Ship
|
||||
scripts/create-release.sh $V
|
||||
scripts/publish-release-assets.sh $V gitea-vps2
|
||||
git push origin main && git push origin --tags # tag or the Releases page stays empty
|
||||
git push gitea-ai main # main is protected; use the `ai` account
|
||||
|
||||
# Verify the live manifest
|
||||
curl -fsS http://146.59.87.168:3000/lfg2025/archy/raw/branch/main/releases/manifest.json
|
||||
```
|
||||
|
||||
Notes: vps2 (`146.59.87.168`) is the **primary** OTA manifest host. Signing is
|
||||
done at the **user's TTY** — do not attempt it unattended. Clean `/tmp` first
|
||||
(past releases hit ENOSPC). Changelogs must be **layman-readable**, leading with
|
||||
user benefit.
|
||||
|
||||
### ISO
|
||||
```bash
|
||||
UNBUNDLED=1 bash image-recipe/build-debian-iso.sh
|
||||
```
|
||||
ISO builds are **always unbundled** — the default env silently builds the wrong
|
||||
full-bundle variant. Only filebrowser + fmcd are baked in. Verify the output
|
||||
filename contains `unbundled` and is ≈2.4G. The ISO's frontend source is
|
||||
`/opt/archipelago/web-ui` — rsync dist there first and verify **inside** the ISO.
|
||||
|
||||
---
|
||||
|
||||
## 6. Node access quick reference
|
||||
|
||||
- **thinkpad (`.116`) is the local machine** — do not SSH to it; read
|
||||
`journalctl -u archipelago` and `/var/lib/archipelago/**` directly.
|
||||
- **OptiPlex `.198`** = Tailscale `archipelago-5` / `100.114.134.21`, user
|
||||
`archipelago`. Its LAN IP is unreachable from the thinkpad — use Tailscale.
|
||||
- `.228` = `archipelago-2` / `100.64.204.114` — **offline as of 2026-07-20**, and
|
||||
it is in real use; don't touch uninvited.
|
||||
- `archipelago-1` (`100.82.34.38`) is a Ryzen AI Max desktop, **not** the OptiPlex.
|
||||
- Nodes have no `sqlite3` — use `sudo -n python3` to read the JSON stores.
|
||||
- `fipsctl` needs `sudo -n` (socket is `root:fips` 0660).
|
||||
- **Never run `archipelago --version` on fleet nodes** (deployed binaries predate #74).
|
||||
@@ -269,11 +269,14 @@ if [ ! -f "$ROOTFS_TAR" ] || [ "${1:-}" == "--rebuild" ] || [ "$(cat "$ROOTFS_ST
|
||||
|
||||
# Create a Dockerfile for building the rootfs
|
||||
cat > "$WORK_DIR/Dockerfile.rootfs" <<DOCKERFILE
|
||||
# ─── Stage 1: Build the FIPS mesh daemon .deb from upstream main ─────────
|
||||
# ─── Stage 1: Build the FIPS mesh daemon .deb at a pinned tag ────────────
|
||||
#
|
||||
# FIPS (github.com/jmcorgan/fips) is a fast Nostr-keyed mesh routing
|
||||
# protocol archipelago uses as its preferred non-Tor transport. We track
|
||||
# upstream main per project decision (2026-04) — v0.2.0 isn't stable yet.
|
||||
# protocol archipelago uses as its preferred non-Tor transport.
|
||||
# Pinned so the shipped version is knowable: an unpinned --depth 1 clone of
|
||||
# main made every ISO carry whatever upstream happened to be that day.
|
||||
# v0.4.1 is the version fips/config.rs renders its typed config against and
|
||||
# the one validated in the field. Bump the two together.
|
||||
# The .deb is rebuilt every ISO build; Docker layer caching keeps the
|
||||
# incremental cost low. Failure here fails the ISO build on purpose:
|
||||
# we don't want to ship an ISO that silently skips FIPS.
|
||||
@@ -291,7 +294,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \\
|
||||
clang libclang-dev libnftnl-dev libmnl-dev \\
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN cargo install --locked cargo-deb
|
||||
RUN git clone --depth 1 https://github.com/jmcorgan/fips.git /src/fips
|
||||
ARG FIPS_VERSION=v0.4.1
|
||||
RUN git clone --depth 1 --branch "\$FIPS_VERSION" \\
|
||||
https://github.com/jmcorgan/fips.git /src/fips
|
||||
WORKDIR /src/fips
|
||||
# fips-gateway is gated behind the `gateway` Cargo feature (depends on
|
||||
# `rustables`). Without the feature, cargo doesn't build it, and
|
||||
@@ -1178,39 +1183,13 @@ BACKENDFILE
|
||||
fi
|
||||
fi
|
||||
|
||||
# Extract NostrVPN binary from container image (native system service, not a container app)
|
||||
# NOTE: The container image must be built against Debian 13's GLIBC (2.40).
|
||||
# If built against a newer GLIBC, the binary will fail at runtime.
|
||||
# Rebuild with: FROM debian:13 AS builder
|
||||
echo " Extracting NostrVPN binary..."
|
||||
_NVPN_IMG="${NOSTR_VPN_IMAGE:-146.59.87.168:3000/lfg2025/nostr-vpn:v0.3.7}"
|
||||
NVPN_IMAGE_ID="$($CONTAINER_CMD images -q "$_NVPN_IMG" 2>/dev/null)"
|
||||
if [ -z "$NVPN_IMAGE_ID" ]; then
|
||||
$CONTAINER_CMD pull "$_NVPN_IMG" 2>/dev/null || true
|
||||
fi
|
||||
NVPN_CONTAINER=$($CONTAINER_CMD create "$_NVPN_IMG" 2>/dev/null) || true
|
||||
if [ -n "$NVPN_CONTAINER" ]; then
|
||||
$CONTAINER_CMD cp "$NVPN_CONTAINER:/usr/local/bin/nvpn" "$ARCH_DIR/bin/nvpn" 2>/dev/null && \
|
||||
chmod +x "$ARCH_DIR/bin/nvpn" && \
|
||||
echo " ✅ NostrVPN binary extracted ($(du -h "$ARCH_DIR/bin/nvpn" | cut -f1))"
|
||||
$CONTAINER_CMD rm "$NVPN_CONTAINER" 2>/dev/null || true
|
||||
# Check GLIBC compatibility — Debian 13 (Trixie) has GLIBC 2.40
|
||||
if [ -f "$ARCH_DIR/bin/nvpn" ]; then
|
||||
NVPN_GLIBC=$(objdump -T "$ARCH_DIR/bin/nvpn" 2>/dev/null | grep -oP 'GLIBC_\K[0-9.]+' | sort -V | tail -1)
|
||||
if [ -n "$NVPN_GLIBC" ]; then
|
||||
# Compare: if required GLIBC > 2.40, warn
|
||||
if printf '%s\n' "2.40" "$NVPN_GLIBC" | sort -V | tail -1 | grep -qv "^2\.40$"; then
|
||||
echo " ⚠ WARNING: nvpn binary requires GLIBC $NVPN_GLIBC but Debian 13 has 2.40"
|
||||
echo " ⚠ The nvpn daemon will fail at runtime. Rebuild the container against Debian 13."
|
||||
echo " ⚠ VPN invite/status will still work via Rust backend config.toml fallback."
|
||||
else
|
||||
echo " ✅ nvpn GLIBC compatibility OK (requires $NVPN_GLIBC, target has 2.40)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo " ⚠ NostrVPN image not available — nvpn binary will be missing"
|
||||
fi
|
||||
# NostrVPN (the native `nvpn` mesh-VPN daemon) has been removed from the
|
||||
# product — the active VPN path is WireGuard/Tailscale (see core vpn.rs). Its
|
||||
# service is already masked below (ln -sf /dev/null nostr-vpn.service) and the
|
||||
# binary is never spawned at runtime, so we no longer extract it. The
|
||||
# nostr-vpn image was deleted from the registry, which is why hard-requiring
|
||||
# it here bricked the build. Intentionally left out; do not re-add without
|
||||
# restoring the daemon.
|
||||
|
||||
# Extract nostr-rs-relay binary from container image (native system service for VPN signaling)
|
||||
echo " Extracting nostr-rs-relay binary..."
|
||||
@@ -1220,7 +1199,11 @@ if [ -z "$RELAY_IMAGE" ]; then
|
||||
fi
|
||||
RELAY_CONTAINER=$($CONTAINER_CMD create 146.59.87.168:3000/lfg2025/nostr-rs-relay:0.9.0 2>/dev/null) || true
|
||||
if [ -n "$RELAY_CONTAINER" ]; then
|
||||
$CONTAINER_CMD cp "$RELAY_CONTAINER:/usr/local/bin/nostr-rs-relay" "$ARCH_DIR/bin/nostr-rs-relay" 2>/dev/null && \
|
||||
# The relay image builds to its WORKDIR /usr/src/app and execs
|
||||
# ./nostr-rs-relay from there (not /usr/local/bin — that path was from an
|
||||
# older image build and silently broke extraction once the image was
|
||||
# rebuilt to the standard layout).
|
||||
$CONTAINER_CMD cp "$RELAY_CONTAINER:/usr/src/app/nostr-rs-relay" "$ARCH_DIR/bin/nostr-rs-relay" 2>/dev/null && \
|
||||
chmod +x "$ARCH_DIR/bin/nostr-rs-relay" && \
|
||||
echo " ✅ nostr-rs-relay binary extracted ($(du -h "$ARCH_DIR/bin/nostr-rs-relay" | cut -f1))"
|
||||
$CONTAINER_CMD rm "$RELAY_CONTAINER" 2>/dev/null || true
|
||||
@@ -1228,11 +1211,11 @@ else
|
||||
echo " ⚠ nostr-rs-relay image not available — relay binary will be missing"
|
||||
fi
|
||||
|
||||
# A missing nvpn/nostr-rs-relay used to be a warning, and the resulting ISO
|
||||
# shipped units that crash-looped (or silently lacked VPN signaling) on every
|
||||
# install. Refuse to produce that ISO unless explicitly overridden.
|
||||
# A missing nostr-rs-relay used to be a warning, and the resulting ISO shipped
|
||||
# an enabled nostr-relay unit that crash-looped on every install. Refuse to
|
||||
# produce that ISO unless explicitly overridden. (nvpn is intentionally no
|
||||
# longer required — NostrVPN was removed; see the note above.)
|
||||
MISSING_VPN_BINARIES=""
|
||||
[ -f "$ARCH_DIR/bin/nvpn" ] || MISSING_VPN_BINARIES="$MISSING_VPN_BINARIES nvpn"
|
||||
[ -f "$ARCH_DIR/bin/nostr-rs-relay" ] || MISSING_VPN_BINARIES="$MISSING_VPN_BINARIES nostr-rs-relay"
|
||||
if [ -n "$MISSING_VPN_BINARIES" ]; then
|
||||
if [ "${ALLOW_MISSING_VPN_BINARIES:-0}" = "1" ]; then
|
||||
@@ -2507,6 +2490,17 @@ HandleLidSwitchExternalPower=ignore
|
||||
HandleLidSwitchDocked=ignore
|
||||
LIDCONF
|
||||
|
||||
# Kiosk appliance: a short press or accidental brush of the power button used
|
||||
# to shut the node down instantly (confirmed on a Framework node — "Power key
|
||||
# pressed short -> Powering off"). Ignore the short press; keep a deliberate
|
||||
# long press (hold ~2s) as the intentional power-off, so the node isn't taken
|
||||
# down by accident but can still be shut down on purpose without a keyboard.
|
||||
cat > /mnt/target/etc/systemd/logind.conf.d/power-key.conf <<'PWRCONF'
|
||||
[Login]
|
||||
HandlePowerKey=ignore
|
||||
HandlePowerKeyLongPress=poweroff
|
||||
PWRCONF
|
||||
|
||||
# Copy Archipelago binaries and files
|
||||
if [ -d "$BOOT_MEDIA/archipelago/bin" ]; then
|
||||
cp -r "$BOOT_MEDIA/archipelago/bin/"* /mnt/target/usr/local/bin/ 2>/dev/null || true
|
||||
@@ -2666,6 +2660,11 @@ if [ -t 0 ] && [ -z "$ARCHIPELAGO_WELCOMED" ]; then
|
||||
W='\033[1;37m'
|
||||
N='\033[0m'
|
||||
|
||||
# The logo uses UTF-8 block-drawing glyphs. Switching back from the kiosk
|
||||
# (Xorg on vt1) can leave the console VT out of UTF-8 mode, which renders
|
||||
# them as garbage bytes ("sometimes corrupt"). ESC % G forces the VT into
|
||||
# UTF-8 mode so the logo always draws correctly.
|
||||
printf '\033%%G' 2>/dev/null || true
|
||||
clear
|
||||
echo -e " ${O}▄▀█ █▀▄ █▀▀ █ █ █ █▀█ █▀▀ █ ▄▀█ █▀▀ █▀█${N}"
|
||||
echo -e " ${O}█▀█ █▀▄ █ █▀█ █ █▀▀ ██▀ █ █▀█ █ █ █ █${N}"
|
||||
@@ -2681,10 +2680,14 @@ if [ -t 0 ] && [ -z "$ARCHIPELAGO_WELCOMED" ]; then
|
||||
if [ -b /dev/mapper/archipelago-data ] || [ -b /dev/mapper/archipelago_crypt ]; then
|
||||
echo -e " ${OD}storage LUKS2 encrypted${N}"
|
||||
fi
|
||||
# The kiosk's Xorg runs on vt1 (see archipelago-kiosk-launcher: "Xorg :0
|
||||
# vt1"), so Ctrl+Alt+F1 IS the kiosk and a terminal is on another VT (F2,
|
||||
# where systemd auto-spawns a getty). The old hints had these backwards —
|
||||
# they sent you to an empty black VT7 and there was no way back to the kiosk.
|
||||
if systemctl is-active archipelago-kiosk.service >/dev/null 2>&1; then
|
||||
echo -e " ${OD}display Kiosk active (Ctrl+Alt+F1 for terminal)${N}"
|
||||
echo -e " ${OD}display Kiosk active (Ctrl+Alt+F2 for terminal)${N}"
|
||||
else
|
||||
echo -e " ${OD}display Console (Ctrl+Alt+F7 for kiosk)${N}"
|
||||
echo -e " ${OD}display Console (Ctrl+Alt+F1 for kiosk)${N}"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
@@ -82,6 +82,24 @@ configure_display() {
|
||||
|
||||
configure_display
|
||||
|
||||
# --- Kiosk UI scaling for large / high-res displays -----------------------
|
||||
# REVERT: set env ARCHIPELAGO_KIOSK_SCALE=1 (per-node, no rebuild), or restore
|
||||
# the hardcoded --force-device-scale-factor=1 below to disable entirely.
|
||||
#
|
||||
# A big TV reports its full native resolution as the CSS viewport, so a 4K
|
||||
# panel becomes a 3840px-wide viewport and the UI renders tiny — and a
|
||||
# keyboard-less kiosk can't zoom. Derive Chromium's device-scale-factor from
|
||||
# the detected panel width so the *effective* CSS viewport lands near a
|
||||
# comfortable across-the-room target, scaling the whole UI up on big screens:
|
||||
# 1920x1080 -> 1.50 | 2560x1440 -> 2.00 | 3840x2160 -> 3.00 (clamped 1.0-3.0)
|
||||
KIOSK_TARGET_CSS_WIDTH=${ARCHIPELAGO_KIOSK_TARGET_CSS_WIDTH:-1280}
|
||||
if [ -n "${ARCHIPELAGO_KIOSK_SCALE:-}" ]; then
|
||||
KIOSK_SCALE=$ARCHIPELAGO_KIOSK_SCALE
|
||||
else
|
||||
KIOSK_SCALE=$(awk -v w="$KIOSK_MODE_W" -v t="$KIOSK_TARGET_CSS_WIDTH" \
|
||||
'BEGIN{if(t<=0)t=1280; s=w/t; s=int(s*4+0.5)/4; if(s<1)s=1; if(s>3)s=3; printf "%.2f", s}')
|
||||
fi
|
||||
|
||||
xhost +SI:localuser:archipelago 2>/dev/null || true
|
||||
xsetroot -solid black 2>/dev/null || true
|
||||
xset s off 2>/dev/null || true
|
||||
@@ -116,8 +134,17 @@ while true; do
|
||||
# backend can't find PipeWire-Pulse's socket at /run/user/<uid>/pulse/native,
|
||||
# falls back to raw ALSA "default", fails to connect, and produces no audio
|
||||
# at all with no visible error (--noerrdialogs suppresses it).
|
||||
sudo -u archipelago env DISPLAY=:0 HOME=/home/archipelago XDG_RUNTIME_DIR=/run/user/$ARCHIPELAGO_UID chromium --kiosk \
|
||||
# Force a DARK color-scheme preference. The main UI hardcodes its dark
|
||||
# theme, but the bundled AIUI app themes via `@media (prefers-color-scheme)`
|
||||
# and defaults to its LIGHT variant (white panels) when the browser reports
|
||||
# no preference — which a minimal kiosk X session does. Two independent dark
|
||||
# signals so this survives a Chromium enum change: GTK_THEME is version-
|
||||
# independent and can never force light (worst case: no effect), and the
|
||||
# blink-settings flag (0 = kDark in modern Chromium) reinforces it. Neither
|
||||
# can regress the always-dark main UI.
|
||||
sudo -u archipelago env DISPLAY=:0 HOME=/home/archipelago GTK_THEME=Adwaita:dark XDG_RUNTIME_DIR=/run/user/$ARCHIPELAGO_UID chromium --kiosk \
|
||||
--app=http://localhost/kiosk?safe_area_x=${KIOSK_SAFE_AREA_X_PX:-0}\&safe_area_y=${KIOSK_SAFE_AREA_Y_PX:-0} \
|
||||
--blink-settings=preferredColorScheme=0 \
|
||||
--noerrdialogs \
|
||||
--disable-infobars \
|
||||
--disable-translate \
|
||||
@@ -133,7 +160,7 @@ while true; do
|
||||
--window-size=${KIOSK_MODE_W},${KIOSK_MODE_H} \
|
||||
--window-position=0,0 \
|
||||
--start-fullscreen \
|
||||
--force-device-scale-factor=1 \
|
||||
--force-device-scale-factor=${KIOSK_SCALE} \
|
||||
--disable-background-networking \
|
||||
--disable-background-timer-throttling \
|
||||
--disable-backgrounding-occluded-windows \
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "neode-ui",
|
||||
"version": "1.7.105-alpha",
|
||||
"version": "1.7.108-alpha",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "neode-ui",
|
||||
"version": "1.7.105-alpha",
|
||||
"version": "1.7.108-alpha",
|
||||
"dependencies": {
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "neode-ui",
|
||||
"private": true,
|
||||
"version": "1.7.105-alpha",
|
||||
"version": "1.7.108-alpha",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "./start-dev.sh",
|
||||
|
||||
@@ -25,6 +25,15 @@
|
||||
<p v-if="currentPeer?.did" class="text-sm text-white/50 font-mono truncate max-w-md" :title="currentPeer.did">{{ currentPeer.did }}</p>
|
||||
<p v-else class="text-sm text-white/50">Peer files</p>
|
||||
</div>
|
||||
<!-- Mobile: the title block above is hidden (the global header carries the
|
||||
peer name), so the transport pill would vanish with it. Render it on
|
||||
its own here so mobile also sees whether this peer is FIPS or Tor. -->
|
||||
<span
|
||||
v-if="transportPill"
|
||||
:class="transportPill.cls"
|
||||
:title="transportPill.title"
|
||||
class="md:hidden text-xs px-2 py-0.5 rounded-full font-medium"
|
||||
>{{ transportPill.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="w-5 h-5 text-white/60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
|
||||
<span class="text-white/80 text-sm">Fuck IPs Mesh</span>
|
||||
<span class="text-white/80 text-sm">F*ck IPs Mesh</span>
|
||||
</div>
|
||||
<span class="text-sm" :class="fipsRowTextClass">{{ fipsRowLabel }}</span>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex items-start justify-between gap-4 mb-2">
|
||||
<h2 class="text-xl font-semibold text-white">Fuck IPs Mesh</h2>
|
||||
<h2 class="text-xl font-semibold text-white">F*ck IPs Mesh</h2>
|
||||
<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>
|
||||
|
||||
@@ -362,6 +362,35 @@ init()
|
||||
</button>
|
||||
</div>
|
||||
<div class="overflow-y-auto flex-1 min-h-0 space-y-6 pr-1">
|
||||
<!-- v1.7.108-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.108-alpha</span>
|
||||
<span class="text-xs text-white/40">July 20, 2026</span>
|
||||
</div>
|
||||
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
|
||||
<p>Your node connects to the private mesh far more reliably. Nodes rely on a public rendezvous point to find each other, and the only one available was unreachable from many home and office networks — leaving some nodes unable to join the mesh at all. There is now a second, always-reachable rendezvous point, and your node tries every one it knows, so it joins the mesh in seconds instead of being stranded.</p>
|
||||
<p>Wi-Fi setup now heals itself on older nodes. Some nodes set up before a mid-year fix couldn't connect to a Wi-Fi network from the screen — it failed with a permissions error — because the piece that lets the node manage networking on your behalf was missing. Nodes now put that piece in place automatically on startup, so "scan, pick a network, type the password, connect" works without reinstalling.</p>
|
||||
<p>Your node rejoins the mesh within seconds after an update. Applying an update briefly restarts the mesh service, and previously a node could sit disconnected from other nodes for up to five minutes before it retried.</p>
|
||||
<p>The TV screen now fits your television. On a large or 4K TV the interface rendered tiny with no way to zoom on a keyboard-less screen; it now sizes itself to a comfortable, readable scale automatically (and small laptop panels are left unchanged).</p>
|
||||
<p>More TV-screen polish: the built-in assistant shows its dark theme instead of bright white panels, the on-screen hint for switching between the kiosk and a terminal now points at the right keys, the welcome logo no longer occasionally renders as garbled characters, and an accidental tap of the power button no longer shuts the node down — hold it to power off on purpose.</p>
|
||||
<p>Behind the scenes: fixed the installer image build so it no longer stops on a component that was removed from the product, and so it correctly includes the private relay it was meant to bundle.</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- v1.7.106-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.106-alpha</span>
|
||||
<span class="text-xs text-white/40">July 20, 2026</span>
|
||||
</div>
|
||||
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
|
||||
<p>Nodes on the same network now find each other directly. Your node announces itself on your local network and connects straight to other Archipelago nodes nearby, instead of every connection having to be introduced by a public rendezvous server out on the internet. Peers in the same home or office stay connected to each other even when that server is unreachable, and they reach each other faster.</p>
|
||||
<p>On a phone, the peer files screen tells you how you're connected again. The badge showing whether a peer's files are arriving over the fast mesh or over Tor was only visible on desktop — on narrow screens it disappeared entirely. It now appears next to the peer name on mobile too.</p>
|
||||
<p>Your node's mesh settings can no longer be written in a way that breaks the mesh. The configuration file used to be assembled as free-form text, where one wrong setting would stop the mesh service from starting and quietly drop your node off the network. It's now generated from a checked description of the file, with tests that verify the exact output.</p>
|
||||
<p>When your node has trouble reaching another node, the logs now record the real reason instead of a generic summary. A failure to open a peer's files previously logged only "Failed to connect to peer" and threw away the actual cause, which made these problems very hard to diagnose. Nothing changes on screen, and no internal detail is exposed.</p>
|
||||
<p>Behind the scenes: the installer image now builds its mesh component at a fixed, known version instead of whatever upstream had published that day, so two images built from the same source are identical.</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- v1.7.105-alpha -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
|
||||
+22
-21
@@ -1,31 +1,32 @@
|
||||
{
|
||||
"version": "1.7.105-alpha",
|
||||
"release_date": "2026-07-20",
|
||||
"changelog": [
|
||||
"Fixed a failure loop where a node that lost power or was moved could get stuck on a blank \"can't reach your node\" screen forever: startup recovery no longer spends minutes retrying containers that no longer exist, and a genuinely large recovery is no longer cut off half-way and forced to start over. The node now reaches its login screen even after the messiest shutdown.",
|
||||
"Phone tunnel setup (WireGuard) is now dependable: the QR screen automatically retries while a fresh install is still settling instead of dead-ending at \"failed to fetch\", and if your node has moved to a different network the QR and downloadable config now carry the node's current address instead of the old one.",
|
||||
"Fixed the white screen some laptop displays showed right after the intro on v1.7.104.",
|
||||
"The companion phone app no longer suggests installing the companion app from inside itself.",
|
||||
"The Tor page now lists onion addresses only for apps you actually have installed \u2014 fresh installs no longer come with six pre-made addresses for apps that were never set up.",
|
||||
"Running `archipelago --version` or `--help` on the command line now prints and exits instead of silently starting a second copy of the node, which could briefly disrupt running apps.",
|
||||
"Behind the scenes: installer image builds now stop loudly if VPN components are missing instead of producing a broken image, and a background file-permission sweep runs far less often, reducing disk churn on busy nodes."
|
||||
"Your node connects to the private mesh far more reliably. Nodes rely on a public rendezvous point to find each other, and the only one available was unreachable from many home and office networks — leaving some nodes unable to join the mesh at all. There is now a second, always-reachable rendezvous point, and your node tries every one it knows, so it joins the mesh in seconds instead of being stranded.",
|
||||
"Wi-Fi setup now heals itself on older nodes. Some nodes set up before a mid-year fix couldn't connect to a Wi-Fi network from the screen — it failed with a permissions error — because the piece that lets the node manage networking on your behalf was missing. Nodes now put that piece in place automatically on startup, so \"scan, pick a network, type the password, connect\" works without reinstalling.",
|
||||
"Your node rejoins the mesh within seconds after an update. Applying an update briefly restarts the mesh service, and previously a node could sit disconnected from other nodes for up to five minutes before it retried.",
|
||||
"The TV screen now fits your television. On a large or 4K TV the interface rendered tiny with no way to zoom on a keyboard-less screen; it now sizes itself to a comfortable, readable scale automatically (and small laptop panels are left unchanged).",
|
||||
"More TV-screen polish: the built-in assistant shows its dark theme instead of bright white panels, the on-screen hint for switching between the kiosk and a terminal now points at the right keys, the welcome logo no longer occasionally renders as garbled characters, and an accidental tap of the power button no longer shuts the node down — hold it to power off on purpose.",
|
||||
"Behind the scenes: fixed the installer image build so it no longer stops on a component that was removed from the product, and so it correctly includes the private relay it was meant to bundle."
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"current_version": "1.7.108-alpha",
|
||||
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.108-alpha/archipelago",
|
||||
"name": "archipelago",
|
||||
"current_version": "1.7.105-alpha",
|
||||
"new_version": "1.7.105-alpha",
|
||||
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.105-alpha/archipelago",
|
||||
"sha256": "5e9eb56458dd1bc4d60904ed3c84f8a8409778e640d900084e0e57b2c8c52143",
|
||||
"size_bytes": 50275432
|
||||
"new_version": "1.7.108-alpha",
|
||||
"sha256": "665d3351980d820287e614b0fdf4916957c042110f1ffd7ef6e93e78766ffa2a",
|
||||
"size_bytes": 50346064
|
||||
},
|
||||
{
|
||||
"name": "archipelago-frontend-1.7.105-alpha.tar.gz",
|
||||
"current_version": "1.7.105-alpha",
|
||||
"new_version": "1.7.105-alpha",
|
||||
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.105-alpha/archipelago-frontend-1.7.105-alpha.tar.gz",
|
||||
"sha256": "486ed02515e62caf3c40e8de0fe388ad7fb6589e454bec77111ac4780a1f6f01",
|
||||
"size_bytes": 174593338
|
||||
"current_version": "1.7.108-alpha",
|
||||
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.108-alpha/archipelago-frontend-1.7.108-alpha.tar.gz",
|
||||
"name": "archipelago-frontend-1.7.108-alpha.tar.gz",
|
||||
"new_version": "1.7.108-alpha",
|
||||
"sha256": "8fba3d3aeb2b506cb6a3b3102a07a1713ae7a2bd1ebd4fc734d83f83ba9a8aa8",
|
||||
"size_bytes": 174598175
|
||||
}
|
||||
]
|
||||
],
|
||||
"release_date": "2026-07-20",
|
||||
"signature": "94e2d15488383858d41c90c49075476ce56e4fed8b5829b92f68b3889229e795f250a57e557049bb46561c7c252d5d30db442d014b796e76d7ea4fbd7215dc00",
|
||||
"signed_by": "did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur",
|
||||
"version": "1.7.108-alpha"
|
||||
}
|
||||
|
||||
+22
-21
@@ -1,31 +1,32 @@
|
||||
{
|
||||
"version": "1.7.105-alpha",
|
||||
"release_date": "2026-07-20",
|
||||
"changelog": [
|
||||
"Fixed a failure loop where a node that lost power or was moved could get stuck on a blank \"can't reach your node\" screen forever: startup recovery no longer spends minutes retrying containers that no longer exist, and a genuinely large recovery is no longer cut off half-way and forced to start over. The node now reaches its login screen even after the messiest shutdown.",
|
||||
"Phone tunnel setup (WireGuard) is now dependable: the QR screen automatically retries while a fresh install is still settling instead of dead-ending at \"failed to fetch\", and if your node has moved to a different network the QR and downloadable config now carry the node's current address instead of the old one.",
|
||||
"Fixed the white screen some laptop displays showed right after the intro on v1.7.104.",
|
||||
"The companion phone app no longer suggests installing the companion app from inside itself.",
|
||||
"The Tor page now lists onion addresses only for apps you actually have installed \u2014 fresh installs no longer come with six pre-made addresses for apps that were never set up.",
|
||||
"Running `archipelago --version` or `--help` on the command line now prints and exits instead of silently starting a second copy of the node, which could briefly disrupt running apps.",
|
||||
"Behind the scenes: installer image builds now stop loudly if VPN components are missing instead of producing a broken image, and a background file-permission sweep runs far less often, reducing disk churn on busy nodes."
|
||||
"Your node connects to the private mesh far more reliably. Nodes rely on a public rendezvous point to find each other, and the only one available was unreachable from many home and office networks — leaving some nodes unable to join the mesh at all. There is now a second, always-reachable rendezvous point, and your node tries every one it knows, so it joins the mesh in seconds instead of being stranded.",
|
||||
"Wi-Fi setup now heals itself on older nodes. Some nodes set up before a mid-year fix couldn't connect to a Wi-Fi network from the screen — it failed with a permissions error — because the piece that lets the node manage networking on your behalf was missing. Nodes now put that piece in place automatically on startup, so \"scan, pick a network, type the password, connect\" works without reinstalling.",
|
||||
"Your node rejoins the mesh within seconds after an update. Applying an update briefly restarts the mesh service, and previously a node could sit disconnected from other nodes for up to five minutes before it retried.",
|
||||
"The TV screen now fits your television. On a large or 4K TV the interface rendered tiny with no way to zoom on a keyboard-less screen; it now sizes itself to a comfortable, readable scale automatically (and small laptop panels are left unchanged).",
|
||||
"More TV-screen polish: the built-in assistant shows its dark theme instead of bright white panels, the on-screen hint for switching between the kiosk and a terminal now points at the right keys, the welcome logo no longer occasionally renders as garbled characters, and an accidental tap of the power button no longer shuts the node down — hold it to power off on purpose.",
|
||||
"Behind the scenes: fixed the installer image build so it no longer stops on a component that was removed from the product, and so it correctly includes the private relay it was meant to bundle."
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"current_version": "1.7.108-alpha",
|
||||
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.108-alpha/archipelago",
|
||||
"name": "archipelago",
|
||||
"current_version": "1.7.105-alpha",
|
||||
"new_version": "1.7.105-alpha",
|
||||
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.105-alpha/archipelago",
|
||||
"sha256": "5e9eb56458dd1bc4d60904ed3c84f8a8409778e640d900084e0e57b2c8c52143",
|
||||
"size_bytes": 50275432
|
||||
"new_version": "1.7.108-alpha",
|
||||
"sha256": "665d3351980d820287e614b0fdf4916957c042110f1ffd7ef6e93e78766ffa2a",
|
||||
"size_bytes": 50346064
|
||||
},
|
||||
{
|
||||
"name": "archipelago-frontend-1.7.105-alpha.tar.gz",
|
||||
"current_version": "1.7.105-alpha",
|
||||
"new_version": "1.7.105-alpha",
|
||||
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.105-alpha/archipelago-frontend-1.7.105-alpha.tar.gz",
|
||||
"sha256": "486ed02515e62caf3c40e8de0fe388ad7fb6589e454bec77111ac4780a1f6f01",
|
||||
"size_bytes": 174593338
|
||||
"current_version": "1.7.108-alpha",
|
||||
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.108-alpha/archipelago-frontend-1.7.108-alpha.tar.gz",
|
||||
"name": "archipelago-frontend-1.7.108-alpha.tar.gz",
|
||||
"new_version": "1.7.108-alpha",
|
||||
"sha256": "8fba3d3aeb2b506cb6a3b3102a07a1713ae7a2bd1ebd4fc734d83f83ba9a8aa8",
|
||||
"size_bytes": 174598175
|
||||
}
|
||||
]
|
||||
],
|
||||
"release_date": "2026-07-20",
|
||||
"signature": "94e2d15488383858d41c90c49075476ce56e4fed8b5829b92f68b3889229e795f250a57e557049bb46561c7c252d5d30db442d014b796e76d7ea4fbd7215dc00",
|
||||
"signed_by": "did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur",
|
||||
"version": "1.7.108-alpha"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user