Compare commits

..
Author SHA1 Message Date
Archipelago 9a73aaf629 Archipelago — open-source initial import 2026-08-12 10:55:49 +00:00
17 changed files with 75 additions and 340 deletions
+1 -5
View File
@@ -1,11 +1,7 @@
# Changelog
## v1.8.0-alpha (2026-08-12)
## Unreleased
- **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.
+1 -1
View File
@@ -104,7 +104,7 @@ dependencies = [
[[package]]
name = "archipelago"
version = "1.8.0-alpha"
version = "1.7.129-alpha"
dependencies = [
"anyhow",
"archipelago-container",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "archipelago"
version = "1.8.0-alpha"
version = "1.7.129-alpha"
edition = "2021"
license.workspace = true
description = "Archipelago Bitcoin Node OS - Native backend"
@@ -64,12 +64,6 @@ pub(super) fn sanitize_error_message(msg: &str) -> String {
"must be",
"cannot",
"Password",
// OTA apply/download errors are all operator-actionable ("download it
// again", "download first") — sanitizing them to "Operation failed"
// left users stuck with no idea what to do, and hid the "already
// running" text the update UI matches on to join an in-flight apply
// instead of showing a false failure. Every such message starts "Update".
"Update",
// The federation escalation sentinel. "Password" above does NOT cover
// it — starts_with is case-sensitive and the sentinel is ALL-CAPS —
// so the frontend's isPasswordRequired() never saw it and the
+27 -78
View File
@@ -1013,7 +1013,7 @@ pub async fn dismiss_update(data_dir: &Path) -> Result<()> {
/// partially-corrupt resume still fails cleanly.
pub async fn download_update(data_dir: &Path) -> Result<DownloadProgress> {
let _op = UPDATE_OP_LOCK.try_lock().map_err(|_| {
anyhow::anyhow!("Update already in progress — another download or apply is already running")
anyhow::anyhow!("another update operation (download or apply) is already running")
})?;
let mut state = load_state(data_dir).await?;
if state.available_update.is_none() {
@@ -1406,8 +1406,8 @@ async fn verify_staged_components(staging_dir: &Path, manifest: &UpdateManifest)
.unwrap_or(0);
if len != component.size_bytes {
anyhow::bail!(
"Update staging is inconsistent: component {} is {} bytes but the manifest says {} — \
re-download before applying (incomplete or concurrently-rewritten download)",
"staged component {} is {} bytes but the manifest says {} — \
refusing to apply (incomplete or concurrently-rewritten download)",
component.name,
len,
component.size_bytes
@@ -1519,11 +1519,11 @@ pub(crate) async fn host_sudo_output(args: &[&str]) -> Result<std::process::Outp
/// Apply a downloaded update. Backs up current binaries, replaces with staged versions.
pub async fn apply_update(data_dir: &Path) -> Result<()> {
let _op = UPDATE_OP_LOCK.try_lock().map_err(|_| {
anyhow::anyhow!("Update already in progress — another download or apply is already running")
anyhow::anyhow!("another update operation (download or apply) is already running")
})?;
let staging_dir = data_dir.join("update-staging");
if !staging_dir.exists() {
anyhow::bail!("Update not staged — download it first, then apply.");
anyhow::bail!("No staged update found. Download first.");
}
// Gate 1: the completion marker is written only after EVERY component
@@ -1531,7 +1531,7 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> {
// or in-flight download — exactly what got installed on .198.
if !has_staged_update(data_dir).await {
anyhow::bail!(
"Update download was incomplete (no completion marker) — download the update again before applying"
"Staged update is incomplete (no completion marker) — download the update again before applying"
);
}
@@ -1540,7 +1540,9 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> {
.await?
.available_update
.ok_or_else(|| {
anyhow::anyhow!("Update manifest missing from state — re-download the update")
anyhow::anyhow!(
"no update manifest in state to verify staged files against — re-download the update"
)
})?;
verify_staged_components(&staging_dir, &manifest).await?;
@@ -1576,83 +1578,41 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> {
info!("Current binary backed up");
}
// Apply staged components in a DETERMINISTIC order, binary LAST.
// read_dir order is filesystem-arbitrary, and each component used to be
// consumed destructively — so a mid-apply failure could leave staging
// half-emptied and un-reappliable (Gate 2 re-verifies EVERY manifest
// component against staging, so a missing one wedges every retry:
// "doesn't apply, still says install, can never apply again"). Two
// guards against that now: (a) nothing is removed from staging here —
// the binary is copied, not moved (see its block) — so a failed apply
// is always retryable from the same staged files; (b) the binary, the
// one component whose swap changes what runs after restart, is applied
// only after the frontend/runtime succeed, so a frontend failure never
// leaves a new binary staged to run against an old frontend on the next
// restart.
let mut names: Vec<String> = Vec::new();
{
let mut entries = fs::read_dir(&staging_dir)
.await
.context("Failed to read staging dir")?;
while let Some(entry) = entries.next_entry().await? {
names.push(entry.file_name().to_string_lossy().to_string());
}
}
names.sort_by_key(|n| match n.as_str() {
"archipelago" => 2, // binary last
n if n.contains("runtime") && n.ends_with(".tar.gz") => 1,
_ => 0, // frontend and everything else first
});
// Apply staged components
let mut entries = fs::read_dir(&staging_dir)
.await
.context("Failed to read staging dir")?;
for name in &names {
let name = name.as_str();
let src = staging_dir.join(name);
while let Some(entry) = entries.next_entry().await? {
let name = entry.file_name().to_string_lossy().to_string();
let src = entry.path();
match name {
match name.as_str() {
"archipelago" => {
// Three constraints this block works around:
// Two namespace gotchas this block works around:
// 1. We're running FROM /usr/local/bin/archipelago, so
// `install`/`cp` (O_TRUNC + write) fail with ETXTBSY.
// rename() over a busy destination is fine.
// Use `mv`, which is atomic rename() and tolerates a
// busy destination.
// 2. archipelago.service sets ProtectSystem=strict, so
// even `sudo mv` into /usr/local/bin/ fails EROFS —
// sudo inherits the service's mount namespace. Route
// through host_sudo (systemd-run transient unit with
// default protections).
// 3. The staged binary must SURVIVE this so a later
// component's failure leaves the apply retryable. So we
// COPY the staged file to a sibling temp in the target
// dir, then atomic-rename the temp over the target —
// the staging copy is never moved. (mv'ing the staged
// file itself was the wedging bug: binary applied, then
// frontend fails, staging now missing the binary, every
// retry fails re-verification forever.)
// the rename through systemd-run so it runs in a
// transient unit with default protections.
let staged = src.to_string_lossy().to_string();
let tmp = format!(
"/usr/local/bin/.archipelago.new.{}",
chrono::Utc::now().timestamp_millis()
);
let cp = host_sudo(&["cp", "-f", &staged, &tmp])
.await
.with_context(|| format!("Failed to copy staged binary for {}", name))?;
if !cp.success() {
let _ = host_sudo(&["rm", "-f", &tmp]).await;
anyhow::bail!("copy of staged binary failed for {}", name);
}
let _ = host_sudo(&["chmod", "0755", &tmp]).await;
let _ = host_sudo(&["chown", "root:root", &tmp]).await;
let status = host_sudo(&["mv", &tmp, "/usr/local/bin/archipelago"])
let _ = host_sudo(&["chmod", "0755", &staged]).await;
let _ = host_sudo(&["chown", "root:root", &staged]).await;
let status = host_sudo(&["mv", &staged, "/usr/local/bin/archipelago"])
.await
.with_context(|| format!("Failed to spawn mv for {}", name))?;
if !status.success() {
let _ = host_sudo(&["rm", "-f", &tmp]).await;
anyhow::bail!(
"mv into /usr/local/bin failed for {} (exit {:?})",
name,
status.code()
);
}
info!(name = %name, "Backend binary applied (staging preserved)");
info!(name = %name, "Backend binary applied");
}
_ if name.contains("frontend") && name.ends_with(".tar.gz") => {
// Tarball contents are the *inside* of web-ui/ (root entries
@@ -2778,20 +2738,9 @@ mod tests {
save_state(dir.path(), &state).await.unwrap();
let err = apply_update(dir.path()).await.unwrap_err();
assert!(
err.to_string().contains("re-download before applying"),
err.to_string().contains("refusing to apply"),
"got: {err:#}"
);
// Resilience: a refused apply must leave the update still available and
// still staged, so the user can re-download and retry — never a wedge.
let loaded = load_state(dir.path()).await.unwrap();
assert!(
loaded.available_update.is_some(),
"a refused apply must not clear the available update"
);
assert!(
loaded.update_in_progress,
"a refused apply must leave the staged-update flag set for retry"
);
}
#[tokio::test]
-1
View File
@@ -86,5 +86,4 @@ file.
## Roadmap & history
- [Roadmap](ROADMAP.md) — where the project is going
- [TODO](TODO.md) — working backlog of unscoped forward-looking items
- [archive/](archive/README.md) — superseded design and status documents, kept for provenance
-52
View File
@@ -1,52 +0,0 @@
# TODO
Working backlog of forward-looking items not yet scoped into a dedicated plan
doc. See [`ROADMAP.md`](ROADMAP.md) for the curated, public-facing direction.
## Dev & build process (priority)
- Formalize the contributor workflow: releases, CI, maintainers, automated
builds, PR/issue flow, branch naming, and reproducible builds.
## Federation & peering
- Peering trust model — define tiers (trusted / public / private / peered)
on top of the existing federation DID trust levels.
- Federation architecture built on the above peering model.
## Distributed git & OTA
- Nostr-hosted git for the alpha (see
[`nostr-git-source-hosting.md`](nostr-git-source-hosting.md)).
- Distributed git beyond the nostr-hosting case.
- Distributed OTA / app delivery.
## Nostr integration
- Nostr signer integration.
## Platform / OS
- Source-availability ISO — define the build/distribution story.
- HW/OS update pipeline.
- Deeper OpenWRT integration.
- GrapheneOS integration — backups, attestation, profiles.
## App ecosystem
- Full pass testing every app in the catalog; expect issues across the board.
- App update strategy — finalize the update policy referenced in
[`app-developer-guide.md`](app-developer-guide.md) (pinned vs. mutable
tags, catalog-vs-disk precedence, rollout/rollback).
- App wishlist — candidates not yet packaged: Cashu wallet, phoenixd.
(CLN is already shipped as `apps/core-lightning`.)
## Access & security
- SSH access strategy — define the access model (keys, rotation, recovery
path, remote-support access).
## Observability
- Capture error logs to troubleshoot customer issues.
- Stats & visualization for traffic, blocked attacks, VPNs, routing.
@@ -408,10 +408,6 @@ DOCKERFILE_HEAD
xorg \
xdotool \
chromium \
mesa-va-drivers \
intel-media-va-driver \
i965-va-driver \
vainfo \
pipewire \
pipewire-pulse \
pipewire-alsa \
@@ -1,28 +1,5 @@
#!/bin/bash
# TearFree BEFORE X starts: bare Xorg with the stock modesetting driver has
# no vsync and no compositor, so video page-flips land mid-scanout — visible
# tearing on every kiosk (reported 2026-08-11, "really bad" on IndeedHub
# playback). The modesetting driver's TearFree option double-buffers the
# scanout at the driver level: no compositor needed, one frame of latency,
# no interaction with the 2026-06-28 choppy-audio GPU decisions. Written
# here (idempotently) rather than baked into the image so existing kiosk
# nodes pick it up through the launcher's own OTA path (bootstrap.rs
# reinstalls this script on every node).
XORG_CONF_DIR=/etc/X11/xorg.conf.d
XORG_TEARFREE="$XORG_CONF_DIR/20-archipelago-kiosk-tearfree.conf"
mkdir -p "$XORG_CONF_DIR"
if [ ! -f "$XORG_TEARFREE" ] || ! grep -q TearFree "$XORG_TEARFREE"; then
cat > "$XORG_TEARFREE" <<'EOF'
# Written by archipelago-kiosk-launcher — vsynced scanout for kiosk video.
Section "Device"
Identifier "Archipelago Kiosk GPU"
Driver "modesetting"
Option "TearFree" "true"
EndSection
EOF
fi
# Start a dedicated X server for the attached kiosk display.
/usr/bin/Xorg :0 vt1 -nolisten tcp -keeptty &
XPID=$!
@@ -180,16 +157,8 @@ sleep 1
# On a GPU-less / headless server (no /dev/dri), disable GPU entirely instead.
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.
ENABLE_FEATURES="OverlayScrollbar,VaapiVideoDecodeLinuxGL,VaapiIgnoreDriverChecks"
else
GPU_FLAGS="--disable-gpu --num-raster-threads=1"
ENABLE_FEATURES="OverlayScrollbar"
fi
ARCHIPELAGO_UID=$(id -u archipelago)
@@ -225,7 +194,7 @@ while true; do
--no-first-run \
--check-for-update-interval=31536000 \
--disable-features=TranslateUI,MetricsReporting,AutofillServerCommunication,PasswordManagerEnabled,GpuRasterization \
--enable-features=$ENABLE_FEATURES \
--enable-features=OverlayScrollbar \
--disable-session-crashed-bubble \
--disable-save-password-bubble \
--disable-suggestions-service \
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "neode-ui",
"version": "1.8.0-alpha",
"version": "1.7.129-alpha",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "neode-ui",
"version": "1.8.0-alpha",
"version": "1.7.129-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.7.129-alpha",
"type": "module",
"scripts": {
"start": "./start-dev.sh",
-5
View File
@@ -54,7 +54,6 @@ import { ref, computed, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useModalKeyboard } from '@/composables/useModalKeyboard'
import { useBodyScrollLock } from '@/composables/useBodyScrollLock'
import { useModalHistory } from '@/composables/useModalHistory'
const props = withDefaults(defineProps<{
show: boolean
@@ -106,10 +105,6 @@ function close() {
useModalKeyboard(modalRef, computed(() => props.show), close)
useBodyScrollLock(computed(() => props.show))
// Browser/mouse/gesture Back closes the modal instead of navigating the
// router out from under it the native-app behaviour kiosk and mobile
// browsers expect (the companion webview already provides it natively).
useModalHistory(computed(() => props.show), close)
</script>
<style scoped>
@@ -1,82 +0,0 @@
// Back/forward integration for modals (kiosk, remote browsers, mobile).
//
// Without this, the browser's Back control (mouse side-button on kiosk,
// gesture on mobile, toolbar button in a remote browser) navigates the
// ROUTER while a modal is open — at best closing the whole screen under a
// dialog, at worst leaving the app. The native-app expectation, and what
// the companion webview already provides, is: Back closes the topmost
// dialog first.
//
// Mechanics: opening a modal pushes one history entry (same URL, a depth
// marker in state — router keys are preserved by spreading the existing
// state). A popstate that lands BELOW our depth means the user pressed
// Back over an open modal: close the topmost one. A UI-side close (X,
// backdrop, Esc) consumes its own entry with history.back() so Back never
// needs pressing twice — guarded by the depth marker so it can never eat
// a router entry. One module-level stack serves every BaseModal instance,
// so stacked modals close one per Back, top first.
import { watch, type Ref } from 'vue'
type Entry = { close: () => void }
const stack: Entry[] = []
// Set when a popstate initiated the close: the history entry is already
// gone, so the close-side cleanup must not call history.back() again.
let poppedClose = false
let listening = false
function modalDepth(state: unknown): number {
return (state as { __archyModal?: number } | null)?.__archyModal ?? 0
}
function ensureListener() {
if (listening || typeof window === 'undefined') return
listening = true
window.addEventListener('popstate', (e) => {
// Landed at a depth below the open-modal count → this Back was aimed
// at the topmost modal. One entry per Back press: close exactly one.
// (A popstate at or above our depth is someone else's navigation —
// e.g. our own cleanup back, or a forward — leave it alone.)
if (modalDepth(e.state) < stack.length) {
const top = stack[stack.length - 1]
if (top) {
poppedClose = true
top.close()
}
}
})
}
/** Call from a modal component with its visibility and close trigger. */
export function useModalHistory(show: Ref<boolean>, close: () => void) {
ensureListener()
const entry: Entry = { close }
watch(show, (open, was) => {
if (open === was) return
if (open) {
stack.push(entry)
try {
// Preserve vue-router's own keys in state — clobbering them breaks
// its scroll restoration and position tracking.
window.history.pushState(
{ ...(window.history.state ?? {}), __archyModal: stack.length },
'',
)
} catch { /* history can throw in exotic embeds — modal still works */ }
} else {
const wasTop = stack[stack.length - 1] === entry
const i = stack.indexOf(entry)
if (i >= 0) stack.splice(i, 1)
if (poppedClose) {
poppedClose = false
return
}
// UI-side close of the top modal: consume the entry we pushed, but
// only if it is still the current one (a route change after opening
// moves history past it — backing out then would eat a real entry).
if (wasTop && modalDepth(window.history.state) > stack.length) {
try { window.history.back() } catch { /* same guard as above */ }
}
}
})
}
+1 -12
View File
@@ -1087,18 +1087,7 @@ async function applyUpdate() {
}
return
}
// Surface the backend's actual, actionable message when it gave one
// ("Update download was incomplete download again", etc.) instead of a
// generic dead end. The staged files are preserved across a failed apply,
// so re-download stays available and a retry is always possible.
const detail = errorMessage(e)
const actionable = /^Update\b/.test(detail)
showStatus(actionable ? detail : t('systemUpdate.applyFailed'), true)
// A staging inconsistency means the download is the thing to redo drop
// the downloaded flag so the button offers Download again, not Apply.
if (/incomplete|not staged|re-download|inconsistent/i.test(detail)) {
downloaded.value = false
}
showStatus(t('systemUpdate.applyFailed'), true)
if (import.meta.env.DEV) console.warn('Apply failed', e)
applying.value = false
}
@@ -362,22 +362,6 @@ init()
</button>
</div>
<div class="overflow-y-auto flex-1 min-h-0 space-y-6 pr-1">
<!-- v1.8.0-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.0-alpha</span>
<span class="text-xs text-white/40">August 12, 2026</span>
</div>
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
<p>**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.</p>
<p>**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".</p>
<p>**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.</p>
<p>**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.</p>
<p>**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.</p>
<p>**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.</p>
<p>**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.</p>
</div>
</div>
<!-- v1.7.129-alpha -->
<div>
<div class="flex items-center gap-2 mb-3">
+20 -21
View File
@@ -1,33 +1,32 @@
{
"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."
"**Every app is now supervised the same way — the last stragglers moved under systemd.** Five apps (Jellyfin, Nextcloud, Home Assistant, Uptime Kuma, Vaultwarden) still ran outside the node's per-app service management for a technical reason: their networking style died with whatever process started it, so they were kept alive by a separate workaround. That workaround is retired: these apps now migrate themselves onto the same managed units as everything else — own service, restart-on-anything, a ten-second breather between restarts so their networking can release its ports cleanly. The migration happens automatically on the node's next housekeeping pass, touches no app data, and was watched live on a real node: both test apps moved over on the first pass and came back healthy.",
"**Leftover companion screens are cleaned up again — driven by real records this time.** When an app is uninstalled, its helper screen (the UI tile that fronts it) should go too. That cleanup was switched off in an earlier release after it wrongly removed the Bitcoin screen from a node whose Bitcoin was installed — it had been guessing \"installed\" from what happened to be running, and a separate bug made a running app look absent. The node now keeps a durable record of what you have installed, written at install time and cleared only by a real uninstall, and the cleanup consults only that record. If the record can't be read, the cleanup does nothing at all — \"I couldn't check\" is never treated as \"nothing is installed\" — and a helper must be orphaned for a sustained period before it is touched.",
"**A warning that fired every minute on every node is gone.** The app catalog and the node disagreed about where Grafana's software comes from, so the node ignored the catalog's answer and logged a complaint roughly every 75 seconds, forever. The catalog was right — Grafana is served from the fleet's own registry, like Bitcoin Knots — and the node's records now agree with it.",
"**The federation map became a real map.** The network view is now a 3D orbital scene of your federation — nodes as a point-cloud globe with calm motion, auto-fit centring, and a 2D top-down toggle that portrait and mobile screens use by default, with the scene filling the viewport instead of sitting in a letterbox. Inbound peer requests appear live on the map as blinking nodes you can accept or reject in place, and revisiting the view no longer replays the whole intro — the scene updates in place.",
"**An app that's mid-restart shows a page that says so — and comes back by itself.** When an app's screen was briefly unreachable behind the gate, the browser got a bare error; it now gets a named page for that app that retries on its own until the app answers.",
"Known gaps, disclosed rather than buried: three voice-assistant ports remain open without authentication. Non-browser clients — phone apps for Vaultwarden, Home Assistant or Jellyfin, and git over the web — meet the login page and need an access token. The 5x real-node lifecycle gate was not run for this release; the supervision migration and the cleanup re-enable were verified live on one node (both apps migrated and healthy, cleanup correctly idle)."
],
"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.7.129-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.7.129-alpha/archipelago",
"name": "archipelago",
"new_version": "1.8.0-alpha",
"sha256": "fd99219ea11ffebc92b83154e1058911ebe72c357c80085310c78aba9714a6b5",
"size_bytes": 59369392
"new_version": "1.7.129-alpha",
"sha256": "675e7dafc855d59b38c5a12d8b9405894677ed8701580227beca95ec2912e3f2",
"size_bytes": 59531400
},
{
"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.7.129-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.7.129-alpha/archipelago-frontend-1.7.129-alpha.tar.gz",
"name": "archipelago-frontend-1.7.129-alpha.tar.gz",
"new_version": "1.7.129-alpha",
"sha256": "53af2743308f4ae6a627255aaa288706d331d567c99e1cb615684dc3abba534d",
"size_bytes": 95452033
}
],
"release_date": "2026-08-12",
"signature": "be3e3c6f8ed6b8d573a6329bf546ec1aa739214cd391272ce3b2f0a0ef6b10a4a4c4f9dfa4b9b32dad88326c9feff908fbdb3d1b86d055e5cf876f67f195c60b",
"release_date": "2026-08-10",
"signature": "902778710674486d5919e4abb1bf5540521c9ef55b50a44a9d64b750812738d51cc193f6675a9969b1c45ae86c2e9e44ab3d8daf8aa1d439799d7f7846e27d04",
"signed_by": "did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT",
"version": "1.8.0-alpha"
"version": "1.7.129-alpha"
}
+20 -21
View File
@@ -1,33 +1,32 @@
{
"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."
"**Every app is now supervised the same way — the last stragglers moved under systemd.** Five apps (Jellyfin, Nextcloud, Home Assistant, Uptime Kuma, Vaultwarden) still ran outside the node's per-app service management for a technical reason: their networking style died with whatever process started it, so they were kept alive by a separate workaround. That workaround is retired: these apps now migrate themselves onto the same managed units as everything else — own service, restart-on-anything, a ten-second breather between restarts so their networking can release its ports cleanly. The migration happens automatically on the node's next housekeeping pass, touches no app data, and was watched live on a real node: both test apps moved over on the first pass and came back healthy.",
"**Leftover companion screens are cleaned up again — driven by real records this time.** When an app is uninstalled, its helper screen (the UI tile that fronts it) should go too. That cleanup was switched off in an earlier release after it wrongly removed the Bitcoin screen from a node whose Bitcoin was installed — it had been guessing \"installed\" from what happened to be running, and a separate bug made a running app look absent. The node now keeps a durable record of what you have installed, written at install time and cleared only by a real uninstall, and the cleanup consults only that record. If the record can't be read, the cleanup does nothing at all — \"I couldn't check\" is never treated as \"nothing is installed\" — and a helper must be orphaned for a sustained period before it is touched.",
"**A warning that fired every minute on every node is gone.** The app catalog and the node disagreed about where Grafana's software comes from, so the node ignored the catalog's answer and logged a complaint roughly every 75 seconds, forever. The catalog was right — Grafana is served from the fleet's own registry, like Bitcoin Knots — and the node's records now agree with it.",
"**The federation map became a real map.** The network view is now a 3D orbital scene of your federation — nodes as a point-cloud globe with calm motion, auto-fit centring, and a 2D top-down toggle that portrait and mobile screens use by default, with the scene filling the viewport instead of sitting in a letterbox. Inbound peer requests appear live on the map as blinking nodes you can accept or reject in place, and revisiting the view no longer replays the whole intro — the scene updates in place.",
"**An app that's mid-restart shows a page that says so — and comes back by itself.** When an app's screen was briefly unreachable behind the gate, the browser got a bare error; it now gets a named page for that app that retries on its own until the app answers.",
"Known gaps, disclosed rather than buried: three voice-assistant ports remain open without authentication. Non-browser clients — phone apps for Vaultwarden, Home Assistant or Jellyfin, and git over the web — meet the login page and need an access token. The 5x real-node lifecycle gate was not run for this release; the supervision migration and the cleanup re-enable were verified live on one node (both apps migrated and healthy, cleanup correctly idle)."
],
"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.7.129-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.7.129-alpha/archipelago",
"name": "archipelago",
"new_version": "1.8.0-alpha",
"sha256": "fd99219ea11ffebc92b83154e1058911ebe72c357c80085310c78aba9714a6b5",
"size_bytes": 59369392
"new_version": "1.7.129-alpha",
"sha256": "675e7dafc855d59b38c5a12d8b9405894677ed8701580227beca95ec2912e3f2",
"size_bytes": 59531400
},
{
"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.7.129-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.7.129-alpha/archipelago-frontend-1.7.129-alpha.tar.gz",
"name": "archipelago-frontend-1.7.129-alpha.tar.gz",
"new_version": "1.7.129-alpha",
"sha256": "53af2743308f4ae6a627255aaa288706d331d567c99e1cb615684dc3abba534d",
"size_bytes": 95452033
}
],
"release_date": "2026-08-12",
"signature": "be3e3c6f8ed6b8d573a6329bf546ec1aa739214cd391272ce3b2f0a0ef6b10a4a4c4f9dfa4b9b32dad88326c9feff908fbdb3d1b86d055e5cf876f67f195c60b",
"release_date": "2026-08-10",
"signature": "902778710674486d5919e4abb1bf5540521c9ef55b50a44a9d64b750812738d51cc193f6675a9969b1c45ae86c2e9e44ab3d8daf8aa1d439799d7f7846e27d04",
"signed_by": "did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT",
"version": "1.8.0-alpha"
"version": "1.7.129-alpha"
}