Some checks failed
Run Check Script / check (pull_request) Failing after 19s
194 lines
6.7 KiB
Rust
194 lines
6.7 KiB
Rust
//! Example: check for firmware updates and apply them.
|
|
//!
|
|
//! ```text
|
|
//! cargo run --example firmware_update
|
|
//! ```
|
|
//!
|
|
//! This runs a full firmware update workflow:
|
|
//! 1. POST /api/core/firmware/check — triggers a background check for updates
|
|
//! 2. POST /api/core/firmware/status — retrieves the check results
|
|
//! 3. If updates are available, POST /api/core/firmware/update — applies them
|
|
//!
|
|
//! The check can take a while since it connects to the update mirror.
|
|
|
|
use std::env;
|
|
|
|
use opnsense_api::client::OpnsenseClient;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct FirmwareCheckResponse {
|
|
pub status: String,
|
|
pub msg_uuid: String,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct FirmwareStatusResponse {
|
|
pub status: String,
|
|
#[serde(default)]
|
|
pub status_msg: Option<String>,
|
|
#[serde(default)]
|
|
pub all_packages: serde_json::Value,
|
|
#[serde(default)]
|
|
pub product: serde_json::Value,
|
|
#[serde(default)]
|
|
pub status_reboot: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct FirmwareActionResponse {
|
|
pub status: String,
|
|
#[serde(default)]
|
|
pub msg_uuid: String,
|
|
#[serde(default)]
|
|
pub status_msg: Option<String>,
|
|
}
|
|
|
|
fn build_client() -> OpnsenseClient {
|
|
let base_url =
|
|
env::var("OPNSENSE_BASE_URL").unwrap_or_else(|_| "https://192.168.1.1/api".to_string());
|
|
|
|
match (
|
|
env::var("OPNSENSE_API_KEY").ok(),
|
|
env::var("OPNSENSE_API_SECRET").ok(),
|
|
) {
|
|
(Some(key), Some(secret)) => OpnsenseClient::builder()
|
|
.base_url(&base_url)
|
|
.auth_from_key_secret(&key, &secret)
|
|
.skip_tls_verify()
|
|
.timeout_secs(120)
|
|
.build()
|
|
.expect("failed to build HTTP client"),
|
|
_ => {
|
|
eprintln!("ERROR: OPNSENSE_API_KEY and OPNSENSE_API_SECRET must be set.");
|
|
eprintln!(" export OPNSENSE_API_KEY=your_key");
|
|
eprintln!(" export OPNSENSE_API_SECRET=your_secret");
|
|
eprintln!(" export OPNSENSE_BASE_URL=https://your-firewall/api");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
|
let client = build_client();
|
|
|
|
println!();
|
|
println!("╔═══════════════════════════════════════════════════════════╗");
|
|
println!("║ OPNsense Firmware Update ║");
|
|
println!("╚═══════════════════════════════════════════════════════════╝");
|
|
println!();
|
|
|
|
println!(" [1/2] Checking for updates (this may take a moment) ...");
|
|
println!();
|
|
log::info!("POST /api/core/firmware/check");
|
|
|
|
let check: FirmwareCheckResponse = client
|
|
.post_typed("core", "firmware", "check", None::<&()>)
|
|
.await
|
|
.expect("check request failed");
|
|
|
|
println!(" Check triggered, msg_uuid: {}", check.msg_uuid);
|
|
println!();
|
|
|
|
println!(" Waiting for check to complete ...");
|
|
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
|
|
|
|
println!();
|
|
println!(" [2/2] Fetching update status ...");
|
|
log::info!("POST /api/core/firmware/status");
|
|
|
|
let status: FirmwareStatusResponse = client
|
|
.post_typed("core", "firmware", "status", None::<&()>)
|
|
.await
|
|
.expect("status request failed");
|
|
|
|
println!();
|
|
match status.status.as_str() {
|
|
"none" => {
|
|
println!(
|
|
" ✓ {}",
|
|
status
|
|
.status_msg
|
|
.as_deref()
|
|
.unwrap_or("No updates available.")
|
|
);
|
|
}
|
|
"update" | "upgrade" => {
|
|
println!(
|
|
" ⚠ {}",
|
|
status.status_msg.as_deref().unwrap_or("Updates available.")
|
|
);
|
|
if let Some(reboot) = status.status_reboot {
|
|
if reboot == "1" {
|
|
println!(" ⚠ This update requires a reboot.");
|
|
}
|
|
}
|
|
|
|
let pkg_count = if let serde_json::Value::Object(ref map) = status.all_packages {
|
|
map.len()
|
|
} else {
|
|
0
|
|
};
|
|
|
|
if pkg_count > 0 {
|
|
println!(" {} package(s) to update:", pkg_count);
|
|
if let serde_json::Value::Object(ref packages) = status.all_packages {
|
|
for (name, info) in packages.iter().take(10) {
|
|
let reason = info.get("reason").and_then(|v| v.as_str()).unwrap_or("?");
|
|
let old_ver = info.get("old").and_then(|v| v.as_str()).unwrap_or("N/A");
|
|
let new_ver = info.get("new").and_then(|v| v.as_str()).unwrap_or("?");
|
|
println!(" {:40} {} {} → {}", name, reason, old_ver, new_ver);
|
|
}
|
|
if pkg_count > 10 {
|
|
println!(" ... and {} more", pkg_count - 10);
|
|
}
|
|
}
|
|
}
|
|
|
|
println!();
|
|
println!(
|
|
" Run with environment variable OPNSENSE_FIRMWARE_UPDATE=1 to apply updates."
|
|
);
|
|
println!(" WARNING: firmware updates can cause connectivity interruptions.");
|
|
}
|
|
"error" => {
|
|
println!(
|
|
" ✗ Error: {}",
|
|
status.status_msg.as_deref().unwrap_or("Unknown error")
|
|
);
|
|
}
|
|
other => {
|
|
println!(" ? Unexpected status: {other}");
|
|
println!(
|
|
" Full response: {}",
|
|
serde_json::to_string_pretty(&status).unwrap()
|
|
);
|
|
}
|
|
}
|
|
|
|
if env::var("OPNSENSE_FIRMWARE_UPDATE").as_deref() == Ok("1") {
|
|
if status.status == "update" || status.status == "upgrade" {
|
|
println!();
|
|
println!(" Applying firmware update ...");
|
|
log::info!("POST /api/core/firmware/update");
|
|
|
|
let result: FirmwareActionResponse = client
|
|
.post_typed("core", "firmware", "update", None::<&()>)
|
|
.await
|
|
.expect("update request failed");
|
|
|
|
if result.status == "ok" {
|
|
println!(" ✓ Update started, msg_uuid: {}", result.msg_uuid);
|
|
println!(" The firewall is updating in the background.");
|
|
} else {
|
|
println!(" ✗ Update failed: {:?}", result.status_msg);
|
|
}
|
|
} else {
|
|
println!();
|
|
println!(" No updates to apply.");
|
|
}
|
|
}
|
|
}
|