diff options
author | HimbeerserverDE <himbeerserverde@gmail.com> | 2023-05-05 18:09:26 +0200 |
---|---|---|
committer | HimbeerserverDE <himbeerserverde@gmail.com> | 2023-05-05 18:09:26 +0200 |
commit | df17d4f35074768c5aa806459d1e12ff8cada14a (patch) | |
tree | 926ac41f48f00d433e261e3e6619798411b9e019 | |
parent | 17a2ad9fed4adb6d3c50d4f433be2f00931de3f6 (diff) |
add boot partition update endpoint
-rw-r--r-- | src/error.rs | 6 | ||||
-rw-r--r-- | src/main.rs | 140 |
2 files changed, 144 insertions, 2 deletions
diff --git a/src/error.rs b/src/error.rs index 38e123f..6f8e217 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,10 +4,16 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum Error { + #[error("can't find disk device")] + NoDiskDev, #[error("no private keys found in file")] NoPrivateKeys, + #[error("no rootfs set in active cmdline")] + RootdevUnset, #[error("io: {0}")] Io(#[from] io::Error), + #[error("actix_web: {0}")] + ActixWeb(#[from] actix_web::Error), #[error("rustls: {0}")] Rustls(#[from] rustls::Error), } diff --git a/src/main.rs b/src/main.rs index 6431c3d..4dde989 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,11 @@ use rustkrazy_admind::{Error, Result}; use std::fs::{self, File}; -use std::io::{self, BufReader}; +use std::io::{self, BufReader, Write}; use actix_web::{ - dev::ServiceRequest, http::header::ContentType, web, App, HttpResponse, HttpServer, + dev::ServiceRequest, http::header::ContentType, web, App, FromRequest, HttpRequest, + HttpResponse, HttpServer, }; use actix_web_httpauth::extractors::basic::{BasicAuth, Config}; use actix_web_httpauth::extractors::AuthenticationError; @@ -36,6 +37,38 @@ async fn handle_shutdown() -> HttpResponse { } } +async fn handle_update_boot(req: HttpRequest) -> HttpResponse { + let boot = match boot_dev() { + Ok(v) => v, + Err(e) => { + return HttpResponse::InternalServerError() + .content_type(ContentType::plaintext()) + .body(format!("can't locate boot partition: {}", e)) + } + }; + + match stream_to(boot, req).await { + Ok(_) => {} + Err(e) => { + return HttpResponse::InternalServerError() + .content_type(ContentType::plaintext()) + .body(format!("can't update boot partition: {}", e)) + } + } + + match switch_to_inactive_root() { + Ok(_) => HttpResponse::Ok() + .content_type(ContentType::plaintext()) + .body("successfully updated boot partition and switched to inactive root"), + Err(e) => HttpResponse::InternalServerError() + .content_type(ContentType::plaintext()) + .body(format!( + "can't switch to inactive root (this is probably fatal): {}", + e + )), + } +} + #[actix_web::main] async fn main() -> io::Result<()> { match start().await { @@ -60,6 +93,7 @@ async fn start() -> Result<()> { .wrap(auth) .service(web::resource("/reboot").to(handle_reboot)) .service(web::resource("/shutdown").to(handle_shutdown)) + .service(web::resource("/update/boot").to(handle_update_boot)) }) .bind_rustls("[::]:8443", config)? .run() @@ -121,3 +155,105 @@ fn validate_credentials(user_id: &str, user_password: &str) -> io::Result<bool> "Invalid credentials", )) } + +fn modify_cmdline(old: &str, new: &str) -> Result<()> { + let cmdline = fs::read_to_string("/boot/cmdline.txt")?; + fs::write("/boot/cmdline.txt", cmdline.replace(old, new))?; + + Ok(()) +} + +fn dev() -> Result<&'static str> { + let devs = ["/dev/mmcblk0", "/dev/sda", "/dev/vda"]; + + for dev in devs { + if fs::metadata(dev).is_ok() { + return Ok(dev); + } + } + + Err(Error::NoDiskDev) +} + +fn boot_dev() -> Result<&'static str> { + Ok(match dev()? { + "/dev/mmcblk0" => "/dev/mmcblk0p1", + "/dev/sda" => "/dev/sda1", + "/dev/vda" => "/dev/vda1", + _ => unreachable!(), + }) +} + +fn active_root() -> Result<String> { + let cmdline = fs::read_to_string("/proc/cmdline")?; + + for seg in cmdline.split(' ') { + if seg.starts_with("root=PARTUUID=00000000-") { + let root_id = seg + .split("root=PARTUUID=00000000-0") + .collect::<Vec<&str>>() + .into_iter() + .next_back() + .ok_or(Error::RootdevUnset)?; + + return Ok(match dev()? { + "/dev/mmcblk0" => format!("/dev/mmcblk0p{}", root_id), + "/dev/sda" => format!("/dev/sda{}", root_id), + "/dev/vda" => format!("/dev/vda{}", root_id), + _ => unreachable!(), + }); + } + } + + Err(Error::RootdevUnset) +} + +fn inactive_root() -> Result<String> { + let cmdline = fs::read_to_string("/proc/cmdline")?; + + for seg in cmdline.split(' ') { + if seg.starts_with("root=PARTUUID=00000000-") { + let root_id = match seg + .split("root=PARTUUID=00000000-0") + .collect::<Vec<&str>>() + .into_iter() + .next_back() + .ok_or(Error::RootdevUnset)? + { + "2" => "3", + "3" => "2", + _ => unreachable!(), + }; + + return Ok(match dev()? { + "/dev/mmcblk0" => format!("/dev/mmcblk0p{}", root_id), + "/dev/sda" => format!("/dev/sda{}", root_id), + "/dev/vda" => format!("/dev/vda{}", root_id), + _ => unreachable!(), + }); + } + } + + Err(Error::RootdevUnset) +} + +async fn stream_to(dst: &str, req: HttpRequest) -> Result<()> { + let bytes = web::Bytes::extract(&req).await?; + let mut file = File::create(dst)?; + + file.write_all(&bytes)?; + file.sync_all()?; + + Ok(()) +} + +fn switch_to_inactive_root() -> Result<()> { + let old = active_root()?; + let new = inactive_root()?; + + let old = String::from("root=PARTUUID=00000000-0") + &old.chars().last().unwrap().to_string(); + let new = String::from("root=PARTUUID=00000000-0") + &new.chars().last().unwrap().to_string(); + + modify_cmdline(&old, &new)?; + Ok(()) +} |