86 Commits

Author SHA1 Message Date
ec93c9830b Formatting 2022-07-28 14:58:24 +02:00
f6c30a327a Remove debug statements 2022-07-28 14:56:27 +02:00
dbf1c648ae Working in docker 2022-07-28 14:53:45 +02:00
8d9aeb6662 Change default name of go-sbot.service to go-sbot 2022-07-28 14:01:19 +02:00
b24fb387b9 Fix supervisorctl 2022-07-27 19:03:36 +02:00
ceb7e502ce modifications to system commands 2022-07-20 14:26:37 +02:00
6407495292 Merge pull request 'Update go-sbot systemctl commands (remove --user)' (#132) from fix_systemctl_calls into main
Reviewed-on: #132
2022-07-15 09:55:06 +00:00
05c1577f2a upgrade probes version to avoid precise_time error
All checks were successful
continuous-integration/drone/pr Build is passing
2022-07-15 09:00:31 +01:00
add169db07 bump patch version
Some checks failed
continuous-integration/drone/pr Build is failing
2022-07-14 08:57:34 +01:00
fcb17d6802 remove --user and add sudo for systemctl calls 2022-07-14 08:57:22 +01:00
01138eef35 Merge branch 'main' of https://git.coopcloud.tech/PeachCloud/peach-workspace into main7 2022-07-11 13:17:34 +02:00
2637b28380 Bump peach-config to v0.1.26 2022-07-11 13:17:24 +02:00
03a0a51f4d Merge pull request 'Add publish-address function to peach-config' (#130) from publish-address into main
Reviewed-on: #130
2022-07-11 10:25:09 +00:00
eddb167c4c Remove KVT to Value map and retrieve sequence number directly from KVT
All checks were successful
continuous-integration/drone/pr Build is passing
2022-07-07 15:54:19 +01:00
9704269c8a Remove Cargo.lock from .gitignore 2022-07-07 15:44:31 +01:00
466db8ceea Update sbot.rs to new history_stream api
All checks were successful
continuous-integration/drone/pr Build is passing
2022-07-07 13:42:32 +02:00
90badbfe30 Add cargo.lock to .gitignore
Some checks failed
continuous-integration/drone/pr Build is failing
2022-07-07 10:57:27 +02:00
7489916d5f Remove cargo.lock 2022-07-07 10:57:03 +02:00
7daab74b37 Fix golgi import
Some checks failed
continuous-integration/drone/pr Build is failing
2022-07-07 10:51:44 +02:00
58bf306d3b Fix golgi import
Some checks failed
continuous-integration/drone/pr Build is failing
2022-07-07 10:49:49 +02:00
bdac23092a Change changepassword to change-password
Some checks failed
continuous-integration/drone/pr Build is failing
2022-07-07 10:41:36 +02:00
f1ab2caa08 Fix golgi imports
Some checks failed
continuous-integration/drone/pr Build is failing
2022-07-06 12:39:55 +02:00
1fab4f3c43 Merge branch 'main' of https://git.coopcloud.tech/PeachCloud/peach-workspace into main7
Some checks failed
continuous-integration/drone/pr Build is failing
2022-07-06 12:34:45 +02:00
8dcd594dd7 Publish address 2022-07-06 12:34:24 +02:00
fcaa9e29c4 Merge pull request 'Add whoami command to peach-config' (#128) from who-am-i into main
Reviewed-on: #128
2022-07-05 13:10:26 +00:00
c6fc5c2992 Merge branch 'main' into who-am-i
All checks were successful
continuous-integration/drone/pr Build is passing
2022-07-05 12:52:21 +00:00
1258a3697d Cargo fmt
All checks were successful
continuous-integration/drone/pr Build is passing
2022-07-04 14:54:00 +02:00
7e94135839 Whoami
Some checks failed
continuous-integration/drone/pr Build is failing
2022-07-04 14:35:17 +02:00
f002f5cf3e Working on peach-config 2022-07-04 13:23:55 +02:00
fba1e91d8b Merge pull request 'Insert domain into invite' (#125) from insert-invite into main
Reviewed-on: #125
2022-06-30 13:00:46 +00:00
6621a09ec9 Insert domain into invite
All checks were successful
continuous-integration/drone/pr Build is passing
2022-06-29 16:23:41 -04:00
170b037248 Merge pull request 'Update configuration sections of README' (#122) from update_readme into main
Reviewed-on: #122
2022-06-28 10:37:12 +00:00
251aaf9237 merge main
All checks were successful
continuous-integration/drone/pr Build is passing
2022-06-28 09:16:09 +01:00
123ebc06cc Merge pull request 'Introduce JS for improved back button behaviour' (#121) from js_back_button into main
Reviewed-on: #121
2022-06-28 08:14:42 +00:00
9ce27d17c5 merge main
All checks were successful
continuous-integration/drone/pr Build is passing
2022-06-28 09:13:54 +01:00
2a8cf4ecfb update configuration sections
All checks were successful
continuous-integration/drone/pr Build is passing
2022-06-27 09:01:00 +01:00
d1a55e29d7 bump patch version and update lockfile
All checks were successful
continuous-integration/drone/pr Build is passing
2022-06-23 12:02:09 +01:00
4568577f81 add js to overlay back button functionality 2022-06-23 12:01:18 +01:00
ab0e27c14d Merge pull request 'Improve back button behaviour' (#120) from improved_back_button into main
Reviewed-on: #120
2022-06-23 11:00:21 +00:00
65b5f95a90 update lockfile
All checks were successful
continuous-integration/drone/pr Build is passing
2022-06-21 12:14:30 +01:00
a60d892e95 bump the patch version 2022-06-21 12:14:14 +01:00
5bd8a68ddf set, retrieve and reset back_url cookies 2022-06-21 12:13:36 +01:00
a6f52ce384 add cookie utils module for requests and responses 2022-06-21 12:13:04 +01:00
c71cc3992d Merge pull request 'Configure EBT replication setting' (#118) from enable_ebt_replication into main
Reviewed-on: #118
2022-06-16 11:04:39 +00:00
56fafc8d67 bump manifest version and add max as author
All checks were successful
continuous-integration/drone/pr Build is passing
2022-06-16 11:41:50 +01:00
414508f8ff merge changes from main 2022-06-16 11:40:35 +01:00
6ad5c620c1 Merge pull request 'URL encode SSB public keys on profile page' (#116) from fix_encoding_decoding into main
Reviewed-on: #116
2022-06-16 10:38:11 +00:00
76d5e6a355 fix probes version
All checks were successful
continuous-integration/drone/pr Build is passing
2022-06-16 11:19:04 +01:00
11e94fa421 merge changes from main
Some checks failed
continuous-integration/drone/pr Build is failing
2022-06-16 11:10:32 +01:00
216b60b86a bump version in manifest 2022-06-16 11:07:44 +01:00
a70f5e227d Merge pull request 'Fix link to Set New Password route' (#117) from fix_set_new_passwork_link into main
Reviewed-on: #117
2022-06-16 10:02:12 +00:00
cddcb8f9bd fix spacing in manifest
All checks were successful
continuous-integration/drone/pr Build is passing
2022-06-16 09:19:59 +01:00
8b33f8c174 Merge branch 'fix_set_new_passwork_link' of ssh://git.coopcloud.tech:2222/PeachCloud/peach-workspace into fix_set_new_passwork_link 2022-06-16 09:19:04 +01:00
1b43dc8b18 bump patch version 2022-06-16 09:18:38 +01:00
2d7b74d377 Merge branch 'main' into fix_set_new_passwork_link
All checks were successful
continuous-integration/drone/pr Build is passing
2022-06-16 08:14:45 +00:00
6b34864289 Merge pull request 'Change go-sbot.service name to be configurable' (#113) from go-sbot-service into main
Reviewed-on: #113
2022-06-15 12:03:29 +00:00
87ad2439b9 Fix cargo fmt
All checks were successful
continuous-integration/drone/pr Build is passing
2022-06-15 13:46:51 +02:00
5838faf128 Cargo fmt
Some checks failed
continuous-integration/drone/pr Build is failing
2022-06-15 13:37:36 +02:00
9c6fa00ec7 Fix cargo.lock
Some checks failed
continuous-integration/drone/pr Build is failing
2022-06-15 13:33:56 +02:00
a81b8b42cf Use main golgi branch 2022-06-15 12:48:51 +02:00
cdcff3475c Fix cargo.lock
Some checks failed
continuous-integration/drone/pr Build is failing
2022-06-15 12:47:11 +02:00
077c2a9178 Fix clippy errors 2022-06-15 12:44:34 +02:00
8b0b872d21 Reponse to CR 2022-06-15 12:36:44 +02:00
218a70b8f8 add lockfile
All checks were successful
continuous-integration/drone/pr Build is passing
2022-06-15 08:58:45 +01:00
50dcb2cf9e add checkbox to enable / disable ebt 2022-06-15 08:58:21 +01:00
e1877b5024 fix link to set new password
All checks were successful
continuous-integration/drone/pr Build is passing
2022-06-15 08:46:16 +01:00
0923c24693 update lockfile
All checks were successful
continuous-integration/drone/pr Build is passing
2022-06-14 09:03:24 +01:00
f3d4ba9fe5 url-encode the ssb id for profile buttons 2022-06-14 09:03:08 +01:00
16e6d42f87 Cargo clippy
Some checks failed
continuous-integration/drone/pr Build is failing
2022-05-26 23:19:31 +02:00
3493e5adb9 Change sbot.rs to use configurable go-sbot service name 2022-05-26 23:18:22 +02:00
5147eed497 Change sbot.rs to use configurable go-sbot service name 2022-05-26 23:17:20 +02:00
9f6ba14123 Cargo fmt
Some checks failed
continuous-integration/drone/pr Build is failing
2022-05-25 14:50:41 +02:00
21fb29c322 Working sbot
Some checks failed
continuous-integration/drone/pr Build is failing
2022-05-25 14:49:05 +02:00
6c9e5fd3fd Merge branch 'go-sbot-service' of https://git.coopcloud.tech/PeachCloud/peach-workspace into change-paths
All checks were successful
continuous-integration/drone/pr Build is passing
2022-05-25 12:26:11 +02:00
3adb226969 Bump version number 2022-05-25 12:25:54 +02:00
92f516b161 Merge pull request 'Fix lockfile' (#114) from fix-lock into go-sbot-service
Some checks reported errors
continuous-integration/drone/pr Build was killed
Reviewed-on: #114
2022-05-20 14:33:18 +00:00
543470b949 Fix lockfile
Some checks reported errors
continuous-integration/drone/pr Build was killed
2022-05-20 16:28:55 +02:00
6434471599 Bump version numbers
All checks were successful
continuous-integration/drone/pr Build is passing
2022-05-20 13:55:21 +02:00
56c142a387 Make go-sbot.service name configurable
All checks were successful
continuous-integration/drone/pr Build is passing
2022-05-20 13:35:37 +02:00
7deaa00d6e Fix hardcoded path
All checks were successful
continuous-integration/drone/pr Build is passing
2022-05-20 13:31:06 +02:00
bf7f2c8e31 Merge pull request 'Update golgi init functions with keystore selector' (#107) from keystore_selector into main
Reviewed-on: #107
2022-05-16 13:12:00 +00:00
dc79833e2b merge wpactrl and golgi api updates
All checks were successful
continuous-integration/drone/pr Build is passing
2022-05-13 13:15:12 +02:00
b0b79fef24 fix probes:0.3.0 lockfile error
Some checks failed
continuous-integration/drone/pr Build is failing
2022-05-13 12:44:51 +02:00
98497fa5ae merge latest changes from main
Some checks failed
continuous-integration/drone/pr Build is failing
2022-05-13 12:35:37 +02:00
827ccbd4dc update lockfile
Some checks failed
continuous-integration/drone/pr Build is failing
2022-05-11 16:42:32 +02:00
c21e2d090c introduce keystore selector for golgi 2022-05-11 16:42:11 +02:00
30 changed files with 756 additions and 537 deletions

687
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,5 @@
[workspace] [workspace]
members = [ members = [
"peach-buttons",
"peach-oled", "peach-oled",
"peach-lib", "peach-lib",
"peach-config", "peach-config",
@ -13,3 +11,4 @@ members = [
"peach-jsonrpc-server", "peach-jsonrpc-server",
"peach-dyndns-updater" "peach-dyndns-updater"
] ]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "peach-config" name = "peach-config"
version = "0.1.25" version = "0.1.26"
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"
@ -37,3 +37,5 @@ log = "0.4"
lazy_static = "1.4.0" lazy_static = "1.4.0"
peach-lib = { path = "../peach-lib" } peach-lib = { path = "../peach-lib" }
rpassword = "5.0" rpassword = "5.0"
golgi = { git = "https://git.coopcloud.tech/golgi-ssb/golgi.git" }
async-std = "1.10.0"

View File

@ -1,4 +1,5 @@
#![allow(clippy::nonstandard_macro_braces)] #![allow(clippy::nonstandard_macro_braces)]
use golgi::error::GolgiError;
use peach_lib::error::PeachError; use peach_lib::error::PeachError;
pub use snafu::ResultExt; pub use snafu::ResultExt;
use snafu::Snafu; use snafu::Snafu;
@ -35,6 +36,12 @@ pub enum PeachConfigError {
ChangePasswordError { source: PeachError }, ChangePasswordError { source: PeachError },
#[snafu(display("Entered passwords did not match. Please try again."))] #[snafu(display("Entered passwords did not match. Please try again."))]
InvalidPassword, 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 { impl From<std::io::Error> for PeachConfigError {
@ -51,3 +58,15 @@ impl From<serde_json::Error> for PeachConfigError {
PeachConfigError::SerdeError { source: err } 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 }
}
}

View File

@ -2,10 +2,12 @@ mod change_password;
mod constants; mod constants;
mod error; mod error;
mod generate_manifest; mod generate_manifest;
mod publish_address;
mod set_permissions; mod set_permissions;
mod setup_networking; mod setup_networking;
mod setup_peach; mod setup_peach;
mod setup_peach_deb; mod setup_peach_deb;
mod status;
mod update; mod update;
mod utils; mod utils;
@ -44,12 +46,21 @@ enum PeachConfig {
Update(UpdateOpts), Update(UpdateOpts),
/// Changes the password for the peach-web interface /// Changes the password for the peach-web interface
#[structopt(name = "changepassword")] #[structopt(name = "change-password")]
ChangePassword(ChangePasswordOpts), ChangePassword(ChangePasswordOpts),
/// Updates file permissions on PeachCloud device /// Updates file permissions on PeachCloud device
#[structopt(name = "permissions")] #[structopt(name = "permissions")]
SetPermissions, 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)] #[derive(StructOpt, Debug)]
@ -90,6 +101,13 @@ pub struct ChangePasswordOpts {
password: Option<String>, password: Option<String>,
} }
#[derive(StructOpt, Debug)]
pub struct PublishAddressOpts {
/// Specify address in the form domain:port
#[structopt(short, long)]
address: String,
}
arg_enum! { arg_enum! {
/// enum options for real-time clock choices /// enum options for real-time clock choices
#[derive(Debug)] #[derive(Debug)]
@ -102,7 +120,7 @@ arg_enum! {
} }
} }
fn main() { async fn run() {
// initialize the logger // initialize the logger
env_logger::init(); 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;
}

View 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(())
}

View File

@ -1,17 +1,17 @@
use lazy_static::lazy_static; use lazy_static::lazy_static;
use peach_lib::config_manager::get_config_value; use peach_lib::config_manager;
use crate::error::PeachConfigError; use crate::error::PeachConfigError;
use crate::utils::cmd; use crate::utils::cmd;
lazy_static! { 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"); .expect("Failed to load config value for PEACH_CONFIGDIR");
pub static ref PEACH_WEBDIR: String = pub static ref PEACH_WEBDIR: String = config_manager::get_config_value("PEACH_WEBDIR")
get_config_value("PEACH_WEBDIR").expect("Failed to load config value for PEACH_WEBDIR"); .expect("Failed to load config value for PEACH_WEBDIR");
pub static ref PEACH_HOMEDIR: String = pub static ref PEACH_HOMEDIR: String = config_manager::get_config_value("PEACH_HOMEDIR")
get_config_value("PEACH_HOMEDIR").expect("Failed to load config value for PEACH_HOMEDIR"); .expect("Failed to load config value for PEACH_HOMEDIR");
} }
/// Utility function to set correct file permissions on the PeachCloud device. /// Utility function to set correct file permissions on the PeachCloud device.

View 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)
}

View File

@ -1,6 +1,6 @@
[package] [package]
name = "peach-lib" name = "peach-lib"
version = "1.3.3" version = "1.3.5"
authors = ["Andrew Reid <glyph@mycelial.technology>"] authors = ["Andrew Reid <glyph@mycelial.technology>"]
edition = "2018" edition = "2018"
@ -9,7 +9,7 @@ async-std = "1.10"
chrono = "0.4" chrono = "0.4"
dirs = "4.0" dirs = "4.0"
fslock="0.1" 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-core = "0.5"
jsonrpc-client-http = "0.5" jsonrpc-client-http = "0.5"
jsonrpc-core = "8.0" jsonrpc-core = "8.0"

View File

@ -57,9 +57,11 @@ pub fn get_peach_config_defaults() -> HashMap<String, String> {
("DYN_NAMESERVER", "ns.peachcloud.org"), ("DYN_NAMESERVER", "ns.peachcloud.org"),
("DYN_ENABLED", "false"), ("DYN_ENABLED", "false"),
("SSB_ADMIN_IDS", ""), ("SSB_ADMIN_IDS", ""),
("SYSTEM_MANAGER", "systemd"),
("ADMIN_PASSWORD_HASH", "47"), ("ADMIN_PASSWORD_HASH", "47"),
("TEMPORARY_PASSWORD_HASH", ""), ("TEMPORARY_PASSWORD_HASH", ""),
("GO_SBOT_DATADIR", "/home/peach/.ssb-go"), ("GO_SBOT_DATADIR", "/home/peach/.ssb-go"),
("GO_SBOT_SERVICE", "go-sbot"),
("PEACH_CONFIGDIR", "/var/lib/peachcloud"), ("PEACH_CONFIGDIR", "/var/lib/peachcloud"),
("PEACH_HOMEDIR", "/home/peach"), ("PEACH_HOMEDIR", "/home/peach"),
("PEACH_WEBDIR", "/usr/share/peach-web"), ("PEACH_WEBDIR", "/usr/share/peach-web"),
@ -131,7 +133,10 @@ pub fn save_peach_config_to_disc(
peach_config: HashMap<String, String>, peach_config: HashMap<String, String>,
) -> Result<HashMap<String, String>, PeachError> { ) -> Result<HashMap<String, String>, PeachError> {
// use a file lock to avoid race conditions while saving config // 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()?; lock.lock()?;
// first convert Hashmap to BTreeMap (so that keys are saved in deterministic alphabetical order) // first convert Hashmap to BTreeMap (so that keys are saved in deterministic alphabetical order)

View File

@ -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. //! 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}; use std::{io, str, string};
/// This type represents all possible errors that can occur when interacting with the PeachCloud library. /// This type represents all possible errors that can occur when interacting with the PeachCloud library.
@ -102,6 +103,13 @@ pub enum PeachError {
/// The file path for the write attempt. /// The file path for the write attempt.
path: String, 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 { impl std::error::Error for PeachError {
@ -130,6 +138,8 @@ impl std::error::Error for PeachError {
PeachError::Utf8ToStr(_) => None, PeachError::Utf8ToStr(_) => None,
PeachError::Utf8ToString(_) => None, PeachError::Utf8ToString(_) => None,
PeachError::Write { ref source, .. } => Some(source), 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, .. } => { PeachError::Write { ref path, .. } => {
write!(f, "Write error: {}", 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) PeachError::Utf8ToString(err)
} }
} }
impl From<GolgiError> for PeachError {
fn from(err: GolgiError) -> PeachError {
PeachError::Golgi(err)
}
}

View File

@ -2,7 +2,10 @@
use std::{fs, fs::File, io, io::Write, path::PathBuf, process::Command, str}; 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 serde::{Deserialize, Serialize};
use crate::error::PeachError; use crate::error::PeachError;
@ -61,13 +64,48 @@ impl Default for SbotStatus {
impl SbotStatus { impl SbotStatus {
/// Retrieve statistics for the go-sbot systemd process by querying `systemctl`. /// Retrieve statistics for the go-sbot systemd process by querying `systemctl`.
pub fn read() -> Result<Self, PeachError> { 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(); let mut status = SbotStatus::default();
// note this command does not need to be run as sudo // note this command does not need to be run as sudo
// because non-privileged users are able to run systemctl show // because non-privileged users are able to run systemctl show
let info_output = Command::new("systemctl") let info_output = Command::new("systemctl")
.arg("show") .arg("show")
.arg("go-sbot.service") .arg(config_manager::get_config_value("GO_SBOT_SERVICE")?)
.arg("--no-page") .arg("--no-page")
.output()?; .output()?;
@ -89,7 +127,7 @@ impl SbotStatus {
// because non-privileged users are able to run systemctl status // because non-privileged users are able to run systemctl status
let status_output = Command::new("systemctl") let status_output = Command::new("systemctl")
.arg("status") .arg("status")
.arg("go-sbot.service") .arg(config_manager::get_config_value("GO_SBOT_SERVICE")?)
.output()?; .output()?;
let service_status = str::from_utf8(&status_output.stdout)?; let service_status = str::from_utf8(&status_output.stdout)?;
@ -128,7 +166,10 @@ impl SbotStatus {
} }
// get path to blobstore // 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 // determine the size of the blobstore directory in bytes
status.blobstore = dir_size(blobstore_path).ok(); status.blobstore = dir_size(blobstore_path).ok();
@ -199,9 +240,11 @@ impl Default for SbotConfig {
impl SbotConfig { impl SbotConfig {
/// Read the go-sbot `config.toml` file from file and deserialize into `SbotConfig`. /// Read the go-sbot `config.toml` file from file and deserialize into `SbotConfig`.
pub fn read() -> Result<Self, PeachError> { pub fn read() -> Result<Self, PeachError> {
// determine path of user's home directory // determine path of user's go-sbot config.toml
let mut config_path = dirs::home_dir().ok_or(PeachError::HomeDir)?; let config_path = format!(
config_path.push(".ssb-go/config.toml"); "{}/config.toml",
config_manager::get_config_value("GO_SBOT_DATADIR")?
);
let config_contents = fs::read_to_string(config_path)?; let config_contents = fs::read_to_string(config_path)?;
@ -217,8 +260,11 @@ impl SbotConfig {
// convert the provided `SbotConfig` instance to a string // convert the provided `SbotConfig` instance to a string
let config_string = toml::to_string(&config)?; let config_string = toml::to_string(&config)?;
// determine path of user's home directory // determine path of user's go-sbot config.toml
let config_path = format!("{}/config.toml", get_config_value("GO_SBOT_DATADIR")?); let config_path = format!(
"{}/config.toml",
config_manager::get_config_value("GO_SBOT_DATADIR")?
);
// open config file for writing // open config file for writing
let mut file = File::create(config_path)?; let mut file = File::create(config_path)?;
@ -232,3 +278,25 @@ impl SbotConfig {
Ok(()) 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)
}

View File

@ -1,6 +1,6 @@
[package] [package]
name = "peach-stats" name = "peach-stats"
version = "0.3.0" version = "0.3.1"
authors = ["Andrew Reid <glyph@mycelial.technology>"] authors = ["Andrew Reid <glyph@mycelial.technology>"]
edition = "2018" edition = "2018"
description = "Query system statistics. Provides a wrapper around the probes and systemstat crates." description = "Query system statistics. Provides a wrapper around the probes and systemstat crates."

View File

@ -44,8 +44,8 @@ impl SbotStat {
pub fn sbot_stats() -> Result<SbotStat, StatsError> { pub fn sbot_stats() -> Result<SbotStat, StatsError> {
let mut status = SbotStat::default(); let mut status = SbotStat::default();
let info_output = Command::new("/usr/bin/systemctl") let info_output = Command::new("sudo")
.arg("--user") .arg("systemctl")
.arg("show") .arg("show")
.arg("go-sbot.service") .arg("go-sbot.service")
.arg("--no-page") .arg("--no-page")
@ -66,8 +66,8 @@ pub fn sbot_stats() -> Result<SbotStat, StatsError> {
} }
} }
let status_output = Command::new("/usr/bin/systemctl") let status_output = Command::new("sudo")
.arg("--user") .arg("systemctl")
.arg("status") .arg("status")
.arg("go-sbot.service") .arg("go-sbot.service")
.output() .output()

View File

@ -1,7 +1,7 @@
[package] [package]
name = "peach-web" name = "peach-web"
version = "0.6.13" version = "0.6.19"
authors = ["Andrew Reid <gnomad@cryptolab.net>"] authors = ["Andrew Reid <gnomad@cryptolab.net>", "Max Fowler <max@mfowler.info>"]
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."
homepage = "https://opencollective.com/peachcloud" homepage = "https://opencollective.com/peachcloud"

View File

@ -1,6 +1,6 @@
# peach-web # peach-web
![Generic badge](https://img.shields.io/badge/version-0.6.0-<COLOR>.svg) ![Generic badge](https://img.shields.io/badge/version-0.6.18-<COLOR>.svg)
## Web Interface for PeachCloud ## 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. 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._ _Note: This is a work-in-progress._
@ -36,11 +36,46 @@ Run the binary:
`../target/release/peach-web` `../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 ### 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 ### Authentication
@ -56,6 +91,14 @@ Logging is made available with `env_logger`:
Other logging levels include `debug`, `warn` and `error`. 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 ## 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. 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` `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 ## 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. `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.

View File

@ -18,6 +18,7 @@ pub enum PeachWebError {
Json(JsonError), Json(JsonError),
OsString, OsString,
PeachLib { source: PeachError, msg: String }, PeachLib { source: PeachError, msg: String },
System(String),
Yaml(YamlError), Yaml(YamlError),
} }
@ -31,6 +32,7 @@ impl std::error::Error for PeachWebError {
PeachWebError::Json(ref source) => Some(source), PeachWebError::Json(ref source) => Some(source),
PeachWebError::OsString => None, PeachWebError::OsString => None,
PeachWebError::PeachLib { ref source, .. } => Some(source), PeachWebError::PeachLib { ref source, .. } => Some(source),
PeachWebError::System(_) => None,
PeachWebError::Yaml(ref source) => Some(source), 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" "Filesystem error: failed to convert OsString to String for go-ssb directory path"
), ),
PeachWebError::PeachLib { ref source, .. } => write!(f, "{}", source), 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), PeachWebError::Yaml(ref source) => write!(f, "Serde YAML error: {}", source),
} }
} }

View File

@ -1,6 +1,10 @@
use rouille::{router, Request, Response}; 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() // TODO: add mount_peachcloud_routes()
// https://github.com/tomaka/rouille/issues/232#issuecomment-919225104 // https://github.com/tomaka/rouille/issues/232#issuecomment-919225104
@ -22,6 +26,8 @@ pub fn mount_peachpub_routes(
router!(request, router!(request,
(GET) (/) => { (GET) (/) => {
Response::html(routes::home::build_template()) Response::html(routes::home::build_template())
// reset the back_url cookie each time we visit the homepage
.reset_cookie("back_url")
}, },
(GET) (/auth/change) => { (GET) (/auth/change) => {
@ -49,6 +55,9 @@ pub fn mount_peachpub_routes(
(GET) (/scuttlebutt/blocks) => { (GET) (/scuttlebutt/blocks) => {
Response::html(routes::scuttlebutt::blocks::build_template()) 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) => { (POST) (/scuttlebutt/follow) => {
@ -57,10 +66,16 @@ pub fn mount_peachpub_routes(
(GET) (/scuttlebutt/follows) => { (GET) (/scuttlebutt/follows) => {
Response::html(routes::scuttlebutt::follows::build_template()) 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) => { (GET) (/scuttlebutt/friends) => {
Response::html(routes::scuttlebutt::friends::build_template()) 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) => { (GET) (/scuttlebutt/invites) => {
@ -117,6 +132,9 @@ pub fn mount_peachpub_routes(
(POST) (/scuttlebutt/search) => { (POST) (/scuttlebutt/search) => {
routes::scuttlebutt::search::handle_form(request) 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) => { (POST) (/scuttlebutt/unblock) => {
@ -187,7 +205,7 @@ pub fn mount_peachpub_routes(
}, },
(GET) (/status/scuttlebutt) => { (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 // render the not_found template and set a 404 status code if none of

View File

@ -30,7 +30,7 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
form id="sendPasswordReset" action="/auth/temporary" method="post" { form id="sendPasswordReset" action="/auth/temporary" method="post" {
div id="buttonDiv" { 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)"; 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" "Set New Password"
} }
} }

View File

@ -4,7 +4,7 @@ use rouille::Request;
use crate::{ use crate::{
templates, templates,
utils::{flash::FlashRequest, sbot, sbot::Profile, theme}, utils::{cookie::CookieRequest, flash::FlashRequest, sbot, sbot::Profile, theme},
}; };
// ROUTE: /scuttlebutt/profile // ROUTE: /scuttlebutt/profile
@ -83,13 +83,15 @@ fn social_interaction_buttons_template(profile: &Profile) -> Markup {
@match (profile.following, &profile.id) { @match (profile.following, &profile.id) {
(Some(false), Some(ssb_id)) => { (Some(false), Some(ssb_id)) => {
form id="followForm" class="center" action="/scuttlebutt/follow" method="post" { 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"; input id="followPeer" class="button button-primary center" type="submit" title="Follow Peer" value="Follow";
} }
}, },
(Some(true), Some(ssb_id)) => { (Some(true), Some(ssb_id)) => {
form id="unfollowForm" class="center" action="/scuttlebutt/unfollow" method="post" { 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"; 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) { @match (profile.blocking, &profile.id) {
(Some(false), Some(ssb_id)) => { (Some(false), Some(ssb_id)) => {
form id="blockForm" class="center" action="/scuttlebutt/block" method="post" { 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"; input id="blockPeer" class="button button-primary center" type="submit" title="Block Peer" value="Block";
} }
}, },
(Some(true), Some(ssb_id)) => { (Some(true), Some(ssb_id)) => {
form id="unblockForm" class="center" action="/scuttlebutt/unblock" method="post" { 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"; 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 { @if let Some(ssb_id) = &profile.id {
form class="center" { 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" "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."), _ => 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 // query the current theme so we can pass it into the base template builder
let theme = theme::get_theme(); let theme = theme::get_theme();

View File

@ -122,6 +122,15 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
input type="text" id="database_dir" name="repo" value=(sbot_config.repo); input type="text" id="database_dir" name="repo" value=(sbot_config.repo);
} }
div class="center" { 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 { @if sbot_config.localadv {
input type="checkbox" id="lanBroadcast" style="margin-bottom: 1rem;" name="localadv" checked; input type="checkbox" id="lanBroadcast" style="margin-bottom: 1rem;" name="localadv" checked;
} @else { } @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="hmac" name="hmac" value=(sbot_config.hmac);
input type="hidden" id="wslis" name="wslis" value=(sbot_config.wslis); 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="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="promisc" name="promisc" value=(sbot_config.promisc);
input type="hidden" id="nounixsock" name="nounixsock" value=(sbot_config.nounixsock); input type="hidden" id="nounixsock" name="nounixsock" value=(sbot_config.nounixsock);
(PreEscaped("<!-- BUTTONS -->")) (PreEscaped("<!-- BUTTONS -->"))
@ -175,8 +183,11 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
// wrap the nav bars around the settings menu template content // wrap the nav bars around the settings menu template content
// parameters are template, title and back url // parameters are template, title and back url
let body = let body = templates::nav::build_template(
templates::nav::build_template(menu_template, "Scuttlebutt Settings", Some("/settings")); menu_template,
"Scuttlebutt Settings",
Some("/settings/scuttlebutt"),
);
// query the current theme so we can pass it into the base template builder // query the current theme so we can pass it into the base template builder
let theme = theme::get_theme(); let theme = theme::get_theme();
@ -232,13 +243,13 @@ pub fn handle_form(request: &Request, restart: bool) -> Response {
match data.startup { match data.startup {
true => { true => {
debug!("Enabling go-sbot.service"); 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) warn!("Failed to enable go-sbot.service: {}", e)
} }
} }
false => { false => {
debug!("Disabling go-sbot.service"); 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) warn!("Failed to disable go-sbot.service: {}", e)
} }
} }

View File

@ -4,7 +4,7 @@ use rouille::Request;
use crate::{ use crate::{
templates, templates,
utils::{flash::FlashRequest, theme}, utils::{cookie::CookieRequest, flash::FlashRequest, theme},
}; };
/// Read the status of the go-sbot service and render buttons accordingly. /// 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 // wrap the nav bars around the settings menu template content
// parameters are template, title and back url // parameters are template, title and back url
let body = let body = templates::nav::build_template(menu_template, "Scuttlebutt Settings", back_url);
templates::nav::build_template(menu_template, "Scuttlebutt Settings", Some("/settings"));
// query the current theme so we can pass it into the base template builder // query the current theme so we can pass it into the base template builder
let theme = theme::get_theme(); let theme = theme::get_theme();

View File

@ -1,7 +1,7 @@
use log::info; use log::info;
use rouille::Response; use rouille::Response;
use crate::utils::{flash::FlashResponse, sbot::systemctl_sbot_cmd}; use crate::utils::{flash::FlashResponse, sbot};
// ROUTE: /settings/scuttlebutt/restart // ROUTE: /settings/scuttlebutt/restart
@ -10,9 +10,9 @@ use crate::utils::{flash::FlashResponse, sbot::systemctl_sbot_cmd};
/// the attempt via a flash message. /// the attempt via a flash message.
pub fn restart_sbot() -> Response { pub fn restart_sbot() -> Response {
info!("Restarting go-sbot.service"); 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 // if stop was successful, try to start the process
Ok(_) => match systemctl_sbot_cmd("start") { Ok(_) => match sbot::system_sbot_cmd("start") {
Ok(_) => ( Ok(_) => (
"flash_name=success".to_string(), "flash_name=success".to_string(),
"flash_msg=Sbot process has been restarted".to_string(), "flash_msg=Sbot process has been restarted".to_string(),

View File

@ -1,7 +1,7 @@
use log::info; use log::info;
use rouille::Response; use rouille::Response;
use crate::utils::{flash::FlashResponse, sbot::systemctl_sbot_cmd}; use crate::utils::{flash::FlashResponse, sbot};
// ROUTE: /settings/scuttlebutt/start // ROUTE: /settings/scuttlebutt/start
@ -10,7 +10,7 @@ use crate::utils::{flash::FlashResponse, sbot::systemctl_sbot_cmd};
/// the attempt via a flash message. /// the attempt via a flash message.
pub fn start_sbot() -> Response { pub fn start_sbot() -> Response {
info!("Starting go-sbot.service"); 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(_) => ( Ok(_) => (
"flash_name=success".to_string(), "flash_name=success".to_string(),
"flash_msg=Sbot process has been started".to_string(), "flash_msg=Sbot process has been started".to_string(),

View File

@ -1,7 +1,7 @@
use log::info; use log::info;
use rouille::Response; use rouille::Response;
use crate::utils::{flash::FlashResponse, sbot::systemctl_sbot_cmd}; use crate::utils::{flash::FlashResponse, sbot};
// ROUTE: /settings/scuttlebutt/stop // ROUTE: /settings/scuttlebutt/stop
@ -10,7 +10,7 @@ use crate::utils::{flash::FlashResponse, sbot::systemctl_sbot_cmd};
/// the attempt via a flash message. /// the attempt via a flash message.
pub fn stop_sbot() -> Response { pub fn stop_sbot() -> Response {
info!("Stopping go-sbot.service"); 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(_) => ( Ok(_) => (
"flash_name=success".to_string(), "flash_name=success".to_string(),
"flash_msg=Sbot process has been stopped".to_string(), "flash_msg=Sbot process has been stopped".to_string(),

View File

@ -1,5 +1,25 @@
use maud::{html, PreEscaped, DOCTYPE}; 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. /// Base template builder.
/// ///
/// Takes an HTML body as input and splices it into the base template. /// 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"; meta name="viewport" content="width=devide-width, initial-scale=1.0";
link rel="stylesheet" href="/css/peachcloud.css"; link rel="stylesheet" href="/css/peachcloud.css";
link rel="stylesheet" href="/css/_variables.css"; link rel="stylesheet" href="/css/_variables.css";
(js_back_button_script())
title { "PeachCloud" } title { "PeachCloud" }
} }
body { body {

View File

@ -38,7 +38,7 @@ pub fn build_template(
html! { html! {
(PreEscaped("<!-- Top navigation bar -->")) (PreEscaped("<!-- Top navigation bar -->"))
nav class="nav-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"; img class="icon-medium nav-icon-left icon-active" src="/icons/back.svg" alt="Back";
} }
h1 class="nav-title" { (title) } h1 class="nav-title" { (title) }

View 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
),
)
}
}

View File

@ -1,3 +1,4 @@
pub mod cookie;
pub mod flash; pub mod flash;
pub mod sbot; pub mod sbot;
pub mod theme; pub mod theme;

View File

@ -3,7 +3,6 @@ use std::{
error::Error, error::Error,
fs, fs,
fs::File, fs::File,
io,
io::prelude::*, io::prelude::*,
path::Path, path::Path,
process::{Command, Output}, process::{Command, Output},
@ -13,9 +12,10 @@ use async_std::task;
use dirs; use dirs;
use futures::stream::TryStreamExt; use futures::stream::TryStreamExt;
use golgi::{ use golgi::{
api::friends::RelationshipQuery, blobs, messages::SsbMessageValue, sbot::Keystore, Sbot, api::friends::RelationshipQuery, blobs, messages::SsbMessageKVT, sbot::Keystore, Sbot,
}; };
use log::debug; use log::debug;
use peach_lib::config_manager;
use peach_lib::sbot::SbotConfig; use peach_lib::sbot::SbotConfig;
use rouille::input::post::BufferedFile; use rouille::input::post::BufferedFile;
use temporary::Directory; use temporary::Directory;
@ -24,22 +24,58 @@ use crate::{error::PeachWebError, utils::sbot};
// SBOT HELPER FUNCTIONS // SBOT HELPER FUNCTIONS
/// Executes a systemctl command for the go-sbot.service process. /// On non-docker based deployments (peachcloud, yunohost), we use systemctl
pub fn systemctl_sbot_cmd(cmd: &str) -> io::Result<Output> { /// On docker-based deployments, we use supervisord
Command::new("sudo") /// 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("systemctl")
.arg(cmd) .arg(cmd)
.arg("go-sbot.service") .arg(config_manager::get_config_value("GO_SBOT_SERVICE")?)
.output() .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. /// Returns a redirect with a flash message stating the output of the restart attempt.
pub fn restart_sbot_process() -> (String, String) { pub fn restart_sbot_process() -> (String, String) {
debug!("Restarting go-sbot.service"); debug!("Restarting go-sbot.service");
match systemctl_sbot_cmd("stop") { match system_sbot_cmd("stop") {
// if stop was successful, try to start the process // if stop was successful, try to start the process
Ok(_) => match systemctl_sbot_cmd("start") { Ok(_) => match system_sbot_cmd("start") {
Ok(_) => ( Ok(_) => (
"success".to_string(), "success".to_string(),
"Updated configuration and restarted the sbot process".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> { ) -> Result<Sbot, PeachWebError> {
debug!("Initialising an sbot client with configuration parameters"); debug!("Initialising an sbot client with configuration parameters");
// initialise sbot connection with ip:port and shscap from config file // 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 { let sbot_client = match sbot_config {
// TODO: panics if we pass `Some(conf.shscap)` as second arg // TODO: panics if we pass `Some(conf.shscap)` as second arg
Some(conf) => { Some(conf) => {
let ip_port = conf.lis.clone(); 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) Ok(sbot_client)
} }
@ -130,7 +169,7 @@ pub fn latest_sequence_number() -> Result<u64, Box<dyn Error>> {
let id = sbot_client.whoami().await?; let id = sbot_client.whoami().await?;
let history_stream = sbot_client.create_history_stream(id).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 // there will be zero messages when the sbot is run for the first time
if msgs.is_empty() { if msgs.is_empty() {
@ -140,7 +179,7 @@ pub fn latest_sequence_number() -> Result<u64, Box<dyn Error>> {
msgs.reverse(); msgs.reverse();
// return the sequence number of the latest msg // 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?; let mut sbot_client = init_sbot_with_config(&sbot_config).await?;
debug!("Generating Scuttlebutt invite code"); 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) Ok(invite_code)
}) })