279 lines
9.7 KiB
Rust
279 lines
9.7 KiB
Rust
//! Data types and associated methods for monitoring and configuring solar-sbot.
|
|
|
|
use std::{fs, fs::File, io, io::Write, path::PathBuf, process::Command, str};
|
|
use std::os::linux::raw::ino_t;
|
|
use tilde_client::{TildeClient};
|
|
use log::debug;
|
|
|
|
use crate::config_manager;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::error::PeachError;
|
|
|
|
/* HELPER FUNCTIONS */
|
|
|
|
// iterate over the given directory path to determine the size of the directory
|
|
fn dir_size(path: impl Into<PathBuf>) -> io::Result<u64> {
|
|
fn dir_size(mut dir: fs::ReadDir) -> io::Result<u64> {
|
|
dir.try_fold(0, |acc, file| {
|
|
let file = file?;
|
|
let size = match file.metadata()? {
|
|
data if data.is_dir() => dir_size(fs::read_dir(file.path())?)?,
|
|
data => data.len(),
|
|
};
|
|
Ok(acc + size)
|
|
})
|
|
}
|
|
|
|
dir_size(fs::read_dir(path.into())?)
|
|
}
|
|
|
|
/* SBOT-RELATED TYPES AND METHODS */
|
|
|
|
/// solar-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>,
|
|
/// Size of the blobs directory in bytes.
|
|
pub blobstore: Option<u64>,
|
|
}
|
|
|
|
/// Default builder for `SbotStatus`.
|
|
impl Default for SbotStatus {
|
|
fn default() -> Self {
|
|
Self {
|
|
state: None,
|
|
boot_state: None,
|
|
memory: None,
|
|
uptime: None,
|
|
downtime: None,
|
|
blobstore: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SbotStatus {
|
|
/// Retrieve statistics for the solar-sbot systemd process by querying `systemctl`.
|
|
pub fn read() -> Result<Self, PeachError> {
|
|
let mut status = SbotStatus::default();
|
|
|
|
// note this command does not need to be run as sudo
|
|
// because non-privileged users are able to run systemctl show
|
|
let service_name = config_manager::get_config_value("TILDE_SBOT_SERVICE")?;
|
|
let info_output = Command::new("systemctl")
|
|
.arg("show")
|
|
.arg(service_name)
|
|
.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()
|
|
}
|
|
}
|
|
}
|
|
|
|
// note this command does not need to be run as sudo
|
|
// because non-privileged users are able to run systemctl status
|
|
let status_output = Command::new("systemctl")
|
|
.arg("status")
|
|
.arg(config_manager::get_config_value("TILDE_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/solar-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())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TOOD restore this
|
|
// get path to blobstore
|
|
// let blobstore_path = format!(
|
|
// "{}/blobs/sha256",
|
|
// config_manager::get_config_value("TILDE_SBOT_DATADIR")?
|
|
// );
|
|
let blobstore_path = format!(
|
|
"{}",
|
|
config_manager::get_config_value("TILDE_SBOT_DATADIR")?
|
|
);
|
|
|
|
// determine the size of the blobstore directory in bytes
|
|
status.blobstore = dir_size(blobstore_path).ok();
|
|
|
|
Ok(status)
|
|
}
|
|
}
|
|
|
|
/// solar-sbot configuration parameters.
|
|
#[derive(Debug, Serialize, Deserialize, Default)]
|
|
#[serde(default)]
|
|
pub struct Config {
|
|
// TODO: maybe define as a Path type?
|
|
/// Directory path for the log and indexes.
|
|
pub database_directory: String,
|
|
/// Secret-handshake app-key (aka. network key).
|
|
pub shscap: String,
|
|
/// HMAC hash used to sign messages.
|
|
pub hmac: String,
|
|
/// Replication hops (1: friends, 2: friends of friends).
|
|
pub replication_hops: u8,
|
|
/// Ip address of pub
|
|
pub ip: String,
|
|
/// Address to listen on.
|
|
pub ssb_port: String,
|
|
/// Enable sending local UDP broadcasts.
|
|
pub localadv: bool,
|
|
/// Enable listening for UDP broadcasts and connecting.
|
|
pub localdiscov: bool,
|
|
/// Enable syncing by using epidemic-broadcast-trees (EBT).
|
|
#[serde(rename(serialize = "enable_ebt", deserialize = "enable-ebt"))]
|
|
pub 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).
|
|
pub promisc: bool,
|
|
/// Disable the UNIX socket RPC interface.
|
|
pub nounixsock: bool,
|
|
}
|
|
|
|
// TODO: make this real
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
#[serde(default)]
|
|
pub struct SbotConfig {
|
|
pub database_directory: String,
|
|
pub shscap: String,
|
|
pub hmac: String,
|
|
pub replication_hops: i8,
|
|
pub ip: String,
|
|
pub ssb_port: String,
|
|
// TODO: below settings have not been configured with tilde
|
|
pub localadv: bool,
|
|
pub localdiscov: bool,
|
|
pub enable_ebt: bool,
|
|
pub promisc: bool,
|
|
pub nounixsock: bool,
|
|
}
|
|
|
|
/// Default configuration values for solar-sbot.
|
|
impl Default for SbotConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
database_directory: "~/.local/share/tildefriends".to_string(),
|
|
shscap: "1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=".to_string(),
|
|
hmac: "".to_string(),
|
|
replication_hops: 1,
|
|
ip: "".to_string(),
|
|
ssb_port: "8008".to_string(),
|
|
localadv: false,
|
|
localdiscov: false,
|
|
enable_ebt: false,
|
|
promisc: false,
|
|
nounixsock: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SbotConfig {
|
|
/// Read the solar-sbot `config.toml` file from file and deserialize into `SbotConfig`.
|
|
pub fn read() -> Result<Self, PeachError> {
|
|
// determine path of user's solar-sbot config.toml
|
|
let config_path = format!(
|
|
"{}/sbot-config.toml",
|
|
config_manager::get_config_value("TILDE_SBOT_DATADIR")?
|
|
);
|
|
|
|
let config_contents = fs::read_to_string(config_path)?;
|
|
|
|
let config: SbotConfig = toml::from_str(&config_contents)?;
|
|
|
|
Ok(config)
|
|
}
|
|
|
|
/// Write the given `SbotConfig` to the tilde-sbot `sbot-config.toml` file.
|
|
pub fn write(config: SbotConfig) -> Result<(), PeachError> {
|
|
let repo_comment = "# For details about tilde-sbot configuration, please visit tilde friends documentation\n".to_string();
|
|
|
|
// convert the provided `SbotConfig` instance to a string
|
|
let config_string = toml::to_string(&config)?;
|
|
|
|
// determine path of user's solar-sbot config.toml
|
|
let config_path = format!(
|
|
"{}/sbot-config.toml",
|
|
config_manager::get_config_value("TILDE_SBOT_DATADIR")?
|
|
);
|
|
|
|
// open config file for writing
|
|
let mut file = File::create(config_path)?;
|
|
|
|
// write the repo comment to file
|
|
write!(file, "{}", repo_comment)?;
|
|
|
|
// write the config string to file
|
|
write!(file, "{}", config_string)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Initialise an sbot client
|
|
pub async fn init_sbot() -> Result<TildeClient, PeachError> {
|
|
|
|
// read sbot config
|
|
let sbot_config = match SbotConfig::read() {
|
|
Ok(config) => config,
|
|
// build default config if an error is returned from the read attempt
|
|
Err(_) => SbotConfig::default(),
|
|
};
|
|
|
|
debug!("Initialising an sbot client with configuration parameters");
|
|
let sbot_client = TildeClient {
|
|
port: sbot_config.ssb_port,
|
|
binary_path: config_manager::get_config_value("TILDE_BINARY_PATH")?
|
|
};
|
|
Ok(sbot_client)
|
|
}
|