peach-workspace/peach-lib/src/sbot.rs

169 lines
6.1 KiB
Rust

//! 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<String>,
/// Current process boot state.
pub boot_state: Option<String>,
/// Current process memory usage in bytes.
pub memory: Option<u32>,
/// Uptime for the process (if state is `active`).
pub uptime: Option<String>,
/// Downtime for the process (if state is `inactive`).
pub downtime: Option<String>,
}
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<Self, PeachError> {
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<String>,
/// Directory path for writing debug output.
debugdir: Option<String>,
/// Secret-handshake app-key (aka. network key).
shscap: Option<String>,
/// HMAC hash used to sign messages.
hmac: Option<String>,
/// Replication hops (1: friends, 2: friends of friends).
hops: Option<u8>,
/// Address to listen on.
lis: Option<String>,
/// Address to listen on for WebSocket connections.
wslis: Option<String>,
/// Address to for metrics and pprof HTTP server.
debuglis: Option<String>,
/// Enable sending local UDP broadcasts.
localadv: Option<bool>,
/// Enable listening for UDP broadcasts and connecting.
localdiscov: Option<bool>,
/// Enable syncing by using epidemic-broadcast-trees (EBT).
#[serde(rename = "enable-ebt")]
enable_ebt: Option<bool>,
/// Bypass graph auth and fetch remote's feed (useful for pubs that are restoring their data
/// from peer; user beware - caveats about).
promisc: Option<bool>,
/// Disable the UNIX socket RPC interface.
nounixsock: Option<bool>,
}
impl SbotConfig {
pub fn read() -> Result<Self, PeachError> {
// 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(())
}
}