Compare commits

...
Author SHA1 Message Date
archipelago db52c06a72 chore(catalog): sign Cuprate registry update 2026-09-07 05:12:05 -04:00
archipelago 4b14b62e74 chore: publish release v1.8.11-alpha
Demo images / Build & push demo images (push) Successful in 3m40s
2026-09-07 04:35:48 -04:00
archipelago 5da91e4099 chore: prepare release v1.8.11-alpha 2026-09-07 04:32:02 -04:00
archipelago 62731cc729 test(ui): use shipped app for generated launch port check 2026-09-07 03:30:29 -04:00
archipelago 5e17ace690 style(openwrt): format TollGate installer 2026-09-07 03:26:31 -04:00
archipelago b010471a4a chore(release): prep v1.8.11 notes and link checks 2026-09-07 03:26:01 -04:00
ai c4ede96517 Merge PR #154: docs(openwrt): OpenWrt Gateway setup guide + live-tested fixes
Demo images / Build & push demo images (push) Successful in 3m52s
2026-09-07 07:24:33 +00:00
ai be06e1a502 Merge PR #153: fix(cuprate): enable fast_sync and raise DB cache 2026-09-07 07:24:25 +00:00
ssmithxandClaude Sonnet 5 f9a1ef031c fix(cuprate): front the restricted RPC port with a Tor onion
The restricted-RPC port (18090) was `auth: none`, which the app gate
treats as fully exempt — no onion, no takeover, LAN/Tailscale IP only.
Flip it to `auth: open`: the gate still binds the external addresses
and fronts a Tor onion for the port, just without a dashboard login
challenge, since Monero wallet clients (Feather, monero-wallet-rpc,
GUI) speak plain HTTP JSON-RPC and can't hold a session cookie.

P2P (18183) stays `none` — no reason to Tor-front raw gossip.

Regenerated releases/app-catalog.json (unsigned) to embed the updated
manifest; needs scripts/sign-catalog.sh before it takes effect on any
node, since origin (catalog) wins over disk for catalog-covered apps.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NZnsiMtyJxiJBuvv7yLPUF
2026-09-03 14:51:24 +00:00
ssmithxandClaude Sonnet 5 cf240df4b6 fix(cuprate): enable fast_sync and raise DB cache — sustained 45% CPU
The default manifest baked in the exact broken config found on an
affected fleet node: no fast_sync (defaults false, forcing full ring-sig/
RandomX verification on every block) and target_max_memory capped at
~2.8GiB, which starved cuprated's DB cache into constant eviction/flush
(595GB/24h of block I/O on a node just appending ~2MB blocks every 2
minutes). A reference node with fast_sync = true and an 8GiB cache ran
at 2.8% CPU at the same chain height and block rate.

Set fast_sync = true and target_max_memory = 8GiB to match the healthy
reference config, and raise resources.memory_limit from 4Gi to 10Gi so
the container still has headroom above the larger cache.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RR7jRaicvqsJaqQQ92jpPQ
2026-09-03 08:56:52 +00:00
14 changed files with 216 additions and 78 deletions
+8
View File
@@ -1,5 +1,13 @@
# Changelog
## v1.8.11-alpha (2026-09-07)
- **Cuprate now syncs without burning a core for days.** The app's shipped config now enables Cuprate's checkpoint-backed `fast_sync` path, raises the database cache to 8 GiB, and gives the container a 10 GiB memory limit so the cache has real headroom. A live comparison that motivated the change saw the affected node sit around 45% CPU while the corrected config held near low single digits at the same chain height and block rate. The restricted RPC remains fronted through the safe app gate/Tor path.
- **OpenWrt Gateway setup is documented from a real install, and two setup bugs are fixed.** The new guide walks a node operator through flashing a GL.iNet AX3000 to stock OpenWrt, pairing it with Archipelago, and installing TollGate pay-as-you-go WiFi. The installer now finds `opkg`/`apk` through the router's actual `PATH` instead of assuming `/usr/bin`, the UI no longer sends an empty password over a saved router connection, and the pinned TollGate package moves to `v0.5.0` with a native `.apk` install path where upstream provides one.
- **Release publishing now checks the public Gitea download links before a manifest goes live.** The publisher already fetched every artifact back and verified its size and SHA-256; this release adds a second guard for the release page itself, so a bad Gitea `ROOT_URL` or proxy setting cannot publish working files behind broken public HTTPS download links.
## v1.8.10-alpha (2026-09-02)
- **Lightning sends work again — v1.8.9's payment switch lost the fee budget.** Moving payments to LND 0.21's supported route (Router.SendPaymentV2) shipped without a fee limit, and the v2 API treats an absent limit as **zero allowed fees**: every real route carries a routing fee, so the pathfinder rejected them all and the wallet answered "No route to the recipient" on every send — all day, on healthy channels with plenty of liquidity. The router debug log made it unambiguous (`fee_limit=0 mSAT` on every failing wallet payment; the same payment succeeded by hand the moment a fee limit was set). Payments now carry lncli's default budget (the payment amount), the wallet's amount handling for zero-value invoices is preserved, and a unit test pins the limit can never be zero again.
+35 -14
View File
@@ -45,7 +45,12 @@ app:
resources:
cpu_limit: 0
memory_limit: 4Gi
# Raised from 4Gi alongside target_max_memory below (see files[] comment)
# — 2026-09-03 incident: a 4Gi/3GB-cache config starved
# cuprated's DB cache into constant eviction/flush, driving 45% sustained
# CPU and ~595GB/24h of block I/O on a fully-synced node. 10Gi leaves
# headroom above the 8GiB cache for the process itself.
memory_limit: 10Gi
disk_limit: 300Gi
security:
@@ -82,17 +87,21 @@ app:
# bind without an explicit i_know_what_im_doing override.
# Restricted RPC: Monero's own purpose-built safe-for-public subset —
# what wallets use when connecting to a "remote node". Disabled by
# cuprated's own default; enabled via files[] below. A dashboard login
# would break wallet clients connecting programmatically, same
# reasoning as electrumx's port. The daemon still uses its canonical
# container port 18089, but Penpot already owns host port 18089, so this
# maps the public host port to the free 18090 instead.
# cuprated's own default; enabled via files[] below. `open`, not `gated`:
# the gate still takes the port over (loopback pin, external binds,
# fronts the Tor onion) but skips the dashboard login challenge, same
# reasoning as electrumx's port — wallet clients (Feather,
# monero-wallet-rpc, GUI) speak plain HTTP JSON-RPC programmatically and
# cannot complete a browser login or hold a session cookie. The daemon
# still uses its canonical container port 18089, but Penpot already owns
# host port 18089, so this maps the public host port to the free 18090
# instead.
- host: 18090
container: 18089
protocol: tcp
auth: none
auth: open
auth_rationale: >-
Monero restricted RPC — the subset upstream considers safe for public/remote-node use. Wallets (Feather, monero-wallet-rpc, GUI) connect directly over plain HTTP JSON-RPC and cannot hold a dashboard session cookie.
Monero restricted RPC — the subset upstream considers safe for public/remote-node use. Wallets (Feather, monero-wallet-rpc, GUI) connect directly over plain HTTP JSON-RPC and cannot complete a browser login or hold a dashboard session cookie.
volumes:
- type: bind
@@ -103,11 +112,23 @@ app:
# Settings that need to differ from cuprated's own documented defaults
# (verified against `cuprated --generate-config` and `--dry-run` locally,
# 2026-08-21):
# - fast_sync: cuprated's own default is false, which performs full
# cryptographic verification (ring signatures + RandomX PoW) on every
# incoming block instead of trusting checkpointed history. Root-caused
# 2026-09-03 as the dominant cause of a sustained 45% CPU node,
# vs. 2.8% on a reference node with fast_sync = true — same chain height, same
# block rate. Set explicitly rather than relying on the binary
# default so fresh deploys don't silently regress into full-verify.
# - target_max_memory: cuprated's own default auto-detects total *host*
# RAM via sysinfo, which inside a memory-limited container would let
# it size caches far past what resources.memory_limit above actually
# grants — same class of problem bitcoin-knots' -dbcache sizing
# comment addresses. Set explicitly, comfortably under the 4Gi limit.
# comment addresses. Set explicitly, comfortably under the 10Gi limit.
# Previously 3000000000 (~2.8GiB); that starved the DB cache and
# forced constant eviction/flush (595GB/24h block I/O on a node just
# appending ~2MB blocks every 2 minutes) — raised to 8GiB, matching
# the healthy reference node, and
# resources.memory_limit above raised in step to keep headroom above it.
# - rpc.restricted.enable: cuprated ships this off by default; flip on
# so the auth:none host port above actually serves something instead
# of refusing every connection. port stays at its documented default
@@ -128,21 +149,21 @@ app:
# - tracing.stdout.level / tracing.file.{level,max_log_files}: an
# operator reading Cuprated.toml on disk should be able to see and
# tune the log level directly instead of the file silently omitting
# the whole [tracing] table (verified live on amishparadise
# the whole [tracing] table (verified live on the affected node
# 2026-09-01: the deployed file had no [tracing] section at all, and
# the level was only discoverable by running `cuprated
# --generate-config` and diffing). file.level is set to "info", NOT
# cuprated's own raw default of "debug" — matches the reference dev
# config this app was built and tested against
# (ssmithx@archy-dev-pa:/home/ssmithx/cuprate/Cuprated.toml,
# verified 2026-09-01), which deliberately runs file logging quieter
# config this app was built and tested against (verified 2026-09-01),
# which deliberately runs file logging quieter
# than the binary default. max_log_files similarly follows that
# reference (14, not the binary default of 7).
files:
- path: /var/lib/archipelago/cuprate/Cuprated.toml
content: |
network = "Mainnet"
target_max_memory = 3000000000
fast_sync = true
target_max_memory = 8589934592
[rpc.restricted]
enable = true
+1 -1
View File
@@ -104,7 +104,7 @@ dependencies = [
[[package]]
name = "archipelago"
version = "1.8.10-alpha"
version = "1.8.11-alpha"
dependencies = [
"anyhow",
"archipelago-container",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "archipelago"
version = "1.8.10-alpha"
version = "1.8.11-alpha"
edition = "2021"
license.workspace = true
description = "Archipelago Bitcoin Node OS - Native backend"
+8 -8
View File
@@ -24,12 +24,12 @@ const TOLLGATE_VERSION: &str = "v0.5.0";
/// Source: https://github.com/OpenTollGate/tollgate-module-basic-go/releases/tag/v0.5.0
fn ipk_url(arch: &str) -> Option<String> {
let name = match arch {
"mips_24kc" => "mips_24kc",
"mipsel_24kc" => "mipsel_24kc",
"mips_24kc" => "mips_24kc",
"mipsel_24kc" => "mipsel_24kc",
"aarch64_cortex-a53" => "aarch64_cortex-a53",
"aarch64_cortex-a72" => "aarch64_cortex-a72",
"arm_cortex-a7" => "arm_cortex-a7",
"x86_64" => "x86_64",
"arm_cortex-a7" => "arm_cortex-a7",
"x86_64" => "x86_64",
_ => return None,
};
Some(format!(
@@ -69,8 +69,9 @@ pub fn install_tollgate(router: &Router) -> Result<()> {
}
// Package not in any feed — download the .ipk directly.
let arch = router
.run_ok("opkg print-architecture | grep -v all | grep -v noarch | tail -1 | awk '{print $2}'")?;
let arch = router.run_ok(
"opkg print-architecture | grep -v all | grep -v noarch | tail -1 | awk '{print $2}'",
)?;
let arch = arch.trim();
let url = ipk_url(arch).ok_or_else(|| {
@@ -162,8 +163,7 @@ pub fn install_tollgate_apk_native(router: &Router) -> Result<()> {
size
);
}
let (add_out, add_code) =
router.run("apk add --allow-untrusted /tmp/tollgate.apk 2>&1")?;
let (add_out, add_code) = router.run("apk add --allow-untrusted /tmp/tollgate.apk 2>&1")?;
router.run_ok("rm -f /tmp/tollgate.apk")?;
if add_code != 0 {
anyhow::bail!("TollGate .apk install failed: {}", add_out.trim());
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "neode-ui",
"version": "1.8.10-alpha",
"version": "1.8.11-alpha",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "neode-ui",
"version": "1.8.10-alpha",
"version": "1.8.11-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.10-alpha",
"version": "1.8.11-alpha",
"type": "module",
"scripts": {
"start": "./start-dev.sh",
@@ -64,10 +64,10 @@ describe('appSessionConfig', () => {
configurable: true,
})
// did-wallet's manifest publishes host port 8088 (apps/did-wallet/
// searxng's manifest publishes host port 8888 (apps/searxng/
// manifest.yml) — assert against the manifest-generated value, which is
// exactly what this test exists to protect.
expect(resolveAppUrl('did-wallet')).toBe('http://192.0.2.10:8088')
expect(resolveAppUrl('searxng')).toBe('http://192.0.2.10:8888')
})
it('does not treat service-only tcp ports as web launch surfaces', () => {
@@ -362,6 +362,18 @@ init()
</button>
</div>
<div class="overflow-y-auto flex-1 min-h-0 space-y-6 pr-1">
<!-- v1.8.11-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.11-alpha</span>
<span class="text-xs text-white/40">September 7, 2026</span>
</div>
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
<p><strong>Cuprate now syncs without burning a core for days.</strong> The app's shipped config now enables Cuprate's checkpoint-backed fast_sync path, raises the database cache to 8 GiB, and gives the container a 10 GiB memory limit so the cache has real headroom. A live comparison that motivated the change saw the affected node sit around 45% CPU while the corrected config held near low single digits at the same chain height and block rate. The restricted RPC remains fronted through the safe app gate/Tor path.</p>
<p><strong>OpenWrt Gateway setup is documented from a real install, and two setup bugs are fixed.</strong> The new guide walks a node operator through flashing a GL.iNet AX3000 to stock OpenWrt, pairing it with Archipelago, and installing TollGate pay-as-you-go WiFi. The installer now finds opkg/apk through the router's actual PATH instead of assuming /usr/bin, the UI no longer sends an empty password over a saved router connection, and the pinned TollGate package moves to v0.5.0 with a native .apk install path where upstream provides one.</p>
<p><strong>Release publishing now checks the public Gitea download links before a manifest goes live.</strong> The publisher already fetched every artifact back and verified its size and SHA-256; this release adds a second guard for the release page itself, so a bad Gitea ROOT_URL or proxy setting cannot publish working files behind broken public HTTPS download links.</p>
</div>
</div>
<!-- v1.8.10-alpha -->
<div>
<div class="flex items-center gap-2 mb-3">
@@ -369,9 +381,9 @@ init()
<span class="text-xs text-white/40">September 2, 2026</span>
</div>
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
<p><strong>Lightning sends work again.</strong> v1.8.9's move to LND 0.21's supported payment route shipped without a fee budget, and the API treats a missing one as zero allowed fees — so every wallet send failed "No route to the recipient" all day, on perfectly healthy channels. Payments now carry a proper fee budget and a test keeps it from ever regressing.</p>
<p><strong>A channel that drops its peer link now heals itself — on every node.</strong> Restarting LND (an app update, a reboot, container churn) can leave a channel's peer connection down for hours while both endpoints keep the channel flagged disabled in the routing graph: the node looks perfectly healthy, the wallet shows balance, and every payment in either direction fails "no route to the recipient". The daemon now watches the channel graph as desired state — every open channel should have a live peer — and reconnects any that don't. Nodes without LND are untouched; an unreachable peer is retried gently.</p>
<p><strong>The Lightning wallet says what's actually wrong, instead of "you have no channel".</strong> Trying to send while a channel you just opened was still confirming — or when all its balance sits on the far side — produced a modal claiming you had no channel at all, and payment routing failures even showed the receiving copy. The gate now reads your real channel list: a confirming channel gets "it unlocks automatically once confirmed, nothing is needed from you", a far-side balance gets "you can receive, but there's nothing to send right now", and only a genuinely channel-less node is sent to open one.</p>
<p><strong>Lightning sends work again — v1.8.9's payment switch lost the fee budget.</strong> Moving payments to LND 0.21's supported route (Router.SendPaymentV2) shipped without a fee limit, and the v2 API treats an absent limit as <strong>zero allowed fees</strong>: every real route carries a routing fee, so the pathfinder rejected them all and the wallet answered "No route to the recipient" on every send — all day, on healthy channels with plenty of liquidity. The router debug log made it unambiguous (fee_limit=0 mSAT on every failing wallet payment; the same payment succeeded by hand the moment a fee limit was set). Payments now carry lncli's default budget (the payment amount), the wallet's amount handling for zero-value invoices is preserved, and a unit test pins the limit can never be zero again.</p>
<p><strong>A channel that drops its peer link now heals itself — on every node.</strong> Restarting LND (an app update, a reboot, container churn) can leave a channel's peer connection down for hours while both endpoints keep the channel flagged disabled in the routing graph: the node looks perfectly healthy, the wallet shows balance, and every payment in either direction fails "no route to the recipient". Observed live: a node's only channel sat unroutable for ~17 hours after the LND 0.21.2 update, with no sign of it in any dashboard. The daemon now watches the channel graph as desired state — every open channel should have a live peer — and reconnects any that don't, using the peer's advertised addresses. Nodes without LND are untouched; an unreachable peer is retried gently, not hammered.</p>
<p><strong>The Lightning wallet states the node's real funding state instead of "you have no channel."</strong> Trying to send while a freshly opened channel was still waiting for on-chain confirmations — or when all its balance sits on the far side — raised a modal that claimed the node had NO channel at all (the outbound sum is legitimately zero in both states), pointed the user at opening a second channel, and — for payment routing failures — even showed the <em>receiving</em> copy. The funding gate now reads the channel list it already fetched: a confirming channel gets "it unlocks automatically once confirmed, nothing is needed from you", a far-side balance gets "you can receive, but there's nothing to send right now", a routing/liquidity payment failure says so instead of claiming channel problems, and only a genuinely channel-less node keeps the open-one guidance.</p>
</div>
</div>
<!-- v1.8.9-alpha -->
@@ -381,12 +393,13 @@ init()
<span class="text-xs text-white/40">September 1, 2026</span>
</div>
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
<p><strong>Lightning sends work again after the LND 0.21.2 update.</strong> LND 0.21 removed the payment route the node's backend used — every send answered "Not Found". Payments now go through LND's supported v2 router route, slow multi-hop payments are still tracked to completion (never falsely declared failed), failures explain themselves in plain language, and a new test speaks the payment route directly at release-gate time so an image/backend mismatch like this can never ship silently again.</p>
<p><strong>HTTP and HTTPS both work, and no longer break each other.</strong> The HTTPS listener used to pin a year-long browser policy (HSTS); once your browser had visited HTTPS, it silently rewrote the HTTP dashboard's calls to HTTPS — cross-origin, so everything showed "Failed to fetch"/CORS errors while the node was healthy. The pin is gone, the HTTPS listener now actively clears the stale policy browsers already cached (visit HTTPS once after this update to clear yours), and plain-HTTP access — which is deliberate on nodes whose self-signed certificate you haven't installed — keeps working exactly as before.</p>
<p><strong>Apps open over HTTPS again, including Mempool, Bitcoin and IndeeHub.</strong> The launcher looked each app's port policy up in the signed catalog under the name you click, but the catalog lists that port under the app that owns it — so Mempool "did not connect", Bitcoin opened a plain-http tab, and Nostr sign-in on IndeeHub silently did nothing over HTTPS. Launches now follow the alias to the owning manifest, the catalog is loaded before the first app you open (not just in the App Store), and the Nostr bridge replies to the app frame's real origin instead of a stale recorded address.</p>
<p><strong>Nginx Proxy Manager starts again.</strong> Its manifest was missing two things its image requires — the LetsEncrypt folder mount and the permission to bind low ports — leaving it in an endless restart loop on nodes that had it installed. Both are declared now; your existing certificates are untouched, and the fix arrives via the signed catalog without waiting for this release.</p>
<p><strong>Portainer's first-run token is on the app page, not buried in "server logs".</strong> New Portainer versions hand the first admin a one-time setup token that was only printed in the container logs — on this box, that token now appears with your app's other credentials, with a copy button, and disappears once setup is done.</p>
<p><strong>The Lightning wallet says what's actually wrong, instead of "you have no channel".</strong> Trying to send while a channel you just opened was still confirming — or when all its balance sits on the far side — produced a modal claiming you had no channel at all. The gate now looks at your real channel list: a confirming channel gets "it unlocks automatically once confirmed, nothing needed from you", a far-side balance gets "you can receive but there's nothing to send right now", and only a genuinely channel-less node is sent to open one.</p>
<p><strong>Lightning sends work again after the LND 0.21.2 update.</strong> LND 0.21 removed the old synchronous payment route the node's backend paid through (/v1/channels/transactions) — every Lightning send answered the literal "Not Found" and the wallet showed "Payment failed: Not Found". The backend now pays through the supported Router.SendPaymentV2 route, keeps the same settle-then-report behaviour (a slow multi-hop payment is still tracked to completion, never falsely declared failed), and translates LND's failure reasons into plain advice. A new gate test speaks the payment route directly against the running LND, so an image/backend skew like this can never ship silently again.</p>
<p><strong>The node no longer pins HSTS — HTTP access is a supported mode, and it stays working.</strong> The HTTPS listener used to send Strict-Transport-Security: max-age=31536000; includeSubDomains; browsers that visited HTTPS once cached that and then silently upgraded the still-open HTTP dashboard's calls to HTTPS, which is a scheme change — cross-origin — so every request died as "CORS blocked / Failed to fetch" while the node was perfectly healthy. The HTTPS listener now actively clears the cached policy (max-age=0) and port 80 sends no HSTS at all, which is deliberate: the node's certificate is optional and self-signed, and devices that haven't installed the CA must keep plain-HTTP access (that's what Settings → Node certificate is for). If your browser already cached the old policy, visiting the dashboard over HTTPS once after this update clears it; a gate test now refuses any config that reintroduces the pin.</p>
<p><strong>App frames open over HTTPS again — including the ones that "did not connect."</strong> The launcher asked the signed catalog for each app's port policy under the name you click ("Mempool Web", "Bitcoin Knots"), but the catalog declares those ports under the manifest that owns them (the Mempool web container, Bitcoin UI). The lookup missed, the launcher handed the iframe an http:// address, and the browser blocked it as mixed content — the app tile went blank or spun forever. Port resolution now follows launch aliases (mempool-web, bitcoin-knots/bitcoin-core, lnd, electrs and friends), falls back to a port-wide catalog scan when the id is unknown, and the catalog is warmed as soon as the dashboard loads rather than only in the App Store, so the very first app you open already knows which ports serve TLS.</p>
<p><strong>Signing in to IndeeHub with Nostr works over HTTPS.</strong> The NIP-07 bridge compared the app frame's origin for exact equality with the recorded http:// app URL — a frame the browser upgraded to HTTPS (or any scheme change) was silently ignored, and replies addressed to the stale origin were refused outright, so Nostr sign-in quietly did nothing. The bridge now matches host and port (scheme intentionally ignored) and always replies to the frame's real origin.</p>
<p><strong>Nginx Proxy Manager starts again.</strong> Converting it to a platform manifest dropped two things its image needs: the /etc/letsencrypt mount its boot script hard-requires, and the NET_BIND_SERVICE capability its internal nginx needs to bind ports 80/443/81 under the orchestrator's --cap-drop=ALL. The result was an endless start/die loop (a node watched it restart 3,176 times). Both are declared in its manifest now, its certs live on unchanged under the same persistent app directory, and the signed catalog carries the fix so installed nodes heal on the next update.</p>
<p><strong>Portainer's first-run token is in the app page, not buried in "server logs."</strong> New Portainer versions mint a one-time setup token on a fresh install and print it only to the container logs — on an appliance that meant telling the user to go read a server log to get into their own app. The token now appears in the same launch interstitial as app login credentials (with a copy button), only while first-run setup is actually pending; once the admin account exists the card disappears on its own.</p>
<p><strong>The Lightning wallet states the node's real funding state instead of "you have no channel."</strong> Trying to send while a freshly opened channel was still waiting for on-chain confirmations — or when all its balance sits on the far side — raised a modal that claimed the node had no channel at all (the outbound sum is legitimately zero in both states). The funding gate now reads the channel list it already fetched: a confirming channel gets "it unlocks automatically once confirmed, nothing is needed from you", a far-side balance gets "you can receive, but there's nothing to send right now", a routing/liquidity payment failure says so instead of pointing at channel setup, and only a genuinely channel-less node is sent to open one.</p>
</div>
</div>
<!-- v1.8.8-alpha -->
+17 -17
View File
@@ -1,29 +1,29 @@
{
"changelog": [
"**Lightning sends work again — v1.8.9's payment switch lost the fee budget.** Moving payments to LND 0.21's supported route (Router.SendPaymentV2) shipped without a fee limit, and the v2 API treats an absent limit as **zero allowed fees**: every real route carries a routing fee, so the pathfinder rejected them all and the wallet answered \"No route to the recipient\" on every send — all day, on healthy channels with plenty of liquidity. The router debug log made it unambiguous (`fee_limit=0 mSAT` on every failing wallet payment; the same payment succeeded by hand the moment a fee limit was set). Payments now carry lncli's default budget (the payment amount), the wallet's amount handling for zero-value invoices is preserved, and a unit test pins the limit can never be zero again.",
"**A channel that drops its peer link now heals itself — on every node.** Restarting LND (an app update, a reboot, container churn) can leave a channel's peer connection down for hours while both endpoints keep the channel flagged disabled in the routing graph: the node looks perfectly healthy, the wallet shows balance, and every payment in either direction fails \"no route to the recipient\". Observed live: a node's only channel sat unroutable for ~17 hours after the LND 0.21.2 update, with no sign of it in any dashboard. The daemon now watches the channel graph as desired state — every open channel should have a live peer — and reconnects any that don't, using the peer's advertised addresses. Nodes without LND are untouched; an unreachable peer is retried gently, not hammered.",
"**The Lightning wallet states the node's real funding state instead of \"you have no channel.\"** Trying to send while a freshly opened channel was still waiting for on-chain confirmations — or when all its balance sits on the far side — raised a modal that claimed the node had NO channel at all (the outbound sum is legitimately zero in both states), pointed the user at opening a second channel, and — for payment routing failures — even showed the *receiving* copy. The funding gate now reads the channel list it already fetched: a confirming channel gets \"it unlocks automatically once confirmed, nothing is needed from you\", a far-side balance gets \"you can receive, but there's nothing to send right now\", a routing/liquidity payment failure says so instead of claiming channel problems, and only a genuinely channel-less node keeps the open-one guidance."
"**Cuprate now syncs without burning a core for days.** The app's shipped config now enables Cuprate's checkpoint-backed `fast_sync` path, raises the database cache to 8 GiB, and gives the container a 10 GiB memory limit so the cache has real headroom. A live comparison that motivated the change saw the affected node sit around 45% CPU while the corrected config held near low single digits at the same chain height and block rate. The restricted RPC remains fronted through the safe app gate/Tor path.",
"**OpenWrt Gateway setup is documented from a real install, and two setup bugs are fixed.** The new guide walks a node operator through flashing a GL.iNet AX3000 to stock OpenWrt, pairing it with Archipelago, and installing TollGate pay-as-you-go WiFi. The installer now finds `opkg`/`apk` through the router's actual `PATH` instead of assuming `/usr/bin`, the UI no longer sends an empty password over a saved router connection, and the pinned TollGate package moves to `v0.5.0` with a native `.apk` install path where upstream provides one.",
"**Release publishing now checks the public Gitea download links before a manifest goes live.** The publisher already fetched every artifact back and verified its size and SHA-256; this release adds a second guard for the release page itself, so a bad Gitea `ROOT_URL` or proxy setting cannot publish working files behind broken public HTTPS download links."
],
"components": [
{
"current_version": "1.8.10-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.10-alpha/archipelago",
"current_version": "1.8.11-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.11-alpha/archipelago",
"name": "archipelago",
"new_version": "1.8.10-alpha",
"sha256": "6c8bd41fed44cd999cb360c00e1b66a2d19d19812cc2b0c8a1677eec2a9579e6",
"size_bytes": 64178056
"new_version": "1.8.11-alpha",
"sha256": "ae569054edd6b2491beb101815f6809bc00c95a7dbe86bd084bcb9a7c36e1853",
"size_bytes": 64179264
},
{
"current_version": "1.8.10-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.10-alpha/archipelago-frontend-1.8.10-alpha.tar.gz",
"name": "archipelago-frontend-1.8.10-alpha.tar.gz",
"new_version": "1.8.10-alpha",
"sha256": "6b25de8a8e1a4f7fe51594f9bbbe21f5820f417af47a8b309c2dbf8f8723b719",
"size_bytes": 97736297
"current_version": "1.8.11-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.11-alpha/archipelago-frontend-1.8.11-alpha.tar.gz",
"name": "archipelago-frontend-1.8.11-alpha.tar.gz",
"new_version": "1.8.11-alpha",
"sha256": "192fd0470b6ccf66e78c80b4a4c3af5468882b85d81959362a3bd11f88b9d71d",
"size_bytes": 97741740
}
],
"release_date": "2026-09-01",
"signature": "b69926bcb1851ff7d6a5b24519cd4a8015aab4ed4b588ee989d8ce6e3beaeb2cc0eb38078f522ded0d389fe53b7dbcdbf3f40c534b4bfafa5cf4a2ab2c59e40f",
"release_date": "2026-09-07",
"signature": "6449ce6ef35a4ef4fa6d0923bb58a2bff52ea5430d5532496e8f0af9ed52eaec293f19d7bec272dc9bc1af5fb2cdfa0e46068c827a9a4dd9cbef92d1c5845301",
"signed_by": "did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT",
"version": "1.8.10-alpha"
"version": "1.8.11-alpha"
}
+6 -6
View File
@@ -1319,7 +1319,7 @@
"description": "Alternative Monero node implementation in Rust. Independently validates Monero consensus rules, providing a layer of security and redundancy for the network.",
"files": [
{
"content": "network = \"Mainnet\"\ntarget_max_memory = 3000000000\n\n[rpc.restricted]\nenable = true\n\n[tracing.stdout]\nlevel = \"info\"\n\n[tracing.file]\nlevel = \"info\"\nmax_log_files = 14\n",
"content": "network = \"Mainnet\"\nfast_sync = true\ntarget_max_memory = 8589934592\n\n[rpc.restricted]\nenable = true\n\n[tracing.stdout]\nlevel = \"info\"\n\n[tracing.file]\nlevel = \"info\"\nmax_log_files = 14\n",
"overwrite": false,
"path": "/var/lib/archipelago/cuprate/Cuprated.toml"
}
@@ -1350,8 +1350,8 @@
"protocol": "tcp"
},
{
"auth": "none",
"auth_rationale": "Monero restricted RPC — the subset upstream considers safe for public/remote-node use. Wallets (Feather, monero-wallet-rpc, GUI) connect directly over plain HTTP JSON-RPC and cannot hold a dashboard session cookie.",
"auth": "open",
"auth_rationale": "Monero restricted RPC — the subset upstream considers safe for public/remote-node use. Wallets (Feather, monero-wallet-rpc, GUI) connect directly over plain HTTP JSON-RPC and cannot complete a browser login or hold a dashboard session cookie.",
"container": 18089,
"host": 18090,
"protocol": "tcp"
@@ -1360,7 +1360,7 @@
"resources": {
"cpu_limit": 0,
"disk_limit": "300Gi",
"memory_limit": "4Gi"
"memory_limit": "10Gi"
},
"security": {
"capabilities": [],
@@ -5429,7 +5429,7 @@
}
},
"schema": 1,
"signature": "f982faeb9823062d9d39f6e4b38a171b4442cad0f35e74792ea161b5d77246ab9128044acbdc390ec23f921363af2d13bbba66c558b188d14d06a3f9a7f42406",
"signature": "3e87496a7197177ea295eba416cd1ed9a2c41ddca3328a160b1db2c65d39ce813c2b1e63df1313680a8e48e0113e2bbe6118df01df4a777bd33b189e7ef69206",
"signed_by": "did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT",
"updated": "2026-09-01"
"updated": "2026-09-03"
}
+17 -17
View File
@@ -1,29 +1,29 @@
{
"changelog": [
"**Lightning sends work again — v1.8.9's payment switch lost the fee budget.** Moving payments to LND 0.21's supported route (Router.SendPaymentV2) shipped without a fee limit, and the v2 API treats an absent limit as **zero allowed fees**: every real route carries a routing fee, so the pathfinder rejected them all and the wallet answered \"No route to the recipient\" on every send — all day, on healthy channels with plenty of liquidity. The router debug log made it unambiguous (`fee_limit=0 mSAT` on every failing wallet payment; the same payment succeeded by hand the moment a fee limit was set). Payments now carry lncli's default budget (the payment amount), the wallet's amount handling for zero-value invoices is preserved, and a unit test pins the limit can never be zero again.",
"**A channel that drops its peer link now heals itself — on every node.** Restarting LND (an app update, a reboot, container churn) can leave a channel's peer connection down for hours while both endpoints keep the channel flagged disabled in the routing graph: the node looks perfectly healthy, the wallet shows balance, and every payment in either direction fails \"no route to the recipient\". Observed live: a node's only channel sat unroutable for ~17 hours after the LND 0.21.2 update, with no sign of it in any dashboard. The daemon now watches the channel graph as desired state — every open channel should have a live peer — and reconnects any that don't, using the peer's advertised addresses. Nodes without LND are untouched; an unreachable peer is retried gently, not hammered.",
"**The Lightning wallet states the node's real funding state instead of \"you have no channel.\"** Trying to send while a freshly opened channel was still waiting for on-chain confirmations — or when all its balance sits on the far side — raised a modal that claimed the node had NO channel at all (the outbound sum is legitimately zero in both states), pointed the user at opening a second channel, and — for payment routing failures — even showed the *receiving* copy. The funding gate now reads the channel list it already fetched: a confirming channel gets \"it unlocks automatically once confirmed, nothing is needed from you\", a far-side balance gets \"you can receive, but there's nothing to send right now\", a routing/liquidity payment failure says so instead of claiming channel problems, and only a genuinely channel-less node keeps the open-one guidance."
"**Cuprate now syncs without burning a core for days.** The app's shipped config now enables Cuprate's checkpoint-backed `fast_sync` path, raises the database cache to 8 GiB, and gives the container a 10 GiB memory limit so the cache has real headroom. A live comparison that motivated the change saw the affected node sit around 45% CPU while the corrected config held near low single digits at the same chain height and block rate. The restricted RPC remains fronted through the safe app gate/Tor path.",
"**OpenWrt Gateway setup is documented from a real install, and two setup bugs are fixed.** The new guide walks a node operator through flashing a GL.iNet AX3000 to stock OpenWrt, pairing it with Archipelago, and installing TollGate pay-as-you-go WiFi. The installer now finds `opkg`/`apk` through the router's actual `PATH` instead of assuming `/usr/bin`, the UI no longer sends an empty password over a saved router connection, and the pinned TollGate package moves to `v0.5.0` with a native `.apk` install path where upstream provides one.",
"**Release publishing now checks the public Gitea download links before a manifest goes live.** The publisher already fetched every artifact back and verified its size and SHA-256; this release adds a second guard for the release page itself, so a bad Gitea `ROOT_URL` or proxy setting cannot publish working files behind broken public HTTPS download links."
],
"components": [
{
"current_version": "1.8.10-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.10-alpha/archipelago",
"current_version": "1.8.11-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.11-alpha/archipelago",
"name": "archipelago",
"new_version": "1.8.10-alpha",
"sha256": "6c8bd41fed44cd999cb360c00e1b66a2d19d19812cc2b0c8a1677eec2a9579e6",
"size_bytes": 64178056
"new_version": "1.8.11-alpha",
"sha256": "ae569054edd6b2491beb101815f6809bc00c95a7dbe86bd084bcb9a7c36e1853",
"size_bytes": 64179264
},
{
"current_version": "1.8.10-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.10-alpha/archipelago-frontend-1.8.10-alpha.tar.gz",
"name": "archipelago-frontend-1.8.10-alpha.tar.gz",
"new_version": "1.8.10-alpha",
"sha256": "6b25de8a8e1a4f7fe51594f9bbbe21f5820f417af47a8b309c2dbf8f8723b719",
"size_bytes": 97736297
"current_version": "1.8.11-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.11-alpha/archipelago-frontend-1.8.11-alpha.tar.gz",
"name": "archipelago-frontend-1.8.11-alpha.tar.gz",
"new_version": "1.8.11-alpha",
"sha256": "192fd0470b6ccf66e78c80b4a4c3af5468882b85d81959362a3bd11f88b9d71d",
"size_bytes": 97741740
}
],
"release_date": "2026-09-01",
"signature": "b69926bcb1851ff7d6a5b24519cd4a8015aab4ed4b588ee989d8ce6e3beaeb2cc0eb38078f522ded0d389fe53b7dbcdbf3f40c534b4bfafa5cf4a2ab2c59e40f",
"release_date": "2026-09-07",
"signature": "6449ce6ef35a4ef4fa6d0923bb58a2bff52ea5430d5532496e8f0af9ed52eaec293f19d7bec272dc9bc1af5fb2cdfa0e46068c827a9a4dd9cbef92d1c5845301",
"signed_by": "did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT",
"version": "1.8.10-alpha"
"version": "1.8.11-alpha"
}
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
# check-gitea-release-download-links.sh - verify Gitea's public release page
# points users at the canonical HTTPS download URLs, not an internal ROOT_URL.
#
# Usage:
# scripts/check-gitea-release-download-links.sh VERSION ASSET_NAME...
set -euo pipefail
VERSION="${1:-}"
if [ -z "$VERSION" ] || [ "$#" -lt 2 ]; then
echo "usage: $0 VERSION ASSET_NAME..." >&2
exit 2
fi
shift
PUBLIC_BASE="${ARCHY_RELEASE_PUBLIC_BASE:-https://source.archipelago-foundation.org/lfg2025/archy}"
page_url="$PUBLIC_BASE/releases/tag/v$VERSION"
command -v curl >/dev/null 2>&1 || { echo "ERROR: curl required" >&2; exit 2; }
command -v python3 >/dev/null 2>&1 || { echo "ERROR: python3 required" >&2; exit 2; }
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
curl -fsSL "$page_url" -o "$tmp"
python3 - "$tmp" "$PUBLIC_BASE" "$VERSION" "$page_url" "$@" <<'PY'
from html.parser import HTMLParser
from urllib.parse import quote
import sys
html_path, public_base, version, page_url, *assets = sys.argv[1:]
with open(html_path, encoding="utf-8") as f:
html = f.read()
class LinkParser(HTMLParser):
def __init__(self):
super().__init__()
self.hrefs = []
def handle_starttag(self, tag, attrs):
if tag.lower() != "a":
return
attrs = dict(attrs)
href = attrs.get("href")
if href:
self.hrefs.append(href)
parser = LinkParser()
parser.feed(html)
hrefs = set(parser.hrefs)
bad_internal = sorted(
h for h in hrefs
if "/releases/download/" in h and h.startswith(("http://", "https://"))
and not h.startswith(public_base + "/releases/download/")
)
failures = []
for asset in assets:
expected = f"{public_base}/releases/download/v{quote(version)}/{quote(asset)}"
if expected not in hrefs:
matches = sorted(h for h in hrefs if h.endswith("/" + quote(asset)))
if matches:
failures.append(f"{asset}: expected {expected}, found {matches[0]}")
else:
failures.append(f"{asset}: expected {expected}, but no matching release-page link was found")
if bad_internal:
failures.append("release page contains non-canonical download href(s):")
failures.extend(f" {h}" for h in bad_internal[:10])
if failures:
print(f"FAIL: public release page has broken download links: {page_url}", file=sys.stderr)
for failure in failures:
print(f" {failure}", file=sys.stderr)
print(
"Fix the Gitea public URL/proxy configuration so release links are generated "
"from the canonical HTTPS origin, then re-run the publish check.",
file=sys.stderr,
)
sys.exit(1)
print(f"OK: public release page download links use {public_base}")
PY
+11
View File
@@ -145,6 +145,11 @@ echo "Verifying public download URLs (full GET + size + sha256)..."
"$PROJECT_ROOT/scripts/check-release-assets.sh" "$MANIFEST" \
|| fail "asset verification failed — NOT pushing main. The manifest stays off the branch nodes read, so no node sees a version it cannot fetch. Repair the assets and re-run."
"$PROJECT_ROOT/scripts/check-gitea-release-download-links.sh" "$VERSION" \
"archipelago" \
"archipelago-frontend-${VERSION}.tar.gz" \
|| fail "release page download links are not public HTTPS URLs — fix Gitea ROOT_URL/proxy configuration before publishing."
# Assets are proven fetchable — only now may the manifest become live. First
# incorporate concurrent work, then promote in a dedicated commit. Until the
# final push succeeds the remote still serves the previous manifest.
@@ -261,4 +266,10 @@ for b in bad:
sys.exit(1 if bad else 0)
PY
"$PROJECT_ROOT/scripts/check-gitea-release-download-links.sh" "$VERSION" \
"$ISO_NAME" \
"$ISO_NAME.sha256" \
"$ISO_NAME.sha256.json" \
|| fail "ISO is uploaded but the release page links are not public HTTPS URLs — fix Gitea ROOT_URL/proxy configuration."
echo "ISO for v${VERSION} published and verified on $REMOTE."