Compare commits

..
Author SHA1 Message Date
ssmithxandClaude Sonnet 5 5f83fd1fee feat(openwrt): make TollGate payout Lightning address configurable
Archipelago never touched /etc/tollgate/identities.json — the "owner"
payout identity was whatever the router's TollGate install happened to
default to. Confirmed live against archy-x250-pa3: an unmodified upstream
placeholder (tollgate@minibits.cash), meaning 79% of every customer payment
would auto-payout to an address the operator never chose and doesn't
control.

Adds TollGateConfig.payout_address (opt-in — None leaves the router
untouched), config::apply_payout_identity() to merge it into the "owner"
entry of identities.json without disturbing the merchant keypair or the
other profit-share identities, an RPC param on openwrt.provision-tollgate,
and a status field + reconfigure-form input in the OpenWrt Gateway panel.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTdMfVJChCwCCYF1ZTRQLc
2026-09-07 20:23:19 +00:00
ssmithxandClaude Sonnet 5 2b2a14c569 docs(tollgate-sweep): document two live-confirmed drain-CLI bugs
sweep_once() has never actually swept anything: `tollgate wallet drain
cashu` (no flags) blocks on an interactive y/N confirmation that Router::run
can never answer over a non-PTY SSH exec (empty stdin -> EOF -> defaults to
N -> "Operation cancelled." with exit code 0), so the drain_code != 0 check
can't catch it and every tick silently no-ops.

The obvious fix isn't safe either: `--json` skips the prompt, but confirmed
live against archy-x250-pa3 that on a wallet.db with a stale duplicate
per-mint entry (trailing-slash leftover from before the mint_url fix), it
completes a real swap against the good entry, then aborts on the second
(empty, stale) entry and reports "success": false without ever printing or
persisting the resulting token anywhere. 50 sats went from spendable balance
to gone in that one call. Documented so nobody "fixes" this by wiring in
--json before upstream fixes the partial-failure data loss.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTdMfVJChCwCCYF1ZTRQLc
2026-09-07 17:32:26 +00:00
ssmithxandClaude Sonnet 5 f355ae753b fix(openwrt): close TollGate free-access gap and mint URL mismatch
Two bugs found live against archy-x250-pa3: TollGate-3458 (the upstream
tollgate-module-basic-go installer's own default AP, rebranded from
OpenWrt's factory default wireless.default_radioN sections) was left
bound to `network=lan` — wide open, unmetered, and sharing the router's
admin LAN — because install_ipk() runs the upstream package's own
uci-defaults scripts but nothing reconciled the AP they create with the
separate `tollgate` network/bridge/firewall this project's own
provision_ssid() sets up for the "archipelago" SSID. Fixed by folding any
default_radioN section left on `lan` onto the `tollgate` network right
after it's created.

Separately, a caller-supplied mint_url with a trailing slash
(https://mint.minibits.cash/Bitcoin/) got written byte-for-byte into
accepted_mints[0].url, which tollgate-wrt string-compares exactly against
a token's embedded (slash-less) mint URL — rejecting every otherwise-valid
token as an "untrusted mint". Fixed by trimming trailing slashes before
the value is used anywhere.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTdMfVJChCwCCYF1ZTRQLc
2026-09-07 14:37:12 +00:00
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
10 changed files with 3502 additions and 3319 deletions
+27 -1
View File
@@ -150,6 +150,7 @@ impl RpcHandler {
"min_steps": router.uci_get("tollgate.main.min_steps").ok().and_then(|v| v.parse::<u32>().ok()).unwrap_or(1),
"currency": router.uci_get("tollgate.main.currency").unwrap_or_default(),
"mint_url": router.uci_get("tollgate.main.mint_url").unwrap_or_default(),
"payout_address":router.uci_get("tollgate.main.payout_address").unwrap_or_default(),
})
} else {
serde_json::json!({ "installed": false })
@@ -199,10 +200,15 @@ impl RpcHandler {
///
/// Params: `{ "host": "192.168.1.1", "ssh_user": "root", "ssh_password": "",
/// "price_sats": 10, "step_size_ms": 60000, "min_steps": 1,
/// "mint_url": "<optional override>" }`
/// "mint_url": "<optional override>",
/// "payout_address": "<optional Lightning address>" }`
///
/// `mint_url` defaults to `http://<this node's IP>:3338` — the local Cashu
/// mint that must be running as an Archy app before calling this endpoint.
///
/// `payout_address` sets the "owner" identity's Lightning address for
/// TollGate's own built-in payout (see `config::apply_payout_identity`).
/// Omitted or blank leaves whatever's already on the router untouched.
pub(super) async fn handle_openwrt_provision_tollgate(
&self,
params: Option<serde_json::Value>,
@@ -240,12 +246,31 @@ impl RpcHandler {
.unwrap_or_default();
let default_mint_url = format!("http://{}:{}", self.config.host_ip, LOCAL_MINT_PORT);
// Trim trailing slash(es): tollgate-wrt matches a token's embedded
// mint URL against this value with an exact string compare, and
// Cashu wallets (Minibits included) encode mint URLs without a
// trailing slash. A stray slash here means every otherwise-valid
// token gets rejected as "untrusted mint" — confirmed live against
// archy-x250-pa3 2026-09-07 with a manually-entered
// "https://mint.minibits.cash/Bitcoin/".
let mint_url = p
.get("mint_url")
.and_then(|v| v.as_str())
.unwrap_or(&default_mint_url)
.trim_end_matches('/')
.to_string();
// `None` (not sent, or sent blank) leaves whatever's already on the
// router untouched — see apply_payout_identity's doc comment for why
// that matters (an upstream-default placeholder otherwise survives
// forever, since nothing else ever writes this field).
let payout_address = p
.get("payout_address")
.and_then(|v| v.as_str())
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_string);
let config = TollGateConfig {
ssid: "archipelago".to_string(),
mint_url,
@@ -256,6 +281,7 @@ impl RpcHandler {
.unwrap_or(60_000),
min_steps: p.get("min_steps").and_then(|v| v.as_u64()).unwrap_or(1) as u32,
enabled: p.get("enabled").and_then(|v| v.as_bool()).unwrap_or(true),
payout_address,
};
// Blocking SSH session, and provision runs `opkg install` over it —
+40
View File
@@ -22,6 +22,46 @@ use crate::wallet::ecash;
///
/// Returns the total sats swept in (0 if there was nothing to do, including
/// when no router is configured or it doesn't have TollGate installed).
///
/// # KNOWN BROKEN as of 2026-09-07 — do not "fix" by adding `--json` without
/// reading the rest of this comment first.
///
/// Confirmed live against archy-x250-pa3, two stacked bugs in the upstream
/// `tollgate` CLI, not in this function:
///
/// 1. **This call never actually drains anything.** `tollgate wallet drain
/// cashu` (no flags — what this function runs) prints an interactive
/// `Are you sure? (y/N)` confirmation and reads stdin for the answer.
/// `Router::run` executes over SSH with no PTY and empty stdin, so it
/// always reads EOF, defaults to "N", and prints "Operation cancelled." —
/// **with exit code 0**. The `drain_code != 0` check below can never catch
/// this, so every single tick silently falls through to "no `Token:`
/// lines found" → `Ok(0)`. No error, no log line (even at `warn!`), just
/// quiet total inaction, forever. This has presumably never swept a
/// single sat on any node.
///
/// 2. **The obvious fix is worse.** `tollgate --json wallet drain cashu`
/// *does* skip the confirmation prompt — but confirmed live: when the
/// wallet's internal per-mint registry holds more than one entry for what
/// is really the same mint (here: `https://mint.minibits.cash/Bitcoin` vs.
/// a stale `.../Bitcoin/` — leftover from before the trailing-slash
/// `mint_url` fix elsewhere in this codebase; `wallet.db` still had a
/// proof/registry entry keyed under the old slashed URL even after
/// `config.json` was corrected), the CLI appears to complete a real swap
/// against the *good* entry — spending and irreversibly consuming the
/// original proofs, per how Cashu swaps work — then hits the second,
/// empty, stale-keyed entry, reports the whole command as
/// `"success": false`, and **never prints or persists the resulting
/// token anywhere** (checked every location its own "will be saved to a
/// file" warning implies: `/etc/tollgate/ecash/`, `/root`, `/tmp`,
/// nothing). Balance went from 50 sats to 0 across that one call. The
/// funds are gone — there is no undo once a swap is submitted to the
/// mint.
///
/// Do not wire `--json` into this function until upstream fixes partial
/// per-mint failure handling in `drain cashu` to preserve/return whatever it
/// already successfully drained. Until then, the current silent-no-op
/// behavior, while useless, is at least safe.
pub async fn sweep_once(data_dir: &Path) -> Result<u64> {
let cfg = net_router::load_router_config(data_dir).await?;
if !cfg.configured {
+88 -13
View File
@@ -23,6 +23,14 @@ pub struct TollGateConfig {
pub min_steps: u32,
/// Whether the TollGate service should be running and enabled at boot.
pub enabled: bool,
/// Operator's own Lightning address for the daemon's built-in payout
/// (the "owner" entry in `/etc/tollgate/identities.json`, `profit_share`
/// weight 0.79 in the upstream default). `None` leaves whatever is
/// already on the router untouched — which, on a router whose TollGate
/// wasn't provisioned through this project, is an unmodified upstream
/// placeholder nobody actually controls (confirmed live against
/// archy-x250-pa3 2026-09-07: shipped as `tollgate@minibits.cash`).
pub payout_address: Option<String>,
}
impl Default for TollGateConfig {
@@ -34,6 +42,7 @@ impl Default for TollGateConfig {
step_size_ms: 60_000,
min_steps: 1,
enabled: true,
payout_address: None,
}
}
}
@@ -46,19 +55,27 @@ impl Default for TollGateConfig {
/// tollgate.main.enabled` etc.); changing pricing or the mint here has no
/// effect on what the daemon advertises or accepts.
pub fn apply(router: &Router, cfg: &TollGateConfig) -> Result<()> {
router.uci_apply(
"tollgate",
&[
("tollgate.main", "tollgate"),
("tollgate.main.enabled", if cfg.enabled { "1" } else { "0" }),
("tollgate.main.metric", "milliseconds"),
("tollgate.main.step_size", &cfg.step_size_ms.to_string()),
("tollgate.main.min_steps", &cfg.min_steps.to_string()),
("tollgate.main.price_per_step", &cfg.price_sats.to_string()),
("tollgate.main.currency", "sat"),
("tollgate.main.mint_url", &cfg.mint_url),
],
)?;
let step_size = cfg.step_size_ms.to_string();
let min_steps = cfg.min_steps.to_string();
let price_sats = cfg.price_sats.to_string();
let mut pairs = vec![
("tollgate.main", "tollgate"),
("tollgate.main.enabled", if cfg.enabled { "1" } else { "0" }),
("tollgate.main.metric", "milliseconds"),
("tollgate.main.step_size", step_size.as_str()),
("tollgate.main.min_steps", min_steps.as_str()),
("tollgate.main.price_per_step", price_sats.as_str()),
("tollgate.main.currency", "sat"),
("tollgate.main.mint_url", &cfg.mint_url),
];
// Status-display only (see doc comment above) — only written when the
// caller actually supplied one, so a reconfigure that doesn't touch
// payout leaves whatever's already there alone.
if let Some(addr) = &cfg.payout_address {
pairs.push(("tollgate.main.payout_address", addr));
}
router.uci_apply("tollgate", &pairs)?;
Ok(())
}
@@ -97,3 +114,61 @@ pub fn apply_daemon_config(router: &Router, cfg: &TollGateConfig) -> Result<()>
.context("upload /etc/tollgate/config.json")?;
Ok(())
}
/// Set the operator's own payout Lightning address in
/// `/etc/tollgate/identities.json` — the "owner" entry under
/// `public_identities` (`profit_share` weight 0.79 in the upstream default;
/// the other entries there are revenue-share addresses for the upstream
/// project's own maintainers and must never be touched by this function).
///
/// No-op when `payout_address` is `None` — the UI only sends one when the
/// operator has actually filled the field in, so a reconfigure of price/mint
/// alone never overwrites this. Merges into whatever identities.json already
/// exists (same reasoning as `apply_daemon_config`: `owned_identities` holds
/// the merchant's own private key and must survive untouched); creates an
/// "owner" entry if none exists yet rather than erroring, since a router
/// whose TollGate wasn't provisioned through this project may have any
/// upstream-default shape here.
///
/// Must run before the daemon restart in `restart_services` — like
/// `config.json`, `tollgate-wrt` only reads `identities.json` at startup.
pub fn apply_payout_identity(router: &Router, payout_address: Option<&str>) -> Result<()> {
let Some(address) = payout_address else {
return Ok(());
};
let existing = router.run_ok("cat /etc/tollgate/identities.json 2>/dev/null || echo '{}'")?;
let mut doc: serde_json::Value =
serde_json::from_str(existing.trim()).unwrap_or_else(|_| serde_json::json!({}));
let identities = doc
.as_object_mut()
.context("identities.json root is not a JSON object")?
.entry("public_identities")
.or_insert_with(|| serde_json::json!([]));
let identities = identities
.as_array_mut()
.context("identities.json public_identities is not an array")?;
match identities
.iter_mut()
.find(|i| i.get("name").and_then(|n| n.as_str()) == Some("owner"))
{
Some(owner) => {
owner["lightning_address"] = serde_json::json!(address);
}
None => {
identities.push(serde_json::json!({
"name": "owner",
"pubkey": "not currently used",
"lightning_address": address,
}));
}
}
let json_str = serde_json::to_string_pretty(&doc).context("serialize identities.json")?;
router
.upload_file("/etc/tollgate/identities.json", json_str.as_bytes())
.context("upload /etc/tollgate/identities.json")?;
Ok(())
}
+7
View File
@@ -59,10 +59,17 @@ pub async fn provision(router: &Router, config: &TollGateConfig) -> Result<()> {
config::apply(router, config)?;
wifi::provision_ssid(router, config)?;
// Must come after provision_ssid (creates the `tollgate` network this
// folds the upstream installer's own default AP onto) — see
// regate_upstream_default_aps for why this is needed at all.
wifi::regate_upstream_default_aps(router)
.context("re-gate upstream tollgate-module-basic-go default AP(s)")?;
// Must come after provision_ssid (which creates br-tollgate) and before
// the daemon restart below — config.json is only read at startup.
config::apply_daemon_config(router, config)
.context("write /etc/tollgate/config.json — tollgate-wrt reads this, not UCI")?;
config::apply_payout_identity(router, config.payout_address.as_deref())
.context("write /etc/tollgate/identities.json owner payout address")?;
// Also must come after provision_ssid: points gatewayinterface at
// br-tollgate, which provision_ssid is what creates.
nodogsplash::configure(router, config)
+43
View File
@@ -118,6 +118,49 @@ fn provision_firewall(router: &Router) -> Result<()> {
Ok(())
}
/// Fold the upstream `tollgate-module-basic-go` installer's own default
/// AP(s) onto the gated `tollgate` network.
///
/// `install::install_ipk` runs the package's `/etc/uci-defaults/*` first-boot
/// scripts itself (no real package manager to trigger them on OpenWrt 25.x —
/// see its doc comment). Those upstream scripts rebrand OpenWrt's
/// factory-default wifi sections (`wireless.default_radioN`, present on
/// every fresh install) to a `TollGate-<serial>` SSID, but only ever touch
/// the SSID — they leave `network` at its original `lan` binding. Nothing
/// else in this project's own provisioning (`provision_ssid` above) ever
/// looks at those sections; it only manages the separate `wireless.tollgate`
/// SSID it creates itself. Left alone, the result is two open SSIDs
/// broadcasting side by side: ours (gated by NoDogSplash) and upstream's
/// (wide open on `lan`, with a direct route to whatever's plugged into the
/// wired LAN port).
///
/// Confirmed live against archy-x250-pa3 2026-09-07: a client joining
/// "TollGate-3458" landed on `br-lan` with unrestricted WAN forwarding and
/// zero NoDogSplash involvement — free, unmetered internet, no captive
/// portal, on the router's own admin network.
///
/// Must run after `provision_network` (needs the `tollgate` network/bridge
/// to already exist) and before the network/wifi restart in
/// `restart_services` picks the new binding up.
pub fn regate_upstream_default_aps(router: &Router) -> Result<()> {
let sections = router.run_ok(
"uci show wireless 2>/dev/null | grep -o '^wireless\\.default_radio[0-9]*' | sort -u",
)?;
for section in sections.lines().map(str::trim).filter(|s| !s.is_empty()) {
let network_key = format!("{}.network", section);
let current = router.uci_get(&network_key).unwrap_or_default();
if current == "lan" {
info!(
"[{}] Re-gating upstream default AP {} (was network=lan) onto the tollgate network",
router.host, section
);
router.uci_set(&network_key, "tollgate")?;
}
}
router.uci_commit(Some("wireless"))?;
Ok(())
}
/// Return the first available wireless radio device name (e.g. "radio0").
fn detect_radio(router: &Router) -> Result<String> {
let out =
@@ -36,6 +36,7 @@ interface TollGateStatus {
min_steps?: number
currency?: string
mint_url?: string
payout_address?: string
}
interface WanStatus {
@@ -149,6 +150,7 @@ const editPriceSats = ref(10)
const editStepSizeMin = ref(1)
const editMinSteps = ref(1)
const editMintUrl = ref('')
const editPayoutAddress = ref('')
const editEnabled = ref(true)
// WAN setup flow
@@ -306,6 +308,7 @@ function startEditTollgate() {
editStepSizeMin.value = Math.max(1, Math.round((tg?.step_size_ms ?? 60000) / 60000))
editMinSteps.value = tg?.min_steps ?? 1
editMintUrl.value = tg?.mint_url ?? ''
editPayoutAddress.value = tg?.payout_address ?? ''
editEnabled.value = tg?.enabled ?? true
updateTollgateError.value = ''
editingTollgate.value = true
@@ -321,6 +324,7 @@ async function saveTollgateConfig() {
step_size_ms: editStepSizeMin.value * 60_000,
min_steps: editMinSteps.value,
mint_url: editMintUrl.value,
payout_address: editPayoutAddress.value,
enabled: editEnabled.value,
}
await rpcClient.call({ method: 'openwrt.provision-tollgate', params, timeout: 300000 })
@@ -935,6 +939,21 @@ onMounted(() => {
class="w-full px-4 py-3 bg-transparent border border-white/20 rounded-lg text-white placeholder-white/30 focus:outline-none focus:border-white/40 transition-colors font-mono text-xs"
/>
</div>
<div class="mb-4">
<label class="block text-xs text-white/40 mb-2">Payout Lightning address</label>
<input
v-model="editPayoutAddress"
type="text"
placeholder="you@yourwallet.example"
class="w-full px-4 py-3 bg-transparent border border-white/20 rounded-lg text-white placeholder-white/30 focus:outline-none focus:border-white/40 transition-colors font-mono text-xs"
/>
<p class="mt-2 text-xs text-white/40">
Where TollGate's built-in auto-payout sends your share once the on-router balance
crosses its threshold. Leave blank to keep whatever's already set on the router —
on a router not originally provisioned here, that may be an upstream default you
don't control.
</p>
</div>
<div class="flex items-center gap-2">
<button
:disabled="updatingTollgate"
+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"
}
+3244 -3242
View File
File diff suppressed because one or more lines are too long
+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"
}
@@ -1,29 +0,0 @@
{
"changelog": [
"**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.11-alpha",
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.11-alpha/archipelago",
"name": "archipelago",
"new_version": "1.8.11-alpha",
"sha256": "ae569054edd6b2491beb101815f6809bc00c95a7dbe86bd084bcb9a7c36e1853",
"size_bytes": 64179264
},
{
"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-07",
"signature": "6449ce6ef35a4ef4fa6d0923bb58a2bff52ea5430d5532496e8f0af9ed52eaec293f19d7bec272dc9bc1af5fb2cdfa0e46068c827a9a4dd9cbef92d1c5845301",
"signed_by": "did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT",
"version": "1.8.11-alpha"
}