feat(openwrt): add archipelago-openwrt crate with TollGate provisioning

New `archipelago-openwrt` workspace crate provides SSH/UCI-based management
of OpenWrt routers, including automated TollGate installation and configuration
of a pay-as-you-go "archipelago" SSID backed by the local Cashu mint.

Exposes two RPC endpoints:
- `openwrt.scan` — discover OpenWrt routers on the LAN
- `openwrt.provision-tollgate` — install tollgate-module-basic-go, write UCI
  config (TIP-01/TIP-02), and create isolated WiFi SSID + firewall zone

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 17:12:57 +00:00
co-authored by Claude Sonnet 4.6
parent a57ae388ec
commit e0cc00be0f
16 changed files with 700 additions and 0 deletions
+59
View File
@@ -0,0 +1,59 @@
use anyhow::Result;
use crate::Router;
/// Thin wrappers around `uci` CLI commands over SSH.
impl Router {
/// `uci get <key>` — returns trimmed value.
pub fn uci_get(&self, key: &str) -> Result<String> {
let out = self.run_ok(&format!("uci get {}", key))?;
Ok(out.trim().to_string())
}
/// `uci set <key>=<value>`
pub fn uci_set(&self, key: &str, value: &str) -> Result<()> {
self.run_ok(&format!("uci set {}={}", key, shell_quote(value)))?;
Ok(())
}
/// `uci add <config> <type>` — returns the new section name.
pub fn uci_add(&self, config: &str, section_type: &str) -> Result<String> {
let out = self.run_ok(&format!("uci add {} {}", config, section_type))?;
Ok(out.trim().to_string())
}
/// `uci add_list <key>=<value>`
pub fn uci_add_list(&self, key: &str, value: &str) -> Result<()> {
self.run_ok(&format!("uci add_list {}={}", key, shell_quote(value)))?;
Ok(())
}
/// `uci delete <key>`
pub fn uci_delete(&self, key: &str) -> Result<()> {
self.run_ok(&format!("uci delete {}", key))?;
Ok(())
}
/// `uci commit [<config>]`
pub fn uci_commit(&self, config: Option<&str>) -> Result<()> {
match config {
Some(c) => self.run_ok(&format!("uci commit {}", c))?,
None => self.run_ok("uci commit")?,
};
Ok(())
}
/// Batch: apply a list of `(key, value)` pairs then commit the config.
pub fn uci_apply(&self, config: &str, pairs: &[(&str, &str)]) -> Result<()> {
for (key, value) in pairs {
self.uci_set(key, value)?;
}
self.uci_commit(Some(config))?;
Ok(())
}
}
/// Wrap a value in single quotes, escaping any embedded single quotes.
fn shell_quote(s: &str) -> String {
format!("'{}'", s.replace('\'', r"'\''"))
}