Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
094f42312c | ||
|
|
da8c3ec193 | ||
|
|
4fdf8e8c58 | ||
|
|
61b5d93b11 | ||
|
|
be06b3ce2b | ||
|
|
f3d96ae2ee |
@@ -135,7 +135,7 @@ impl RpcHandler {
|
|||||||
// not /usr/bin/tollgate-module-basic-go — that's only the opkg/apk
|
// not /usr/bin/tollgate-module-basic-go — that's only the opkg/apk
|
||||||
// *package* name, never an on-disk filename.
|
// *package* name, never an on-disk filename.
|
||||||
let tollgate_installed = router
|
let tollgate_installed = router
|
||||||
.run("/usr/bin/opkg list-installed 2>/dev/null | grep -q '^tollgate-module-basic-go ' || \
|
.run("opkg list-installed 2>/dev/null | grep -q '^tollgate-module-basic-go ' || \
|
||||||
test -f /usr/bin/tollgate-wrt 2>/dev/null")
|
test -f /usr/bin/tollgate-wrt 2>/dev/null")
|
||||||
.map(|(_, code)| code == 0)
|
.map(|(_, code)| code == 0)
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|||||||
+19
-15
@@ -15,25 +15,32 @@ pub enum PkgManager {
|
|||||||
impl Router {
|
impl Router {
|
||||||
/// Detect which package manager is available.
|
/// Detect which package manager is available.
|
||||||
///
|
///
|
||||||
/// - If `/usr/bin/opkg` exists → `PkgManager::Opkg` (nothing to do).
|
/// Looks up `opkg`/`apk` via the router's `$PATH` (`command -v`) rather
|
||||||
/// - If `/usr/bin/apk` exists → run `apk update` (switching repos to HTTP
|
/// than a hardcoded `/usr/bin/<tool>` — official OpenWrt images don't all
|
||||||
|
/// symlink `/bin` into `/usr/bin` (e.g. the `glinet_gl-mt3000` 24.10.2
|
||||||
|
/// build keeps them as separate real directories with `opkg` living in
|
||||||
|
/// `/bin`), so a fixed absolute path silently misses a perfectly normal
|
||||||
|
/// install and reports "no package management" (archy-x250-pa3, 2026-09-05).
|
||||||
|
///
|
||||||
|
/// - If `opkg` is on PATH → `PkgManager::Opkg` (nothing to do).
|
||||||
|
/// - If `apk` is on PATH → run `apk update` (switching repos to HTTP
|
||||||
/// first to work around missing CA bundle on fresh images), then try
|
/// first to work around missing CA bundle on fresh images), then try
|
||||||
/// `apk add opkg`. If opkg is in the repos → `Opkg`. If not (OpenWrt
|
/// `apk add opkg`. If opkg is in the repos → `Opkg`. If not (OpenWrt
|
||||||
/// 25.x) → `ApkNative`.
|
/// 25.x) → `ApkNative`.
|
||||||
/// - Neither found → error.
|
/// - Neither found → error.
|
||||||
pub fn opkg_check(&self) -> Result<PkgManager> {
|
pub fn opkg_check(&self) -> Result<PkgManager> {
|
||||||
let (_, code) = self.run("test -x /usr/bin/opkg")?;
|
let (_, code) = self.run("command -v opkg >/dev/null 2>&1")?;
|
||||||
if code == 0 {
|
if code == 0 {
|
||||||
return Ok(PkgManager::Opkg);
|
return Ok(PkgManager::Opkg);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (_, apk_code) = self.run("test -x /usr/bin/apk")?;
|
let (_, apk_code) = self.run("command -v apk >/dev/null 2>&1")?;
|
||||||
if apk_code == 0 {
|
if apk_code == 0 {
|
||||||
info!("[{}] opkg not found — using apk (OpenWrt 25.x+)", self.host);
|
info!("[{}] opkg not found — using apk (OpenWrt 25.x+)", self.host);
|
||||||
// Fresh images ship without a CA bundle; switch repos to HTTP so
|
// Fresh images ship without a CA bundle; switch repos to HTTP so
|
||||||
// apk's wget can reach the package index without TLS verification.
|
// apk's wget can reach the package index without TLS verification.
|
||||||
self.run_ok("sed -i 's|https://|http://|g' /etc/apk/repositories 2>/dev/null || true")?;
|
self.run_ok("sed -i 's|https://|http://|g' /etc/apk/repositories 2>/dev/null || true")?;
|
||||||
let (update_out, update_code) = self.run("/usr/bin/apk update 2>&1")?;
|
let (update_out, update_code) = self.run("apk update 2>&1")?;
|
||||||
if update_code != 0 {
|
if update_code != 0 {
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"apk update failed (exit {}) — router may have no internet access. \
|
"apk update failed (exit {}) — router may have no internet access. \
|
||||||
@@ -43,7 +50,7 @@ impl Router {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Try to install opkg (only available on some 25.x builds).
|
// Try to install opkg (only available on some 25.x builds).
|
||||||
let (add_out, add_code) = self.run("/usr/bin/apk add opkg 2>&1")?;
|
let (add_out, add_code) = self.run("apk add opkg 2>&1")?;
|
||||||
if add_code == 0 {
|
if add_code == 0 {
|
||||||
return Ok(PkgManager::Opkg);
|
return Ok(PkgManager::Opkg);
|
||||||
}
|
}
|
||||||
@@ -62,7 +69,7 @@ impl Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"opkg not found at /usr/bin/opkg — this router's firmware may not \
|
"Neither opkg nor apk found on this router's $PATH — its firmware may not \
|
||||||
support package management (TollGate requires a standard OpenWrt build)"
|
support package management (TollGate requires a standard OpenWrt build)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -70,31 +77,28 @@ impl Router {
|
|||||||
/// `opkg update` — refresh package lists.
|
/// `opkg update` — refresh package lists.
|
||||||
pub fn opkg_update(&self) -> Result<()> {
|
pub fn opkg_update(&self) -> Result<()> {
|
||||||
info!("[{}] opkg update", self.host);
|
info!("[{}] opkg update", self.host);
|
||||||
self.run_ok("/usr/bin/opkg update")?;
|
self.run_ok("opkg update")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Install a package, skipping if already installed.
|
/// Install a package, skipping if already installed.
|
||||||
pub fn opkg_install(&self, package: &str) -> Result<()> {
|
pub fn opkg_install(&self, package: &str) -> Result<()> {
|
||||||
// Check if already installed to avoid unnecessary network traffic.
|
// Check if already installed to avoid unnecessary network traffic.
|
||||||
let (_, code) = self.run(&format!(
|
let (_, code) = self.run(&format!("opkg list-installed | grep -q '^{} '", package))?;
|
||||||
"/usr/bin/opkg list-installed | grep -q '^{} '",
|
|
||||||
package
|
|
||||||
))?;
|
|
||||||
if code == 0 {
|
if code == 0 {
|
||||||
info!("[{}] {} already installed", self.host, package);
|
info!("[{}] {} already installed", self.host, package);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("[{}] opkg install {}", self.host, package);
|
info!("[{}] opkg install {}", self.host, package);
|
||||||
self.run_ok(&format!("/usr/bin/opkg install {}", package))?;
|
self.run_ok(&format!("opkg install {}", package))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a package.
|
/// Remove a package.
|
||||||
pub fn opkg_remove(&self, package: &str) -> Result<()> {
|
pub fn opkg_remove(&self, package: &str) -> Result<()> {
|
||||||
info!("[{}] opkg remove {}", self.host, package);
|
info!("[{}] opkg remove {}", self.host, package);
|
||||||
self.run_ok(&format!("/usr/bin/opkg remove {}", package))?;
|
self.run_ok(&format!("opkg remove {}", package))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +125,7 @@ impl Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
info!("[{}] apk add {}", self.host, package);
|
info!("[{}] apk add {}", self.host, package);
|
||||||
self.run_ok(&format!("/usr/bin/apk add {}", package))?;
|
self.run_ok(&format!("apk add {}", package))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,53 @@ use crate::Router;
|
|||||||
/// The OpenWrt package name for the TollGate reference implementation.
|
/// The OpenWrt package name for the TollGate reference implementation.
|
||||||
const TOLLGATE_PACKAGE: &str = "tollgate-module-basic-go";
|
const TOLLGATE_PACKAGE: &str = "tollgate-module-basic-go";
|
||||||
|
|
||||||
/// Direct-download fallback URLs by opkg architecture string.
|
/// Pinned upstream release. Was stuck on v0.2.0 (Oct 2025) until 2026-09-05 —
|
||||||
|
/// nine releases behind. v0.5.0's changelog covers exactly the failure modes
|
||||||
|
/// hit live against archy-x250-pa3: a mint with an empty/broken keyset used
|
||||||
|
/// to crash-loop the daemon forever ("graceful degradation when Cashu mints
|
||||||
|
/// fail" in v0.5.0), and the bundled captive-portal build had no CBOR support
|
||||||
|
/// at all, so it could only decode legacy `cashuA` tokens — rejecting the
|
||||||
|
/// `cashuB` (NUT-00 V4) tokens modern wallets like Minibits generate by
|
||||||
|
/// default ("portal improvements" in v0.5.0 include a JS bundle update that
|
||||||
|
/// should carry a current cashu-ts with V4 support). Bump this string to move
|
||||||
|
/// both this crate's URLs and the version baked into the source comments.
|
||||||
|
const TOLLGATE_VERSION: &str = "v0.5.0";
|
||||||
|
|
||||||
|
/// Direct-download fallback URLs by opkg architecture string, for the
|
||||||
|
/// `.ipk` (ar-archive) package format.
|
||||||
/// Used when the package is not in any configured feed.
|
/// Used when the package is not in any configured feed.
|
||||||
/// Source: https://github.com/OpenTollGate/tollgate-module-basic-go/releases/tag/v0.2.0
|
/// Source: https://github.com/OpenTollGate/tollgate-module-basic-go/releases/tag/v0.5.0
|
||||||
fn ipk_url(arch: &str) -> Option<&'static str> {
|
fn ipk_url(arch: &str) -> Option<String> {
|
||||||
match arch {
|
let name = match arch {
|
||||||
"mips_24kc" => Some("https://github.com/OpenTollGate/tollgate-module-basic-go/releases/download/v0.2.0/mips_24kc.ipk"),
|
"mips_24kc" => "mips_24kc",
|
||||||
"mipsel_24kc" => Some("https://github.com/OpenTollGate/tollgate-module-basic-go/releases/download/v0.2.0/mipsel_24kc.ipk"),
|
"mipsel_24kc" => "mipsel_24kc",
|
||||||
"aarch64_cortex-a53" => Some("https://github.com/OpenTollGate/tollgate-module-basic-go/releases/download/v0.2.0/aarch64_cortex-a53.ipk"),
|
"aarch64_cortex-a53" => "aarch64_cortex-a53",
|
||||||
"aarch64_cortex-a72" => Some("https://github.com/OpenTollGate/tollgate-module-basic-go/releases/download/v0.2.0/aarch64_cortex-a72.ipk"),
|
"aarch64_cortex-a72" => "aarch64_cortex-a72",
|
||||||
"arm_cortex-a7" => Some("https://github.com/OpenTollGate/tollgate-module-basic-go/releases/download/v0.2.0/arm_cortex-a7.ipk"),
|
"arm_cortex-a7" => "arm_cortex-a7",
|
||||||
_ => None,
|
"x86_64" => "x86_64",
|
||||||
}
|
_ => return None,
|
||||||
|
};
|
||||||
|
Some(format!(
|
||||||
|
"https://github.com/OpenTollGate/tollgate-module-basic-go/releases/download/{TOLLGATE_VERSION}/tollgate-wrt_{TOLLGATE_VERSION}_{name}.ipk"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Direct-download URLs for the native Alpine-style `.apk` package format —
|
||||||
|
/// only published for a subset of architectures as of v0.5.0. Where
|
||||||
|
/// available this is strictly better than [`ipk_url`] on an apk-native
|
||||||
|
/// (OpenWrt 25.x+) router: `apk add` installs it directly (dependency
|
||||||
|
/// resolution, postinst, uci-defaults all handled by apk itself), instead of
|
||||||
|
/// the manual `ar`/`tar` extraction dance `install_ipk` has to do to unpack
|
||||||
|
/// an `.ipk` on a router with no `opkg`.
|
||||||
|
fn apk_url(arch: &str) -> Option<String> {
|
||||||
|
let name = match arch {
|
||||||
|
"aarch64_cortex-a53" => "aarch64_cortex-a53",
|
||||||
|
"x86_64" => "x86_64",
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
Some(format!(
|
||||||
|
"https://github.com/OpenTollGate/tollgate-module-basic-go/releases/download/{TOLLGATE_VERSION}/tollgate-wrt_{TOLLGATE_VERSION}_{name}.apk"
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Install tollgate-module-basic-go via opkg (OpenWrt ≤24.x).
|
/// Install tollgate-module-basic-go via opkg (OpenWrt ≤24.x).
|
||||||
@@ -35,7 +70,7 @@ pub fn install_tollgate(router: &Router) -> Result<()> {
|
|||||||
|
|
||||||
// Package not in any feed — download the .ipk directly.
|
// Package not in any feed — download the .ipk directly.
|
||||||
let arch = router
|
let arch = router
|
||||||
.run_ok("/usr/bin/opkg print-architecture | grep -v all | grep -v noarch | tail -1 | awk '{print $2}'")?;
|
.run_ok("opkg print-architecture | grep -v all | grep -v noarch | tail -1 | awk '{print $2}'")?;
|
||||||
let arch = arch.trim();
|
let arch = arch.trim();
|
||||||
|
|
||||||
let url = ipk_url(arch).ok_or_else(|| {
|
let url = ipk_url(arch).ok_or_else(|| {
|
||||||
@@ -88,7 +123,7 @@ pub fn install_tollgate_apk_native(router: &Router) -> Result<()> {
|
|||||||
". /etc/openwrt_release 2>/dev/null \
|
". /etc/openwrt_release 2>/dev/null \
|
||||||
&& a=\"${DISTRIB_ARCH:-${OPENWRT_ARCH:-}}\" \
|
&& a=\"${DISTRIB_ARCH:-${OPENWRT_ARCH:-}}\" \
|
||||||
&& [ -n \"$a\" ] && echo \"$a\" \
|
&& [ -n \"$a\" ] && echo \"$a\" \
|
||||||
|| /usr/bin/apk --print-arch 2>/dev/null \
|
|| apk --print-arch 2>/dev/null \
|
||||||
|| uname -m",
|
|| uname -m",
|
||||||
)?;
|
)?;
|
||||||
// Normalise: uname -m returns bare "mipsel"/"mips"; map to 24kc variant
|
// Normalise: uname -m returns bare "mipsel"/"mips"; map to 24kc variant
|
||||||
@@ -103,6 +138,39 @@ pub fn install_tollgate_apk_native(router: &Router) -> Result<()> {
|
|||||||
anyhow::bail!("Could not determine router architecture");
|
anyhow::bail!("Could not determine router architecture");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prefer a native .apk when the release publishes one for this arch —
|
||||||
|
// `apk add` handles the install itself (deps, postinst, uci-defaults),
|
||||||
|
// skipping the manual ar/tar extraction the .ipk fallback below needs.
|
||||||
|
if let Some(url) = apk_url(arch) {
|
||||||
|
info!(
|
||||||
|
"[{}] Downloading native TollGate .apk for {} from GitHub releases",
|
||||||
|
router.host, arch
|
||||||
|
);
|
||||||
|
let (dl_out, dl_code) = router.run(&format!(
|
||||||
|
"wget --no-check-certificate -O /tmp/tollgate.apk '{}' 2>&1",
|
||||||
|
url
|
||||||
|
))?;
|
||||||
|
if dl_code != 0 {
|
||||||
|
anyhow::bail!("TollGate .apk download failed: {}", dl_out.trim());
|
||||||
|
}
|
||||||
|
let (size_out, _) = router.run("wc -c < /tmp/tollgate.apk 2>/dev/null")?;
|
||||||
|
let size: u64 = size_out.trim().parse().unwrap_or(0);
|
||||||
|
if size < 50_000 {
|
||||||
|
anyhow::bail!(
|
||||||
|
"Downloaded TollGate .apk is only {}B — wget likely captured an error page. \
|
||||||
|
Check router internet access and that the release URL is reachable.",
|
||||||
|
size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let url = ipk_url(arch).ok_or_else(|| {
|
let url = ipk_url(arch).ok_or_else(|| {
|
||||||
anyhow::anyhow!(
|
anyhow::anyhow!(
|
||||||
"No pre-built TollGate package for architecture '{}'. \
|
"No pre-built TollGate package for architecture '{}'. \
|
||||||
|
|||||||
@@ -69,6 +69,15 @@ matches the prebuilt TollGate `.ipk` architectures exactly
|
|||||||
From here, continue with the Prerequisites/Step 2 flow above to connect
|
From here, continue with the Prerequisites/Step 2 flow above to connect
|
||||||
it to the Archipelago node.
|
it to the Archipelago node.
|
||||||
|
|
||||||
|
> The Archipelago UI's Connect form (Step 2) authenticates *with* a
|
||||||
|
> password — it has no flow for setting the initial one on a fresh,
|
||||||
|
> passwordless router. You have to set it out-of-band first. If you're
|
||||||
|
> working from the node's own local kiosk display rather than a normal
|
||||||
|
> desktop browser, there's no visible tab bar/address bar to open a new
|
||||||
|
> tab from — press **Ctrl+T** to open one anyway, navigate to
|
||||||
|
> `192.168.1.1`, and use LuCI's first-boot prompt to set the root
|
||||||
|
> password. Then switch back to the Archipelago tab and Connect with it.
|
||||||
|
|
||||||
**If the flash fails / the router doesn't come back**: filogic devices
|
**If the flash fails / the router doesn't come back**: filogic devices
|
||||||
don't use a reset-button recovery. Instead, connect to the router's LAN
|
don't use a reset-button recovery. Instead, connect to the router's LAN
|
||||||
port and, during boot, press a key within the first ~2 seconds to enter
|
port and, during boot, press a key within the first ~2 seconds to enter
|
||||||
@@ -169,6 +178,52 @@ installed:
|
|||||||
Anyone who joins the `archipelago` SSID sees TollGate's captive portal and
|
Anyone who joins the `archipelago` SSID sees TollGate's captive portal and
|
||||||
pays sats (via the configured Cashu mint) for timed access.
|
pays sats (via the configured Cashu mint) for timed access.
|
||||||
|
|
||||||
|
## Verifying a successful install
|
||||||
|
|
||||||
|
A clean install (flash → Connect → WAN/WISP → Install TollGate, all through
|
||||||
|
the UI as above) ends in this state — worth checking if you want to confirm
|
||||||
|
everything actually landed correctly rather than trusting the UI's success
|
||||||
|
toast alone:
|
||||||
|
|
||||||
|
- `tollgate-wrt` is running (`/etc/init.d/tollgate-wrt status` → `running`).
|
||||||
|
- nodogsplash's **rendered** config — not just the UCI source — has
|
||||||
|
`GatewayInterface br-tollgate`. Check the actual file the daemon was
|
||||||
|
started with (typically `/tmp/etc/nodogsplash_main.conf`), since that's
|
||||||
|
what's actually enforced, not `uci show nodogsplash`. This matters because
|
||||||
|
provisioning must stop nodogsplash and reconfigure it to gate the
|
||||||
|
`br-tollgate` bridge *before* starting it — installing the package by hand
|
||||||
|
(bypassing the UI/RPC flow) leaves nodogsplash on its default
|
||||||
|
`br-lan`-gating behavior instead, which locks out the router's own
|
||||||
|
admin/SSH access. If you ever see a router become unreachable right after
|
||||||
|
a TollGate install, this is the first thing to check.
|
||||||
|
- The router's own LAN (the interface you manage it over — SSH, ping) is
|
||||||
|
still reachable and untouched by the portal.
|
||||||
|
- TollGate's own log (`logread | grep tollgate-wrt`) shows successful mint
|
||||||
|
probes for each configured mint.
|
||||||
|
|
||||||
|
A `dev build detected (branch=unknown), injecting test mint:
|
||||||
|
https://nofee.testnut.cashu.space` line in that log means the installed
|
||||||
|
build considers itself a dev build and silently adds a test mint alongside
|
||||||
|
your configured one(s) — check the Edit panel's Mint URL afterward if you
|
||||||
|
don't want that test mint accepted.
|
||||||
|
|
||||||
|
### A note on network topology during setup
|
||||||
|
|
||||||
|
If the Archipelago node reaches the router over the same wired interface the
|
||||||
|
router uses as its LAN, expect the router to become the node's default
|
||||||
|
route on that interface once it has its own working WAN/WISP uplink — this
|
||||||
|
is normal and, once WAN is actually configured with internet access, works
|
||||||
|
fine end-to-end (the node's traffic routes out through the router's
|
||||||
|
uplink). It's only a problem *before* WAN is configured: a freshly flashed
|
||||||
|
or freshly factory-reset router has no upstream internet yet, so if it wins
|
||||||
|
the node's default-route race (lowest metric on its own interface) while
|
||||||
|
still offline, it creates a dead-end route and the node loses its own
|
||||||
|
connectivity (including anything tunneled, e.g. a VPN/mesh network the node
|
||||||
|
relies on) until that route is removed or the router gets its uplink
|
||||||
|
working. If you hit this, either wait until WAN/WISP is actually up before
|
||||||
|
letting the router's interface win the route race, or temporarily lower the
|
||||||
|
priority of that route until it is.
|
||||||
|
|
||||||
## Reconfiguring or moving to a different router
|
## Reconfiguring or moving to a different router
|
||||||
|
|
||||||
Use **Disconnect** on the status dashboard to return to the connect form —
|
Use **Disconnect** on the status dashboard to return to the connect form —
|
||||||
@@ -198,6 +253,18 @@ new host/credentials; the newly connected router becomes the persisted one.
|
|||||||
fails**: the node sanity-checks the downloaded `.ipk` is at least 50 KB —
|
fails**: the node sanity-checks the downloaded `.ipk` is at least 50 KB —
|
||||||
a smaller file usually means `wget` captured an HTML error page instead
|
a smaller file usually means `wget` captured an HTML error page instead
|
||||||
(no internet access from the router, or a bad release URL).
|
(no internet access from the router, or a bad release URL).
|
||||||
|
- **Install fails right after a reboot or a fresh WAN setup** with `apk
|
||||||
|
update failed ... router may have no internet access` even though WAN
|
||||||
|
looks configured: this is usually just timing, not a real problem — the
|
||||||
|
router's WiFi-uplink association (`wwan`/`hakodosh`-style STA interface)
|
||||||
|
can take a few seconds longer to reconnect than the dashboard takes to
|
||||||
|
let you click Install. Wait ~10–15 seconds after WAN shows `sta_state:
|
||||||
|
up` and retry; it should succeed on the next attempt.
|
||||||
|
- **Install fails with `opkg not found at /usr/bin/opkg` (or similar) even
|
||||||
|
though the router clearly has `opkg`/`apk` installed**: fixed as of
|
||||||
|
2026-09-05 — the backend used to hardcode `/usr/bin/opkg`/`/usr/bin/apk`,
|
||||||
|
which some official OpenWrt builds don't symlink into `/bin`. If you're
|
||||||
|
running an Archipelago build from before that fix, update first.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -115,6 +115,24 @@ const showConnectForm = ref(false)
|
|||||||
const connecting = ref(false)
|
const connecting = ref(false)
|
||||||
const connectedParams = ref<Record<string, string> | null>(null)
|
const connectedParams = ref<Record<string, string> | null>(null)
|
||||||
|
|
||||||
|
// Every action below (install/edit TollGate, WiFi scan, WAN configure) needs
|
||||||
|
// host/ssh_user/ssh_password to reach the router. `connectedParams` only gets
|
||||||
|
// set when the Connect form was actually submitted this session (WR-03 above)
|
||||||
|
// — on a normal page load the router reconnects via the server-persisted
|
||||||
|
// config instead, so `sshPassword`/`sshUser`/`host` (the Connect form's own
|
||||||
|
// local refs) sit at their untouched defaults ('', 'root', ''). Falling back
|
||||||
|
// to those refs here used to send an explicit-but-empty ssh_password, which
|
||||||
|
// the backend treats as "the caller provided this" and never falls back to
|
||||||
|
// the real saved password — a real router password then fails auth on every
|
||||||
|
// action even though the status poll (which sends no params at all) keeps
|
||||||
|
// working fine (archy-x250-pa3, 2026-09-05: dropbear logged one bad-password
|
||||||
|
// attempt at the exact moment "Install TollGate" was clicked). Omitting the
|
||||||
|
// fields entirely when there's no explicit connectedParams lets the backend's
|
||||||
|
// own saved-config fallback do the right thing, same as the status poll.
|
||||||
|
function authParams(): Record<string, string> {
|
||||||
|
return connectedParams.value ?? {}
|
||||||
|
}
|
||||||
|
|
||||||
const detecting = ref(false)
|
const detecting = ref(false)
|
||||||
const detectError = ref('')
|
const detectError = ref('')
|
||||||
const detectedCandidates = ref<string[]>([])
|
const detectedCandidates = ref<string[]>([])
|
||||||
@@ -271,11 +289,7 @@ async function provisionTollgate() {
|
|||||||
provisionError.value = ''
|
provisionError.value = ''
|
||||||
provisionSuccess.value = false
|
provisionSuccess.value = false
|
||||||
try {
|
try {
|
||||||
const params: Record<string, unknown> = {
|
const params: Record<string, unknown> = { ...authParams() }
|
||||||
host: connectedParams.value?.host ?? status.value?.host,
|
|
||||||
ssh_user: connectedParams.value?.ssh_user ?? sshUser.value,
|
|
||||||
ssh_password: connectedParams.value?.ssh_password ?? sshPassword.value,
|
|
||||||
}
|
|
||||||
await rpcClient.call({ method: 'openwrt.provision-tollgate', params, timeout: 300000 })
|
await rpcClient.call({ method: 'openwrt.provision-tollgate', params, timeout: 300000 })
|
||||||
provisionSuccess.value = true
|
provisionSuccess.value = true
|
||||||
await load(connectedParams.value ?? undefined)
|
await load(connectedParams.value ?? undefined)
|
||||||
@@ -302,9 +316,7 @@ async function saveTollgateConfig() {
|
|||||||
updateTollgateError.value = ''
|
updateTollgateError.value = ''
|
||||||
try {
|
try {
|
||||||
const params: Record<string, unknown> = {
|
const params: Record<string, unknown> = {
|
||||||
host: connectedParams.value?.host ?? status.value?.host,
|
...authParams(),
|
||||||
ssh_user: connectedParams.value?.ssh_user ?? sshUser.value,
|
|
||||||
ssh_password: connectedParams.value?.ssh_password ?? sshPassword.value,
|
|
||||||
price_sats: editPriceSats.value,
|
price_sats: editPriceSats.value,
|
||||||
step_size_ms: editStepSizeMin.value * 60_000,
|
step_size_ms: editStepSizeMin.value * 60_000,
|
||||||
min_steps: editMinSteps.value,
|
min_steps: editMinSteps.value,
|
||||||
@@ -336,11 +348,7 @@ async function scanWifi() {
|
|||||||
wanStep.value = 'scanning'
|
wanStep.value = 'scanning'
|
||||||
wanError.value = ''
|
wanError.value = ''
|
||||||
try {
|
try {
|
||||||
const params: Record<string, unknown> = {
|
const params: Record<string, unknown> = { ...authParams() }
|
||||||
host: connectedParams.value?.host ?? status.value?.host,
|
|
||||||
ssh_user: connectedParams.value?.ssh_user ?? sshUser.value,
|
|
||||||
ssh_password: connectedParams.value?.ssh_password ?? sshPassword.value,
|
|
||||||
}
|
|
||||||
const result = await rpcClient.call<{ networks: ScannedNetwork[] }>({
|
const result = await rpcClient.call<{ networks: ScannedNetwork[] }>({
|
||||||
method: 'openwrt.scan-wifi',
|
method: 'openwrt.scan-wifi',
|
||||||
params,
|
params,
|
||||||
@@ -367,9 +375,7 @@ async function configureWan() {
|
|||||||
wanError.value = ''
|
wanError.value = ''
|
||||||
try {
|
try {
|
||||||
const params: Record<string, unknown> = {
|
const params: Record<string, unknown> = {
|
||||||
host: connectedParams.value?.host ?? status.value?.host,
|
...authParams(),
|
||||||
ssh_user: connectedParams.value?.ssh_user ?? sshUser.value,
|
|
||||||
ssh_password: connectedParams.value?.ssh_password ?? sshPassword.value,
|
|
||||||
ssid: selectedNetwork.value.ssid,
|
ssid: selectedNetwork.value.ssid,
|
||||||
password: wanPassword.value,
|
password: wanPassword.value,
|
||||||
encryption: selectedNetwork.value.encryption,
|
encryption: selectedNetwork.value.encryption,
|
||||||
|
|||||||
Reference in New Issue
Block a user