add config file reader for go-sbot
This commit is contained in:
parent
e474ea519f
commit
3397e5eb75
|
@ -6,6 +6,7 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
|
dirs = "4.0"
|
||||||
fslock="0.1.6"
|
fslock="0.1.6"
|
||||||
jsonrpc-client-core = "0.5"
|
jsonrpc-client-core = "0.5"
|
||||||
jsonrpc-client-http = "0.5"
|
jsonrpc-client-http = "0.5"
|
||||||
|
@ -16,4 +17,5 @@ regex = "1"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_yaml = "0.8"
|
serde_yaml = "0.8"
|
||||||
|
toml = "0.5.8"
|
||||||
sha3 = "0.10.0"
|
sha3 = "0.10.0"
|
||||||
|
|
|
@ -7,6 +7,9 @@ use std::{io, str, string};
|
||||||
/// This type represents all possible errors that can occur when interacting with the PeachCloud library.
|
/// This type represents all possible errors that can occur when interacting with the PeachCloud library.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum PeachError {
|
pub enum PeachError {
|
||||||
|
/// Represents a failure to determine the path of the user's home directory.
|
||||||
|
HomeDir,
|
||||||
|
|
||||||
/// Represents all other cases of `std::io::Error`.
|
/// Represents all other cases of `std::io::Error`.
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
|
|
||||||
|
@ -67,6 +70,9 @@ pub enum PeachError {
|
||||||
/// Represents a failure to serialize or deserialize JSON.
|
/// Represents a failure to serialize or deserialize JSON.
|
||||||
SerdeJson(serde_json::error::Error),
|
SerdeJson(serde_json::error::Error),
|
||||||
|
|
||||||
|
/// Represents a failure to deserialize TOML.
|
||||||
|
Toml(toml::de::Error),
|
||||||
|
|
||||||
/// Represents a failure to serialize or deserialize YAML.
|
/// Represents a failure to serialize or deserialize YAML.
|
||||||
SerdeYaml(serde_yaml::Error),
|
SerdeYaml(serde_yaml::Error),
|
||||||
|
|
||||||
|
@ -87,7 +93,7 @@ pub enum PeachError {
|
||||||
Write {
|
Write {
|
||||||
/// The underlying source of the error.
|
/// The underlying source of the error.
|
||||||
source: io::Error,
|
source: io::Error,
|
||||||
/// The file path for the write attemp.
|
/// The file path for the write attempt.
|
||||||
path: String,
|
path: String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -95,6 +101,7 @@ pub enum PeachError {
|
||||||
impl std::error::Error for PeachError {
|
impl std::error::Error for PeachError {
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
match *self {
|
match *self {
|
||||||
|
PeachError::HomeDir => None,
|
||||||
PeachError::Io(_) => None,
|
PeachError::Io(_) => None,
|
||||||
PeachError::JsonRpcClientCore(_) => None,
|
PeachError::JsonRpcClientCore(_) => None,
|
||||||
PeachError::JsonRpcCore(_) => None,
|
PeachError::JsonRpcCore(_) => None,
|
||||||
|
@ -111,6 +118,7 @@ impl std::error::Error for PeachError {
|
||||||
PeachError::SerdeJson(_) => None,
|
PeachError::SerdeJson(_) => None,
|
||||||
PeachError::SerdeYaml(_) => None,
|
PeachError::SerdeYaml(_) => None,
|
||||||
PeachError::SsbAdminIdNotFound { .. } => None,
|
PeachError::SsbAdminIdNotFound { .. } => None,
|
||||||
|
PeachError::Toml(_) => None,
|
||||||
PeachError::Utf8ToStr(_) => None,
|
PeachError::Utf8ToStr(_) => None,
|
||||||
PeachError::Utf8ToString(_) => None,
|
PeachError::Utf8ToString(_) => None,
|
||||||
PeachError::Write { ref source, .. } => Some(source),
|
PeachError::Write { ref source, .. } => Some(source),
|
||||||
|
@ -121,6 +129,12 @@ impl std::error::Error for PeachError {
|
||||||
impl std::fmt::Display for PeachError {
|
impl std::fmt::Display for PeachError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
|
PeachError::HomeDir => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Unable to determine the path of the user's home directory"
|
||||||
|
)
|
||||||
|
}
|
||||||
PeachError::Io(ref err) => err.fmt(f),
|
PeachError::Io(ref err) => err.fmt(f),
|
||||||
PeachError::JsonRpcClientCore(ref err) => err.fmt(f),
|
PeachError::JsonRpcClientCore(ref err) => err.fmt(f),
|
||||||
PeachError::JsonRpcCore(ref err) => {
|
PeachError::JsonRpcCore(ref err) => {
|
||||||
|
@ -158,6 +172,7 @@ impl std::fmt::Display for PeachError {
|
||||||
PeachError::SsbAdminIdNotFound { ref id } => {
|
PeachError::SsbAdminIdNotFound { ref id } => {
|
||||||
write!(f, "Config error: SSB admin ID `{}` not found", id)
|
write!(f, "Config error: SSB admin ID `{}` not found", id)
|
||||||
}
|
}
|
||||||
|
PeachError::Toml(ref err) => err.fmt(f),
|
||||||
PeachError::Utf8ToStr(ref err) => err.fmt(f),
|
PeachError::Utf8ToStr(ref err) => err.fmt(f),
|
||||||
PeachError::Utf8ToString(ref err) => err.fmt(f),
|
PeachError::Utf8ToString(ref err) => err.fmt(f),
|
||||||
PeachError::Write { ref path, .. } => {
|
PeachError::Write { ref path, .. } => {
|
||||||
|
@ -209,6 +224,12 @@ impl From<serde_yaml::Error> for PeachError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<toml::de::Error> for PeachError {
|
||||||
|
fn from(err: toml::de::Error) -> PeachError {
|
||||||
|
PeachError::Toml(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<str::Utf8Error> for PeachError {
|
impl From<str::Utf8Error> for PeachError {
|
||||||
fn from(err: str::Utf8Error) -> PeachError {
|
fn from(err: str::Utf8Error) -> PeachError {
|
||||||
PeachError::Utf8ToStr(err)
|
PeachError::Utf8ToStr(err)
|
||||||
|
|
|
@ -4,7 +4,7 @@ pub mod error;
|
||||||
pub mod network_client;
|
pub mod network_client;
|
||||||
pub mod oled_client;
|
pub mod oled_client;
|
||||||
pub mod password_utils;
|
pub mod password_utils;
|
||||||
pub mod sbot_client;
|
pub mod sbot;
|
||||||
pub mod stats_client;
|
pub mod stats_client;
|
||||||
|
|
||||||
// re-export error types
|
// re-export error types
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use nanorand::{Rng, WyRand};
|
use nanorand::{Rng, WyRand};
|
||||||
use sha3::{Digest, Sha3_256};
|
use sha3::{Digest, Sha3_256};
|
||||||
|
|
||||||
use crate::{config_manager, error::PeachError, sbot_client};
|
use crate::{config_manager, error::PeachError};
|
||||||
|
|
||||||
/// Returns Ok(()) if the supplied password is correct,
|
/// Returns Ok(()) if the supplied password is correct,
|
||||||
/// and returns Err if the supplied password is incorrect.
|
/// and returns Err if the supplied password is incorrect.
|
||||||
|
@ -102,7 +102,8 @@ using this link: http://peach.local/reset_password",
|
||||||
// finally send the message to the admins
|
// finally send the message to the admins
|
||||||
let peach_config = config_manager::load_peach_config()?;
|
let peach_config = config_manager::load_peach_config()?;
|
||||||
for ssb_admin_id in peach_config.ssb_admin_ids {
|
for ssb_admin_id in peach_config.ssb_admin_ids {
|
||||||
sbot_client::private_message(&msg, &ssb_admin_id)?;
|
// TODO: replace with golgi
|
||||||
|
//sbot_client::private_message(&msg, &ssb_admin_id)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
//! Data types and associated methods for monitoring and configuring go-sbot.
|
||||||
|
|
||||||
|
use std::{fs, process::Command, str};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use toml;
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,111 +0,0 @@
|
||||||
//! Interfaces for monitoring and configuring go-sbot using sbotcli.
|
|
||||||
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::error::PeachError;
|
|
||||||
|
|
||||||
pub fn is_sbot_online() -> Result<bool, PeachError> {
|
|
||||||
let output = Command::new("/usr/bin/systemctl")
|
|
||||||
.arg("status")
|
|
||||||
.arg("peach-go-sbot")
|
|
||||||
.output()?;
|
|
||||||
let status = output.status;
|
|
||||||
// returns true if the service had an exist status of 0 (is running)
|
|
||||||
let is_running = status.success();
|
|
||||||
Ok(is_running)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// currently go-sbotcli determines where the working directory is
|
|
||||||
/// using the home directory of th user that invokes it
|
|
||||||
/// this could be changed to be supplied as CLI arg
|
|
||||||
/// but for now all sbotcli commands must first become peach-go-sbot before running
|
|
||||||
/// the sudoers file is configured to allow this to happen without a password
|
|
||||||
pub fn sbotcli_command() -> Command {
|
|
||||||
let mut command = Command::new("sudo");
|
|
||||||
command
|
|
||||||
.arg("-u")
|
|
||||||
.arg("peach-go-sbot")
|
|
||||||
.arg("/usr/bin/sbotcli");
|
|
||||||
command
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn post(msg: &str) -> Result<(), PeachError> {
|
|
||||||
let mut command = sbotcli_command();
|
|
||||||
let output = command.arg("publish").arg("post").arg(msg).output()?;
|
|
||||||
if output.status.success() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
let stderr = std::str::from_utf8(&output.stderr)?;
|
|
||||||
Err(PeachError::SbotCli {
|
|
||||||
msg: format!("Error making ssb post: {}", stderr),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct WhoAmIValue {
|
|
||||||
id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn whoami() -> Result<String, PeachError> {
|
|
||||||
let mut command = sbotcli_command();
|
|
||||||
let output = command.arg("call").arg("whoami").output()?;
|
|
||||||
let text_output = std::str::from_utf8(&output.stdout)?;
|
|
||||||
let value: WhoAmIValue = serde_json::from_str(text_output)?;
|
|
||||||
let id = value.id;
|
|
||||||
Ok(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_invite(uses: i32) -> Result<String, PeachError> {
|
|
||||||
let mut command = sbotcli_command();
|
|
||||||
let output = command
|
|
||||||
.arg("invite")
|
|
||||||
.arg("create")
|
|
||||||
.arg("--uses")
|
|
||||||
.arg(uses.to_string())
|
|
||||||
.output()?;
|
|
||||||
let text_output = std::str::from_utf8(&output.stdout)?;
|
|
||||||
let output = text_output.replace('\n', "");
|
|
||||||
Ok(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_pub_name(new_name: &str) -> Result<(), PeachError> {
|
|
||||||
let pub_ssb_id = whoami()?;
|
|
||||||
let mut command = sbotcli_command();
|
|
||||||
let output = command
|
|
||||||
.arg("publish")
|
|
||||||
.arg("about")
|
|
||||||
.arg("--name")
|
|
||||||
.arg(new_name)
|
|
||||||
.arg(pub_ssb_id)
|
|
||||||
.output()?;
|
|
||||||
if output.status.success() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
let stderr = std::str::from_utf8(&output.stderr)?;
|
|
||||||
Err(PeachError::SbotCli {
|
|
||||||
msg: format!("Error updating pub name: {}", stderr),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn private_message(msg: &str, recipient: &str) -> Result<(), PeachError> {
|
|
||||||
let mut command = sbotcli_command();
|
|
||||||
let output = command
|
|
||||||
.arg("publish")
|
|
||||||
.arg("post")
|
|
||||||
.arg("--recps")
|
|
||||||
.arg(recipient)
|
|
||||||
.arg(msg)
|
|
||||||
.output()?;
|
|
||||||
if output.status.success() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
let stderr = std::str::from_utf8(&output.stderr)?;
|
|
||||||
Err(PeachError::SbotCli {
|
|
||||||
msg: format!("Error sending ssb private message: {}", stderr),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue