Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f83fd1fee | ||
|
|
2b2a14c569 | ||
|
|
f355ae753b | ||
|
|
db52c06a72 | ||
|
|
4b14b62e74 |
@@ -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),
|
"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(),
|
"currency": router.uci_get("tollgate.main.currency").unwrap_or_default(),
|
||||||
"mint_url": router.uci_get("tollgate.main.mint_url").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 {
|
} else {
|
||||||
serde_json::json!({ "installed": false })
|
serde_json::json!({ "installed": false })
|
||||||
@@ -199,10 +200,15 @@ impl RpcHandler {
|
|||||||
///
|
///
|
||||||
/// Params: `{ "host": "192.168.1.1", "ssh_user": "root", "ssh_password": "",
|
/// Params: `{ "host": "192.168.1.1", "ssh_user": "root", "ssh_password": "",
|
||||||
/// "price_sats": 10, "step_size_ms": 60000, "min_steps": 1,
|
/// "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_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.
|
/// 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(
|
pub(super) async fn handle_openwrt_provision_tollgate(
|
||||||
&self,
|
&self,
|
||||||
params: Option<serde_json::Value>,
|
params: Option<serde_json::Value>,
|
||||||
@@ -240,12 +246,31 @@ impl RpcHandler {
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let default_mint_url = format!("http://{}:{}", self.config.host_ip, LOCAL_MINT_PORT);
|
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
|
let mint_url = p
|
||||||
.get("mint_url")
|
.get("mint_url")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or(&default_mint_url)
|
.unwrap_or(&default_mint_url)
|
||||||
|
.trim_end_matches('/')
|
||||||
.to_string();
|
.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 {
|
let config = TollGateConfig {
|
||||||
ssid: "archipelago".to_string(),
|
ssid: "archipelago".to_string(),
|
||||||
mint_url,
|
mint_url,
|
||||||
@@ -256,6 +281,7 @@ impl RpcHandler {
|
|||||||
.unwrap_or(60_000),
|
.unwrap_or(60_000),
|
||||||
min_steps: p.get("min_steps").and_then(|v| v.as_u64()).unwrap_or(1) as u32,
|
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),
|
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 —
|
// Blocking SSH session, and provision runs `opkg install` over it —
|
||||||
|
|||||||
@@ -22,6 +22,46 @@ use crate::wallet::ecash;
|
|||||||
///
|
///
|
||||||
/// Returns the total sats swept in (0 if there was nothing to do, including
|
/// 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).
|
/// 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> {
|
pub async fn sweep_once(data_dir: &Path) -> Result<u64> {
|
||||||
let cfg = net_router::load_router_config(data_dir).await?;
|
let cfg = net_router::load_router_config(data_dir).await?;
|
||||||
if !cfg.configured {
|
if !cfg.configured {
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ pub struct TollGateConfig {
|
|||||||
pub min_steps: u32,
|
pub min_steps: u32,
|
||||||
/// Whether the TollGate service should be running and enabled at boot.
|
/// Whether the TollGate service should be running and enabled at boot.
|
||||||
pub enabled: bool,
|
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 {
|
impl Default for TollGateConfig {
|
||||||
@@ -34,6 +42,7 @@ impl Default for TollGateConfig {
|
|||||||
step_size_ms: 60_000,
|
step_size_ms: 60_000,
|
||||||
min_steps: 1,
|
min_steps: 1,
|
||||||
enabled: true,
|
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
|
/// tollgate.main.enabled` etc.); changing pricing or the mint here has no
|
||||||
/// effect on what the daemon advertises or accepts.
|
/// effect on what the daemon advertises or accepts.
|
||||||
pub fn apply(router: &Router, cfg: &TollGateConfig) -> Result<()> {
|
pub fn apply(router: &Router, cfg: &TollGateConfig) -> Result<()> {
|
||||||
router.uci_apply(
|
let step_size = cfg.step_size_ms.to_string();
|
||||||
"tollgate",
|
let min_steps = cfg.min_steps.to_string();
|
||||||
&[
|
let price_sats = cfg.price_sats.to_string();
|
||||||
("tollgate.main", "tollgate"),
|
|
||||||
("tollgate.main.enabled", if cfg.enabled { "1" } else { "0" }),
|
let mut pairs = vec![
|
||||||
("tollgate.main.metric", "milliseconds"),
|
("tollgate.main", "tollgate"),
|
||||||
("tollgate.main.step_size", &cfg.step_size_ms.to_string()),
|
("tollgate.main.enabled", if cfg.enabled { "1" } else { "0" }),
|
||||||
("tollgate.main.min_steps", &cfg.min_steps.to_string()),
|
("tollgate.main.metric", "milliseconds"),
|
||||||
("tollgate.main.price_per_step", &cfg.price_sats.to_string()),
|
("tollgate.main.step_size", step_size.as_str()),
|
||||||
("tollgate.main.currency", "sat"),
|
("tollgate.main.min_steps", min_steps.as_str()),
|
||||||
("tollgate.main.mint_url", &cfg.mint_url),
|
("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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,3 +114,61 @@ pub fn apply_daemon_config(router: &Router, cfg: &TollGateConfig) -> Result<()>
|
|||||||
.context("upload /etc/tollgate/config.json")?;
|
.context("upload /etc/tollgate/config.json")?;
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -59,10 +59,17 @@ pub async fn provision(router: &Router, config: &TollGateConfig) -> Result<()> {
|
|||||||
|
|
||||||
config::apply(router, config)?;
|
config::apply(router, config)?;
|
||||||
wifi::provision_ssid(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
|
// Must come after provision_ssid (which creates br-tollgate) and before
|
||||||
// the daemon restart below — config.json is only read at startup.
|
// the daemon restart below — config.json is only read at startup.
|
||||||
config::apply_daemon_config(router, config)
|
config::apply_daemon_config(router, config)
|
||||||
.context("write /etc/tollgate/config.json — tollgate-wrt reads this, not UCI")?;
|
.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
|
// Also must come after provision_ssid: points gatewayinterface at
|
||||||
// br-tollgate, which provision_ssid is what creates.
|
// br-tollgate, which provision_ssid is what creates.
|
||||||
nodogsplash::configure(router, config)
|
nodogsplash::configure(router, config)
|
||||||
|
|||||||
@@ -118,6 +118,49 @@ fn provision_firewall(router: &Router) -> Result<()> {
|
|||||||
Ok(())
|
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").
|
/// Return the first available wireless radio device name (e.g. "radio0").
|
||||||
fn detect_radio(router: &Router) -> Result<String> {
|
fn detect_radio(router: &Router) -> Result<String> {
|
||||||
let out =
|
let out =
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ interface TollGateStatus {
|
|||||||
min_steps?: number
|
min_steps?: number
|
||||||
currency?: string
|
currency?: string
|
||||||
mint_url?: string
|
mint_url?: string
|
||||||
|
payout_address?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WanStatus {
|
interface WanStatus {
|
||||||
@@ -149,6 +150,7 @@ const editPriceSats = ref(10)
|
|||||||
const editStepSizeMin = ref(1)
|
const editStepSizeMin = ref(1)
|
||||||
const editMinSteps = ref(1)
|
const editMinSteps = ref(1)
|
||||||
const editMintUrl = ref('')
|
const editMintUrl = ref('')
|
||||||
|
const editPayoutAddress = ref('')
|
||||||
const editEnabled = ref(true)
|
const editEnabled = ref(true)
|
||||||
|
|
||||||
// WAN setup flow
|
// WAN setup flow
|
||||||
@@ -306,6 +308,7 @@ function startEditTollgate() {
|
|||||||
editStepSizeMin.value = Math.max(1, Math.round((tg?.step_size_ms ?? 60000) / 60000))
|
editStepSizeMin.value = Math.max(1, Math.round((tg?.step_size_ms ?? 60000) / 60000))
|
||||||
editMinSteps.value = tg?.min_steps ?? 1
|
editMinSteps.value = tg?.min_steps ?? 1
|
||||||
editMintUrl.value = tg?.mint_url ?? ''
|
editMintUrl.value = tg?.mint_url ?? ''
|
||||||
|
editPayoutAddress.value = tg?.payout_address ?? ''
|
||||||
editEnabled.value = tg?.enabled ?? true
|
editEnabled.value = tg?.enabled ?? true
|
||||||
updateTollgateError.value = ''
|
updateTollgateError.value = ''
|
||||||
editingTollgate.value = true
|
editingTollgate.value = true
|
||||||
@@ -321,6 +324,7 @@ async function saveTollgateConfig() {
|
|||||||
step_size_ms: editStepSizeMin.value * 60_000,
|
step_size_ms: editStepSizeMin.value * 60_000,
|
||||||
min_steps: editMinSteps.value,
|
min_steps: editMinSteps.value,
|
||||||
mint_url: editMintUrl.value,
|
mint_url: editMintUrl.value,
|
||||||
|
payout_address: editPayoutAddress.value,
|
||||||
enabled: editEnabled.value,
|
enabled: editEnabled.value,
|
||||||
}
|
}
|
||||||
await rpcClient.call({ method: 'openwrt.provision-tollgate', params, timeout: 300000 })
|
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"
|
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>
|
||||||
|
<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">
|
<div class="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
:disabled="updatingTollgate"
|
:disabled="updatingTollgate"
|
||||||
|
|||||||
+17
-17
@@ -1,29 +1,29 @@
|
|||||||
{
|
{
|
||||||
"changelog": [
|
"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.",
|
"**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.",
|
||||||
"**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.",
|
"**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.",
|
||||||
"**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."
|
"**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": [
|
"components": [
|
||||||
{
|
{
|
||||||
"current_version": "1.8.10-alpha",
|
"current_version": "1.8.11-alpha",
|
||||||
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.10-alpha/archipelago",
|
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.11-alpha/archipelago",
|
||||||
"name": "archipelago",
|
"name": "archipelago",
|
||||||
"new_version": "1.8.10-alpha",
|
"new_version": "1.8.11-alpha",
|
||||||
"sha256": "6c8bd41fed44cd999cb360c00e1b66a2d19d19812cc2b0c8a1677eec2a9579e6",
|
"sha256": "ae569054edd6b2491beb101815f6809bc00c95a7dbe86bd084bcb9a7c36e1853",
|
||||||
"size_bytes": 64178056
|
"size_bytes": 64179264
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"current_version": "1.8.10-alpha",
|
"current_version": "1.8.11-alpha",
|
||||||
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.10-alpha/archipelago-frontend-1.8.10-alpha.tar.gz",
|
"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.10-alpha.tar.gz",
|
"name": "archipelago-frontend-1.8.11-alpha.tar.gz",
|
||||||
"new_version": "1.8.10-alpha",
|
"new_version": "1.8.11-alpha",
|
||||||
"sha256": "6b25de8a8e1a4f7fe51594f9bbbe21f5820f417af47a8b309c2dbf8f8723b719",
|
"sha256": "192fd0470b6ccf66e78c80b4a4c3af5468882b85d81959362a3bd11f88b9d71d",
|
||||||
"size_bytes": 97736297
|
"size_bytes": 97741740
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"release_date": "2026-09-01",
|
"release_date": "2026-09-07",
|
||||||
"signature": "b69926bcb1851ff7d6a5b24519cd4a8015aab4ed4b588ee989d8ce6e3beaeb2cc0eb38078f522ded0d389fe53b7dbcdbf3f40c534b4bfafa5cf4a2ab2c59e40f",
|
"signature": "6449ce6ef35a4ef4fa6d0923bb58a2bff52ea5430d5532496e8f0af9ed52eaec293f19d7bec272dc9bc1af5fb2cdfa0e46068c827a9a4dd9cbef92d1c5845301",
|
||||||
"signed_by": "did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT",
|
"signed_by": "did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT",
|
||||||
"version": "1.8.10-alpha"
|
"version": "1.8.11-alpha"
|
||||||
}
|
}
|
||||||
|
|||||||
+3244
-3242
File diff suppressed because one or more lines are too long
+17
-17
@@ -1,29 +1,29 @@
|
|||||||
{
|
{
|
||||||
"changelog": [
|
"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.",
|
"**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.",
|
||||||
"**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.",
|
"**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.",
|
||||||
"**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."
|
"**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": [
|
"components": [
|
||||||
{
|
{
|
||||||
"current_version": "1.8.10-alpha",
|
"current_version": "1.8.11-alpha",
|
||||||
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.10-alpha/archipelago",
|
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.11-alpha/archipelago",
|
||||||
"name": "archipelago",
|
"name": "archipelago",
|
||||||
"new_version": "1.8.10-alpha",
|
"new_version": "1.8.11-alpha",
|
||||||
"sha256": "6c8bd41fed44cd999cb360c00e1b66a2d19d19812cc2b0c8a1677eec2a9579e6",
|
"sha256": "ae569054edd6b2491beb101815f6809bc00c95a7dbe86bd084bcb9a7c36e1853",
|
||||||
"size_bytes": 64178056
|
"size_bytes": 64179264
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"current_version": "1.8.10-alpha",
|
"current_version": "1.8.11-alpha",
|
||||||
"download_url": "https://source.archipelago-foundation.org/lfg2025/archy/releases/download/v1.8.10-alpha/archipelago-frontend-1.8.10-alpha.tar.gz",
|
"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.10-alpha.tar.gz",
|
"name": "archipelago-frontend-1.8.11-alpha.tar.gz",
|
||||||
"new_version": "1.8.10-alpha",
|
"new_version": "1.8.11-alpha",
|
||||||
"sha256": "6b25de8a8e1a4f7fe51594f9bbbe21f5820f417af47a8b309c2dbf8f8723b719",
|
"sha256": "192fd0470b6ccf66e78c80b4a4c3af5468882b85d81959362a3bd11f88b9d71d",
|
||||||
"size_bytes": 97736297
|
"size_bytes": 97741740
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"release_date": "2026-09-01",
|
"release_date": "2026-09-07",
|
||||||
"signature": "b69926bcb1851ff7d6a5b24519cd4a8015aab4ed4b588ee989d8ce6e3beaeb2cc0eb38078f522ded0d389fe53b7dbcdbf3f40c534b4bfafa5cf4a2ab2c59e40f",
|
"signature": "6449ce6ef35a4ef4fa6d0923bb58a2bff52ea5430d5532496e8f0af9ed52eaec293f19d7bec272dc9bc1af5fb2cdfa0e46068c827a9a4dd9cbef92d1c5845301",
|
||||||
"signed_by": "did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT",
|
"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"
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user