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); self.run_ok("/usr/bin/apk add opkg")?; 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(()) } }