merge latest changes from main
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "peach-stats"
|
name = "peach-stats"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
authors = ["Andrew Reid <glyph@mycelial.technology>"]
|
authors = ["Andrew Reid <glyph@mycelial.technology>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Query system statistics. Provides a wrapper around the probes and systemstat crates."
|
description = "Query system statistics. Provides a wrapper around the probes and systemstat crates."
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
# peach-stats
|
# peach-stats
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
System statistics library for PeachCloud. Provides a wrapper around the [probes](https://crates.io/crates/probes) and [systemstat](https://crates.io/crates/systemstat) crates.
|
System statistics library for PeachCloud. Provides a wrapper around the [probes](https://crates.io/crates/probes) and [systemstat](https://crates.io/crates/systemstat) crates.
|
||||||
|
|
||||||
Currently offers the following statistics and associated data structures:
|
Currently offers the following system statistics and associated data structures:
|
||||||
|
|
||||||
- CPU: `user`, `system`, `nice`, `idle` (as values or percentages)
|
- CPU: `user`, `system`, `nice`, `idle` (as values or percentages)
|
||||||
- Disk usage: `filesystem`, `one_k_blocks`, `one_k_blocks_used`,
|
- Disk usage: `filesystem`, `one_k_blocks`, `one_k_blocks_used`,
|
||||||
@ -13,10 +13,14 @@ Currently offers the following statistics and associated data structures:
|
|||||||
- Memory: `total`, `free`, `used`
|
- Memory: `total`, `free`, `used`
|
||||||
- Uptime: `seconds`
|
- Uptime: `seconds`
|
||||||
|
|
||||||
|
As well as the following go-sbot process statistics:
|
||||||
|
|
||||||
|
- Sbot: `state`, `memory`, `uptime`, `downtime`
|
||||||
|
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use peach_stats::{stats, StatsError};
|
use peach_stats::{sbot, stats, StatsError};
|
||||||
|
|
||||||
fn main() -> Result<(), StatsError> {
|
fn main() -> Result<(), StatsError> {
|
||||||
let cpu = stats::cpu_stats()?;
|
let cpu = stats::cpu_stats()?;
|
||||||
@ -25,6 +29,7 @@ fn main() -> Result<(), StatsError> {
|
|||||||
let load = stats::load_average()?;
|
let load = stats::load_average()?;
|
||||||
let mem = stats::mem_stats()?;
|
let mem = stats::mem_stats()?;
|
||||||
let uptime = stats::uptime()?;
|
let uptime = stats::uptime()?;
|
||||||
|
let sbot_process = sbot::sbot_stats()?;
|
||||||
|
|
||||||
// do things with the retrieved values...
|
// do things with the retrieved values...
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Custom error type for `peach-stats`.
|
//! Custom error type for `peach-stats`.
|
||||||
|
|
||||||
use probes::ProbeError;
|
use probes::ProbeError;
|
||||||
use std::{error, fmt, io::Error as IoError};
|
use std::{error, fmt, io::Error as IoError, str::Utf8Error};
|
||||||
|
|
||||||
/// Custom error type encapsulating all possible errors when retrieving system
|
/// Custom error type encapsulating all possible errors when retrieving system
|
||||||
/// statistics.
|
/// statistics.
|
||||||
@ -17,6 +17,10 @@ pub enum StatsError {
|
|||||||
MemStat(ProbeError),
|
MemStat(ProbeError),
|
||||||
/// Failed to retrieve system uptime.
|
/// Failed to retrieve system uptime.
|
||||||
Uptime(IoError),
|
Uptime(IoError),
|
||||||
|
/// Systemctl command returned an error.
|
||||||
|
Systemctl(IoError),
|
||||||
|
/// Failed to interpret sequence of `u8` as a string.
|
||||||
|
Utf8String(Utf8Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for StatsError {}
|
impl error::Error for StatsError {}
|
||||||
@ -39,6 +43,12 @@ impl fmt::Display for StatsError {
|
|||||||
StatsError::Uptime(ref source) => {
|
StatsError::Uptime(ref source) => {
|
||||||
write!(f, "Failed to retrieve system uptime: {}", source)
|
write!(f, "Failed to retrieve system uptime: {}", source)
|
||||||
}
|
}
|
||||||
|
StatsError::Systemctl(ref source) => {
|
||||||
|
write!(f, "Systemctl command returned an error: {}", source)
|
||||||
|
}
|
||||||
|
StatsError::Utf8String(ref source) => {
|
||||||
|
write!(f, "Failed to convert stdout to string: {}", source)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod sbot;
|
||||||
pub mod stats;
|
pub mod stats;
|
||||||
|
|
||||||
pub use crate::error::StatsError;
|
pub use crate::error::StatsError;
|
||||||
|
90
peach-stats/src/sbot.rs
Normal file
90
peach-stats/src/sbot.rs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
//! Systemd go-sbot process statistics retrieval functions and associated data types.
|
||||||
|
|
||||||
|
use std::{process::Command, str};
|
||||||
|
|
||||||
|
use crate::StatsError;
|
||||||
|
|
||||||
|
/// go-sbot process statistics.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SbotStat {
|
||||||
|
/// Current process state.
|
||||||
|
state: String,
|
||||||
|
/// Current process memory usage in bytes.
|
||||||
|
memory: Option<u32>,
|
||||||
|
/// Uptime for the process (if state is `active`).
|
||||||
|
uptime: Option<String>,
|
||||||
|
/// Downtime for the process (if state is `inactive`).
|
||||||
|
downtime: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SbotStat {
|
||||||
|
/// Default builder for `SbotStat`.
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
state: String::new(),
|
||||||
|
memory: None,
|
||||||
|
uptime: None,
|
||||||
|
downtime: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve statistics for the go-sbot systemd process by querying `systemctl`.
|
||||||
|
pub fn sbot_stats() -> Result<SbotStat, StatsError> {
|
||||||
|
let mut status = SbotStat::default();
|
||||||
|
|
||||||
|
let info_output = Command::new("/usr/bin/systemctl")
|
||||||
|
.arg("--user")
|
||||||
|
.arg("show")
|
||||||
|
.arg("go-sbot.service")
|
||||||
|
.arg("--no-page")
|
||||||
|
.output()
|
||||||
|
.map_err(StatsError::Systemctl)?;
|
||||||
|
|
||||||
|
let service_info = std::str::from_utf8(&info_output.stdout).map_err(StatsError::Utf8String)?;
|
||||||
|
|
||||||
|
for line in service_info.lines() {
|
||||||
|
if line.starts_with("ActiveState=") {
|
||||||
|
if let Some(state) = line.strip_prefix("ActiveState=") {
|
||||||
|
status.state = 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()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let service_status = str::from_utf8(&status_output.stdout).map_err(StatsError::Utf8String)?;
|
||||||
|
|
||||||
|
// example of the output line we're looking for:
|
||||||
|
// `Active: active (running) since Mon 2022-01-24 16:22:51 SAST; 4min 14s ago`
|
||||||
|
|
||||||
|
for line in service_status.lines() {
|
||||||
|
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 == "active" {
|
||||||
|
status.uptime = time.map(|t| t.to_string())
|
||||||
|
// if service is inactive then the `time` reading is downtime
|
||||||
|
} else if status.state == "inactive" {
|
||||||
|
status.downtime = time.map(|t| t.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(status)
|
||||||
|
}
|
@ -36,7 +36,6 @@ maintenance = { status = "actively-developed" }
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
lazy_static = "1.4.0"
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
nest = "1.0.0"
|
nest = "1.0.0"
|
||||||
peach-lib = { path = "../peach-lib" }
|
peach-lib = { path = "../peach-lib" }
|
||||||
|
@ -25,7 +25,7 @@ Move into the repo and compile:
|
|||||||
|
|
||||||
Run the tests:
|
Run the tests:
|
||||||
|
|
||||||
`ROCKET_DISABLE_AUTH=true PEACH_STANDALONE_MODE=false cargo test`
|
`ROCKET_DISABLE_AUTH=true ROCKET_STANDALONE_MODE=false cargo test`
|
||||||
|
|
||||||
Move back to the `peach-workspace` directory:
|
Move back to the `peach-workspace` directory:
|
||||||
|
|
||||||
@ -37,21 +37,23 @@ Run the binary:
|
|||||||
|
|
||||||
### Environment
|
### Environment
|
||||||
|
|
||||||
**Deployment Mode**
|
**Deployment Profile**
|
||||||
|
|
||||||
The web application deployment mode is configured with the `ROCKET_ENV` environment variable:
|
The web application deployment profile can be configured with the `ROCKET_ENV` environment variable:
|
||||||
|
|
||||||
`export ROCKET_ENV=stage`
|
`export ROCKET_ENV=stage`
|
||||||
|
|
||||||
Other deployment modes are `dev` and `prod`. Read the [Rocket Environment Configurations docs](https://rocket.rs/v0.5-rc/guide/configuration/#environment-variables) for further information.
|
Default configuration parameters are defined in `Rocket.toml`. This file defines a set of default parameters, some of which are overwritten when running in `debug` mode (ie. `cargo run` or `cargo build`) or `release` mode (ie. `cargo run --release` or `cargo build --release`).
|
||||||
|
|
||||||
|
Read the [Rocket Environment Configurations docs](https://rocket.rs/v0.5-rc/guide/configuration/#environment-variables) for further information.
|
||||||
|
|
||||||
**Configuration Mode**
|
**Configuration Mode**
|
||||||
|
|
||||||
The web application can be run with a minimal set of routes and functionality (PeachPub - a simple sbot manager) or with the full-suite of capabilities, including network management and access to device statistics (PeachCloud). The mode is configured with the `PEACH_STANDALONE_MODE` environment variable: `true` or `false`. If the variable is unset or the value is incorrectly set, the application defaults to standalone mode.
|
The web application can be run with a minimal set of routes and functionality (PeachPub - a simple sbot manager) or with the full-suite of capabilities, including network management and access to device statistics (PeachCloud). The mode is enabled by default (as defined in `Rocket.toml`) but can be overwritten using the `ROCKET_STANDALONE_MODE` environment variable: `true` or `false`. If the variable is unset or the value is incorrectly set, the application defaults to standalone mode.
|
||||||
|
|
||||||
**Authentication**
|
**Authentication**
|
||||||
|
|
||||||
Authentication is disabled in `development` mode and enabled by default when running the application in `production` mode. It can be disabled by setting the `ROCKET_DISABLE_AUTH` environment variable to `true`:
|
Authentication is disabled in `debug` mode and enabled by default when running the application in `release` mode. It can be disabled by setting the `ROCKET_DISABLE_AUTH` environment variable to `true`:
|
||||||
|
|
||||||
`export ROCKET_DISABLE_AUTH=true`
|
`export ROCKET_DISABLE_AUTH=true`
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
[default]
|
[default]
|
||||||
secret_key = "VYVUDivXvu8g6llxeJd9F92pMfocml5xl/Jjv5Sk4yw="
|
secret_key = "VYVUDivXvu8g6llxeJd9F92pMfocml5xl/Jjv5Sk4yw="
|
||||||
|
disable_auth = false
|
||||||
|
standalone_mode = true
|
||||||
|
|
||||||
[development]
|
[debug]
|
||||||
template_dir = "templates/"
|
template_dir = "templates/"
|
||||||
disable_auth = true
|
disable_auth = true
|
||||||
|
|
||||||
[production]
|
[release]
|
||||||
template_dir = "templates/"
|
template_dir = "templates/"
|
||||||
disable_auth = false
|
|
||||||
|
@ -32,21 +32,23 @@ pub mod routes;
|
|||||||
mod tests;
|
mod tests;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
use std::{env, process};
|
use std::process;
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use log::{debug, error, info};
|
||||||
use log::{error, info};
|
use rocket::{fairing::AdHoc, serde::Deserialize, Build, Rocket};
|
||||||
use rocket::{Build, Rocket};
|
|
||||||
|
|
||||||
pub type BoxError = Box<dyn std::error::Error>;
|
pub type BoxError = Box<dyn std::error::Error>;
|
||||||
|
|
||||||
lazy_static! {
|
/// Application configuration parameters.
|
||||||
// determine run-mode from env var; default to standalone mode (aka peachpub)
|
/// These values are extracted from Rocket's default configuration provider:
|
||||||
static ref STANDALONE_MODE: bool = match env::var("PEACH_STANDALONE_MODE") {
|
/// `Config::figment()`. As such, the values are drawn from `Rocket.toml` or
|
||||||
// parse the value to a boolean; default to true for any error
|
/// the TOML file path in the `ROCKET_CONFIG` environment variable. The TOML
|
||||||
Ok(val) => val.parse().unwrap_or(true),
|
/// file parameters are automatically overruled by any `ROCKET_` variables
|
||||||
Err(_) => true
|
/// which might be set.
|
||||||
};
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct RocketConfig {
|
||||||
|
disable_auth: bool,
|
||||||
|
standalone_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
static WLAN_IFACE: &str = "wlan0";
|
static WLAN_IFACE: &str = "wlan0";
|
||||||
@ -54,11 +56,27 @@ static AP_IFACE: &str = "ap0";
|
|||||||
|
|
||||||
pub fn init_rocket() -> Rocket<Build> {
|
pub fn init_rocket() -> Rocket<Build> {
|
||||||
info!("Initializing Rocket");
|
info!("Initializing Rocket");
|
||||||
if *STANDALONE_MODE {
|
// build a basic rocket instance
|
||||||
router::mount_peachpub_routes()
|
let rocket = rocket::build();
|
||||||
|
|
||||||
|
// return the default provider figment used by `rocket::build()`
|
||||||
|
let figment = rocket.figment();
|
||||||
|
|
||||||
|
// deserialize configuration parameters into our `RocketConfig` struct (defined above)
|
||||||
|
// since we're in the intialisation phase, panic if the extraction fails
|
||||||
|
let config: RocketConfig = figment.extract().expect("configuration extraction failed");
|
||||||
|
|
||||||
|
debug!("{:?}", config);
|
||||||
|
|
||||||
|
info!("Mounting Rocket routes");
|
||||||
|
let mounted_rocket = if config.standalone_mode {
|
||||||
|
router::mount_peachpub_routes(rocket)
|
||||||
} else {
|
} else {
|
||||||
router::mount_peachcloud_routes()
|
router::mount_peachcloud_routes(rocket)
|
||||||
}
|
};
|
||||||
|
|
||||||
|
info!("Attaching application configuration to managed state");
|
||||||
|
mounted_rocket.attach(AdHoc::config::<RocketConfig>())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Launch the peach-web rocket server.
|
/// Launch the peach-web rocket server.
|
||||||
|
@ -14,8 +14,8 @@ use crate::routes::{
|
|||||||
/// catchers. This gives us everything we need to run PeachPub and excludes
|
/// catchers. This gives us everything we need to run PeachPub and excludes
|
||||||
/// settings and status routes related to networking and the device (memory,
|
/// settings and status routes related to networking and the device (memory,
|
||||||
/// hard disk, CPU etc.).
|
/// hard disk, CPU etc.).
|
||||||
pub fn mount_peachpub_routes() -> Rocket<Build> {
|
pub fn mount_peachpub_routes(rocket: Rocket<Build>) -> Rocket<Build> {
|
||||||
rocket::build()
|
rocket
|
||||||
.mount(
|
.mount(
|
||||||
"/",
|
"/",
|
||||||
routes![
|
routes![
|
||||||
@ -66,8 +66,8 @@ pub fn mount_peachpub_routes() -> Rocket<Build> {
|
|||||||
/// Create a Rocket instance with PeachPub routes, fileserver and catchers by
|
/// Create a Rocket instance with PeachPub routes, fileserver and catchers by
|
||||||
/// calling `mount_peachpub_routes()` and then mount all additional routes
|
/// calling `mount_peachpub_routes()` and then mount all additional routes
|
||||||
/// required to run a complete PeachCloud build.
|
/// required to run a complete PeachCloud build.
|
||||||
pub fn mount_peachcloud_routes() -> Rocket<Build> {
|
pub fn mount_peachcloud_routes(rocket: Rocket<Build>) -> Rocket<Build> {
|
||||||
mount_peachpub_routes()
|
mount_peachpub_routes(rocket)
|
||||||
.mount(
|
.mount(
|
||||||
"/settings/network",
|
"/settings/network",
|
||||||
routes![
|
routes![
|
||||||
|
@ -7,7 +7,6 @@ use rocket::{
|
|||||||
request::{self, FlashMessage, FromRequest, Request},
|
request::{self, FlashMessage, FromRequest, Request},
|
||||||
response::{Flash, Redirect},
|
response::{Flash, Redirect},
|
||||||
serde::Deserialize,
|
serde::Deserialize,
|
||||||
Config,
|
|
||||||
};
|
};
|
||||||
use rocket_dyn_templates::{tera::Context, Template};
|
use rocket_dyn_templates::{tera::Context, Template};
|
||||||
|
|
||||||
@ -15,6 +14,8 @@ use peach_lib::{error::PeachError, password_utils};
|
|||||||
|
|
||||||
use crate::error::PeachWebError;
|
use crate::error::PeachWebError;
|
||||||
use crate::utils::TemplateOrRedirect;
|
use crate::utils::TemplateOrRedirect;
|
||||||
|
//use crate::DisableAuth;
|
||||||
|
use crate::RocketConfig;
|
||||||
|
|
||||||
// HELPERS AND STRUCTS FOR AUTHENTICATION WITH COOKIES
|
// HELPERS AND STRUCTS FOR AUTHENTICATION WITH COOKIES
|
||||||
|
|
||||||
@ -42,14 +43,14 @@ impl<'r> FromRequest<'r> for Authenticated {
|
|||||||
type Error = LoginError;
|
type Error = LoginError;
|
||||||
|
|
||||||
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||||
// check for `disable_auth` config value; set to `false` if unset
|
// retrieve auth state from managed state (returns `Option<bool>`).
|
||||||
// can be set via the `ROCKET_DISABLE_AUTH` environment variable
|
// this value is read from the Rocket.toml config file on start-up
|
||||||
// - env var, if set, takes precedence over value defined in `Rocket.toml`
|
let authentication_is_disabled: bool = *req
|
||||||
let authentication_is_disabled: bool = match Config::figment().find_value("disable_auth") {
|
.rocket()
|
||||||
// deserialize the boolean value; set to `false` if an error is encountered
|
.state::<RocketConfig>()
|
||||||
Ok(value) => value.deserialize().unwrap_or(false),
|
.map(|config| (&config.disable_auth))
|
||||||
Err(_) => false,
|
.unwrap_or(&false);
|
||||||
};
|
|
||||||
if authentication_is_disabled {
|
if authentication_is_disabled {
|
||||||
let auth = Authenticated {};
|
let auth = Authenticated {};
|
||||||
request::Outcome::Success(auth)
|
request::Outcome::Success(auth)
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
use rocket::{get, request::FlashMessage};
|
use rocket::{get, request::FlashMessage, State};
|
||||||
use rocket_dyn_templates::{tera::Context, Template};
|
use rocket_dyn_templates::{tera::Context, Template};
|
||||||
|
|
||||||
use crate::routes::authentication::Authenticated;
|
use crate::routes::authentication::Authenticated;
|
||||||
use crate::STANDALONE_MODE;
|
use crate::RocketConfig;
|
||||||
|
|
||||||
// HELPERS AND ROUTES FOR / (HOME PAGE)
|
// HELPERS AND ROUTES FOR / (HOME PAGE)
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub fn home(_auth: Authenticated) -> Template {
|
pub fn home(_auth: Authenticated, config: &State<RocketConfig>) -> Template {
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
context.insert("flash_name", &None::<()>);
|
context.insert("flash_name", &None::<()>);
|
||||||
context.insert("flash_msg", &None::<()>);
|
context.insert("flash_msg", &None::<()>);
|
||||||
context.insert("title", &None::<()>);
|
context.insert("title", &None::<()>);
|
||||||
// pass in mode so we can define appropriate urls in template
|
|
||||||
context.insert("standalone_mode", &*STANDALONE_MODE);
|
// pass in mode from managed state so we can define appropriate urls in template
|
||||||
|
context.insert("standalone_mode", &config.standalone_mode);
|
||||||
|
|
||||||
Template::render("home", &context.into_json())
|
Template::render("home", &context.into_json())
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use rocket::{get, request::FlashMessage};
|
use rocket::get;
|
||||||
use rocket_dyn_templates::{tera::Context, Template};
|
use rocket_dyn_templates::{tera::Context, Template};
|
||||||
|
|
||||||
use crate::routes::authentication::Authenticated;
|
use crate::routes::authentication::Authenticated;
|
||||||
|
Reference in New Issue
Block a user