forked from PeachCloud/peach-workspace
Compare commits
30 Commits
main
...
refactor_n
Author | SHA1 | Date | |
---|---|---|---|
8032b83c41 | |||
dfa1306b2d | |||
2f7c7aac8f | |||
46b7c0fc2b | |||
f62c8f0b51 | |||
06e48deb3a | |||
fb6d0317b6 | |||
cfbf052d27 | |||
d240741958 | |||
33486b4e1d | |||
8c3fecb875 | |||
0907fbc474 | |||
b747ff6db2 | |||
220c7fd540 | |||
ed7e172efb | |||
bc0f2d595b | |||
61b33d1613 | |||
c3fa188400 | |||
a1444cf478 | |||
79c94e6af0 | |||
30f00524f4 | |||
cd8e5737c4 | |||
2429ea8fdd | |||
406206bab3 | |||
c72c57c345 | |||
bf9dd7f567 | |||
e6a6fcdc89 | |||
b2f7747357 | |||
f17ae95b21 | |||
91180ed1fe |
404
Cargo.lock
generated
404
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,7 @@ members = [
|
||||
"peach-menu",
|
||||
"peach-monitor",
|
||||
"peach-stats",
|
||||
"peach-jsonrpc-server",
|
||||
"peach-probe",
|
||||
"peach-dyndns-updater"
|
||||
]
|
||||
|
25
peach-jsonrpc-server/Cargo.toml
Normal file
25
peach-jsonrpc-server/Cargo.toml
Normal file
@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "peach-jsonrpc-server"
|
||||
authors = ["Andrew Reid <glyph@mycelial.technology>"]
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "JSON-RPC over HTTP for the PeachCloud system. Provides a JSON-RPC wrapper around the stats, network and oled libraries."
|
||||
homepage = "https://opencollective.com/peachcloud"
|
||||
repository = "https://git.coopcloud.tech/PeachCloud/peach-workspace"
|
||||
readme = "README.md"
|
||||
license = "AGPL-3.0-only"
|
||||
publish = false
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[dependencies]
|
||||
env_logger = "0.9"
|
||||
jsonrpc-core = "18"
|
||||
jsonrpc-http-server = "18"
|
||||
log = "0.4"
|
||||
miniserde = "0.1.15"
|
||||
peach-stats = { path = "../peach-stats", features = ["miniserde_support"] }
|
||||
|
||||
[dev-dependencies]
|
||||
jsonrpc-test = "18"
|
72
peach-jsonrpc-server/README.md
Normal file
72
peach-jsonrpc-server/README.md
Normal file
@ -0,0 +1,72 @@
|
||||
# peach-jsonrpc-server
|
||||
|
||||
A JSON-RPC server for the PeachCloud system which exposes an API over HTTP.
|
||||
|
||||
Currently includes peach-stats capability (system statistics).
|
||||
|
||||
## JSON-RPC API
|
||||
|
||||
| Method | Description | Returns |
|
||||
| --- | --- | --- |
|
||||
| `cpu_stats` | CPU statistics | `user`, `system`, `nice`, `idle` |
|
||||
| `cpu_stats_percent` | CPU statistics as percentages | `user`, `system`, `nice`, `idle` |
|
||||
| `disk_usage` | Disk usage statistics (array of disks) | `filesystem`, `one_k_blocks`, `one_k_blocks_used`, `one_k_blocks_free`, `used_percentage`, `mountpoint` |
|
||||
| `load_average` | Load average statistics | `one`, `five`, `fifteen` |
|
||||
| `mem_stats` | Memory statistics | `total`, `free`, `used` |
|
||||
| `ping` | Microservice status | `success` if running |
|
||||
| `uptime` | System uptime | `secs` |
|
||||
|
||||
## Environment
|
||||
|
||||
The JSON-RPC HTTP server is currently hardcoded to run on "127.0.0.1:5110". Address and port configuration settings will later be exposed via CLI arguments and possibly an environment variable.
|
||||
|
||||
Logging is made available with `env_logger`:
|
||||
|
||||
`export RUST_LOG=info`
|
||||
|
||||
Other logging levels include `debug`, `warn` and `error`.
|
||||
|
||||
## Setup
|
||||
|
||||
Clone the peach-workspace repo:
|
||||
|
||||
`git clone https://git.coopcloud.tech/PeachCloud/peach-workspace`
|
||||
|
||||
Move into the repo peaach-jsonrpc-server directory and compile a release build:
|
||||
|
||||
`cd peach-jsonrpc-server`
|
||||
`cargo build --release`
|
||||
|
||||
Run the binary:
|
||||
|
||||
`./peach-workspace/target/release/peach-jsonrpc-server`
|
||||
|
||||
## Debian Packaging
|
||||
|
||||
TODO.
|
||||
|
||||
## Example Usage
|
||||
|
||||
**Get CPU Statistics**
|
||||
|
||||
With microservice running, open a second terminal window and use `curl` to call server methods:
|
||||
|
||||
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "cpu_stats", "id":1 }' 127.0.0.1:5110`
|
||||
|
||||
Server responds with:
|
||||
|
||||
`{"jsonrpc":"2.0","result":"{\"user\":4661083,\"system\":1240371,\"idle\":326838290,\"nice\":0}","id":1}`
|
||||
|
||||
**Get System Uptime**
|
||||
|
||||
With microservice running, open a second terminal window and use `curl` to call server methods:
|
||||
|
||||
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "uptime", "id":1 }' 127.0.0.1:5110`
|
||||
|
||||
Server responds with:
|
||||
|
||||
`{"jsonrpc":"2.0","result":"{\"secs\":840968}","id":1}`
|
||||
|
||||
### Licensing
|
||||
|
||||
AGPL-3.0
|
46
peach-jsonrpc-server/src/error.rs
Normal file
46
peach-jsonrpc-server/src/error.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use std::fmt;
|
||||
|
||||
use jsonrpc_core::{Error as JsonRpcError, ErrorCode};
|
||||
use peach_stats::StatsError;
|
||||
|
||||
/// Custom error type encapsulating all possible errors for a JSON-RPC server
|
||||
/// and associated methods.
|
||||
#[derive(Debug)]
|
||||
pub enum JsonRpcServerError {
|
||||
/// An error returned from the `peach-stats` library.
|
||||
Stats(StatsError),
|
||||
/// An expected JSON-RPC method parameter was not provided.
|
||||
MissingParameter(JsonRpcError),
|
||||
/// Failed to parse a provided JSON-RPC method parameter.
|
||||
ParseParameter(JsonRpcError),
|
||||
}
|
||||
|
||||
impl fmt::Display for JsonRpcServerError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
JsonRpcServerError::ParseParameter(ref source) => {
|
||||
write!(f, "Failed to parse parameter: {}", source)
|
||||
}
|
||||
JsonRpcServerError::MissingParameter(ref source) => {
|
||||
write!(f, "Missing expected parameter: {}", source)
|
||||
}
|
||||
JsonRpcServerError::Stats(ref source) => {
|
||||
write!(f, "{}", source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsonRpcServerError> for JsonRpcError {
|
||||
fn from(err: JsonRpcServerError) -> Self {
|
||||
match &err {
|
||||
JsonRpcServerError::Stats(source) => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32001),
|
||||
message: format!("{}", source),
|
||||
data: None,
|
||||
},
|
||||
JsonRpcServerError::MissingParameter(source) => source.clone(),
|
||||
JsonRpcServerError::ParseParameter(source) => source.clone(),
|
||||
}
|
||||
}
|
||||
}
|
141
peach-jsonrpc-server/src/lib.rs
Normal file
141
peach-jsonrpc-server/src/lib.rs
Normal file
@ -0,0 +1,141 @@
|
||||
//! # peach-jsonrpc-server
|
||||
//!
|
||||
//! A JSON-RPC server which exposes an API over HTTP.
|
||||
|
||||
use std::env;
|
||||
use std::result::Result;
|
||||
|
||||
use jsonrpc_core::{IoHandler, Value};
|
||||
use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder};
|
||||
use log::info;
|
||||
use miniserde::json;
|
||||
use peach_stats::stats;
|
||||
|
||||
mod error;
|
||||
use crate::error::JsonRpcServerError;
|
||||
|
||||
/// Create JSON-RPC I/O handler, add RPC methods and launch HTTP server.
|
||||
pub fn run() -> Result<(), JsonRpcServerError> {
|
||||
info!("Starting up.");
|
||||
|
||||
info!("Creating JSON-RPC I/O handler.");
|
||||
let mut io = IoHandler::default();
|
||||
|
||||
io.add_sync_method("ping", |_| Ok(Value::String("success".to_string())));
|
||||
|
||||
// TODO: add blocks of methods according to provided flags
|
||||
|
||||
/* PEACH-STATS RPC METHODS */
|
||||
|
||||
io.add_sync_method("cpu_stats", move |_| {
|
||||
info!("Fetching CPU statistics.");
|
||||
let cpu = stats::cpu_stats().map_err(JsonRpcServerError::Stats)?;
|
||||
let json_cpu = json::to_string(&cpu);
|
||||
|
||||
Ok(Value::String(json_cpu))
|
||||
});
|
||||
|
||||
io.add_sync_method("cpu_stats_percent", move |_| {
|
||||
info!("Fetching CPU statistics as percentages.");
|
||||
let cpu = stats::cpu_stats_percent().map_err(JsonRpcServerError::Stats)?;
|
||||
let json_cpu = json::to_string(&cpu);
|
||||
|
||||
Ok(Value::String(json_cpu))
|
||||
});
|
||||
|
||||
io.add_sync_method("disk_usage", move |_| {
|
||||
info!("Fetching disk usage statistics.");
|
||||
let disks = stats::disk_usage().map_err(JsonRpcServerError::Stats)?;
|
||||
let json_disks = json::to_string(&disks);
|
||||
|
||||
Ok(Value::String(json_disks))
|
||||
});
|
||||
|
||||
io.add_sync_method("load_average", move |_| {
|
||||
info!("Fetching system load average statistics.");
|
||||
let avg = stats::load_average().map_err(JsonRpcServerError::Stats)?;
|
||||
let json_avg = json::to_string(&avg);
|
||||
|
||||
Ok(Value::String(json_avg))
|
||||
});
|
||||
|
||||
io.add_sync_method("mem_stats", move |_| {
|
||||
info!("Fetching current memory statistics.");
|
||||
let mem = stats::mem_stats().map_err(JsonRpcServerError::Stats)?;
|
||||
let json_mem = json::to_string(&mem);
|
||||
|
||||
Ok(Value::String(json_mem))
|
||||
});
|
||||
|
||||
io.add_sync_method("uptime", move |_| {
|
||||
info!("Fetching system uptime.");
|
||||
let uptime = stats::uptime().map_err(JsonRpcServerError::Stats)?;
|
||||
let json_uptime = json::to_string(&uptime);
|
||||
|
||||
Ok(Value::String(json_uptime))
|
||||
});
|
||||
|
||||
let http_server =
|
||||
env::var("PEACH_JSONRPC_SERVER").unwrap_or_else(|_| "127.0.0.1:5110".to_string());
|
||||
|
||||
info!("Starting JSON-RPC server on {}.", http_server);
|
||||
let server = ServerBuilder::new(io)
|
||||
.cors(DomainsValidation::AllowOnly(vec![
|
||||
AccessControlAllowOrigin::Null,
|
||||
]))
|
||||
.start_http(
|
||||
&http_server
|
||||
.parse()
|
||||
.expect("Invalid HTTP address and port combination"),
|
||||
)
|
||||
.expect("Unable to start RPC server");
|
||||
|
||||
server.wait();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use jsonrpc_core::{Error as JsonRpcError, ErrorCode};
|
||||
use jsonrpc_test as test_rpc;
|
||||
|
||||
#[test]
|
||||
fn rpc_success() {
|
||||
let rpc = {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_sync_method("rpc_success_response", |_| {
|
||||
Ok(Value::String("success".into()))
|
||||
});
|
||||
test_rpc::Rpc::from(io)
|
||||
};
|
||||
|
||||
assert_eq!(rpc.request("rpc_success_response", &()), r#""success""#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rpc_parse_error() {
|
||||
let rpc = {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_sync_method("rpc_parse_error", |_| {
|
||||
let e = JsonRpcError {
|
||||
code: ErrorCode::ParseError,
|
||||
message: String::from("Parse error"),
|
||||
data: None,
|
||||
};
|
||||
Err(JsonRpcError::from(JsonRpcServerError::MissingParameter(e)))
|
||||
});
|
||||
test_rpc::Rpc::from(io)
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
rpc.request("rpc_parse_error", &()),
|
||||
r#"{
|
||||
"code": -32700,
|
||||
"message": "Parse error"
|
||||
}"#
|
||||
);
|
||||
}
|
||||
}
|
34
peach-jsonrpc-server/src/main.rs
Normal file
34
peach-jsonrpc-server/src/main.rs
Normal file
@ -0,0 +1,34 @@
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! # peach-jsonrpc-server
|
||||
//!
|
||||
//! A JSON-RPC server which exposes an over HTTP.
|
||||
//!
|
||||
//! Currently includes peach-stats capability (system statistics).
|
||||
//!
|
||||
//! ## API
|
||||
//!
|
||||
//! | Method | Description | Returns |
|
||||
//! | --- | --- | --- |
|
||||
//! | `cpu_stats` | CPU statistics | `user`, `system`, `nice`, `idle` |
|
||||
//! | `cpu_stats_percent` | CPU statistics as percentages | `user`, `system`, `nice`, `idle` |
|
||||
//! | `disk_usage` | Disk usage statistics (array of disks) | `filesystem`, `one_k_blocks`, `one_k_blocks_used`, `one_k_blocks_free`, `used_percentage`, `mountpoint` |
|
||||
//! | `load_average` | Load average statistics | `one`, `five`, `fifteen` |
|
||||
//! | `mem_stats` | Memory statistics | `total`, `free`, `used` |
|
||||
//! | `ping` | Microservice status | `success` if running |
|
||||
//! | `uptime` | System uptime | `secs` |
|
||||
|
||||
use std::process;
|
||||
|
||||
use log::error;
|
||||
|
||||
fn main() {
|
||||
// initalize the logger
|
||||
env_logger::init();
|
||||
|
||||
// handle errors returned from `run`
|
||||
if let Err(e) = peach_jsonrpc_server::run() {
|
||||
error!("Application error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
@ -1,24 +1,19 @@
|
||||
[package]
|
||||
name = "peach-lib"
|
||||
version = "1.2.15"
|
||||
authors = ["Andrew Reid <gnomad@cryptolab.net>"]
|
||||
version = "1.3.1"
|
||||
authors = ["Andrew Reid <glyph@mycelial.technology>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
chrono = "0.4.19"
|
||||
fslock="0.1.6"
|
||||
jsonrpc-client-core = "0.5"
|
||||
jsonrpc-client-http = "0.5"
|
||||
jsonrpc-core = "8.0.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
log = "0.4"
|
||||
nanorand = "0.6.1"
|
||||
regex = "1"
|
||||
rust-crypto = "0.2.36"
|
||||
serde_derive = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_yaml = "0.8"
|
||||
env_logger = "0.6"
|
||||
snafu = "0.6"
|
||||
regex = "1"
|
||||
chrono = "0.4.19"
|
||||
rand="0.8.4"
|
||||
fslock="0.1.6"
|
||||
|
@ -4,12 +4,12 @@
|
||||
//!
|
||||
//! The configuration file is located at: "/var/lib/peachcloud/config.yml"
|
||||
|
||||
use fslock::{LockFile};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
|
||||
use fslock::LockFile;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::PeachError;
|
||||
use crate::error::*;
|
||||
|
||||
// main configuration file
|
||||
pub const YAML_PATH: &str = "/var/lib/peachcloud/config.yml";
|
||||
@ -48,8 +48,9 @@ fn save_peach_config(peach_config: PeachConfig) -> Result<PeachConfig, PeachErro
|
||||
|
||||
let yaml_str = serde_yaml::to_string(&peach_config)?;
|
||||
|
||||
fs::write(YAML_PATH, yaml_str).context(WriteConfigError {
|
||||
file: YAML_PATH.to_string(),
|
||||
fs::write(YAML_PATH, yaml_str).map_err(|source| PeachError::Write {
|
||||
source,
|
||||
path: YAML_PATH.to_string(),
|
||||
})?;
|
||||
|
||||
// unlock file lock
|
||||
@ -79,8 +80,9 @@ pub fn load_peach_config() -> Result<PeachConfig, PeachError> {
|
||||
}
|
||||
// otherwise we load peach config from disk
|
||||
else {
|
||||
let contents = fs::read_to_string(YAML_PATH).context(ReadConfigError {
|
||||
file: YAML_PATH.to_string(),
|
||||
let contents = fs::read_to_string(YAML_PATH).map_err(|source| PeachError::Read {
|
||||
source,
|
||||
path: YAML_PATH.to_string(),
|
||||
})?;
|
||||
peach_config = serde_yaml::from_str(&contents)?;
|
||||
}
|
||||
@ -176,4 +178,4 @@ pub fn get_temporary_password_hash() -> Result<String, PeachError> {
|
||||
} else {
|
||||
Err(PeachError::PasswordNotSet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,24 +9,24 @@
|
||||
//!
|
||||
//! The domain for dyndns updates is stored in /var/lib/peachcloud/config.yml
|
||||
//! The tsig key for authenticating the updates is stored in /var/lib/peachcloud/peach-dyndns/tsig.key
|
||||
use crate::config_manager::{load_peach_config, set_peach_dyndns_config};
|
||||
use crate::error::PeachError;
|
||||
use crate::error::{
|
||||
ChronoParseError, DecodeNsUpdateOutputError, DecodePublicIpError, GetPublicIpError,
|
||||
NsCommandError, SaveDynDnsResultError, SaveTsigKeyError,
|
||||
use std::{
|
||||
fs,
|
||||
fs::OpenOptions,
|
||||
io::Write,
|
||||
process::{Command, Stdio},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use chrono::prelude::*;
|
||||
use jsonrpc_client_core::{expand_params, jsonrpc_client};
|
||||
use jsonrpc_client_http::HttpTransport;
|
||||
use log::{debug, info};
|
||||
use regex::Regex;
|
||||
use snafu::ResultExt;
|
||||
use std::fs;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::str::FromStr;
|
||||
use std::str::ParseBoolError;
|
||||
|
||||
use crate::{
|
||||
config_manager::{load_peach_config, set_peach_dyndns_config},
|
||||
error::PeachError,
|
||||
};
|
||||
|
||||
/// constants for dyndns configuration
|
||||
pub const PEACH_DYNDNS_URL: &str = "http://dynserver.dyn.peachcloud.org";
|
||||
@ -37,20 +37,21 @@ pub const DYNDNS_LOG_PATH: &str = "/var/lib/peachcloud/peach-dyndns/latest_resul
|
||||
/// helper function which saves dyndns TSIG key returned by peach-dyndns-server to /var/lib/peachcloud/peach-dyndns/tsig.key
|
||||
pub fn save_dyndns_key(key: &str) -> Result<(), PeachError> {
|
||||
// create directory if it doesn't exist
|
||||
fs::create_dir_all(PEACH_DYNDNS_CONFIG_PATH).context(SaveTsigKeyError {
|
||||
path: PEACH_DYNDNS_CONFIG_PATH.to_string(),
|
||||
})?;
|
||||
fs::create_dir_all(PEACH_DYNDNS_CONFIG_PATH)?;
|
||||
//.context(SaveTsigKeyError {
|
||||
//path: PEACH_DYNDNS_CONFIG_PATH.to_string(),
|
||||
//})?;
|
||||
// write key text
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(TSIG_KEY_PATH)
|
||||
.context(SaveTsigKeyError {
|
||||
path: TSIG_KEY_PATH.to_string(),
|
||||
})?;
|
||||
writeln!(file, "{}", key).context(SaveTsigKeyError {
|
||||
// TODO: consider adding context msg
|
||||
.open(TSIG_KEY_PATH)?;
|
||||
writeln!(file, "{}", key).map_err(|source| PeachError::Write {
|
||||
source,
|
||||
path: TSIG_KEY_PATH.to_string(),
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -68,23 +69,17 @@ pub fn register_domain(domain: &str) -> std::result::Result<String, PeachError>
|
||||
let mut client = PeachDynDnsClient::new(transport_handle);
|
||||
|
||||
info!("Performing register_domain call to peach-dyndns-server");
|
||||
let res = client.register_domain(domain).call();
|
||||
match res {
|
||||
Ok(key) => {
|
||||
// save new TSIG key
|
||||
save_dyndns_key(&key)?;
|
||||
// save new configuration values
|
||||
let set_config_result =
|
||||
set_peach_dyndns_config(domain, PEACH_DYNDNS_URL, TSIG_KEY_PATH, true);
|
||||
match set_config_result {
|
||||
Ok(_) => {
|
||||
let response = "success".to_string();
|
||||
Ok(response)
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
let key = client.register_domain(domain).call()?;
|
||||
// save new TSIG key
|
||||
save_dyndns_key(&key)?;
|
||||
// save new configuration values
|
||||
let set_config_result = set_peach_dyndns_config(domain, PEACH_DYNDNS_URL, TSIG_KEY_PATH, true);
|
||||
match set_config_result {
|
||||
Ok(_) => {
|
||||
let response = "success".to_string();
|
||||
Ok(response)
|
||||
}
|
||||
Err(err) => Err(PeachError::JsonRpcClientCore { source: err }),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,29 +94,20 @@ pub fn is_domain_available(domain: &str) -> std::result::Result<bool, PeachError
|
||||
let mut client = PeachDynDnsClient::new(transport_handle);
|
||||
|
||||
info!("Performing register_domain call to peach-dyndns-server");
|
||||
let res = client.is_domain_available(domain).call();
|
||||
info!("res: {:?}", res);
|
||||
match res {
|
||||
Ok(result_str) => {
|
||||
let result: Result<bool, ParseBoolError> = FromStr::from_str(&result_str);
|
||||
match result {
|
||||
Ok(result_bool) => Ok(result_bool),
|
||||
Err(err) => Err(PeachError::PeachParseBoolError { source: err }),
|
||||
}
|
||||
}
|
||||
Err(err) => Err(PeachError::JsonRpcClientCore { source: err }),
|
||||
}
|
||||
let domain_availability = client.is_domain_available(domain).call()?;
|
||||
info!("Domain availability: {:?}", domain_availability);
|
||||
// convert availability status to a bool
|
||||
let available: bool = FromStr::from_str(&domain_availability)?;
|
||||
|
||||
Ok(available)
|
||||
}
|
||||
|
||||
/// Helper function to get public ip address of PeachCloud device.
|
||||
fn get_public_ip_address() -> Result<String, PeachError> {
|
||||
// TODO: consider other ways to get public IP address
|
||||
let output = Command::new("/usr/bin/curl")
|
||||
.arg("ifconfig.me")
|
||||
.output()
|
||||
.context(GetPublicIpError)?;
|
||||
let command_output = std::str::from_utf8(&output.stdout).context(DecodePublicIpError)?;
|
||||
Ok(command_output.to_string())
|
||||
let output = Command::new("/usr/bin/curl").arg("ifconfig.me").output()?;
|
||||
let command_output = String::from_utf8(output.stdout)?;
|
||||
Ok(command_output)
|
||||
}
|
||||
|
||||
/// Reads dyndns configurations from config.yml
|
||||
@ -146,13 +132,12 @@ pub fn dyndns_update_ip() -> Result<bool, PeachError> {
|
||||
Ok(false)
|
||||
} else {
|
||||
// call nsupdate passing appropriate configs
|
||||
let nsupdate_command = Command::new("/usr/bin/nsupdate")
|
||||
let mut nsupdate_command = Command::new("/usr/bin/nsupdate")
|
||||
.arg("-k")
|
||||
.arg(peach_config.dyn_tsig_key_path)
|
||||
.arg(&peach_config.dyn_tsig_key_path)
|
||||
.arg("-v")
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()
|
||||
.context(NsCommandError)?;
|
||||
.spawn()?;
|
||||
// pass nsupdate commands via stdin
|
||||
let public_ip_address = get_public_ip_address()?;
|
||||
info!("found public ip address: {}", public_ip_address);
|
||||
@ -168,11 +153,15 @@ pub fn dyndns_update_ip() -> Result<bool, PeachError> {
|
||||
DOMAIN = peach_config.dyn_domain,
|
||||
PUBLIC_IP_ADDRESS = public_ip_address,
|
||||
);
|
||||
write!(nsupdate_command.stdin.as_ref().unwrap(), "{}", ns_commands).unwrap();
|
||||
let nsupdate_output = nsupdate_command
|
||||
.wait_with_output()
|
||||
.context(NsCommandError)?;
|
||||
info!("output: {:?}", nsupdate_output);
|
||||
let mut nsupdate_stdin = nsupdate_command.stdin.take().ok_or(PeachError::NsUpdate {
|
||||
msg: "unable to capture stdin handle for `nsupdate` command".to_string(),
|
||||
})?;
|
||||
write!(nsupdate_stdin, "{}", ns_commands).map_err(|source| PeachError::Write {
|
||||
source,
|
||||
path: peach_config.dyn_tsig_key_path.to_string(),
|
||||
})?;
|
||||
let nsupdate_output = nsupdate_command.wait_with_output()?;
|
||||
info!("nsupdate output: {:?}", nsupdate_output);
|
||||
// We only return a successful result if nsupdate was successful
|
||||
if nsupdate_output.status.success() {
|
||||
info!("nsupdate succeeded, returning ok");
|
||||
@ -182,9 +171,8 @@ pub fn dyndns_update_ip() -> Result<bool, PeachError> {
|
||||
Ok(true)
|
||||
} else {
|
||||
info!("nsupdate failed, returning error");
|
||||
let err_msg =
|
||||
String::from_utf8(nsupdate_output.stdout).context(DecodeNsUpdateOutputError)?;
|
||||
Err(PeachError::NsUpdateError { msg: err_msg })
|
||||
let err_msg = String::from_utf8(nsupdate_output.stdout)?;
|
||||
Err(PeachError::NsUpdate { msg: err_msg })
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -195,9 +183,12 @@ pub fn log_successful_nsupdate() -> Result<bool, PeachError> {
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(DYNDNS_LOG_PATH)
|
||||
.context(SaveDynDnsResultError)?;
|
||||
write!(file, "{}", now_timestamp).context(SaveDynDnsResultError)?;
|
||||
// TODO: possibly add a context msg here ("failed to open dynamic dns success log")
|
||||
.open(DYNDNS_LOG_PATH)?;
|
||||
write!(file, "{}", now_timestamp).map_err(|source| PeachError::Write {
|
||||
source,
|
||||
path: DYNDNS_LOG_PATH.to_string(),
|
||||
})?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@ -207,12 +198,19 @@ pub fn get_num_seconds_since_successful_dns_update() -> Result<Option<i64>, Peac
|
||||
if !log_exists {
|
||||
Ok(None)
|
||||
} else {
|
||||
let contents =
|
||||
fs::read_to_string(DYNDNS_LOG_PATH).expect("Something went wrong reading the file");
|
||||
let contents = fs::read_to_string(DYNDNS_LOG_PATH).map_err(|source| PeachError::Read {
|
||||
source,
|
||||
path: DYNDNS_LOG_PATH.to_string(),
|
||||
})?;
|
||||
// replace newline if found
|
||||
// TODO: maybe we can use `.trim()` instead
|
||||
let contents = contents.replace("\n", "");
|
||||
let time_ran_dt = DateTime::parse_from_rfc3339(&contents).context(ChronoParseError {
|
||||
msg: "Error parsing dyndns time from latest_result.log".to_string(),
|
||||
// TODO: consider adding additional context?
|
||||
let time_ran_dt = DateTime::parse_from_rfc3339(&contents).map_err(|source| {
|
||||
PeachError::ParseDateTime {
|
||||
source,
|
||||
path: DYNDNS_LOG_PATH.to_string(),
|
||||
}
|
||||
})?;
|
||||
let current_time: DateTime<Utc> = Utc::now();
|
||||
let duration = current_time.signed_duration_since(time_ran_dt);
|
||||
@ -261,6 +259,7 @@ pub fn get_dyndns_subdomain(dyndns_full_domain: &str) -> Option<String> {
|
||||
|
||||
// helper function which checks if a dyndns domain is new
|
||||
pub fn check_is_new_dyndns_domain(dyndns_full_domain: &str) -> bool {
|
||||
// TODO: return `Result<bool, PeachError>` and replace `unwrap` with `?` operator
|
||||
let peach_config = load_peach_config().unwrap();
|
||||
let previous_dyndns_domain = peach_config.dyn_domain;
|
||||
dyndns_full_domain != previous_dyndns_domain
|
||||
|
@ -1,136 +1,222 @@
|
||||
//! Basic error handling for the network, OLED, stats and dyndns JSON-RPC clients.
|
||||
pub use snafu::ResultExt;
|
||||
use snafu::Snafu;
|
||||
use std::error;
|
||||
pub type BoxError = Box<dyn error::Error>;
|
||||
#![warn(missing_docs)]
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
#[snafu(visibility(pub(crate)))]
|
||||
//! Error handling for various aspects of the PeachCloud system, including the network, OLED, stats and dyndns JSON-RPC clients, as well as the configuration manager, sbot client and password utilities.
|
||||
|
||||
use std::{io, str, string};
|
||||
|
||||
/// This type represents all possible errors that can occur when interacting with the PeachCloud library.
|
||||
#[derive(Debug)]
|
||||
pub enum PeachError {
|
||||
#[snafu(display("{}", source))]
|
||||
JsonRpcHttp { source: jsonrpc_client_http::Error },
|
||||
#[snafu(display("{}", source))]
|
||||
JsonRpcClientCore { source: jsonrpc_client_core::Error },
|
||||
#[snafu(display("{}", source))]
|
||||
Serde { source: serde_json::error::Error },
|
||||
#[snafu(display("{}", source))]
|
||||
PeachParseBoolError { source: std::str::ParseBoolError },
|
||||
#[snafu(display("{}", source))]
|
||||
SetConfigError { source: serde_yaml::Error },
|
||||
#[snafu(display("Failed to read: {}", file))]
|
||||
ReadConfigError {
|
||||
source: std::io::Error,
|
||||
file: String,
|
||||
},
|
||||
#[snafu(display("Failed to save: {}", file))]
|
||||
WriteConfigError {
|
||||
source: std::io::Error,
|
||||
file: String,
|
||||
},
|
||||
#[snafu(display("Failed to save tsig key: {} {}", path, source))]
|
||||
SaveTsigKeyError {
|
||||
source: std::io::Error,
|
||||
path: String,
|
||||
},
|
||||
#[snafu(display("{}", msg))]
|
||||
NsUpdateError { msg: String },
|
||||
#[snafu(display("Failed to run nsupdate: {}", source))]
|
||||
NsCommandError { source: std::io::Error },
|
||||
#[snafu(display("Failed to get public IP address: {}", source))]
|
||||
GetPublicIpError { source: std::io::Error },
|
||||
#[snafu(display("Failed to decode public ip: {}", source))]
|
||||
DecodePublicIpError { source: std::str::Utf8Error },
|
||||
#[snafu(display("Failed to decode nsupdate output: {}", source))]
|
||||
DecodeNsUpdateOutputError { source: std::string::FromUtf8Error },
|
||||
#[snafu(display("{}", source))]
|
||||
YamlError { source: serde_yaml::Error },
|
||||
#[snafu(display("{:?}", err))]
|
||||
JsonRpcCore { err: jsonrpc_core::Error },
|
||||
#[snafu(display("Error creating regex: {}", source))]
|
||||
RegexError { source: regex::Error },
|
||||
#[snafu(display("Failed to decode utf8: {}", source))]
|
||||
FromUtf8Error { source: std::string::FromUtf8Error },
|
||||
#[snafu(display("Encountered Utf8Error: {}", source))]
|
||||
Utf8Error { source: std::str::Utf8Error },
|
||||
#[snafu(display("Stdio error: {}: {}", msg, source))]
|
||||
StdIoError { source: std::io::Error, msg: String },
|
||||
#[snafu(display("Failed to parse time from {} {}", source, msg))]
|
||||
ChronoParseError {
|
||||
source: chrono::ParseError,
|
||||
/// Represents all other cases of `std::io::Error`.
|
||||
Io(io::Error),
|
||||
|
||||
/// Represents a JSON-RPC core error returned from a JSON-RPC client.
|
||||
JsonRpcClientCore(jsonrpc_client_core::Error),
|
||||
|
||||
/// Represents a JSON-RPC core error returned from a JSON-RPC server.
|
||||
JsonRpcCore(jsonrpc_core::Error),
|
||||
|
||||
/// Represents a JSON-RPC HTTP error returned from a JSON-RPC client.
|
||||
JsonRpcHttp(jsonrpc_client_http::Error),
|
||||
|
||||
/// Represents a failure to update the nameserver.
|
||||
NsUpdate {
|
||||
/// A message describing the context of the attempted nameserver update.
|
||||
msg: String,
|
||||
},
|
||||
#[snafu(display("Failed to save dynamic dns success log: {}", source))]
|
||||
SaveDynDnsResultError { source: std::io::Error },
|
||||
#[snafu(display("New passwords do not match"))]
|
||||
PasswordsDoNotMatch,
|
||||
#[snafu(display("No admin password is set"))]
|
||||
|
||||
/// Represents a failure to parse a string slice to a boolean value.
|
||||
ParseBool(str::ParseBoolError),
|
||||
|
||||
/// Represents a failure to parse a `DateTime`. Includes the error source and the file path
|
||||
/// used in the parse attempt.
|
||||
ParseDateTime {
|
||||
/// The underlying source of the error.
|
||||
source: chrono::ParseError,
|
||||
/// The file path for the parse attempt.
|
||||
path: String,
|
||||
},
|
||||
|
||||
/// Represents the submission of an incorrect admin password.
|
||||
PasswordIncorrect,
|
||||
|
||||
/// Represents the submission of two passwords which do not match.
|
||||
PasswordMismatch,
|
||||
|
||||
/// Represents an unset admin password (empty password hash value) in the config file.
|
||||
PasswordNotSet,
|
||||
#[snafu(display("The supplied password was not correct"))]
|
||||
InvalidPassword,
|
||||
#[snafu(display("Error saving new password: {}", msg))]
|
||||
FailedToSetNewPassword { msg: String },
|
||||
#[snafu(display("Error calling sbotcli: {}", msg))]
|
||||
SbotCliError { msg: String },
|
||||
#[snafu(display("Error deleting ssb admin id, id not found"))]
|
||||
SsbAdminIdNotFound { id: String },
|
||||
|
||||
/// Represents a failure to read from input. Includes the error source and the file path used
|
||||
/// in the read attempt.
|
||||
Read {
|
||||
/// The underlying source of the error.
|
||||
source: io::Error,
|
||||
/// The file path for the read attempt.
|
||||
path: String,
|
||||
},
|
||||
|
||||
/// Represents a failure to parse or compile a regular expression.
|
||||
Regex(regex::Error),
|
||||
|
||||
/// Represents a failure to successfully execute an sbot command.
|
||||
SbotCli {
|
||||
/// The `stderr` output from the sbot command.
|
||||
msg: String,
|
||||
},
|
||||
|
||||
/// Represents a failure to serialize or deserialize JSON.
|
||||
SerdeJson(serde_json::error::Error),
|
||||
|
||||
/// Represents a failure to serialize or deserialize YAML.
|
||||
SerdeYaml(serde_yaml::Error),
|
||||
|
||||
/// Represents a failure to find the given SSB ID in the config file.
|
||||
SsbAdminIdNotFound {
|
||||
/// An SSB ID (public key).
|
||||
id: String,
|
||||
},
|
||||
|
||||
/// Represents a failure to interpret a sequence of u8 as a string slice.
|
||||
Utf8ToStr(str::Utf8Error),
|
||||
|
||||
/// Represents a failure to interpret a sequence of u8 as a String.
|
||||
Utf8ToString(string::FromUtf8Error),
|
||||
|
||||
/// Represents a failure to write to output. Includes the error source and the file path used
|
||||
/// in the write attempt.
|
||||
Write {
|
||||
/// The underlying source of the error.
|
||||
source: io::Error,
|
||||
/// The file path for the write attemp.
|
||||
path: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<jsonrpc_client_http::Error> for PeachError {
|
||||
fn from(err: jsonrpc_client_http::Error) -> PeachError {
|
||||
PeachError::JsonRpcHttp { source: err }
|
||||
impl std::error::Error for PeachError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match *self {
|
||||
PeachError::Io(_) => None,
|
||||
PeachError::JsonRpcClientCore(_) => None,
|
||||
PeachError::JsonRpcCore(_) => None,
|
||||
PeachError::JsonRpcHttp(_) => None,
|
||||
PeachError::NsUpdate { .. } => None,
|
||||
PeachError::ParseBool(_) => None,
|
||||
PeachError::ParseDateTime { ref source, .. } => Some(source),
|
||||
PeachError::PasswordIncorrect => None,
|
||||
PeachError::PasswordMismatch => None,
|
||||
PeachError::PasswordNotSet => None,
|
||||
PeachError::Read { ref source, .. } => Some(source),
|
||||
PeachError::Regex(_) => None,
|
||||
PeachError::SbotCli { .. } => None,
|
||||
PeachError::SerdeJson(_) => None,
|
||||
PeachError::SerdeYaml(_) => None,
|
||||
PeachError::SsbAdminIdNotFound { .. } => None,
|
||||
PeachError::Utf8ToStr(_) => None,
|
||||
PeachError::Utf8ToString(_) => None,
|
||||
PeachError::Write { ref source, .. } => Some(source),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jsonrpc_client_core::Error> for PeachError {
|
||||
fn from(err: jsonrpc_client_core::Error) -> PeachError {
|
||||
PeachError::JsonRpcClientCore { source: err }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::error::Error> for PeachError {
|
||||
fn from(err: serde_json::error::Error) -> PeachError {
|
||||
PeachError::Serde { source: err }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_yaml::Error> for PeachError {
|
||||
fn from(err: serde_yaml::Error) -> PeachError {
|
||||
PeachError::YamlError { source: err }
|
||||
impl std::fmt::Display for PeachError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match *self {
|
||||
PeachError::Io(ref err) => err.fmt(f),
|
||||
PeachError::JsonRpcClientCore(ref err) => err.fmt(f),
|
||||
PeachError::JsonRpcCore(ref err) => {
|
||||
write!(f, "{:?}", err)
|
||||
}
|
||||
PeachError::JsonRpcHttp(ref err) => err.fmt(f),
|
||||
PeachError::NsUpdate { ref msg } => {
|
||||
write!(f, "Nameserver error: {}", msg)
|
||||
}
|
||||
PeachError::ParseBool(ref err) => err.fmt(f),
|
||||
PeachError::ParseDateTime { ref path, .. } => {
|
||||
write!(f, "Date/time parse error: {}", path)
|
||||
}
|
||||
PeachError::PasswordIncorrect => {
|
||||
write!(f, "Password error: user-supplied password is incorrect")
|
||||
}
|
||||
PeachError::PasswordMismatch => {
|
||||
write!(f, "Password error: user-supplied passwords do not match")
|
||||
}
|
||||
PeachError::PasswordNotSet => {
|
||||
write!(
|
||||
f,
|
||||
"Password error: hash value in YAML configuration file is empty"
|
||||
)
|
||||
}
|
||||
PeachError::Read { ref path, .. } => {
|
||||
write!(f, "Read error: {}", path)
|
||||
}
|
||||
PeachError::Regex(ref err) => err.fmt(f),
|
||||
PeachError::SbotCli { ref msg } => {
|
||||
write!(f, "Sbot error: {}", msg)
|
||||
}
|
||||
PeachError::SerdeJson(ref err) => err.fmt(f),
|
||||
PeachError::SerdeYaml(ref err) => err.fmt(f),
|
||||
PeachError::SsbAdminIdNotFound { ref id } => {
|
||||
write!(f, "Config error: SSB admin ID `{}` not found", id)
|
||||
}
|
||||
PeachError::Utf8ToStr(ref err) => err.fmt(f),
|
||||
PeachError::Utf8ToString(ref err) => err.fmt(f),
|
||||
PeachError::Write { ref path, .. } => {
|
||||
write!(f, "Write error: {}", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for PeachError {
|
||||
fn from(err: std::io::Error) -> PeachError {
|
||||
PeachError::StdIoError {
|
||||
source: err,
|
||||
msg: "".to_string(),
|
||||
}
|
||||
PeachError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jsonrpc_client_core::Error> for PeachError {
|
||||
fn from(err: jsonrpc_client_core::Error) -> PeachError {
|
||||
PeachError::JsonRpcClientCore(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jsonrpc_client_http::Error> for PeachError {
|
||||
fn from(err: jsonrpc_client_http::Error) -> PeachError {
|
||||
PeachError::JsonRpcHttp(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<str::ParseBoolError> for PeachError {
|
||||
fn from(err: str::ParseBoolError) -> PeachError {
|
||||
PeachError::ParseBool(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<regex::Error> for PeachError {
|
||||
fn from(err: regex::Error) -> PeachError {
|
||||
PeachError::RegexError { source: err }
|
||||
PeachError::Regex(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::string::FromUtf8Error> for PeachError {
|
||||
fn from(err: std::string::FromUtf8Error) -> PeachError {
|
||||
PeachError::FromUtf8Error { source: err }
|
||||
impl From<serde_json::error::Error> for PeachError {
|
||||
fn from(err: serde_json::error::Error) -> PeachError {
|
||||
PeachError::SerdeJson(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::str::Utf8Error> for PeachError {
|
||||
fn from(err: std::str::Utf8Error) -> PeachError {
|
||||
PeachError::Utf8Error { source: err }
|
||||
impl From<serde_yaml::Error> for PeachError {
|
||||
fn from(err: serde_yaml::Error) -> PeachError {
|
||||
PeachError::SerdeYaml(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<chrono::ParseError> for PeachError {
|
||||
fn from(err: chrono::ParseError) -> PeachError {
|
||||
PeachError::ChronoParseError {
|
||||
source: err,
|
||||
msg: "".to_string(),
|
||||
}
|
||||
impl From<str::Utf8Error> for PeachError {
|
||||
fn from(err: str::Utf8Error) -> PeachError {
|
||||
PeachError::Utf8ToStr(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<string::FromUtf8Error> for PeachError {
|
||||
fn from(err: string::FromUtf8Error) -> PeachError {
|
||||
PeachError::Utf8ToString(err)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,3 @@
|
||||
// this is to ignore a clippy warning that suggests
|
||||
// to replace code with the same code that is already there (possibly a bug)
|
||||
#![allow(clippy::nonstandard_macro_braces)]
|
||||
|
||||
pub mod config_manager;
|
||||
pub mod dyndns_client;
|
||||
pub mod error;
|
||||
|
@ -9,9 +9,6 @@
|
||||
//! Several helper methods are also included here which bundle multiple client
|
||||
//! calls to achieve the desired functionality.
|
||||
|
||||
// TODO: fix these clippy errors so this allow can be removed
|
||||
#![allow(clippy::needless_borrow)]
|
||||
|
||||
use std::env;
|
||||
|
||||
use jsonrpc_client_core::{expand_params, jsonrpc_client};
|
||||
@ -166,9 +163,9 @@ pub fn disable(iface: &str, ssid: &str) -> std::result::Result<String, PeachErro
|
||||
let mut client = PeachNetworkClient::new(transport_handle);
|
||||
|
||||
info!("Performing id call to peach-network microservice.");
|
||||
let id = client.id(&iface, &ssid).call()?;
|
||||
let id = client.id(iface, ssid).call()?;
|
||||
info!("Performing disable call to peach-network microservice.");
|
||||
client.disable(&id, &iface).call()?;
|
||||
client.disable(&id, iface).call()?;
|
||||
|
||||
let response = "success".to_string();
|
||||
|
||||
@ -194,12 +191,12 @@ pub fn forget(iface: &str, ssid: &str) -> std::result::Result<String, PeachError
|
||||
let mut client = PeachNetworkClient::new(transport_handle);
|
||||
|
||||
info!("Performing id call to peach-network microservice.");
|
||||
let id = client.id(&iface, &ssid).call()?;
|
||||
let id = client.id(iface, ssid).call()?;
|
||||
info!("Performing delete call to peach-network microservice.");
|
||||
// WEIRD BUG: the parameters below are technically in the wrong order:
|
||||
// it should be id first and then iface, but somehow they get twisted.
|
||||
// i don't understand computers.
|
||||
client.delete(&iface, &id).call()?;
|
||||
client.delete(iface, &id).call()?;
|
||||
info!("Performing save call to peach-network microservice.");
|
||||
client.save().call()?;
|
||||
|
||||
@ -357,8 +354,7 @@ pub fn saved_ap(ssid: &str) -> std::result::Result<bool, PeachError> {
|
||||
// retrieve a list of access points with saved credentials
|
||||
let saved_aps = match client.saved_networks().call() {
|
||||
Ok(ssids) => {
|
||||
let networks: Vec<Networks> = serde_json::from_str(ssids.as_str())
|
||||
.expect("Failed to deserialize saved_networks response");
|
||||
let networks: Vec<Networks> = serde_json::from_str(ssids.as_str())?;
|
||||
networks
|
||||
}
|
||||
// return an empty vector if there are no saved access point credentials
|
||||
@ -479,7 +475,7 @@ pub fn traffic(iface: &str) -> std::result::Result<Traffic, PeachError> {
|
||||
let mut client = PeachNetworkClient::new(transport_handle);
|
||||
|
||||
let response = client.traffic(iface).call()?;
|
||||
let t: Traffic = serde_json::from_str(&response).unwrap();
|
||||
let t: Traffic = serde_json::from_str(&response)?;
|
||||
|
||||
Ok(t)
|
||||
}
|
||||
@ -506,13 +502,13 @@ pub fn update(iface: &str, ssid: &str, pass: &str) -> std::result::Result<String
|
||||
|
||||
// get the id of the network
|
||||
info!("Performing id call to peach-network microservice.");
|
||||
let id = client.id(&iface, &ssid).call()?;
|
||||
let id = client.id(iface, ssid).call()?;
|
||||
// delete the old credentials
|
||||
// WEIRD BUG: the parameters below are technically in the wrong order:
|
||||
// it should be id first and then iface, but somehow they get twisted.
|
||||
// i don't understand computers.
|
||||
info!("Performing delete call to peach-network microservice.");
|
||||
client.delete(&iface, &id).call()?;
|
||||
client.delete(iface, &id).call()?;
|
||||
// save the updates to wpa_supplicant.conf
|
||||
info!("Performing save call to peach-network microservice.");
|
||||
client.save().call()?;
|
||||
|
@ -1,23 +1,17 @@
|
||||
use crate::config_manager::{get_peachcloud_domain, load_peach_config,
|
||||
set_admin_password_hash, get_admin_password_hash,
|
||||
get_temporary_password_hash, set_temporary_password_hash};
|
||||
use crate::error::PeachError;
|
||||
use crate::sbot_client;
|
||||
use rand::distributions::Alphanumeric;
|
||||
use rand::{thread_rng, Rng};
|
||||
use std::iter;
|
||||
use crypto::digest::Digest;
|
||||
use crypto::sha3::Sha3;
|
||||
use crypto::{digest::Digest, sha3::Sha3};
|
||||
use nanorand::{Rng, WyRand};
|
||||
|
||||
use crate::{config_manager, error::PeachError, sbot_client};
|
||||
|
||||
/// Returns Ok(()) if the supplied password is correct,
|
||||
/// and returns Err if the supplied password is incorrect.
|
||||
pub fn verify_password(password: &str) -> Result<(), PeachError> {
|
||||
let real_admin_password_hash = get_admin_password_hash()?;
|
||||
let real_admin_password_hash = config_manager::get_admin_password_hash()?;
|
||||
let password_hash = hash_password(&password.to_string());
|
||||
if real_admin_password_hash == password_hash {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(PeachError::InvalidPassword)
|
||||
Err(PeachError::PasswordIncorrect)
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,22 +23,16 @@ pub fn validate_new_passwords(new_password1: &str, new_password2: &str) -> Resul
|
||||
if new_password1 == new_password2 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(PeachError::PasswordsDoNotMatch)
|
||||
Err(PeachError::PasswordMismatch)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a new password for the admin user
|
||||
pub fn set_new_password(new_password: &str) -> Result<(), PeachError> {
|
||||
let new_password_hash = hash_password(&new_password.to_string());
|
||||
let result = set_admin_password_hash(&new_password_hash);
|
||||
match result {
|
||||
Ok(_) => {
|
||||
Ok(())
|
||||
},
|
||||
Err(_err) => {
|
||||
Err(PeachError::FailedToSetNewPassword { msg: "failed to save password hash".to_string() })
|
||||
}
|
||||
}
|
||||
config_manager::set_admin_password_hash(&new_password_hash)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a hash from a password string
|
||||
@ -58,42 +46,33 @@ pub fn hash_password(password: &str) -> String {
|
||||
/// which can be used to reset the permanent password
|
||||
pub fn set_new_temporary_password(new_password: &str) -> Result<(), PeachError> {
|
||||
let new_password_hash = hash_password(&new_password.to_string());
|
||||
let result = set_temporary_password_hash(&new_password_hash);
|
||||
match result {
|
||||
Ok(_) => {
|
||||
Ok(())
|
||||
},
|
||||
Err(_err) => {
|
||||
Err(PeachError::FailedToSetNewPassword { msg: "failed to save temporary password hash".to_string() })
|
||||
}
|
||||
}
|
||||
config_manager::set_temporary_password_hash(&new_password_hash)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns Ok(()) if the supplied temp_password is correct,
|
||||
/// and returns Err if the supplied temp_password is incorrect
|
||||
pub fn verify_temporary_password(password: &str) -> Result<(), PeachError> {
|
||||
let temporary_admin_password_hash = get_temporary_password_hash()?;
|
||||
let temporary_admin_password_hash = config_manager::get_temporary_password_hash()?;
|
||||
let password_hash = hash_password(&password.to_string());
|
||||
if temporary_admin_password_hash == password_hash {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(PeachError::InvalidPassword)
|
||||
Err(PeachError::PasswordIncorrect)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a temporary password and sends it via ssb dm
|
||||
/// to the ssb id configured to be the admin of the peachcloud device
|
||||
pub fn send_password_reset() -> Result<(), PeachError> {
|
||||
// first generate a new random password of ascii characters
|
||||
let mut rng = thread_rng();
|
||||
let temporary_password: String = iter::repeat(())
|
||||
.map(|()| rng.sample(Alphanumeric))
|
||||
.map(char::from)
|
||||
.take(10)
|
||||
.collect();
|
||||
// initialise random number generator
|
||||
let mut rng = WyRand::new();
|
||||
// generate a new password of random numbers
|
||||
let temporary_password = rng.generate::<u64>().to_string();
|
||||
// save this string as a new temporary password
|
||||
set_new_temporary_password(&temporary_password)?;
|
||||
let domain = get_peachcloud_domain()?;
|
||||
let domain = config_manager::get_peachcloud_domain()?;
|
||||
|
||||
// then send temporary password as a private ssb message to admin
|
||||
let mut msg = format!(
|
||||
@ -117,7 +96,7 @@ using this link: http://peach.local/reset_password",
|
||||
};
|
||||
msg += &remote_link;
|
||||
// finally send the message to the admins
|
||||
let peach_config = load_peach_config()?;
|
||||
let peach_config = config_manager::load_peach_config()?;
|
||||
for ssb_admin_id in peach_config.ssb_admin_ids {
|
||||
sbot_client::private_message(&msg, &ssb_admin_id)?;
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
//! Interfaces for monitoring and configuring go-sbot using sbotcli.
|
||||
//!
|
||||
use crate::error::PeachError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
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")
|
||||
@ -36,7 +38,7 @@ pub fn post(msg: &str) -> Result<(), PeachError> {
|
||||
Ok(())
|
||||
} else {
|
||||
let stderr = std::str::from_utf8(&output.stderr)?;
|
||||
Err(PeachError::SbotCliError {
|
||||
Err(PeachError::SbotCli {
|
||||
msg: format!("Error making ssb post: {}", stderr),
|
||||
})
|
||||
}
|
||||
@ -83,7 +85,7 @@ pub fn update_pub_name(new_name: &str) -> Result<(), PeachError> {
|
||||
Ok(())
|
||||
} else {
|
||||
let stderr = std::str::from_utf8(&output.stderr)?;
|
||||
Err(PeachError::SbotCliError {
|
||||
Err(PeachError::SbotCli {
|
||||
msg: format!("Error updating pub name: {}", stderr),
|
||||
})
|
||||
}
|
||||
@ -102,7 +104,7 @@ pub fn private_message(msg: &str, recipient: &str) -> Result<(), PeachError> {
|
||||
Ok(())
|
||||
} else {
|
||||
let stderr = std::str::from_utf8(&output.stderr)?;
|
||||
Err(PeachError::SbotCliError {
|
||||
Err(PeachError::SbotCli {
|
||||
msg: format!("Error sending ssb private message: {}", stderr),
|
||||
})
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "peach-network"
|
||||
version = "0.2.12"
|
||||
version = "0.2.13"
|
||||
authors = ["Andrew Reid <gnomad@cryptolab.net>"]
|
||||
edition = "2018"
|
||||
description = "Query and configure network interfaces using JSON-RPC over HTTP."
|
||||
@ -28,7 +28,6 @@ maintenance = { status = "actively-developed" }
|
||||
|
||||
[dependencies]
|
||||
env_logger = "0.6"
|
||||
failure = "0.1"
|
||||
get_if_addrs = "0.5.3"
|
||||
jsonrpc-core = "11"
|
||||
jsonrpc-http-server = "11"
|
||||
@ -36,9 +35,9 @@ log = "0.4"
|
||||
probes = "0.4"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
snafu = "0.6"
|
||||
regex = "1"
|
||||
wpactrl = "0.3.1"
|
||||
# replace this with crate import once latest changes have been published
|
||||
wpactrl = { git = "https://github.com/sauyon/wpa-ctrl-rs.git", branch = "master" }
|
||||
|
||||
[dev-dependencies]
|
||||
jsonrpc-test = "11"
|
||||
|
@ -1,6 +1,6 @@
|
||||
# peach-network
|
||||
|
||||
[](https://travis-ci.com/peachcloud/peach-network) 
|
||||
[](https://travis-ci.com/peachcloud/peach-network) 
|
||||
|
||||
Networking microservice module for PeachCloud. Query and configure device interfaces using [JSON-RPC](https://www.jsonrpc.org/specification) over http.
|
||||
|
||||
|
@ -1,156 +1,302 @@
|
||||
use std::{error, io, str};
|
||||
use std::io;
|
||||
use std::num::ParseIntError;
|
||||
|
||||
use jsonrpc_core::{types::error::Error, ErrorCode};
|
||||
use io::Error as IoError;
|
||||
use jsonrpc_core::{types::error::Error as JsonRpcError, ErrorCode};
|
||||
use probes::ProbeError;
|
||||
use serde_json::error::Error as SerdeError;
|
||||
use snafu::Snafu;
|
||||
use regex::Error as RegexError;
|
||||
use serde_json::Error as SerdeError;
|
||||
use wpactrl::WpaError;
|
||||
|
||||
pub type BoxError = Box<dyn error::Error>;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
#[snafu(visibility(pub(crate)))]
|
||||
#[derive(Debug)]
|
||||
pub enum NetworkError {
|
||||
#[snafu(display("{}", err_msg))]
|
||||
ActivateAp { err_msg: String },
|
||||
|
||||
#[snafu(display("{}", err_msg))]
|
||||
ActivateClient { err_msg: String },
|
||||
|
||||
#[snafu(display("Failed to add network for {}", ssid))]
|
||||
Add { ssid: String },
|
||||
|
||||
#[snafu(display("Failed to retrieve state for interface: {}", iface))]
|
||||
NoState { iface: String, source: io::Error },
|
||||
NoState { iface: String, source: IoError },
|
||||
|
||||
#[snafu(display("Failed to disable network {} for interface: {}", id, iface))]
|
||||
Disable { id: String, iface: String },
|
||||
|
||||
#[snafu(display("Failed to disconnect {}", iface))]
|
||||
Disconnect { iface: String },
|
||||
|
||||
#[snafu(display("Failed to generate wpa passphrase for {}: {}", ssid, source))]
|
||||
GenWpaPassphrase { ssid: String, source: io::Error },
|
||||
GenWpaPassphrase { ssid: String, source: IoError },
|
||||
|
||||
#[snafu(display("Failed to generate wpa passphrase for {}: {}", ssid, err_msg))]
|
||||
GenWpaPassphraseWarning { ssid: String, err_msg: String },
|
||||
|
||||
#[snafu(display("No ID found for {} on interface: {}", ssid, iface))]
|
||||
Id { ssid: String, iface: String },
|
||||
|
||||
#[snafu(display("Could not access IP address for interface: {}", iface))]
|
||||
NoIp { iface: String, source: io::Error },
|
||||
NoIp { iface: String, source: IoError },
|
||||
|
||||
#[snafu(display("Could not find RSSI for interface: {}", iface))]
|
||||
Rssi { iface: String },
|
||||
|
||||
#[snafu(display("Could not find signal quality (%) for interface: {}", iface))]
|
||||
RssiPercent { iface: String },
|
||||
|
||||
#[snafu(display("Could not find SSID for interface: {}", iface))]
|
||||
Ssid { iface: String },
|
||||
|
||||
#[snafu(display("No state found for interface: {}", iface))]
|
||||
State { iface: String },
|
||||
|
||||
#[snafu(display("No status found for interface: {}", iface))]
|
||||
Status { iface: String },
|
||||
|
||||
#[snafu(display("Could not find network traffic for interface: {}", iface))]
|
||||
Traffic { iface: String },
|
||||
|
||||
#[snafu(display("No saved networks found for default interface"))]
|
||||
SavedNetworks,
|
||||
|
||||
#[snafu(display("No networks found in range of interface: {}", iface))]
|
||||
AvailableNetworks { iface: String },
|
||||
|
||||
#[snafu(display("Missing expected parameters: {}", e))]
|
||||
MissingParams { e: Error },
|
||||
MissingParams(JsonRpcError),
|
||||
|
||||
#[snafu(display("Failed to set new password for network {} on {}", id, iface))]
|
||||
Modify { id: String, iface: String },
|
||||
|
||||
#[snafu(display("No IP found for interface: {}", iface))]
|
||||
Ip { iface: String },
|
||||
|
||||
#[snafu(display("Failed to parse integer from string for RSSI value: {}", source))]
|
||||
ParseString { source: std::num::ParseIntError },
|
||||
ParseInt(ParseIntError),
|
||||
|
||||
#[snafu(display(
|
||||
"Failed to retrieve network traffic measurement for {}: {}",
|
||||
iface,
|
||||
source
|
||||
))]
|
||||
NoTraffic { iface: String, source: ProbeError },
|
||||
|
||||
#[snafu(display("Failed to reassociate with WiFi network for interface: {}", iface))]
|
||||
Reassociate { iface: String },
|
||||
|
||||
#[snafu(display("Failed to force reread of wpa_supplicant configuration file"))]
|
||||
Reconfigure,
|
||||
|
||||
#[snafu(display("Failed to reconnect with WiFi network for interface: {}", iface))]
|
||||
Reconnect { iface: String },
|
||||
|
||||
#[snafu(display("Regex command failed"))]
|
||||
Regex { source: regex::Error },
|
||||
Regex(RegexError),
|
||||
|
||||
#[snafu(display("Failed to delete network {} for interface: {}", id, iface))]
|
||||
Delete { id: String, iface: String },
|
||||
|
||||
#[snafu(display("Failed to retrieve state of wlan0 service: {}", source))]
|
||||
WlanState { source: io::Error },
|
||||
WlanState(IoError),
|
||||
|
||||
#[snafu(display("Failed to retrieve connection state of wlan0 interface: {}", source))]
|
||||
WlanOperstate { source: io::Error },
|
||||
WlanOperstate(IoError),
|
||||
|
||||
#[snafu(display("Failed to save configuration changes to file"))]
|
||||
Save,
|
||||
|
||||
#[snafu(display("Failed to connect to network {} for interface: {}", id, iface))]
|
||||
Connect { id: String, iface: String },
|
||||
|
||||
#[snafu(display("Failed to start ap0 service: {}", source))]
|
||||
StartAp0 { source: io::Error },
|
||||
StartAp0(IoError),
|
||||
|
||||
#[snafu(display("Failed to start wlan0 service: {}", source))]
|
||||
StartWlan0 { source: io::Error },
|
||||
StartWlan0(IoError),
|
||||
|
||||
#[snafu(display("JSON serialization failed: {}", source))]
|
||||
SerdeSerialize { source: SerdeError },
|
||||
SerdeSerialize(SerdeError),
|
||||
|
||||
#[snafu(display("Failed to open control interface for wpasupplicant"))]
|
||||
WpaCtrlOpen {
|
||||
#[snafu(source(from(failure::Error, std::convert::Into::into)))]
|
||||
source: BoxError,
|
||||
},
|
||||
|
||||
#[snafu(display("Request to wpasupplicant via wpactrl failed"))]
|
||||
WpaCtrlRequest {
|
||||
#[snafu(source(from(failure::Error, std::convert::Into::into)))]
|
||||
source: BoxError,
|
||||
},
|
||||
WpaCtrl(WpaError),
|
||||
}
|
||||
|
||||
impl From<NetworkError> for Error {
|
||||
impl std::error::Error for NetworkError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match *self {
|
||||
NetworkError::Add { .. } => None,
|
||||
NetworkError::NoState { ref source, .. } => Some(source),
|
||||
NetworkError::Disable { .. } => None,
|
||||
NetworkError::Disconnect { .. } => None,
|
||||
NetworkError::GenWpaPassphrase { ref source, .. } => Some(source),
|
||||
NetworkError::GenWpaPassphraseWarning { .. } => None,
|
||||
NetworkError::Id { .. } => None,
|
||||
NetworkError::NoIp { ref source, .. } => Some(source),
|
||||
NetworkError::Rssi { .. } => None,
|
||||
NetworkError::RssiPercent { .. } => None,
|
||||
NetworkError::Ssid { .. } => None,
|
||||
NetworkError::State { .. } => None,
|
||||
NetworkError::Status { .. } => None,
|
||||
NetworkError::Traffic { .. } => None,
|
||||
NetworkError::SavedNetworks => None,
|
||||
NetworkError::AvailableNetworks { .. } => None,
|
||||
NetworkError::MissingParams(ref source) => Some(source),
|
||||
NetworkError::Modify { .. } => None,
|
||||
NetworkError::Ip { .. } => None,
|
||||
NetworkError::ParseInt(ref source) => Some(source),
|
||||
NetworkError::NoTraffic { ref source, .. } => Some(source),
|
||||
NetworkError::Reassociate { .. } => None,
|
||||
NetworkError::Reconfigure { .. } => None,
|
||||
NetworkError::Reconnect { .. } => None,
|
||||
NetworkError::Regex(ref source) => Some(source),
|
||||
NetworkError::Delete { .. } => None,
|
||||
NetworkError::WlanState(ref source) => Some(source),
|
||||
NetworkError::WlanOperstate(ref source) => Some(source),
|
||||
NetworkError::Save => None,
|
||||
NetworkError::Connect { .. } => None,
|
||||
NetworkError::StartWlan0(ref source) => Some(source),
|
||||
NetworkError::StartAp0(ref source) => Some(source),
|
||||
NetworkError::SerdeSerialize(ref source) => Some(source),
|
||||
NetworkError::WpaCtrl(ref source) => Some(source),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for NetworkError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match *self {
|
||||
NetworkError::Add { ref ssid } => {
|
||||
write!(f, "Failed to add network for {}", ssid)
|
||||
}
|
||||
NetworkError::NoState { ref iface, .. } => {
|
||||
write!(f, "Failed to retrieve state for interface: {}", iface)
|
||||
}
|
||||
NetworkError::Disable { ref id, ref iface } => {
|
||||
write!(
|
||||
f,
|
||||
"Failed to disable network {} for interface: {}",
|
||||
id, iface
|
||||
)
|
||||
}
|
||||
NetworkError::Disconnect { ref iface } => {
|
||||
write!(f, "Failed to disconnect {}", iface)
|
||||
}
|
||||
NetworkError::GenWpaPassphrase { ref ssid, .. } => {
|
||||
write!(f, "Failed to generate wpa passphrase for {}", ssid)
|
||||
}
|
||||
NetworkError::GenWpaPassphraseWarning {
|
||||
ref ssid,
|
||||
ref err_msg,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"Failed to generate wpa passphrase for {}: {}",
|
||||
ssid, err_msg
|
||||
)
|
||||
}
|
||||
NetworkError::Id {
|
||||
ref ssid,
|
||||
ref iface,
|
||||
} => {
|
||||
write!(f, "No ID found for {} on interface: {}", ssid, iface)
|
||||
}
|
||||
NetworkError::NoIp { ref iface, .. } => {
|
||||
write!(f, "Could not access IP address for interface: {}", iface)
|
||||
}
|
||||
NetworkError::Rssi { ref iface } => {
|
||||
write!(f, "Could not find RSSI for interface: {}", iface)
|
||||
}
|
||||
NetworkError::RssiPercent { ref iface } => {
|
||||
write!(
|
||||
f,
|
||||
"Could not find signal quality (%) for interface: {}",
|
||||
iface
|
||||
)
|
||||
}
|
||||
NetworkError::Ssid { ref iface } => {
|
||||
write!(f, "Could not find SSID for interface: {}", iface)
|
||||
}
|
||||
NetworkError::State { ref iface } => {
|
||||
write!(f, "No state found for interface: {}", iface)
|
||||
}
|
||||
NetworkError::Status { ref iface } => {
|
||||
write!(f, "No status found for interface: {}", iface)
|
||||
}
|
||||
NetworkError::Traffic { ref iface } => {
|
||||
write!(
|
||||
f,
|
||||
"Could not find network traffice for interface: {}",
|
||||
iface
|
||||
)
|
||||
}
|
||||
NetworkError::SavedNetworks => {
|
||||
write!(f, "No saved networks found for default interface")
|
||||
}
|
||||
NetworkError::AvailableNetworks { ref iface } => {
|
||||
write!(f, "No networks found in range of interface: {}", iface)
|
||||
}
|
||||
NetworkError::MissingParams(ref source) => {
|
||||
write!(f, "Missing expected parameters: {}", source)
|
||||
}
|
||||
NetworkError::Modify { ref id, ref iface } => {
|
||||
write!(
|
||||
f,
|
||||
"Failed to set new password for network {} on {}",
|
||||
id, iface
|
||||
)
|
||||
}
|
||||
NetworkError::Ip { ref iface } => {
|
||||
write!(f, "No IP found for interface: {}", iface)
|
||||
}
|
||||
NetworkError::ParseInt(_) => {
|
||||
write!(f, "Failed to parse integer from string for RSSI value")
|
||||
}
|
||||
NetworkError::NoTraffic { ref iface, .. } => {
|
||||
write!(
|
||||
f,
|
||||
"Failed to retrieve network traffic measurement for {}",
|
||||
iface
|
||||
)
|
||||
}
|
||||
NetworkError::Reassociate { ref iface } => {
|
||||
write!(
|
||||
f,
|
||||
"Failed to reassociate with WiFi network for interface: {}",
|
||||
iface
|
||||
)
|
||||
}
|
||||
NetworkError::Reconfigure => {
|
||||
write!(
|
||||
f,
|
||||
"Failed to force reread of wpa_supplicant configuration file"
|
||||
)
|
||||
}
|
||||
NetworkError::Reconnect { ref iface } => {
|
||||
write!(
|
||||
f,
|
||||
"Failed to reconnect with WiFi network for interface: {}",
|
||||
iface
|
||||
)
|
||||
}
|
||||
NetworkError::Regex(_) => write!(f, "Regex command failed"),
|
||||
NetworkError::Delete { ref id, ref iface } => {
|
||||
write!(
|
||||
f,
|
||||
"Failed to delete network {} for interface: {}",
|
||||
id, iface
|
||||
)
|
||||
}
|
||||
NetworkError::WlanState(_) => write!(f, "Failed to retrieve state of wlan0 service"),
|
||||
NetworkError::WlanOperstate(_) => {
|
||||
write!(f, "Failed to retrieve connection state of wlan0 interface")
|
||||
}
|
||||
NetworkError::Save => write!(f, "Failed to save configuration changes to file"),
|
||||
NetworkError::Connect { ref id, ref iface } => {
|
||||
write!(
|
||||
f,
|
||||
"Failed to connect to network {} for interface: {}",
|
||||
id, iface
|
||||
)
|
||||
}
|
||||
NetworkError::StartWlan0(_) => write!(f, "Failed to start ap0 service"),
|
||||
NetworkError::StartAp0(_) => write!(f, "Failed to start wlan0 service"),
|
||||
NetworkError::SerdeSerialize(_) => write!(f, "JSON serialization failed"),
|
||||
NetworkError::WpaCtrl(_) => write!(f, "WpaCtrl command failed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SerdeError> for NetworkError {
|
||||
fn from(err: SerdeError) -> Self {
|
||||
NetworkError::SerdeSerialize(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WpaError> for NetworkError {
|
||||
fn from(err: WpaError) -> Self {
|
||||
NetworkError::WpaCtrl(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for NetworkError {
|
||||
fn from(err: ParseIntError) -> Self {
|
||||
NetworkError::ParseInt(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RegexError> for NetworkError {
|
||||
fn from(err: RegexError) -> Self {
|
||||
NetworkError::Regex(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NetworkError> for JsonRpcError {
|
||||
fn from(err: NetworkError) -> Self {
|
||||
match &err {
|
||||
NetworkError::ActivateAp { err_msg } => Error {
|
||||
code: ErrorCode::ServerError(-32015),
|
||||
message: err_msg.to_string(),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::ActivateClient { err_msg } => Error {
|
||||
code: ErrorCode::ServerError(-32017),
|
||||
message: err_msg.to_string(),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::Add { ssid } => Error {
|
||||
NetworkError::Add { ssid } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32000),
|
||||
message: format!("Failed to add network for {}", ssid),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::NoState { iface, source } => Error {
|
||||
NetworkError::NoState { iface, source } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32022),
|
||||
message: format!(
|
||||
"Failed to retrieve interface state for {}: {}",
|
||||
@ -158,22 +304,22 @@ impl From<NetworkError> for Error {
|
||||
),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::Disable { id, iface } => Error {
|
||||
NetworkError::Disable { id, iface } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32029),
|
||||
message: format!("Failed to disable network {} for {}", id, iface),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::Disconnect { iface } => Error {
|
||||
NetworkError::Disconnect { iface } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32032),
|
||||
message: format!("Failed to disconnect {}", iface),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::GenWpaPassphrase { ssid, source } => Error {
|
||||
NetworkError::GenWpaPassphrase { ssid, source } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32025),
|
||||
message: format!("Failed to generate wpa passphrase for {}: {}", ssid, source),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::GenWpaPassphraseWarning { ssid, err_msg } => Error {
|
||||
NetworkError::GenWpaPassphraseWarning { ssid, err_msg } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32036),
|
||||
message: format!(
|
||||
"Failed to generate wpa passphrase for {}: {}",
|
||||
@ -181,17 +327,17 @@ impl From<NetworkError> for Error {
|
||||
),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::Id { iface, ssid } => Error {
|
||||
NetworkError::Id { iface, ssid } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32026),
|
||||
message: format!("No ID found for {} on interface {}", ssid, iface),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::NoIp { iface, source } => Error {
|
||||
NetworkError::NoIp { iface, source } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32001),
|
||||
message: format!("Failed to retrieve IP address for {}: {}", iface, source),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::Rssi { iface } => Error {
|
||||
NetworkError::Rssi { iface } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32002),
|
||||
message: format!(
|
||||
"Failed to retrieve RSSI for {}. Interface may not be connected",
|
||||
@ -199,7 +345,7 @@ impl From<NetworkError> for Error {
|
||||
),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::RssiPercent { iface } => Error {
|
||||
NetworkError::RssiPercent { iface } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32034),
|
||||
message: format!(
|
||||
"Failed to retrieve signal quality (%) for {}. Interface may not be connected",
|
||||
@ -207,7 +353,7 @@ impl From<NetworkError> for Error {
|
||||
),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::Ssid { iface } => Error {
|
||||
NetworkError::Ssid { iface } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32003),
|
||||
message: format!(
|
||||
"Failed to retrieve SSID for {}. Interface may not be connected",
|
||||
@ -215,17 +361,17 @@ impl From<NetworkError> for Error {
|
||||
),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::State { iface } => Error {
|
||||
NetworkError::State { iface } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32023),
|
||||
message: format!("No state found for {}. Interface may not exist", iface),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::Status { iface } => Error {
|
||||
NetworkError::Status { iface } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32024),
|
||||
message: format!("No status found for {}. Interface may not exist", iface),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::Traffic { iface } => Error {
|
||||
NetworkError::Traffic { iface } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32004),
|
||||
message: format!(
|
||||
"No network traffic statistics found for {}. Interface may not exist",
|
||||
@ -233,28 +379,28 @@ impl From<NetworkError> for Error {
|
||||
),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::SavedNetworks => Error {
|
||||
NetworkError::SavedNetworks => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32005),
|
||||
message: "No saved networks found".to_string(),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::AvailableNetworks { iface } => Error {
|
||||
NetworkError::AvailableNetworks { iface } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32006),
|
||||
message: format!("No networks found in range of {}", iface),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::MissingParams { e } => e.clone(),
|
||||
NetworkError::Modify { id, iface } => Error {
|
||||
NetworkError::MissingParams(e) => e.clone(),
|
||||
NetworkError::Modify { id, iface } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32033),
|
||||
message: format!("Failed to set new password for network {} on {}", id, iface),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::Ip { iface } => Error {
|
||||
NetworkError::Ip { iface } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32007),
|
||||
message: format!("No IP address found for {}", iface),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::ParseString { source } => Error {
|
||||
NetworkError::ParseInt(source) => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32035),
|
||||
message: format!(
|
||||
"Failed to parse integer from string for RSSI value: {}",
|
||||
@ -262,7 +408,7 @@ impl From<NetworkError> for Error {
|
||||
),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::NoTraffic { iface, source } => Error {
|
||||
NetworkError::NoTraffic { iface, source } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32015),
|
||||
message: format!(
|
||||
"Failed to retrieve network traffic statistics for {}: {}",
|
||||
@ -270,37 +416,37 @@ impl From<NetworkError> for Error {
|
||||
),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::Reassociate { iface } => Error {
|
||||
NetworkError::Reassociate { iface } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32008),
|
||||
message: format!("Failed to reassociate with WiFi network for {}", iface),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::Reconfigure => Error {
|
||||
NetworkError::Reconfigure => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32030),
|
||||
message: "Failed to force reread of wpa_supplicant configuration file".to_string(),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::Reconnect { iface } => Error {
|
||||
NetworkError::Reconnect { iface } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32009),
|
||||
message: format!("Failed to reconnect with WiFi network for {}", iface),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::Regex { source } => Error {
|
||||
NetworkError::Regex(source) => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32010),
|
||||
message: format!("Regex command error: {}", source),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::Delete { id, iface } => Error {
|
||||
NetworkError::Delete { id, iface } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32028),
|
||||
message: format!("Failed to delete network {} for {}", id, iface),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::WlanState { source } => Error {
|
||||
NetworkError::WlanState(source) => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32011),
|
||||
message: format!("Failed to retrieve state of wlan0 service: {}", source),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::WlanOperstate { source } => Error {
|
||||
NetworkError::WlanOperstate(source) => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32021),
|
||||
message: format!(
|
||||
"Failed to retrieve connection state of wlan0 interface: {}",
|
||||
@ -308,42 +454,34 @@ impl From<NetworkError> for Error {
|
||||
),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::Save => Error {
|
||||
NetworkError::Save => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32031),
|
||||
message: "Failed to save configuration changes to file".to_string(),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::Connect { id, iface } => Error {
|
||||
NetworkError::Connect { id, iface } => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32027),
|
||||
message: format!("Failed to connect to network {} for {}", id, iface),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::StartAp0 { source } => Error {
|
||||
NetworkError::StartAp0(source) => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32016),
|
||||
message: format!("Failed to start ap0 service: {}", source),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::StartWlan0 { source } => Error {
|
||||
NetworkError::StartWlan0(source) => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32018),
|
||||
message: format!("Failed to start wlan0 service: {}", source),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::SerdeSerialize { source } => Error {
|
||||
NetworkError::SerdeSerialize(source) => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32012),
|
||||
message: format!("JSON serialization failed: {}", source),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::WpaCtrlOpen { source } => Error {
|
||||
NetworkError::WpaCtrl(source) => JsonRpcError {
|
||||
code: ErrorCode::ServerError(-32013),
|
||||
message: format!(
|
||||
"Failed to open control interface for wpasupplicant: {}",
|
||||
source
|
||||
),
|
||||
data: None,
|
||||
},
|
||||
NetworkError::WpaCtrlRequest { source } => Error {
|
||||
code: ErrorCode::ServerError(-32014),
|
||||
message: format!("WPA supplicant request failed: {}", source),
|
||||
message: format!("WPA control interface failure: {}", source),
|
||||
data: None,
|
||||
},
|
||||
}
|
||||
|
@ -25,11 +25,11 @@ use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBui
|
||||
use log::info;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::error::{BoxError, NetworkError};
|
||||
use crate::error::NetworkError;
|
||||
use crate::network::{Iface, IfaceId, IfaceIdPass, IfaceSsid, WiFi};
|
||||
|
||||
/// Create JSON-RPC I/O handler, add RPC methods and launch HTTP server.
|
||||
pub fn run() -> Result<(), BoxError> {
|
||||
pub fn run() -> Result<(), NetworkError> {
|
||||
info!("Starting up.");
|
||||
|
||||
info!("Creating JSON-RPC I/O handler.");
|
||||
@ -47,7 +47,7 @@ pub fn run() -> Result<(), BoxError> {
|
||||
None => Err(Error::from(NetworkError::AvailableNetworks { iface })),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams { e })),
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams(e))),
|
||||
}
|
||||
});
|
||||
|
||||
@ -62,7 +62,7 @@ pub fn run() -> Result<(), BoxError> {
|
||||
None => Err(Error::from(NetworkError::Id { iface, ssid })),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams { e })),
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams(e))),
|
||||
}
|
||||
});
|
||||
|
||||
@ -76,7 +76,7 @@ pub fn run() -> Result<(), BoxError> {
|
||||
None => Err(Error::from(NetworkError::Ip { iface })),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams { e })),
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams(e))),
|
||||
}
|
||||
});
|
||||
|
||||
@ -92,7 +92,7 @@ pub fn run() -> Result<(), BoxError> {
|
||||
None => Err(Error::from(NetworkError::Rssi { iface })),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams { e })),
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams(e))),
|
||||
}
|
||||
});
|
||||
|
||||
@ -106,7 +106,7 @@ pub fn run() -> Result<(), BoxError> {
|
||||
None => Err(Error::from(NetworkError::RssiPercent { iface })),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams { e })),
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams(e))),
|
||||
}
|
||||
});
|
||||
|
||||
@ -128,7 +128,7 @@ pub fn run() -> Result<(), BoxError> {
|
||||
None => Err(Error::from(NetworkError::Ssid { iface })),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams { e })),
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams(e))),
|
||||
}
|
||||
});
|
||||
|
||||
@ -142,7 +142,7 @@ pub fn run() -> Result<(), BoxError> {
|
||||
None => Err(Error::from(NetworkError::State { iface })),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams { e })),
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams(e))),
|
||||
}
|
||||
});
|
||||
|
||||
@ -159,7 +159,7 @@ pub fn run() -> Result<(), BoxError> {
|
||||
None => Err(Error::from(NetworkError::Status { iface })),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams { e })),
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams(e))),
|
||||
}
|
||||
});
|
||||
|
||||
@ -173,7 +173,7 @@ pub fn run() -> Result<(), BoxError> {
|
||||
None => Err(Error::from(NetworkError::Traffic { iface })),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams { e })),
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams(e))),
|
||||
}
|
||||
});
|
||||
|
||||
@ -198,7 +198,7 @@ pub fn run() -> Result<(), BoxError> {
|
||||
Ok(_) => Ok(Value::String("success".to_string())),
|
||||
Err(e) => Err(Error::from(e)),
|
||||
},
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams { e })),
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams(e))),
|
||||
}
|
||||
});
|
||||
|
||||
@ -219,7 +219,7 @@ pub fn run() -> Result<(), BoxError> {
|
||||
Err(_) => Err(Error::from(NetworkError::Delete { id, iface })),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams { e })),
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams(e))),
|
||||
}
|
||||
});
|
||||
|
||||
@ -234,7 +234,7 @@ pub fn run() -> Result<(), BoxError> {
|
||||
Err(_) => Err(Error::from(NetworkError::Disable { id, iface })),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams { e })),
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams(e))),
|
||||
}
|
||||
});
|
||||
|
||||
@ -248,7 +248,7 @@ pub fn run() -> Result<(), BoxError> {
|
||||
Err(_) => Err(Error::from(NetworkError::Disconnect { iface })),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams { e })),
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams(e))),
|
||||
}
|
||||
});
|
||||
|
||||
@ -264,7 +264,7 @@ pub fn run() -> Result<(), BoxError> {
|
||||
Err(_) => Err(Error::from(NetworkError::Modify { iface, id })),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams { e })),
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams(e))),
|
||||
}
|
||||
});
|
||||
|
||||
@ -278,7 +278,7 @@ pub fn run() -> Result<(), BoxError> {
|
||||
Err(_) => Err(Error::from(NetworkError::Reassociate { iface })),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams { e })),
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams(e))),
|
||||
}
|
||||
});
|
||||
|
||||
@ -297,7 +297,7 @@ pub fn run() -> Result<(), BoxError> {
|
||||
Err(_) => Err(Error::from(NetworkError::Reconnect { iface })),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams { e })),
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams(e))),
|
||||
}
|
||||
});
|
||||
|
||||
@ -317,7 +317,7 @@ pub fn run() -> Result<(), BoxError> {
|
||||
Err(_) => Err(Error::from(NetworkError::Connect { id, iface })),
|
||||
}
|
||||
}
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams { e })),
|
||||
Err(e) => Err(Error::from(NetworkError::MissingParams(e))),
|
||||
}
|
||||
});
|
||||
|
||||
@ -374,7 +374,7 @@ mod tests {
|
||||
message: String::from("Parse error"),
|
||||
data: None,
|
||||
};
|
||||
Err(Error::from(NetworkError::MissingParams { e }))
|
||||
Err(Error::from(NetworkError::MissingParams(e)))
|
||||
});
|
||||
test_rpc::Rpc::from(io)
|
||||
};
|
||||
@ -689,7 +689,7 @@ mod tests {
|
||||
),
|
||||
data: None,
|
||||
};
|
||||
Err(Error::from(NetworkError::MissingParams { e }))
|
||||
Err(Error::from(NetworkError::MissingParams(e)))
|
||||
});
|
||||
test_rpc::Rpc::from(io)
|
||||
};
|
||||
@ -842,7 +842,7 @@ mod tests {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_method("rpc_regex_error", |_| {
|
||||
let source = regex::Error::Syntax("oh no!".to_string());
|
||||
Err(Error::from(NetworkError::Regex { source }))
|
||||
Err(Error::from(NetworkError::Regex(source)))
|
||||
});
|
||||
test_rpc::Rpc::from(io)
|
||||
};
|
||||
@ -886,7 +886,7 @@ mod tests {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_method("rpc_wlanstate_error", |_| {
|
||||
let source = IoError::new(ErrorKind::PermissionDenied, "oh no!");
|
||||
Err(Error::from(NetworkError::WlanState { source }))
|
||||
Err(Error::from(NetworkError::WlanState(source)))
|
||||
});
|
||||
test_rpc::Rpc::from(io)
|
||||
};
|
||||
@ -907,7 +907,7 @@ mod tests {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_method("rpc_wlanoperstate_error", |_| {
|
||||
let source = IoError::new(ErrorKind::PermissionDenied, "oh no!");
|
||||
Err(Error::from(NetworkError::WlanOperstate { source }))
|
||||
Err(Error::from(NetworkError::WlanOperstate(source)))
|
||||
});
|
||||
test_rpc::Rpc::from(io)
|
||||
};
|
||||
@ -945,9 +945,13 @@ mod tests {
|
||||
let rpc = {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_method("rpc_wpactrlopen_error", |_| {
|
||||
let fail_err = failure::err_msg("Permission denied (os error 13)").compat();
|
||||
let source = Box::new(fail_err);
|
||||
Err(Error::from(NetworkError::WpaCtrlOpen { source }))
|
||||
let permission_error = IoError::new(
|
||||
ErrorKind::PermissionDenied,
|
||||
"Permission denied (os error 13)",
|
||||
);
|
||||
Err(Error::from(NetworkError::WpaCtrl(wpactrl::WpaError::Io(
|
||||
permission_error,
|
||||
))))
|
||||
});
|
||||
test_rpc::Rpc::from(io)
|
||||
};
|
||||
@ -956,7 +960,7 @@ mod tests {
|
||||
rpc.request("rpc_wpactrlopen_error", &()),
|
||||
r#"{
|
||||
"code": -32013,
|
||||
"message": "Failed to open control interface for wpasupplicant: Permission denied (os error 13)"
|
||||
"message": "WPA control interface failure: Failed to execute the specified command: Permission denied (os error 13)"
|
||||
}"#
|
||||
);
|
||||
}
|
||||
@ -967,9 +971,10 @@ mod tests {
|
||||
let rpc = {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_method("rpc_wpactrlrequest_error", |_| {
|
||||
let fail_err = failure::err_msg("oh no!").compat();
|
||||
let source = Box::new(fail_err);
|
||||
Err(Error::from(NetworkError::WpaCtrlRequest { source }))
|
||||
let conn_refused_error = IoError::new(ErrorKind::ConnectionRefused, "oh no!");
|
||||
Err(Error::from(NetworkError::WpaCtrl(wpactrl::WpaError::Io(
|
||||
conn_refused_error,
|
||||
))))
|
||||
});
|
||||
test_rpc::Rpc::from(io)
|
||||
};
|
||||
@ -977,8 +982,8 @@ mod tests {
|
||||
assert_eq!(
|
||||
rpc.request("rpc_wpactrlrequest_error", &()),
|
||||
r#"{
|
||||
"code": -32014,
|
||||
"message": "WPA supplicant request failed: oh no!"
|
||||
"code": -32013,
|
||||
"message": "WPA control interface failure: Failed to execute the specified command: oh no!"
|
||||
}"#
|
||||
);
|
||||
}
|
||||
|
@ -21,13 +21,9 @@ use std::{
|
||||
str,
|
||||
};
|
||||
|
||||
use crate::error::{
|
||||
GenWpaPassphrase, NetworkError, NoIp, NoState, NoTraffic, ParseString, SerdeSerialize,
|
||||
StartAp0, StartWlan0, WlanState, WpaCtrlOpen, WpaCtrlRequest,
|
||||
};
|
||||
use crate::error::NetworkError;
|
||||
use probes::network;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::ResultExt;
|
||||
|
||||
use crate::utils;
|
||||
|
||||
@ -122,6 +118,11 @@ pub struct WiFi {
|
||||
pub pass: String,
|
||||
}
|
||||
|
||||
// TODO: wrap this into a helper function:
|
||||
//
|
||||
// let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||
// let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
||||
|
||||
/* GET - Methods for retrieving data */
|
||||
|
||||
/// Retrieve list of available wireless access points for a given network
|
||||
@ -142,12 +143,9 @@ pub struct WiFi {
|
||||
///
|
||||
pub fn available_networks(iface: &str) -> Result<Option<String>, NetworkError> {
|
||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||
let mut wpa = wpactrl::WpaCtrl::new()
|
||||
.ctrl_path(wpa_path)
|
||||
.open()
|
||||
.context(WpaCtrlOpen)?;
|
||||
wpa.request("SCAN").context(WpaCtrlRequest)?;
|
||||
let networks = wpa.request("SCAN_RESULTS").context(WpaCtrlRequest)?;
|
||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
||||
wpa.request("SCAN")?;
|
||||
let networks = wpa.request("SCAN_RESULTS")?;
|
||||
let mut scan = Vec::new();
|
||||
for network in networks.lines() {
|
||||
let v: Vec<&str> = network.split('\t').collect();
|
||||
@ -178,7 +176,7 @@ pub fn available_networks(iface: &str) -> Result<Option<String>, NetworkError> {
|
||||
if scan.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
let results = serde_json::to_string(&scan).context(SerdeSerialize)?;
|
||||
let results = serde_json::to_string(&scan)?;
|
||||
Ok(Some(results))
|
||||
}
|
||||
}
|
||||
@ -201,11 +199,8 @@ pub fn available_networks(iface: &str) -> Result<Option<String>, NetworkError> {
|
||||
///
|
||||
pub fn id(iface: &str, ssid: &str) -> Result<Option<String>, NetworkError> {
|
||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||
let mut wpa = wpactrl::WpaCtrl::new()
|
||||
.ctrl_path(wpa_path)
|
||||
.open()
|
||||
.context(WpaCtrlOpen)?;
|
||||
let networks = wpa.request("LIST_NETWORKS").context(WpaCtrlRequest)?;
|
||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
||||
let networks = wpa.request("LIST_NETWORKS")?;
|
||||
let mut id = Vec::new();
|
||||
for network in networks.lines() {
|
||||
let v: Vec<&str> = network.split('\t').collect();
|
||||
@ -239,7 +234,10 @@ pub fn id(iface: &str, ssid: &str) -> Result<Option<String>, NetworkError> {
|
||||
///
|
||||
pub fn ip(iface: &str) -> Result<Option<String>, NetworkError> {
|
||||
let net_if: String = iface.to_string();
|
||||
let ifaces = get_if_addrs::get_if_addrs().context(NoIp { iface: net_if })?;
|
||||
let ifaces = get_if_addrs::get_if_addrs().map_err(|source| NetworkError::NoIp {
|
||||
iface: net_if,
|
||||
source,
|
||||
})?;
|
||||
let ip = ifaces
|
||||
.iter()
|
||||
.find(|&i| i.name == iface)
|
||||
@ -265,11 +263,8 @@ pub fn ip(iface: &str) -> Result<Option<String>, NetworkError> {
|
||||
///
|
||||
pub fn rssi(iface: &str) -> Result<Option<String>, NetworkError> {
|
||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||
let mut wpa = wpactrl::WpaCtrl::new()
|
||||
.ctrl_path(wpa_path)
|
||||
.open()
|
||||
.context(WpaCtrlOpen)?;
|
||||
let status = wpa.request("SIGNAL_POLL").context(WpaCtrlRequest)?;
|
||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
||||
let status = wpa.request("SIGNAL_POLL")?;
|
||||
let rssi = utils::regex_finder(r"RSSI=(.*)\n", &status)?;
|
||||
|
||||
if rssi.is_none() {
|
||||
@ -297,17 +292,14 @@ pub fn rssi(iface: &str) -> Result<Option<String>, NetworkError> {
|
||||
///
|
||||
pub fn rssi_percent(iface: &str) -> Result<Option<String>, NetworkError> {
|
||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||
let mut wpa = wpactrl::WpaCtrl::new()
|
||||
.ctrl_path(wpa_path)
|
||||
.open()
|
||||
.context(WpaCtrlOpen)?;
|
||||
let status = wpa.request("SIGNAL_POLL").context(WpaCtrlRequest)?;
|
||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
||||
let status = wpa.request("SIGNAL_POLL")?;
|
||||
let rssi = utils::regex_finder(r"RSSI=(.*)\n", &status)?;
|
||||
|
||||
match rssi {
|
||||
Some(rssi) => {
|
||||
// parse the string to a signed integer (for math)
|
||||
let rssi_parsed = rssi.parse::<i32>().context(ParseString)?;
|
||||
let rssi_parsed = rssi.parse::<i32>()?;
|
||||
// perform rssi (dBm) to quality (%) conversion
|
||||
let quality_percent = 2 * (rssi_parsed + 100);
|
||||
// convert signal quality integer to string
|
||||
@ -335,8 +327,8 @@ pub fn rssi_percent(iface: &str) -> Result<Option<String>, NetworkError> {
|
||||
/// sent to the caller.
|
||||
///
|
||||
pub fn saved_networks() -> Result<Option<String>, NetworkError> {
|
||||
let mut wpa = wpactrl::WpaCtrl::new().open().context(WpaCtrlOpen)?;
|
||||
let networks = wpa.request("LIST_NETWORKS").context(WpaCtrlRequest)?;
|
||||
let mut wpa = wpactrl::WpaCtrl::builder().open()?;
|
||||
let networks = wpa.request("LIST_NETWORKS")?;
|
||||
let mut ssids = Vec::new();
|
||||
for network in networks.lines() {
|
||||
let v: Vec<&str> = network.split('\t').collect();
|
||||
@ -351,7 +343,7 @@ pub fn saved_networks() -> Result<Option<String>, NetworkError> {
|
||||
if ssids.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
let results = serde_json::to_string(&ssids).context(SerdeSerialize)?;
|
||||
let results = serde_json::to_string(&ssids)?;
|
||||
Ok(Some(results))
|
||||
}
|
||||
}
|
||||
@ -372,11 +364,8 @@ pub fn saved_networks() -> Result<Option<String>, NetworkError> {
|
||||
///
|
||||
pub fn ssid(iface: &str) -> Result<Option<String>, NetworkError> {
|
||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||
let mut wpa = wpactrl::WpaCtrl::new()
|
||||
.ctrl_path(wpa_path)
|
||||
.open()
|
||||
.context(WpaCtrlOpen)?;
|
||||
let status = wpa.request("STATUS").context(WpaCtrlRequest)?;
|
||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
||||
let status = wpa.request("STATUS")?;
|
||||
|
||||
// pass the regex pattern and status output to the regex finder
|
||||
let ssid = utils::regex_finder(r"\nssid=(.*)\n", &status)?;
|
||||
@ -404,7 +393,10 @@ pub fn state(iface: &str) -> Result<Option<String>, NetworkError> {
|
||||
let output = Command::new("cat")
|
||||
.arg(iface_path)
|
||||
.output()
|
||||
.context(NoState { iface })?;
|
||||
.map_err(|source| NetworkError::NoState {
|
||||
iface: iface.to_string(),
|
||||
source,
|
||||
})?;
|
||||
if !output.stdout.is_empty() {
|
||||
// unwrap the command result and convert to String
|
||||
let mut state = String::from_utf8(output.stdout).unwrap();
|
||||
@ -433,11 +425,8 @@ pub fn state(iface: &str) -> Result<Option<String>, NetworkError> {
|
||||
///
|
||||
pub fn status(iface: &str) -> Result<Option<Status>, NetworkError> {
|
||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||
let mut wpa = wpactrl::WpaCtrl::new()
|
||||
.ctrl_path(wpa_path)
|
||||
.open()
|
||||
.context(WpaCtrlOpen)?;
|
||||
let wpa_status = wpa.request("STATUS").context(WpaCtrlRequest)?;
|
||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
||||
let wpa_status = wpa.request("STATUS")?;
|
||||
|
||||
// pass the regex pattern and status output to the regex finder
|
||||
let state = utils::regex_finder(r"wpa_state=(.*)\n", &wpa_status)?;
|
||||
@ -495,7 +484,10 @@ pub fn status(iface: &str) -> Result<Option<Status>, NetworkError> {
|
||||
/// response is sent to the caller.
|
||||
///
|
||||
pub fn traffic(iface: &str) -> Result<Option<String>, NetworkError> {
|
||||
let network = network::read().context(NoTraffic { iface })?;
|
||||
let network = network::read().map_err(|source| NetworkError::NoTraffic {
|
||||
iface: iface.to_string(),
|
||||
source,
|
||||
})?;
|
||||
// iterate through interfaces returned in network data
|
||||
for (interface, traffic) in network.interfaces {
|
||||
if interface == iface {
|
||||
@ -506,7 +498,7 @@ pub fn traffic(iface: &str) -> Result<Option<String>, NetworkError> {
|
||||
transmitted,
|
||||
};
|
||||
// TODO: add test for SerdeSerialize error
|
||||
let t = serde_json::to_string(&traffic).context(SerdeSerialize)?;
|
||||
let t = serde_json::to_string(&traffic)?;
|
||||
return Ok(Some(t));
|
||||
}
|
||||
}
|
||||
@ -531,7 +523,7 @@ pub fn activate_ap() -> Result<(), NetworkError> {
|
||||
.arg("start")
|
||||
.arg("wpa_supplicant@ap0.service")
|
||||
.output()
|
||||
.context(StartAp0)?;
|
||||
.map_err(NetworkError::StartAp0)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -551,7 +543,7 @@ pub fn activate_client() -> Result<(), NetworkError> {
|
||||
.arg("start")
|
||||
.arg("wpa_supplicant@wlan0.service")
|
||||
.output()
|
||||
.context(StartWlan0)?;
|
||||
.map_err(NetworkError::StartWlan0)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -576,7 +568,10 @@ pub fn add(wifi: &WiFi) -> Result<(), NetworkError> {
|
||||
.arg(&wifi.pass)
|
||||
.stdout(Stdio::piped())
|
||||
.output()
|
||||
.context(GenWpaPassphrase { ssid: &wifi.ssid })?;
|
||||
.map_err(|source| NetworkError::GenWpaPassphrase {
|
||||
ssid: wifi.ssid.to_string(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
// prepend newline to wpa_details to safeguard against malformed supplicant
|
||||
let mut wpa_details = "\n".as_bytes().to_vec();
|
||||
@ -621,7 +616,7 @@ pub fn check_iface() -> Result<(), NetworkError> {
|
||||
.arg("is-active")
|
||||
.arg("wpa_supplicant@wlan0.service")
|
||||
.status()
|
||||
.context(WlanState)?;
|
||||
.map_err(NetworkError::WlanState)?;
|
||||
|
||||
// returns the current state of the wlan0 interface
|
||||
let iface_state = state("wlan0")?;
|
||||
@ -657,12 +652,9 @@ pub fn check_iface() -> Result<(), NetworkError> {
|
||||
///
|
||||
pub fn connect(id: &str, iface: &str) -> Result<(), NetworkError> {
|
||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||
let mut wpa = wpactrl::WpaCtrl::new()
|
||||
.ctrl_path(wpa_path)
|
||||
.open()
|
||||
.context(WpaCtrlOpen)?;
|
||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
||||
let select = format!("SELECT {}", id);
|
||||
wpa.request(&select).context(WpaCtrlRequest)?;
|
||||
wpa.request(&select)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -682,12 +674,9 @@ pub fn connect(id: &str, iface: &str) -> Result<(), NetworkError> {
|
||||
///
|
||||
pub fn delete(id: &str, iface: &str) -> Result<(), NetworkError> {
|
||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||
let mut wpa = wpactrl::WpaCtrl::new()
|
||||
.ctrl_path(wpa_path)
|
||||
.open()
|
||||
.context(WpaCtrlOpen)?;
|
||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
||||
let remove = format!("REMOVE_NETWORK {}", id);
|
||||
wpa.request(&remove).context(WpaCtrlRequest)?;
|
||||
wpa.request(&remove)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -706,12 +695,9 @@ pub fn delete(id: &str, iface: &str) -> Result<(), NetworkError> {
|
||||
///
|
||||
pub fn disable(id: &str, iface: &str) -> Result<(), NetworkError> {
|
||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||
let mut wpa = wpactrl::WpaCtrl::new()
|
||||
.ctrl_path(wpa_path)
|
||||
.open()
|
||||
.context(WpaCtrlOpen)?;
|
||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
||||
let disable = format!("DISABLE_NETWORK {}", id);
|
||||
wpa.request(&disable).context(WpaCtrlRequest)?;
|
||||
wpa.request(&disable)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -729,12 +715,9 @@ pub fn disable(id: &str, iface: &str) -> Result<(), NetworkError> {
|
||||
///
|
||||
pub fn disconnect(iface: &str) -> Result<(), NetworkError> {
|
||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||
let mut wpa = wpactrl::WpaCtrl::new()
|
||||
.ctrl_path(wpa_path)
|
||||
.open()
|
||||
.context(WpaCtrlOpen)?;
|
||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
||||
let disconnect = "DISCONNECT".to_string();
|
||||
wpa.request(&disconnect).context(WpaCtrlRequest)?;
|
||||
wpa.request(&disconnect)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -754,12 +737,9 @@ pub fn disconnect(iface: &str) -> Result<(), NetworkError> {
|
||||
///
|
||||
pub fn modify(id: &str, iface: &str, pass: &str) -> Result<(), NetworkError> {
|
||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||
let mut wpa = wpactrl::WpaCtrl::new()
|
||||
.ctrl_path(wpa_path)
|
||||
.open()
|
||||
.context(WpaCtrlOpen)?;
|
||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
||||
let new_pass = format!("NEW_PASSWORD {} {}", id, pass);
|
||||
wpa.request(&new_pass).context(WpaCtrlRequest)?;
|
||||
wpa.request(&new_pass)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -777,11 +757,8 @@ pub fn modify(id: &str, iface: &str, pass: &str) -> Result<(), NetworkError> {
|
||||
///
|
||||
pub fn reassociate(iface: &str) -> Result<(), NetworkError> {
|
||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||
let mut wpa = wpactrl::WpaCtrl::new()
|
||||
.ctrl_path(wpa_path)
|
||||
.open()
|
||||
.context(WpaCtrlOpen)?;
|
||||
wpa.request("REASSOCIATE").context(WpaCtrlRequest)?;
|
||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
||||
wpa.request("REASSOCIATE")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -795,8 +772,8 @@ pub fn reassociate(iface: &str) -> Result<(), NetworkError> {
|
||||
/// caller.
|
||||
///
|
||||
pub fn reconfigure() -> Result<(), NetworkError> {
|
||||
let mut wpa = wpactrl::WpaCtrl::new().open().context(WpaCtrlOpen)?;
|
||||
wpa.request("RECONFIGURE").context(WpaCtrlRequest)?;
|
||||
let mut wpa = wpactrl::WpaCtrl::builder().open()?;
|
||||
wpa.request("RECONFIGURE")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -814,12 +791,9 @@ pub fn reconfigure() -> Result<(), NetworkError> {
|
||||
///
|
||||
pub fn reconnect(iface: &str) -> Result<(), NetworkError> {
|
||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||
let mut wpa = wpactrl::WpaCtrl::new()
|
||||
.ctrl_path(wpa_path)
|
||||
.open()
|
||||
.context(WpaCtrlOpen)?;
|
||||
wpa.request("DISCONNECT").context(WpaCtrlRequest)?;
|
||||
wpa.request("RECONNECT").context(WpaCtrlRequest)?;
|
||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
||||
wpa.request("DISCONNECT")?;
|
||||
wpa.request("RECONNECT")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -832,7 +806,7 @@ pub fn reconnect(iface: &str) -> Result<(), NetworkError> {
|
||||
/// appropriate JSON RPC response is sent to the caller.
|
||||
///
|
||||
pub fn save() -> Result<(), NetworkError> {
|
||||
let mut wpa = wpactrl::WpaCtrl::new().open().context(WpaCtrlOpen)?;
|
||||
wpa.request("SAVE_CONFIG").context(WpaCtrlRequest)?;
|
||||
let mut wpa = wpactrl::WpaCtrl::builder().open()?;
|
||||
wpa.request("SAVE_CONFIG")?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use regex::Regex;
|
||||
use snafu::ResultExt;
|
||||
|
||||
use crate::error::*;
|
||||
use crate::error::NetworkError;
|
||||
|
||||
/// Return matches for a given Regex pattern and text
|
||||
///
|
||||
@ -11,7 +10,7 @@ use crate::error::*;
|
||||
/// * `text` - A string slice containing the text to be matched on
|
||||
///
|
||||
pub fn regex_finder(pattern: &str, text: &str) -> Result<Option<String>, NetworkError> {
|
||||
let re = Regex::new(pattern).context(Regex)?;
|
||||
let re = Regex::new(pattern)?;
|
||||
let caps = re.captures(text);
|
||||
let result = caps.map(|caps| caps[1].to_string());
|
||||
|
||||
|
@ -1,40 +1,31 @@
|
||||
[package]
|
||||
name = "peach-stats"
|
||||
version = "0.1.3"
|
||||
authors = ["Andrew Reid <gnomad@cryptolab.net>"]
|
||||
version = "0.2.0"
|
||||
authors = ["Andrew Reid <glyph@mycelial.technology>"]
|
||||
edition = "2018"
|
||||
description = "Query system statistics using JSON-RPC over HTTP. Provides a JSON-RPC wrapper around the probes and systemstat crates."
|
||||
description = "Query system statistics. Provides a wrapper around the probes and systemstat crates."
|
||||
keywords = ["peachcloud", "system stats", "system statistics", "disk", "memory"]
|
||||
homepage = "https://opencollective.com/peachcloud"
|
||||
repository = "https://github.com/peachcloud/peach-stats"
|
||||
repository = "https://git.coopcloud.tech/PeachCloud/peach-workspace/src/branch/main/peach-stats"
|
||||
readme = "README.md"
|
||||
license = "AGPL-3.0-only"
|
||||
license = "LGPL-3.0-only"
|
||||
publish = false
|
||||
|
||||
[package.metadata.deb]
|
||||
depends = "$auto"
|
||||
extended-description = """\
|
||||
peach-stats is a system statistics microservice module for PeachCloud. \
|
||||
Query system statistics using JSON-RPC over HTTP. Provides a JSON-RPC \
|
||||
wrapper around the probes and systemstat crates."""
|
||||
maintainer-scripts="debian"
|
||||
systemd-units = { unit-name = "peach-stats" }
|
||||
assets = [
|
||||
["target/release/peach-stats", "usr/bin/", "755"],
|
||||
["README.md", "usr/share/doc/peach-stats/README", "644"],
|
||||
]
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "peachcloud/peach-stats", branch = "master" }
|
||||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[dependencies]
|
||||
env_logger = "0.9"
|
||||
jsonrpc-core = "18"
|
||||
jsonrpc-http-server = "18"
|
||||
log = "0.4"
|
||||
miniserde = "0.1.15"
|
||||
miniserde = { version = "0.1.15", optional = true }
|
||||
probes = "0.4.1"
|
||||
serde = { version = "1.0.130", features = ["derive"], optional = true }
|
||||
systemstat = "0.1.10"
|
||||
|
||||
[dev-dependencies]
|
||||
jsonrpc-test = "18"
|
||||
[features]
|
||||
default = []
|
||||
|
||||
# Provide `Serialize` and `Deserialize` traits for library structs using `miniserde`
|
||||
miniserde_support = ["miniserde"]
|
||||
|
||||
# Provide `Serialize` and `Deserialize` traits for library structs using `serde`
|
||||
serde_support = ["serde"]
|
||||
|
@ -1,109 +1,47 @@
|
||||
# peach-stats
|
||||
|
||||
[](https://travis-ci.com/peachcloud/peach-stats) 
|
||||

|
||||
|
||||
System statistics microservice module for PeachCloud. Provides a JSON-RPC 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.
|
||||
|
||||
### JSON-RPC API
|
||||
Currently offers the following statistics and associated data structures:
|
||||
|
||||
| Method | Description | Returns |
|
||||
| --- | --- | --- |
|
||||
| `cpu_stats` | CPU statistics | `user`, `system`, `nice`, `idle` |
|
||||
| `cpu_stats_percent` | CPU statistics as percentages | `user`, `system`, `nice`, `idle` |
|
||||
| `disk_usage` | Disk usage statistics (array of disks) | `filesystem`, `one_k_blocks`, `one_k_blocks_used`, `one_k_blocks_free`, `used_percentage`, `mountpoint` |
|
||||
| `load_average` | Load average statistics | `one`, `five`, `fifteen` |
|
||||
| `mem_stats` | Memory statistics | `total`, `free`, `used` |
|
||||
| `ping` | Microservice status | `success` if running |
|
||||
| `uptime` | System uptime | `secs` |
|
||||
- CPU: `user`, `system`, `nice`, `idle` (as values or percentages)
|
||||
- Disk usage: `filesystem`, `one_k_blocks`, `one_k_blocks_used`,
|
||||
`one_k_blocks_free`, `used_percentage`, `mountpoint`
|
||||
- Load average: `one`, `five`, `fifteen`
|
||||
- Memory: `total`, `free`, `used`
|
||||
- Uptime: `seconds`
|
||||
|
||||
### Environment
|
||||
## Example Usage
|
||||
|
||||
The JSON-RPC HTTP server address and port can be configured with the `PEACH_STATS_SERVER` environment variable:
|
||||
```rust
|
||||
use peach_stats::{stats, StatsError};
|
||||
|
||||
`export PEACH_STATS_SERVER=127.0.0.1:5000`
|
||||
fn main() -> Result<(), StatsError> {
|
||||
let cpu = stats::cpu_stats()?;
|
||||
let cpu_percentages = stats::cpu_stats_percent()?;
|
||||
let disks = stats::disk_usage()?;
|
||||
let load = stats::load_average()?;
|
||||
let mem = stats::mem_stats()?;
|
||||
let uptime = stats::uptime()?;
|
||||
|
||||
When not set, the value defaults to `127.0.0.1:5113`.
|
||||
// do things with the retrieved values...
|
||||
|
||||
Logging is made available with `env_logger`:
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
`export RUST_LOG=info`
|
||||
## Feature Flags
|
||||
|
||||
Other logging levels include `debug`, `warn` and `error`.
|
||||
Feature flags are used to offer `Serialize` and `Deserialize` implementations for all `struct` data types provided by this library. These traits are not provided by default. A choice of `miniserde` and `serde` is provided.
|
||||
|
||||
### Setup
|
||||
Define the desired feature in the `Cargo.toml` manifest of your project:
|
||||
|
||||
Clone this repo:
|
||||
```toml
|
||||
peach-stats = { version = "0.1.0", features = ["miniserde_support"] }
|
||||
```
|
||||
|
||||
`git clone https://github.com/peachcloud/peach-stats.git`
|
||||
|
||||
Move into the repo and compile a release build:
|
||||
|
||||
`cd peach-stats`
|
||||
`cargo build --release`
|
||||
|
||||
Run the binary:
|
||||
|
||||
`./target/release/peach-stats`
|
||||
|
||||
### Debian Packaging
|
||||
|
||||
A `systemd` service file and Debian maintainer scripts are included in the `debian` directory, allowing `peach-stats` to be easily bundled as a Debian package (`.deb`). The `cargo-deb` [crate](https://crates.io/crates/cargo-deb) can be used to achieve this.
|
||||
|
||||
Install `cargo-deb`:
|
||||
|
||||
`cargo install cargo-deb`
|
||||
|
||||
Move into the repo:
|
||||
|
||||
`cd peach-stats`
|
||||
|
||||
Build the package:
|
||||
|
||||
`cargo deb`
|
||||
|
||||
The output will be written to `target/debian/peach-stats_0.1.0_arm64.deb` (or similar).
|
||||
|
||||
Build the package (aarch64):
|
||||
|
||||
`cargo deb --target aarch64-unknown-linux-gnu`
|
||||
|
||||
Install the package as follows:
|
||||
|
||||
`sudo dpkg -i target/debian/peach-stats_0.1.0_arm64.deb`
|
||||
|
||||
The service will be automatically enabled and started.
|
||||
|
||||
Uninstall the service:
|
||||
|
||||
`sudo apt-get remove peach-stats`
|
||||
|
||||
Remove configuration files (not removed with `apt-get remove`):
|
||||
|
||||
`sudo apt-get purge peach-stats`
|
||||
|
||||
### Example Usage
|
||||
|
||||
**Get CPU Statistics**
|
||||
|
||||
With microservice running, open a second terminal window and use `curl` to call server methods:
|
||||
|
||||
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "cpu_stats", "id":1 }' 127.0.0.1:5113`
|
||||
|
||||
Server responds with:
|
||||
|
||||
`{"jsonrpc":"2.0","result":"{\"user\":4661083,\"system\":1240371,\"idle\":326838290,\"nice\":0}","id":1}`
|
||||
|
||||
**Get System Uptime**
|
||||
|
||||
With microservice running, open a second terminal window and use `curl` to call server methods:
|
||||
|
||||
`curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "uptime", "id":1 }' 127.0.0.1:5113`
|
||||
|
||||
Server responds with:
|
||||
|
||||
`{"jsonrpc":"2.0","result":"{\"secs\":840968}","id":1}`
|
||||
|
||||
### Licensing
|
||||
|
||||
AGPL-3.0
|
||||
## License
|
||||
|
||||
LGPL-3.0.
|
||||
|
@ -1,27 +0,0 @@
|
||||
[Unit]
|
||||
Description=Query system statistics using JSON-RPC over HTTP.
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=peach-stats
|
||||
Environment="RUST_LOG=error"
|
||||
ExecStart=/usr/bin/peach-stats
|
||||
Restart=always
|
||||
CapabilityBoundingSet=~CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_SYS_BOOT CAP_SYS_TIME CAP_KILL CAP_WAKE_ALARM CAP_LINUX_IMMUTABLE CAP_BLOCK_SUSPEND CAP_LEASE CAP_SYS_NICE CAP_SYS_RESOURCE CAP_RAWIO CAP_CHOWN CAP_FSETID CAP_SETFCAP CAP_DAC_* CAP_FOWNER CAP_IPC_OWNER CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_AUDIT_*
|
||||
InaccessibleDirectories=/home
|
||||
LockPersonality=yes
|
||||
NoNewPrivileges=yes
|
||||
PrivateDevices=yes
|
||||
PrivateTmp=yes
|
||||
PrivateUsers=yes
|
||||
ProtectControlGroups=yes
|
||||
ProtectHome=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectSystem=yes
|
||||
ReadOnlyDirectories=/var
|
||||
RestrictAddressFamilies=~AF_INET6 AF_UNIX
|
||||
SystemCallFilter=~@reboot @clock @debug @module @mount @swap @resources @privileged
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -1,69 +1,44 @@
|
||||
use std::{error, fmt, io};
|
||||
//! Custom error type for `peach-stats`.
|
||||
|
||||
use jsonrpc_core::{types::error::Error, ErrorCode};
|
||||
use probes::ProbeError;
|
||||
use std::{error, fmt, io::Error as IoError};
|
||||
|
||||
/// Custom error type encapsulating all possible errors when retrieving system
|
||||
/// statistics.
|
||||
#[derive(Debug)]
|
||||
pub enum StatError {
|
||||
CpuStat { source: ProbeError },
|
||||
DiskUsage { source: ProbeError },
|
||||
LoadAvg { source: ProbeError },
|
||||
MemStat { source: ProbeError },
|
||||
Uptime { source: io::Error },
|
||||
pub enum StatsError {
|
||||
/// Failed to retrieve CPU statistics.
|
||||
CpuStat(ProbeError),
|
||||
/// Failed to retrieve disk usage statistics.
|
||||
DiskUsage(ProbeError),
|
||||
/// Failed to retrieve load average statistics.
|
||||
LoadAvg(ProbeError),
|
||||
/// Failed to retrieve memory usage statistics.
|
||||
MemStat(ProbeError),
|
||||
/// Failed to retrieve system uptime.
|
||||
Uptime(IoError),
|
||||
}
|
||||
|
||||
impl error::Error for StatError {}
|
||||
impl error::Error for StatsError {}
|
||||
|
||||
impl fmt::Display for StatError {
|
||||
impl fmt::Display for StatsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
StatError::CpuStat { ref source } => {
|
||||
StatsError::CpuStat(ref source) => {
|
||||
write!(f, "Failed to retrieve CPU statistics: {}", source)
|
||||
}
|
||||
StatError::DiskUsage { ref source } => {
|
||||
StatsError::DiskUsage(ref source) => {
|
||||
write!(f, "Failed to retrieve disk usage statistics: {}", source)
|
||||
}
|
||||
StatError::LoadAvg { ref source } => {
|
||||
StatsError::LoadAvg(ref source) => {
|
||||
write!(f, "Failed to retrieve load average statistics: {}", source)
|
||||
}
|
||||
StatError::MemStat { ref source } => {
|
||||
StatsError::MemStat(ref source) => {
|
||||
write!(f, "Failed to retrieve memory statistics: {}", source)
|
||||
}
|
||||
StatError::Uptime { ref source } => {
|
||||
StatsError::Uptime(ref source) => {
|
||||
write!(f, "Failed to retrieve system uptime: {}", source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StatError> for Error {
|
||||
fn from(err: StatError) -> Self {
|
||||
match &err {
|
||||
StatError::CpuStat { source } => Error {
|
||||
code: ErrorCode::ServerError(-32001),
|
||||
message: format!("Failed to retrieve CPU statistics: {}", source),
|
||||
data: None,
|
||||
},
|
||||
StatError::DiskUsage { source } => Error {
|
||||
code: ErrorCode::ServerError(-32001),
|
||||
message: format!("Failed to retrieve disk usage statistics: {}", source),
|
||||
data: None,
|
||||
},
|
||||
StatError::LoadAvg { source } => Error {
|
||||
code: ErrorCode::ServerError(-32001),
|
||||
message: format!("Failed to retrieve load average statistics: {}", source),
|
||||
data: None,
|
||||
},
|
||||
StatError::MemStat { source } => Error {
|
||||
code: ErrorCode::ServerError(-32001),
|
||||
message: format!("Failed to retrieve memory statistics: {}", source),
|
||||
data: None,
|
||||
},
|
||||
StatError::Uptime { source } => Error {
|
||||
code: ErrorCode::ServerError(-32001),
|
||||
message: format!("Failed to retrieve system uptime: {}", source),
|
||||
data: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,103 +1,48 @@
|
||||
mod error;
|
||||
mod stats;
|
||||
mod structs;
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use std::{env, result::Result};
|
||||
//! # peach-stats
|
||||
//!
|
||||
//! System statistics retrieval library; designed for use with the PeachCloud platform.
|
||||
//!
|
||||
//! Currently offers the following statistics and associated data structures:
|
||||
//!
|
||||
//! - CPU: `user`, `system`, `nice`, `idle` (as values or percentages)
|
||||
//! - Disk usage: `filesystem`, `one_k_blocks`, `one_k_blocks_used`,
|
||||
//! `one_k_blocks_free`, `used_percentage`, `mountpoint`
|
||||
//! - Load average: `one`, `five`, `fifteen`
|
||||
//! - Memory: `total`, `free`, `used`
|
||||
//! - Uptime: `seconds`
|
||||
//!
|
||||
//! ## Example Usage
|
||||
//!
|
||||
//! ```rust
|
||||
//! use peach_stats::{stats, StatsError};
|
||||
//!
|
||||
//! fn main() -> Result<(), StatsError> {
|
||||
//! let cpu = stats::cpu_stats()?;
|
||||
//! let cpu_percentages = stats::cpu_stats_percent()?;
|
||||
//! let disks = stats::disk_usage()?;
|
||||
//! let load = stats::load_average()?;
|
||||
//! let mem = stats::mem_stats()?;
|
||||
//! let uptime = stats::uptime()?;
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Feature Flags
|
||||
//!
|
||||
//! Feature flags are used to offer `Serialize` and `Deserialize` implementations
|
||||
//! for all `struct` data types provided by this library. These traits are not
|
||||
//! provided by default. A choice of `miniserde` and `serde` is provided.
|
||||
//!
|
||||
//! Define the desired feature in the `Cargo.toml` manifest of your project:
|
||||
//!
|
||||
//! ```toml
|
||||
//! peach-stats = { version = "0.1.0", features = ["miniserde_support"] }
|
||||
//! ```
|
||||
|
||||
use jsonrpc_core::{IoHandler, Value};
|
||||
use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder};
|
||||
use log::info;
|
||||
pub mod error;
|
||||
pub mod stats;
|
||||
|
||||
use crate::error::StatError;
|
||||
|
||||
pub fn run() -> Result<(), StatError> {
|
||||
info!("Starting up.");
|
||||
|
||||
info!("Creating JSON-RPC I/O handler.");
|
||||
let mut io = IoHandler::default();
|
||||
|
||||
io.add_method("cpu_stats", move |_| async {
|
||||
info!("Fetching CPU statistics.");
|
||||
let stats = stats::cpu_stats()?;
|
||||
|
||||
Ok(Value::String(stats))
|
||||
});
|
||||
|
||||
io.add_method("cpu_stats_percent", move |_| async {
|
||||
info!("Fetching CPU statistics as percentages.");
|
||||
let stats = stats::cpu_stats_percent()?;
|
||||
|
||||
Ok(Value::String(stats))
|
||||
});
|
||||
|
||||
io.add_method("disk_usage", move |_| async {
|
||||
info!("Fetching disk usage statistics.");
|
||||
let disks = stats::disk_usage()?;
|
||||
|
||||
Ok(Value::String(disks))
|
||||
});
|
||||
|
||||
io.add_method("load_average", move |_| async {
|
||||
info!("Fetching system load average statistics.");
|
||||
let avg = stats::load_average()?;
|
||||
|
||||
Ok(Value::String(avg))
|
||||
});
|
||||
|
||||
io.add_method("mem_stats", move |_| async {
|
||||
info!("Fetching current memory statistics.");
|
||||
let mem = stats::mem_stats()?;
|
||||
|
||||
Ok(Value::String(mem))
|
||||
});
|
||||
|
||||
io.add_method("ping", |_| async {
|
||||
Ok(Value::String("success".to_string()))
|
||||
});
|
||||
|
||||
io.add_method("uptime", move |_| async {
|
||||
info!("Fetching system uptime.");
|
||||
let uptime = stats::uptime()?;
|
||||
|
||||
Ok(Value::String(uptime))
|
||||
});
|
||||
|
||||
let http_server = env::var("PEACH_OLED_STATS").unwrap_or_else(|_| "127.0.0.1:5113".to_string());
|
||||
|
||||
info!("Starting JSON-RPC server on {}.", http_server);
|
||||
let server = ServerBuilder::new(io)
|
||||
.cors(DomainsValidation::AllowOnly(vec![
|
||||
AccessControlAllowOrigin::Null,
|
||||
]))
|
||||
.start_http(
|
||||
&http_server
|
||||
.parse()
|
||||
.expect("Invalid HTTP address and port combination"),
|
||||
)
|
||||
.expect("Unable to start RPC server");
|
||||
|
||||
info!("Listening for requests.");
|
||||
server.wait();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use jsonrpc_test as test_rpc;
|
||||
|
||||
// test to ensure correct success response
|
||||
#[test]
|
||||
fn rpc_success() {
|
||||
let rpc = {
|
||||
let mut io = IoHandler::new();
|
||||
io.add_method("rpc_success_response", |_| async {
|
||||
Ok(Value::String("success".into()))
|
||||
});
|
||||
test_rpc::Rpc::from(io)
|
||||
};
|
||||
|
||||
assert_eq!(rpc.request("rpc_success_response", &()), r#""success""#);
|
||||
}
|
||||
}
|
||||
pub use crate::error::StatsError;
|
||||
|
@ -1,14 +0,0 @@
|
||||
use std::process;
|
||||
|
||||
use log::error;
|
||||
|
||||
fn main() {
|
||||
// initialize the logger
|
||||
env_logger::init();
|
||||
|
||||
// handle errors returned from `run`
|
||||
if let Err(e) = peach_stats::run() {
|
||||
error!("Application error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
@ -1,14 +1,96 @@
|
||||
//! System statistics retrieval functions and associated data types.
|
||||
|
||||
use std::result::Result;
|
||||
|
||||
use miniserde::json;
|
||||
#[cfg(feature = "miniserde_support")]
|
||||
use miniserde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg(feature = "serde_support")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use probes::{cpu, disk_usage, load, memory};
|
||||
use systemstat::{Platform, System};
|
||||
|
||||
use crate::error::StatError;
|
||||
use crate::structs::{CpuStat, CpuStatPercentages, DiskUsage, LoadAverage, MemStat};
|
||||
use crate::error::StatsError;
|
||||
|
||||
pub fn cpu_stats() -> Result<String, StatError> {
|
||||
let cpu_stats = cpu::proc::read().map_err(|source| StatError::CpuStat { source })?;
|
||||
/// CPU statistics.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "miniserde_support", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
|
||||
pub struct CpuStat {
|
||||
/// Time spent running user space (application) code.
|
||||
pub user: u64,
|
||||
/// Time spent running kernel code.
|
||||
pub system: u64,
|
||||
/// Time spent doing nothing.
|
||||
pub idle: u64,
|
||||
/// Time spent running user space processes which have been niced.
|
||||
pub nice: u64,
|
||||
}
|
||||
|
||||
/// CPU statistics as percentages.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "miniserde_support", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
|
||||
pub struct CpuStatPercentages {
|
||||
/// Time spent running user space (application) code.
|
||||
pub user: f32,
|
||||
/// Time spent running kernel code.
|
||||
pub system: f32,
|
||||
/// Time spent doing nothing.
|
||||
pub idle: f32,
|
||||
/// Time spent running user space processes which have been niced.
|
||||
pub nice: f32,
|
||||
}
|
||||
|
||||
/// Disk usage statistics.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "miniserde_support", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
|
||||
pub struct DiskUsage {
|
||||
/// Filesystem device path.
|
||||
pub filesystem: Option<String>,
|
||||
/// Total amount of disk space as a number of 1,000 kilobyte blocks.
|
||||
pub one_k_blocks: u64,
|
||||
/// Total amount of used disk space as a number of 1,000 kilobyte blocks.
|
||||
pub one_k_blocks_used: u64,
|
||||
/// Total amount of free / available disk space as a number of 1,000 kilobyte blocks.
|
||||
pub one_k_blocks_free: u64,
|
||||
/// Total amount of used disk space as a percentage.
|
||||
pub used_percentage: u32,
|
||||
/// Mountpoint of the disk / partition.
|
||||
pub mountpoint: String,
|
||||
}
|
||||
|
||||
/// Load average statistics.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "miniserde_support", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
|
||||
pub struct LoadAverage {
|
||||
/// Average computational work performed over the past minute.
|
||||
pub one: f32,
|
||||
/// Average computational work performed over the past five minutes.
|
||||
pub five: f32,
|
||||
/// Average computational work performed over the past fifteen minutes.
|
||||
pub fifteen: f32,
|
||||
}
|
||||
|
||||
/// Memory statistics.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "miniserde_support", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
|
||||
pub struct MemStat {
|
||||
/// Total amount of physical memory in kilobytes.
|
||||
pub total: u64,
|
||||
/// Total amount of free / available physical memory in kilobytes.
|
||||
pub free: u64,
|
||||
/// Total amount of used physical memory in kilobytes.
|
||||
pub used: u64,
|
||||
}
|
||||
|
||||
/// Retrieve the current CPU statistics.
|
||||
pub fn cpu_stats() -> Result<CpuStat, StatsError> {
|
||||
let cpu_stats = cpu::proc::read().map_err(StatsError::CpuStat)?;
|
||||
let s = cpu_stats.stat;
|
||||
let cpu = CpuStat {
|
||||
user: s.user,
|
||||
@ -16,13 +98,13 @@ pub fn cpu_stats() -> Result<String, StatError> {
|
||||
nice: s.nice,
|
||||
idle: s.idle,
|
||||
};
|
||||
let json_cpu = json::to_string(&cpu);
|
||||
|
||||
Ok(json_cpu)
|
||||
Ok(cpu)
|
||||
}
|
||||
|
||||
pub fn cpu_stats_percent() -> Result<String, StatError> {
|
||||
let cpu_stats = cpu::proc::read().map_err(|source| StatError::CpuStat { source })?;
|
||||
/// Retrieve the current CPU statistics as percentages.
|
||||
pub fn cpu_stats_percent() -> Result<CpuStatPercentages, StatsError> {
|
||||
let cpu_stats = cpu::proc::read().map_err(StatsError::CpuStat)?;
|
||||
let s = cpu_stats.stat.in_percentages();
|
||||
let cpu = CpuStatPercentages {
|
||||
user: s.user,
|
||||
@ -30,13 +112,13 @@ pub fn cpu_stats_percent() -> Result<String, StatError> {
|
||||
nice: s.nice,
|
||||
idle: s.idle,
|
||||
};
|
||||
let json_cpu = json::to_string(&cpu);
|
||||
|
||||
Ok(json_cpu)
|
||||
Ok(cpu)
|
||||
}
|
||||
|
||||
pub fn disk_usage() -> Result<String, StatError> {
|
||||
let disks = disk_usage::read().map_err(|source| StatError::DiskUsage { source })?;
|
||||
/// Retrieve the current disk usage statistics for each available disk / partition.
|
||||
pub fn disk_usage() -> Result<Vec<DiskUsage>, StatsError> {
|
||||
let disks = disk_usage::read().map_err(StatsError::DiskUsage)?;
|
||||
let mut disk_usages = Vec::new();
|
||||
for d in disks {
|
||||
let disk = DiskUsage {
|
||||
@ -49,42 +131,39 @@ pub fn disk_usage() -> Result<String, StatError> {
|
||||
};
|
||||
disk_usages.push(disk);
|
||||
}
|
||||
let json_disks = json::to_string(&disk_usages);
|
||||
|
||||
Ok(json_disks)
|
||||
Ok(disk_usages)
|
||||
}
|
||||
|
||||
pub fn load_average() -> Result<String, StatError> {
|
||||
let l = load::read().map_err(|source| StatError::LoadAvg { source })?;
|
||||
/// Retrieve the current load average statistics.
|
||||
pub fn load_average() -> Result<LoadAverage, StatsError> {
|
||||
let l = load::read().map_err(StatsError::LoadAvg)?;
|
||||
let load_avg = LoadAverage {
|
||||
one: l.one,
|
||||
five: l.five,
|
||||
fifteen: l.fifteen,
|
||||
};
|
||||
let json_load_avg = json::to_string(&load_avg);
|
||||
|
||||
Ok(json_load_avg)
|
||||
Ok(load_avg)
|
||||
}
|
||||
|
||||
pub fn mem_stats() -> Result<String, StatError> {
|
||||
let m = memory::read().map_err(|source| StatError::MemStat { source })?;
|
||||
/// Retrieve the current memory usage statistics.
|
||||
pub fn mem_stats() -> Result<MemStat, StatsError> {
|
||||
let m = memory::read().map_err(StatsError::MemStat)?;
|
||||
let mem = MemStat {
|
||||
total: m.total(),
|
||||
free: m.free(),
|
||||
used: m.used(),
|
||||
};
|
||||
let json_mem = json::to_string(&mem);
|
||||
|
||||
Ok(json_mem)
|
||||
Ok(mem)
|
||||
}
|
||||
|
||||
pub fn uptime() -> Result<String, StatError> {
|
||||
/// Retrieve the system uptime in seconds.
|
||||
pub fn uptime() -> Result<u64, StatsError> {
|
||||
let sys = System::new();
|
||||
let uptime = sys
|
||||
.uptime()
|
||||
.map_err(|source| StatError::Uptime { source })?;
|
||||
let uptime = sys.uptime().map_err(StatsError::Uptime)?;
|
||||
let uptime_secs = uptime.as_secs();
|
||||
let json_uptime = json::to_string(&uptime_secs);
|
||||
|
||||
Ok(json_uptime)
|
||||
Ok(uptime_secs)
|
||||
}
|
||||
|
@ -1,41 +0,0 @@
|
||||
use miniserde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct CpuStat {
|
||||
pub user: u64,
|
||||
pub system: u64,
|
||||
pub idle: u64,
|
||||
pub nice: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct CpuStatPercentages {
|
||||
pub user: f32,
|
||||
pub system: f32,
|
||||
pub idle: f32,
|
||||
pub nice: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct DiskUsage {
|
||||
pub filesystem: Option<String>,
|
||||
pub one_k_blocks: u64,
|
||||
pub one_k_blocks_used: u64,
|
||||
pub one_k_blocks_free: u64,
|
||||
pub used_percentage: u32,
|
||||
pub mountpoint: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct LoadAverage {
|
||||
pub one: f32,
|
||||
pub five: f32,
|
||||
pub fifteen: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct MemStat {
|
||||
pub total: u64,
|
||||
pub free: u64,
|
||||
pub used: u64,
|
||||
}
|
@ -52,7 +52,7 @@ pub fn save_dns_configuration(dns_form: DnsForm) -> Result<(), PeachWebError> {
|
||||
info!("Failed to register dyndns domain: {:?}", err);
|
||||
// json response for failed update
|
||||
let msg: String = match err {
|
||||
PeachError::JsonRpcClientCore { source } => {
|
||||
PeachError::JsonRpcClientCore(source) => {
|
||||
match source {
|
||||
Error(ErrorKind::JsonRpcError(err), _state) => match err.code {
|
||||
ErrorCode::ServerError(-32030) => {
|
||||
|
Reference in New Issue
Block a user