diff --git a/core/archipelago/src/api/rpc/openwrt.rs b/core/archipelago/src/api/rpc/openwrt.rs index 32ac1ea6..72b271c7 100644 --- a/core/archipelago/src/api/rpc/openwrt.rs +++ b/core/archipelago/src/api/rpc/openwrt.rs @@ -240,10 +240,18 @@ 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(); let config = TollGateConfig { diff --git a/core/openwrt/src/tollgate/mod.rs b/core/openwrt/src/tollgate/mod.rs index b87073f7..71439e0b 100644 --- a/core/openwrt/src/tollgate/mod.rs +++ b/core/openwrt/src/tollgate/mod.rs @@ -59,6 +59,11 @@ 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) diff --git a/core/openwrt/src/tollgate/wifi.rs b/core/openwrt/src/tollgate/wifi.rs index 2bdf786e..bd88e338 100644 --- a/core/openwrt/src/tollgate/wifi.rs +++ b/core/openwrt/src/tollgate/wifi.rs @@ -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-` 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 { let out =