fix(openwrt): resolve opkg/apk via $PATH, not a hardcoded /usr/bin path

opkg_check() and every opkg/apk invocation hardcoded /usr/bin/opkg and
/usr/bin/apk. Official OpenWrt images don't all symlink /bin into
/usr/bin — the glinet_gl-mt3000 24.10.2 build keeps them as separate
real directories with opkg living in /bin — so the check silently
missed a perfectly normal install and TollGate provisioning failed
with "this router's firmware may not support package management".

Switched every call to resolve through the router's own $PATH
(command -v / bare opkg / apk) instead. Reproduced and fixed live
against archy-x250-pa3, 2026-09-05.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0176RpCxFNS9ZaSJjL72W9Z5
This commit is contained in:
2026-09-05 15:14:00 +00:00
co-authored by Claude Sonnet 5
parent a4ae375617
commit f3d96ae2ee
3 changed files with 22 additions and 18 deletions
+1 -1
View File
@@ -135,7 +135,7 @@ impl RpcHandler {
// not /usr/bin/tollgate-module-basic-go — that's only the opkg/apk
// *package* name, never an on-disk filename.
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")
.map(|(_, code)| code == 0)
.unwrap_or(false);
+19 -15
View File
@@ -15,25 +15,32 @@ pub enum PkgManager {
impl Router {
/// Detect which package manager is available.
///
/// - If `/usr/bin/opkg` exists → `PkgManager::Opkg` (nothing to do).
/// - If `/usr/bin/apk` exists → run `apk update` (switching repos to HTTP
/// Looks up `opkg`/`apk` via the router's `$PATH` (`command -v`) rather
/// 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
/// `apk add opkg`. If opkg is in the repos → `Opkg`. If not (OpenWrt
/// 25.x) → `ApkNative`.
/// - Neither found → error.
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 {
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 {
info!("[{}] opkg not found — using apk (OpenWrt 25.x+)", self.host);
// Fresh images ship without a CA bundle; switch repos to HTTP so
// 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")?;
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 {
anyhow::bail!(
"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).
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 {
return Ok(PkgManager::Opkg);
}
@@ -62,7 +69,7 @@ impl Router {
}
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)"
);
}
@@ -70,31 +77,28 @@ impl Router {
/// `opkg update` — refresh package lists.
pub fn opkg_update(&self) -> Result<()> {
info!("[{}] opkg update", self.host);
self.run_ok("/usr/bin/opkg update")?;
self.run_ok("opkg update")?;
Ok(())
}
/// Install a package, skipping if already installed.
pub fn opkg_install(&self, package: &str) -> Result<()> {
// Check if already installed to avoid unnecessary network traffic.
let (_, code) = self.run(&format!(
"/usr/bin/opkg list-installed | grep -q '^{} '",
package
))?;
let (_, code) = self.run(&format!("opkg list-installed | grep -q '^{} '", package))?;
if code == 0 {
info!("[{}] {} already installed", self.host, package);
return Ok(());
}
info!("[{}] opkg install {}", self.host, package);
self.run_ok(&format!("/usr/bin/opkg install {}", package))?;
self.run_ok(&format!("opkg install {}", package))?;
Ok(())
}
/// Remove a package.
pub fn opkg_remove(&self, package: &str) -> Result<()> {
info!("[{}] opkg remove {}", self.host, package);
self.run_ok(&format!("/usr/bin/opkg remove {}", package))?;
self.run_ok(&format!("opkg remove {}", package))?;
Ok(())
}
@@ -121,7 +125,7 @@ impl Router {
}
info!("[{}] apk add {}", self.host, package);
self.run_ok(&format!("/usr/bin/apk add {}", package))?;
self.run_ok(&format!("apk add {}", package))?;
Ok(())
}
}
+2 -2
View File
@@ -35,7 +35,7 @@ pub fn install_tollgate(router: &Router) -> Result<()> {
// Package not in any feed — download the .ipk directly.
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 url = ipk_url(arch).ok_or_else(|| {
@@ -88,7 +88,7 @@ pub fn install_tollgate_apk_native(router: &Router) -> Result<()> {
". /etc/openwrt_release 2>/dev/null \
&& a=\"${DISTRIB_ARCH:-${OPENWRT_ARCH:-}}\" \
&& [ -n \"$a\" ] && echo \"$a\" \
|| /usr/bin/apk --print-arch 2>/dev/null \
|| apk --print-arch 2>/dev/null \
|| uname -m",
)?;
// Normalise: uname -m returns bare "mipsel"/"mips"; map to 24kc variant