Compare commits
86 Commits
update_wpa
...
supervisor
Author | SHA1 | Date | |
---|---|---|---|
ec93c9830b | |||
f6c30a327a | |||
dbf1c648ae | |||
8d9aeb6662 | |||
b24fb387b9 | |||
ceb7e502ce | |||
6407495292 | |||
05c1577f2a | |||
add169db07 | |||
fcb17d6802 | |||
01138eef35 | |||
2637b28380 | |||
03a0a51f4d | |||
eddb167c4c | |||
9704269c8a | |||
466db8ceea | |||
90badbfe30 | |||
7489916d5f | |||
7daab74b37 | |||
58bf306d3b | |||
bdac23092a | |||
f1ab2caa08 | |||
1fab4f3c43 | |||
8dcd594dd7 | |||
fcaa9e29c4 | |||
c6fc5c2992 | |||
1258a3697d | |||
7e94135839 | |||
f002f5cf3e | |||
fba1e91d8b | |||
6621a09ec9 | |||
170b037248 | |||
251aaf9237 | |||
123ebc06cc | |||
9ce27d17c5 | |||
2a8cf4ecfb | |||
d1a55e29d7 | |||
4568577f81 | |||
ab0e27c14d | |||
65b5f95a90 | |||
a60d892e95 | |||
5bd8a68ddf | |||
a6f52ce384 | |||
c71cc3992d | |||
56fafc8d67 | |||
414508f8ff | |||
6ad5c620c1 | |||
76d5e6a355 | |||
11e94fa421 | |||
216b60b86a | |||
a70f5e227d | |||
cddcb8f9bd | |||
8b33f8c174 | |||
1b43dc8b18 | |||
2d7b74d377 | |||
6b34864289 | |||
87ad2439b9 | |||
5838faf128 | |||
9c6fa00ec7 | |||
a81b8b42cf | |||
cdcff3475c | |||
077c2a9178 | |||
8b0b872d21 | |||
218a70b8f8 | |||
50dcb2cf9e | |||
e1877b5024 | |||
0923c24693 | |||
f3d4ba9fe5 | |||
16e6d42f87 | |||
3493e5adb9 | |||
5147eed497 | |||
9f6ba14123 | |||
21fb29c322 | |||
6c9e5fd3fd | |||
3adb226969 | |||
92f516b161 | |||
543470b949 | |||
6434471599 | |||
56c142a387 | |||
7deaa00d6e | |||
bf7f2c8e31 | |||
dc79833e2b | |||
b0b79fef24 | |||
98497fa5ae | |||
827ccbd4dc | |||
c21e2d090c |
687
Cargo.lock
generated
687
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,5 @@
|
||||
[workspace]
|
||||
|
||||
members = [
|
||||
"peach-buttons",
|
||||
"peach-oled",
|
||||
"peach-lib",
|
||||
"peach-config",
|
||||
@ -13,3 +11,4 @@ members = [
|
||||
"peach-jsonrpc-server",
|
||||
"peach-dyndns-updater"
|
||||
]
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "peach-config"
|
||||
version = "0.1.25"
|
||||
version = "0.1.26"
|
||||
authors = ["Andrew Reid <gnomad@cryptolab.net>", "Max Fowler <max@mfowler.info>"]
|
||||
edition = "2018"
|
||||
description = "Command line tool for installing, updating and configuring PeachCloud"
|
||||
@ -37,3 +37,5 @@ log = "0.4"
|
||||
lazy_static = "1.4.0"
|
||||
peach-lib = { path = "../peach-lib" }
|
||||
rpassword = "5.0"
|
||||
golgi = { git = "https://git.coopcloud.tech/golgi-ssb/golgi.git" }
|
||||
async-std = "1.10.0"
|
||||
|
@ -1,4 +1,5 @@
|
||||
#![allow(clippy::nonstandard_macro_braces)]
|
||||
use golgi::error::GolgiError;
|
||||
use peach_lib::error::PeachError;
|
||||
pub use snafu::ResultExt;
|
||||
use snafu::Snafu;
|
||||
@ -35,6 +36,12 @@ pub enum PeachConfigError {
|
||||
ChangePasswordError { source: PeachError },
|
||||
#[snafu(display("Entered passwords did not match. Please try again."))]
|
||||
InvalidPassword,
|
||||
#[snafu(display("Error in peach lib: {}", source))]
|
||||
PeachLibError { source: PeachError },
|
||||
#[snafu(display("Error in golgi: {}", source))]
|
||||
Golgi { source: GolgiError },
|
||||
#[snafu(display("{}", message))]
|
||||
CmdInputError { message: String },
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for PeachConfigError {
|
||||
@ -51,3 +58,15 @@ impl From<serde_json::Error> for PeachConfigError {
|
||||
PeachConfigError::SerdeError { source: err }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PeachError> for PeachConfigError {
|
||||
fn from(err: PeachError) -> PeachConfigError {
|
||||
PeachConfigError::PeachLibError { source: err }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GolgiError> for PeachConfigError {
|
||||
fn from(err: GolgiError) -> PeachConfigError {
|
||||
PeachConfigError::Golgi { source: err }
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,12 @@ mod change_password;
|
||||
mod constants;
|
||||
mod error;
|
||||
mod generate_manifest;
|
||||
mod publish_address;
|
||||
mod set_permissions;
|
||||
mod setup_networking;
|
||||
mod setup_peach;
|
||||
mod setup_peach_deb;
|
||||
mod status;
|
||||
mod update;
|
||||
mod utils;
|
||||
|
||||
@ -44,12 +46,21 @@ enum PeachConfig {
|
||||
Update(UpdateOpts),
|
||||
|
||||
/// Changes the password for the peach-web interface
|
||||
#[structopt(name = "changepassword")]
|
||||
#[structopt(name = "change-password")]
|
||||
ChangePassword(ChangePasswordOpts),
|
||||
|
||||
/// Updates file permissions on PeachCloud device
|
||||
#[structopt(name = "permissions")]
|
||||
SetPermissions,
|
||||
|
||||
/// Returns sbot id if sbot is running
|
||||
#[structopt(name = "whoami")]
|
||||
WhoAmI,
|
||||
|
||||
/// Publish domain and port.
|
||||
/// It takes an address argument of the form host:port
|
||||
#[structopt(name = "publish-address")]
|
||||
PublishAddress(PublishAddressOpts),
|
||||
}
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
@ -90,6 +101,13 @@ pub struct ChangePasswordOpts {
|
||||
password: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(StructOpt, Debug)]
|
||||
pub struct PublishAddressOpts {
|
||||
/// Specify address in the form domain:port
|
||||
#[structopt(short, long)]
|
||||
address: String,
|
||||
}
|
||||
|
||||
arg_enum! {
|
||||
/// enum options for real-time clock choices
|
||||
#[derive(Debug)]
|
||||
@ -102,7 +120,7 @@ arg_enum! {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
async fn run() {
|
||||
// initialize the logger
|
||||
env_logger::init();
|
||||
|
||||
@ -155,6 +173,34 @@ fn main() {
|
||||
)
|
||||
}
|
||||
},
|
||||
PeachConfig::WhoAmI => match status::whoami().await {
|
||||
Ok(sbot_id) => {
|
||||
println!("{:?}", sbot_id);
|
||||
{}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("sbot whoami encountered an error: {}", err)
|
||||
}
|
||||
},
|
||||
PeachConfig::PublishAddress(opts) => {
|
||||
match publish_address::publish_address(opts.address).await {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
error!(
|
||||
"peach-config encountered an error during publish address: {}",
|
||||
err
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable an async main function and execute the `run()` function,
|
||||
// catching any errors and printing them to `stderr` before exiting the
|
||||
// process.
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
run().await;
|
||||
}
|
||||
|
37
peach-config/src/publish_address.rs
Normal file
37
peach-config/src/publish_address.rs
Normal file
@ -0,0 +1,37 @@
|
||||
use crate::error::PeachConfigError;
|
||||
use golgi::kuska_ssb::api::dto::content::PubAddress;
|
||||
use golgi::messages::SsbMessageContent;
|
||||
use peach_lib::sbot::init_sbot;
|
||||
|
||||
/// Utility function to publish the address (domain:port) of the pub
|
||||
/// publishing the address causes the domain and port to be used for invite generation,
|
||||
/// and also gossips this pub address to their peers
|
||||
pub async fn publish_address(address: String) -> Result<(), PeachConfigError> {
|
||||
// split address into domain:port
|
||||
let split: Vec<&str> = address.split(':').collect();
|
||||
let (domain, port): (&str, &str) = (split[0], split[1]);
|
||||
// convert port to u16
|
||||
let port_as_u16: u16 = port
|
||||
.parse()
|
||||
.map_err(|_err| PeachConfigError::CmdInputError {
|
||||
message: "Failure to parse domain and port. Address must be of the format host:port."
|
||||
.to_string(),
|
||||
})?;
|
||||
// publish address
|
||||
let mut sbot = init_sbot().await?;
|
||||
let pub_id = sbot.whoami().await?;
|
||||
// Compose a `pub` address type message.
|
||||
let pub_address_msg = SsbMessageContent::Pub {
|
||||
address: Some(PubAddress {
|
||||
// Host name (can be an IP address if onboarding over WiFi).
|
||||
host: Some(domain.to_string()),
|
||||
// Port.
|
||||
port: port_as_u16,
|
||||
// Public key.
|
||||
key: pub_id,
|
||||
}),
|
||||
};
|
||||
// Publish the `pub` address message.
|
||||
let _pub_msg_ref = sbot.publish(pub_address_msg).await?;
|
||||
Ok(())
|
||||
}
|
@ -1,17 +1,17 @@
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use peach_lib::config_manager::get_config_value;
|
||||
use peach_lib::config_manager;
|
||||
|
||||
use crate::error::PeachConfigError;
|
||||
use crate::utils::cmd;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref PEACH_CONFIGDIR: String = get_config_value("PEACH_CONFIGDIR")
|
||||
pub static ref PEACH_CONFIGDIR: String = config_manager::get_config_value("PEACH_CONFIGDIR")
|
||||
.expect("Failed to load config value for PEACH_CONFIGDIR");
|
||||
pub static ref PEACH_WEBDIR: String =
|
||||
get_config_value("PEACH_WEBDIR").expect("Failed to load config value for PEACH_WEBDIR");
|
||||
pub static ref PEACH_HOMEDIR: String =
|
||||
get_config_value("PEACH_HOMEDIR").expect("Failed to load config value for PEACH_HOMEDIR");
|
||||
pub static ref PEACH_WEBDIR: String = config_manager::get_config_value("PEACH_WEBDIR")
|
||||
.expect("Failed to load config value for PEACH_WEBDIR");
|
||||
pub static ref PEACH_HOMEDIR: String = config_manager::get_config_value("PEACH_HOMEDIR")
|
||||
.expect("Failed to load config value for PEACH_HOMEDIR");
|
||||
}
|
||||
|
||||
/// Utility function to set correct file permissions on the PeachCloud device.
|
||||
|
9
peach-config/src/status.rs
Normal file
9
peach-config/src/status.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use crate::error::PeachConfigError;
|
||||
use peach_lib::sbot::init_sbot;
|
||||
|
||||
/// Utility function to check if sbot is running via the whoami method
|
||||
pub async fn whoami() -> Result<String, PeachConfigError> {
|
||||
let mut sbot = init_sbot().await?;
|
||||
let sbot_id = sbot.whoami().await?;
|
||||
Ok(sbot_id)
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "peach-lib"
|
||||
version = "1.3.3"
|
||||
version = "1.3.5"
|
||||
authors = ["Andrew Reid <glyph@mycelial.technology>"]
|
||||
edition = "2018"
|
||||
|
||||
@ -9,7 +9,7 @@ async-std = "1.10"
|
||||
chrono = "0.4"
|
||||
dirs = "4.0"
|
||||
fslock="0.1"
|
||||
golgi = { git = "https://git.coopcloud.tech/golgi-ssb/golgi" }
|
||||
golgi = { git = "https://git.coopcloud.tech/golgi-ssb/golgi.git" }
|
||||
jsonrpc-client-core = "0.5"
|
||||
jsonrpc-client-http = "0.5"
|
||||
jsonrpc-core = "8.0"
|
||||
|
@ -57,9 +57,11 @@ pub fn get_peach_config_defaults() -> HashMap<String, String> {
|
||||
("DYN_NAMESERVER", "ns.peachcloud.org"),
|
||||
("DYN_ENABLED", "false"),
|
||||
("SSB_ADMIN_IDS", ""),
|
||||
("SYSTEM_MANAGER", "systemd"),
|
||||
("ADMIN_PASSWORD_HASH", "47"),
|
||||
("TEMPORARY_PASSWORD_HASH", ""),
|
||||
("GO_SBOT_DATADIR", "/home/peach/.ssb-go"),
|
||||
("GO_SBOT_SERVICE", "go-sbot"),
|
||||
("PEACH_CONFIGDIR", "/var/lib/peachcloud"),
|
||||
("PEACH_HOMEDIR", "/home/peach"),
|
||||
("PEACH_WEBDIR", "/usr/share/peach-web"),
|
||||
@ -131,7 +133,10 @@ pub fn save_peach_config_to_disc(
|
||||
peach_config: HashMap<String, String>,
|
||||
) -> Result<HashMap<String, String>, PeachError> {
|
||||
// use a file lock to avoid race conditions while saving config
|
||||
let mut lock = LockFile::open(&*LOCK_FILE_PATH)?;
|
||||
let mut lock = LockFile::open(&*LOCK_FILE_PATH).map_err(|source| PeachError::Read {
|
||||
source,
|
||||
path: LOCK_FILE_PATH.to_string(),
|
||||
})?;
|
||||
lock.lock()?;
|
||||
|
||||
// first convert Hashmap to BTreeMap (so that keys are saved in deterministic alphabetical order)
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
//! 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 golgi::GolgiError;
|
||||
use std::{io, str, string};
|
||||
|
||||
/// This type represents all possible errors that can occur when interacting with the PeachCloud library.
|
||||
@ -102,6 +103,13 @@ pub enum PeachError {
|
||||
/// The file path for the write attempt.
|
||||
path: String,
|
||||
},
|
||||
|
||||
/// Represents a Golgi error
|
||||
Golgi(GolgiError),
|
||||
|
||||
/// Represents a generic system error, whose details are specified in the string message
|
||||
System(String),
|
||||
|
||||
}
|
||||
|
||||
impl std::error::Error for PeachError {
|
||||
@ -130,6 +138,8 @@ impl std::error::Error for PeachError {
|
||||
PeachError::Utf8ToStr(_) => None,
|
||||
PeachError::Utf8ToString(_) => None,
|
||||
PeachError::Write { ref source, .. } => Some(source),
|
||||
PeachError::Golgi(_) => None,
|
||||
PeachError::System(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -187,6 +197,10 @@ impl std::fmt::Display for PeachError {
|
||||
PeachError::Write { ref path, .. } => {
|
||||
write!(f, "Write error: {}", path)
|
||||
}
|
||||
PeachError::Golgi(ref err) => err.fmt(f),
|
||||
PeachError::System(ref msg) => {
|
||||
write!(f, "system error: {}", msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -256,3 +270,10 @@ impl From<string::FromUtf8Error> for PeachError {
|
||||
PeachError::Utf8ToString(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GolgiError> for PeachError {
|
||||
fn from(err: GolgiError) -> PeachError {
|
||||
PeachError::Golgi(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,10 @@
|
||||
|
||||
use std::{fs, fs::File, io, io::Write, path::PathBuf, process::Command, str};
|
||||
|
||||
use crate::config_manager::get_config_value;
|
||||
use golgi::{sbot::Keystore, Sbot};
|
||||
use log::{debug};
|
||||
|
||||
use crate::config_manager;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::PeachError;
|
||||
@ -61,13 +64,48 @@ impl Default for SbotStatus {
|
||||
impl SbotStatus {
|
||||
/// Retrieve statistics for the go-sbot systemd process by querying `systemctl`.
|
||||
pub fn read() -> Result<Self, PeachError> {
|
||||
let system_manager = config_manager::get_config_value("SYSTEM_MANAGER")?;
|
||||
match system_manager.as_str() {
|
||||
"systemd" => {
|
||||
SbotStatus::read_from_systemctl()
|
||||
},
|
||||
"supervisord" => {
|
||||
SbotStatus::read_from_supervisorctl()
|
||||
},
|
||||
_ => Err(PeachError::System(format!(
|
||||
"Invalid configuration for SYSTEM_MANAGER: {:?}",
|
||||
system_manager)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_from_supervisorctl() -> Result<Self, PeachError> {
|
||||
let mut status = SbotStatus::default();
|
||||
let info_output = Command::new("supervisorctl")
|
||||
.arg("status")
|
||||
.arg(config_manager::get_config_value("GO_SBOT_SERVICE")?)
|
||||
.output()?;
|
||||
|
||||
let service_info = std::str::from_utf8(&info_output.stdout)?;
|
||||
|
||||
for line in service_info.lines() {
|
||||
// example line
|
||||
// go-sbot RUNNING pid 11, uptime 0:04:23
|
||||
if line.contains("RUNNING") {
|
||||
// TODO: this should be an enum
|
||||
status.state = Some("active".to_string());
|
||||
}
|
||||
}
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
pub fn read_from_systemctl() -> Result<Self, PeachError> {
|
||||
let mut status = SbotStatus::default();
|
||||
|
||||
// note this command does not need to be run as sudo
|
||||
// because non-privileged users are able to run systemctl show
|
||||
let info_output = Command::new("systemctl")
|
||||
.arg("show")
|
||||
.arg("go-sbot.service")
|
||||
.arg(config_manager::get_config_value("GO_SBOT_SERVICE")?)
|
||||
.arg("--no-page")
|
||||
.output()?;
|
||||
|
||||
@ -89,7 +127,7 @@ impl SbotStatus {
|
||||
// because non-privileged users are able to run systemctl status
|
||||
let status_output = Command::new("systemctl")
|
||||
.arg("status")
|
||||
.arg("go-sbot.service")
|
||||
.arg(config_manager::get_config_value("GO_SBOT_SERVICE")?)
|
||||
.output()?;
|
||||
|
||||
let service_status = str::from_utf8(&status_output.stdout)?;
|
||||
@ -107,8 +145,8 @@ impl SbotStatus {
|
||||
// using the index of the first ';' + 2 and the last ';'
|
||||
status.boot_state = Some(line[start + 2..end].to_string());
|
||||
}
|
||||
// example of the output line we're looking for here:
|
||||
// `Active: active (running) since Mon 2022-01-24 16:22:51 SAST; 4min 14s ago`
|
||||
// example of the output line we're looking for here:
|
||||
// `Active: active (running) since Mon 2022-01-24 16:22:51 SAST; 4min 14s ago`
|
||||
} else if line.contains("Active:") {
|
||||
let before_time = line.find(';');
|
||||
let after_time = line.find(" ago");
|
||||
@ -119,7 +157,7 @@ impl SbotStatus {
|
||||
// if service is active then the `time` reading is uptime
|
||||
if status.state == Some("active".to_string()) {
|
||||
status.uptime = time.map(|t| t.to_string())
|
||||
// if service is inactive then the `time` reading is downtime
|
||||
// if service is inactive then the `time` reading is downtime
|
||||
} else if status.state == Some("inactive".to_string()) {
|
||||
status.downtime = time.map(|t| t.to_string())
|
||||
}
|
||||
@ -128,7 +166,10 @@ impl SbotStatus {
|
||||
}
|
||||
|
||||
// get path to blobstore
|
||||
let blobstore_path = format!("{}/blobs/sha256", get_config_value("GO_SBOT_DATADIR")?);
|
||||
let blobstore_path = format!(
|
||||
"{}/blobs/sha256",
|
||||
config_manager::get_config_value("GO_SBOT_DATADIR")?
|
||||
);
|
||||
|
||||
// determine the size of the blobstore directory in bytes
|
||||
status.blobstore = dir_size(blobstore_path).ok();
|
||||
@ -199,9 +240,11 @@ impl Default for SbotConfig {
|
||||
impl SbotConfig {
|
||||
/// Read the go-sbot `config.toml` file from file and deserialize into `SbotConfig`.
|
||||
pub fn read() -> Result<Self, PeachError> {
|
||||
// determine path of user's home directory
|
||||
let mut config_path = dirs::home_dir().ok_or(PeachError::HomeDir)?;
|
||||
config_path.push(".ssb-go/config.toml");
|
||||
// determine path of user's go-sbot config.toml
|
||||
let config_path = format!(
|
||||
"{}/config.toml",
|
||||
config_manager::get_config_value("GO_SBOT_DATADIR")?
|
||||
);
|
||||
|
||||
let config_contents = fs::read_to_string(config_path)?;
|
||||
|
||||
@ -217,8 +260,11 @@ impl SbotConfig {
|
||||
// convert the provided `SbotConfig` instance to a string
|
||||
let config_string = toml::to_string(&config)?;
|
||||
|
||||
// determine path of user's home directory
|
||||
let config_path = format!("{}/config.toml", get_config_value("GO_SBOT_DATADIR")?);
|
||||
// determine path of user's go-sbot config.toml
|
||||
let config_path = format!(
|
||||
"{}/config.toml",
|
||||
config_manager::get_config_value("GO_SBOT_DATADIR")?
|
||||
);
|
||||
|
||||
// open config file for writing
|
||||
let mut file = File::create(config_path)?;
|
||||
@ -232,3 +278,25 @@ impl SbotConfig {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialise an sbot client
|
||||
pub async fn init_sbot() -> Result<Sbot, PeachError> {
|
||||
// read sbot config from config.toml
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
||||
debug!("Initialising an sbot client with configuration parameters");
|
||||
// initialise sbot connection with ip:port and shscap from config file
|
||||
let key_path = format!(
|
||||
"{}/secret",
|
||||
config_manager::get_config_value("GO_SBOT_DATADIR")?
|
||||
);
|
||||
let sbot_client = match sbot_config {
|
||||
// TODO: panics if we pass `Some(conf.shscap)` as second arg
|
||||
Some(conf) => {
|
||||
let ip_port = conf.lis.clone();
|
||||
Sbot::init(Keystore::CustomGoSbot(key_path), Some(ip_port), None).await?
|
||||
}
|
||||
None => Sbot::init(Keystore::CustomGoSbot(key_path), None, None).await?,
|
||||
};
|
||||
Ok(sbot_client)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "peach-stats"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
authors = ["Andrew Reid <glyph@mycelial.technology>"]
|
||||
edition = "2018"
|
||||
description = "Query system statistics. Provides a wrapper around the probes and systemstat crates."
|
||||
|
@ -44,8 +44,8 @@ impl SbotStat {
|
||||
pub fn sbot_stats() -> Result<SbotStat, StatsError> {
|
||||
let mut status = SbotStat::default();
|
||||
|
||||
let info_output = Command::new("/usr/bin/systemctl")
|
||||
.arg("--user")
|
||||
let info_output = Command::new("sudo")
|
||||
.arg("systemctl")
|
||||
.arg("show")
|
||||
.arg("go-sbot.service")
|
||||
.arg("--no-page")
|
||||
@ -66,8 +66,8 @@ pub fn sbot_stats() -> Result<SbotStat, StatsError> {
|
||||
}
|
||||
}
|
||||
|
||||
let status_output = Command::new("/usr/bin/systemctl")
|
||||
.arg("--user")
|
||||
let status_output = Command::new("sudo")
|
||||
.arg("systemctl")
|
||||
.arg("status")
|
||||
.arg("go-sbot.service")
|
||||
.output()
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "peach-web"
|
||||
version = "0.6.13"
|
||||
authors = ["Andrew Reid <gnomad@cryptolab.net>"]
|
||||
version = "0.6.19"
|
||||
authors = ["Andrew Reid <gnomad@cryptolab.net>", "Max Fowler <max@mfowler.info>"]
|
||||
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."
|
||||
homepage = "https://opencollective.com/peachcloud"
|
||||
|
@ -1,6 +1,6 @@
|
||||
# peach-web
|
||||
|
||||

|
||||

|
||||
|
||||
## Web Interface for PeachCloud
|
||||
|
||||
@ -17,7 +17,7 @@ The web interface is primarily designed as a means of managing a Scuttlebutt pub
|
||||
|
||||
Additional features are focused on administration of the device itself. This includes networking functionality and device statistics.
|
||||
|
||||
The peach-web stack currently consists of [Rouille](https://crates.io/crates/rouille) (Rust web framework), [Maud](https://maud.lambda.xyz/) (Rust template engine), HTML and CSS. Scuttlebutt functionality is provided by [golgi](http://golgi.mycelial.technology).
|
||||
The peach-web stack currently consists of [Rouille](https://crates.io/crates/rouille) (Rust web framework), [Maud](https://maud.lambda.xyz/) (Rust template engine), HTML, CSS and a tiny bit of JS. Scuttlebutt functionality is provided by [golgi](http://golgi.mycelial.technology).
|
||||
|
||||
_Note: This is a work-in-progress._
|
||||
|
||||
@ -36,11 +36,46 @@ Run the binary:
|
||||
|
||||
`../target/release/peach-web`
|
||||
|
||||
## Environment
|
||||
## Development Setup
|
||||
|
||||
In order to test `peach-web` on a development machine you will need to have a running instance of `go-sbot` (please see the [go-sbot README](https://github.com/cryptoscope/ssb) for installation details). The `GO_SBOT_DATADIR` environment variable or corresponding config variable must be set to `/home/<user>/.ssb-go` and the `PEACH_HOMEDIR` variable must be set to `/home/<user>`. See the Configuration section below for more details.
|
||||
|
||||
The `go-sbot` process must be managed by `systemd` in order for it to be controlled via the `peach-web` web interface. Here is a basic `go-sbot.service` file:
|
||||
|
||||
```
|
||||
[Unit]
|
||||
Description=GoSSB server.
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/go-sbot
|
||||
Environment="LIBRARIAN_WRITEALL=0"
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
And a `sudoers` rule must be created to allow the `go-sbot.service` state to be modified without requiring a password. Here is an example `/etc/sudoers.d/peach-web` file:
|
||||
|
||||
```
|
||||
# Control go-sbot service without sudo passworkd
|
||||
|
||||
<user> ALL=(ALL) NOPASSWD: /bin/systemctl start go-sbot.service, /bin/systemctl restart go-sbot.service, /bin/systemctl stop go-sbot.service, /bin/systemctl enable go-sbot.service, /bin/systemctl disable go-sbot.service
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
By default, configuration variables are stored in `/var/lib/peachcloud/config.yml`. The variables in the file are updated by `peach-web` when changes are made to configurations via the web interface. Since `peach-web` has no database, all configurations are stored in this file.
|
||||
|
||||
A non-default configuration directory can be defined via the `PEACH_CONFIGDIR` environment variable or corresponding key in the `config.yml` file.
|
||||
|
||||
### Configuration Mode
|
||||
|
||||
The web application can be run with a minimal set of routes and functionality (PeachPub - a simple sbot manager) or with the full-suite of capabilities, including network management and access to device statistics (PeachCloud). The mode is enabled by default (as defined in `Rocket.toml`) but can be overwritten using the `STANDALONE_MODE` environment variable: `true` or `false`. If the variable is unset or the value is incorrectly set, the application defaults to standalone mode.
|
||||
The web application can be run with a minimal set of routes and functionality (PeachPub - a simple sbot manager) or with the full-suite of capabilities, including network management and access to device statistics (PeachCloud).
|
||||
|
||||
The application runs in PeachPub mode by default. The complete PeachCloud mode will be available once a large refactor is complete; it is not currently in working order so it's best to stick with PeachPub for now.
|
||||
|
||||
The running mode can be defined by setting the `STANDALONE_MODE` environment variable (`true` for PeachPub or `false` for PeachCloud). Alternatively, the desired mode can be set by modifying the PeachCloud configuration file.
|
||||
|
||||
### Authentication
|
||||
|
||||
@ -56,6 +91,14 @@ Logging is made available with `env_logger`:
|
||||
|
||||
Other logging levels include `debug`, `warn` and `error`.
|
||||
|
||||
### 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.
|
||||
|
||||
## Debian Packaging
|
||||
|
||||
A `systemd` service file and Debian maintainer scripts are included in the `debian` directory, allowing `peach-web` 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.
|
||||
@ -88,20 +131,6 @@ Remove configuration files (not removed with `apt-get remove`):
|
||||
|
||||
`sudo apt-get purge peach-web`
|
||||
|
||||
## 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.
|
||||
|
||||
## Design
|
||||
|
||||
`peach-web` has been designed with simplicity and resource minimalism in mind. Both the dependencies used by the project, as well as the code itself, reflect these design priorities. The Rouille micro-web-framework and Maud templating engine have been used to present a web interface for interacting with the device. HTML is rendered server-side and request handlers call `peach-` libraries and serve HTML and assets. The optimised binary for `peach-web` can be compiled on a RPi 3 B+ in approximately 30 minutes.
|
||||
|
@ -18,6 +18,7 @@ pub enum PeachWebError {
|
||||
Json(JsonError),
|
||||
OsString,
|
||||
PeachLib { source: PeachError, msg: String },
|
||||
System(String),
|
||||
Yaml(YamlError),
|
||||
}
|
||||
|
||||
@ -31,6 +32,7 @@ impl std::error::Error for PeachWebError {
|
||||
PeachWebError::Json(ref source) => Some(source),
|
||||
PeachWebError::OsString => None,
|
||||
PeachWebError::PeachLib { ref source, .. } => Some(source),
|
||||
PeachWebError::System(_) => None,
|
||||
PeachWebError::Yaml(ref source) => Some(source),
|
||||
}
|
||||
}
|
||||
@ -54,6 +56,9 @@ impl std::fmt::Display for PeachWebError {
|
||||
"Filesystem error: failed to convert OsString to String for go-ssb directory path"
|
||||
),
|
||||
PeachWebError::PeachLib { ref source, .. } => write!(f, "{}", source),
|
||||
PeachWebError::System(ref msg) => {
|
||||
write!(f, "system error: {}", msg)
|
||||
}
|
||||
PeachWebError::Yaml(ref source) => write!(f, "Serde YAML error: {}", source),
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
use rouille::{router, Request, Response};
|
||||
|
||||
use crate::{routes, templates, utils::flash::FlashResponse, SessionData};
|
||||
use crate::{
|
||||
routes, templates,
|
||||
utils::{cookie::CookieResponse, flash::FlashResponse},
|
||||
SessionData,
|
||||
};
|
||||
|
||||
// TODO: add mount_peachcloud_routes()
|
||||
// https://github.com/tomaka/rouille/issues/232#issuecomment-919225104
|
||||
@ -22,6 +26,8 @@ pub fn mount_peachpub_routes(
|
||||
router!(request,
|
||||
(GET) (/) => {
|
||||
Response::html(routes::home::build_template())
|
||||
// reset the back_url cookie each time we visit the homepage
|
||||
.reset_cookie("back_url")
|
||||
},
|
||||
|
||||
(GET) (/auth/change) => {
|
||||
@ -49,6 +55,9 @@ pub fn mount_peachpub_routes(
|
||||
|
||||
(GET) (/scuttlebutt/blocks) => {
|
||||
Response::html(routes::scuttlebutt::blocks::build_template())
|
||||
// add a back_url cookie to allow the path of the back button
|
||||
// to be set correctly on the /scuttlebutt/profile page
|
||||
.add_cookie("back_url=/scuttlebutt/blocks")
|
||||
},
|
||||
|
||||
(POST) (/scuttlebutt/follow) => {
|
||||
@ -57,10 +66,16 @@ pub fn mount_peachpub_routes(
|
||||
|
||||
(GET) (/scuttlebutt/follows) => {
|
||||
Response::html(routes::scuttlebutt::follows::build_template())
|
||||
// add a back_url cookie to allow the path of the back button
|
||||
// to be set correctly on the /scuttlebutt/profile page
|
||||
.add_cookie("back_url=/scuttlebutt/follows")
|
||||
},
|
||||
|
||||
(GET) (/scuttlebutt/friends) => {
|
||||
Response::html(routes::scuttlebutt::friends::build_template())
|
||||
// add a back_url cookie to allow the path of the back button
|
||||
// to be set correctly on the /scuttlebutt/profile page
|
||||
.add_cookie("back_url=/scuttlebutt/friends")
|
||||
},
|
||||
|
||||
(GET) (/scuttlebutt/invites) => {
|
||||
@ -117,6 +132,9 @@ pub fn mount_peachpub_routes(
|
||||
|
||||
(POST) (/scuttlebutt/search) => {
|
||||
routes::scuttlebutt::search::handle_form(request)
|
||||
// add a back_url cookie to allow the path of the back button
|
||||
// to be set correctly on the /scuttlebutt/profile page
|
||||
.add_cookie("back_url=/scuttlebutt/search")
|
||||
},
|
||||
|
||||
(POST) (/scuttlebutt/unblock) => {
|
||||
@ -187,7 +205,7 @@ pub fn mount_peachpub_routes(
|
||||
},
|
||||
|
||||
(GET) (/status/scuttlebutt) => {
|
||||
Response::html(routes::status::scuttlebutt::build_template())
|
||||
Response::html(routes::status::scuttlebutt::build_template()).add_cookie("back_url=/status/scuttlebutt")
|
||||
},
|
||||
|
||||
// render the not_found template and set a 404 status code if none of
|
||||
|
@ -30,7 +30,7 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
|
||||
form id="sendPasswordReset" action="/auth/temporary" method="post" {
|
||||
div id="buttonDiv" {
|
||||
input class="button button-primary center" style="margin-top: 1rem;" type="submit" value="Send Temporary Password" title="Send temporary password to Scuttlebutt admin(s)";
|
||||
a href="/auth/reset_password" class="button button-primary center" title="Set a new password using the temporary password" {
|
||||
a href="/auth/reset" class="button button-primary center" title="Set a new password using the temporary password" {
|
||||
"Set New Password"
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use rouille::Request;
|
||||
|
||||
use crate::{
|
||||
templates,
|
||||
utils::{flash::FlashRequest, sbot, sbot::Profile, theme},
|
||||
utils::{cookie::CookieRequest, flash::FlashRequest, sbot, sbot::Profile, theme},
|
||||
};
|
||||
|
||||
// ROUTE: /scuttlebutt/profile
|
||||
@ -83,13 +83,15 @@ fn social_interaction_buttons_template(profile: &Profile) -> Markup {
|
||||
@match (profile.following, &profile.id) {
|
||||
(Some(false), Some(ssb_id)) => {
|
||||
form id="followForm" class="center" action="/scuttlebutt/follow" method="post" {
|
||||
input type="hidden" id="publicKey" name="public_key" value=(ssb_id);
|
||||
// url encode the ssb_id value
|
||||
input type="hidden" id="publicKey" name="public_key" value=(ssb_id.replace('/', "%2F"));
|
||||
input id="followPeer" class="button button-primary center" type="submit" title="Follow Peer" value="Follow";
|
||||
}
|
||||
},
|
||||
(Some(true), Some(ssb_id)) => {
|
||||
form id="unfollowForm" class="center" action="/scuttlebutt/unfollow" method="post" {
|
||||
input type="hidden" id="publicKey" name="public_key" value=(ssb_id);
|
||||
// url encode the ssb_id value
|
||||
input type="hidden" id="publicKey" name="public_key" value=(ssb_id.replace('/', "%2F"));
|
||||
input id="unfollowPeer" class="button button-primary center" type="submit" title="Unfollow Peer" value="Unfollow";
|
||||
}
|
||||
},
|
||||
@ -98,13 +100,15 @@ fn social_interaction_buttons_template(profile: &Profile) -> Markup {
|
||||
@match (profile.blocking, &profile.id) {
|
||||
(Some(false), Some(ssb_id)) => {
|
||||
form id="blockForm" class="center" action="/scuttlebutt/block" method="post" {
|
||||
input type="hidden" id="publicKey" name="public_key" value=(ssb_id);
|
||||
// url encode the ssb_id value
|
||||
input type="hidden" id="publicKey" name="public_key" value=(ssb_id.replace('/', "%2F"));
|
||||
input id="blockPeer" class="button button-primary center" type="submit" title="Block Peer" value="Block";
|
||||
}
|
||||
},
|
||||
(Some(true), Some(ssb_id)) => {
|
||||
form id="unblockForm" class="center" action="/scuttlebutt/unblock" method="post" {
|
||||
input type="hidden" id="publicKey" name="public_key" value=(ssb_id);
|
||||
// url encode the ssb_id value
|
||||
input type="hidden" id="publicKey" name="public_key" value=(ssb_id.replace('/', "%2F"));
|
||||
input id="unblockPeer" class="button button-primary center" type="submit" title="Unblock Peer" value="Unblock";
|
||||
}
|
||||
},
|
||||
@ -112,7 +116,8 @@ fn social_interaction_buttons_template(profile: &Profile) -> Markup {
|
||||
}
|
||||
@if let Some(ssb_id) = &profile.id {
|
||||
form class="center" {
|
||||
a id="privateMessage" class="button button-primary center" href={ "/scuttlebutt/private/" (ssb_id) } title="Private Message" {
|
||||
// url encode the ssb_id
|
||||
a id="privateMessage" class="button button-primary center" href={ "/scuttlebutt/private/" (ssb_id.replace('/', "%2F")) } title="Private Message" {
|
||||
"Send Private Message"
|
||||
}
|
||||
}
|
||||
@ -169,7 +174,15 @@ pub fn build_template(request: &Request, ssb_id: Option<String>) -> PreEscaped<S
|
||||
_ => templates::inactive::build_template("Profile is unavailable."),
|
||||
};
|
||||
|
||||
let body = templates::nav::build_template(profile_template, "Profile", Some("/"));
|
||||
// a request to /scuttlebutt/profile can originate via the Friends,
|
||||
// Follows or Blocks menu - as well as the Search page and Homepage.
|
||||
// therefore, we check to see if the `back_url` cookie has been set
|
||||
// and assign the path of the back button accordingly.
|
||||
// for example, if the request has come via the Friends menu then the
|
||||
// `back_url` cookie will be set with a value of "/scuttlebutt/friends".
|
||||
let back_url = request.retrieve_cookie("back_url").or(Some("/"));
|
||||
|
||||
let body = templates::nav::build_template(profile_template, "Profile", back_url);
|
||||
|
||||
// query the current theme so we can pass it into the base template builder
|
||||
let theme = theme::get_theme();
|
||||
|
@ -122,6 +122,15 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
|
||||
input type="text" id="database_dir" name="repo" value=(sbot_config.repo);
|
||||
}
|
||||
div class="center" {
|
||||
@if sbot_config.enable_ebt {
|
||||
input type="checkbox" id="ebtReplication" style="margin-bottom: 1rem;" name="enable_ebt" checked;
|
||||
} @else {
|
||||
input type="checkbox" id="ebtReplication" style="margin-bottom: 1rem;" name="enable_ebt";
|
||||
}
|
||||
label class="font-normal" for="ebtReplication" title="Enable Epidemic Broadcast Tree (EBT) replication instead of legacy replication" {
|
||||
"Enable EBT Replication"
|
||||
}
|
||||
br;
|
||||
@if sbot_config.localadv {
|
||||
input type="checkbox" id="lanBroadcast" style="margin-bottom: 1rem;" name="localadv" checked;
|
||||
} @else {
|
||||
@ -157,7 +166,6 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
|
||||
input type="hidden" id="hmac" name="hmac" value=(sbot_config.hmac);
|
||||
input type="hidden" id="wslis" name="wslis" value=(sbot_config.wslis);
|
||||
input type="hidden" id="debuglis" name="debuglis" value=(sbot_config.debuglis);
|
||||
input type="hidden" id="enable_ebt" name="enable_ebt" value=(sbot_config.enable_ebt);
|
||||
input type="hidden" id="promisc" name="promisc" value=(sbot_config.promisc);
|
||||
input type="hidden" id="nounixsock" name="nounixsock" value=(sbot_config.nounixsock);
|
||||
(PreEscaped("<!-- BUTTONS -->"))
|
||||
@ -175,8 +183,11 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
|
||||
|
||||
// wrap the nav bars around the settings menu template content
|
||||
// parameters are template, title and back url
|
||||
let body =
|
||||
templates::nav::build_template(menu_template, "Scuttlebutt Settings", Some("/settings"));
|
||||
let body = templates::nav::build_template(
|
||||
menu_template,
|
||||
"Scuttlebutt Settings",
|
||||
Some("/settings/scuttlebutt"),
|
||||
);
|
||||
|
||||
// query the current theme so we can pass it into the base template builder
|
||||
let theme = theme::get_theme();
|
||||
@ -232,13 +243,13 @@ pub fn handle_form(request: &Request, restart: bool) -> Response {
|
||||
match data.startup {
|
||||
true => {
|
||||
debug!("Enabling go-sbot.service");
|
||||
if let Err(e) = sbot::systemctl_sbot_cmd("enable") {
|
||||
if let Err(e) = sbot::system_sbot_cmd("enable") {
|
||||
warn!("Failed to enable go-sbot.service: {}", e)
|
||||
}
|
||||
}
|
||||
false => {
|
||||
debug!("Disabling go-sbot.service");
|
||||
if let Err(e) = sbot::systemctl_sbot_cmd("disable") {
|
||||
if let Err(e) = sbot::system_sbot_cmd("disable") {
|
||||
warn!("Failed to disable go-sbot.service: {}", e)
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use rouille::Request;
|
||||
|
||||
use crate::{
|
||||
templates,
|
||||
utils::{flash::FlashRequest, theme},
|
||||
utils::{cookie::CookieRequest, flash::FlashRequest, theme},
|
||||
};
|
||||
|
||||
/// Read the status of the go-sbot service and render buttons accordingly.
|
||||
@ -53,10 +53,13 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
|
||||
}
|
||||
};
|
||||
|
||||
// retrieve the value of the "back_url" cookie
|
||||
// if the cookie value is not found then set a hardcoded fallback value
|
||||
let back_url = request.retrieve_cookie("back_url").or(Some("/settings"));
|
||||
|
||||
// wrap the nav bars around the settings menu template content
|
||||
// parameters are template, title and back url
|
||||
let body =
|
||||
templates::nav::build_template(menu_template, "Scuttlebutt Settings", Some("/settings"));
|
||||
let body = templates::nav::build_template(menu_template, "Scuttlebutt Settings", back_url);
|
||||
|
||||
// query the current theme so we can pass it into the base template builder
|
||||
let theme = theme::get_theme();
|
||||
|
@ -1,7 +1,7 @@
|
||||
use log::info;
|
||||
use rouille::Response;
|
||||
|
||||
use crate::utils::{flash::FlashResponse, sbot::systemctl_sbot_cmd};
|
||||
use crate::utils::{flash::FlashResponse, sbot};
|
||||
|
||||
// ROUTE: /settings/scuttlebutt/restart
|
||||
|
||||
@ -10,9 +10,9 @@ use crate::utils::{flash::FlashResponse, sbot::systemctl_sbot_cmd};
|
||||
/// the attempt via a flash message.
|
||||
pub fn restart_sbot() -> Response {
|
||||
info!("Restarting go-sbot.service");
|
||||
let (flash_name, flash_msg) = match systemctl_sbot_cmd("stop") {
|
||||
let (flash_name, flash_msg) = match sbot::system_sbot_cmd("stop") {
|
||||
// if stop was successful, try to start the process
|
||||
Ok(_) => match systemctl_sbot_cmd("start") {
|
||||
Ok(_) => match sbot::system_sbot_cmd("start") {
|
||||
Ok(_) => (
|
||||
"flash_name=success".to_string(),
|
||||
"flash_msg=Sbot process has been restarted".to_string(),
|
||||
|
@ -1,7 +1,7 @@
|
||||
use log::info;
|
||||
use rouille::Response;
|
||||
|
||||
use crate::utils::{flash::FlashResponse, sbot::systemctl_sbot_cmd};
|
||||
use crate::utils::{flash::FlashResponse, sbot};
|
||||
|
||||
// ROUTE: /settings/scuttlebutt/start
|
||||
|
||||
@ -10,7 +10,7 @@ use crate::utils::{flash::FlashResponse, sbot::systemctl_sbot_cmd};
|
||||
/// the attempt via a flash message.
|
||||
pub fn start_sbot() -> Response {
|
||||
info!("Starting go-sbot.service");
|
||||
let (flash_name, flash_msg) = match systemctl_sbot_cmd("start") {
|
||||
let (flash_name, flash_msg) = match sbot::system_sbot_cmd("start") {
|
||||
Ok(_) => (
|
||||
"flash_name=success".to_string(),
|
||||
"flash_msg=Sbot process has been started".to_string(),
|
||||
|
@ -1,7 +1,7 @@
|
||||
use log::info;
|
||||
use rouille::Response;
|
||||
|
||||
use crate::utils::{flash::FlashResponse, sbot::systemctl_sbot_cmd};
|
||||
use crate::utils::{flash::FlashResponse, sbot};
|
||||
|
||||
// ROUTE: /settings/scuttlebutt/stop
|
||||
|
||||
@ -10,7 +10,7 @@ use crate::utils::{flash::FlashResponse, sbot::systemctl_sbot_cmd};
|
||||
/// the attempt via a flash message.
|
||||
pub fn stop_sbot() -> Response {
|
||||
info!("Stopping go-sbot.service");
|
||||
let (flash_name, flash_msg) = match systemctl_sbot_cmd("stop") {
|
||||
let (flash_name, flash_msg) = match sbot::system_sbot_cmd("stop") {
|
||||
Ok(_) => (
|
||||
"flash_name=success".to_string(),
|
||||
"flash_msg=Sbot process has been stopped".to_string(),
|
||||
|
@ -1,5 +1,25 @@
|
||||
use maud::{html, PreEscaped, DOCTYPE};
|
||||
|
||||
/// JavaScript event listener for the back button on the top navigation bar of
|
||||
/// the UI.
|
||||
///
|
||||
/// When the button is clicked, prevent the default behaviour and invoke
|
||||
/// the history API to load the previous URL (page) in the history list.
|
||||
fn js_back_button_script() -> PreEscaped<String> {
|
||||
html! {
|
||||
(PreEscaped("
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.getElementById('backButton').onclick = function(e) {
|
||||
e.preventDefault();
|
||||
history.back();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Base template builder.
|
||||
///
|
||||
/// Takes an HTML body as input and splices it into the base template.
|
||||
@ -14,6 +34,7 @@ pub fn build_template(body: PreEscaped<String>, theme: String) -> PreEscaped<Str
|
||||
meta name="viewport" content="width=devide-width, initial-scale=1.0";
|
||||
link rel="stylesheet" href="/css/peachcloud.css";
|
||||
link rel="stylesheet" href="/css/_variables.css";
|
||||
(js_back_button_script())
|
||||
title { "PeachCloud" }
|
||||
}
|
||||
body {
|
||||
|
@ -38,7 +38,7 @@ pub fn build_template(
|
||||
html! {
|
||||
(PreEscaped("<!-- Top navigation bar -->"))
|
||||
nav class="nav-bar" {
|
||||
a class="nav-item" href=[back] title="Back" {
|
||||
a id="backButton" class="nav-item" href=[back] title="Back" {
|
||||
img class="icon-medium nav-icon-left icon-active" src="/icons/back.svg" alt="Back";
|
||||
}
|
||||
h1 class="nav-title" { (title) }
|
||||
|
64
peach-web/src/utils/cookie.rs
Normal file
64
peach-web/src/utils/cookie.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use rouille::{input, Request, Response};
|
||||
|
||||
// The CookieRequest and CookieResponse traits are currently only used
|
||||
// to add, retrieve and reset the `back_url` cookie. That cookie is
|
||||
// used to set the URL of the in-UI back button when visiting a page
|
||||
// which can be arrived at via several paths.
|
||||
//
|
||||
// An example of this is the Scuttlebutt Settings menu (/settings/scuttlebutt),
|
||||
// which can be accessed via the Settings menu (/settings) or the Scuttlebutt
|
||||
// Status page (/status/scuttlebutt). We need to be able to set the path of
|
||||
// the back button to point to the correct page (ie. the one from which we've
|
||||
// come).
|
||||
//
|
||||
// The `back_url` cookie is also used on the Profile page
|
||||
// (/scuttlebutt/profile).
|
||||
|
||||
/// Cookie trait for `Request`.
|
||||
pub trait CookieRequest {
|
||||
/// Retrieve a cookie value from a `Request`.
|
||||
fn retrieve_cookie(&self, cookie_name: &str) -> Option<&str>;
|
||||
}
|
||||
|
||||
impl CookieRequest for Request {
|
||||
fn retrieve_cookie(&self, cookie_name: &str) -> Option<&str> {
|
||||
// check for cookie using given name
|
||||
let cookie_val = input::cookies(self)
|
||||
.find(|&(n, _)| n == cookie_name)
|
||||
// return the value of the cookie (key is already known)
|
||||
.map(|key_val| key_val.1);
|
||||
|
||||
cookie_val
|
||||
}
|
||||
}
|
||||
|
||||
/// Cookie trait for `Response`.
|
||||
pub trait CookieResponse {
|
||||
/// Add a cookie containing the given data to a `Response`. Data should be
|
||||
/// in the form of `cookie_name=cookie_val`.
|
||||
fn add_cookie(self, cookie_name_val: &str) -> Response;
|
||||
/// Reset a cookie value for a `Response`.
|
||||
fn reset_cookie(self, cookie_name: &str) -> Response;
|
||||
}
|
||||
|
||||
impl CookieResponse for Response {
|
||||
fn add_cookie(self, cookie_name_val: &str) -> Response {
|
||||
// set the cookie header
|
||||
// max-age is currently set to 3600 seconds (1 hour)
|
||||
self.with_additional_header(
|
||||
"Set-Cookie",
|
||||
format!("{}; Max-Age=3600; SameSite=Lax; Path=/", cookie_name_val),
|
||||
)
|
||||
}
|
||||
|
||||
fn reset_cookie(self, cookie_name: &str) -> Response {
|
||||
// set a blank cookie to clear the cookie from the previous request
|
||||
self.with_additional_header(
|
||||
"Set-Cookie",
|
||||
format!(
|
||||
"{}=; Max-Age=0; SameSite=Lax; Path=/; Expires=Fri, 21 Aug 1987 12:00:00 UTC",
|
||||
cookie_name
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
pub mod cookie;
|
||||
pub mod flash;
|
||||
pub mod sbot;
|
||||
pub mod theme;
|
||||
|
@ -3,7 +3,6 @@ use std::{
|
||||
error::Error,
|
||||
fs,
|
||||
fs::File,
|
||||
io,
|
||||
io::prelude::*,
|
||||
path::Path,
|
||||
process::{Command, Output},
|
||||
@ -13,9 +12,10 @@ use async_std::task;
|
||||
use dirs;
|
||||
use futures::stream::TryStreamExt;
|
||||
use golgi::{
|
||||
api::friends::RelationshipQuery, blobs, messages::SsbMessageValue, sbot::Keystore, Sbot,
|
||||
api::friends::RelationshipQuery, blobs, messages::SsbMessageKVT, sbot::Keystore, Sbot,
|
||||
};
|
||||
use log::debug;
|
||||
use peach_lib::config_manager;
|
||||
use peach_lib::sbot::SbotConfig;
|
||||
use rouille::input::post::BufferedFile;
|
||||
use temporary::Directory;
|
||||
@ -24,22 +24,58 @@ use crate::{error::PeachWebError, utils::sbot};
|
||||
|
||||
// SBOT HELPER FUNCTIONS
|
||||
|
||||
/// Executes a systemctl command for the go-sbot.service process.
|
||||
pub fn systemctl_sbot_cmd(cmd: &str) -> io::Result<Output> {
|
||||
Command::new("sudo")
|
||||
.arg("systemctl")
|
||||
.arg(cmd)
|
||||
.arg("go-sbot.service")
|
||||
.output()
|
||||
/// On non-docker based deployments (peachcloud, yunohost), we use systemctl
|
||||
/// On docker-based deployments, we use supervisord
|
||||
/// This utility function calls the correct system calls based on these parameters.
|
||||
pub fn system_sbot_cmd(cmd: &str) -> Result<Output, PeachWebError> {
|
||||
let system_manager = config_manager::get_config_value("SYSTEM_MANAGER")?;
|
||||
match system_manager.as_str() {
|
||||
"systemd" => {
|
||||
let output = Command::new("sudo")
|
||||
.arg("systemctl")
|
||||
.arg(cmd)
|
||||
.arg(config_manager::get_config_value("GO_SBOT_SERVICE")?)
|
||||
.output()?;
|
||||
Ok(output)
|
||||
}
|
||||
"supervisord" => {
|
||||
match cmd {
|
||||
"enable" => {
|
||||
// TODO: implement this
|
||||
let output = Command::new("echo")
|
||||
.arg("implement this (enable)")
|
||||
.output()?;
|
||||
Ok(output)
|
||||
}
|
||||
"disable" => {
|
||||
let output = Command::new("echo")
|
||||
.arg("implement this (disable)")
|
||||
.output()?;
|
||||
Ok(output)
|
||||
}
|
||||
_ => {
|
||||
let output = Command::new("supervisorctl")
|
||||
.arg(cmd)
|
||||
.arg(config_manager::get_config_value("GO_SBOT_SERVICE")?)
|
||||
.output()?;
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Err(PeachWebError::System(format!(
|
||||
"Invalid configuration for SYSTEM_MANAGER: {:?}",
|
||||
system_manager
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes a systemctl stop command followed by start command.
|
||||
/// Executes a system stop command followed by start command.
|
||||
/// Returns a redirect with a flash message stating the output of the restart attempt.
|
||||
pub fn restart_sbot_process() -> (String, String) {
|
||||
debug!("Restarting go-sbot.service");
|
||||
match systemctl_sbot_cmd("stop") {
|
||||
match system_sbot_cmd("stop") {
|
||||
// if stop was successful, try to start the process
|
||||
Ok(_) => match systemctl_sbot_cmd("start") {
|
||||
Ok(_) => match system_sbot_cmd("start") {
|
||||
Ok(_) => (
|
||||
"success".to_string(),
|
||||
"Updated configuration and restarted the sbot process".to_string(),
|
||||
@ -68,15 +104,18 @@ pub async fn init_sbot_with_config(
|
||||
) -> Result<Sbot, PeachWebError> {
|
||||
debug!("Initialising an sbot client with configuration parameters");
|
||||
// initialise sbot connection with ip:port and shscap from config file
|
||||
let key_path = format!(
|
||||
"{}/secret",
|
||||
config_manager::get_config_value("GO_SBOT_DATADIR")?
|
||||
);
|
||||
let sbot_client = match sbot_config {
|
||||
// TODO: panics if we pass `Some(conf.shscap)` as second arg
|
||||
Some(conf) => {
|
||||
let ip_port = conf.lis.clone();
|
||||
Sbot::init(Keystore::GoSbot, Some(ip_port), None).await?
|
||||
Sbot::init(Keystore::CustomGoSbot(key_path), Some(ip_port), None).await?
|
||||
}
|
||||
None => Sbot::init(Keystore::GoSbot, None, None).await?,
|
||||
None => Sbot::init(Keystore::CustomGoSbot(key_path), None, None).await?,
|
||||
};
|
||||
|
||||
Ok(sbot_client)
|
||||
}
|
||||
|
||||
@ -130,7 +169,7 @@ pub fn latest_sequence_number() -> Result<u64, Box<dyn Error>> {
|
||||
let id = sbot_client.whoami().await?;
|
||||
|
||||
let history_stream = sbot_client.create_history_stream(id).await?;
|
||||
let mut msgs: Vec<SsbMessageValue> = history_stream.try_collect().await?;
|
||||
let mut msgs: Vec<SsbMessageKVT> = history_stream.try_collect().await?;
|
||||
|
||||
// there will be zero messages when the sbot is run for the first time
|
||||
if msgs.is_empty() {
|
||||
@ -140,7 +179,7 @@ pub fn latest_sequence_number() -> Result<u64, Box<dyn Error>> {
|
||||
msgs.reverse();
|
||||
|
||||
// return the sequence number of the latest msg
|
||||
Ok(msgs[0].sequence)
|
||||
Ok(msgs[0].value.sequence)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -153,7 +192,13 @@ pub fn create_invite(uses: u16) -> Result<String, Box<dyn Error>> {
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config).await?;
|
||||
|
||||
debug!("Generating Scuttlebutt invite code");
|
||||
let invite_code = sbot_client.invite_create(uses).await?;
|
||||
let mut invite_code = sbot_client.invite_create(uses).await?;
|
||||
|
||||
// insert domain into invite if one is configured
|
||||
let domain = config_manager::get_config_value("EXTERNAL_DOMAIN")?;
|
||||
if !domain.is_empty() {
|
||||
invite_code = domain + &invite_code[4..];
|
||||
}
|
||||
|
||||
Ok(invite_code)
|
||||
})
|
||||
|
Reference in New Issue
Block a user