use std::{ io, process::{Command, Output}, }; use log::{info, warn}; use peach_lib::sbot::{SbotConfig, SbotStatus}; use rocket::{ form::{Form, FromForm}, get, post, request::FlashMessage, response::{Flash, Redirect}, serde::Deserialize, }; use rocket_dyn_templates::{tera::Context, Template}; use crate::routes::authentication::Authenticated; #[derive(Debug, Deserialize, FromForm)] pub struct SbotConfigForm { /// Directory path for the log and indexes. repo: String, /// Directory path for writing debug output. debugdir: String, /// Secret-handshake app-key (aka. network key). shscap: String, /// HMAC hash used to sign messages. hmac: String, /// Replication hops (1: friends, 2: friends of friends). hops: u8, /// IP address to listen on. lis_ip: String, /// Port to listen on. lis_port: String, /// Address to listen on for WebSocket connections. wslis: String, /// Address to for metrics and pprof HTTP server. debuglis: String, /// Enable sending local UDP broadcasts. localadv: bool, /// Enable listening for UDP broadcasts and connecting. localdiscov: bool, /// Enable syncing by using epidemic-broadcast-trees (EBT). enable_ebt: bool, /// Bypass graph auth and fetch remote's feed (useful for pubs that are restoring their data /// from peer; user beware - caveats about). promisc: bool, /// Disable the UNIX socket RPC interface. nounixsock: bool, /// Run the go-sbot on system start-up (systemd service enabled). startup: bool, } // HELPERS AND ROUTES FOR /settings/scuttlebutt /// Scuttlebutt settings menu. #[get("/")] pub fn ssb_settings_menu(flash: Option, _auth: Authenticated) -> Template { // retrieve go-sbot systemd process status let sbot_status = SbotStatus::read().ok(); let mut context = Context::new(); context.insert("sbot_status", &sbot_status); context.insert("back", &Some("/settings".to_string())); context.insert("title", &Some("Scuttlebutt Settings".to_string())); if let Some(flash) = flash { context.insert("flash_name", &Some(flash.kind().to_string())); context.insert("flash_msg", &Some(flash.message().to_string())); }; Template::render("settings/scuttlebutt/menu", &context.into_json()) } /// Sbot configuration page (includes form for updating configuration parameters). #[get("/configure")] pub fn configure_sbot(flash: Option, _auth: Authenticated) -> Template { // retrieve go-sbot systemd process status let sbot_status = SbotStatus::read().ok(); let run_on_startup = sbot_status.map(|status| status.boot_state); // retrieve sbot config parameters let sbot_config = SbotConfig::read().ok(); let mut context = Context::new(); context.insert("back", &Some("/settings/scuttlebutt".to_string())); context.insert("title", &Some("Sbot Configuration".to_string())); context.insert("sbot_config", &sbot_config); context.insert("run_on_startup", &Some(run_on_startup)); if let Some(flash) = flash { context.insert("flash_name", &Some(flash.kind().to_string())); context.insert("flash_msg", &Some(flash.message().to_string())); }; Template::render("settings/scuttlebutt/configure_sbot", &context.into_json()) } // TODO: consider using `Contextual` here to collect all submitted form // fields to re-render forms with submitted values on error /// Receive the sbot configuration form data and save it to file. #[post("/configure", data = "")] pub fn configure_sbot_post(config: Form, _auth: Authenticated) -> Flash { // call `into_inner()` to take ownership of the `config` data let owned_config = config.into_inner(); // concat the ip and port for listen address let lis = format!("{}:{}", owned_config.lis_ip, owned_config.lis_port); // instantiate `SbotConfig` from form data let config = SbotConfig { lis, hops: owned_config.hops, repo: owned_config.repo, debugdir: owned_config.debugdir, shscap: owned_config.shscap, localadv: owned_config.localadv, localdiscov: owned_config.localdiscov, hmac: owned_config.hmac, wslis: owned_config.wslis, debuglis: owned_config.debuglis, enable_ebt: owned_config.enable_ebt, promisc: owned_config.promisc, nounixsock: owned_config.nounixsock, }; match owned_config.startup { true => { info!("Enabling go-sbot.service"); match systemctl_sbot_cmd("enable") { Err(e) => warn!("Failed to enable go-sbot.service: {}", e), _ => (), } } false => { info!("Disabling go-sbot.service"); match systemctl_sbot_cmd("disable") { Err(e) => warn!("Failed to disable go-sbot.service: {}", e), _ => (), } } }; // write config to file match SbotConfig::write(config) { Ok(_) => Flash::success( Redirect::to("/settings/scuttlebutt/configure"), "Updated configuration", ), Err(e) => Flash::error( Redirect::to("/settings/scuttlebutt/configure"), format!("Failed to update configuration: {}", e), ), } } /// Set default configuration parameters for the go-sbot and save them to file. #[get("/configure/default")] pub fn configure_sbot_default(_auth: Authenticated) -> Flash { let default_config = SbotConfig::default(); // write default config to file match SbotConfig::write(default_config) { Ok(_) => Flash::success( Redirect::to("/settings/scuttlebutt/configure"), "Restored default configuration", ), Err(e) => Flash::error( Redirect::to("/settings/scuttlebutt/configure"), format!("Failed to restore default configuration: {}", e), ), } } /// Attempt to start the go-sbot.service process. /// Redirect to the Scuttlebutt settings menu and communicate the outcome of /// the attempt via a flash message. #[get("/start")] pub fn start_sbot(_auth: Authenticated) -> Flash { info!("Starting go-sbot.service"); match systemctl_sbot_cmd("start") { Ok(_) => Flash::success( Redirect::to("/settings/scuttlebutt"), "Sbot process has been started", ), Err(_) => Flash::error( Redirect::to("/settings/scuttlebutt"), "Failed to start the sbot process", ), } } /// Attempt to stop the go-sbot.service process. /// Redirect to the Scuttlebutt settings menu and communicate the outcome of /// the attempt via a flash message. #[get("/stop")] pub fn stop_sbot(_auth: Authenticated) -> Flash { info!("Stopping go-sbot.service"); match systemctl_sbot_cmd("stop") { Ok(_) => Flash::success( Redirect::to("/settings/scuttlebutt"), "Sbot process has been stopped", ), Err(_) => Flash::error( Redirect::to("/settings/scuttlebutt"), "Failed to stop the sbot process", ), } } /// Attempt to restart the go-sbot.service process. /// Redirect to the Scuttlebutt settings menu and communicate the outcome of /// the attempt via a flash message. #[get("/restart")] pub fn restart_sbot(_auth: Authenticated) -> Flash { info!("Restarting go-sbot.service"); match systemctl_sbot_cmd("stop") { // if stop was successful, try to start the process Ok(_) => match systemctl_sbot_cmd("start") { Ok(_) => Flash::success( Redirect::to("/settings/scuttlebutt"), "Sbot process has been restarted", ), Err(_) => Flash::error( Redirect::to("/settings/scuttlebutt"), "Failed to start the sbot process", ), }, Err(_) => Flash::error( Redirect::to("/settings/scuttlebutt"), "Failed to stop the sbot process", ), } } // HELPER FUNCTIONS /// Executes a systemctl command for the go-sbot.service process. pub fn systemctl_sbot_cmd(cmd: &str) -> io::Result { //info!("Disabling go-sbot.service"); Command::new("systemctl") .arg("--user") .arg(cmd) .arg("go-sbot.service") .output() } /// Executes a systemctl disable command for the go-sbot.service process. pub fn disable_sbot_cmd() -> io::Result { info!("Disabling go-sbot.service"); Command::new("systemctl") .arg("--user") .arg("disable") .arg("go-sbot.service") .output() } /// Executes a systemctl enable command for the go-sbot.service process. pub fn enable_sbot_cmd() -> io::Result { info!("Enabling go-sbot.service"); Command::new("systemctl") .arg("--user") .arg("enable") .arg("go-sbot.service") .output() } /// Executes a systemctl start command for the go-sbot.service process. pub fn start_sbot_cmd() -> io::Result { info!("Starting go-sbot.service"); Command::new("systemctl") .arg("--user") .arg("start") .arg("go-sbot.service") .output() } /// Executes a systemctl stop command for the go-sbot.service process. pub fn stop_sbot_cmd() -> io::Result { info!("Stopping go-sbot.service"); Command::new("systemctl") .arg("--user") .arg("stop") .arg("go-sbot.service") .output() }