mirror of
https://github.com/AlbrechtL/openwrt-docker.git
synced 2026-10-20 00:01:42 +00:00
Migrate web-backend from tide/async-std to axum/tokio
tide is unmaintained and async-std is discontinued. Move to axum 0.8, tokio 1.53 and tower-http 0.7 (CORS), switch to edition 2024 and drop lazy_static and serde_json. The API paths and JSON output stay the same. If a command cannot be started, the backend now returns HTTP 500 instead of panicking. Also enable weekly dependabot updates for the cargo ecosystem. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
dede682981
commit
917de6b089
@@ -6,3 +6,9 @@ updates:
|
||||
schedule:
|
||||
# Check for updates to GitHub Actions every week
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/web-backend"
|
||||
schedule:
|
||||
# Check for updates to Rust crates every week
|
||||
interval: "weekly"
|
||||
|
||||
Generated
+261
-2092
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,10 @@
|
||||
[package]
|
||||
name = "qemu-openwrt-web-backend"
|
||||
version = "0.2.0"
|
||||
edition = '2021'
|
||||
version = "0.3.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
tide = "0.16.0"
|
||||
async-std = "1.10"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
lazy_static = "1.4"
|
||||
axum = "0.8.9"
|
||||
tokio = { version = "1.53", features = ["macros", "rt", "net", "process", "sync"] }
|
||||
tower-http = { version = "0.7.1", features = ["cors"] }
|
||||
serde = { version = "1.0.229", features = ["derive"] }
|
||||
|
||||
+53
-133
@@ -1,14 +1,9 @@
|
||||
// Disclaimer: This code is mainly generated by ChatGPT. Quite impressive!
|
||||
// TODO: I expect that this code needs some refactoring
|
||||
|
||||
use std::process::Command;
|
||||
use std::sync::{Mutex};
|
||||
use lazy_static::lazy_static;
|
||||
use tide::{Request, Response, StatusCode, Next};
|
||||
use serde::Serialize;
|
||||
use std::str;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use axum::{Json, Router, http::Method, http::StatusCode, routing::get};
|
||||
use serde::Serialize;
|
||||
use tokio::{net::TcpListener, process::Command, sync::Mutex};
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct JsonResponse {
|
||||
@@ -16,131 +11,56 @@ struct JsonResponse {
|
||||
combined_output: String,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref run_mutex: Mutex<i32> = Mutex::new(0i32);
|
||||
}
|
||||
// Middleware for adding CORS headers
|
||||
fn cors_middleware<'a>(
|
||||
req: Request<()>,
|
||||
next: Next<'a, ()>,
|
||||
) -> Pin<Box<dyn Future<Output = tide::Result> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let mut res = next.run(req).await;
|
||||
// Only one command may run at a time
|
||||
static RUN_LOCK: Mutex<()> = Mutex::const_new(());
|
||||
|
||||
res.insert_header("Access-Control-Allow-Origin", "*");
|
||||
res.insert_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
||||
res.insert_header("Access-Control-Allow-Headers", "*");
|
||||
async fn run(command: &'static str, args: &'static [&'static str]) -> (StatusCode, Json<JsonResponse>) {
|
||||
let _lock = RUN_LOCK.lock().await;
|
||||
|
||||
Ok(res)
|
||||
})
|
||||
let command_str = format!("{} {:?}", command, args);
|
||||
|
||||
let (status, combined_output) = match Command::new(command).args(args).output().await {
|
||||
Ok(output) => {
|
||||
let stdout = str::from_utf8(&output.stdout).unwrap_or("Invalid UTF-8 in stdout");
|
||||
let stderr = str::from_utf8(&output.stderr).unwrap_or("Invalid UTF-8 in stderr");
|
||||
(StatusCode::OK, format!("{}\n{}", stdout, stderr))
|
||||
}
|
||||
Err(err) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Failed to execute command: {}", err),
|
||||
),
|
||||
};
|
||||
|
||||
(
|
||||
status,
|
||||
Json(JsonResponse {
|
||||
command: command_str,
|
||||
combined_output,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// Pre-flight handling for OPTIONS method (for browsers)
|
||||
async fn handle_options(_req: Request<()>) -> tide::Result {
|
||||
let mut res = Response::new(StatusCode::Ok);
|
||||
res.insert_header("Access-Control-Allow-Origin", "*");
|
||||
res.insert_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
||||
res.insert_header("Access-Control-Allow-Headers", "*");
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
async fn run_command(command: &str, args: &[&str]) -> JsonResponse {
|
||||
let _my_lock = run_mutex.lock().unwrap();
|
||||
|
||||
let output = Command::new(command)
|
||||
.args(args)
|
||||
.output()
|
||||
.expect("Failed to execute command");
|
||||
|
||||
let stdout = str::from_utf8(&output.stdout).unwrap_or("Invalid UTF-8 in stdout").to_string();
|
||||
let stderr = str::from_utf8(&output.stderr).unwrap_or("Invalid UTF-8 in stderr").to_string();
|
||||
|
||||
let combined_output = format!("{}\n{}", stdout, stderr);
|
||||
|
||||
JsonResponse {
|
||||
command: format!("{} {:?}", command, args),
|
||||
combined_output,
|
||||
}
|
||||
}
|
||||
|
||||
async fn endpoint_reboot(_req: Request<()>) -> tide::Result {
|
||||
let response_data = run_command("/run/qemu_qmp.sh", &["-R"]).await;
|
||||
let mut res = Response::new(StatusCode::Ok);
|
||||
res.set_body(serde_json::to_string(&response_data)?);
|
||||
res.insert_header("Content-Type", "application/json");
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
async fn endpoint_reset(_req: Request<()>) -> tide::Result {
|
||||
let response_data = run_command("/run/qemu_qmp.sh", &["-r"]).await;
|
||||
let mut res = Response::new(StatusCode::Ok);
|
||||
res.set_body(serde_json::to_string(&response_data)?);
|
||||
res.insert_header("Content-Type", "application/json");
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
async fn endpoint_get_openwrt_info(_req: Request<()>) -> tide::Result {
|
||||
let response_data = run_command("/run/qemu_qmp.sh", &["-V"]).await;
|
||||
let mut res = Response::new(StatusCode::Ok);
|
||||
res.set_body(serde_json::to_string(&response_data)?);
|
||||
res.insert_header("Content-Type", "application/json");
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
async fn endpoint_get_container_info(_req: Request<()>) -> tide::Result {
|
||||
let response_data = run_command("cat", &["/var/www/system_info.txt"]).await;
|
||||
let mut res = Response::new(StatusCode::Ok);
|
||||
res.set_body(serde_json::to_string(&response_data)?);
|
||||
res.insert_header("Content-Type", "application/json");
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
async fn endpoint_factory_reset(_req: Request<()>) -> tide::Result {
|
||||
let response_data = run_command("/run/factory_reset.sh", &[]).await;
|
||||
let mut res = Response::new(StatusCode::Ok);
|
||||
res.set_body(serde_json::to_string(&response_data)?);
|
||||
res.insert_header("Content-Type", "application/json");
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
async fn endpoint_get_ip_addr(_req: Request<()>) -> tide::Result {
|
||||
let response_data = run_command("/run/qemu_qmp.sh", &["-c", "ip -json addr"]).await;
|
||||
let mut res = Response::new(StatusCode::Ok);
|
||||
res.set_body(serde_json::to_string(&response_data)?);
|
||||
res.insert_header("Content-Type", "application/json");
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
async fn endpoint_get_luci_web_button_json(_req: Request<()>) -> tide::Result {
|
||||
let response_data = run_command("sh", &["-c", "echo $LUCI_WEB_BUTTON_JSON"]).await;
|
||||
let mut res = Response::new(StatusCode::Ok);
|
||||
res.set_body(serde_json::to_string(&response_data)?);
|
||||
res.insert_header("Content-Type", "application/json");
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn main() -> tide::Result<()> {
|
||||
async_std::task::block_on(async {
|
||||
println!("*******************************************");
|
||||
println!("qemu-openwrt-web-backend proof-of-concept");
|
||||
println!("*******************************************");
|
||||
|
||||
let mut app = tide::new();
|
||||
|
||||
// Add CORS middleware
|
||||
app.with(cors_middleware);
|
||||
|
||||
// Handle preflight OPTIONS request for CORS
|
||||
app.at("/reboot").get(endpoint_reboot).options(handle_options);
|
||||
app.at("/reset").get(endpoint_reset).options(handle_options);
|
||||
app.at("/get_openwrt_info").get(endpoint_get_openwrt_info).options(handle_options);
|
||||
app.at("/get_container_info").get(endpoint_get_container_info).options(handle_options);
|
||||
app.at("/factory_reset").get(endpoint_factory_reset).options(handle_options);
|
||||
app.at("/get_openwrt_ip_addresses").get(endpoint_get_ip_addr).options(handle_options);
|
||||
app.at("/get_luci_web_button_json").get(endpoint_get_luci_web_button_json).options(handle_options);
|
||||
|
||||
// Start the server at localhost:8080
|
||||
app.listen("127.0.0.1:8080").await?;
|
||||
Ok(())
|
||||
})
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
println!("*******************************************");
|
||||
println!("qemu-openwrt-web-backend");
|
||||
println!("*******************************************");
|
||||
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin(Any)
|
||||
.allow_methods([Method::GET, Method::POST, Method::OPTIONS])
|
||||
.allow_headers(Any);
|
||||
|
||||
let app = Router::new()
|
||||
.route("/reboot", get(|| run("/run/qemu_qmp.sh", &["-R"])))
|
||||
.route("/reset", get(|| run("/run/qemu_qmp.sh", &["-r"])))
|
||||
.route("/get_openwrt_info", get(|| run("/run/qemu_qmp.sh", &["-V"])))
|
||||
.route("/get_container_info", get(|| run("cat", &["/var/www/system_info.txt"])))
|
||||
.route("/factory_reset", get(|| run("/run/factory_reset.sh", &[])))
|
||||
.route("/get_openwrt_ip_addresses", get(|| run("/run/qemu_qmp.sh", &["-c", "ip -json addr"])))
|
||||
.route("/get_luci_web_button_json", get(|| run("sh", &["-c", "echo $LUCI_WEB_BUTTON_JSON"])))
|
||||
.layer(cors);
|
||||
|
||||
let listener = TcpListener::bind("127.0.0.1:8080").await?;
|
||||
axum::serve(listener, app).await
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user