Compare commits
134 Commits
fix_clippy
...
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 | |||
039cbcdfc4 | |||
4cd2692b9a | |||
3e5e7e0f7c | |||
b0b79fef24 | |||
98497fa5ae | |||
e6fd9a48cf | |||
8960df6635 | |||
781af460ae | |||
4a08e4ed6d | |||
908d265de6 | |||
8202d4af5f | |||
5ea6a86700 | |||
99fd3be4ad | |||
e041e1c7f9 | |||
e10777a5a5 | |||
288941e8a3 | |||
f96c950aa6 | |||
827ccbd4dc | |||
c21e2d090c | |||
bab33b602a | |||
b84e470a42 | |||
97680f9010 | |||
ab0401e555 | |||
810a97db8a | |||
610d60d989 | |||
f4c1bc1169 | |||
3ae182caa9 | |||
8a6ad4ad61 | |||
600f9c58bf | |||
2540a77af1 | |||
5b86f754f4 | |||
29804b0dce | |||
e2ac5de6e4 | |||
d03de8cf5d | |||
03720a7338 | |||
cf9c0c7eca | |||
f764acc2df | |||
a347e4726d | |||
3d3006049b | |||
2adb3006fe | |||
64b5929e5c | |||
5629a048a1 | |||
713c3da4cc | |||
92c7d7daa9 | |||
5a95ade8b9 | |||
315b04a63e | |||
1866e289a6 | |||
bff86a490b | |||
65d5352c85 | |||
df3b4b8858 | |||
2f1535fbee | |||
b75aadd62d |
@ -30,5 +30,4 @@ steps:
|
|||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
event:
|
event:
|
||||||
- push
|
|
||||||
- pull_request
|
- pull_request
|
||||||
|
955
Cargo.lock
generated
955
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
13
README.md
13
README.md
@ -45,6 +45,19 @@ _Better [Scuttlebutt](https://scuttlebutt.nz) cloud infrastructure as a hardware
|
|||||||
- [peach-patterns](https://github.com/peachcloud/peach-patterns) - Pattern library for the PeachCloud UI design system
|
- [peach-patterns](https://github.com/peachcloud/peach-patterns) - Pattern library for the PeachCloud UI design system
|
||||||
- [peach-web](https://github.com/peachcloud/peach-web) - A web interface for monitoring and interacting with the PeachCloud device
|
- [peach-web](https://github.com/peachcloud/peach-web) - A web interface for monitoring and interacting with the PeachCloud device
|
||||||
|
|
||||||
|
## Continuous Integration
|
||||||
|
|
||||||
|
[Drone CI](https://docs.drone.io/) is used to provide continuous integration for this workspace. The configuration file can be found in `.drone.yml` in the root of this repository. It is currently configured to run `cargo fmt`, `cargo clippy`, `cargo test` and `cargo build` on every `pull request` event. The pipeline runs on the AMD64 Debian Buster image from the official Rust Docker image repository.
|
||||||
|
|
||||||
|
The status of the current and previous CI builds can be viewed via the [Drone CI Build UI](https://build.coopcloud.tech/PeachCloud/peach-workspace) (kindly hosted by Co-op Cloud).
|
||||||
|
|
||||||
|
Adding `[CI SKIP]` to the end of a commit message results in the CI checks being skipped for the next event. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
git commit -m "update readme [CI SKIP]"
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
## Developer Diaries
|
## Developer Diaries
|
||||||
|
|
||||||
- [@ahdinosaur](https://github.com/ahdinosaur): `@6ilZq3kN0F+dXFHAPjAwMm87JEb/VdB+LC9eIMW3sa0=.ed25519`
|
- [@ahdinosaur](https://github.com/ahdinosaur): `@6ilZq3kN0F+dXFHAPjAwMm87JEb/VdB+LC9eIMW3sa0=.ed25519`
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "peach-config"
|
name = "peach-config"
|
||||||
version = "0.1.17"
|
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"
|
||||||
|
@ -10,7 +10,7 @@ pub const SERVICES: [&str; 8] = [
|
|||||||
"peach-buttons",
|
"peach-buttons",
|
||||||
"peach-oled",
|
"peach-oled",
|
||||||
"peach-dyndns-updater",
|
"peach-dyndns-updater",
|
||||||
"peach-go-sbot",
|
"go-sbot",
|
||||||
"peach-config",
|
"peach-config",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,40 +1,32 @@
|
|||||||
use regex::Regex;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use snafu::ResultExt;
|
use snafu::ResultExt;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use crate::constants::HARDWARE_CONFIG_FILE;
|
use crate::constants::{HARDWARE_CONFIG_FILE, SERVICES};
|
||||||
use crate::error::{FileReadError, FileWriteError, PeachConfigError};
|
use crate::error::{FileReadError, FileWriteError, PeachConfigError};
|
||||||
use crate::utils::get_output;
|
use crate::utils::get_output;
|
||||||
use crate::RtcOption;
|
use crate::RtcOption;
|
||||||
|
|
||||||
|
/// Helper function which returns the version of a package currently installed,
|
||||||
|
/// as an Ok(String) if found, and as an Err if not found
|
||||||
|
pub fn get_package_version_number(package: &str) -> Result<String, PeachConfigError> {
|
||||||
|
let version = get_output(&["dpkg-query", "--showformat=${Version}", "--show", package])?;
|
||||||
|
Ok(version)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a HashMap<String, String> of all the peach-packages which are currently installed
|
/// Returns a HashMap<String, String> of all the peach-packages which are currently installed
|
||||||
/// mapped to their version number e.g. { "peach-probe": "1.2.0", "peach-network": "1.4.0" }
|
/// mapped to their version number e.g. { "peach-probe": "1.2.0", "peach-network": "1.4.0" }
|
||||||
pub fn get_currently_installed_microservices() -> Result<HashMap<String, String>, PeachConfigError>
|
pub fn get_currently_installed_microservices() -> Result<HashMap<String, String>, PeachConfigError>
|
||||||
{
|
{
|
||||||
// gets a list of all packages currently installed with dpkg
|
// gets a list of all packages currently installed with dpkg-query
|
||||||
let packages = get_output(&["dpkg", "-l"])?;
|
let peach_packages: HashMap<String, String> = SERVICES
|
||||||
|
.iter()
|
||||||
// this regex matches packages which contain the word peach in them
|
.filter_map(|service| {
|
||||||
// and has two match groups
|
let version = get_package_version_number(service);
|
||||||
// 1. the first match group gets the package name
|
match version {
|
||||||
// 2. the second match group gets the version number of the package
|
Ok(v) => Some((service.to_string(), v)),
|
||||||
let re: Regex = Regex::new(r"\S+\s+(\S*peach\S+)\s+(\S+).*\n").unwrap();
|
Err(_) => None,
|
||||||
|
|
||||||
// the following iterator, iterates through the captures matched via the regex
|
|
||||||
// and for each capture, creates a value in the hash map,
|
|
||||||
// which maps the name of the package, to its version number
|
|
||||||
// e.g. { "peach-probe": "1.2.0", "peach-network": "1.4.0" }
|
|
||||||
let peach_packages: HashMap<String, String> = re
|
|
||||||
.captures_iter(&packages)
|
|
||||||
.filter_map(|cap| {
|
|
||||||
let groups = (cap.get(1), cap.get(2));
|
|
||||||
match groups {
|
|
||||||
(Some(package), Some(version)) => {
|
|
||||||
Some((package.as_str().to_string(), version.as_str().to_string()))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
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,21 +1,30 @@
|
|||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
use peach_lib::config_manager;
|
||||||
|
|
||||||
use crate::error::PeachConfigError;
|
use crate::error::PeachConfigError;
|
||||||
use crate::utils::cmd;
|
use crate::utils::cmd;
|
||||||
|
|
||||||
/// All configs are stored in this folder, and should be read/writeable by peach group
|
lazy_static! {
|
||||||
/// so they can be read and written by all PeachCloud services.
|
pub static ref PEACH_CONFIGDIR: String = config_manager::get_config_value("PEACH_CONFIGDIR")
|
||||||
pub const CONFIGS_DIR: &str = "/var/lib/peachcloud";
|
.expect("Failed to load config value for PEACH_CONFIGDIR");
|
||||||
pub const PEACH_WEB_DIR: &str = "/usr/share/peach-web";
|
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.
|
/// Utility function to set correct file permissions on the PeachCloud device.
|
||||||
/// Accidentally changing file permissions is a fairly common thing to happen,
|
/// Accidentally changing file permissions is a fairly common thing to happen,
|
||||||
/// so this is a useful CLI function for quickly correcting anything that may be out of order.
|
/// so this is a useful CLI function for quickly correcting anything that may be out of order.
|
||||||
pub fn set_permissions() -> Result<(), PeachConfigError> {
|
pub fn set_permissions() -> Result<(), PeachConfigError> {
|
||||||
println!("[ UPDATING FILE PERMISSIONS ON PEACHCLOUD DEVICE ]");
|
println!("[ UPDATING FILE PERMISSIONS ON PEACHCLOUD DEVICE ]");
|
||||||
cmd(&["chmod", "-R", "u+rwX,g+rwX", CONFIGS_DIR])?;
|
cmd(&["chmod", "-R", "u+rwX,g+rwX", &PEACH_CONFIGDIR])?;
|
||||||
cmd(&["chown", "-R", "peach", CONFIGS_DIR])?;
|
cmd(&["chown", "-R", "peach:peach", &PEACH_CONFIGDIR])?;
|
||||||
cmd(&["chgrp", "-R", "peach", CONFIGS_DIR])?;
|
cmd(&["chmod", "-R", "u+rwX,g+rwX", &PEACH_WEBDIR])?;
|
||||||
cmd(&["chmod", "-R", "u+rwX,g+rwX", PEACH_WEB_DIR])?;
|
cmd(&["chown", "-R", "peach:peach", &PEACH_WEBDIR])?;
|
||||||
cmd(&["chown", "-R", "peach-web:peach", PEACH_WEB_DIR])?;
|
cmd(&["chmod", "-R", "u+rwX,g+rwX", &PEACH_HOMEDIR])?;
|
||||||
|
cmd(&["chown", "-R", "peach:peach", &PEACH_HOMEDIR])?;
|
||||||
println!("[ PERMISSIONS SUCCESSFULLY UPDATED ]");
|
println!("[ PERMISSIONS SUCCESSFULLY UPDATED ]");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ use std::fs;
|
|||||||
|
|
||||||
use crate::error::{FileWriteError, PeachConfigError};
|
use crate::error::{FileWriteError, PeachConfigError};
|
||||||
use crate::generate_manifest::save_hardware_config;
|
use crate::generate_manifest::save_hardware_config;
|
||||||
|
use crate::set_permissions::set_permissions;
|
||||||
use crate::setup_networking::configure_networking;
|
use crate::setup_networking::configure_networking;
|
||||||
use crate::setup_peach_deb::setup_peach_deb;
|
use crate::setup_peach_deb::setup_peach_deb;
|
||||||
use crate::update::update_microservices;
|
use crate::update::update_microservices;
|
||||||
@ -239,6 +240,9 @@ pub fn setup_peach(
|
|||||||
info!("[ SAVING LOG OF HARDWARE CONFIGURATIONS ]");
|
info!("[ SAVING LOG OF HARDWARE CONFIGURATIONS ]");
|
||||||
save_hardware_config(i2c, rtc)?;
|
save_hardware_config(i2c, rtc)?;
|
||||||
|
|
||||||
|
info!("[ SETTING FILE PERMISSIONS ]");
|
||||||
|
set_permissions()?;
|
||||||
|
|
||||||
info!("[ PEACHCLOUD SETUP COMPLETE ]");
|
info!("[ PEACHCLOUD SETUP COMPLETE ]");
|
||||||
info!("[ ------------------------- ]");
|
info!("[ ------------------------- ]");
|
||||||
info!("[ please reboot your device ]");
|
info!("[ please reboot your 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]
|
[package]
|
||||||
name = "peach-lib"
|
name = "peach-lib"
|
||||||
version = "1.3.2"
|
version = "1.3.5"
|
||||||
authors = ["Andrew Reid <glyph@mycelial.technology>"]
|
authors = ["Andrew Reid <glyph@mycelial.technology>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
@ -9,15 +9,16 @@ 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"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
nanorand = "0.6"
|
nanorand = { version = "0.6", features = ["getrandom"] }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_yaml = "0.8"
|
serde_yaml = "0.8"
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
sha3 = "0.10"
|
sha3 = "0.10"
|
||||||
|
lazy_static = "1.4"
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
[target.aarch64-unknown-linux-gnu]
|
|
||||||
linker = "aarch64-linux-gnu-gcc"
|
|
||||||
objcopy = { path ="aarch64-linux-gnu-objcopy" }
|
|
||||||
strip = { path ="aarch64-linux-gnu-strip" }
|
|
@ -1,12 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "debug"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["notplants <mfowler.email@gmail.com>"]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
peach-lib = { path = "../" }
|
|
||||||
env_logger = "0.6"
|
|
||||||
chrono = "0.4.19"
|
|
@ -1,65 +0,0 @@
|
|||||||
use peach_lib::dyndns_client::{dyndns_update_ip, register_domain, is_dns_updater_online, log_successful_nsupdate, get_num_seconds_since_successful_dns_update };
|
|
||||||
use peach_lib::password_utils::{verify_password, set_new_password, verify_temporary_password, set_new_temporary_password, send_password_reset};
|
|
||||||
use peach_lib::config_manager::{add_ssb_admin_id, delete_ssb_admin_id};
|
|
||||||
use peach_lib::sbot_client;
|
|
||||||
use std::process;
|
|
||||||
use chrono::prelude::*;
|
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// initalize the logger
|
|
||||||
env_logger::init();
|
|
||||||
//
|
|
||||||
// println!("Hello, world its debug!");
|
|
||||||
// let result = set_new_password("password3");
|
|
||||||
// println!("result: {:?}", result);
|
|
||||||
//
|
|
||||||
// let result = verify_password("password1");
|
|
||||||
// println!("result should be error: {:?}", result);
|
|
||||||
//
|
|
||||||
// let result = verify_password("password3");
|
|
||||||
// println!("result should be ok: {:?}", result);
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// println!("Testing temporary passwords");
|
|
||||||
// let result = set_new_temporary_password("abcd");
|
|
||||||
// println!("result: {:?}", result);
|
|
||||||
//
|
|
||||||
// let result = verify_temporary_password("password1");
|
|
||||||
// println!("result should be error: {:?}", result);
|
|
||||||
//
|
|
||||||
// let result = verify_temporary_password("abcd");
|
|
||||||
// println!("result should be ok: {:?}", result);
|
|
||||||
//
|
|
||||||
let result = send_password_reset();
|
|
||||||
println!("send password reset result should be ok: {:?}", result);
|
|
||||||
|
|
||||||
// sbot_client::post("hi cat");
|
|
||||||
// let result = sbot_client::whoami();
|
|
||||||
// let result = sbot_client::create_invite(50);
|
|
||||||
// let result = sbot_client::post("is this working");
|
|
||||||
// println!("result: {:?}", result);
|
|
||||||
// let result = sbot_client::post("nice we have contact");
|
|
||||||
// let result = sbot_client::update_pub_name("vermont-pub");
|
|
||||||
// let result = sbot_client::private_message("this is a private message", "@LZx+HP6/fcjUm7vef2eaBKAQ9gAKfzmrMVGzzdJiQtA=.ed25519");
|
|
||||||
// println!("result: {:?}", result);
|
|
||||||
|
|
||||||
// let result = send_password_reset();
|
|
||||||
// let result = add_ssb_admin_id("xyzdab");
|
|
||||||
// println!("result: {:?}", result);
|
|
||||||
// let result = delete_ssb_admin_id("xyzdab");
|
|
||||||
// println!("result: {:?}", result);
|
|
||||||
// let result = delete_ssb_admin_id("ab");
|
|
||||||
// println!("result: {:?}", result);
|
|
||||||
|
|
||||||
//// let result = log_successful_nsupdate();
|
|
||||||
//// let result = get_num_seconds_since_successful_dns_update();
|
|
||||||
// let is_online = is_dns_updater_online();
|
|
||||||
// println!("is online: {:?}", is_online);
|
|
||||||
//
|
|
||||||
//// let result = get_last_successful_dns_update();
|
|
||||||
//// println!("result: {:?}", result);
|
|
||||||
//// register_domain("newquarter299.dyn.peachcloud.org");
|
|
||||||
// let result = dyndns_update_ip();
|
|
||||||
// println!("result: {:?}", result);
|
|
||||||
}
|
|
11
peach-lib/examples/config.rs
Normal file
11
peach-lib/examples/config.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
use peach_lib::config_manager::{get_config_value, save_config_value};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("Running example of PeachCloud configuration management");
|
||||||
|
let v = get_config_value("ADDR").unwrap();
|
||||||
|
println!("ADDR: {}", v);
|
||||||
|
|
||||||
|
save_config_value("ADDR", "1.1.1.1");
|
||||||
|
let v = get_config_value("ADDR").unwrap();
|
||||||
|
println!("ADDR: {}", v);
|
||||||
|
}
|
@ -3,170 +3,279 @@
|
|||||||
//! Different PeachCloud microservices import peach-lib, so that they can share
|
//! Different PeachCloud microservices import peach-lib, so that they can share
|
||||||
//! this interface.
|
//! this interface.
|
||||||
//!
|
//!
|
||||||
|
//! Config values are looked up from three locations in this order by key name:
|
||||||
|
//! 1. from environmental variables
|
||||||
|
//! 2. from a configuration file
|
||||||
|
//! 3. from default values
|
||||||
|
//!
|
||||||
//! The configuration file is located at: "/var/lib/peachcloud/config.yml"
|
//! The configuration file is located at: "/var/lib/peachcloud/config.yml"
|
||||||
|
//! unless its path is configured by setting PEACH_CONFIG_PATH env variable.
|
||||||
|
|
||||||
use std::fs;
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
use std::{env, fs};
|
||||||
|
|
||||||
use fslock::LockFile;
|
use fslock::LockFile;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::error::PeachError;
|
use crate::error::PeachError;
|
||||||
|
|
||||||
// main configuration file
|
// load path to main configuration file
|
||||||
pub const YAML_PATH: &str = "/var/lib/peachcloud/config.yml";
|
// from PEACH_CONFIG_PATH if that environment variable is set
|
||||||
|
// or using the default value if not set
|
||||||
// lock file (used to avoid race conditions during config reading & writing)
|
pub const DEFAULT_YAML_PATH: &str = "/var/lib/peachcloud/config.yml";
|
||||||
pub const LOCK_FILE_PATH: &str = "/var/lib/peachcloud/config.lock";
|
lazy_static! {
|
||||||
|
static ref CONFIG_PATH: String = {
|
||||||
// default values
|
if let Ok(val) = env::var("PEACH_CONFIG_PATH") {
|
||||||
pub const DEFAULT_DYN_SERVER_ADDRESS: &str = "http://dynserver.dyn.peachcloud.org";
|
val
|
||||||
pub const DEFAULT_DYN_NAMESERVER: &str = "ns.peachcloud.org";
|
}
|
||||||
|
else {
|
||||||
// we make use of Serde default values in order to make PeachCloud
|
DEFAULT_YAML_PATH.to_string()
|
||||||
// robust and keep running even with a not fully complete config.yml
|
}
|
||||||
// main type which represents all peachcloud configurations
|
};
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
// lock file (used to avoid race conditions during config reading & writing)
|
||||||
pub struct PeachConfig {
|
// the lock file path is the config file path + ".lock"
|
||||||
#[serde(default)]
|
static ref LOCK_FILE_PATH: String = format!("{}.lock", *CONFIG_PATH);
|
||||||
pub external_domain: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub dyn_domain: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub dyn_dns_server_address: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub dyn_use_custom_server: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub dyn_nameserver: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub dyn_tsig_key_path: String,
|
|
||||||
#[serde(default)] // default is false
|
|
||||||
pub dyn_enabled: bool,
|
|
||||||
#[serde(default)] // default is empty vector
|
|
||||||
pub ssb_admin_ids: Vec<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub admin_password_hash: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub temporary_password_hash: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper functions for serializing and deserializing PeachConfig from disc
|
// Default values for PeachCloud configs which are used for any key which is not set
|
||||||
pub fn save_peach_config(peach_config: PeachConfig) -> Result<PeachConfig, PeachError> {
|
// via an environment variable or in a saved configuration file.
|
||||||
|
pub fn get_peach_config_defaults() -> HashMap<String, String> {
|
||||||
|
let peach_config_defaults: HashMap<&str, &str> = HashMap::from([
|
||||||
|
("STANDALONE_MODE", "true"),
|
||||||
|
("DISABLE_AUTH", "false"),
|
||||||
|
("ADDR", "127.0.0.1"),
|
||||||
|
("PORT", "8000"),
|
||||||
|
("EXTERNAL_DOMAIN", ""),
|
||||||
|
("DYN_DOMAIN", ""),
|
||||||
|
(
|
||||||
|
"DYN_DNS_SERVER_ADDRESS",
|
||||||
|
"http://dynserver.dyn.peachcloud.org",
|
||||||
|
),
|
||||||
|
("DYN_USE_CUSTOM_SERVER", "true"),
|
||||||
|
("DYN_TSIG_KEY_PATH", ""),
|
||||||
|
("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"),
|
||||||
|
]);
|
||||||
|
// convert HashMap<&str, &str> to HashMap<String, String> and return
|
||||||
|
let pc_defaults: HashMap<String, String> = peach_config_defaults
|
||||||
|
.iter()
|
||||||
|
.map(|(key, val)| (key.to_string(), val.to_string()))
|
||||||
|
.collect();
|
||||||
|
pc_defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
// primary interface for getting config values
|
||||||
|
// Config values are looked up from three locations in this order by key name:
|
||||||
|
// 1. from environmental variables
|
||||||
|
// 2. from a configuration file
|
||||||
|
// 3. from default values
|
||||||
|
pub fn get_config_value(key: &str) -> Result<String, PeachError> {
|
||||||
|
// first check if there is an environmental variable set
|
||||||
|
if let Ok(val) = env::var(key) {
|
||||||
|
Ok(val)
|
||||||
|
} else {
|
||||||
|
// then check if a value is set in the config file
|
||||||
|
let peach_config_on_disc = load_peach_config_from_disc()?;
|
||||||
|
let val = peach_config_on_disc.get(key);
|
||||||
|
// if no value is found in the config file, then get the default value
|
||||||
|
match val {
|
||||||
|
// return config value
|
||||||
|
Some(v) => Ok(v.to_string()),
|
||||||
|
// get default value
|
||||||
|
None => {
|
||||||
|
match get_peach_config_defaults().get(key) {
|
||||||
|
Some(v) => Ok(v.to_string()),
|
||||||
|
// if this key was not found in the defaults, then it was an invalid key
|
||||||
|
None => Err(PeachError::InvalidKey {
|
||||||
|
key: key.to_string(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to load PeachCloud configuration file saved to disc
|
||||||
|
pub fn load_peach_config_from_disc() -> Result<HashMap<String, String>, PeachError> {
|
||||||
|
let peach_config_exists = std::path::Path::new(CONFIG_PATH.as_str()).exists();
|
||||||
|
// if config file does not exist, return an emtpy HashMap
|
||||||
|
if !peach_config_exists {
|
||||||
|
let peach_config: HashMap<String, String> = HashMap::new();
|
||||||
|
Ok(peach_config)
|
||||||
|
}
|
||||||
|
// otherwise we load peach config from disk
|
||||||
|
else {
|
||||||
|
debug!("Loading peach config: {} exists", CONFIG_PATH.as_str());
|
||||||
|
let contents =
|
||||||
|
fs::read_to_string(CONFIG_PATH.as_str()).map_err(|source| PeachError::Read {
|
||||||
|
source,
|
||||||
|
path: CONFIG_PATH.to_string(),
|
||||||
|
})?;
|
||||||
|
let peach_config: HashMap<String, String> = serde_yaml::from_str(&contents)?;
|
||||||
|
Ok(peach_config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to save PeachCloud configuration file to disc
|
||||||
|
// takes in a Hashmap<String, String> and saves the whole HashMap as a yaml file
|
||||||
|
// with the keys in alphabetical order
|
||||||
|
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
|
// 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()?;
|
||||||
|
|
||||||
let yaml_str = serde_yaml::to_string(&peach_config)?;
|
// first convert Hashmap to BTreeMap (so that keys are saved in deterministic alphabetical order)
|
||||||
|
let ordered: BTreeMap<_, _> = peach_config.iter().collect();
|
||||||
|
// then serialize BTreeMap as yaml
|
||||||
|
let yaml_str = serde_yaml::to_string(&ordered)?;
|
||||||
|
|
||||||
fs::write(YAML_PATH, yaml_str).map_err(|source| PeachError::Write {
|
// write yaml to file
|
||||||
|
fs::write(CONFIG_PATH.as_str(), yaml_str).map_err(|source| PeachError::Write {
|
||||||
source,
|
source,
|
||||||
path: YAML_PATH.to_string(),
|
path: CONFIG_PATH.to_string(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// unlock file lock
|
// unlock file lock
|
||||||
lock.unlock()?;
|
lock.unlock()?;
|
||||||
|
|
||||||
// return peach_config
|
// return modified HashMap
|
||||||
Ok(peach_config)
|
Ok(peach_config)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_peach_config() -> Result<PeachConfig, PeachError> {
|
// helper functions for serializing and deserializing PeachConfig values from disc
|
||||||
let peach_config_exists = std::path::Path::new(YAML_PATH).exists();
|
pub fn save_config_value(key: &str, value: &str) -> Result<HashMap<String, String>, PeachError> {
|
||||||
|
// get current config from disc
|
||||||
|
let mut peach_config = load_peach_config_from_disc()?;
|
||||||
|
|
||||||
let peach_config: PeachConfig = if !peach_config_exists {
|
// insert new key/value
|
||||||
debug!("Loading peach config: {} does not exist", YAML_PATH);
|
peach_config.insert(key.to_string(), value.to_string());
|
||||||
PeachConfig {
|
|
||||||
external_domain: "".to_string(),
|
|
||||||
dyn_domain: "".to_string(),
|
|
||||||
dyn_dns_server_address: DEFAULT_DYN_SERVER_ADDRESS.to_string(),
|
|
||||||
dyn_use_custom_server: false,
|
|
||||||
dyn_nameserver: DEFAULT_DYN_NAMESERVER.to_string(),
|
|
||||||
dyn_tsig_key_path: "".to_string(),
|
|
||||||
dyn_enabled: false,
|
|
||||||
ssb_admin_ids: Vec::new(),
|
|
||||||
// default password is `peach`
|
|
||||||
admin_password_hash: "146".to_string(),
|
|
||||||
temporary_password_hash: "".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// otherwise we load peach config from disk
|
|
||||||
else {
|
|
||||||
debug!("Loading peach config: {} exists", YAML_PATH);
|
|
||||||
let contents = fs::read_to_string(YAML_PATH).map_err(|source| PeachError::Read {
|
|
||||||
source,
|
|
||||||
path: YAML_PATH.to_string(),
|
|
||||||
})?;
|
|
||||||
serde_yaml::from_str(&contents)?
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(peach_config)
|
// save the modified hashmap to disc
|
||||||
|
save_peach_config_to_disc(peach_config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// interfaces for setting specific config values
|
// set all dyn configuration values at once
|
||||||
pub fn set_peach_dyndns_config(
|
pub fn set_peach_dyndns_config(
|
||||||
dyn_domain: &str,
|
dyn_domain: &str,
|
||||||
dyn_dns_server_address: &str,
|
dyn_dns_server_address: &str,
|
||||||
dyn_tsig_key_path: &str,
|
dyn_tsig_key_path: &str,
|
||||||
dyn_enabled: bool,
|
dyn_enabled: bool,
|
||||||
) -> Result<PeachConfig, PeachError> {
|
) -> Result<HashMap<String, String>, PeachError> {
|
||||||
let mut peach_config = load_peach_config()?;
|
let mut peach_config = load_peach_config_from_disc()?;
|
||||||
peach_config.dyn_domain = dyn_domain.to_string();
|
let dyn_enabled_str = match dyn_enabled {
|
||||||
peach_config.dyn_dns_server_address = dyn_dns_server_address.to_string();
|
true => "true",
|
||||||
peach_config.dyn_tsig_key_path = dyn_tsig_key_path.to_string();
|
false => "false",
|
||||||
peach_config.dyn_enabled = dyn_enabled;
|
};
|
||||||
save_peach_config(peach_config)
|
peach_config.insert("DYN_DOMAIN".to_string(), dyn_domain.to_string());
|
||||||
|
peach_config.insert(
|
||||||
|
"DYN_DNS_SERVER_ADDRESS".to_string(),
|
||||||
|
dyn_dns_server_address.to_string(),
|
||||||
|
);
|
||||||
|
peach_config.insert(
|
||||||
|
"DYN_TSIG_KEY_PATH".to_string(),
|
||||||
|
dyn_tsig_key_path.to_string(),
|
||||||
|
);
|
||||||
|
peach_config.insert("DYN_ENABLED".to_string(), dyn_enabled_str.to_string());
|
||||||
|
save_peach_config_to_disc(peach_config)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_external_domain(new_external_domain: &str) -> Result<PeachConfig, PeachError> {
|
pub fn set_external_domain(
|
||||||
let mut peach_config = load_peach_config()?;
|
new_external_domain: &str,
|
||||||
peach_config.external_domain = new_external_domain.to_string();
|
) -> Result<HashMap<String, String>, PeachError> {
|
||||||
save_peach_config(peach_config)
|
save_config_value("EXTERNAL_DOMAIN", new_external_domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_peachcloud_domain() -> Result<Option<String>, PeachError> {
|
pub fn get_peachcloud_domain() -> Result<Option<String>, PeachError> {
|
||||||
let peach_config = load_peach_config()?;
|
let external_domain = get_config_value("EXTERNAL_DOMAIN")?;
|
||||||
if !peach_config.external_domain.is_empty() {
|
let dyn_domain = get_config_value("DYN_DOMAIN")?;
|
||||||
Ok(Some(peach_config.external_domain))
|
if !external_domain.is_empty() {
|
||||||
} else if !peach_config.dyn_domain.is_empty() {
|
Ok(Some(external_domain))
|
||||||
Ok(Some(peach_config.dyn_domain))
|
} else if !dyn_domain.is_empty() {
|
||||||
|
Ok(Some(dyn_domain))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_dyndns_server_address() -> Result<String, PeachError> {
|
pub fn get_dyndns_server_address() -> Result<String, PeachError> {
|
||||||
let peach_config = load_peach_config()?;
|
get_config_value("DYN_DNS_SERVER_ADDRESS")
|
||||||
// if the user is using a custom dyn server then load the address from the config
|
}
|
||||||
if peach_config.dyn_use_custom_server {
|
|
||||||
Ok(peach_config.dyn_dns_server_address)
|
pub fn set_dyndns_enabled_value(
|
||||||
}
|
enabled_value: bool,
|
||||||
// otherwise hardcode the address
|
) -> Result<HashMap<String, String>, PeachError> {
|
||||||
else {
|
match enabled_value {
|
||||||
Ok(DEFAULT_DYN_SERVER_ADDRESS.to_string())
|
true => save_config_value("DYN_ENABLED", "true"),
|
||||||
|
false => save_config_value("DYN_ENABLED", "false"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_dyndns_enabled_value(enabled_value: bool) -> Result<PeachConfig, PeachError> {
|
pub fn get_dyndns_enabled_value() -> Result<bool, PeachError> {
|
||||||
let mut peach_config = load_peach_config()?;
|
let val = get_config_value("DYN_ENABLED")?;
|
||||||
peach_config.dyn_enabled = enabled_value;
|
Ok(val == "true")
|
||||||
save_peach_config(peach_config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_ssb_admin_id(ssb_id: &str) -> Result<PeachConfig, PeachError> {
|
pub fn set_admin_password_hash(
|
||||||
let mut peach_config = load_peach_config()?;
|
password_hash: String,
|
||||||
peach_config.ssb_admin_ids.push(ssb_id.to_string());
|
) -> Result<HashMap<String, String>, PeachError> {
|
||||||
save_peach_config(peach_config)
|
save_config_value("ADMIN_PASSWORD_HASH", &password_hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_ssb_admin_id(ssb_id: &str) -> Result<PeachConfig, PeachError> {
|
pub fn get_admin_password_hash() -> Result<String, PeachError> {
|
||||||
let mut peach_config = load_peach_config()?;
|
let admin_password_hash = get_config_value("ADMIN_PASSWORD_HASH")?;
|
||||||
let mut ssb_admin_ids = peach_config.ssb_admin_ids;
|
if !admin_password_hash.is_empty() {
|
||||||
|
Ok(admin_password_hash)
|
||||||
|
} else {
|
||||||
|
Err(PeachError::PasswordNotSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_temporary_password_hash(
|
||||||
|
password_hash: String,
|
||||||
|
) -> Result<HashMap<String, String>, PeachError> {
|
||||||
|
save_config_value("TEMPORARY_PASSWORD_HASH", &password_hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_temporary_password_hash() -> Result<String, PeachError> {
|
||||||
|
let admin_password_hash = get_config_value("TEMPORARY_PASSWORD_HASH")?;
|
||||||
|
if !admin_password_hash.is_empty() {
|
||||||
|
Ok(admin_password_hash)
|
||||||
|
} else {
|
||||||
|
Err(PeachError::PasswordNotSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add ssb_id to vector of admin ids and save new value for SSB_ADMIN_IDS
|
||||||
|
pub fn add_ssb_admin_id(ssb_id: &str) -> Result<Vec<String>, PeachError> {
|
||||||
|
let mut ssb_admin_ids = get_ssb_admin_ids()?;
|
||||||
|
ssb_admin_ids.push(ssb_id.to_string());
|
||||||
|
save_ssb_admin_ids(ssb_admin_ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove ssb_id from vector of admin ids if found and save new value for SSB_ADMIN_IDS
|
||||||
|
// if value is not found then return an error
|
||||||
|
pub fn delete_ssb_admin_id(ssb_id: &str) -> Result<Vec<String>, PeachError> {
|
||||||
|
let mut ssb_admin_ids = get_ssb_admin_ids()?;
|
||||||
let index_result = ssb_admin_ids.iter().position(|x| *x == ssb_id);
|
let index_result = ssb_admin_ids.iter().position(|x| *x == ssb_id);
|
||||||
match index_result {
|
match index_result {
|
||||||
Some(index) => {
|
Some(index) => {
|
||||||
ssb_admin_ids.remove(index);
|
ssb_admin_ids.remove(index);
|
||||||
peach_config.ssb_admin_ids = ssb_admin_ids;
|
save_ssb_admin_ids(ssb_admin_ids)
|
||||||
save_peach_config(peach_config)
|
|
||||||
}
|
}
|
||||||
None => Err(PeachError::SsbAdminIdNotFound {
|
None => Err(PeachError::SsbAdminIdNotFound {
|
||||||
id: ssb_id.to_string(),
|
id: ssb_id.to_string(),
|
||||||
@ -174,32 +283,16 @@ pub fn delete_ssb_admin_id(ssb_id: &str) -> Result<PeachConfig, PeachError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_admin_password_hash(password_hash: &str) -> Result<PeachConfig, PeachError> {
|
// looks up the String value for SSB_ADMIN_IDS and converts it into a Vec<String>
|
||||||
let mut peach_config = load_peach_config()?;
|
pub fn get_ssb_admin_ids() -> Result<Vec<String>, PeachError> {
|
||||||
peach_config.admin_password_hash = password_hash.to_string();
|
let ssb_admin_ids_str = get_config_value("SSB_ADMIN_IDS")?;
|
||||||
save_peach_config(peach_config)
|
let ssb_admin_ids: Vec<String> = serde_json::from_str(&ssb_admin_ids_str)?;
|
||||||
|
Ok(ssb_admin_ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_admin_password_hash() -> Result<String, PeachError> {
|
// takes in a Vec<String> and saves SSB_ADMIN_IDS as a json string representation of this vec
|
||||||
let peach_config = load_peach_config()?;
|
pub fn save_ssb_admin_ids(ssb_admin_ids: Vec<String>) -> Result<Vec<String>, PeachError> {
|
||||||
if !peach_config.admin_password_hash.is_empty() {
|
let ssb_admin_ids_as_json_str = serde_json::to_string(&ssb_admin_ids)?;
|
||||||
Ok(peach_config.admin_password_hash)
|
save_config_value("SSB_ADMIN_IDS", &ssb_admin_ids_as_json_str)?;
|
||||||
} else {
|
Ok(ssb_admin_ids)
|
||||||
Err(PeachError::PasswordNotSet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_temporary_password_hash(password_hash: &str) -> Result<PeachConfig, PeachError> {
|
|
||||||
let mut peach_config = load_peach_config()?;
|
|
||||||
peach_config.temporary_password_hash = password_hash.to_string();
|
|
||||||
save_peach_config(peach_config)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_temporary_password_hash() -> Result<String, PeachError> {
|
|
||||||
let peach_config = load_peach_config()?;
|
|
||||||
if !peach_config.temporary_password_hash.is_empty() {
|
|
||||||
Ok(peach_config.temporary_password_hash)
|
|
||||||
} else {
|
|
||||||
Err(PeachError::PasswordNotSet)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,9 @@ use jsonrpc_client_http::HttpTransport;
|
|||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::config_manager::get_dyndns_server_address;
|
use crate::config_manager::{
|
||||||
|
get_config_value, get_dyndns_enabled_value, get_dyndns_server_address,
|
||||||
|
};
|
||||||
use crate::{config_manager, error::PeachError};
|
use crate::{config_manager, error::PeachError};
|
||||||
|
|
||||||
/// constants for dyndns configuration
|
/// constants for dyndns configuration
|
||||||
@ -107,7 +109,11 @@ fn get_public_ip_address() -> Result<String, PeachError> {
|
|||||||
/// Reads dyndns configurations from config.yml
|
/// Reads dyndns configurations from config.yml
|
||||||
/// and then uses nsupdate to update the IP address for the configured domain
|
/// and then uses nsupdate to update the IP address for the configured domain
|
||||||
pub fn dyndns_update_ip() -> Result<bool, PeachError> {
|
pub fn dyndns_update_ip() -> Result<bool, PeachError> {
|
||||||
let peach_config = config_manager::load_peach_config()?;
|
let dyn_tsig_key_path = get_config_value("DYN_TSIG_KEY_PATH")?;
|
||||||
|
let dyn_enabled = get_dyndns_enabled_value()?;
|
||||||
|
let dyn_domain = get_config_value("DYN_DOMAIN")?;
|
||||||
|
let dyn_dns_server_address = get_config_value("DYN_DNS_SERVER_ADDRESS")?;
|
||||||
|
let dyn_nameserver = get_config_value("DYN_NAMESERVER")?;
|
||||||
info!(
|
info!(
|
||||||
"Using config:
|
"Using config:
|
||||||
dyn_tsig_key_path: {:?}
|
dyn_tsig_key_path: {:?}
|
||||||
@ -116,22 +122,15 @@ pub fn dyndns_update_ip() -> Result<bool, PeachError> {
|
|||||||
dyn_enabled: {:?}
|
dyn_enabled: {:?}
|
||||||
dyn_nameserver: {:?}
|
dyn_nameserver: {:?}
|
||||||
",
|
",
|
||||||
peach_config.dyn_tsig_key_path,
|
dyn_tsig_key_path, dyn_domain, dyn_dns_server_address, dyn_enabled, dyn_nameserver,
|
||||||
peach_config.dyn_domain,
|
|
||||||
peach_config.dyn_dns_server_address,
|
|
||||||
peach_config.dyn_enabled,
|
|
||||||
peach_config.dyn_nameserver,
|
|
||||||
);
|
);
|
||||||
if !peach_config.dyn_enabled {
|
if !dyn_enabled {
|
||||||
info!("dyndns is not enabled, not updating");
|
info!("dyndns is not enabled, not updating");
|
||||||
Ok(false)
|
Ok(false)
|
||||||
} else {
|
} else {
|
||||||
// call nsupdate passing appropriate configs
|
// call nsupdate passing appropriate configs
|
||||||
let mut nsupdate_command = Command::new("nsupdate");
|
let mut nsupdate_command = Command::new("nsupdate");
|
||||||
nsupdate_command
|
nsupdate_command.arg("-k").arg(&dyn_tsig_key_path).arg("-v");
|
||||||
.arg("-k")
|
|
||||||
.arg(&peach_config.dyn_tsig_key_path)
|
|
||||||
.arg("-v");
|
|
||||||
// pass nsupdate commands via stdin
|
// pass nsupdate commands via stdin
|
||||||
let public_ip_address = get_public_ip_address()?;
|
let public_ip_address = get_public_ip_address()?;
|
||||||
info!("found public ip address: {}", public_ip_address);
|
info!("found public ip address: {}", public_ip_address);
|
||||||
@ -142,9 +141,9 @@ pub fn dyndns_update_ip() -> Result<bool, PeachError> {
|
|||||||
update delete {DOMAIN} A
|
update delete {DOMAIN} A
|
||||||
update add {DOMAIN} 30 A {PUBLIC_IP_ADDRESS}
|
update add {DOMAIN} 30 A {PUBLIC_IP_ADDRESS}
|
||||||
send",
|
send",
|
||||||
NAMESERVER = peach_config.dyn_nameserver,
|
NAMESERVER = dyn_nameserver,
|
||||||
ZONE = peach_config.dyn_domain,
|
ZONE = dyn_domain,
|
||||||
DOMAIN = peach_config.dyn_domain,
|
DOMAIN = dyn_domain,
|
||||||
PUBLIC_IP_ADDRESS = public_ip_address,
|
PUBLIC_IP_ADDRESS = public_ip_address,
|
||||||
);
|
);
|
||||||
info!("ns_commands: {:?}", ns_commands);
|
info!("ns_commands: {:?}", ns_commands);
|
||||||
@ -217,8 +216,7 @@ pub fn get_num_seconds_since_successful_dns_update() -> Result<Option<i64>, Peac
|
|||||||
/// and has successfully run recently (in the last six minutes)
|
/// and has successfully run recently (in the last six minutes)
|
||||||
pub fn is_dns_updater_online() -> Result<bool, PeachError> {
|
pub fn is_dns_updater_online() -> Result<bool, PeachError> {
|
||||||
// first check if it is enabled in peach-config
|
// first check if it is enabled in peach-config
|
||||||
let peach_config = config_manager::load_peach_config()?;
|
let is_enabled = get_dyndns_enabled_value()?;
|
||||||
let is_enabled = peach_config.dyn_enabled;
|
|
||||||
// then check if it has successfully run within the last 6 minutes (60*6 seconds)
|
// then check if it has successfully run within the last 6 minutes (60*6 seconds)
|
||||||
let num_seconds_since_successful_update = get_num_seconds_since_successful_dns_update()?;
|
let num_seconds_since_successful_update = get_num_seconds_since_successful_dns_update()?;
|
||||||
let ran_recently: bool = match num_seconds_since_successful_update {
|
let ran_recently: bool = match num_seconds_since_successful_update {
|
||||||
@ -248,8 +246,7 @@ pub fn get_dyndns_subdomain(dyndns_full_domain: &str) -> Option<String> {
|
|||||||
|
|
||||||
// helper function which checks if a dyndns domain is new
|
// helper function which checks if a dyndns domain is new
|
||||||
pub fn check_is_new_dyndns_domain(dyndns_full_domain: &str) -> Result<bool, PeachError> {
|
pub fn check_is_new_dyndns_domain(dyndns_full_domain: &str) -> Result<bool, PeachError> {
|
||||||
let peach_config = config_manager::load_peach_config()?;
|
let previous_dyndns_domain = get_config_value("DYN_DOMAIN")?;
|
||||||
let previous_dyndns_domain = peach_config.dyn_domain;
|
|
||||||
Ok(dyndns_full_domain != previous_dyndns_domain)
|
Ok(dyndns_full_domain != previous_dyndns_domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,11 +2,18 @@
|
|||||||
|
|
||||||
//! 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.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum PeachError {
|
pub enum PeachError {
|
||||||
|
/// Represents looking up a Config value with a non-existent key
|
||||||
|
InvalidKey {
|
||||||
|
/// the key value which was invalid
|
||||||
|
key: String,
|
||||||
|
},
|
||||||
|
|
||||||
/// Represents a failure to determine the path of the user's home directory.
|
/// Represents a failure to determine the path of the user's home directory.
|
||||||
HomeDir,
|
HomeDir,
|
||||||
|
|
||||||
@ -96,12 +103,20 @@ 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 {
|
||||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
match *self {
|
match *self {
|
||||||
PeachError::HomeDir => None,
|
PeachError::HomeDir => None,
|
||||||
|
PeachError::InvalidKey { .. } => None,
|
||||||
PeachError::Io(_) => None,
|
PeachError::Io(_) => None,
|
||||||
PeachError::JsonRpcClientCore(_) => None,
|
PeachError::JsonRpcClientCore(_) => None,
|
||||||
PeachError::JsonRpcCore(_) => None,
|
PeachError::JsonRpcCore(_) => None,
|
||||||
@ -123,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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,6 +147,9 @@ impl std::error::Error for PeachError {
|
|||||||
impl std::fmt::Display for PeachError {
|
impl std::fmt::Display for PeachError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
|
PeachError::InvalidKey { ref key } => {
|
||||||
|
write!(f, "Invalid key in config lookup for key: {}", key)
|
||||||
|
}
|
||||||
PeachError::HomeDir => {
|
PeachError::HomeDir => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
@ -177,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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -246,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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use async_std::task;
|
use async_std::task;
|
||||||
use golgi::Sbot;
|
use golgi::{sbot::Keystore, Sbot};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use nanorand::{Rng, WyRand};
|
use nanorand::{Rng, WyRand};
|
||||||
use sha3::{Digest, Sha3_256};
|
use sha3::{Digest, Sha3_256};
|
||||||
@ -33,7 +33,7 @@ pub fn validate_new_passwords(new_password1: &str, new_password2: &str) -> Resul
|
|||||||
/// Sets a new password for the admin user
|
/// Sets a new password for the admin user
|
||||||
pub fn set_new_password(new_password: &str) -> Result<(), PeachError> {
|
pub fn set_new_password(new_password: &str) -> Result<(), PeachError> {
|
||||||
let new_password_hash = hash_password(new_password);
|
let new_password_hash = hash_password(new_password);
|
||||||
config_manager::set_admin_password_hash(&new_password_hash)?;
|
config_manager::set_admin_password_hash(new_password_hash)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -53,7 +53,7 @@ pub fn hash_password(password: &str) -> String {
|
|||||||
/// which can be used to reset the permanent password
|
/// which can be used to reset the permanent password
|
||||||
pub fn set_new_temporary_password(new_password: &str) -> Result<(), PeachError> {
|
pub fn set_new_temporary_password(new_password: &str) -> Result<(), PeachError> {
|
||||||
let new_password_hash = hash_password(new_password);
|
let new_password_hash = hash_password(new_password);
|
||||||
config_manager::set_temporary_password_hash(&new_password_hash)?;
|
config_manager::set_temporary_password_hash(new_password_hash)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -103,8 +103,8 @@ using this link: http://peach.local/auth/reset",
|
|||||||
};
|
};
|
||||||
msg += &remote_link;
|
msg += &remote_link;
|
||||||
// finally send the message to the admins
|
// finally send the message to the admins
|
||||||
let peach_config = config_manager::load_peach_config()?;
|
let ssb_admin_ids = config_manager::get_ssb_admin_ids()?;
|
||||||
for ssb_admin_id in peach_config.ssb_admin_ids {
|
for ssb_admin_id in ssb_admin_ids {
|
||||||
// use golgi to send a private message on scuttlebutt
|
// use golgi to send a private message on scuttlebutt
|
||||||
match task::block_on(publish_private_msg(&msg, &ssb_admin_id)) {
|
match task::block_on(publish_private_msg(&msg, &ssb_admin_id)) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
@ -126,11 +126,13 @@ async fn publish_private_msg(msg: &str, recipient: &str) -> Result<(), String> {
|
|||||||
// 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(Some(ip_port), None)
|
Sbot::init(Keystore::GoSbot, Some(ip_port), None)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
}
|
}
|
||||||
None => Sbot::init(None, None).await.map_err(|e| e.to_string())?,
|
None => Sbot::init(Keystore::GoSbot, None, None)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?,
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("Publishing a Scuttlebutt private message with temporary password");
|
debug!("Publishing a Scuttlebutt private message with temporary password");
|
||||||
|
@ -2,6 +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 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;
|
||||||
@ -60,12 +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
|
||||||
|
// because non-privileged users are able to run systemctl show
|
||||||
let info_output = Command::new("systemctl")
|
let info_output = Command::new("systemctl")
|
||||||
.arg("--user")
|
|
||||||
.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()?;
|
||||||
|
|
||||||
@ -83,10 +123,11 @@ impl SbotStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// note this command does not need to be run as sudo
|
||||||
|
// because non-privileged users are able to run systemctl status
|
||||||
let status_output = Command::new("systemctl")
|
let status_output = Command::new("systemctl")
|
||||||
.arg("--user")
|
|
||||||
.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)?;
|
||||||
@ -104,8 +145,8 @@ impl SbotStatus {
|
|||||||
// using the index of the first ';' + 2 and the last ';'
|
// using the index of the first ';' + 2 and the last ';'
|
||||||
status.boot_state = Some(line[start + 2..end].to_string());
|
status.boot_state = Some(line[start + 2..end].to_string());
|
||||||
}
|
}
|
||||||
// example of the output line we're looking for here:
|
// 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`
|
// `Active: active (running) since Mon 2022-01-24 16:22:51 SAST; 4min 14s ago`
|
||||||
} else if line.contains("Active:") {
|
} else if line.contains("Active:") {
|
||||||
let before_time = line.find(';');
|
let before_time = line.find(';');
|
||||||
let after_time = line.find(" ago");
|
let after_time = line.find(" ago");
|
||||||
@ -116,7 +157,7 @@ impl SbotStatus {
|
|||||||
// if service is active then the `time` reading is uptime
|
// if service is active then the `time` reading is uptime
|
||||||
if status.state == Some("active".to_string()) {
|
if status.state == Some("active".to_string()) {
|
||||||
status.uptime = time.map(|t| t.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()) {
|
} else if status.state == Some("inactive".to_string()) {
|
||||||
status.downtime = time.map(|t| t.to_string())
|
status.downtime = time.map(|t| t.to_string())
|
||||||
}
|
}
|
||||||
@ -124,11 +165,11 @@ impl SbotStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine path of user's home directory
|
// get path to blobstore
|
||||||
let mut blobstore_path = dirs::home_dir().ok_or(PeachError::HomeDir)?;
|
let blobstore_path = format!(
|
||||||
|
"{}/blobs/sha256",
|
||||||
// append the blobstore path
|
config_manager::get_config_value("GO_SBOT_DATADIR")?
|
||||||
blobstore_path.push(".ssb-go/blobs/sha256");
|
);
|
||||||
|
|
||||||
// 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,9 +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 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")?
|
||||||
|
);
|
||||||
|
|
||||||
// open config file for writing
|
// open config file for writing
|
||||||
let mut file = File::create(config_path)?;
|
let mut file = File::create(config_path)?;
|
||||||
@ -233,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)
|
||||||
|
}
|
||||||
|
@ -6,7 +6,7 @@ use std::num::ParseIntError;
|
|||||||
use io::Error as IoError;
|
use io::Error as IoError;
|
||||||
use probes::ProbeError;
|
use probes::ProbeError;
|
||||||
use regex::Error as RegexError;
|
use regex::Error as RegexError;
|
||||||
use wpactrl::WpaError;
|
use wpactrl::Error as WpaError;
|
||||||
|
|
||||||
/// Custom error type encapsulating all possible errors when querying
|
/// Custom error type encapsulating all possible errors when querying
|
||||||
/// network interfaces and modifying their state.
|
/// network interfaces and modifying their state.
|
||||||
|
@ -22,6 +22,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use probes::network;
|
use probes::network;
|
||||||
|
use wpactrl::Client as WpaClient;
|
||||||
|
|
||||||
#[cfg(feature = "miniserde_support")]
|
#[cfg(feature = "miniserde_support")]
|
||||||
use miniserde::{Deserialize, Serialize};
|
use miniserde::{Deserialize, Serialize};
|
||||||
@ -121,7 +122,7 @@ pub struct Traffic {
|
|||||||
/// In the event of an error, a `NetworkError` is returned in the `Result`.
|
/// In the event of an error, a `NetworkError` is returned in the `Result`.
|
||||||
pub fn available_networks(iface: &str) -> Result<Option<Vec<Scan>>, NetworkError> {
|
pub fn available_networks(iface: &str) -> Result<Option<Vec<Scan>>, NetworkError> {
|
||||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
let mut wpa = WpaClient::builder().ctrl_path(wpa_path).open()?;
|
||||||
wpa.request("SCAN")?;
|
wpa.request("SCAN")?;
|
||||||
let networks = wpa.request("SCAN_RESULTS")?;
|
let networks = wpa.request("SCAN_RESULTS")?;
|
||||||
let mut scan = Vec::new();
|
let mut scan = Vec::new();
|
||||||
@ -173,7 +174,7 @@ pub fn available_networks(iface: &str) -> Result<Option<Vec<Scan>>, NetworkError
|
|||||||
/// event of an error, a `NetworkError` is returned in the `Result`.
|
/// event of an error, a `NetworkError` is returned in the `Result`.
|
||||||
pub fn id(iface: &str, ssid: &str) -> Result<Option<String>, NetworkError> {
|
pub fn id(iface: &str, ssid: &str) -> Result<Option<String>, NetworkError> {
|
||||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
let mut wpa = WpaClient::builder().ctrl_path(wpa_path).open()?;
|
||||||
let networks = wpa.request("LIST_NETWORKS")?;
|
let networks = wpa.request("LIST_NETWORKS")?;
|
||||||
let mut id = Vec::new();
|
let mut id = Vec::new();
|
||||||
for network in networks.lines() {
|
for network in networks.lines() {
|
||||||
@ -232,7 +233,7 @@ pub fn ip(iface: &str) -> Result<Option<String>, NetworkError> {
|
|||||||
/// `Result`.
|
/// `Result`.
|
||||||
pub fn rssi(iface: &str) -> Result<Option<String>, NetworkError> {
|
pub fn rssi(iface: &str) -> Result<Option<String>, NetworkError> {
|
||||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
let mut wpa = WpaClient::builder().ctrl_path(wpa_path).open()?;
|
||||||
let status = wpa.request("SIGNAL_POLL")?;
|
let status = wpa.request("SIGNAL_POLL")?;
|
||||||
let rssi = utils::regex_finder(r"RSSI=(.*)\n", &status)?;
|
let rssi = utils::regex_finder(r"RSSI=(.*)\n", &status)?;
|
||||||
|
|
||||||
@ -259,7 +260,7 @@ pub fn rssi(iface: &str) -> Result<Option<String>, NetworkError> {
|
|||||||
/// the `Result`.
|
/// the `Result`.
|
||||||
pub fn rssi_percent(iface: &str) -> Result<Option<String>, NetworkError> {
|
pub fn rssi_percent(iface: &str) -> Result<Option<String>, NetworkError> {
|
||||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
let mut wpa = WpaClient::builder().ctrl_path(wpa_path).open()?;
|
||||||
let status = wpa.request("SIGNAL_POLL")?;
|
let status = wpa.request("SIGNAL_POLL")?;
|
||||||
let rssi = utils::regex_finder(r"RSSI=(.*)\n", &status)?;
|
let rssi = utils::regex_finder(r"RSSI=(.*)\n", &status)?;
|
||||||
|
|
||||||
@ -291,7 +292,7 @@ pub fn rssi_percent(iface: &str) -> Result<Option<String>, NetworkError> {
|
|||||||
/// is returned in the `Result`. In the event of an error, a `NetworkError` is
|
/// is returned in the `Result`. In the event of an error, a `NetworkError` is
|
||||||
/// returned in the `Result`.
|
/// returned in the `Result`.
|
||||||
pub fn saved_networks() -> Result<Option<Vec<String>>, NetworkError> {
|
pub fn saved_networks() -> Result<Option<Vec<String>>, NetworkError> {
|
||||||
let mut wpa = wpactrl::WpaCtrl::builder().open()?;
|
let mut wpa = WpaClient::builder().open()?;
|
||||||
let networks = wpa.request("LIST_NETWORKS")?;
|
let networks = wpa.request("LIST_NETWORKS")?;
|
||||||
let mut ssids = Vec::new();
|
let mut ssids = Vec::new();
|
||||||
for network in networks.lines() {
|
for network in networks.lines() {
|
||||||
@ -323,7 +324,7 @@ pub fn saved_networks() -> Result<Option<Vec<String>>, NetworkError> {
|
|||||||
/// returned in the `Result`.
|
/// returned in the `Result`.
|
||||||
pub fn ssid(iface: &str) -> Result<Option<String>, NetworkError> {
|
pub fn ssid(iface: &str) -> Result<Option<String>, NetworkError> {
|
||||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
let mut wpa = WpaClient::builder().ctrl_path(wpa_path).open()?;
|
||||||
let status = wpa.request("STATUS")?;
|
let status = wpa.request("STATUS")?;
|
||||||
|
|
||||||
// pass the regex pattern and status output to the regex finder
|
// pass the regex pattern and status output to the regex finder
|
||||||
@ -379,7 +380,7 @@ pub fn state(iface: &str) -> Result<Option<String>, NetworkError> {
|
|||||||
/// a `NetworkError` is returned in the `Result`.
|
/// a `NetworkError` is returned in the `Result`.
|
||||||
pub fn status(iface: &str) -> Result<Option<Status>, NetworkError> {
|
pub fn status(iface: &str) -> Result<Option<Status>, NetworkError> {
|
||||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
let mut wpa = WpaClient::builder().ctrl_path(wpa_path).open()?;
|
||||||
let wpa_status = wpa.request("STATUS")?;
|
let wpa_status = wpa.request("STATUS")?;
|
||||||
|
|
||||||
// pass the regex pattern and status output to the regex finder
|
// pass the regex pattern and status output to the regex finder
|
||||||
@ -579,7 +580,7 @@ pub fn check_iface(wlan_iface: &str, ap_iface: &str) -> Result<(), NetworkError>
|
|||||||
/// is returned in the `Result`.
|
/// is returned in the `Result`.
|
||||||
pub fn connect(id: &str, iface: &str) -> Result<(), NetworkError> {
|
pub fn connect(id: &str, iface: &str) -> Result<(), NetworkError> {
|
||||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
let mut wpa = WpaClient::builder().ctrl_path(wpa_path).open()?;
|
||||||
let select = format!("SELECT {}", id);
|
let select = format!("SELECT {}", id);
|
||||||
wpa.request(&select)?;
|
wpa.request(&select)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -598,7 +599,7 @@ pub fn connect(id: &str, iface: &str) -> Result<(), NetworkError> {
|
|||||||
/// returned in the `Result`.
|
/// returned in the `Result`.
|
||||||
pub fn delete(id: &str, iface: &str) -> Result<(), NetworkError> {
|
pub fn delete(id: &str, iface: &str) -> Result<(), NetworkError> {
|
||||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
let mut wpa = WpaClient::builder().ctrl_path(wpa_path).open()?;
|
||||||
let remove = format!("REMOVE_NETWORK {}", id);
|
let remove = format!("REMOVE_NETWORK {}", id);
|
||||||
wpa.request(&remove)?;
|
wpa.request(&remove)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -617,7 +618,7 @@ pub fn delete(id: &str, iface: &str) -> Result<(), NetworkError> {
|
|||||||
/// `Result`.
|
/// `Result`.
|
||||||
pub fn disable(id: &str, iface: &str) -> Result<(), NetworkError> {
|
pub fn disable(id: &str, iface: &str) -> Result<(), NetworkError> {
|
||||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
let mut wpa = WpaClient::builder().ctrl_path(wpa_path).open()?;
|
||||||
let disable = format!("DISABLE_NETWORK {}", id);
|
let disable = format!("DISABLE_NETWORK {}", id);
|
||||||
wpa.request(&disable)?;
|
wpa.request(&disable)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -634,7 +635,7 @@ pub fn disable(id: &str, iface: &str) -> Result<(), NetworkError> {
|
|||||||
/// error, a `NetworkError` is returned in the `Result`.
|
/// error, a `NetworkError` is returned in the `Result`.
|
||||||
pub fn disconnect(iface: &str) -> Result<(), NetworkError> {
|
pub fn disconnect(iface: &str) -> Result<(), NetworkError> {
|
||||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
let mut wpa = WpaClient::builder().ctrl_path(wpa_path).open()?;
|
||||||
let disconnect = "DISCONNECT".to_string();
|
let disconnect = "DISCONNECT".to_string();
|
||||||
wpa.request(&disconnect)?;
|
wpa.request(&disconnect)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -685,7 +686,7 @@ pub fn forget(iface: &str, ssid: &str) -> Result<(), NetworkError> {
|
|||||||
/// event of an error, a `NetworkError` is returned in the `Result`.
|
/// event of an error, a `NetworkError` is returned in the `Result`.
|
||||||
pub fn modify(id: &str, iface: &str, pass: &str) -> Result<(), NetworkError> {
|
pub fn modify(id: &str, iface: &str, pass: &str) -> Result<(), NetworkError> {
|
||||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
let mut wpa = WpaClient::builder().ctrl_path(wpa_path).open()?;
|
||||||
let new_pass = format!("NEW_PASSWORD {} {}", id, pass);
|
let new_pass = format!("NEW_PASSWORD {} {}", id, pass);
|
||||||
wpa.request(&new_pass)?;
|
wpa.request(&new_pass)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -702,7 +703,7 @@ pub fn modify(id: &str, iface: &str, pass: &str) -> Result<(), NetworkError> {
|
|||||||
/// error, a `NetworkError` is returned in the `Result`.
|
/// error, a `NetworkError` is returned in the `Result`.
|
||||||
pub fn reassociate(iface: &str) -> Result<(), NetworkError> {
|
pub fn reassociate(iface: &str) -> Result<(), NetworkError> {
|
||||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
let mut wpa = WpaClient::builder().ctrl_path(wpa_path).open()?;
|
||||||
wpa.request("REASSOCIATE")?;
|
wpa.request("REASSOCIATE")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -714,7 +715,7 @@ pub fn reassociate(iface: &str) -> Result<(), NetworkError> {
|
|||||||
/// `Result` type is returned. In the event of an error, a `NetworkError` is
|
/// `Result` type is returned. In the event of an error, a `NetworkError` is
|
||||||
/// returned in the `Result`.
|
/// returned in the `Result`.
|
||||||
pub fn reconfigure() -> Result<(), NetworkError> {
|
pub fn reconfigure() -> Result<(), NetworkError> {
|
||||||
let mut wpa = wpactrl::WpaCtrl::builder().open()?;
|
let mut wpa = WpaClient::builder().open()?;
|
||||||
wpa.request("RECONFIGURE")?;
|
wpa.request("RECONFIGURE")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -730,7 +731,7 @@ pub fn reconfigure() -> Result<(), NetworkError> {
|
|||||||
/// event of an error, a `NetworkError` is returned in the `Result`.
|
/// event of an error, a `NetworkError` is returned in the `Result`.
|
||||||
pub fn reconnect(iface: &str) -> Result<(), NetworkError> {
|
pub fn reconnect(iface: &str) -> Result<(), NetworkError> {
|
||||||
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
let wpa_path: String = format!("/var/run/wpa_supplicant/{}", iface);
|
||||||
let mut wpa = wpactrl::WpaCtrl::builder().ctrl_path(wpa_path).open()?;
|
let mut wpa = WpaClient::builder().ctrl_path(wpa_path).open()?;
|
||||||
wpa.request("DISCONNECT")?;
|
wpa.request("DISCONNECT")?;
|
||||||
wpa.request("RECONNECT")?;
|
wpa.request("RECONNECT")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -742,7 +743,7 @@ pub fn reconnect(iface: &str) -> Result<(), NetworkError> {
|
|||||||
/// `wpa_supplicant.conf` file, an `Ok` `Result` type is returned. In the
|
/// `wpa_supplicant.conf` file, an `Ok` `Result` type is returned. In the
|
||||||
/// event of an error, a `NetworkError` is returned in the `Result`.
|
/// event of an error, a `NetworkError` is returned in the `Result`.
|
||||||
pub fn save() -> Result<(), NetworkError> {
|
pub fn save() -> Result<(), NetworkError> {
|
||||||
let mut wpa = wpactrl::WpaCtrl::builder().open()?;
|
let mut wpa = WpaClient::builder().open()?;
|
||||||
wpa.request("SAVE_CONFIG")?;
|
wpa.request("SAVE_CONFIG")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -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."
|
||||||
|
@ -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()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "peach-web"
|
name = "peach-web"
|
||||||
version = "0.6.0"
|
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"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# peach-web
|
# peach-web
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 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.
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Rocket web application for serving the PeachCloud web interface.
|
Description=Rouille web application for serving the PeachCloud web interface.
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
User=peach-web
|
User=peach
|
||||||
Group=www-data
|
Group=peach
|
||||||
WorkingDirectory=/usr/share/peach-web
|
WorkingDirectory=/usr/share/peach-web
|
||||||
Environment="ROCKET_ENV=prod"
|
|
||||||
Environment="ROCKET_ADDRESS=127.0.0.1"
|
|
||||||
Environment="ROCKET_PORT=3000"
|
|
||||||
Environment="ROCKET_LOG=critical"
|
|
||||||
Environment="RUST_LOG=info"
|
Environment="RUST_LOG=info"
|
||||||
ExecStart=/usr/bin/peach-web
|
ExecStart=/usr/bin/peach-web
|
||||||
Restart=always
|
Restart=always
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
# create user which peach-web runs as
|
# create user which peach-web runs as
|
||||||
adduser --quiet --system peach-web
|
id -u peach &>/dev/null || adduser --quiet peach
|
||||||
usermod -g peach peach-web
|
|
||||||
|
|
||||||
# create nginx config
|
# create nginx config
|
||||||
cat <<EOF > /etc/nginx/sites-enabled/default
|
cat <<EOF > /etc/nginx/sites-enabled/default
|
||||||
@ -15,16 +14,25 @@ server {
|
|||||||
rewrite ^/(.*)/$ /$1 permanent;
|
rewrite ^/(.*)/$ /$1 permanent;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://127.0.0.1:3000;
|
proxy_pass http://127.0.0.1:8000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat <<EOF > /etc/sudoers.d/peach-web
|
# update sudoers to allow peach-web to stop and restart go-sbot.service
|
||||||
# allow peach-web to run commands as peach-go-sbot without a password
|
mkdir -p /etc/sudoers.d/
|
||||||
peach-web ALL=(peach-go-sbot) NOPASSWD:ALL
|
|
||||||
|
|
||||||
|
SYSTEMCTL=/bin/systemctl
|
||||||
|
START="${SYSTEMCTL} start go-sbot.service"
|
||||||
|
RESTART="${SYSTEMCTL} restart go-sbot.service"
|
||||||
|
STOP="${SYSTEMCTL} stop go-sbot.service"
|
||||||
|
ENABLE="${SYSTEMCTL} enable go-sbot.service"
|
||||||
|
DISABLE="${SYSTEMCTL} disable go-sbot.service"
|
||||||
|
|
||||||
|
cat <<EOF > /etc/sudoers.d/peach-web
|
||||||
|
peach ALL=(ALL) NOPASSWD: $START, $STOP, $RESTART, $ENABLE, $DISABLE
|
||||||
EOF
|
EOF
|
||||||
|
chmod 0440 /etc/sudoers.d/peach-web
|
||||||
|
|
||||||
# cargo deb automatically replaces this token below, see https://github.com/mmstick/cargo-deb/blob/master/systemd.md
|
# cargo deb automatically replaces this token below, see https://github.com/mmstick/cargo-deb/blob/master/systemd.md
|
||||||
#DEBHELPER#
|
#DEBHELPER#
|
@ -1,53 +1,31 @@
|
|||||||
//! Define the configuration parameters for the web application.
|
//! Define the configuration parameters for the web application.
|
||||||
//!
|
//!
|
||||||
//! Sets default values and updates them if the corresponding environment
|
//! These configs are loaded using peach-lib::config_manager which checks config keys from
|
||||||
//! variables have been set.
|
//! three sources:
|
||||||
|
//! 1. from environmental variables
|
||||||
|
//! 2. from a configuration file
|
||||||
|
//! 3. from default values
|
||||||
|
|
||||||
use std::env;
|
use crate::error::PeachWebError;
|
||||||
|
use peach_lib::config_manager::get_config_value;
|
||||||
|
|
||||||
// environment variable keys to check for
|
pub struct ServerConfig {
|
||||||
const ENV_VARS: [&str; 4] = ["STANDALONE_MODE", "DISABLE_AUTH", "ADDR", "PORT"];
|
|
||||||
|
|
||||||
pub struct Config {
|
|
||||||
pub standalone_mode: bool,
|
pub standalone_mode: bool,
|
||||||
pub disable_auth: bool,
|
pub disable_auth: bool,
|
||||||
pub addr: String,
|
pub addr: String,
|
||||||
pub port: String,
|
pub port: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl ServerConfig {
|
||||||
fn default() -> Self {
|
pub fn new() -> Result<ServerConfig, PeachWebError> {
|
||||||
Self {
|
|
||||||
standalone_mode: true,
|
|
||||||
disable_auth: false,
|
|
||||||
addr: "127.0.0.1".to_string(),
|
|
||||||
port: "8000".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn new() -> Config {
|
|
||||||
// define default config values
|
// define default config values
|
||||||
let mut config = Config::default();
|
let config = ServerConfig {
|
||||||
|
standalone_mode: get_config_value("STANDALONE_MODE")?.as_str() == "true",
|
||||||
|
disable_auth: get_config_value("DISABLE_AUTH")?.as_str() == "true",
|
||||||
|
addr: get_config_value("ADDR")?,
|
||||||
|
port: get_config_value("PORT")?,
|
||||||
|
};
|
||||||
|
|
||||||
// check for the environment variables in our config
|
Ok(config)
|
||||||
for key in ENV_VARS {
|
|
||||||
// if a variable (key) has been set, check the value
|
|
||||||
if let Ok(val) = env::var(key) {
|
|
||||||
// if the value is of the correct type, update the config value
|
|
||||||
match key {
|
|
||||||
"STANDALONE_MODE" if val.as_str() == "true" => config.standalone_mode = true,
|
|
||||||
"STANDALONE_MODE" if val.as_str() == "false" => config.standalone_mode = false,
|
|
||||||
"DISABLE_AUTH" if val.as_str() == "true" => config.disable_auth = true,
|
|
||||||
"DISABLE_AUTH" if val.as_str() == "false" => config.disable_auth = false,
|
|
||||||
"ADDR" => config.addr = val,
|
|
||||||
"PORT" => config.port = val,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,16 +26,16 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use log::{debug, info};
|
use log::info;
|
||||||
use peach_lib::{config_manager, config_manager::YAML_PATH as PEACH_CONFIG};
|
|
||||||
|
|
||||||
// crate-local dependencies
|
// crate-local dependencies
|
||||||
use config::Config;
|
use config::ServerConfig;
|
||||||
use utils::theme::Theme;
|
use utils::theme::Theme;
|
||||||
|
|
||||||
// load the application configuration and create the theme switcher
|
// load the application configuration and create the theme switcher
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref CONFIG: Config = Config::new();
|
static ref SERVER_CONFIG: ServerConfig =
|
||||||
|
ServerConfig::new().expect("Failed to load rouille configuration values on server startup");
|
||||||
static ref THEME: RwLock<Theme> = RwLock::new(Theme::Light);
|
static ref THEME: RwLock<Theme> = RwLock::new(Theme::Light);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,21 +50,9 @@ fn main() {
|
|||||||
// initialize logger
|
// initialize logger
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
// check if /var/lib/peachcloud/config.yml exists
|
|
||||||
if !std::path::Path::new(PEACH_CONFIG).exists() {
|
|
||||||
debug!("PeachCloud configuration file not found; loading default values");
|
|
||||||
// since we're in the intialisation phase, panic if the loading fails
|
|
||||||
let config =
|
|
||||||
config_manager::load_peach_config().expect("peachcloud configuration loading failed");
|
|
||||||
|
|
||||||
debug!("Saving default PeachCloud configuration values to file");
|
|
||||||
// this ensures a config file is created if it does not already exist
|
|
||||||
config_manager::save_peach_config(config).expect("peachcloud configuration saving failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// set ip address / hostname and port for the webserver
|
// set ip address / hostname and port for the webserver
|
||||||
// defaults to "127.0.0.1:8000"
|
// defaults to "127.0.0.1:8000"
|
||||||
let addr_and_port = format!("{}:{}", CONFIG.addr, CONFIG.port);
|
let addr_and_port = format!("{}:{}", SERVER_CONFIG.addr, SERVER_CONFIG.port);
|
||||||
|
|
||||||
// store the session data for each session and a hashmap that associates
|
// store the session data for each session and a hashmap that associates
|
||||||
// each session id with the data
|
// each session id with the data
|
||||||
@ -80,7 +68,7 @@ fn main() {
|
|||||||
// with a name of "SID" and a duration of one hour (3600 seconds)
|
// with a name of "SID" and a duration of one hour (3600 seconds)
|
||||||
rouille::session::session(request, "SID", 3600, |session| {
|
rouille::session::session(request, "SID", 3600, |session| {
|
||||||
// if the "DISABLE_AUTH" env var is true, authenticate the session
|
// if the "DISABLE_AUTH" env var is true, authenticate the session
|
||||||
let mut session_data = if CONFIG.disable_auth {
|
let mut session_data = if SERVER_CONFIG.disable_auth {
|
||||||
Some(SessionData {
|
Some(SessionData {
|
||||||
_login: "success".to_string(),
|
_login: "success".to_string(),
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ pub fn build_template() -> PreEscaped<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
" to start the sbot. If the server starts successfully, you will see a green smiley face on the home page. If the face is orange and sleeping, that means the sbot is still inactive (ie. the process is not running). If the face is red and dead, that means the sbot failed to start - indicated an error. For now, the best way to gain insight into the problem is to check the systemd log. Open a terminal and enter: "
|
" to start the sbot. If the server starts successfully, you will see a green smiley face on the home page. If the face is orange and sleeping, that means the sbot is still inactive (ie. the process is not running). If the face is red and dead, that means the sbot failed to start - indicated an error. For now, the best way to gain insight into the problem is to check the systemd log. Open a terminal and enter: "
|
||||||
code { "systemctl --user status go-sbot.service" }
|
code { "systemctl status go-sbot.service" }
|
||||||
". The log output may give some clues about the source of the error."
|
". The log output may give some clues about the source of the error."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -13,8 +13,8 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
|
|||||||
let (mut flash_name, mut flash_msg) = request.retrieve_flash();
|
let (mut flash_name, mut flash_msg) = request.retrieve_flash();
|
||||||
|
|
||||||
// attempt to load peachcloud config file
|
// attempt to load peachcloud config file
|
||||||
let ssb_admins = match config_manager::load_peach_config() {
|
let ssb_admins = match config_manager::get_ssb_admin_ids() {
|
||||||
Ok(config) => Some(config.ssb_admin_ids),
|
Ok(ssb_admin_ids) => Some(ssb_admin_ids),
|
||||||
// note: this will overwrite any received flash cookie values
|
// note: this will overwrite any received flash cookie values
|
||||||
// TODO: find a way to include the `err` in the flash_msg
|
// TODO: find a way to include the `err` in the flash_msg
|
||||||
// currently produces an error because we end up with Some(String)
|
// currently produces an error because we end up with Some(String)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use maud::{html, PreEscaped};
|
use maud::{html, PreEscaped};
|
||||||
|
|
||||||
use crate::{templates, utils::theme, CONFIG};
|
use crate::{templates, utils::theme, SERVER_CONFIG};
|
||||||
|
|
||||||
// ROUTE: /settings
|
// ROUTE: /settings
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ pub fn build_template() -> PreEscaped<String> {
|
|||||||
(PreEscaped("<!-- BUTTONS -->"))
|
(PreEscaped("<!-- BUTTONS -->"))
|
||||||
div id="settingsButtons" {
|
div id="settingsButtons" {
|
||||||
// render the network settings button if we're not in standalone mode
|
// render the network settings button if we're not in standalone mode
|
||||||
@if !CONFIG.standalone_mode {
|
@if !SERVER_CONFIG.standalone_mode {
|
||||||
a id="network" class="button button-primary center" href="/settings/network" title="Network Settings" { "Network" }
|
a id="network" class="button button-primary center" href="/settings/network" title="Network Settings" { "Network" }
|
||||||
}
|
}
|
||||||
a id="scuttlebutt" class="button button-primary center" href="/settings/scuttlebutt" title="Scuttlebutt Settings" { "Scuttlebutt" }
|
a id="scuttlebutt" class="button button-primary center" href="/settings/scuttlebutt" title="Scuttlebutt Settings" { "Scuttlebutt" }
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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(),
|
||||||
|
@ -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(),
|
||||||
|
@ -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(),
|
||||||
|
@ -53,7 +53,6 @@ fn run_on_startup_element(boot_state: &Option<String>) -> Markup {
|
|||||||
fn database_element(state: &str) -> Markup {
|
fn database_element(state: &str) -> Markup {
|
||||||
// retrieve the sequence number of the latest message in the sbot database
|
// retrieve the sequence number of the latest message in the sbot database
|
||||||
let sequence_num = sbot::latest_sequence_number();
|
let sequence_num = sbot::latest_sequence_number();
|
||||||
|
|
||||||
match (state, sequence_num) {
|
match (state, sequence_num) {
|
||||||
// if the state is "active" and latest_sequence_number() was successful
|
// if the state is "active" and latest_sequence_number() was successful
|
||||||
("active", Ok(number)) => {
|
("active", Ok(number)) => {
|
||||||
@ -62,7 +61,9 @@ fn database_element(state: &str) -> Markup {
|
|||||||
label class="label-small font-gray" { "MESSAGES IN LOCAL DATABASE" }
|
label class="label-small font-gray" { "MESSAGES IN LOCAL DATABASE" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(_, _) => html! { label class="label-small font-gray" { "DATABASE UNAVAILABLE" } },
|
(_, _) => {
|
||||||
|
html! { label class="label-small font-gray" { "DATABASE UNAVAILABLE" } }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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) }
|
||||||
|
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 flash;
|
||||||
pub mod sbot;
|
pub mod sbot;
|
||||||
pub mod theme;
|
pub mod theme;
|
||||||
|
@ -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},
|
||||||
@ -12,8 +11,11 @@ use std::{
|
|||||||
use async_std::task;
|
use async_std::task;
|
||||||
use dirs;
|
use dirs;
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
use golgi::{api::friends::RelationshipQuery, blobs, messages::SsbMessageValue, Sbot};
|
use golgi::{
|
||||||
|
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;
|
||||||
@ -22,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("systemctl")
|
/// This utility function calls the correct system calls based on these parameters.
|
||||||
.arg("--user")
|
pub fn system_sbot_cmd(cmd: &str) -> Result<Output, PeachWebError> {
|
||||||
.arg(cmd)
|
let system_manager = config_manager::get_config_value("SYSTEM_MANAGER")?;
|
||||||
.arg("go-sbot.service")
|
match system_manager.as_str() {
|
||||||
.output()
|
"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.
|
/// 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(),
|
||||||
@ -66,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(Some(ip_port), None).await?
|
Sbot::init(Keystore::CustomGoSbot(key_path), Some(ip_port), None).await?
|
||||||
}
|
}
|
||||||
None => Sbot::init(None, None).await?,
|
None => Sbot::init(Keystore::CustomGoSbot(key_path), None, None).await?,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(sbot_client)
|
Ok(sbot_client)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,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() {
|
||||||
@ -138,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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -151,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)
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user