Compare commits

...
Author SHA1 Message Date
archipelago 246916c77b chore: release v1.8.1-alpha
Demo images / Build & push demo images (push) Successful in 3m48s
2026-08-13 14:30:16 -04:00
archipelagoandClaude Fable 5 00416c3c24 feat(app-catalog): curated store entries for Alby Hub + phoenixd
The hand-curated app-catalog/catalog.json is the release gate's drift
baseline; the generator syncs fields but never adds entries, so the two
new apps needed appending — fields taken verbatim from their manifests
(drift check green, 30 catalog / 58 manifest apps).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:48:03 -04:00
archipelagoandClaude Fable 5 1dc6d3e0e3 feat(catalog): Alby Hub + phoenixd live; registry refs move to the domain
Signed catalog: 68 apps (alby-hub v1.23.0 + phoenixd 0.9.0 join), every
image ref rewritten from the retired bare-IP host to the Foundation
domain. Trust floor promoted in this same commit: all five active fleet
nodes confirmed on 1.8.0-alpha (which trusts the domain); archy-x250-beta
is root-pin-stranded pre-.122 and needs a re-image regardless.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 13:44:11 -04:00
archipelagoandClaude Fable 5 816a06747a docs(changelog): curate v1.8.1-alpha notes + What's New block
Demo images / Build & push demo images (push) Successful in 3m35s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 09:26:46 -04:00
archipelagoandClaude Fable 5 9243babcdb feat(kiosk): graphics tiers + Settings knob; network map kiosk mode
Demo images / Build & push demo images (push) Successful in 3m51s
The animated federation map froze the framework-pt 4K TV: the launcher
held every machine to the HD 5500-era choppy-audio flags (single raster
thread, GpuRasterization banned) while the map wrote SVG attrs at 60fps.

- Launcher: two flag tiers. legacy = the proven conservative set; modern
  (Intel gen8+, 'NNth Gen' models, AMD Ryzen) = default raster threads +
  GPU rasterization. Classified from /proc/cpuinfo (11 model strings
  covered by tests in-session); KIOSK_GRAPHICS=performance|quality in
  kiosk-display.conf overrides; headless unchanged. Reaches deployed
  kiosks via the include_str! self-heal, same as the vsync fix.
- system.kiosk-display.get/set: carries a 'graphics' field alongside
  'preset'; setting one no longer clobbers the other.
- Settings → Display: Graphics picker (Auto / Compatibility / Quality).
- NetworkMap3D: kiosks default to the 2D projection (remembered toggle
  still works) and tick at half rate with carried-over deltas — same
  spin speed, half the paint cost.
- Changelog: curated Unreleased notes for all of the above + the gate
  frame-embedding fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 09:23:07 -04:00
archipelagoandClaude Fable 5 6672d978f7 feat(ui): Kammergut gloss test on the logo badge inner circle
Demo images / Build & push demo images (push) Successful in 3m46s
Black gloss paint from plan-b's Kammergut wordmark (verbatim #paintGloss
SVG filter + the .paint-3d sheen gradient) applied to
.logo-gradient-border::after — the circle behind the A on the
screensaver, intro, splash and login. Marked as a TEST in both files;
revert = git revert of this one commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 13:21:45 -04:00
archipelagoandClaude Fable 5 a80712963c feat(appgate): proxied apps become embeddable — gate neutralizes frame blocking
Apps that ship X-Frame-Options (Alby Hub: DENY) or a CSP frame-ancestors
directive rendered as a dead grey pane in the dashboard's embedded app
session; the historical fix was a bespoke per-app nginx strip proxy
(gitea). The gate now removes X-Frame-Options and strips ONLY the
frame-ancestors directive from proxied responses — the rest of the app's
CSP passes through untouched. The clickjacking threat those headers
address is handled the same way the gate's own pages handle it: every
proxied request is authenticated first, and the gate already declares
permissive frame-ancestors on its own responses. Unit-tested; verified
live on archi-dev-box (Alby Hub embeds, CSP intact).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 13:05:25 -04:00
archipelagoandClaude Fable 5 76e6f06995 fix(phoenixd): dot-free datadir + data_uid; docs: iframe rules
phoenixd: the orchestrator treats bind paths containing a dot as file
mounts and never creates their source dir, so the image's default
/phoenix/.phoenix target crash-looped the unit (statfs: no such file).
Datadir moved to /data via PHOENIX_DATADIR; data_uid 1000:1000 matches the
image's phoenix user — without it phoenixd dies on phoenix.conf
'Permission denied'. Both verified end-to-end on archi-dev-box: orch
install OK, seed.dat + db on the host, authenticated /getinfo answers.

alby-hub: launch flips to embedded — pairs with the gate change that
neutralizes upstream frame blocking.

Dev guide: iframe embedding rules (who blocks framing and why the gate
may strip it; when open_in_new_tab is legitimate; test in the embedded
session, never a tab).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 12:38:17 -04:00
archipelagoandClaude Fable 5 b7ba35477c docs(dev-guide): package.install needs dockerImage too; helper gotchas
Learned installing alby-hub/phoenixd for real: the id-only payload fails
with 'Missing dockerImage' (the store normally injects the image from the
catalog), the session helper silently reuses a stale cached session
without ARCHY_FORCE_LOGIN=1, needs jq, and its set -euo pipefail kills an
interactive shell chain without output — so the guide now wraps the flow
in a heredoc subshell.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 12:15:55 -04:00
archipelagoandClaude Fable 5 e87e8017bf docs(dev-guide): real pre-catalog testing flow on a live node
The old RPC example skipped login entirely and implied disk manifests show
up in the App Store. Documents: store lists signed-catalog + Nostr apps
only; the runtime-payload staging path (naive /opt/archipelago/apps copies
are deleted on every backend start); the rpc.bash session helper; and the
full lifecycle loop to run before submitting to the catalog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 12:09:58 -04:00
archipelagoandClaude Fable 5 6168f6a7d0 fix(alby-hub): host port 8087→8187 — 8087 is netbird's
Caught by the orchestrator's collision check on archi-dev-box.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 11:57:23 -04:00
archipelagoandClaude Fable 5 fdd26ba1e3 feat(apps): Alby Hub 1.23.0 + phoenixd 0.9.0 manifests with official icons
Demo images / Build & push demo images (push) Successful in 4m20s
Both images mirrored to the Foundation registry. Alby Hub: gated web UI
on 8087, LDK data under /var/lib/archipelago/alby-hub. phoenixd: headless
loopback API on 9740 (own password auth), seed dir preserved under
/var/lib/archipelago/phoenixd. Not yet in the signed catalog — disk
manifests only, pending install verification on archi-dev-box.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 11:55:17 -04:00
24 changed files with 973 additions and 197 deletions
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## v1.8.1-alpha (2026-08-13)
- **Apps that refused to open inside the dashboard now embed like everything else.** Some apps ship browser headers that forbid being shown inside another page — correct hardening on the open web, but inside Archipelago it produced a dead grey pane when you opened them from My Apps (Alby Hub was the first to hit it). The app gate, which already checks your login on every request to an app, now removes just those framing headers on the way through; each app's own content-security rules pass through untouched. No more per-app proxy workarounds.
- **The network map no longer freezes kiosk TVs.** The animated federation map at 4K was too much for the deliberately conservative graphics settings the on-screen display used on every machine — settings chosen years back to stop audio crackle on much older hardware. Two fixes: on kiosk screens the map now opens in its flat 2D view (the 3D globe is one tap away, and remembered) and animates at half rate — invisible from the couch, half the work. And the display itself now recognizes what machine it runs on: older kiosk boxes keep the proven careful settings, modern ones finally get real GPU rendering.
- **New Settings → Display → Graphics choice for the on-screen display.** Auto (recommended) picks the right rendering mode for the machine by itself; Compatibility forces the most conservative mode if a screen ever stutters, tears, or crackles; Quality forces full GPU rendering on hardware the automatic detection doesn't recognize. Changing it restarts the on-screen display, like the size presets.
## v1.8.0-alpha (2026-08-12)
- **Archipelago is now open source.** The full source code of the node you are running — the orchestrator, the dashboard, the app platform, the mesh, the release tooling — is published for anyone to read, build and audit at source.archipelago-foundation.org/lfg2025/archy. A node that holds your money, your files and your communications should not ask to be taken on faith: from this release onward you, or anyone you trust, can see exactly what it does and follow every change we make in the open.
+26 -2
View File
@@ -374,7 +374,7 @@
"id": "pine",
"title": "Pine",
"version": "1.3.0",
"description": "A private voice assistant for your home. Pine runs speech-to-text (Whisper), text-to-speech (Piper) and wake-word detection (openWakeWord) on your own node and pairs with a PineVoice satellite speaker, so Home Assistant Assist works locally with nothing sent to the cloud. Ask it about your node block height, sync, peers, Lightning balance and, when a Claude API key is set, anything else.",
"description": "A private voice assistant for your home. Pine runs speech-to-text (Whisper), text-to-speech (Piper) and wake-word detection (openWakeWord) on your own node and pairs with a PineVoice satellite speaker, so Home Assistant Assist works locally with nothing sent to the cloud. Ask it about your node \u2014 block height, sync, peers, Lightning balance \u2014 and, when a Claude API key is set, anything else.",
"icon": "/assets/img/app-icons/pine.svg",
"author": "Archipelago",
"category": "home",
@@ -459,7 +459,7 @@
"id": "netbird",
"title": "NetBird",
"version": "2.38.0",
"description": "Self-hosted WireGuard mesh VPN control plane with dashboard, embedded identity provider, management API, signal, relay, and STUN. The user-facing entry point a TLS proxy in front of the dashboard + server.",
"description": "Self-hosted WireGuard mesh VPN control plane with dashboard, embedded identity provider, management API, signal, relay, and STUN. The user-facing entry point \u2014 a TLS proxy in front of the dashboard + server.",
"icon": "/assets/img/app-icons/netbird.svg",
"author": "NetBird",
"category": "networking",
@@ -547,6 +547,30 @@
"/var/lib/archipelago/nextcloud:/var/www/html"
]
}
},
{
"id": "alby-hub",
"title": "Alby Hub",
"version": "1.23.0",
"description": "Self-custodial Lightning wallet hub. Runs its own Lightning node on your Archipelago and connects your apps to it over Nostr Wallet Connect \u2014 one hub, every app pays through it.",
"icon": "/assets/img/app-icons/alby-hub.svg",
"author": "Alby",
"category": "money",
"tier": "optional",
"dockerImage": "source.archipelago-foundation.org/lfg2025/alby-hub:v1.23.0",
"repoUrl": "https://github.com/getAlby/hub"
},
{
"id": "phoenixd",
"title": "phoenixd",
"version": "0.9.0",
"description": "Headless Lightning daemon by ACINQ (the Phoenix wallet team). No screen of its own \u2014 it exposes a small local API that other apps and tools use to send and receive Lightning payments. Channel liquidity is managed automatically for a fee.",
"icon": "/assets/img/app-icons/phoenixd.svg",
"author": "ACINQ",
"category": "money",
"tier": "optional",
"dockerImage": "source.archipelago-foundation.org/lfg2025/phoenixd:0.9.0",
"repoUrl": "https://github.com/ACINQ/phoenixd"
}
]
}
+75
View File
@@ -0,0 +1,75 @@
app:
id: alby-hub
name: Alby Hub
version: 1.23.0
description: Self-custodial Lightning wallet hub. Runs its own Lightning node on your Archipelago and connects your apps to it over Nostr Wallet Connect — one hub, every app pays through it.
category: money
container:
image: source.archipelago-foundation.org/lfg2025/alby-hub:v1.23.0
pull_policy: if-not-present
dependencies:
- storage: 1Gi
resources:
cpu_limit: 1
memory_limit: 512Mi
disk_limit: 2Gi
security:
capabilities: []
readonly_root: true
no_new_privileges: true
network_policy: bridge
ports:
- host: 8187
container: 8080
protocol: tcp
bind: 127.0.0.1
auth: gated
volumes:
- type: bind
source: /var/lib/archipelago/alby-hub
target: /data
options: [rw]
environment:
- WORK_DIR=/data
- PORT=8080
# LDK peers are dialed outbound-only in v1; no inbound p2p port is
# advertised, so no extra port mapping is needed for payments to work.
- LOG_LEVEL=info
health_check:
type: http
endpoint: http://localhost:8080
path: /
interval: 30s
timeout: 5s
retries: 5
interfaces:
main:
name: Web UI
description: Alby Hub wallet interface
type: ui
port: 8187
protocol: http
metadata:
icon: /assets/img/app-icons/alby-hub.svg
repo: https://github.com/getAlby/hub
tier: optional
launch:
# Embedded: the gate neutralizes Alby Hub's X-Frame-Options: DENY on
# proxied responses. Nodes older than the gate fix show a blocked
# frame — flip to true only if targeting such nodes.
open_in_new_tab: false
features:
- Self-custodial Lightning node (LDK) with a friendly wallet UI
- Connect wallets and apps via Nostr Wallet Connect (NWC)
- Per-app budgets and isolated sub-wallets
- Works with the Alby browser extension and mobile app
+75
View File
@@ -0,0 +1,75 @@
app:
id: phoenixd
name: phoenixd
version: 0.9.0
description: Headless Lightning daemon by ACINQ (the Phoenix wallet team). No screen of its own — it exposes a small local API that other apps and tools use to send and receive Lightning payments. Channel liquidity is managed automatically for a fee.
category: money
container:
# Image entrypoint already runs with --agree-to-terms-of-service and
# --http-bind-ip 0.0.0.0, as user "phoenix"; no custom args needed.
image: source.archipelago-foundation.org/lfg2025/phoenixd:0.9.0
pull_policy: if-not-present
# The image runs as user phoenix (1000:1000); the datadir bind source
# must be chowned to that identity or phoenixd dies on
# "Failed to open /data/phoenix.conf with Permission denied".
data_uid: "1000:1000"
dependencies:
- storage: 500Mi
resources:
cpu_limit: 1
memory_limit: 512Mi
disk_limit: 1Gi
security:
capabilities: []
readonly_root: true
no_new_privileges: true
network_policy: bridge
ports:
- host: 9740
container: 9740
protocol: tcp
bind: 127.0.0.1
auth: none
auth_rationale: >-
Loopback-only JSON API, not a web page. Every request is
authenticated by the http password phoenixd generates in its own
data directory on first run; the app gate's browser login page
would break the API clients this port exists for.
volumes:
# The wallet seed (seed.dat) and phoenix.conf live here. This directory
# must survive reinstall/migration like any other app data dir —
# losing it means losing funds.
# Target is /data (via PHOENIX_DATADIR below), NOT the image's default
# /phoenix/.phoenix: the orchestrator treats any bind path containing a
# dot as a file mount and skips creating its source directory, so a
# hidden-dir target never gets its host dir and the unit crash-loops.
- type: bind
source: /var/lib/archipelago/phoenixd
target: /data
options: [rw]
environment:
- PHOENIX_DATADIR=/data
health_check:
type: tcp
endpoint: localhost:9740
interval: 30s
timeout: 5s
retries: 5
metadata:
icon: /assets/img/app-icons/phoenixd.svg
repo: https://github.com/ACINQ/phoenixd
tier: optional
features:
- Ultra-light Lightning node — no bitcoin node required
- Automated channel and liquidity management (fees apply)
- Simple HTTP API + websockets for payments
- Backed by the team behind the Phoenix mobile wallet
+1 -1
View File
@@ -104,7 +104,7 @@ dependencies = [
[[package]]
name = "archipelago"
version = "1.8.0-alpha"
version = "1.8.1-alpha"
dependencies = [
"anyhow",
"archipelago-container",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "archipelago"
version = "1.8.0-alpha"
version = "1.8.1-alpha"
edition = "2021"
license.workspace = true
description = "Archipelago Bitcoin Node OS - Native backend"
+52 -13
View File
@@ -1268,7 +1268,14 @@ impl RpcHandler {
} else {
"auto"
};
Ok(serde_json::json!({ "has_kiosk": has_kiosk, "preset": preset }))
let graphics = if conf.contains("KIOSK_GRAPHICS=performance") {
"performance"
} else if conf.contains("KIOSK_GRAPHICS=quality") {
"quality"
} else {
"auto"
};
Ok(serde_json::json!({ "has_kiosk": has_kiosk, "preset": preset, "graphics": graphics }))
}
/// system.kiosk-display.set — Write the kiosk display preset and restart
@@ -1279,24 +1286,56 @@ impl RpcHandler {
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
let preset = params
.get("preset")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing preset"))?;
let preset = params.get("preset").and_then(|v| v.as_str());
let graphics = params.get("graphics").and_then(|v| v.as_str());
if preset.is_none() && graphics.is_none() {
anyhow::bail!("Missing preset or graphics");
}
let conf = match preset {
// The conf carries two independent settings (display scale preset +
// graphics tier). A set of one must not clobber the other, so the
// half not being changed is carried over from the file as-is.
let existing = tokio::fs::read_to_string(KIOSK_DISPLAY_CONF)
.await
.unwrap_or_default();
let display_part = match preset {
// Resolution-derived default: 4K -> 2.0 (1920-wide layout),
// 1080p TV -> 1.5, laptop panels -> 1.0.
"auto" => String::new(),
Some("auto") => String::new(),
// Biggest UI: every panel targets a 1280-wide layout.
"large" => "ARCHIPELAGO_KIOSK_TARGET_CSS_WIDTH=1280\n".to_string(),
Some("large") => "ARCHIPELAGO_KIOSK_TARGET_CSS_WIDTH=1280\n".to_string(),
// Full-HD layout on any panel that can carry it.
"balanced" => "ARCHIPELAGO_KIOSK_TARGET_CSS_WIDTH=1920\n".to_string(),
Some("balanced") => "ARCHIPELAGO_KIOSK_TARGET_CSS_WIDTH=1920\n".to_string(),
// No scaling: native CSS viewport, most content, smallest UI.
"native" => "ARCHIPELAGO_KIOSK_SCALE=1\n".to_string(),
other => anyhow::bail!("Unknown display preset: {other}"),
Some("native") => "ARCHIPELAGO_KIOSK_SCALE=1\n".to_string(),
Some(other) => anyhow::bail!("Unknown display preset: {other}"),
None => existing
.lines()
.filter(|l| l.starts_with("ARCHIPELAGO_KIOSK_"))
.map(|l| format!("{l}\n"))
.collect(),
};
let graphics_part = match graphics {
// Auto: the launcher classifies the hardware itself (CPU/iGPU
// generation) — legacy boxes keep the choppy-audio-safe flags,
// modern iGPUs get GPU rasterization.
Some("auto") => String::new(),
// Force the conservative legacy flag set (troubleshooting).
Some("performance") => "KIOSK_GRAPHICS=performance\n".to_string(),
// Force the modern flag set even on unclassified hardware.
Some("quality") => "KIOSK_GRAPHICS=quality\n".to_string(),
Some(other) => anyhow::bail!("Unknown graphics mode: {other}"),
None => existing
.lines()
.find(|l| l.starts_with("KIOSK_GRAPHICS="))
.map(|l| format!("{l}\n"))
.unwrap_or_default(),
};
let conf = format!("{display_part}{graphics_part}");
host_sudo(&["/usr/bin/mkdir", "-p", "/etc/archipelago"]).await?;
if conf.is_empty() {
let _ = host_sudo(&["/usr/bin/rm", "-f", KIOSK_DISPLAY_CONF]).await;
@@ -1332,8 +1371,8 @@ impl RpcHandler {
])
.await;
info!(preset, "Kiosk display preset applied");
Ok(serde_json::json!({ "preset": preset, "applied": true }))
info!(?preset, ?graphics, "Kiosk display settings applied");
Ok(serde_json::json!({ "preset": preset, "graphics": graphics, "applied": true }))
}
}
+97 -1
View File
@@ -520,11 +520,63 @@ async fn proxy_to_app(
let client = hyper::Client::new();
match client.request(Request::from_parts(parts, body)).await {
Ok(resp) => resp,
Ok(mut resp) => {
neutralize_frame_blocking(resp.headers_mut());
resp
}
Err(_) => app_down_page(app),
}
}
/// Make gate-proxied app responses embeddable by the dashboard's My Apps
/// iframe. Apps that were never designed for framing ship
/// `X-Frame-Options: DENY` (Alby Hub) or a CSP `frame-ancestors` directive,
/// and either one makes the embedded app session a dead grey pane — the
/// historical workaround was a bespoke per-app nginx proxy (gitea), which is
/// exactly the per-app patching the manifest platform exists to delete.
///
/// Framing protection exists to stop a FOREIGN origin from framing an authed
/// page and clickjacking it. Behind the gate that threat model is already
/// handled the way the gate's own pages handle it: every proxied request is
/// authenticated by the gate first, and the gate's own responses declare
/// `frame-ancestors 'self' http://*:* https://*:*` (see `page()`) because the
/// dashboard is reached by LAN IP, mDNS name, and onion alike. Upstream
/// X-Frame-Options is dropped entirely; only the `frame-ancestors` directive
/// is removed from the app's CSP — the rest of the app's policy (script-src,
/// connect-src, …) is the app's business and passes through untouched.
fn neutralize_frame_blocking(headers: &mut hyper::HeaderMap) {
headers.remove("x-frame-options");
let Some(csp) = headers.get("content-security-policy") else {
return;
};
let Ok(raw) = csp.to_str() else {
return;
};
if !raw.to_ascii_lowercase().contains("frame-ancestors") {
return;
}
let kept: Vec<&str> = raw
.split(';')
.map(str::trim)
.filter(|d| !d.to_ascii_lowercase().starts_with("frame-ancestors") && !d.is_empty())
.collect();
if kept.is_empty() {
headers.remove("content-security-policy");
return;
}
match header::HeaderValue::from_str(&kept.join("; ")) {
Ok(v) => {
headers.insert("content-security-policy", v);
}
Err(_) => {
// Unrepresentable after filtering — fail open for framing but
// closed for the policy: better to drop a mangled CSP than to
// serve one we rewrote incorrectly.
headers.remove("content-security-policy");
}
}
}
/// Cookie names owned by the gate/daemon, never the app's to see.
const GATE_COOKIE_NAMES: &[&str] = &["session", "csrf_token"];
@@ -1072,6 +1124,50 @@ mod tests {
/// 2026-08-05). It must still be uncacheable, and still refuse to be
/// framed by a foreign origin, which `frame-ancestors` expresses and
/// `X-Frame-Options` cannot.
/// Upstream frame-blocking must not survive the proxy: X-Frame-Options
/// goes away entirely, CSP loses ONLY its frame-ancestors directive —
/// the app's remaining policy must pass through byte-preserving in
/// content (Alby Hub's DENY + strict CSP was the real-world case,
/// archi-dev-box 2026-08-12).
#[test]
fn proxied_responses_lose_frame_blocking_but_keep_the_apps_csp() {
let mut headers = hyper::HeaderMap::new();
headers.insert("x-frame-options", "DENY".parse().unwrap());
headers.insert(
"content-security-policy",
"default-src 'self'; frame-ancestors 'none'; img-src 'self' https://cdn.example"
.parse()
.unwrap(),
);
neutralize_frame_blocking(&mut headers);
assert!(!headers.contains_key("x-frame-options"));
let csp = headers["content-security-policy"].to_str().unwrap();
assert!(!csp.contains("frame-ancestors"));
assert!(csp.contains("default-src 'self'"));
assert!(csp.contains("img-src 'self' https://cdn.example"));
// CSP that is ONLY a frame-ancestors directive disappears entirely.
let mut only = hyper::HeaderMap::new();
only.insert(
"content-security-policy",
"frame-ancestors 'self'".parse().unwrap(),
);
neutralize_frame_blocking(&mut only);
assert!(!only.contains_key("content-security-policy"));
// No frame directives at all → CSP untouched.
let mut plain = hyper::HeaderMap::new();
plain.insert(
"content-security-policy",
"default-src 'self'".parse().unwrap(),
);
neutralize_frame_blocking(&mut plain);
assert_eq!(
plain["content-security-policy"].to_str().unwrap(),
"default-src 'self'"
);
}
#[test]
fn challenge_pages_are_uncacheable_and_framable_only_by_this_node() {
let resp = login_page(&app(), None, StatusCode::UNAUTHORIZED);
+98 -13
View File
@@ -130,7 +130,38 @@ app:
Additional extension keys may exist for current integrations, for example Bitcoin, Lightning, or app-specific launch/interface metadata. Treat extension keys as transitional unless they are documented as reusable platform primitives.
Use `metadata.launch.open_in_new_tab: true` when the app UI is known to reject iframe embedding with headers such as `X-Frame-Options` or restrictive CSP. The frontend app-session metadata is generated from this flag during release work.
### Iframe embedding — the rules
The dashboard opens apps in an **embedded frame** (My Apps → app session) by
default. Whether that works is decided by HTTP headers, not by wishes, so
know the mechanics:
- Browsers refuse to render a page in an iframe when the response carries
`X-Frame-Options: DENY`/`SAMEORIGIN` (the dashboard and the app are
different origins — different port at minimum) or a CSP `frame-ancestors`
directive that excludes the dashboard's origin.
- Many upstream apps ship exactly those headers (Alby Hub sends
`X-Frame-Options: DENY`). In a normal deployment that is correct hardening;
behind Archipelago's app gate the clickjacking threat those headers address
is already handled — every proxied request is authenticated by the gate
first.
- Therefore **the gate neutralizes frame blocking on proxied responses**: it
removes `X-Frame-Options` and strips only the `frame-ancestors` directive
from the app's CSP. The rest of the app's CSP (script-src, connect-src, …)
passes through untouched — the gate never weakens the app's own content
policy, only its framing policy. You do not need a bespoke reverse proxy,
header patches, or app config to be embeddable.
Set `metadata.launch.open_in_new_tab: true` only when embedding is broken by
things headers can't fix — the app frame-busts in JavaScript, requires being
the top-level origin (OAuth redirect flows, WebAuthn), or sets
`SameSite=Strict` session cookies that never accompany framed requests. Test
in the real embedded app session, **not** a plain browser tab: tabs don't
enforce framing headers, so a tab proves nothing about the iframe.
(Historical note: before the gate handled this, embeddable-but-blocking apps
each carried a hand-built nginx strip proxy — gitea's port-3000 proxy is the
surviving example. Do not copy that pattern for new apps.)
### Launch Interfaces
@@ -406,19 +437,73 @@ curl http://localhost:8180/health
podman logs my-app
```
### On an Archipelago Node
### On an Archipelago Node (before your app is in the catalog)
1. Install via the marketplace UI or RPC:
```bash
curl -b cookies.txt -X POST http://archipelago.local/rpc/v1 \
-d '{"method":"package.install","params":{"id":"my-app","dockerImage":"docker.io/myorg/my-app:1.0.0"}}'
```
2. Verify the container is running:
```bash
curl -b cookies.txt -X POST http://archipelago.local/rpc/v1 \
-d '{"method":"container-list"}'
```
3. Check the UI. The app's detail page is `http://archipelago.local/dashboard/apps/my-app`; the embedded launch surface is `http://archipelago.local/dashboard/app-session/my-app`
The App Store lists **signed-catalog apps and Nostr-discovered apps only**
a manifest on the node's disk never appears in the store by itself. That is
deliberate: the store is a trust surface. But the orchestrator installs from
disk manifests just fine, so you can test the complete install/run/uninstall
lifecycle on your own node before your app is published anywhere.
**1. Stage the manifest where it survives reboots.**
`/opt/archipelago/apps/` is *rebuilt on every backend start* from the runtime
payload that ships inside the frontend bundle
(`/opt/archipelago/web-ui/archipelago-runtime/apps/`). If you copy your
manifest only into `/opt/archipelago/apps/`, the next restart silently deletes
it. Stage into the payload directory instead — the boot sync then promotes it
for you:
```bash
sudo mkdir -p /opt/archipelago/web-ui/archipelago-runtime/apps/my-app
sudo cp apps/my-app/manifest.yml /opt/archipelago/web-ui/archipelago-runtime/apps/my-app/
sudo systemctl restart archipelago # manifests are loaded at startup
```
Watch `journalctl -u archipelago` after the restart — the orchestrator
validates every manifest on load and tells you about problems immediately
(for example a host-port collision with another installed app).
**2. Install over JSON-RPC.**
The repo ships the same session helper the release lifecycle gate uses.
Three things to know before using it: it needs `jq`; it reuses a cached
session from `/tmp/archy-rpc-session-<uid>` unless `ARCHY_FORCE_LOGIN=1` is
set (a stale cache fails every call quietly); and it sets `set -euo pipefail`,
so run it inside a script or subshell — sourcing it into your interactive
shell makes the first failed step kill the whole chain without printing
anything.
```bash
bash <<'EOF'
export ARCHY_PASSWORD='<your dashboard password>' ARCHY_FORCE_LOGIN=1
# Stock nodes serve HTTPS on 443; dev boxes behind plain nginx use:
# export ARCHY_HOST=127.0.0.1 ARCHY_SCHEME=http
source tests/lifecycle/lib/rpc.bash
rpc_login && echo "login ok"
# Both fields are required: `dockerImage` is normally supplied by the App
# Store from the signed catalog — pre-catalog, you pass your manifest's
# image yourself (it must match, and must come from a trusted registry).
rpc_call package.install '{"id":"my-app","dockerImage":"docker.io/myorg/my-app:1.0.0"}'
EOF
```
**3. Verify the lifecycle, not just the install:**
```bash
rpc_call package.status '{"id":"my-app"}' # state + health (run inside the same subshell pattern)
podman ps --filter name=my-app # container is up
rpc_call package.stop '{"id":"my-app"}' # …and start, restart
sudo systemctl restart archipelago # app must survive this
rpc_call package.uninstall '{"id":"my-app","preserve_data":true}'
rpc_call package.install '{"id":"my-app"}' # data still there?
```
The app's detail page is `https://<node>/dashboard/apps/my-app`; a gated web
UI is reachable through the app gate on its manifest port once running.
Only after this loop is green does the app belong in a catalog submission —
catalog inclusion is what makes it appear in the App Store.
### Validate Manifest
@@ -172,23 +172,70 @@ xset s noblank 2>/dev/null || true
pkill -u archipelago -f 'chromium.*localhost' 2>/dev/null || true
sleep 1
# GPU vs headless (#36, choppy-audio incident 2026-06-28). --enable-gpu-rasterization
# spins a dedicated GPU process at 55-92% CPU even on real GPU hardware (Intel HD 5500)
# because under X11 it falls back to software compositing anyway — that CPU
# starvation is what caused choppy HDMI audio. --in-process-gpu avoids the
# separate process; GpuRasterization is also disabled via --disable-features below.
# On a GPU-less / headless server (no /dev/dri), disable GPU entirely instead.
# ── Graphics tier ────────────────────────────────────────────────────────
# One flag set does not fit all kiosk hardware. The June-2026 choppy-audio
# incident (#36) proved HD 5500-era boxes melt with GPU rasterization on
# (under X11 they fall back to software compositing while a GPU process
# burns 55-92% CPU) — but holding MODERN iGPUs to those same defensive
# flags (single raster thread, raster ban) froze framework-pt outright on
# the animated network map at 4K (2026-08-13). So: two tiers.
#
# legacy — the proven HD 5500 tuning: --in-process-gpu, ONE raster
# thread, GpuRasterization banned via --disable-features.
# modern — --in-process-gpu, Chromium's default raster threads,
# GpuRasterization allowed.
#
# KIOSK_GRAPHICS in /etc/archipelago/kiosk-display.conf overrides:
# auto (default) | performance (force legacy) | quality (force modern)
# Set from Settings → Display; sourced with the rest of the conf above.
#
# Auto classifies by CPU model string — transparent and greppable, and the
# iGPU generation tracks the CPU generation on this hardware. Rules:
# * "NNth Gen Intel" → only stamped on gen 10+ model names → modern
# * AMD Ryzen → modern
# * Intel iN-NNNNN (5 dig)→ gen 10+ desktop → modern
# * Intel iN-8xxx/9xxx → gen 8/9 → modern
# * anything else → legacy (fail conservative: slow-but-stable)
detect_graphics_tier() {
case "${KIOSK_GRAPHICS:-auto}" in
performance) echo legacy; return ;;
quality) echo modern; return ;;
esac
_cpu=$(grep -m1 '^model name' /proc/cpuinfo 2>/dev/null || true)
case "$_cpu" in
*"Gen Intel"*) echo modern; return ;;
*AMD*Ryzen*) echo modern; return ;;
esac
_num=$(printf '%s' "$_cpu" | grep -oE 'i[3579]-[0-9]{4,5}' | head -1 | cut -d- -f2)
case "$_num" in
[0-9][0-9][0-9][0-9][0-9]) echo modern; return ;; # 5 digits = gen 10+
[89][0-9][0-9][0-9]) echo modern; return ;; # gen 8/9
esac
echo legacy
}
if [ -e /dev/dri/card0 ] || [ -e /dev/dri/renderD128 ]; then
GPU_FLAGS="--in-process-gpu --num-raster-threads=1"
# Hardware VIDEO DECODE (VA-API) on GPU hardware. Orthogonal to the
# GpuRasterization ban above: decode offload REDUCES the CPU pressure
# that caused the choppy-audio incident, it doesn't re-create it.
# Falls back silently to software decode when the platform lacks a
# va driver — never a black player. IgnoreDriverChecks: older Intel
# gens (HD 5500-era kiosks) are wrongly blocklisted upstream.
GRAPHICS_TIER=$(detect_graphics_tier)
if [ "$GRAPHICS_TIER" = "modern" ]; then
GPU_FLAGS="--in-process-gpu"
EXTRA_DISABLED_FEATURES=""
else
GPU_FLAGS="--in-process-gpu --num-raster-threads=1"
EXTRA_DISABLED_FEATURES=",GpuRasterization"
fi
# Hardware VIDEO DECODE (VA-API) on GPU hardware — both tiers. Decode
# offload REDUCES the CPU pressure that caused the choppy-audio
# incident, it doesn't re-create it. Falls back silently to software
# decode when the platform lacks a va driver — never a black player.
# IgnoreDriverChecks: older Intel gens (HD 5500-era kiosks) are wrongly
# blocklisted upstream.
ENABLE_FEATURES="OverlayScrollbar,VaapiVideoDecodeLinuxGL,VaapiIgnoreDriverChecks"
echo "archipelago-kiosk: graphics tier=$GRAPHICS_TIER (KIOSK_GRAPHICS=${KIOSK_GRAPHICS:-auto})"
else
# GPU-less / headless server (no /dev/dri): no GPU at all.
GRAPHICS_TIER=headless
GPU_FLAGS="--disable-gpu --num-raster-threads=1"
EXTRA_DISABLED_FEATURES=",GpuRasterization"
ENABLE_FEATURES="OverlayScrollbar"
fi
@@ -224,7 +271,7 @@ while true; do
--disable-translate \
--no-first-run \
--check-for-update-interval=31536000 \
--disable-features=TranslateUI,MetricsReporting,AutofillServerCommunication,PasswordManagerEnabled,GpuRasterization \
--disable-features=TranslateUI,MetricsReporting,AutofillServerCommunication,PasswordManagerEnabled${EXTRA_DISABLED_FEATURES} \
--enable-features=$ENABLE_FEATURES \
--disable-session-crashed-bubble \
--disable-save-password-bubble \
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "neode-ui",
"version": "1.8.0-alpha",
"version": "1.8.1-alpha",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "neode-ui",
"version": "1.8.0-alpha",
"version": "1.8.1-alpha",
"dependencies": {
"@scure/bip39": "^2.2.0",
"@types/dompurify": "^3.0.5",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "neode-ui",
"private": true,
"version": "1.8.0-alpha",
"version": "1.8.1-alpha",
"type": "module",
"scripts": {
"start": "./start-dev.sh",
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8000 8000">
<path d="M1404.94034,2404.75143c.19948.39893.416.79745.63896,1.19934-.21965-.3998-.4293-.7996-.63896-1.19934ZM1398.36987,2392.55825c.01.02001.01999.03996.01999.04996.01999.02001.03998.04996.04998.06997-.01999-.03996-.04998-.07997-.06997-.11993ZM7957.22848,6407.80899c-75.3022-453.82005-341.31246-1051.68618-631.76608-1536.83905-217.58172,280.41299-443.70125,543.12568-695.66263,796.78364,151.96083,272.22676,381.52012,831.33939,371.78586,1131.07073-3.34955,103.13742-84.59871,186.38404-187.48323,193.03587-323.69068,20.92769-761.15088-169.80941-1126.96417-352.01125-274.08918,250.36976-539.73074,478.97192-815.02811,690.20321,570.85548,338.56749,1305.54247,669.74202,1904.46573,668.08293,872.77622,46.26671,1378.68446-776.20339,1180.65263-1590.32607ZM5249.15884,5683.56479c1101.91285-1101.01171,2353.03599-2527.31198,2708.11113-4090.5697,253.93721-1273.80049-760.34667-1869.98142-1895.16006-1469.66567,211.65045,278.23428,364.22738,589.33979,457.73054,914.98744,86.80988-20.20216,196.93112-34.80914,289.99476-31.50156,103.33929,3.67279,186.93541,86.38156,191.60732,189.77353,13.96581,309.07109-235.73659,902.8025-402.1382,1193.8406,0,.01-.01.02001-.01.03001l-.04998.07991c-624.44753,1143.09071-1591.89389,2208.42396-2600.29743,3077.06633l-.22964-.19984c-263.64443,226.46298-526.16056,433.98687-804.2439,626.56893.39938.34966.79882.69956,1.19826,1.04946-509.31622,353.04862-1419.46673,925.33606-2012.13405,893.44541-103.82534-5.58671-186.23795-90.1864-188.67838-194.22674-6.97909-297.53161,215.6413-843.71786,363.83869-1111.11806-237.05325-259.94423-464.71032-529.69327-686.31643-814.38364-1228.33988,1900.32534-772.83666,3911.20697,1772.96127,2814.86279,1004.95316-454.63593,1968.00538-1208.05697,2803.81609-2000.0392ZM2528.2177,4000.38756c-468.17105-551.20508-806.89093-1020.88409-1125.16461-1599.13417h-.01c-.00197-.02709-.02111-.04517-.02999-.07997h-.01c-.00225-.03154-.04191-.05163-.03986-.08992,0,0-.01,0-.01-.01-1.46642-2.7001-3.08021-5.72568-4.5634-8.46529.01999.02001.03998.04996.04998.06997-165.01013-287.23702-415.64692-882.63022-403.53149-1193.46936,4.07951-104.66591,88.1281-188.31126,192.71288-192.04153,92.1143-3.28547,200.69379,10.46969,286.62767,30.61985,93.58312-325.69743,246.26999-636.85321,458.06014-915.09749C797.66386-275.84563-214.1867,319.25478,39.32792,1592.99819c208.75964,1126.24494,1140.55524,2354.14531,1865.30175,3206.20191-1.24817,1.76903,1.23818-1.76903,0,0,177.91975,202.67638,363.17881,406.52227,558.17368,604.08153,267.29912-191.50259,530.82373-396.10806,795.10723-617.7438-259.10108-257.46588-503.73334-520.6384-729.69288-785.15026ZM1420.59738,2433.86513c.84886,1.56913,1.71746,3.17821,2.5963,4.82725-.85885-1.58908-1.72745-3.19816-2.5963-4.82725ZM1457.40366,2502.26682c.92857,1.699,1.83716,3.39806,2.73599,5.07711-.89883-1.64904-1.80742-3.3481-2.73599-5.07711ZM1403.01311,2401.17342c-.01-.01-.01987-.02995-.02986-.03996.01.01.01987.02995.01987.03996.01999.03001.02999.04996.03998.06997-.01-.02001-.01999-.03996-.02999-.06997ZM1404.94034,2404.75143c.19948.39893.416.79745.63896,1.19934-.21965-.3998-.4293-.7996-.63896-1.19934Z" fill="#ffe480"/>
<path d="M2762.56314,2987.43935c-683.41752-683.42045-683.41752-1791.46134,0-2474.87781,683.41752-683.41755,1791.45841-683.41755,2474.87885,0,683.41068,683.41648,683.41068,1791.45737,0,2474.87781l-1237.44089,1237.44089-1237.43796-1237.44089Z" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 94 94" id="vector" xmlns="http://www.w3.org/2000/svg">
<g id="group" transform="matrix(1, 0, 0, 1, -6.999915, -7)">
<path id="path" d="M 30 81 C 23 80 21.63 71.49 29.16 45.87 C 35 26 46.18 26 58 26 C 73.17 26 81.61 29.18 83.79 35.73 C 84.243 36.883 84.39 38.135 84.214 39.362 C 84.039 40.589 83.548 41.75 82.79 42.73 C 82.613 42.96 82.419 43.178 82.21 43.38 C 85.1 45.31 86.88 48.02 86.52 51.9 C 86.13 56.26 83.16 58.55 78.52 59.66 C 78.849 60.396 79.013 61.194 79 62 C 78.988 63.199 78.695 64.378 78.147 65.444 C 77.598 66.51 76.808 67.433 75.84 68.14 C 70.09 72.76 60 75 46 73 C 43 72.57 41 75 39 77 C 36.62 79.38 34.24 81.61 30 81 Z" fill="#25A5FF"/>
<path id="path_1" d="M 58 30 C 66 30 78 31 80 37 C 82.24 43.71 69 43 59 42 C 68 43 83.18 44 82.5 51.5 C 82 57 72 57 55 56 C 63 57 75 58 75 62 C 75 68.28 58.74 71 47.67 69.31 C 39 68 38.06 76.77 31 75 C 27 74 28 63 33 47 C 38.29 30.09 46.49 30 58 30 Z" fill="#ffffff" stroke="#25A5FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+26 -2
View File
@@ -374,7 +374,7 @@
"id": "pine",
"title": "Pine",
"version": "1.3.0",
"description": "A private voice assistant for your home. Pine runs speech-to-text (Whisper), text-to-speech (Piper) and wake-word detection (openWakeWord) on your own node and pairs with a PineVoice satellite speaker, so Home Assistant Assist works locally with nothing sent to the cloud. Ask it about your node block height, sync, peers, Lightning balance and, when a Claude API key is set, anything else.",
"description": "A private voice assistant for your home. Pine runs speech-to-text (Whisper), text-to-speech (Piper) and wake-word detection (openWakeWord) on your own node and pairs with a PineVoice satellite speaker, so Home Assistant Assist works locally with nothing sent to the cloud. Ask it about your node \u2014 block height, sync, peers, Lightning balance \u2014 and, when a Claude API key is set, anything else.",
"icon": "/assets/img/app-icons/pine.svg",
"author": "Archipelago",
"category": "home",
@@ -459,7 +459,7 @@
"id": "netbird",
"title": "NetBird",
"version": "2.38.0",
"description": "Self-hosted WireGuard mesh VPN control plane with dashboard, embedded identity provider, management API, signal, relay, and STUN. The user-facing entry point a TLS proxy in front of the dashboard + server.",
"description": "Self-hosted WireGuard mesh VPN control plane with dashboard, embedded identity provider, management API, signal, relay, and STUN. The user-facing entry point \u2014 a TLS proxy in front of the dashboard + server.",
"icon": "/assets/img/app-icons/netbird.svg",
"author": "NetBird",
"category": "networking",
@@ -547,6 +547,30 @@
"/var/lib/archipelago/nextcloud:/var/www/html"
]
}
},
{
"id": "alby-hub",
"title": "Alby Hub",
"version": "1.23.0",
"description": "Self-custodial Lightning wallet hub. Runs its own Lightning node on your Archipelago and connects your apps to it over Nostr Wallet Connect \u2014 one hub, every app pays through it.",
"icon": "/assets/img/app-icons/alby-hub.svg",
"author": "Alby",
"category": "money",
"tier": "optional",
"dockerImage": "source.archipelago-foundation.org/lfg2025/alby-hub:v1.23.0",
"repoUrl": "https://github.com/getAlby/hub"
},
{
"id": "phoenixd",
"title": "phoenixd",
"version": "0.9.0",
"description": "Headless Lightning daemon by ACINQ (the Phoenix wallet team). No screen of its own \u2014 it exposes a small local API that other apps and tools use to send and receive Lightning payments. Channel liquidity is managed automatically for a fee.",
"icon": "/assets/img/app-icons/phoenixd.svg",
"author": "ACINQ",
"category": "money",
"tier": "optional",
"dockerImage": "source.archipelago-foundation.org/lfg2025/phoenixd:0.9.0",
"repoUrl": "https://github.com/ACINQ/phoenixd"
}
]
}
+34
View File
@@ -1,5 +1,39 @@
<template>
<div id="app">
<!-- KAMMERGUT GLOSS TEST (2026-08-12): SVG paint filter lifted verbatim
from plan-b's Kammergut wordmark (#paintGloss). Consumed by the
.logo-gradient-border::after override in style.css. Revert by
deleting this svg block + that css block (one commit). -->
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
<defs>
<filter id="paintGloss" x="-12%" y="-30%" width="124%" height="160%" color-interpolation-filters="sRGB">
<feMorphology in="SourceAlpha" operator="dilate" radius="2.5" result="dilated"/>
<feGaussianBlur in="dilated" stdDeviation="6" result="puddle"/>
<feFlood flood-color="#fff4dc" flood-opacity="0.85" result="puddleColor"/>
<feComposite in="puddleColor" in2="puddle" operator="in" result="puddleHi"/>
<feFlood flood-color="#1A1A18" flood-opacity="0.18" result="puddleShadowColor"/>
<feComposite in="puddleShadowColor" in2="puddle" operator="in" result="puddleShadow"/>
<feOffset in="puddleShadow" dx="0" dy="3" result="puddleShadowOff"/>
<feGaussianBlur in="SourceAlpha" stdDeviation="1.4" result="bump"/>
<feSpecularLighting in="bump" surfaceScale="4" specularConstant="0.9"
specularExponent="40" lighting-color="#fff2d4" result="spec2">
<fePointLight x="600" y="-200" z="380"/>
</feSpecularLighting>
<feComposite in="spec2" in2="SourceAlpha" operator="in" result="specClip2"/>
<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="dsBlur"/>
<feOffset in="dsBlur" dx="0" dy="6" result="dsOff"/>
<feComponentTransfer in="dsOff" result="dsFinal">
<feFuncA type="linear" slope="0.45"/>
</feComponentTransfer>
<feMerge>
<feMergeNode in="dsFinal"/>
<feMergeNode in="SourceGraphic"/>
<feMergeNode in="specClip2"/>
</feMerge>
</filter>
</defs>
</svg>
<!-- Splash Screen (only on first visit) -->
<SplashScreen v-if="showSplash" @complete="handleSplashComplete" />
@@ -853,7 +853,25 @@ function render() {
}
}
/** Kiosk TVs run the kiosk image's conservative rasterizer (single raster
* thread, GPU raster off the June choppy-audio tuning), so a 60fps
* SVG-attribute animation at 4K froze the browser outright (framework-pt,
* 2026-08-12). On kiosks the ticker renders every OTHER frame (~30fps at a
* 60Hz panel) with the skipped frame's delta carried over so spin speed is
* unchanged at couch distance the half rate is invisible, the paint cost
* halves. */
const kioskLowPower =
typeof document !== 'undefined' && document.documentElement.classList.contains('kiosk-mode')
let lowPowerPhase = 0
let carriedDeltaMS = 0
function tick(_time: number, deltaMS: number) {
if (kioskLowPower) {
carriedDeltaMS += deltaMS
if ((lowPowerPhase++ & 1) === 1) return
deltaMS = carriedDeltaMS
carriedDeltaMS = 0
}
elapsed += deltaMS / 1000
if (!dragging) cam.rotY += cam.spin * (deltaMS / 1000)
render()
@@ -1169,11 +1187,14 @@ function initialBuild() {
applyGraph()
// First measurement decides the default projection: saved preference wins,
// otherwise portrait containers (phone/companion) open in the flat 2D view.
// otherwise portrait containers (phone/companion) AND kiosk TVs open in the
// flat 2D view the depth view stays one tap away and is remembered. The
// kiosk default exists for the same reason as the half-rate ticker above.
modeInitialized = true
let saved: string | null = null
try { saved = localStorage.getItem('federation-map-projection') } catch { /* private mode */ }
viewMode.value = saved === '2d' || saved === '3d' ? saved : (height > width ? '2d' : '3d')
viewMode.value =
saved === '2d' || saved === '3d' ? saved : (kioskLowPower || height > width ? '2d' : '3d')
applyMode(viewMode.value, false)
if (staticMode) {
+10
View File
@@ -1128,6 +1128,16 @@ html.controller-nav [data-controller-container]:focus {
z-index: 0;
}
/* KAMMERGUT GLOSS TEST (2026-08-12) black gloss paint on the badge's
inner circle, lifted from plan-b's Kammergut wordmark (.paint-3d
gradient + #paintGloss filter, defs injected in App.vue). Revert:
delete this block + the svg defs block in App.vue (one commit). */
.logo-gradient-border::after {
background: linear-gradient(180deg, #2a2a26 0%, #1a1a18 45%, #060604 100%);
filter: url(#paintGloss);
-webkit-filter: url(#paintGloss);
}
.logo-gradient-border img,
.logo-gradient-border svg {
border-radius: 9999px;
@@ -362,6 +362,18 @@ init()
</button>
</div>
<div class="overflow-y-auto flex-1 min-h-0 space-y-6 pr-1">
<!-- v1.8.1-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.8.1-alpha</span>
<span class="text-xs text-white/40">August 13, 2026</span>
</div>
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
<p>**Apps that refused to open inside the dashboard now embed like everything else.** Some apps ship browser headers that forbid being shown inside another page correct hardening on the open web, but inside Archipelago it produced a dead grey pane when you opened them from My Apps (Alby Hub was the first to hit it). The app gate, which already checks your login on every request to an app, now removes just those framing headers on the way through; each app's own content-security rules pass through untouched. No more per-app proxy workarounds.</p>
<p>**The network map no longer freezes kiosk TVs.** The animated federation map at 4K was too much for the deliberately conservative graphics settings the on-screen display used on every machine settings chosen years back to stop audio crackle on much older hardware. Two fixes: on kiosk screens the map now opens in its flat 2D view (the 3D globe is one tap away, and remembered) and animates at half rate invisible from the couch, half the work. And the display itself now recognizes what machine it runs on: older kiosk boxes keep the proven careful settings, modern ones finally get real GPU rendering.</p>
<p>**New Settings Display Graphics choice for the on-screen display.** Auto (recommended) picks the right rendering mode for the machine by itself; Compatibility forces the most conservative mode if a screen ever stutters, tears, or crackles; Quality forces full GPU rendering on hardware the automatic detection doesn't recognize. Changing it restarts the on-screen display, like the size presets.</p>
</div>
</div>
<!-- v1.8.0-alpha -->
<div>
<div class="flex items-center gap-2 mb-3">
@@ -6,6 +6,7 @@ import { rpcClient } from '@/api/rpc-client'
// have a kiosk display (has_kiosk from the backend).
const hasKiosk = ref(false)
const preset = ref('auto')
const graphics = ref('auto')
const applying = ref(false)
const error = ref('')
@@ -32,11 +33,35 @@ const presets = [
},
]
// Graphics tier: which browser rendering flags the on-screen display runs
// with. Auto classifies the hardware (older kiosk boxes keep the proven
// conservative flags; modern chips get GPU rendering); the two overrides
// exist for troubleshooting and unclassified hardware.
const graphicsModes = [
{
id: 'auto',
label: 'Auto (recommended)',
description: 'Detect this machines graphics hardware and pick the right rendering mode for it.',
},
{
id: 'performance',
label: 'Compatibility',
description: 'Most conservative rendering — use if the screen stutters, tears, or the audio crackles.',
},
{
id: 'quality',
label: 'Quality',
description: 'Full GPU rendering — smoothest animations on capable hardware. If unsure, use Auto.',
},
]
onMounted(async () => {
try {
const res = await rpcClient.call<{ has_kiosk: boolean; preset: string }>({ method: 'system.kiosk-display.get' })
const res = await rpcClient.call<{ has_kiosk: boolean; preset: string; graphics?: string }>({ method: 'system.kiosk-display.get' })
hasKiosk.value = res.has_kiosk
preset.value = res.preset
// Older backend without the graphics field: hide nothing, default Auto.
graphics.value = res.graphics ?? 'auto'
} catch { /* backend without the RPC — leave the section hidden */ }
})
@@ -55,6 +80,22 @@ async function apply(id: string) {
applying.value = false
}
}
async function applyGraphics(id: string) {
if (applying.value || id === graphics.value) return
applying.value = true
error.value = ''
const prev = graphics.value
graphics.value = id
try {
await rpcClient.call({ method: 'system.kiosk-display.set', params: { graphics: id }, timeout: 20000 })
} catch (e: unknown) {
graphics.value = prev
error.value = e instanceof Error ? e.message : 'Failed to apply graphics setting'
} finally {
applying.value = false
}
}
</script>
<template>
@@ -77,6 +118,25 @@ async function apply(id: string) {
<p class="text-sm text-white/60">{{ p.description }}</p>
</button>
</div>
<h3 class="text-lg font-semibold text-white/96 mt-8 mb-2">Graphics</h3>
<p class="text-sm text-white/60 mb-6">
How the on-screen display uses this machine&rsquo;s graphics hardware. Changing this restarts the on-screen display.
</p>
<div data-controller-container tabindex="0" class="grid grid-cols-1 md:grid-cols-3 gap-4">
<button
v-for="g in graphicsModes"
:key="g.id"
:disabled="applying"
@click="applyGraphics(g.id)"
class="path-option-card text-left p-5 disabled:opacity-60"
:class="{ 'path-option-card--selected': graphics === g.id }"
>
<div class="font-medium text-white/90 mb-1">{{ g.label }}</div>
<p class="text-sm text-white/60">{{ g.description }}</p>
</button>
</div>
<div v-if="error" class="mt-4 alert-error text-sm">{{ error }}</div>
</div>
</template>
+17 -21
View File
@@ -1,33 +1,29 @@
{
"changelog": [
"**Archipelago is now open source.** The full source code of the node you are running — the orchestrator, the dashboard, the app platform, the mesh, the release tooling — is published for anyone to read, build and audit at source.archipelago-foundation.org/lfg2025/archy. A node that holds your money, your files and your communications should not ask to be taken on faith: from this release onward you, or anyone you trust, can see exactly what it does and follow every change we make in the open.",
"**Installing an update is reliable again, and tells you what happened when it isn't.** Some nodes could download an update but never apply it — the button stayed on \"Install\", and no amount of retrying worked. The cause: applying the update consumed the downloaded files as it went, so if any one step hit a snag partway through, the leftover files were incomplete and every later attempt failed the safety re-check forever, needing a technician to recover. Applying no longer consumes the download — a failed apply can always be retried from the same files — and the pieces are now applied in a fixed order with the program itself last, so a hiccup can't leave a half-swapped node. When an apply does fail, the screen now shows the real reason and what to do (\"download the update again\"), and offers Download again instead of a dead \"Install\" button, rather than a generic \"it failed\".",
"**Video on the kiosk stops tearing.** The kiosk's display had no vertical sync at all, so fast motion — IndeedHub films especially — showed horizontal tearing lines. The display driver now syncs every frame to the panel (no extra hardware needed, existing kiosks pick it up with this update), and on machines with a GPU, video decoding moves off the CPU onto the video hardware — smoother playback that also leaves more headroom for audio, not less.",
"**The Back button finally does what you expect.** Pressing Back — the mouse's side button on a kiosk, a swipe on a phone, the toolbar button in any browser — used to navigate the screen underneath an open window, or leave the dashboard entirely. Back now closes the topmost open window first, one per press, exactly like a native app; closing a window yourself never leaves a phantom entry that makes you press Back twice.",
"**No more bare IP addresses in your update or app-registry settings.** The update mirrors and the app registry each listed the same server twice — once by its proper name, once as a raw `http://146…` address left over from before the domain existed. The raw-address entries are retired: new nodes never see them, and existing nodes clean them out of their saved lists automatically on the next read. Everything now goes through the named, TLS-protected origin — which was always the same machine.",
"**The phone companion app downloads over the proper domain.** The download QR pointed at a raw address over plain HTTP; it now points at the same file on the https domain. Scanning it gets you an encrypted download from a named server.",
"**The Receive window now tells you when the money is on its way.** Previously it showed a QR code and left you to check elsewhere whether anything happened. Now, the moment the sender's transaction is broadcast, the QR gives way to a clock: the amount, the transaction ID (tap to copy), and a note that the funds arrive on their own — with a single Done button. If you keep the window open, the clock becomes a green check at the first confirmation. Verified live on a real node: payment detected within seconds of broadcast."
"**Apps that refused to open inside the dashboard now embed like everything else.** Some apps ship browser headers that forbid being shown inside another page — correct hardening on the open web, but inside Archipelago it produced a dead grey pane when you opened them from My Apps (Alby Hub was the first to hit it). The app gate, which already checks your login on every request to an app, now removes just those framing headers on the way through; each app's own content-security rules pass through untouched. No more per-app proxy workarounds.",
"**The network map no longer freezes kiosk TVs.** The animated federation map at 4K was too much for the deliberately conservative graphics settings the on-screen display used on every machine — settings chosen years back to stop audio crackle on much older hardware. Two fixes: on kiosk screens the map now opens in its flat 2D view (the 3D globe is one tap away, and remembered) and animates at half rate — invisible from the couch, half the work. And the display itself now recognizes what machine it runs on: older kiosk boxes keep the proven careful settings, modern ones finally get real GPU rendering.",
"**New Settings → Display → Graphics choice for the on-screen display.** Auto (recommended) picks the right rendering mode for the machine by itself; Compatibility forces the most conservative mode if a screen ever stutters, tears, or crackles; Quality forces full GPU rendering on hardware the automatic detection doesn't recognize. Changing it restarts the on-screen display, like the size presets."
],
"components": [
{
"current_version": "1.8.0-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.0-alpha/archipelago",
"current_version": "1.8.1-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.1-alpha/archipelago",
"name": "archipelago",
"new_version": "1.8.0-alpha",
"sha256": "fd99219ea11ffebc92b83154e1058911ebe72c357c80085310c78aba9714a6b5",
"size_bytes": 59369392
"new_version": "1.8.1-alpha",
"sha256": "50fe97705717f8176afc03285a3a459b1d4e219dba0442aa4ee5b8cea8e3ec30",
"size_bytes": 59896032
},
{
"current_version": "1.8.0-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.0-alpha/archipelago-frontend-1.8.0-alpha.tar.gz",
"name": "archipelago-frontend-1.8.0-alpha.tar.gz",
"new_version": "1.8.0-alpha",
"sha256": "f6bbf7b3f78a00dd7051abef805c943cfa9a58f624e09e94b6bee6c9b2f22902",
"size_bytes": 97608790
"current_version": "1.8.1-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.1-alpha/archipelago-frontend-1.8.1-alpha.tar.gz",
"name": "archipelago-frontend-1.8.1-alpha.tar.gz",
"new_version": "1.8.1-alpha",
"sha256": "ae77c97e8f1ec7d1044f7d565319ddc503f6f5b87dbc9a524fa9cc9639b04f29",
"size_bytes": 97611693
}
],
"release_date": "2026-08-12",
"signature": "be3e3c6f8ed6b8d573a6329bf546ec1aa739214cd391272ce3b2f0a0ef6b10a4a4c4f9dfa4b9b32dad88326c9feff908fbdb3d1b86d055e5cf876f67f195c60b",
"release_date": "2026-08-13",
"signature": "7a8932503d1c33156f79d11671bb275a2bb63347988da02562c3dd138287e7ed77c7d4cb0a1f6bcbcf8665da506fafedd2d81bb844de0cbcbaf2b9045210ca0b",
"signed_by": "did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT",
"version": "1.8.0-alpha"
"version": "1.8.1-alpha"
}
+258 -97
View File
@@ -1,7 +1,7 @@
{
"apps": {
"adguardhome": {
"image": "146.59.87.168:3000/lfg2025/adguardhome:v0.107.55",
"image": "source.archipelago-foundation.org/lfg2025/adguardhome:v0.107.55",
"version": "v0.107.55"
},
"aiui": {
@@ -51,6 +51,93 @@
},
"version": "0.1.0"
},
"alby-hub": {
"manifest": {
"app": {
"category": "money",
"container": {
"image": "source.archipelago-foundation.org/lfg2025/alby-hub:v1.23.0",
"pull_policy": "if-not-present"
},
"dependencies": [
{
"storage": "1Gi"
}
],
"description": "Self-custodial Lightning wallet hub. Runs its own Lightning node on your Archipelago and connects your apps to it over Nostr Wallet Connect — one hub, every app pays through it.",
"environment": [
"WORK_DIR=/data",
"PORT=8080",
"LOG_LEVEL=info"
],
"health_check": {
"endpoint": "http://localhost:8080",
"interval": "30s",
"path": "/",
"retries": 5,
"timeout": "5s",
"type": "http"
},
"id": "alby-hub",
"interfaces": {
"main": {
"description": "Alby Hub wallet interface",
"name": "Web UI",
"port": 8187,
"protocol": "http",
"type": "ui"
}
},
"metadata": {
"features": [
"Self-custodial Lightning node (LDK) with a friendly wallet UI",
"Connect wallets and apps via Nostr Wallet Connect (NWC)",
"Per-app budgets and isolated sub-wallets",
"Works with the Alby browser extension and mobile app"
],
"icon": "/assets/img/app-icons/alby-hub.svg",
"launch": {
"open_in_new_tab": false
},
"repo": "https://github.com/getAlby/hub",
"tier": "optional"
},
"name": "Alby Hub",
"ports": [
{
"auth": "gated",
"bind": "127.0.0.1",
"container": 8080,
"host": 8187,
"protocol": "tcp"
}
],
"resources": {
"cpu_limit": 1,
"disk_limit": "2Gi",
"memory_limit": "512Mi"
},
"security": {
"capabilities": [],
"network_policy": "bridge",
"no_new_privileges": true,
"readonly_root": true
},
"version": "1.23.0",
"volumes": [
{
"options": [
"rw"
],
"source": "/var/lib/archipelago/alby-hub",
"target": "/data",
"type": "bind"
}
]
}
},
"version": "1.23.0"
},
"archy-btcpay-db": {
"manifest": {
"app": {
@@ -60,7 +147,7 @@
},
"container": {
"data_uid": "100998:100998",
"image": "146.59.87.168:3000/lfg2025/postgres:15.17",
"image": "source.archipelago-foundation.org/lfg2025/postgres:15.17",
"network": "archy-net",
"pull_policy": "if-not-present",
"secret_env": [
@@ -129,7 +216,7 @@
},
"container": {
"data_uid": "100998:100998",
"image": "146.59.87.168:3000/lfg2025/mariadb:11.4.10",
"image": "source.archipelago-foundation.org/lfg2025/mariadb:11.4.10",
"network": "archy-net",
"pull_policy": "if-not-present",
"secret_env": [
@@ -201,7 +288,7 @@
"sync_required": false
},
"container": {
"image": "146.59.87.168:3000/lfg2025/mempool-frontend:v3.0.1",
"image": "source.archipelago-foundation.org/lfg2025/mempool-frontend:v3.0.1",
"network": "archy-net",
"pull_policy": "if-not-present"
},
@@ -257,7 +344,7 @@
"sync_required": true
},
"container": {
"image": "146.59.87.168:3000/lfg2025/nbxplorer:2.6.0",
"image": "source.archipelago-foundation.org/lfg2025/nbxplorer:2.6.0",
"network": "archy-net",
"pull_policy": "if-not-present",
"secret_env": [
@@ -347,7 +434,7 @@
"name": "barkd-secret"
}
],
"image": "146.59.87.168:3000/lfg2025/barkd:0.3.0",
"image": "source.archipelago-foundation.org/lfg2025/barkd:0.3.0",
"network": "archy-net",
"pull_policy": "if-not-present",
"secret_env": [
@@ -434,7 +521,7 @@
"sh",
"-lc"
],
"image": "146.59.87.168:3000/lfg2025/bitcoin:28.4",
"image": "source.archipelago-foundation.org/lfg2025/bitcoin:28.4",
"network": "archy-net",
"pull_policy": "if-not-present",
"secret_env": [
@@ -516,47 +603,47 @@
"versions": [
{
"default": true,
"image": "146.59.87.168:3000/lfg2025/bitcoin:latest",
"image": "source.archipelago-foundation.org/lfg2025/bitcoin:latest",
"version": "latest"
},
{
"image": "146.59.87.168:3000/lfg2025/bitcoin:31.0",
"image": "source.archipelago-foundation.org/lfg2025/bitcoin:31.0",
"version": "31.0"
},
{
"image": "146.59.87.168:3000/lfg2025/bitcoin:30.2",
"image": "source.archipelago-foundation.org/lfg2025/bitcoin:30.2",
"version": "30.2"
},
{
"image": "146.59.87.168:3000/lfg2025/bitcoin:29.3",
"image": "source.archipelago-foundation.org/lfg2025/bitcoin:29.3",
"version": "29.3"
},
{
"image": "146.59.87.168:3000/lfg2025/bitcoin:29.2",
"image": "source.archipelago-foundation.org/lfg2025/bitcoin:29.2",
"version": "29.2"
},
{
"image": "146.59.87.168:3000/lfg2025/bitcoin:28.4",
"image": "source.archipelago-foundation.org/lfg2025/bitcoin:28.4",
"version": "28.4.0"
},
{
"image": "146.59.87.168:3000/lfg2025/bitcoin:27.2",
"image": "source.archipelago-foundation.org/lfg2025/bitcoin:27.2",
"version": "27.2"
},
{
"deprecated": true,
"image": "146.59.87.168:3000/lfg2025/bitcoin:26.2",
"image": "source.archipelago-foundation.org/lfg2025/bitcoin:26.2",
"version": "26.2"
},
{
"deprecated": true,
"image": "146.59.87.168:3000/lfg2025/bitcoin:25.2",
"image": "source.archipelago-foundation.org/lfg2025/bitcoin:25.2",
"version": "25.2"
}
]
},
"bitcoin-knots": {
"image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260210",
"image": "source.archipelago-foundation.org/lfg2025/bitcoin-knots:29.3.knots20260210",
"manifest": {
"app": {
"bitcoin_integration": {
@@ -580,7 +667,7 @@
"sh",
"-lc"
],
"image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260210",
"image": "source.archipelago-foundation.org/lfg2025/bitcoin-knots:29.3.knots20260210",
"network": "archy-net",
"pull_policy": "if-not-present",
"secret_env": [
@@ -662,25 +749,25 @@
"versions": [
{
"default": true,
"image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260210",
"image": "source.archipelago-foundation.org/lfg2025/bitcoin-knots:29.3.knots20260210",
"version": "29.3.knots20260210"
},
{
"image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260508",
"image": "source.archipelago-foundation.org/lfg2025/bitcoin-knots:29.3.knots20260508",
"version": "29.3.knots20260508"
},
{
"image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.3.knots20260507",
"image": "source.archipelago-foundation.org/lfg2025/bitcoin-knots:29.3.knots20260507",
"version": "29.3.knots20260507"
},
{
"image": "146.59.87.168:3000/lfg2025/bitcoin-knots:29.2.knots20251110",
"image": "source.archipelago-foundation.org/lfg2025/bitcoin-knots:29.2.knots20251110",
"version": "29.2.knots20251110"
}
]
},
"bitcoin-ui": {
"image": "146.59.87.168:3000/lfg2025/bitcoin-ui:1.7.123-alpha",
"image": "source.archipelago-foundation.org/lfg2025/bitcoin-ui:1.7.123-alpha",
"manifest": {
"app": {
"container": {
@@ -751,7 +838,7 @@
"name": "botfights-jwt-secret"
}
],
"image": "146.59.87.168:3000/lfg2025/botfights:1.2.11",
"image": "source.archipelago-foundation.org/lfg2025/botfights:1.2.11",
"pull_policy": "always",
"secret_env": [
{
@@ -856,8 +943,8 @@
"btcpay": {
"image": "docker.io/btcpayserver/btcpayserver:2.4.2",
"images": {
"archy-btcpay-db": "146.59.87.168:3000/lfg2025/postgres:15.17",
"archy-nbxplorer": "146.59.87.168:3000/lfg2025/nbxplorer:2.6.0",
"archy-btcpay-db": "source.archipelago-foundation.org/lfg2025/postgres:15.17",
"archy-nbxplorer": "source.archipelago-foundation.org/lfg2025/nbxplorer:2.6.0",
"btcpay-server": "docker.io/btcpayserver/btcpayserver:2.4.2"
},
"version": "2.4.2"
@@ -1069,7 +1156,7 @@
"version": "23.08.2"
},
"cryptpad": {
"image": "146.59.87.168:3000/lfg2025/cryptpad:2024.12.0",
"image": "source.archipelago-foundation.org/lfg2025/cryptpad:2024.12.0",
"version": "2024.12.0"
},
"did-wallet": {
@@ -1143,7 +1230,7 @@
"version": "1.0.0"
},
"electrs-ui": {
"image": "146.59.87.168:3000/lfg2025/electrs-ui:1.7.123-alpha",
"image": "source.archipelago-foundation.org/lfg2025/electrs-ui:1.7.123-alpha",
"manifest": {
"app": {
"container": {
@@ -1190,7 +1277,7 @@
"version": "1.7.123-alpha"
},
"electrumx": {
"image": "146.59.87.168:3000/lfg2025/electrumx:v1.18.0",
"image": "source.archipelago-foundation.org/lfg2025/electrumx:v1.18.0",
"manifest": {
"app": {
"bitcoin_integration": {
@@ -1207,7 +1294,7 @@
"sh",
"-lc"
],
"image": "146.59.87.168:3000/lfg2025/electrumx:v1.18.0",
"image": "source.archipelago-foundation.org/lfg2025/electrumx:v1.18.0",
"network": "archy-net",
"pull_policy": "if-not-present",
"secret_env": [
@@ -1291,7 +1378,7 @@
"version": "v1.18.0"
},
"fedimint": {
"image": "146.59.87.168:3000/lfg2025/fedimintd:v0.10.0",
"image": "source.archipelago-foundation.org/lfg2025/fedimintd:v0.10.0",
"manifest": {
"app": {
"bitcoin_integration": {
@@ -1321,7 +1408,7 @@
"sh",
"-lc"
],
"image": "146.59.87.168:3000/lfg2025/fedimintd:v0.10.0",
"image": "source.archipelago-foundation.org/lfg2025/fedimintd:v0.10.0",
"network": "archy-net",
"pull_policy": "if-not-present",
"secret_env": [
@@ -1428,7 +1515,7 @@
"name": "fmcd-password"
}
],
"image": "146.59.87.168:3000/lfg2025/fmcd:0.8.1",
"image": "source.archipelago-foundation.org/lfg2025/fmcd:0.8.1",
"network": "archy-net",
"pull_policy": "if-not-present",
"secret_env": [
@@ -1500,7 +1587,7 @@
"version": "0.8.0"
},
"fedimint-gateway": {
"image": "146.59.87.168:3000/lfg2025/gatewayd:v0.10.0",
"image": "source.archipelago-foundation.org/lfg2025/gatewayd:v0.10.0",
"manifest": {
"app": {
"bitcoin_integration": {
@@ -1528,7 +1615,7 @@
"name": "fedimint-gateway-hash"
}
],
"image": "146.59.87.168:3000/lfg2025/gatewayd:v0.10.0",
"image": "source.archipelago-foundation.org/lfg2025/gatewayd:v0.10.0",
"network": "archy-net",
"pull_policy": "if-not-present",
"secret_env": [
@@ -1616,7 +1703,7 @@
"version": "v0.10.0"
},
"filebrowser": {
"image": "146.59.87.168:3000/lfg2025/filebrowser:v2.27.0",
"image": "source.archipelago-foundation.org/lfg2025/filebrowser:v2.27.0",
"manifest": {
"app": {
"bitcoin_integration": {
@@ -1629,7 +1716,7 @@
"/data/.filebrowser.json"
],
"data_uid": "100000:100000",
"image": "146.59.87.168:3000/lfg2025/filebrowser:v2.27.0",
"image": "source.archipelago-foundation.org/lfg2025/filebrowser:v2.27.0",
"network": "archy-net",
"pull_policy": "if-not-present"
},
@@ -1699,7 +1786,7 @@
"version": "v2.27.0"
},
"fips": {
"image": "146.59.87.168:3000/lfg2025/fips:v0.1.0",
"image": "source.archipelago-foundation.org/lfg2025/fips:v0.1.0",
"version": "v0.1.0"
},
"fips-ui": {
@@ -1870,12 +1957,12 @@
"version": "1.23"
},
"grafana": {
"image": "146.59.87.168:3000/lfg2025/grafana:10.2.0",
"image": "source.archipelago-foundation.org/lfg2025/grafana:10.2.0",
"manifest": {
"app": {
"container": {
"data_uid": "472:472",
"image": "grafana/grafana:10.2.0",
"image": "source.archipelago-foundation.org/lfg2025/grafana:10.2.0",
"image_signature": "cosign://...",
"pull_policy": "if-not-present"
},
@@ -1945,11 +2032,11 @@
"version": "10.2.0"
},
"homeassistant": {
"image": "146.59.87.168:3000/lfg2025/home-assistant:2026.7.3",
"image": "source.archipelago-foundation.org/lfg2025/home-assistant:2026.7.3",
"manifest": {
"app": {
"container": {
"image": "146.59.87.168:3000/lfg2025/home-assistant:2026.7.3",
"image": "source.archipelago-foundation.org/lfg2025/home-assistant:2026.7.3",
"network": "pasta",
"pull_policy": "if-not-present"
},
@@ -2038,16 +2125,16 @@
"version": "2026.7.3"
},
"immich": {
"image": "146.59.87.168:3000/lfg2025/immich-server:release",
"image": "source.archipelago-foundation.org/lfg2025/immich-server:release",
"images": {
"immich_postgres": "146.59.87.168:3000/lfg2025/immich-postgres:14-vectorchord0.4.3-pgvectors0.2.0",
"immich_redis": "146.59.87.168:3000/lfg2025/redis:7.4.8",
"immich_server": "146.59.87.168:3000/lfg2025/immich-server:release"
"immich_postgres": "source.archipelago-foundation.org/lfg2025/immich-postgres:14-vectorchord0.4.3-pgvectors0.2.0",
"immich_redis": "source.archipelago-foundation.org/lfg2025/redis:7.4.8",
"immich_server": "source.archipelago-foundation.org/lfg2025/immich-server:release"
},
"manifest": {
"app": {
"container": {
"image": "146.59.87.168:3000/lfg2025/immich-server:release",
"image": "source.archipelago-foundation.org/lfg2025/immich-server:release",
"network": "archy-net",
"pull_policy": "if-not-present",
"secret_env": [
@@ -2150,7 +2237,7 @@
"name": "immich-db-password"
}
],
"image": "146.59.87.168:3000/lfg2025/immich-postgres:14-vectorchord0.4.3-pgvectors0.2.0",
"image": "source.archipelago-foundation.org/lfg2025/immich-postgres:14-vectorchord0.4.3-pgvectors0.2.0",
"network": "archy-net",
"pull_policy": "if-not-present",
"secret_env": [
@@ -2215,7 +2302,7 @@
"manifest": {
"app": {
"container": {
"image": "146.59.87.168:3000/lfg2025/valkey:7-alpine",
"image": "source.archipelago-foundation.org/lfg2025/valkey:7-alpine",
"network": "archy-net",
"pull_policy": "if-not-present"
},
@@ -2251,17 +2338,17 @@
"version": "7-alpine"
},
"indeedhub": {
"image": "146.59.87.168:3000/lfg2025/indeedhub:1.0.0",
"image": "source.archipelago-foundation.org/lfg2025/indeedhub:1.0.0",
"images": {
"indeedhub": "146.59.87.168:3000/lfg2025/indeedhub:1.0.0",
"indeedhub-api": "146.59.87.168:3000/lfg2025/indeedhub-api:1.0.0",
"indeedhub-ffmpeg": "146.59.87.168:3000/lfg2025/indeedhub-ffmpeg:1.0.0"
"indeedhub": "source.archipelago-foundation.org/lfg2025/indeedhub:1.0.0",
"indeedhub-api": "source.archipelago-foundation.org/lfg2025/indeedhub-api:1.0.0",
"indeedhub-ffmpeg": "source.archipelago-foundation.org/lfg2025/indeedhub-ffmpeg:1.0.0"
},
"manifest": {
"app": {
"category": "community",
"container": {
"image": "146.59.87.168:3000/lfg2025/indeedhub:1.0.0",
"image": "source.archipelago-foundation.org/lfg2025/indeedhub:1.0.0",
"network": "indeedhub-net",
"pull_policy": "if-not-present"
},
@@ -2404,7 +2491,7 @@
"name": "indeedhub-jwt"
}
],
"image": "146.59.87.168:3000/lfg2025/indeedhub-api:1.0.0",
"image": "source.archipelago-foundation.org/lfg2025/indeedhub-api:1.0.0",
"network": "indeedhub-net",
"network_aliases": [
"api"
@@ -2485,7 +2572,7 @@
"app": {
"category": "community",
"container": {
"image": "146.59.87.168:3000/lfg2025/indeedhub-ffmpeg:1.0.0",
"image": "source.archipelago-foundation.org/lfg2025/indeedhub-ffmpeg:1.0.0",
"network": "indeedhub-net",
"pull_policy": "if-not-present",
"secret_env": [
@@ -2553,7 +2640,7 @@
"name": "indeedhub-minio-password"
}
],
"image": "146.59.87.168:3000/lfg2025/minio:RELEASE.2024-11-07T00-52-20Z",
"image": "source.archipelago-foundation.org/lfg2025/minio:RELEASE.2024-11-07T00-52-20Z",
"network": "indeedhub-net",
"network_aliases": [
"minio"
@@ -2622,7 +2709,7 @@
"name": "indeedhub-db-password"
}
],
"image": "146.59.87.168:3000/lfg2025/postgres:16.13-alpine",
"image": "source.archipelago-foundation.org/lfg2025/postgres:16.13-alpine",
"network": "indeedhub-net",
"network_aliases": [
"postgres"
@@ -2691,7 +2778,7 @@
"app": {
"category": "community",
"container": {
"image": "146.59.87.168:3000/lfg2025/redis:7.4.8-alpine",
"image": "source.archipelago-foundation.org/lfg2025/redis:7.4.8-alpine",
"network": "indeedhub-net",
"network_aliases": [
"redis"
@@ -2749,7 +2836,7 @@
"app": {
"category": "community",
"container": {
"image": "146.59.87.168:3000/lfg2025/nostr-rs-relay:0.9.0",
"image": "source.archipelago-foundation.org/lfg2025/nostr-rs-relay:0.9.0",
"network": "indeedhub-net",
"network_aliases": [
"relay"
@@ -2799,11 +2886,11 @@
"version": "0.9.0"
},
"jellyfin": {
"image": "146.59.87.168:3000/lfg2025/jellyfin:10.8.13",
"image": "source.archipelago-foundation.org/lfg2025/jellyfin:10.8.13",
"manifest": {
"app": {
"container": {
"image": "146.59.87.168:3000/lfg2025/jellyfin:10.8.13",
"image": "source.archipelago-foundation.org/lfg2025/jellyfin:10.8.13",
"network": "pasta",
"pull_policy": "if-not-present"
},
@@ -2983,7 +3070,7 @@
"version": "0.12.0"
},
"lnd": {
"image": "146.59.87.168:3000/lfg2025/lnd:v0.18.4-beta",
"image": "source.archipelago-foundation.org/lfg2025/lnd:v0.18.4-beta",
"manifest": {
"app": {
"bitcoin_integration": {
@@ -2998,7 +3085,7 @@
"template": "{{BITCOIN_HOST}}"
}
],
"image": "146.59.87.168:3000/lfg2025/lnd:v0.18.4-beta",
"image": "source.archipelago-foundation.org/lfg2025/lnd:v0.18.4-beta",
"network": "archy-net",
"pull_policy": "if-not-present",
"secret_env": [
@@ -3088,7 +3175,7 @@
"version": "v0.18.4-beta"
},
"lnd-ui": {
"image": "146.59.87.168:3000/lfg2025/lnd-ui:1.7.123-alpha",
"image": "source.archipelago-foundation.org/lfg2025/lnd-ui:1.7.123-alpha",
"manifest": {
"app": {
"container": {
@@ -3139,11 +3226,11 @@
"version": "1.7.123-alpha"
},
"mempool": {
"image": "146.59.87.168:3000/lfg2025/mempool-frontend:v3.0.1",
"image": "source.archipelago-foundation.org/lfg2025/mempool-frontend:v3.0.1",
"images": {
"archy-mempool-db": "146.59.87.168:3000/lfg2025/mariadb:11.4.10",
"archy-mempool-web": "146.59.87.168:3000/lfg2025/mempool-frontend:v3.0.1",
"mempool-api": "146.59.87.168:3000/lfg2025/mempool-backend:v3.0.0"
"archy-mempool-db": "source.archipelago-foundation.org/lfg2025/mariadb:11.4.10",
"archy-mempool-web": "source.archipelago-foundation.org/lfg2025/mempool-frontend:v3.0.1",
"mempool-api": "source.archipelago-foundation.org/lfg2025/mempool-backend:v3.0.0"
},
"manifest": {
"app": {
@@ -3152,7 +3239,7 @@
"sync_required": true
},
"container": {
"image": "146.59.87.168:3000/lfg2025/mempool-frontend:v3.0.1",
"image": "source.archipelago-foundation.org/lfg2025/mempool-frontend:v3.0.1",
"image_signature": "cosign://...",
"pull_policy": "if-not-present"
},
@@ -3237,7 +3324,7 @@
"template": "{{BITCOIN_HOST}}"
}
],
"image": "146.59.87.168:3000/lfg2025/mempool-backend:v3.0.0",
"image": "source.archipelago-foundation.org/lfg2025/mempool-backend:v3.0.0",
"network": "archy-net",
"pull_policy": "if-not-present",
"secret_env": [
@@ -3713,11 +3800,11 @@
"version": "0.71.2"
},
"nextcloud": {
"image": "146.59.87.168:3000/lfg2025/nextcloud:29",
"image": "source.archipelago-foundation.org/lfg2025/nextcloud:29",
"manifest": {
"app": {
"container": {
"image": "146.59.87.168:3000/lfg2025/nextcloud:29",
"image": "source.archipelago-foundation.org/lfg2025/nextcloud:29",
"network": "pasta",
"pull_policy": "if-not-present"
},
@@ -3796,11 +3883,11 @@
"version": "29"
},
"nginx-proxy-manager": {
"image": "146.59.87.168:3000/lfg2025/nginx-proxy-manager:latest",
"image": "source.archipelago-foundation.org/lfg2025/nginx-proxy-manager:latest",
"version": "latest"
},
"nostr-rs-relay": {
"image": "146.59.87.168:3000/lfg2025/nostr-rs-relay:0.9.0",
"image": "source.archipelago-foundation.org/lfg2025/nostr-rs-relay:0.9.0",
"manifest": {
"app": {
"container": {
@@ -3875,30 +3962,104 @@
"version": "0.9.0"
},
"nostr-vpn": {
"image": "146.59.87.168:3000/lfg2025/nostr-vpn:v0.3.7",
"image": "source.archipelago-foundation.org/lfg2025/nostr-vpn:v0.3.7",
"version": "v0.3.7"
},
"ollama": {
"image": "146.59.87.168:3000/lfg2025/ollama:latest",
"image": "source.archipelago-foundation.org/lfg2025/ollama:latest",
"version": "latest"
},
"penpot": {
"image": "146.59.87.168:3000/lfg2025/penpot-frontend:2.4",
"image": "source.archipelago-foundation.org/lfg2025/penpot-frontend:2.4",
"images": {
"penpot-backend": "146.59.87.168:3000/lfg2025/penpot-backend:2.4",
"penpot-exporter": "146.59.87.168:3000/lfg2025/penpot-exporter:2.4",
"penpot-frontend": "146.59.87.168:3000/lfg2025/penpot-frontend:2.4",
"penpot-postgres": "146.59.87.168:3000/lfg2025/postgres:15",
"penpot-valkey": "146.59.87.168:3000/lfg2025/valkey:8.1"
"penpot-backend": "source.archipelago-foundation.org/lfg2025/penpot-backend:2.4",
"penpot-exporter": "source.archipelago-foundation.org/lfg2025/penpot-exporter:2.4",
"penpot-frontend": "source.archipelago-foundation.org/lfg2025/penpot-frontend:2.4",
"penpot-postgres": "source.archipelago-foundation.org/lfg2025/postgres:15",
"penpot-valkey": "source.archipelago-foundation.org/lfg2025/valkey:8.1"
},
"version": "2.4"
},
"phoenixd": {
"manifest": {
"app": {
"category": "money",
"container": {
"data_uid": "1000:1000",
"image": "source.archipelago-foundation.org/lfg2025/phoenixd:0.9.0",
"pull_policy": "if-not-present"
},
"dependencies": [
{
"storage": "500Mi"
}
],
"description": "Headless Lightning daemon by ACINQ (the Phoenix wallet team). No screen of its own — it exposes a small local API that other apps and tools use to send and receive Lightning payments. Channel liquidity is managed automatically for a fee.",
"environment": [
"PHOENIX_DATADIR=/data"
],
"health_check": {
"endpoint": "localhost:9740",
"interval": "30s",
"retries": 5,
"timeout": "5s",
"type": "tcp"
},
"id": "phoenixd",
"metadata": {
"features": [
"Ultra-light Lightning node — no bitcoin node required",
"Automated channel and liquidity management (fees apply)",
"Simple HTTP API + websockets for payments",
"Backed by the team behind the Phoenix mobile wallet"
],
"icon": "/assets/img/app-icons/phoenixd.svg",
"repo": "https://github.com/ACINQ/phoenixd",
"tier": "optional"
},
"name": "phoenixd",
"ports": [
{
"auth": "none",
"auth_rationale": "Loopback-only JSON API, not a web page. Every request is authenticated by the http password phoenixd generates in its own data directory on first run; the app gate's browser login page would break the API clients this port exists for.",
"bind": "127.0.0.1",
"container": 9740,
"host": 9740,
"protocol": "tcp"
}
],
"resources": {
"cpu_limit": 1,
"disk_limit": "1Gi",
"memory_limit": "512Mi"
},
"security": {
"capabilities": [],
"network_policy": "bridge",
"no_new_privileges": true,
"readonly_root": true
},
"version": "0.9.0",
"volumes": [
{
"options": [
"rw"
],
"source": "/var/lib/archipelago/phoenixd",
"target": "/data",
"type": "bind"
}
]
}
},
"version": "0.9.0"
},
"photoprism": {
"image": "146.59.87.168:3000/lfg2025/photoprism:240915",
"image": "source.archipelago-foundation.org/lfg2025/photoprism:240915",
"manifest": {
"app": {
"container": {
"image": "146.59.87.168:3000/lfg2025/photoprism:240915",
"image": "source.archipelago-foundation.org/lfg2025/photoprism:240915",
"pull_policy": "if-not-present"
},
"dependencies": [
@@ -4377,13 +4538,13 @@
"version": "3.4.2"
},
"portainer": {
"image": "146.59.87.168:3000/lfg2025/portainer:2.39.1",
"image": "source.archipelago-foundation.org/lfg2025/portainer:2.39.1",
"manifest": {
"app": {
"category": "development",
"container": {
"data_uid": "1000:1000",
"image": "146.59.87.168:3000/lfg2025/portainer:2.39.1",
"image": "source.archipelago-foundation.org/lfg2025/portainer:2.39.1",
"pull_policy": "if-not-present"
},
"dependencies": [
@@ -4574,15 +4735,15 @@
"version": "1.0.0"
},
"routstr": {
"image": "146.59.87.168:3000/lfg2025/routstr:v0.4.3",
"image": "source.archipelago-foundation.org/lfg2025/routstr:v0.4.3",
"version": "v0.4.3"
},
"searxng": {
"image": "146.59.87.168:3000/lfg2025/searxng:latest",
"image": "source.archipelago-foundation.org/lfg2025/searxng:latest",
"manifest": {
"app": {
"container": {
"image": "146.59.87.168:3000/lfg2025/searxng:latest",
"image": "source.archipelago-foundation.org/lfg2025/searxng:latest",
"pull_policy": "if-not-present"
},
"dependencies": [
@@ -4724,11 +4885,11 @@
"version": "0.9.0"
},
"tailscale": {
"image": "146.59.87.168:3000/lfg2025/tailscale:stable",
"image": "source.archipelago-foundation.org/lfg2025/tailscale:stable",
"version": "stable"
},
"uptime-kuma": {
"image": "146.59.87.168:3000/lfg2025/uptime-kuma:1",
"image": "source.archipelago-foundation.org/lfg2025/uptime-kuma:1",
"manifest": {
"app": {
"container": {
@@ -4737,7 +4898,7 @@
"node",
"server/server.js"
],
"image": "146.59.87.168:3000/lfg2025/uptime-kuma:1",
"image": "source.archipelago-foundation.org/lfg2025/uptime-kuma:1",
"network": "pasta",
"pull_policy": "if-not-present"
},
@@ -4809,11 +4970,11 @@
"version": "1"
},
"vaultwarden": {
"image": "146.59.87.168:3000/lfg2025/vaultwarden:1.30.0-alpine",
"image": "source.archipelago-foundation.org/lfg2025/vaultwarden:1.30.0-alpine",
"manifest": {
"app": {
"container": {
"image": "146.59.87.168:3000/lfg2025/vaultwarden:1.30.0-alpine",
"image": "source.archipelago-foundation.org/lfg2025/vaultwarden:1.30.0-alpine",
"network": "pasta",
"pull_policy": "if-not-present"
},
@@ -4893,7 +5054,7 @@
}
},
"schema": 1,
"signature": "c7b1901c5b67b7f83140cc12fc96c8bb17a696272afc1915a4df72d1c38c5c8d8691c528af102a3d34b00daa40317a08c394d51f806c6e67262b232dd2bb220a",
"signature": "0aaf681435434b6e325269745d1fc3f90686c5391525d6b591666d6e36097bef0e5b999e2b9fe37d7a945e59aa40db74ed618824ab78c7fc6af73271c62ff20a",
"signed_by": "did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT",
"updated": "2026-08-09"
"updated": "2026-08-13"
}
+17 -21
View File
@@ -1,33 +1,29 @@
{
"changelog": [
"**Archipelago is now open source.** The full source code of the node you are running — the orchestrator, the dashboard, the app platform, the mesh, the release tooling — is published for anyone to read, build and audit at source.archipelago-foundation.org/lfg2025/archy. A node that holds your money, your files and your communications should not ask to be taken on faith: from this release onward you, or anyone you trust, can see exactly what it does and follow every change we make in the open.",
"**Installing an update is reliable again, and tells you what happened when it isn't.** Some nodes could download an update but never apply it — the button stayed on \"Install\", and no amount of retrying worked. The cause: applying the update consumed the downloaded files as it went, so if any one step hit a snag partway through, the leftover files were incomplete and every later attempt failed the safety re-check forever, needing a technician to recover. Applying no longer consumes the download — a failed apply can always be retried from the same files — and the pieces are now applied in a fixed order with the program itself last, so a hiccup can't leave a half-swapped node. When an apply does fail, the screen now shows the real reason and what to do (\"download the update again\"), and offers Download again instead of a dead \"Install\" button, rather than a generic \"it failed\".",
"**Video on the kiosk stops tearing.** The kiosk's display had no vertical sync at all, so fast motion — IndeedHub films especially — showed horizontal tearing lines. The display driver now syncs every frame to the panel (no extra hardware needed, existing kiosks pick it up with this update), and on machines with a GPU, video decoding moves off the CPU onto the video hardware — smoother playback that also leaves more headroom for audio, not less.",
"**The Back button finally does what you expect.** Pressing Back — the mouse's side button on a kiosk, a swipe on a phone, the toolbar button in any browser — used to navigate the screen underneath an open window, or leave the dashboard entirely. Back now closes the topmost open window first, one per press, exactly like a native app; closing a window yourself never leaves a phantom entry that makes you press Back twice.",
"**No more bare IP addresses in your update or app-registry settings.** The update mirrors and the app registry each listed the same server twice — once by its proper name, once as a raw `http://146…` address left over from before the domain existed. The raw-address entries are retired: new nodes never see them, and existing nodes clean them out of their saved lists automatically on the next read. Everything now goes through the named, TLS-protected origin — which was always the same machine.",
"**The phone companion app downloads over the proper domain.** The download QR pointed at a raw address over plain HTTP; it now points at the same file on the https domain. Scanning it gets you an encrypted download from a named server.",
"**The Receive window now tells you when the money is on its way.** Previously it showed a QR code and left you to check elsewhere whether anything happened. Now, the moment the sender's transaction is broadcast, the QR gives way to a clock: the amount, the transaction ID (tap to copy), and a note that the funds arrive on their own — with a single Done button. If you keep the window open, the clock becomes a green check at the first confirmation. Verified live on a real node: payment detected within seconds of broadcast."
"**Apps that refused to open inside the dashboard now embed like everything else.** Some apps ship browser headers that forbid being shown inside another page — correct hardening on the open web, but inside Archipelago it produced a dead grey pane when you opened them from My Apps (Alby Hub was the first to hit it). The app gate, which already checks your login on every request to an app, now removes just those framing headers on the way through; each app's own content-security rules pass through untouched. No more per-app proxy workarounds.",
"**The network map no longer freezes kiosk TVs.** The animated federation map at 4K was too much for the deliberately conservative graphics settings the on-screen display used on every machine — settings chosen years back to stop audio crackle on much older hardware. Two fixes: on kiosk screens the map now opens in its flat 2D view (the 3D globe is one tap away, and remembered) and animates at half rate — invisible from the couch, half the work. And the display itself now recognizes what machine it runs on: older kiosk boxes keep the proven careful settings, modern ones finally get real GPU rendering.",
"**New Settings → Display → Graphics choice for the on-screen display.** Auto (recommended) picks the right rendering mode for the machine by itself; Compatibility forces the most conservative mode if a screen ever stutters, tears, or crackles; Quality forces full GPU rendering on hardware the automatic detection doesn't recognize. Changing it restarts the on-screen display, like the size presets."
],
"components": [
{
"current_version": "1.8.0-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.0-alpha/archipelago",
"current_version": "1.8.1-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.1-alpha/archipelago",
"name": "archipelago",
"new_version": "1.8.0-alpha",
"sha256": "fd99219ea11ffebc92b83154e1058911ebe72c357c80085310c78aba9714a6b5",
"size_bytes": 59369392
"new_version": "1.8.1-alpha",
"sha256": "50fe97705717f8176afc03285a3a459b1d4e219dba0442aa4ee5b8cea8e3ec30",
"size_bytes": 59896032
},
{
"current_version": "1.8.0-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.0-alpha/archipelago-frontend-1.8.0-alpha.tar.gz",
"name": "archipelago-frontend-1.8.0-alpha.tar.gz",
"new_version": "1.8.0-alpha",
"sha256": "f6bbf7b3f78a00dd7051abef805c943cfa9a58f624e09e94b6bee6c9b2f22902",
"size_bytes": 97608790
"current_version": "1.8.1-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.1-alpha/archipelago-frontend-1.8.1-alpha.tar.gz",
"name": "archipelago-frontend-1.8.1-alpha.tar.gz",
"new_version": "1.8.1-alpha",
"sha256": "ae77c97e8f1ec7d1044f7d565319ddc503f6f5b87dbc9a524fa9cc9639b04f29",
"size_bytes": 97611693
}
],
"release_date": "2026-08-12",
"signature": "be3e3c6f8ed6b8d573a6329bf546ec1aa739214cd391272ce3b2f0a0ef6b10a4a4c4f9dfa4b9b32dad88326c9feff908fbdb3d1b86d055e5cf876f67f195c60b",
"release_date": "2026-08-13",
"signature": "7a8932503d1c33156f79d11671bb275a2bb63347988da02562c3dd138287e7ed77c7d4cb0a1f6bcbcf8665da506fafedd2d81bb844de0cbcbaf2b9045210ca0b",
"signed_by": "did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT",
"version": "1.8.0-alpha"
"version": "1.8.1-alpha"
}
+8 -5
View File
@@ -21,13 +21,16 @@
"docker.io",
"ghcr.io",
"localhost",
"146.59.87.168:3000"
"146.59.87.168:3000",
"source.archipelago-foundation.org"
],
"pending": {
"pending": {},
"promotions": {
"source.archipelago-foundation.org": {
"trusted_from_binary": "unreleased",
"note": "Added to TRUSTED_REGISTRIES 2026-08-07. Not yet shipped in any OTA, so no deployed node accepts it. Promote to `hosts` only after the fleet is confirmed on a binary that includes it."
"trusted_from_binary": "1.8.0-alpha",
"confirmed": "2026-08-13",
"note": "Fleet confirmed on 1.8.0-alpha (TRUSTED_REGISTRIES includes this host since 2026-08-07): archi-dev-box, zaza-optiplex, archipelago-1, shorty-s, austin-sapien all report current_version 1.8.0-alpha via update_state.json on 2026-08-13. Exception: archy-x250-beta is stranded pre-v1.7.122 (old release-root pin, cannot OTA at all) and needs a full re-image — it cannot install catalog apps from ANY host today, so it does not hold the floor down."
}
},
"updated": "2026-08-07"
"updated": "2026-08-13"
}