37 Commits

Author SHA1 Message Date
a5415aad99 merge direct_call_net_stats branch 2022-01-12 11:35:55 +02:00
037e5c34b6 merge replace_rust_crypto branch 2022-01-12 11:21:18 +02:00
699f2b13c9 merge update_network_args branch 2022-01-12 11:19:24 +02:00
c3fbc5cd73 try operator for dyns dns domain check 2022-01-12 10:59:13 +02:00
4a27892ab6 idiomatic paths and result type for checking new dns address 2022-01-12 10:58:36 +02:00
4adf5547c9 formatting 2022-01-12 10:58:11 +02:00
bdfbd7057f Remove commented out code 2022-01-11 18:10:36 -05:00
171d051710 Fix clippy warmings 2022-01-11 18:06:51 -05:00
1ea0ea2ed1 Fix regression of peach-dyndns-updater 2022-01-11 18:03:07 -05:00
57ed0ab66a add fullstop to docs sentence 2022-01-06 11:56:45 +02:00
49ad74595c cleanup use paths and leave network_ping note 2022-01-06 11:56:23 +02:00
17d52c771f Merge branch 'main' into direct_call_net_stats
Merge crypto library update for peach-lib.
2022-01-04 18:34:21 +02:00
6792e4702d Merge pull request 'Replace outdated crypto crate' (#61) from replace_rust_crypto into main
Reviewed-on: #61
2022-01-04 16:33:24 +00:00
446927f587 replace outdated crypto crate 2022-01-04 15:23:41 +02:00
567b0bbc2a replace network rpc client calls with direct calls to peach_network 2022-01-04 14:55:17 +02:00
3ab3e65eb7 replace stats rpc client calls with direct calls to peach_stats 2022-01-04 14:06:57 +02:00
a0e80fcda7 add deps for network and stats 2022-01-04 14:06:35 +02:00
731bc1958b Merge pull request 'Update network args and remove structs' (#60) from update_network_args into main
Reviewed-on: #60
2022-01-04 08:38:23 +00:00
c75608fb1a Merge branch 'main' of https://git.coopcloud.tech/PeachCloud/peach-workspace into main3 2021-12-22 12:19:56 -05:00
068d3430d7 Merge pull request 'Add permissions function peach-config' (#56) from permissions into main
Reviewed-on: #56
2021-12-22 17:18:24 +00:00
62793f401e Change imports and add permissions for peach-web dir 2021-12-22 10:04:15 -05:00
b8f394b901 Debugging dyndns 2021-12-22 09:59:20 -05:00
9324b3ec0b Merge pull request 'Copy Rocket.toml to /usr/share/peach-web' (#55) from copy-rocket-toml into main
Reviewed-on: #55
2021-12-22 14:53:21 +00:00
f43fbf19f5 Merge pull request 'Add changepassword function to peach-config' (#53) from change-password into main
Reviewed-on: #53
2021-12-22 14:51:27 +00:00
29cc40be48 Fix setup of nsupdate 2021-12-18 11:24:43 -05:00
570f6a679b Change permissions to u+rwX,g+rwX 2021-12-18 10:22:50 -05:00
399af51ccc Add permissions function peach-config 2021-12-18 10:00:40 -05:00
94bac00664 Fix typo in secret_key 2021-12-18 09:22:55 -05:00
c41dae8d04 Copy Rocket.toml to /usr/share/peach-web 2021-12-17 17:23:27 -05:00
e34df3b656 Remove configuration of http basic auth 2021-12-17 17:19:04 -05:00
3399a3c80f Add changepassword function to peach-config 2021-12-17 16:23:47 -05:00
1c26cb70fa Merge pull request 'Bump version number for peach-config' (#51) from version-number into main
Reviewed-on: #51
2021-12-17 17:38:14 +00:00
c79bd4b19f Bump version number for peach-config 2021-12-17 12:37:43 -05:00
7743511923 Merge pull request 'Update kernel version to 4.19.0-18-arm64' (#50) from kernel-version into main
Reviewed-on: #50
2021-12-17 17:15:36 +00:00
10833078fa Update kernel version to 4.19.0-18-arm64 2021-12-17 12:15:02 -05:00
244a2132fa Merge pull request 'Move cargo/.config to root of workspace' (#49) from workspace into main
Reviewed-on: #49
2021-12-17 15:59:14 +00:00
f737236abc Move cargo/.config to root of workspace 2021-12-16 12:55:14 -05:00
40 changed files with 739 additions and 1054 deletions

3
.gitignore vendored
View File

@ -1,2 +1,5 @@
.idea .idea
target target
*peachdeploy.sh
*vpsdeploy.sh
*bindeploy.sh

705
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,5 @@ members = [
"peach-monitor", "peach-monitor",
"peach-stats", "peach-stats",
"peach-jsonrpc-server", "peach-jsonrpc-server",
"peach-probe",
"peach-dyndns-updater" "peach-dyndns-updater"
] ]

View File

@ -1,4 +0,0 @@
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
objcopy = { path ="aarch64-linux-gnu-objcopy" }
strip = { path ="aarch64-linux-gnu-strip" }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "peach-config" name = "peach-config"
version = "0.1.10" version = "0.1.15"
authors = ["Andrew Reid <gnomad@cryptolab.net>", "Max Fowler <max@mfowler.info>"] authors = ["Andrew Reid <gnomad@cryptolab.net>", "Max Fowler <max@mfowler.info>"]
edition = "2018" edition = "2018"
description = "Command line tool for installing, updating and configuring PeachCloud" description = "Command line tool for installing, updating and configuring PeachCloud"
@ -35,3 +35,5 @@ structopt = "0.3.13"
clap = "2.33.3" clap = "2.33.3"
log = "0.4" log = "0.4"
lazy_static = "1.4.0" lazy_static = "1.4.0"
peach-lib = { path = "../peach-lib" }
rpassword = "5.0"

View File

@ -8,7 +8,7 @@ dtparam=i2c_arm=on
# Apply device tree overlay to enable pull-up resistors for buttons # Apply device tree overlay to enable pull-up resistors for buttons
device_tree_overlay=overlays/mygpio.dtbo device_tree_overlay=overlays/mygpio.dtbo
kernel=vmlinuz-4.19.0-17-arm64 kernel=vmlinuz-4.19.0-18-arm64
# For details on the initramfs directive, see # For details on the initramfs directive, see
# https://www.raspberrypi.org/forums/viewtopic.php?f=63&t=10532 # https://www.raspberrypi.org/forums/viewtopic.php?f=63&t=10532
initramfs initrd.img-4.19.0-17-arm64 initramfs initrd.img-4.19.0-18-arm64

View File

@ -0,0 +1,35 @@
use crate::error::PeachConfigError;
use crate::ChangePasswordOpts;
use peach_lib::password_utils::set_new_password;
/// Utility function to set the admin password for peach-web from the command-line.
pub fn set_peach_web_password(opts: ChangePasswordOpts) -> Result<(), PeachConfigError> {
match opts.password {
// read password from CLI arg
Some(password) => {
set_new_password(&password)
.map_err(|err| PeachConfigError::ChangePasswordError { source: err })?;
println!(
"Your new password has been set for peach-web. You can login through the \
web interface with username admin."
);
Ok(())
}
// read password from tty
None => {
let pass1 = rpassword::read_password_from_tty(Some("New password: "))?;
let pass2 = rpassword::read_password_from_tty(Some("Confirm password: "))?;
if pass1 != pass2 {
Err(PeachConfigError::InvalidPassword)
} else {
set_new_password(&pass1)
.map_err(|err| PeachConfigError::ChangePasswordError { source: err })?;
println!(
"Your new password has been set for peach-web. You can login through the \
web interface with username admin."
);
Ok(())
}
}
}
}

View File

@ -1,4 +1,5 @@
#![allow(clippy::nonstandard_macro_braces)] #![allow(clippy::nonstandard_macro_braces)]
use peach_lib::error::PeachError;
pub use snafu::ResultExt; pub use snafu::ResultExt;
use snafu::Snafu; use snafu::Snafu;
@ -30,6 +31,10 @@ pub enum PeachConfigError {
}, },
#[snafu(display("Error serializing json: {}", source))] #[snafu(display("Error serializing json: {}", source))]
SerdeError { source: serde_json::Error }, SerdeError { source: serde_json::Error },
#[snafu(display("Error changing password: {}", source))]
ChangePasswordError { source: PeachError },
#[snafu(display("Entered passwords did not match. Please try again."))]
InvalidPassword,
} }
impl From<std::io::Error> for PeachConfigError { impl From<std::io::Error> for PeachConfigError {

View File

@ -1,6 +1,8 @@
mod change_password;
mod constants; mod constants;
mod error; mod error;
mod generate_manifest; mod generate_manifest;
mod set_permissions;
mod setup_networking; mod setup_networking;
mod setup_peach; mod setup_peach;
mod setup_peach_deb; mod setup_peach_deb;
@ -12,10 +14,6 @@ use log::error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use structopt::StructOpt; use structopt::StructOpt;
use crate::generate_manifest::generate_manifest;
use crate::setup_peach::setup_peach;
use crate::update::update;
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
#[structopt( #[structopt(
name = "peach-config", name = "peach-config",
@ -44,6 +42,14 @@ enum PeachConfig {
/// Updates all PeachCloud microservices /// Updates all PeachCloud microservices
#[structopt(name = "update")] #[structopt(name = "update")]
Update(UpdateOpts), Update(UpdateOpts),
/// Changes the password for the peach-web interface
#[structopt(name = "changepassword")]
ChangePassword(ChangePasswordOpts),
/// Updates file permissions on PeachCloud device
#[structopt(name = "permissions")]
SetPermissions,
} }
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
@ -76,6 +82,14 @@ pub struct UpdateOpts {
list: bool, list: bool,
} }
#[derive(StructOpt, Debug)]
pub struct ChangePasswordOpts {
/// Optional argument to specify password as CLI argument
/// if not specified, this command asks for user input for the passwords
#[structopt(short, long)]
password: Option<String>,
}
arg_enum! { arg_enum! {
/// enum options for real-time clock choices /// enum options for real-time clock choices
#[derive(Debug)] #[derive(Debug)]
@ -99,28 +113,48 @@ fn main() {
if let Some(subcommand) = opt.commands { if let Some(subcommand) = opt.commands {
match subcommand { match subcommand {
PeachConfig::Setup(cfg) => { PeachConfig::Setup(cfg) => {
match setup_peach(cfg.no_input, cfg.default_locale, cfg.i2c, cfg.rtc) { match setup_peach::setup_peach(cfg.no_input, cfg.default_locale, cfg.i2c, cfg.rtc) {
Ok(_) => {} Ok(_) => {}
Err(err) => { Err(err) => {
error!("peach-config encountered an error: {}", err) error!("peach-config encountered an error: {}", err)
} }
} }
} }
PeachConfig::Manifest => match generate_manifest() { PeachConfig::Manifest => match generate_manifest::generate_manifest() {
Ok(_) => {} Ok(_) => {}
Err(err) => { Err(err) => {
error!( error!(
"peach-config countered an error generating manifest: {}", "peach-config encountered an error generating manifest: {}",
err err
) )
} }
}, },
PeachConfig::Update(opts) => match update(opts) { PeachConfig::Update(opts) => match update::update(opts) {
Ok(_) => {} Ok(_) => {}
Err(err) => { Err(err) => {
error!("peach-config encountered an error during update: {}", err) error!("peach-config encountered an error during update: {}", err)
} }
}, },
PeachConfig::ChangePassword(opts) => {
match change_password::set_peach_web_password(opts) {
Ok(_) => {}
Err(err) => {
error!(
"peach-config encountered an error during password update: {}",
err
)
}
}
}
PeachConfig::SetPermissions => match set_permissions::set_permissions() {
Ok(_) => {}
Err(err) => {
error!(
"peach-config ecountered an error updating file permissions: {}",
err
)
}
},
} }
} }
} }

View File

@ -0,0 +1,21 @@
use crate::error::PeachConfigError;
use crate::utils::cmd;
/// All configs are stored in this folder, and should be read/writeable by peach group
/// so they can be read and written by all PeachCloud services.
pub const CONFIGS_DIR: &str = "/var/lib/peachcloud";
pub const PEACH_WEB_DIR: &str = "/usr/share/peach-web";
/// Utility function to set correct file permissions on the PeachCloud device.
/// Accidentally changing file permissions is a fairly common thing to happen,
/// so this is a useful CLI function for quickly correcting anything that may be out of order.
pub fn set_permissions() -> Result<(), PeachConfigError> {
println!("[ UPDATING FILE PERMISSIONS ON PEACHCLOUD DEVICE ]");
cmd(&["chmod", "-R", "u+rwX,g+rwX", CONFIGS_DIR])?;
cmd(&["chown", "-R", "peach", CONFIGS_DIR])?;
cmd(&["chgrp", "-R", "peach", CONFIGS_DIR])?;
cmd(&["chmod", "-R", "u+rwX,g+rwX", PEACH_WEB_DIR])?;
cmd(&["chown", "-R", "peach-web:peach", PEACH_WEB_DIR])?;
println!("[ PERMISSIONS SUCCESSFULLY UPDATED ]");
Ok(())
}

View File

@ -68,6 +68,7 @@ pub fn setup_peach(
"libssl-dev", "libssl-dev",
"nginx", "nginx",
"wget", "wget",
"dnsutils",
"-y", "-y",
])?; ])?;

View File

@ -1,4 +0,0 @@
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
objcopy = { path ="aarch64-linux-gnu-objcopy" }
strip = { path ="aarch64-linux-gnu-strip" }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "peach-dyndns-updater" name = "peach-dyndns-updater"
version = "0.1.6" version = "0.1.8"
authors = ["Max Fowler <mfowler@commoninternet.net>"] authors = ["Max Fowler <mfowler@commoninternet.net>"]
edition = "2018" edition = "2018"
description = "Sytemd timer which keeps a dynamic dns subdomain up to date with the latest device IP using nsupdate." description = "Sytemd timer which keeps a dynamic dns subdomain up to date with the latest device IP using nsupdate."

View File

@ -0,0 +1,29 @@
#!/usr/bin/env bash
# exit when any command fails
set -e
KEYFILE=/Users/notplants/.ssh/id_rsa
SERVICE=peach-dyndns-updater
# deploy
rsync -avzh --exclude target --exclude .idea --exclude .git -e "ssh -i $KEYFILE" . rust@167.99.136.83:/srv/peachcloud/automation/peach-workspace/$SERVICE/
rsync -avzh --exclude target --exclude .idea --exclude .git -e "ssh -i $KEYFILE" ~/computer/projects/peachcloud/peach-workspace/peach-lib/ rust@167.99.136.83:/srv/peachcloud/automation/peach-workspace/peach-lib/
echo "++ cross compiling on vps"
BIN_PATH=$(ssh -i $KEYFILE rust@167.99.136.83 'cd /srv/peachcloud/automation/peach-workspace/peach-dyndns-updater; /home/rust/.cargo/bin/cargo clean -p peach-lib; /home/rust/.cargo/bin/cargo build --release --target=aarch64-unknown-linux-gnu')
echo "++ copying ${BIN_PATH} to local"
rm -f target/$SERVICE
scp -i $KEYFILE rust@167.99.136.83:/srv/peachcloud/automation/peach-workspace/target/aarch64-unknown-linux-gnu/release/peach-dyndns-updater ../target/vps-bin-$SERVICE
#echo "++ cross compiling"
BINFILE="../target/vps-bin-$SERVICE"
echo $BINFILE
echo "++ build successful"
echo "++ copying to pi"
ssh -t -i $KEYFILE peach@peach.link 'mkdir -p /srv/dev/bins'
scp -i $KEYFILE $BINFILE peach@peach.link:/srv/dev/bins/$SERVICE

View File

@ -1,6 +1,5 @@
use log::info;
use peach_lib::dyndns_client::dyndns_update_ip; use peach_lib::dyndns_client::dyndns_update_ip;
use log::{info};
fn main() { fn main() {
// initalize the logger // initalize the logger

View File

@ -1,7 +1,7 @@
[package] [package]
name = "peach-jsonrpc-server" name = "peach-jsonrpc-server"
authors = ["Andrew Reid <glyph@mycelial.technology>"] authors = ["Andrew Reid <glyph@mycelial.technology>"]
version = "0.2.0" version = "0.1.0"
edition = "2021" edition = "2021"
description = "JSON-RPC over HTTP for the PeachCloud system. Provides a JSON-RPC wrapper around the stats, network and oled libraries." 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" homepage = "https://opencollective.com/peachcloud"
@ -18,10 +18,8 @@ env_logger = "0.9"
jsonrpc-core = "18" jsonrpc-core = "18"
jsonrpc-http-server = "18" jsonrpc-http-server = "18"
log = "0.4" log = "0.4"
peach-network = { path = "../peach-network", features = ["serde_support"] } miniserde = "0.1.15"
peach-stats = { path = "../peach-stats", features = ["serde_support"] } peach-stats = { path = "../peach-stats", features = ["miniserde_support"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
[dev-dependencies] [dev-dependencies]
jsonrpc-test = "18" jsonrpc-test = "18"

View File

@ -1,18 +1,12 @@
use std::fmt; use std::fmt;
use jsonrpc_core::{Error as JsonRpcError, ErrorCode}; use jsonrpc_core::{Error as JsonRpcError, ErrorCode};
use peach_network::NetworkError;
use peach_stats::StatsError; use peach_stats::StatsError;
use serde_json::Error as SerdeError;
/// Custom error type encapsulating all possible errors for a JSON-RPC server /// Custom error type encapsulating all possible errors for a JSON-RPC server
/// and associated methods. /// and associated methods.
#[derive(Debug)] #[derive(Debug)]
pub enum ServerError { pub enum JsonRpcServerError {
/// An error returned from the `peach-network` library.
Network(NetworkError),
/// Failed to serialize a data structure.
Serialize(SerdeError),
/// An error returned from the `peach-stats` library. /// An error returned from the `peach-stats` library.
Stats(StatsError), Stats(StatsError),
/// An expected JSON-RPC method parameter was not provided. /// An expected JSON-RPC method parameter was not provided.
@ -21,48 +15,32 @@ pub enum ServerError {
ParseParameter(JsonRpcError), ParseParameter(JsonRpcError),
} }
impl fmt::Display for ServerError { impl fmt::Display for JsonRpcServerError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
ServerError::ParseParameter(ref source) => { JsonRpcServerError::ParseParameter(ref source) => {
write!(f, "Failed to parse parameter: {}", source) write!(f, "Failed to parse parameter: {}", source)
} }
ServerError::MissingParameter(ref source) => { JsonRpcServerError::MissingParameter(ref source) => {
write!(f, "Missing expected parameter: {}", source) write!(f, "Missing expected parameter: {}", source)
} }
ServerError::Network(ref source) => { JsonRpcServerError::Stats(ref source) => {
write!(f, "{}", source)
}
ServerError::Serialize(ref source) => {
write!(f, "Serde serialization failure: {}", source)
}
ServerError::Stats(ref source) => {
write!(f, "{}", source) write!(f, "{}", source)
} }
} }
} }
} }
impl From<ServerError> for JsonRpcError { impl From<JsonRpcServerError> for JsonRpcError {
fn from(err: ServerError) -> Self { fn from(err: JsonRpcServerError) -> Self {
match &err { match &err {
ServerError::Network(source) => JsonRpcError { JsonRpcServerError::Stats(source) => JsonRpcError {
code: ErrorCode::ServerError(-32001), code: ErrorCode::ServerError(-32001),
message: format!("{}", source), message: format!("{}", source),
data: None, data: None,
}, },
ServerError::Stats(source) => JsonRpcError { JsonRpcServerError::MissingParameter(source) => source.clone(),
code: ErrorCode::ServerError(-32003), JsonRpcServerError::ParseParameter(source) => source.clone(),
message: format!("{}", source),
data: None,
},
ServerError::Serialize(source) => JsonRpcError {
code: ErrorCode::ServerError(-32000),
message: format!("{}", source),
data: None,
},
ServerError::MissingParameter(source) => source.clone(),
ServerError::ParseParameter(source) => source.clone(),
} }
} }
} }

View File

@ -5,332 +5,72 @@
use std::env; use std::env;
use std::result::Result; use std::result::Result;
use jsonrpc_core::{Error as RpcCoreError, IoHandler, Params, Value}; use jsonrpc_core::{IoHandler, Value};
use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder}; use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder};
use log::info; use log::info;
use peach_network::network; use miniserde::json;
use peach_stats::stats; use peach_stats::stats;
mod error; mod error;
mod params; use crate::error::JsonRpcServerError;
use crate::error::ServerError;
use crate::params::{Iface, IfaceId, IfaceIdPass, IfaceSsid, WiFi, WlanAndAp};
/// Create JSON-RPC I/O handler, add RPC methods and launch HTTP server. /// Create JSON-RPC I/O handler, add RPC methods and launch HTTP server.
pub fn run() -> Result<(), ServerError> { pub fn run() -> Result<(), JsonRpcServerError> {
info!("Starting up."); info!("Starting up.");
info!("Creating JSON-RPC I/O handler."); info!("Creating JSON-RPC I/O handler.");
let mut io = IoHandler::default(); let mut io = IoHandler::default();
io.add_method("ping", |_| async { io.add_sync_method("ping", |_| Ok(Value::String("success".to_string())));
Ok(Value::String("success".to_string()))
});
// TODO: add blocks of methods according to provided flags // TODO: add blocks of methods according to provided flags
/* PEACH-NETWORK RPC METHODS */
// get - all network rpc methods for querying state
io.add_method("available_networks", |params: Params| async move {
let parsed: Result<Iface, RpcCoreError> = params.parse();
match parsed {
Ok(i) => {
match network::available_networks(&i.iface).map_err(ServerError::Network)? {
Some(list) => {
let json_list =
serde_json::to_string(&list).map_err(ServerError::Serialize)?;
Ok(Value::String(json_list))
}
// return `Null` if no networks were found
None => Ok(Value::Null),
}
}
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
io.add_method("id", |params: Params| async move {
let parsed: Result<IfaceSsid, RpcCoreError> = params.parse();
match parsed {
Ok(i) => match network::id(&i.iface, &i.ssid).map_err(ServerError::Network)? {
Some(id) => Ok(Value::String(id)),
None => Ok(Value::Null),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
io.add_method("ip", |params: Params| async move {
let parsed: Result<Iface, RpcCoreError> = params.parse();
match parsed {
Ok(i) => match network::ip(&i.iface).map_err(ServerError::Network)? {
Some(ip) => Ok(Value::String(ip)),
None => Ok(Value::Null),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
io.add_method("rssi", |params: Params| async move {
let parsed: Result<Iface, RpcCoreError> = params.parse();
match parsed {
Ok(i) => match network::rssi(&i.iface).map_err(ServerError::Network)? {
Some(rssi) => Ok(Value::String(rssi)),
None => Ok(Value::Null),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
io.add_method("rssi_percent", |params: Params| async move {
let parsed: Result<Iface, RpcCoreError> = params.parse();
match parsed {
Ok(i) => match network::rssi_percent(&i.iface).map_err(ServerError::Network)? {
Some(rssi) => Ok(Value::String(rssi)),
None => Ok(Value::Null),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
io.add_method("saved_networks", |_| async {
let list = network::saved_networks().map_err(ServerError::Network)?;
match list {
Some(list) => {
let json_list = serde_json::to_string(&list).map_err(ServerError::Serialize)?;
Ok(Value::String(json_list))
}
None => Ok(Value::Null),
}
});
io.add_method("ssid", |params: Params| async move {
let parsed: Result<Iface, RpcCoreError> = params.parse();
match parsed {
Ok(i) => match network::ssid(&i.iface).map_err(ServerError::Network)? {
Some(ip) => Ok(Value::String(ip)),
None => Ok(Value::Null),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
io.add_method("state", |params: Params| async move {
let parsed: Result<Iface, RpcCoreError> = params.parse();
match parsed {
Ok(i) => match network::state(&i.iface).map_err(ServerError::Network)? {
Some(state) => Ok(Value::String(state)),
None => Ok(Value::Null),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
io.add_method("status", |params: Params| async move {
let parsed: Result<Iface, RpcCoreError> = params.parse();
match parsed {
Ok(i) => match network::status(&i.iface).map_err(ServerError::Network)? {
Some(status) => {
let json_status =
serde_json::to_string(&status).map_err(ServerError::Serialize)?;
Ok(Value::String(json_status))
}
None => Ok(Value::Null),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
io.add_method("traffic", |params: Params| async move {
let parsed: Result<Iface, RpcCoreError> = params.parse();
match parsed {
Ok(i) => match network::traffic(&i.iface).map_err(ServerError::Network)? {
Some(traffic) => {
let json_traffic =
serde_json::to_string(&traffic).map_err(ServerError::Serialize)?;
Ok(Value::String(json_traffic))
}
None => Ok(Value::Null),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
// set - all network rpc methods for modifying state
io.add_method("add", |params: Params| async move {
let parsed: Result<WiFi, RpcCoreError> = params.parse();
match parsed {
Ok(w) => match network::add(&w.iface, &w.ssid, &w.pass) {
Ok(_) => Ok(Value::String("success".to_string())),
Err(e) => Err(RpcCoreError::from(ServerError::Network(e))),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
io.add_method("check_iface", |params: Params| async move {
let parsed: Result<WlanAndAp, RpcCoreError> = params.parse();
match parsed {
Ok(w) => match network::check_iface(&w.wlan_iface, &w.ap_iface) {
Ok(_) => Ok(Value::String("success".to_string())),
Err(e) => Err(RpcCoreError::from(ServerError::Network(e))),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
io.add_method("connect", |params: Params| async move {
let parsed: Result<IfaceId, RpcCoreError> = params.parse();
match parsed {
Ok(i) => match network::connect(&i.id, &i.iface) {
Ok(_) => Ok(Value::String("success".to_string())),
Err(e) => Err(RpcCoreError::from(ServerError::Network(e))),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
io.add_method("delete", |params: Params| async move {
let parsed: Result<IfaceId, RpcCoreError> = params.parse();
match parsed {
Ok(i) => match network::delete(&i.id, &i.iface) {
Ok(_) => Ok(Value::String("success".to_string())),
Err(e) => Err(RpcCoreError::from(ServerError::Network(e))),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
io.add_method("disable", |params: Params| async move {
let parsed: Result<IfaceId, RpcCoreError> = params.parse();
match parsed {
Ok(i) => match network::disable(&i.id, &i.iface) {
Ok(_) => Ok(Value::String("success".to_string())),
Err(e) => Err(RpcCoreError::from(ServerError::Network(e))),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
io.add_method("disconnect", |params: Params| async move {
let parsed: Result<Iface, RpcCoreError> = params.parse();
match parsed {
Ok(i) => match network::disconnect(&i.iface) {
Ok(_) => Ok(Value::String("success".to_string())),
Err(e) => Err(RpcCoreError::from(ServerError::Network(e))),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
io.add_method("modify", |params: Params| async move {
let parsed: Result<IfaceIdPass, RpcCoreError> = params.parse();
match parsed {
Ok(i) => match network::modify(&i.iface, &i.id, &i.pass) {
Ok(_) => Ok(Value::String("success".to_string())),
Err(e) => Err(RpcCoreError::from(ServerError::Network(e))),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
io.add_method("reassociate", |params: Params| async move {
let parsed: Result<Iface, RpcCoreError> = params.parse();
match parsed {
Ok(i) => match network::reassociate(&i.iface) {
Ok(_) => Ok(Value::String("success".to_string())),
Err(e) => Err(RpcCoreError::from(ServerError::Network(e))),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
io.add_method("reconfigure", |_| async {
match network::reconfigure() {
Ok(_) => Ok(Value::String("success".to_string())),
Err(e) => Err(RpcCoreError::from(ServerError::Network(e))),
}
});
io.add_method("reconnect", |params: Params| async move {
let parsed: Result<Iface, RpcCoreError> = params.parse();
match parsed {
Ok(i) => match network::reconnect(&i.iface) {
Ok(_) => Ok(Value::String("success".to_string())),
Err(e) => Err(RpcCoreError::from(ServerError::Network(e))),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
io.add_method("save", |_| async {
match network::save() {
Ok(_) => Ok(Value::String("success".to_string())),
Err(e) => Err(RpcCoreError::from(ServerError::Network(e))),
}
});
io.add_method("start_iface_service", |params: Params| async move {
let parsed: Result<Iface, RpcCoreError> = params.parse();
match parsed {
Ok(i) => match network::start_iface_service(&i.iface) {
Ok(_) => Ok(Value::String("success".to_string())),
Err(e) => Err(RpcCoreError::from(ServerError::Network(e))),
},
Err(e) => Err(RpcCoreError::from(ServerError::MissingParameter(e))),
}
});
/* PEACH-STATS RPC METHODS */ /* PEACH-STATS RPC METHODS */
io.add_method("cpu_stats", |_| async { io.add_sync_method("cpu_stats", move |_| {
info!("Fetching CPU statistics."); info!("Fetching CPU statistics.");
let cpu = stats::cpu_stats().map_err(ServerError::Stats)?; let cpu = stats::cpu_stats().map_err(JsonRpcServerError::Stats)?;
let json_cpu = serde_json::to_string(&cpu).map_err(ServerError::Serialize)?; let json_cpu = json::to_string(&cpu);
Ok(Value::String(json_cpu)) Ok(Value::String(json_cpu))
}); });
io.add_method("cpu_stats_percent", |_| async { io.add_sync_method("cpu_stats_percent", move |_| {
info!("Fetching CPU statistics as percentages."); info!("Fetching CPU statistics as percentages.");
let cpu = stats::cpu_stats_percent().map_err(ServerError::Stats)?; let cpu = stats::cpu_stats_percent().map_err(JsonRpcServerError::Stats)?;
let json_cpu = serde_json::to_string(&cpu).map_err(ServerError::Serialize)?; let json_cpu = json::to_string(&cpu);
Ok(Value::String(json_cpu)) Ok(Value::String(json_cpu))
}); });
io.add_method("disk_usage", |_| async { io.add_sync_method("disk_usage", move |_| {
info!("Fetching disk usage statistics."); info!("Fetching disk usage statistics.");
let disks = stats::disk_usage().map_err(ServerError::Stats)?; let disks = stats::disk_usage().map_err(JsonRpcServerError::Stats)?;
let json_disks = serde_json::to_string(&disks).map_err(ServerError::Serialize)?; let json_disks = json::to_string(&disks);
Ok(Value::String(json_disks)) Ok(Value::String(json_disks))
}); });
io.add_method("load_average", |_| async { io.add_sync_method("load_average", move |_| {
info!("Fetching system load average statistics."); info!("Fetching system load average statistics.");
let avg = stats::load_average().map_err(ServerError::Stats)?; let avg = stats::load_average().map_err(JsonRpcServerError::Stats)?;
let json_avg = serde_json::to_string(&avg).map_err(ServerError::Serialize)?; let json_avg = json::to_string(&avg);
Ok(Value::String(json_avg)) Ok(Value::String(json_avg))
}); });
io.add_method("mem_stats", |_| async { io.add_sync_method("mem_stats", move |_| {
info!("Fetching current memory statistics."); info!("Fetching current memory statistics.");
let mem = stats::mem_stats().map_err(ServerError::Stats)?; let mem = stats::mem_stats().map_err(JsonRpcServerError::Stats)?;
let json_mem = serde_json::to_string(&mem).map_err(ServerError::Serialize)?; let json_mem = json::to_string(&mem);
Ok(Value::String(json_mem)) Ok(Value::String(json_mem))
}); });
io.add_method("uptime", |_| async { io.add_sync_method("uptime", move |_| {
info!("Fetching system uptime."); info!("Fetching system uptime.");
let uptime = stats::uptime().map_err(ServerError::Stats)?; let uptime = stats::uptime().map_err(JsonRpcServerError::Stats)?;
let json_uptime = serde_json::to_string(&uptime).map_err(ServerError::Serialize)?; let json_uptime = json::to_string(&uptime);
Ok(Value::String(json_uptime)) Ok(Value::String(json_uptime))
}); });
@ -366,7 +106,7 @@ mod tests {
fn rpc_success() { fn rpc_success() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_success_response", |_| async { io.add_sync_method("rpc_success_response", |_| {
Ok(Value::String("success".into())) Ok(Value::String("success".into()))
}); });
test_rpc::Rpc::from(io) test_rpc::Rpc::from(io)
@ -379,13 +119,13 @@ mod tests {
fn rpc_parse_error() { fn rpc_parse_error() {
let rpc = { let rpc = {
let mut io = IoHandler::new(); let mut io = IoHandler::new();
io.add_method("rpc_parse_error", |_| async { io.add_sync_method("rpc_parse_error", |_| {
let e = JsonRpcError { let e = JsonRpcError {
code: ErrorCode::ParseError, code: ErrorCode::ParseError,
message: String::from("Parse error"), message: String::from("Parse error"),
data: None, data: None,
}; };
Err(JsonRpcError::from(ServerError::MissingParameter(e))) Err(JsonRpcError::from(JsonRpcServerError::MissingParameter(e)))
}); });
test_rpc::Rpc::from(io) test_rpc::Rpc::from(io)
}; };

View File

@ -10,51 +10,12 @@
//! //!
//! | Method | Description | Returns | //! | Method | Description | Returns |
//! | --- | --- | --- | //! | --- | --- | --- |
//! | `ping` | Microservice status | `success` if running |
//!
//! ### Network
//!
//! Methods for **retrieving data**:
//!
//! | Method | Description | Returns |
//! | --- | --- | --- |
//! | `available_networks` | `iface` | List SSID, flags (security), frequency and signal level for all networks in range of given interface |
//! | `id` | `iface`, `ssid` | Return ID of given SSID |
//! | `ip` | `iface` | Return IP of given network interface |
//! | `rssi` | `iface` | Return average signal strength (dBm) for given interface |
//! | `rssi_percent` | `iface` | Return average signal strength (%) for given interface |
//! | `saved_networks` | | List all networks saved in wpasupplicant config |
//! | `ssid` | `iface` | Return SSID of currently-connected network for given interface |
//! | `state` | `iface` | Return state of given interface |
//! | `status` | `iface` | Return status parameters for given interface |
//! | `traffic` | `iface` | Return network traffic for given interface |
//!
//! Methods for **modifying state**:
//!
//! | Method | Parameters | Description |
//! | --- | --- | --- |
//! | `add` | `iface`, `ssid`, `pass` | Add WiFi credentials to `wpa_supplicant-<iface>.conf` |
//! | `check_iface` | `wlan_iface`, `ap_iface` | Activate WiFi access point on <ap_iface> if <wlan_iface> is active without a connection |
//! | `connect` | `id`, `iface` | Disable other networks and attempt connection with AP represented by given id |
//! | `delete` | `id`, `iface` | Remove WiFi credentials for given network id and interface |
//! | `disable` | `id`, `iface` | Disable connection with AP represented by given id |
//! | `disconnect` | `iface` | Disconnect given interface |
//! | `modify` | `id`, `iface`, `pass` | Set a new password for given network id and interface |
//! | `reassociate` | `iface` | Reassociate with current AP for given interface |
//! | `reconfigure` | | Force wpa_supplicant to re-read its configuration file |
//! | `reconnect` | `iface` | Disconnect and reconnect given interface |
//! | `save` | | Save configuration changes to `wpa_supplicant-wlan0.conf` |
//! | `start_iface_server` | `iface` | Start the `systemd` service for the given interface |
//!
//! ### System Statistics
//!
//! | Method | Description | Returns |
//! | --- | --- | --- |
//! | `cpu_stats` | CPU statistics | `user`, `system`, `nice`, `idle` | //! | `cpu_stats` | CPU statistics | `user`, `system`, `nice`, `idle` |
//! | `cpu_stats_percent` | CPU statistics as percentages | `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` | //! | `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` | //! | `load_average` | Load average statistics | `one`, `five`, `fifteen` |
//! | `mem_stats` | Memory statistics | `total`, `free`, `used` | //! | `mem_stats` | Memory statistics | `total`, `free`, `used` |
//! | `ping` | Microservice status | `success` if running |
//! | `uptime` | System uptime | `secs` | //! | `uptime` | System uptime | `secs` |
use std::process; use std::process;

View File

@ -1,54 +0,0 @@
//! Data structures for parsing JSON-RPC method parameters.
use serde::Deserialize;
// why do we have multiple structs when we could rather combine them by using
// `Option<String>` for some fields? simply because it's easier to handle the
// parsed parameters in our json-rpc server methods. we don't have to check
// if a given field is `Some` or `None` before passing it into the relevant
// function.
/// Network interface name.
#[derive(Debug, Deserialize)]
pub struct Iface {
pub iface: String,
}
/// Network interface name and network identifier.
#[derive(Debug, Deserialize)]
pub struct IfaceId {
pub iface: String,
pub id: String,
}
/// Network interface name, network identifier and password.
#[derive(Debug, Deserialize)]
pub struct IfaceIdPass {
pub iface: String,
pub id: String,
pub pass: String,
}
/// Network interface name and network SSID.
#[derive(Debug, Deserialize)]
pub struct IfaceSsid {
pub iface: String,
pub ssid: String,
}
/// Wireless interface (for which the WiFi credentials will be added), SSID
/// and password for a wireless access point.
#[derive(Debug, Deserialize)]
pub struct WiFi {
pub iface: String,
pub ssid: String,
pub pass: String,
}
/// Wireles network interface and local Access Point network interface (the
/// interface on which we deloy or own AP).
#[derive(Debug, Deserialize)]
pub struct WlanAndAp {
pub wlan_iface: String,
pub ap_iface: String,
}

View File

@ -1,6 +1,6 @@
[package] [package]
name = "peach-lib" name = "peach-lib"
version = "1.3.1" version = "1.3.2"
authors = ["Andrew Reid <glyph@mycelial.technology>"] authors = ["Andrew Reid <glyph@mycelial.technology>"]
edition = "2018" edition = "2018"
@ -13,7 +13,7 @@ jsonrpc-core = "8.0.1"
log = "0.4" log = "0.4"
nanorand = "0.6.1" nanorand = "0.6.1"
regex = "1" regex = "1"
rust-crypto = "0.2.36"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
serde_yaml = "0.8" serde_yaml = "0.8"
sha3 = "0.10.0"

View File

@ -17,6 +17,10 @@ pub const YAML_PATH: &str = "/var/lib/peachcloud/config.yml";
// lock file (used to avoid race conditions during config reading & writing) // lock file (used to avoid race conditions during config reading & writing)
pub const LOCK_FILE_PATH: &str = "/var/lib/peachcloud/config.lock"; pub const LOCK_FILE_PATH: &str = "/var/lib/peachcloud/config.lock";
// default values
pub const DEFAULT_DYN_SERVER_ADDRESS: &str = "http://dynserver.dyn.peachcloud.org";
pub const DEFAULT_DYN_NAMESERVER: &str = "ns.peachcloud.org";
// we make use of Serde default values in order to make PeachCloud // we make use of Serde default values in order to make PeachCloud
// robust and keep running even with a not fully complete config.yml // robust and keep running even with a not fully complete config.yml
// main type which represents all peachcloud configurations // main type which represents all peachcloud configurations
@ -29,6 +33,10 @@ pub struct PeachConfig {
#[serde(default)] #[serde(default)]
pub dyn_dns_server_address: String, pub dyn_dns_server_address: String,
#[serde(default)] #[serde(default)]
pub dyn_use_custom_server: bool,
#[serde(default)]
pub dyn_nameserver: String,
#[serde(default)]
pub dyn_tsig_key_path: String, pub dyn_tsig_key_path: String,
#[serde(default)] // default is false #[serde(default)] // default is false
pub dyn_enabled: bool, pub dyn_enabled: bool,
@ -63,20 +71,19 @@ fn save_peach_config(peach_config: PeachConfig) -> Result<PeachConfig, PeachErro
pub fn load_peach_config() -> Result<PeachConfig, PeachError> { pub fn load_peach_config() -> Result<PeachConfig, PeachError> {
let peach_config_exists = std::path::Path::new(YAML_PATH).exists(); let peach_config_exists = std::path::Path::new(YAML_PATH).exists();
let peach_config: PeachConfig; let peach_config: PeachConfig = if !peach_config_exists {
PeachConfig {
// if this is the first time loading peach_config, we can create a default here
if !peach_config_exists {
peach_config = PeachConfig {
external_domain: "".to_string(), external_domain: "".to_string(),
dyn_domain: "".to_string(), dyn_domain: "".to_string(),
dyn_dns_server_address: "".to_string(), dyn_dns_server_address: DEFAULT_DYN_SERVER_ADDRESS.to_string(),
dyn_use_custom_server: false,
dyn_nameserver: DEFAULT_DYN_NAMESERVER.to_string(),
dyn_tsig_key_path: "".to_string(), dyn_tsig_key_path: "".to_string(),
dyn_enabled: false, dyn_enabled: false,
ssb_admin_ids: Vec::new(), ssb_admin_ids: Vec::new(),
admin_password_hash: "".to_string(), admin_password_hash: "".to_string(),
temporary_password_hash: "".to_string(), temporary_password_hash: "".to_string(),
}; }
} }
// otherwise we load peach config from disk // otherwise we load peach config from disk
else { else {
@ -84,8 +91,8 @@ pub fn load_peach_config() -> Result<PeachConfig, PeachError> {
source, source,
path: YAML_PATH.to_string(), path: YAML_PATH.to_string(),
})?; })?;
peach_config = serde_yaml::from_str(&contents)?; serde_yaml::from_str(&contents)?
} };
Ok(peach_config) Ok(peach_config)
} }
@ -122,6 +129,18 @@ pub fn get_peachcloud_domain() -> Result<Option<String>, PeachError> {
} }
} }
pub fn get_dyndns_server_address() -> Result<String, PeachError> {
let peach_config = load_peach_config()?;
// if the user is using a custom dyn server then load the address from the config
if peach_config.dyn_use_custom_server {
Ok(peach_config.dyn_dns_server_address)
}
// otherwise hardcode the address
else {
Ok(DEFAULT_DYN_SERVER_ADDRESS.to_string())
}
}
pub fn set_dyndns_enabled_value(enabled_value: bool) -> Result<PeachConfig, PeachError> { pub fn set_dyndns_enabled_value(enabled_value: bool) -> Result<PeachConfig, PeachError> {
let mut peach_config = load_peach_config()?; let mut peach_config = load_peach_config()?;
peach_config.dyn_enabled = enabled_value; peach_config.dyn_enabled = enabled_value;

View File

@ -9,13 +9,8 @@
//! //!
//! The domain for dyndns updates is stored in /var/lib/peachcloud/config.yml //! 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 //! The tsig key for authenticating the updates is stored in /var/lib/peachcloud/peach-dyndns/tsig.key
use std::{ use std::ffi::OsStr;
fs, use std::{fs, fs::OpenOptions, io::Write, process::Command, str::FromStr};
fs::OpenOptions,
io::Write,
process::{Command, Stdio},
str::FromStr,
};
use chrono::prelude::*; use chrono::prelude::*;
use jsonrpc_client_core::{expand_params, jsonrpc_client}; use jsonrpc_client_core::{expand_params, jsonrpc_client};
@ -23,13 +18,10 @@ use jsonrpc_client_http::HttpTransport;
use log::{debug, info}; use log::{debug, info};
use regex::Regex; use regex::Regex;
use crate::{ use crate::config_manager::get_dyndns_server_address;
config_manager::{load_peach_config, set_peach_dyndns_config}, use crate::{config_manager, error::PeachError};
error::PeachError,
};
/// constants for dyndns configuration /// constants for dyndns configuration
pub const PEACH_DYNDNS_URL: &str = "http://dynserver.dyn.peachcloud.org";
pub const TSIG_KEY_PATH: &str = "/var/lib/peachcloud/peach-dyndns/tsig.key"; pub const TSIG_KEY_PATH: &str = "/var/lib/peachcloud/peach-dyndns/tsig.key";
pub const PEACH_DYNDNS_CONFIG_PATH: &str = "/var/lib/peachcloud/peach-dyndns"; pub const PEACH_DYNDNS_CONFIG_PATH: &str = "/var/lib/peachcloud/peach-dyndns";
pub const DYNDNS_LOG_PATH: &str = "/var/lib/peachcloud/peach-dyndns/latest_result.log"; pub const DYNDNS_LOG_PATH: &str = "/var/lib/peachcloud/peach-dyndns/latest_result.log";
@ -62,9 +54,10 @@ pub fn save_dyndns_key(key: &str) -> Result<(), PeachError> {
pub fn register_domain(domain: &str) -> std::result::Result<String, PeachError> { pub fn register_domain(domain: &str) -> std::result::Result<String, PeachError> {
debug!("Creating HTTP transport for dyndns client."); debug!("Creating HTTP transport for dyndns client.");
let transport = HttpTransport::new().standalone()?; let transport = HttpTransport::new().standalone()?;
let http_server = PEACH_DYNDNS_URL; let http_server = get_dyndns_server_address()?;
debug!("Creating HTTP transport handle on {}.", http_server); info!("Using dyndns http server address: {:?}", http_server);
let transport_handle = transport.handle(http_server)?; debug!("Creating HTTP transport handle on {}.", &http_server);
let transport_handle = transport.handle(&http_server)?;
info!("Creating client for peach-dyndns service."); info!("Creating client for peach-dyndns service.");
let mut client = PeachDynDnsClient::new(transport_handle); let mut client = PeachDynDnsClient::new(transport_handle);
@ -73,7 +66,8 @@ pub fn register_domain(domain: &str) -> std::result::Result<String, PeachError>
// save new TSIG key // save new TSIG key
save_dyndns_key(&key)?; save_dyndns_key(&key)?;
// save new configuration values // save new configuration values
let set_config_result = set_peach_dyndns_config(domain, PEACH_DYNDNS_URL, TSIG_KEY_PATH, true); let set_config_result =
config_manager::set_peach_dyndns_config(domain, &http_server, TSIG_KEY_PATH, true);
match set_config_result { match set_config_result {
Ok(_) => { Ok(_) => {
let response = "success".to_string(); let response = "success".to_string();
@ -87,9 +81,9 @@ pub fn register_domain(domain: &str) -> std::result::Result<String, PeachError>
pub fn is_domain_available(domain: &str) -> std::result::Result<bool, PeachError> { pub fn is_domain_available(domain: &str) -> std::result::Result<bool, PeachError> {
debug!("Creating HTTP transport for dyndns client."); debug!("Creating HTTP transport for dyndns client.");
let transport = HttpTransport::new().standalone()?; let transport = HttpTransport::new().standalone()?;
let http_server = PEACH_DYNDNS_URL; let http_server = get_dyndns_server_address()?;
debug!("Creating HTTP transport handle on {}.", http_server); debug!("Creating HTTP transport handle on {}.", &http_server);
let transport_handle = transport.handle(http_server)?; let transport_handle = transport.handle(&http_server)?;
info!("Creating client for peach_network service."); info!("Creating client for peach_network service.");
let mut client = PeachDynDnsClient::new(transport_handle); let mut client = PeachDynDnsClient::new(transport_handle);
@ -113,31 +107,31 @@ fn get_public_ip_address() -> Result<String, PeachError> {
/// Reads dyndns configurations from config.yml /// Reads dyndns configurations from config.yml
/// and then uses nsupdate to update the IP address for the configured domain /// and then uses nsupdate to update the IP address for the configured domain
pub fn dyndns_update_ip() -> Result<bool, PeachError> { pub fn dyndns_update_ip() -> Result<bool, PeachError> {
info!("Running dyndns_update_ip"); let peach_config = config_manager::load_peach_config()?;
let peach_config = load_peach_config()?;
info!( info!(
"Using config: "Using config:
dyn_tsig_key_path: {:?} dyn_tsig_key_path: {:?}
dyn_domain: {:?} dyn_domain: {:?}
dyn_dns_server_address: {:?} dyn_dns_server_address: {:?}
dyn_enabled: {:?} dyn_enabled: {:?}
dyn_nameserver: {:?}
", ",
peach_config.dyn_tsig_key_path, peach_config.dyn_tsig_key_path,
peach_config.dyn_domain, peach_config.dyn_domain,
peach_config.dyn_dns_server_address, peach_config.dyn_dns_server_address,
peach_config.dyn_enabled, peach_config.dyn_enabled,
peach_config.dyn_nameserver,
); );
if !peach_config.dyn_enabled { if !peach_config.dyn_enabled {
info!("dyndns is not enabled, not updating"); info!("dyndns is not enabled, not updating");
Ok(false) Ok(false)
} else { } else {
// call nsupdate passing appropriate configs // call nsupdate passing appropriate configs
let mut nsupdate_command = Command::new("/usr/bin/nsupdate") let mut nsupdate_command = Command::new("/usr/bin/nsupdate");
nsupdate_command
.arg("-k") .arg("-k")
.arg(&peach_config.dyn_tsig_key_path) .arg(&peach_config.dyn_tsig_key_path)
.arg("-v") .arg("-v");
.stdin(Stdio::piped())
.spawn()?;
// pass nsupdate commands via stdin // pass nsupdate commands via stdin
let public_ip_address = get_public_ip_address()?; let public_ip_address = get_public_ip_address()?;
info!("found public ip address: {}", public_ip_address); info!("found public ip address: {}", public_ip_address);
@ -148,20 +142,20 @@ pub fn dyndns_update_ip() -> Result<bool, PeachError> {
update delete {DOMAIN} A update delete {DOMAIN} A
update add {DOMAIN} 30 A {PUBLIC_IP_ADDRESS} update add {DOMAIN} 30 A {PUBLIC_IP_ADDRESS}
send", send",
NAMESERVER = "ns.peachcloud.org", NAMESERVER = peach_config.dyn_nameserver,
ZONE = peach_config.dyn_domain, ZONE = peach_config.dyn_domain,
DOMAIN = peach_config.dyn_domain, DOMAIN = peach_config.dyn_domain,
PUBLIC_IP_ADDRESS = public_ip_address, PUBLIC_IP_ADDRESS = public_ip_address,
); );
let mut nsupdate_stdin = nsupdate_command.stdin.take().ok_or(PeachError::NsUpdate { info!("ns_commands: {:?}", ns_commands);
msg: "unable to capture stdin handle for `nsupdate` command".to_string(), info!("creating nsupdate temp file");
})?; let temp_file_path = "/var/lib/peachcloud/nsupdate.sh";
write!(nsupdate_stdin, "{}", ns_commands).map_err(|source| PeachError::Write { // write ns_commands to temp_file
source, fs::write(temp_file_path, ns_commands)?;
path: peach_config.dyn_tsig_key_path.to_string(), nsupdate_command.arg(temp_file_path);
})?; let nsupdate_output = nsupdate_command.output()?;
let nsupdate_output = nsupdate_command.wait_with_output()?; let args: Vec<&OsStr> = nsupdate_command.get_args().collect();
info!("nsupdate output: {:?}", nsupdate_output); info!("nsupdate command: {:?}", args);
// We only return a successful result if nsupdate was successful // We only return a successful result if nsupdate was successful
if nsupdate_output.status.success() { if nsupdate_output.status.success() {
info!("nsupdate succeeded, returning ok"); info!("nsupdate succeeded, returning ok");
@ -204,7 +198,7 @@ pub fn get_num_seconds_since_successful_dns_update() -> Result<Option<i64>, Peac
})?; })?;
// replace newline if found // replace newline if found
// TODO: maybe we can use `.trim()` instead // TODO: maybe we can use `.trim()` instead
let contents = contents.replace("\n", ""); let contents = contents.replace('\n', "");
// TODO: consider adding additional context? // TODO: consider adding additional context?
let time_ran_dt = DateTime::parse_from_rfc3339(&contents).map_err(|source| { let time_ran_dt = DateTime::parse_from_rfc3339(&contents).map_err(|source| {
PeachError::ParseDateTime { PeachError::ParseDateTime {
@ -223,20 +217,15 @@ pub fn get_num_seconds_since_successful_dns_update() -> Result<Option<i64>, Peac
/// and has successfully run recently (in the last six minutes) /// and has successfully run recently (in the last six minutes)
pub fn is_dns_updater_online() -> Result<bool, PeachError> { pub fn is_dns_updater_online() -> Result<bool, PeachError> {
// first check if it is enabled in peach-config // first check if it is enabled in peach-config
let peach_config = load_peach_config()?; let peach_config = config_manager::load_peach_config()?;
let is_enabled = peach_config.dyn_enabled; let is_enabled = peach_config.dyn_enabled;
// then check if it has successfully run within the last 6 minutes (60*6 seconds) // then check if it has successfully run within the last 6 minutes (60*6 seconds)
let num_seconds_since_successful_update = get_num_seconds_since_successful_dns_update()?; let num_seconds_since_successful_update = get_num_seconds_since_successful_dns_update()?;
let ran_recently: bool; let ran_recently: bool = match num_seconds_since_successful_update {
match num_seconds_since_successful_update { Some(seconds) => seconds < (60 * 6),
Some(seconds) => {
ran_recently = seconds < (60 * 6);
}
// if the value is None, then the last time it ran successfully is unknown // if the value is None, then the last time it ran successfully is unknown
None => { None => false,
ran_recently = false; };
}
}
// debug log // debug log
info!("is_dyndns_enabled: {:?}", is_enabled); info!("is_dyndns_enabled: {:?}", is_enabled);
info!("dyndns_ran_recently: {:?}", ran_recently); info!("dyndns_ran_recently: {:?}", ran_recently);
@ -258,11 +247,10 @@ pub fn get_dyndns_subdomain(dyndns_full_domain: &str) -> Option<String> {
} }
// helper function which checks if a dyndns domain is new // helper function which checks if a dyndns domain is new
pub fn check_is_new_dyndns_domain(dyndns_full_domain: &str) -> bool { pub fn check_is_new_dyndns_domain(dyndns_full_domain: &str) -> Result<bool, PeachError> {
// TODO: return `Result<bool, PeachError>` and replace `unwrap` with `?` operator let peach_config = config_manager::load_peach_config()?;
let peach_config = load_peach_config().unwrap();
let previous_dyndns_domain = peach_config.dyn_domain; let previous_dyndns_domain = peach_config.dyn_domain;
dyndns_full_domain != previous_dyndns_domain Ok(dyndns_full_domain != previous_dyndns_domain)
} }
jsonrpc_client!(pub struct PeachDynDnsClient { jsonrpc_client!(pub struct PeachDynDnsClient {

View File

@ -1,5 +1,5 @@
use crypto::{digest::Digest, sha3::Sha3};
use nanorand::{Rng, WyRand}; use nanorand::{Rng, WyRand};
use sha3::{Digest, Sha3_256};
use crate::{config_manager, error::PeachError, sbot_client}; use crate::{config_manager, error::PeachError, sbot_client};
@ -37,9 +37,13 @@ pub fn set_new_password(new_password: &str) -> Result<(), PeachError> {
/// Creates a hash from a password string /// Creates a hash from a password string
pub fn hash_password(password: &str) -> String { pub fn hash_password(password: &str) -> String {
let mut hasher = Sha3::sha3_256(); let mut hasher = Sha3_256::new();
hasher.input_str(password); // write input message
hasher.result_str() hasher.update(password);
// read hash digest
let result = hasher.finalize();
// convert `u8` to `String`
result[0].to_string()
} }
/// Sets a new temporary password for the admin user /// Sets a new temporary password for the admin user

View File

@ -1,4 +0,0 @@
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
objcopy = { path ="aarch64-linux-gnu-objcopy" }
strip = { path ="aarch64-linux-gnu-strip" }

View File

@ -1,4 +0,0 @@
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
objcopy = { path ="aarch64-linux-gnu-objcopy" }
strip = { path ="aarch64-linux-gnu-strip" }

View File

@ -1,4 +0,0 @@
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
objcopy = { path ="aarch64-linux-gnu-objcopy" }
strip = { path ="aarch64-linux-gnu-strip" }

View File

@ -1,4 +0,0 @@
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
objcopy = { path ="aarch64-linux-gnu-objcopy" }
strip = { path ="aarch64-linux-gnu-strip" }

View File

@ -1,4 +0,0 @@
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
objcopy = { path ="aarch64-linux-gnu-objcopy" }
strip = { path ="aarch64-linux-gnu-strip" }

View File

@ -1,4 +0,0 @@
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
objcopy = { path ="aarch64-linux-gnu-objcopy" }
strip = { path ="aarch64-linux-gnu-strip" }

View File

@ -1,4 +0,0 @@
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
objcopy = { path ="aarch64-linux-gnu-objcopy" }
strip = { path ="aarch64-linux-gnu-strip" }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "peach-web" name = "peach-web"
version = "0.4.12" version = "0.4.17"
authors = ["Andrew Reid <gnomad@cryptolab.net>"] authors = ["Andrew Reid <gnomad@cryptolab.net>"]
edition = "2018" edition = "2018"
description = "peach-web is a web application which provides a web interface for monitoring and interacting with the PeachCloud device. This allows administration of the single-board computer (ie. Raspberry Pi) running PeachCloud, as well as the ssb-server and related plugins." description = "peach-web is a web application which provides a web interface for monitoring and interacting with the PeachCloud device. This allows administration of the single-board computer (ie. Raspberry Pi) running PeachCloud, as well as the ssb-server and related plugins."
@ -21,6 +21,7 @@ maintainer-scripts="debian"
systemd-units = { unit-name = "peach-web" } systemd-units = { unit-name = "peach-web" }
assets = [ assets = [
["target/release/peach-web", "/usr/bin/", "755"], ["target/release/peach-web", "/usr/bin/", "755"],
["Rocket.toml", "/usr/share/peach-web/Rocket.toml", "644"],
["templates/**/*", "/usr/share/peach-web/templates/", "644"], ["templates/**/*", "/usr/share/peach-web/templates/", "644"],
["static/*", "/usr/share/peach-web/static/", "644"], ["static/*", "/usr/share/peach-web/static/", "644"],
["static/css/*", "/usr/share/peach-web/static/css/", "644"], ["static/css/*", "/usr/share/peach-web/static/css/", "644"],
@ -40,6 +41,8 @@ log = "0.4"
nest = "1.0.0" nest = "1.0.0"
openssl = { version = "0.10", features = ["vendored"] } openssl = { version = "0.10", features = ["vendored"] }
peach-lib = { path = "../peach-lib" } peach-lib = { path = "../peach-lib" }
peach-network = { path = "../peach-network", features = ["serde_support"] }
peach-stats = { path = "../peach-stats", features = ["serde_support"] }
percent-encoding = "2.1.0" percent-encoding = "2.1.0"
regex = "1" regex = "1"
rocket = { version = "0.5.0-rc.1", features = ["json", "secrets"] } rocket = { version = "0.5.0-rc.1", features = ["json", "secrets"] }

View File

@ -97,6 +97,20 @@ Remove configuration files (not removed with `apt-get remove`):
`peach-web` is built on the Rocket webserver and Tera templating engine. It presents a web interface for interacting with the device. HTML is rendered server-side. Request handlers call JSON-RPC microservices and serve HTML and assets. A JSON API is exposed for remote calls and dynamic client-side content updates (via plain JavaScript following unobstructive design principles). Each Tera template is passed a context object. In the case of Rust, this object is a `struct` and must implement `Serialize`. The fields of the context object are available in the context of the template to be rendered. `peach-web` is built on the Rocket webserver and Tera templating engine. It presents a web interface for interacting with the device. HTML is rendered server-side. Request handlers call JSON-RPC microservices and serve HTML and assets. A JSON API is exposed for remote calls and dynamic client-side content updates (via plain JavaScript following unobstructive design principles). Each Tera template is passed a context object. In the case of Rust, this object is a `struct` and must implement `Serialize`. The fields of the context object are available in the context of the template to be rendered.
### Configuration
Configuration variables are stored in /var/lib/peachcloud/config.yml.
Peach-web also updates this file when changes are made to configurations via
the web interface. peach-web has no database, so all configurations are stored in this file.
#### Dynamic DNS Configuration
Most users will want to use the default PeachCloud dynamic dns server.
If the config dyn_use_custom_server=false, then default values will be used.
If the config dyn_use_custom_server=true, then a value must also be set for dyn_dns_server_address (e.g. "http://peachdynserver.commoninternet.net").
This value is the URL of the instance of peach-dyndns-server that requests will be sent to for domain registration.
Using a custom value can here can be useful for testing.
### Licensing ### Licensing
AGPL-3.0 AGPL-3.0

View File

@ -1,3 +1,6 @@
[default]
secret_key = "VYVUDivXvu8g6llxeJd9F92pMfocml5xl/Jjv5Sk4yw="
[development] [development]
template_dir = "templates/" template_dir = "templates/"
disable_auth = true disable_auth = true

View File

@ -5,54 +5,18 @@ set -e
adduser --quiet --system peach-web adduser --quiet --system peach-web
usermod -g peach peach-web usermod -g peach peach-web
# create secret passwords folder if it doesn't already exist
mkdir -p /var/lib/peachcloud/passwords
chown -R peach-web:peach /var/lib/peachcloud/passwords
chmod -R u+rwX,go+rX,go-w /var/lib/peachcloud/passwords
# create nginx config # create nginx config
cat <<EOF > /etc/nginx/sites-enabled/default cat <<EOF > /etc/nginx/sites-enabled/default
server { server {
listen 80 default_server; listen 80 default_server;
server_name peach.local www.peach.local; server_name peach.local www.peach.local;
# nginx authentication
auth_basic "If you have forgotten your password visit: http://peach.local/send_password_reset/";
auth_basic_user_file /var/lib/peachcloud/passwords/htpasswd;
# remove trailing slash if found # remove trailing slash if found
rewrite ^/(.*)/$ /$1 permanent; rewrite ^/(.*)/$ /$1 permanent;
location / { location / {
proxy_pass http://127.0.0.1:3000; proxy_pass http://127.0.0.1:3000;
} }
# public routes
location /send_password_reset {
auth_basic off;
proxy_pass http://127.0.0.1:3000;
}
location /reset_password {
auth_basic off;
proxy_pass http://127.0.0.1:3000;
}
location /public/ {
auth_basic off;
proxy_pass http://127.0.0.1:3000;
}
location /js/ {
auth_basic off;
proxy_pass http://127.0.0.1:3000;
}
location /css/ {
auth_basic off;
proxy_pass http://127.0.0.1:3000;
}
location /icons/ {
auth_basic off;
proxy_pass http://127.0.0.1:3000;
}
} }
EOF EOF

View File

@ -40,7 +40,7 @@ pub fn save_dns_configuration(dns_form: DnsForm) -> Result<(), PeachWebError> {
if dns_form.enable_dyndns { if dns_form.enable_dyndns {
let full_dynamic_domain = get_full_dynamic_domain(&dns_form.dynamic_domain); let full_dynamic_domain = get_full_dynamic_domain(&dns_form.dynamic_domain);
// check if this is a new domain or if its already registered // check if this is a new domain or if its already registered
let is_new_domain = check_is_new_dyndns_domain(&full_dynamic_domain); let is_new_domain = check_is_new_dyndns_domain(&full_dynamic_domain)?;
if is_new_domain { if is_new_domain {
match dyndns_client::register_domain(&full_dynamic_domain) { match dyndns_client::register_domain(&full_dynamic_domain) {
Ok(_) => { Ok(_) => {

View File

@ -3,8 +3,8 @@ use rocket::{
get, post, get, post,
request::FlashMessage, request::FlashMessage,
response::{Flash, Redirect}, response::{Flash, Redirect},
serde::json::Value,
}; };
use rocket_dyn_templates::Template; use rocket_dyn_templates::Template;
use serde::Serialize; use serde::Serialize;
use std::{ use std::{
@ -12,13 +12,16 @@ use std::{
process::{Command, Output}, process::{Command, Output},
}; };
use peach_lib::config_manager::load_peach_config; use peach_lib::{
use peach_lib::stats_client::{CpuStatPercentages, DiskUsage, LoadAverage, MemStat}; config_manager::load_peach_config, dyndns_client, network_client, oled_client, sbot_client,
use peach_lib::{dyndns_client, network_client, oled_client, sbot_client, stats_client}; };
use peach_stats::{
stats,
stats::{CpuStatPercentages, DiskUsage, LoadAverage, MemStat},
};
use crate::routes::authentication::Authenticated; use crate::routes::authentication::Authenticated;
use crate::utils::build_json_response; use crate::utils::build_json_response;
use rocket::serde::json::Value;
// HELPERS AND ROUTES FOR /status // HELPERS AND ROUTES FOR /status
@ -34,7 +37,6 @@ pub struct StatusContext {
pub mem_stats: Option<MemStat>, pub mem_stats: Option<MemStat>,
pub network_ping: String, pub network_ping: String,
pub oled_ping: String, pub oled_ping: String,
pub stats_ping: String,
pub dyndns_enabled: bool, pub dyndns_enabled: bool,
pub dyndns_is_online: bool, pub dyndns_is_online: bool,
pub config_is_valid: bool, pub config_is_valid: bool,
@ -46,9 +48,12 @@ pub struct StatusContext {
impl StatusContext { impl StatusContext {
pub fn build() -> StatusContext { pub fn build() -> StatusContext {
// convert result to Option<CpuStatPercentages>, discard any error // convert result to Option<CpuStatPercentages>, discard any error
let cpu_stat_percent = stats_client::cpu_stats_percent().ok(); let cpu_stat_percent = stats::cpu_stats_percent().ok();
let load_average = stats_client::load_average().ok(); let load_average = stats::load_average().ok();
let mem_stats = stats_client::mem_stats().ok(); let mem_stats = stats::mem_stats().ok();
// TODO: add `wpa_supplicant_status` to peach_network to replace this ping call
// instead of: "is the network json-rpc server running?", we want to ask:
// "is the wpa_supplicant systemd service functioning correctly?"
let network_ping = match network_client::ping() { let network_ping = match network_client::ping() {
Ok(_) => "ONLINE".to_string(), Ok(_) => "ONLINE".to_string(),
Err(_) => "OFFLINE".to_string(), Err(_) => "OFFLINE".to_string(),
@ -57,22 +62,21 @@ impl StatusContext {
Ok(_) => "ONLINE".to_string(), Ok(_) => "ONLINE".to_string(),
Err(_) => "OFFLINE".to_string(), Err(_) => "OFFLINE".to_string(),
}; };
let stats_ping = match stats_client::ping() {
Ok(_) => "ONLINE".to_string(), let uptime = match stats::uptime() {
Err(_) => "OFFLINE".to_string(), Ok(secs) => {
}; let uptime_mins = secs / 60;
let uptime = match stats_client::uptime() { uptime_mins.to_string()
Ok(mins) => mins, }
Err(_) => "Unavailable".to_string(), Err(_) => "Unavailable".to_string(),
}; };
// parse the uptime string to a signed integer (for math)
let uptime_parsed = uptime.parse::<i32>().ok();
// serialize disk usage data into Vec<DiskUsage> // serialize disk usage data into Vec<DiskUsage>
let disk_usage_stats = match stats_client::disk_usage() { let disk_usage_stats = match stats::disk_usage() {
Ok(disks) => { Ok(disks) => disks,
let partitions: Vec<DiskUsage> = serde_json::from_str(disks.as_str())
.expect("Failed to deserialize disk_usage response");
partitions
}
Err(_) => Vec::new(), Err(_) => Vec::new(),
}; };
@ -84,9 +88,6 @@ impl StatusContext {
} }
} }
// parse the uptime string to a signed integer (for math)
let uptime_parsed = uptime.parse::<i32>().ok();
// dyndns_is_online & config_is_valid // dyndns_is_online & config_is_valid
let dyndns_enabled: bool; let dyndns_enabled: bool;
let dyndns_is_online: bool; let dyndns_is_online: bool;
@ -139,7 +140,6 @@ impl StatusContext {
mem_stats, mem_stats,
network_ping, network_ping,
oled_ping, oled_ping,
stats_ping,
dyndns_enabled, dyndns_enabled,
dyndns_is_online, dyndns_is_online,
config_is_valid, config_is_valid,

View File

@ -2,25 +2,77 @@ use rocket::{get, request::FlashMessage};
use rocket_dyn_templates::Template; use rocket_dyn_templates::Template;
use serde::Serialize; use serde::Serialize;
use peach_lib::network_client; use peach_network::{
use peach_lib::stats_client::Traffic; network,
network::{Status, Traffic},
};
use crate::routes::authentication::Authenticated; use crate::routes::authentication::Authenticated;
// HELPERS AND ROUTES FOR /status/network // HELPERS AND ROUTES FOR /status/network
#[derive(Debug, Serialize)]
pub struct IfaceTraffic {
pub rx: u64,
pub rx_unit: Option<String>,
pub tx: u64,
pub tx_unit: Option<String>,
}
impl IfaceTraffic {
fn default() -> Self {
IfaceTraffic {
rx: 0,
rx_unit: None,
tx: 0,
tx_unit: None,
}
}
}
fn convert_traffic(traffic: Traffic) -> Option<IfaceTraffic> {
let mut t = IfaceTraffic::default();
// modify traffic values & assign measurement units
// based on received and transmitted values.
// if received > 999 MB, convert it to GB
if traffic.received > 1_047_527_424 {
t.rx = traffic.received / 1_073_741_824;
t.rx_unit = Some("GB".to_string());
} else if traffic.received > 0 {
// otherwise, convert it to MB
t.rx = (traffic.received / 1024) / 1024;
t.rx_unit = Some("MB".to_string());
} else {
t.rx = 0;
t.rx_unit = Some("MB".to_string());
}
if traffic.transmitted > 1_047_527_424 {
t.tx = traffic.transmitted / 1_073_741_824;
t.tx_unit = Some("GB".to_string());
} else if traffic.transmitted > 0 {
t.tx = (traffic.transmitted / 1024) / 1024;
t.tx_unit = Some("MB".to_string());
} else {
t.tx = 0;
t.tx_unit = Some("MB".to_string());
}
Some(t)
}
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct NetworkContext { pub struct NetworkContext {
pub ap_ip: String, pub ap_ip: String,
pub ap_ssid: String, pub ap_ssid: String,
pub ap_state: String, pub ap_state: String,
pub ap_traffic: Option<Traffic>, pub ap_traffic: Option<IfaceTraffic>,
pub wlan_ip: String, pub wlan_ip: String,
pub wlan_rssi: Option<String>, pub wlan_rssi: Option<String>,
pub wlan_ssid: String, pub wlan_ssid: String,
pub wlan_state: String, pub wlan_state: String,
pub wlan_status: String, pub wlan_status: Option<Status>,
pub wlan_traffic: Option<Traffic>, pub wlan_traffic: Option<IfaceTraffic>,
pub flash_name: Option<String>, pub flash_name: Option<String>,
pub flash_msg: Option<String>, pub flash_msg: Option<String>,
// page title for header in navbar // page title for header in navbar
@ -31,101 +83,47 @@ pub struct NetworkContext {
impl NetworkContext { impl NetworkContext {
pub fn build() -> NetworkContext { pub fn build() -> NetworkContext {
let ap_ip = match network_client::ip("ap0") { let ap_ip = match network::ip("ap0") {
Ok(ip) => ip, Ok(Some(ip)) => ip,
Err(_) => "x.x.x.x".to_string(), _ => "x.x.x.x".to_string(),
}; };
let ap_ssid = match network_client::ssid("ap0") { let ap_ssid = match network::ssid("ap0") {
Ok(ssid) => ssid, Ok(Some(ssid)) => ssid,
Err(_) => "Not currently activated".to_string(), _ => "Not currently activated".to_string(),
}; };
let ap_state = match network_client::state("ap0") { let ap_state = match network::state("ap0") {
Ok(state) => state, Ok(Some(state)) => state,
Err(_) => "Interface unavailable".to_string(), _ => "Interface unavailable".to_string(),
}; };
let ap_traffic = match network_client::traffic("ap0") { let ap_traffic = match network::traffic("ap0") {
Ok(traffic) => { // convert bytes to mb or gb and add appropriate units
let mut t = traffic; Ok(Some(traffic)) => convert_traffic(traffic),
// modify traffic values & assign measurement unit _ => None,
// based on received and transmitted values
// if received > 999 MB, convert it to GB
if t.received > 1_047_527_424 {
t.received /= 1_073_741_824;
t.rx_unit = Some("GB".to_string());
} else if t.received > 0 {
// otherwise, convert it to MB
t.received = (t.received / 1024) / 1024;
t.rx_unit = Some("MB".to_string());
} else {
t.received = 0;
t.rx_unit = Some("MB".to_string());
}
if t.transmitted > 1_047_527_424 {
t.transmitted /= 1_073_741_824;
t.tx_unit = Some("GB".to_string());
} else if t.transmitted > 0 {
t.transmitted = (t.transmitted / 1024) / 1024;
t.tx_unit = Some("MB".to_string());
} else {
t.transmitted = 0;
t.tx_unit = Some("MB".to_string());
}
Some(t)
}
Err(_) => None,
}; };
let wlan_ip = match network_client::ip("wlan0") { let wlan_ip = match network::ip("wlan0") {
Ok(ip) => ip, Ok(Some(ip)) => ip,
Err(_) => "x.x.x.x".to_string(), _ => "x.x.x.x".to_string(),
}; };
let wlan_rssi = match network_client::rssi_percent("wlan0") { let wlan_rssi = match network::rssi_percent("wlan0") {
Ok(rssi) => Some(rssi), Ok(rssi) => rssi,
Err(_) => None, _ => None,
}; };
let wlan_ssid = match network_client::ssid("wlan0") { let wlan_ssid = match network::ssid("wlan0") {
Ok(ssid) => ssid, Ok(Some(ssid)) => ssid,
Err(_) => "Not connected".to_string(), _ => "Not connected".to_string(),
}; };
let wlan_state = match network_client::state("wlan0") { let wlan_state = match network::state("wlan0") {
Ok(state) => state, Ok(Some(state)) => state,
Err(_) => "Interface unavailable".to_string(), _ => "Interface unavailable".to_string(),
}; };
let wlan_status = match network_client::status("wlan0") { let wlan_status = match network::status("wlan0") {
Ok(status) => status, Ok(status) => status,
Err(_) => "Interface unavailable".to_string(), _ => None,
}; };
let wlan_traffic = match network_client::traffic("wlan0") { let wlan_traffic = match network::traffic("wlan0") {
Ok(traffic) => { // convert bytes to mb or gb and add appropriate units
let mut t = traffic; Ok(Some(traffic)) => convert_traffic(traffic),
// modify traffic values & assign measurement unit _ => None,
// based on received and transmitted values
// if received > 999 MB, convert it to GB
if t.received > 1_047_527_424 {
t.received /= 1_073_741_824;
t.rx_unit = Some("GB".to_string());
} else if t.received > 0 {
// otherwise, convert it to MB
t.received = (t.received / 1024) / 1024;
t.rx_unit = Some("MB".to_string());
} else {
t.received = 0;
t.rx_unit = Some("MB".to_string());
}
if t.transmitted > 1_047_527_424 {
t.transmitted /= 1_073_741_824;
t.tx_unit = Some("GB".to_string());
} else if t.transmitted > 0 {
t.transmitted = (t.transmitted / 1024) / 1024;
t.tx_unit = Some("MB".to_string());
} else {
t.transmitted = 0;
t.tx_unit = Some("MB".to_string());
}
Some(t)
}
Err(_) => None,
}; };
NetworkContext { NetworkContext {

View File

@ -42,11 +42,11 @@
</div> </div>
</div> </div>
<!-- PEACH-STATS STATUS STACK --> <!-- PEACH-STATS STATUS STACK -->
<div class="stack capsule{% if stats_ping == "ONLINE" %} success-border{% else %} warning-border{% endif %}"> <div class="stack capsule success-border">
<img id="statsIcon" class="icon{% if stats_ping == "OFFLINE" %} icon-inactive{% endif %} icon-medium" alt="Stats" title="System statistics microservice status" src="/icons/chart.svg"> <img id="statsIcon" class="icon icon-medium" alt="Stats" title="System statistics microservice status" src="/icons/chart.svg">
<div class="stack" style="padding-top: 0.5rem;"> <div class="stack" style="padding-top: 0.5rem;">
<label class="label-small font-near-black">Statistics</label> <label class="label-small font-near-black">Statistics</label>
<label class="label-small font-near-black">{{ stats_ping }}</label> <label class="label-small font-near-black">AVAILABLE</label>
</div> </div>
</div> </div>
{# Display status for dynsdns, config & sbot #} {# Display status for dynsdns, config & sbot #}