use anyhow::Result; use tracing::info; use crate::Router; impl Router { /// Verify opkg is available. On OpenWrt 25.x+ which ships apk instead of /// opkg, bootstraps opkg via `apk add opkg` before returning. pub fn opkg_check(&self) -> Result<()> { let (_, code) = self.run("test -x /usr/bin/opkg")?; if code == 0 { return Ok(()); } // OpenWrt 25.x switched to apk as default — install opkg through it. let (_, apk_code) = self.run("test -x /usr/bin/apk")?; if apk_code == 0 { info!("[{}] opkg not found, bootstrapping via apk", self.host); // Capture stderr so apk errors are visible in server logs. let (update_out, update_code) = self.run("/usr/bin/apk update 2>&1")?; if update_code != 0 { anyhow::bail!( "apk update failed (exit {}) — router may have no internet access. \ Ensure WAN/internet is working on the router before provisioning.\n{}", update_code, update_out.trim() ); } self.run_ok("/usr/bin/apk add opkg 2>&1")?; return Ok(()); } anyhow::bail!( "opkg not found at /usr/bin/opkg — this router's firmware may not \ support package management (TollGate requires a standard OpenWrt build)" ); } /// `opkg update` — refresh package lists. pub fn opkg_update(&self) -> Result<()> { info!("[{}] opkg update", self.host); self.run_ok("/usr/bin/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))?; 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))?; 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))?; Ok(()) } }