//! Data types and associated methods for monitoring and configuring go-sbot. use std::{fs, fs::File, io::Write, process::Command, str}; use serde::{Deserialize, Serialize}; use crate::error::PeachError; /// go-sbot process status. #[derive(Debug, Serialize, Deserialize)] pub struct SbotStatus { /// Current process state. pub state: Option, /// Current process boot state. pub boot_state: Option, /// Current process memory usage in bytes. pub memory: Option, /// Uptime for the process (if state is `active`). pub uptime: Option, /// Downtime for the process (if state is `inactive`). pub downtime: Option, } impl SbotStatus { /// Default builder for `SbotStatus`. fn default() -> Self { Self { state: None, boot_state: None, memory: None, uptime: None, downtime: None, } } /// Retrieve statistics for the go-sbot systemd process by querying `systemctl`. pub fn read() -> Result { let mut status = SbotStatus::default(); let info_output = Command::new("/usr/bin/systemctl") .arg("--user") .arg("show") .arg("go-sbot.service") .arg("--no-page") .output()?; let service_info = std::str::from_utf8(&info_output.stdout)?; for line in service_info.lines() { if line.starts_with("ActiveState=") { if let Some(state) = line.strip_prefix("ActiveState=") { status.state = Some(state.to_string()) } } else if line.starts_with("MemoryCurrent=") { if let Some(memory) = line.strip_prefix("MemoryCurrent=") { status.memory = memory.parse().ok() } } } let status_output = Command::new("/usr/bin/systemctl") .arg("--user") .arg("status") .arg("go-sbot.service") .output()?; let service_status = str::from_utf8(&status_output.stdout)?; //.map_err(PeachError::Utf8ToStr)?; for line in service_status.lines() { // example of the output line we're looking for: // `Loaded: loaded (/home/glyph/.config/systemd/user/go-sbot.service; enabled; vendor // preset: enabled)` if line.contains("Loaded:") { let before_boot_state = line.find(';'); let after_boot_state = line.rfind(';'); if let (Some(start), Some(end)) = (before_boot_state, after_boot_state) { // extract the enabled / disabled from the `Loaded: ...` line // using the index of the first ';' + 2 and the last ';' status.boot_state = Some(line[start + 2..end].to_string()); } // example of the output line we're looking for here: // `Active: active (running) since Mon 2022-01-24 16:22:51 SAST; 4min 14s ago` } else if line.contains("Active:") { let before_time = line.find(';'); let after_time = line.find(" ago"); if let (Some(start), Some(end)) = (before_time, after_time) { // extract the uptime / downtime from the `Active: ...` line // using the index of ';' + 2 and the index of " ago" let time = Some(&line[start + 2..end]); // if service is active then the `time` reading is uptime if status.state == Some("active".to_string()) { status.uptime = time.map(|t| t.to_string()) // if service is inactive then the `time` reading is downtime } else if status.state == Some("inactive".to_string()) { status.downtime = time.map(|t| t.to_string()) } } } } Ok(status) } } #[derive(Debug, Serialize, Deserialize)] pub struct SbotConfig { // TODO: maybe define as a Path type? /// Directory path for the log and indexes. repo: Option, /// Directory path for writing debug output. debugdir: Option, /// Secret-handshake app-key (aka. network key). shscap: Option, /// HMAC hash used to sign messages. hmac: Option, /// Replication hops (1: friends, 2: friends of friends). hops: Option, /// Address to listen on. lis: Option, /// Address to listen on for WebSocket connections. wslis: Option, /// Address to for metrics and pprof HTTP server. debuglis: Option, /// Enable sending local UDP broadcasts. localadv: Option, /// Enable listening for UDP broadcasts and connecting. localdiscov: Option, /// Enable syncing by using epidemic-broadcast-trees (EBT). #[serde(rename = "enable-ebt")] enable_ebt: Option, /// Bypass graph auth and fetch remote's feed (useful for pubs that are restoring their data /// from peer; user beware - caveats about). promisc: Option, /// Disable the UNIX socket RPC interface. nounixsock: Option, } impl SbotConfig { pub fn read() -> Result { // determine path of user's home directory let mut config_path = dirs::home_dir().ok_or(PeachError::HomeDir)?; config_path.push(".ssb-go/config.toml"); let config_contents = fs::read_to_string(config_path)?; let config: SbotConfig = toml::from_str(&config_contents)?; Ok(config) } pub fn write(config: SbotConfig) -> Result<(), PeachError> { // convert the provided `SbotConfig` instance to a string let config_string = toml::to_string(&config)?; // determine path of user's home directory let mut config_path = dirs::home_dir().ok_or(PeachError::HomeDir)?; config_path.push(".ssb-go/config.toml"); // open config file for writing let mut file = File::create(config_path)?; // write the config string to file write!(file, "{}", config_string)?; Ok(()) } }