9 Commits

Author SHA1 Message Date
bc190051db bundle change-password cli with peach-web 2025-05-26 17:56:04 -04:00
eaca10954e working 2025-05-23 14:01:38 -04:00
8501296ea1 working version with yunohost 2025-05-21 18:07:38 -04:00
1439bb942f working version with binary wrapper 2025-05-21 17:28:55 -04:00
ce861c69ba README 2025-05-08 15:36:39 -04:00
ac875097b3 working bash script 2025-05-08 14:51:07 -04:00
0e655841f5 almost working with tilde 2025-05-08 14:01:58 -04:00
bf05149d93 peachpub mostly working 2025-05-06 14:03:06 -04:00
fc2ea78876 mostly working with tilde 2025-03-08 16:24:26 -05:00
57 changed files with 735 additions and 548 deletions

8
Cargo.lock generated
View File

@ -2575,7 +2575,9 @@ dependencies = [
"peach-stats", "peach-stats",
"reqwest", "reqwest",
"rouille", "rouille",
"rpassword",
"temporary", "temporary",
"urlencoding",
"vnstat_parse", "vnstat_parse",
"xdg", "xdg",
] ]
@ -4187,6 +4189,12 @@ dependencies = [
"percent-encoding 2.1.0", "percent-encoding 2.1.0",
] ]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]] [[package]]
name = "value-bag" name = "value-bag"
version = "1.0.0-alpha.9" version = "1.0.0-alpha.9"

View File

@ -0,0 +1,74 @@
# [PeachCloud](http://peachcloud.org) :peach: :cloud:
_Better [Scuttlebutt](https://scuttlebutt.nz) cloud infrastructure as a hardware product._
[**_Support us on OpenCollective!_**](https://opencollective.com/peachcloud)
[![Build Status](https://build.coopcloud.tech/api/badges/PeachCloud/peach-workspace/status.svg?ref=refs/heads/main)](https://build.coopcloud.tech/PeachCloud/peach-workspace)
## Background
- April 2018 project proposal: [`%HqwAsltORROCh4uyOq6iV+SsqU3OuNUevnq+5dwCqVI=.sha256`](https://viewer.scuttlebot.io/%25HqwAsltORROCh4uyOq6iV%2BSsqU3OuNUevnq%2B5dwCqVI%3D.sha256)
- November 2018 project pivot: [`%9NCyTf+oBxG0APlXRCKtrGZj3t+i+Kp3pKPN1gtFX2c=.sha256`](https://viewer.scuttlebot.io/%259NCyTf%2BoBxG0APlXRCKtrGZj3t%2Bi%2BKp3pKPN1gtFX2c%3D.sha256)
## Active Repositories
**Documentation**
- [peach-devdocs](https://github.com/peachcloud/peach-devdocs) - Developer documentation for PeachCloud in the form of a Markdown book
**Devops**
- [peach-vps](https://github.com/peachcloud/peach-vps) - Setup scripts and configuration files for deploying a PeachCloud development server
**Image building & device configuration**
- [peach-config](https://github.com/peachcloud/peach-config) - Configuration instructions, files and scripts
- [peach-img-builder](https://github.com/peachcloud/peach-img-builder) - Vmdb2 script for building a Debian disc image for Raspberry Pi with PeachCloud pre-installed
**Microservices**
- [peach-buttons](https://github.com/peachcloud/peach-buttons) - Emit GPIO events using JSON-RPC pubsub over WS
- [peach-oled](https://github.com/peachcloud/peach-oled) - Write and draw to OLED display using JSON-RPC over HTTP
- [peach-menu](https://github.com/peachcloud/peach-menu) - A menu for monitoring and interacting with the PeachCloud device
- [peach-network](https://github.com/peachcloud/peach-network) - Query and configure network interfaces using JSON-RPC over HTTP
- [peach-stats](https://github.com/peachcloud/peach-stats) - Query system statistics using JSON-RPC over HTTP
- [peach-lib](https://github.com/peachcloud/peach-lib) - JSON-RPC client library for the PeachCloud ecosystem
- [peach-monitor](https://github.com/peachcloud/peach-monitor) - Monitor network data usage and set alert flags based on user-defined thresholds
**Diagnostics**
- [peach-probe](https://github.com/peachcloud/peach-probe) - Probe PeachCloud microservices to evaluate their state and ensure correct API responses
**Web interface**
- [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
## 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
- [@ahdinosaur](https://github.com/ahdinosaur): `@6ilZq3kN0F+dXFHAPjAwMm87JEb/VdB+LC9eIMW3sa0=.ed25519`
- 1: [`%bSkZCJBmNYUmECNKYOiWkgEeRxrlo2UghNBzE6Cph94=.sha256`](https://viewer.scuttlebot.io/%25bSkZCJBmNYUmECNKYOiWkgEeRxrlo2UghNBzE6Cph94%3D.sha256)
- 2: [`%2L7gYAh2ih+7eFCrtObPWIUYHuGnJjwj4KCXrCIsWhM=.sha256`](https://viewer.scuttlebot.io/%252L7gYAh2ih%2B7eFCrtObPWIUYHuGnJjwj4KCXrCIsWhM%3D.sha256)
- [@mycognosist](https://github.com/mycognosist): `@HEqy940T6uB+T+d9Jaa58aNfRzLx9eRWqkZljBmnkmk=.ed25519`
- [`%mKUByRp4Gib6fqP1q2/dHg+ueSoR+Sj2Y0D7T0Np0D4=.sha256`](https://viewer.scuttlebot.io/%25mKUByRp4Gib6fqP1q2%2FdHg%2BueSoR%2BSj2Y0D7T0Np0D4%3D.sha256)
## Accounts
- [GitHub](https://github.com/peachcloud)
- [Twitter](https://twitter.com/peachcloudorg)
- [Email](mailto:peachcloudorg@gmail.com)
- [OpenCollective](https://opencollective.com/peachcloud)

View File

@ -2,73 +2,35 @@
_Better [Scuttlebutt](https://scuttlebutt.nz) cloud infrastructure as a hardware product._ _Better [Scuttlebutt](https://scuttlebutt.nz) cloud infrastructure as a hardware product._
[**_Support us on OpenCollective!_**](https://opencollective.com/peachcloud) ![image of peachcloud device](https://peach.commoninternet.net/assets/peachcloud.jpg)
## About
This project is a hack that combines PeachCloud with TildFriends.
The original PeachCloud project was paused when most development in the Scuttlebutt ecosystem stopped (reference),
but even after most funding and development and left the ecosystem, a version of the Sbot in C called TildeFriends was finished,
and continues to be maintained.
This fork of the PeachCloud project makes use of the TildeFriends sbot to make a minimal and functional PeachCloud Scuttlebutt pub,
that can be easily deployed and operated by a non-technical user.
Due to the timing and conditions of the Scuttlebutt ecosystem, and the rise of new protocols,
for this particular hack, the focus was on getting the project working in a minimal form (aka more hacky),
with less of a focus on long-term sustainability of the project as an ecosystem.
This project serves to provide a working simple-to-use pub for a P2P protocol which is in some ways frozen in amber,
as well as to share the PeachCloud interface and project, as a design-inspiration for other projects.
## Setup
TODO: how to install with yunohost
TODO: how to install without yunohost
[![Build Status](https://build.coopcloud.tech/api/badges/PeachCloud/peach-workspace/status.svg?ref=refs/heads/main)](https://build.coopcloud.tech/PeachCloud/peach-workspace)
## Background ## Background
- April 2018 project proposal: [`%HqwAsltORROCh4uyOq6iV+SsqU3OuNUevnq+5dwCqVI=.sha256`](https://viewer.scuttlebot.io/%25HqwAsltORROCh4uyOq6iV%2BSsqU3OuNUevnq%2B5dwCqVI%3D.sha256) - April 2018 project proposal: [`%HqwAsltORROCh4uyOq6iV+SsqU3OuNUevnq+5dwCqVI=.sha256`](https://viewer.scuttlebot.io/%25HqwAsltORROCh4uyOq6iV%2BSsqU3OuNUevnq%2B5dwCqVI%3D.sha256)
- November 2018 project pivot: [`%9NCyTf+oBxG0APlXRCKtrGZj3t+i+Kp3pKPN1gtFX2c=.sha256`](https://viewer.scuttlebot.io/%259NCyTf%2BoBxG0APlXRCKtrGZj3t%2Bi%2BKp3pKPN1gtFX2c%3D.sha256) - November 2018 project pivot: [`%9NCyTf+oBxG0APlXRCKtrGZj3t+i+Kp3pKPN1gtFX2c=.sha256`](https://viewer.scuttlebot.io/%259NCyTf%2BoBxG0APlXRCKtrGZj3t%2Bi%2BKp3pKPN1gtFX2c%3D.sha256)
- [Original PeachCloud ReadMe](/ORIGINAL-PEACHCLOUD-README.md)
## Active Repositories - [Original PeachCloud Documentation](https://peach.commoninternet.net)
**Documentation**
- [peach-devdocs](https://github.com/peachcloud/peach-devdocs) - Developer documentation for PeachCloud in the form of a Markdown book
**Devops**
- [peach-vps](https://github.com/peachcloud/peach-vps) - Setup scripts and configuration files for deploying a PeachCloud development server
**Image building & device configuration**
- [peach-config](https://github.com/peachcloud/peach-config) - Configuration instructions, files and scripts
- [peach-img-builder](https://github.com/peachcloud/peach-img-builder) - Vmdb2 script for building a Debian disc image for Raspberry Pi with PeachCloud pre-installed
**Microservices**
- [peach-buttons](https://github.com/peachcloud/peach-buttons) - Emit GPIO events using JSON-RPC pubsub over WS
- [peach-oled](https://github.com/peachcloud/peach-oled) - Write and draw to OLED display using JSON-RPC over HTTP
- [peach-menu](https://github.com/peachcloud/peach-menu) - A menu for monitoring and interacting with the PeachCloud device
- [peach-network](https://github.com/peachcloud/peach-network) - Query and configure network interfaces using JSON-RPC over HTTP
- [peach-stats](https://github.com/peachcloud/peach-stats) - Query system statistics using JSON-RPC over HTTP
- [peach-lib](https://github.com/peachcloud/peach-lib) - JSON-RPC client library for the PeachCloud ecosystem
- [peach-monitor](https://github.com/peachcloud/peach-monitor) - Monitor network data usage and set alert flags based on user-defined thresholds
**Diagnostics**
- [peach-probe](https://github.com/peachcloud/peach-probe) - Probe PeachCloud microservices to evaluate their state and ensure correct API responses
**Web interface**
- [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
## 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
- [@ahdinosaur](https://github.com/ahdinosaur): `@6ilZq3kN0F+dXFHAPjAwMm87JEb/VdB+LC9eIMW3sa0=.ed25519`
- 1: [`%bSkZCJBmNYUmECNKYOiWkgEeRxrlo2UghNBzE6Cph94=.sha256`](https://viewer.scuttlebot.io/%25bSkZCJBmNYUmECNKYOiWkgEeRxrlo2UghNBzE6Cph94%3D.sha256)
- 2: [`%2L7gYAh2ih+7eFCrtObPWIUYHuGnJjwj4KCXrCIsWhM=.sha256`](https://viewer.scuttlebot.io/%252L7gYAh2ih%2B7eFCrtObPWIUYHuGnJjwj4KCXrCIsWhM%3D.sha256)
- [@mycognosist](https://github.com/mycognosist): `@HEqy940T6uB+T+d9Jaa58aNfRzLx9eRWqkZljBmnkmk=.ed25519`
- [`%mKUByRp4Gib6fqP1q2/dHg+ueSoR+Sj2Y0D7T0Np0D4=.sha256`](https://viewer.scuttlebot.io/%25mKUByRp4Gib6fqP1q2%2FdHg%2BueSoR%2BSj2Y0D7T0Np0D4%3D.sha256)
## Accounts
- [GitHub](https://github.com/peachcloud)
- [Twitter](https://twitter.com/peachcloudorg)
- [Email](mailto:peachcloudorg@gmail.com)
- [OpenCollective](https://opencollective.com/peachcloud)

4
pdeploy.sh Executable file
View File

@ -0,0 +1,4 @@
#! /bin/bash
cargo build --package peach-web --release
rsync -azvh /home/notplants/computer/projects/peachpub/peach-workspace/target/release/peach-web root@159.89.42.156:/var/www/peachpub_ynh/peach-web
ssh root@159.89.42.156 'systemctl restart peachpub_ynh-peach-web'

View File

@ -59,8 +59,11 @@ pub fn get_peach_config_defaults() -> HashMap<String, String> {
("SSB_ADMIN_IDS", ""), ("SSB_ADMIN_IDS", ""),
("ADMIN_PASSWORD_HASH", "47"), ("ADMIN_PASSWORD_HASH", "47"),
("TEMPORARY_PASSWORD_HASH", ""), ("TEMPORARY_PASSWORD_HASH", ""),
("TILDE_SBOT_DATADIR", "/home/notplants/.local/share/tildefriends/"), ("TILDE_SBOT_DATADIR", "/home/peach/.local/share/tildefriends/"),
("TILDE_SBOT_SERVICE", "tilde-sbot.service"), ("TILDE_SBOT_SERVICE", "tilde-sbot.service"),
("TILDE_BINARY_PATH", "/home/peach/tildefriends.standalone"),
("TILDE_WRAPPER_PATH", "/home/peach/run-tilde-sbot.sh"),
("TILDE_CONFIG_PATH", "/home/peach/.local/share/tildefriends/tilde-sbot.toml"),
("PEACH_CONFIGDIR", "/var/lib/peachcloud"), ("PEACH_CONFIGDIR", "/var/lib/peachcloud"),
("PEACH_HOMEDIR", "/home/peach"), ("PEACH_HOMEDIR", "/home/peach"),
("PEACH_WEBDIR", "/usr/share/peach-web"), ("PEACH_WEBDIR", "/usr/share/peach-web"),
@ -285,8 +288,12 @@ pub fn delete_ssb_admin_id(ssb_id: &str) -> Result<Vec<String>, PeachError> {
// looks up the String value for SSB_ADMIN_IDS and converts it into a Vec<String> // looks up the String value for SSB_ADMIN_IDS and converts it into a Vec<String>
pub fn get_ssb_admin_ids() -> Result<Vec<String>, PeachError> { pub fn get_ssb_admin_ids() -> Result<Vec<String>, PeachError> {
let ssb_admin_ids_str = get_config_value("SSB_ADMIN_IDS")?; let ssb_admin_ids_str = get_config_value("SSB_ADMIN_IDS")?;
let ssb_admin_ids: Vec<String> = serde_json::from_str(&ssb_admin_ids_str)?; if ssb_admin_ids_str.trim().is_empty() {
Ok(ssb_admin_ids) Ok(vec![])
} else {
let ssb_admin_ids: Vec<String> = serde_json::from_str(&ssb_admin_ids_str)?;
Ok(ssb_admin_ids)
}
} }
// takes in a Vec<String> and saves SSB_ADMIN_IDS as a json string representation of this vec // takes in a Vec<String> and saves SSB_ADMIN_IDS as a json string representation of this vec

View File

@ -110,6 +110,9 @@ pub enum PeachError {
/// Represents an Anyhow error with Solar /// Represents an Anyhow error with Solar
SolarClientError(String), SolarClientError(String),
/// Represents an Anyhow error with Solar
TildeClientError(String),
/// Represents an error with encoding or decoding an SsbMessage /// Represents an error with encoding or decoding an SsbMessage
SsbMessageError(String), SsbMessageError(String),
@ -143,6 +146,7 @@ impl std::error::Error for PeachError {
PeachError::Write { ref source, .. } => Some(source), PeachError::Write { ref source, .. } => Some(source),
PeachError::JsonRpcError(_) => None, PeachError::JsonRpcError(_) => None,
PeachError::SolarClientError(_) => None, PeachError::SolarClientError(_) => None,
PeachError::TildeClientError(_) => None,
PeachError::SsbMessageError(_) => None, PeachError::SsbMessageError(_) => None,
} }
} }
@ -203,6 +207,7 @@ impl std::fmt::Display for PeachError {
} }
PeachError::JsonRpcError(ref err) => err.fmt(f), PeachError::JsonRpcError(ref err) => err.fmt(f),
PeachError::SolarClientError(ref err) => err.fmt(f), PeachError::SolarClientError(ref err) => err.fmt(f),
PeachError::TildeClientError(ref err) => err.fmt(f),
PeachError::SsbMessageError(ref err) => err.fmt(f), PeachError::SsbMessageError(ref err) => err.fmt(f),
} }
} }

View File

@ -108,29 +108,20 @@ using this link: http://peach.local/auth/reset",
// 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(_) => (),
Err(e) => return Err(PeachError::Sbot(e)), Err(e) => return Err(e),
} }
} }
Ok(()) Ok(())
} }
async fn publish_private_msg(msg: &str, recipient: &str) -> Result<(), String> { async fn publish_private_msg(msg: &str, recipient: &str) -> Result<(), PeachError> {
// retrieve latest go-sbot configuration parameters
let sbot_config = SbotConfig::read().ok();
let msg = msg.to_string();
let recipient = vec![recipient.to_string()];
// initialise sbot connection with ip:port and shscap from config file // initialise sbot connection with ip:port and shscap from config file
let mut sbot_client = init_sbot(); let mut sbot_client = init_sbot().await?;
debug!("Publishing a Scuttlebutt private message with temporary password"); debug!("Publishing a Scuttlebutt private message with temporary password");
// TODO: implement publish private message in solar, and then implement this match sbot_client.private_message(recipient, msg).await {
Err(format!("Failed to publish private message: \ Ok(_) => Ok(()),
private publishing is not yet implemented in solar_client: \ Err(e) => Err(PeachError::TildeClientError(format!("Failed to publish private message: {}", e))),
the message meant to be sent was: {}", msg)) }
// match sbot_client.publish_private(msg, recipient).await {
// Ok(_) => Ok(()),
// Err(e) => Err(format!("Failed to publish private message: {}", e)),
// }
} }

View File

@ -2,7 +2,7 @@
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 std::os::linux::raw::ino_t; use std::os::linux::raw::ino_t;
use tilde_client::{TildeClient, get_sbot_client}; use tilde_client::{TildeClient};
use log::debug; use log::debug;
use crate::config_manager; use crate::config_manager;
@ -155,21 +155,17 @@ impl SbotStatus {
pub struct Config { pub struct Config {
// TODO: maybe define as a Path type? // TODO: maybe define as a Path type?
/// Directory path for the log and indexes. /// Directory path for the log and indexes.
pub repo: String, pub database_directory: String,
/// Directory path for writing debug output.
pub debugdir: String,
/// Secret-handshake app-key (aka. network key). /// Secret-handshake app-key (aka. network key).
pub shscap: String, pub shscap: String,
/// HMAC hash used to sign messages. /// HMAC hash used to sign messages.
pub hmac: String, pub hmac: String,
/// Replication hops (1: friends, 2: friends of friends). /// Replication hops (1: friends, 2: friends of friends).
pub hops: u8, pub replication_hops: u8,
/// Ip address of pub
pub ip: String,
/// Address to listen on. /// Address to listen on.
pub lis: String, pub ssb_port: String,
/// Address to listen on for WebSocket connections.
pub wslis: String,
/// Address to for metrics and pprof HTTP server.
pub debuglis: String,
/// Enable sending local UDP broadcasts. /// Enable sending local UDP broadcasts.
pub localadv: bool, pub localadv: bool,
/// Enable listening for UDP broadcasts and connecting. /// Enable listening for UDP broadcasts and connecting.
@ -182,48 +178,41 @@ pub struct Config {
pub promisc: bool, pub promisc: bool,
/// Disable the UNIX socket RPC interface. /// Disable the UNIX socket RPC interface.
pub nounixsock: bool, pub nounixsock: bool,
/// Attempt to repair the filesystem before starting.
pub repair: bool,
} }
// TODO: make this real // TODO: make this real
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
#[serde(default)] #[serde(default)]
pub struct SbotConfig { pub struct SbotConfig {
pub repo: String, pub database_directory: String,
pub debugdir: String,
pub shscap: String, pub shscap: String,
pub hmac: String, pub hmac: String,
pub hops: i8, pub replication_hops: i8,
pub lis: String, pub ip: String,
pub wslis: String, pub ssb_port: String,
pub debuglis: String, // TODO: below settings have not been configured with tilde
pub localadv: bool, pub localadv: bool,
pub localdiscov: bool, pub localdiscov: bool,
pub enable_ebt: bool, pub enable_ebt: bool,
pub promisc: bool, pub promisc: bool,
pub nounixsock: bool, pub nounixsock: bool,
pub repair: bool,
} }
/// Default configuration values for solar-sbot. /// Default configuration values for solar-sbot.
impl Default for SbotConfig { impl Default for SbotConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
repo: ".ssb-go".to_string(), database_directory: "~/.local/share/tildefriends".to_string(),
debugdir: "".to_string(),
shscap: "1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=".to_string(), shscap: "1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=".to_string(),
hmac: "".to_string(), hmac: "".to_string(),
hops: 1, replication_hops: 1,
lis: ":8008".to_string(), ip: "".to_string(),
wslis: ":8989".to_string(), ssb_port: "8008".to_string(),
debuglis: "localhost:6078".to_string(),
localadv: false, localadv: false,
localdiscov: false, localdiscov: false,
enable_ebt: false, enable_ebt: false,
promisc: false, promisc: false,
nounixsock: false, nounixsock: false,
repair: false,
} }
} }
} }
@ -233,9 +222,10 @@ impl SbotConfig {
pub fn read() -> Result<Self, PeachError> { pub fn read() -> Result<Self, PeachError> {
// determine path of user's solar-sbot config.toml // determine path of user's solar-sbot config.toml
let config_path = format!( let config_path = format!(
"{}/config.toml", "{}/tilde-sbot.toml",
config_manager::get_config_value("SOLAR_SBOT_DATADIR")? config_manager::get_config_value("TILDE_SBOT_DATADIR")?
); );
println!("TILDE_SBOT_CONFIG_PATH: {}", config_path);
let config_contents = fs::read_to_string(config_path)?; let config_contents = fs::read_to_string(config_path)?;
@ -244,17 +234,17 @@ impl SbotConfig {
Ok(config) Ok(config)
} }
/// Write the given `SbotConfig` to the solar-sbot `config.toml` file. /// Write the given `SbotConfig` to the tilde-sbot `tilde-sbot.toml` file.
pub fn write(config: SbotConfig) -> Result<(), PeachError> { pub fn write(config: SbotConfig) -> Result<(), PeachError> {
let repo_comment = "# For details about solar-sbot configuration, please visit the repo: https://github.com/cryptoscope/ssb\n".to_string(); let repo_comment = "# For details about tilde-sbot configuration, please visit tilde friends documentation\n".to_string();
// 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 solar-sbot config.toml // determine path of user's solar-sbot config.toml
let config_path = format!( let config_path = format!(
"{}/config.toml", "{}/tilde-sbot.toml",
config_manager::get_config_value("SOLAR_SBOT_DATADIR")? config_manager::get_config_value("TILDE_SBOT_DATADIR")?
); );
// open config file for writing // open config file for writing
@ -272,17 +262,20 @@ impl SbotConfig {
/// Initialise an sbot client /// Initialise an sbot client
pub async fn init_sbot() -> Result<TildeClient, PeachError> { pub async fn init_sbot() -> Result<TildeClient, PeachError> {
// read sbot config from config.toml
let sbot_config = SbotConfig::read().ok(); // read sbot config
let sbot_config = match SbotConfig::read() {
Ok(config) => config,
// build default config if an error is returned from the read attempt
Err(_) => SbotConfig::default(),
};
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 let database_path = format!("{}/db.sqlite", config_manager::get_config_value("TILDE_SBOT_DATADIR")?);
let key_path = format!( let sbot_client = TildeClient {
"{}/secret", ssb_port: sbot_config.ssb_port,
config_manager::get_config_value("SOLAR_SBOT_DATADIR")? tilde_binary_path: config_manager::get_config_value("TILDE_BINARY_PATH")?,
); tilde_database_path: database_path,
// TODO: read this from config };
const SERVER_ADDR: &str = "http://127.0.0.1:3030";
let sbot_client = get_sbot_client();
Ok(sbot_client) Ok(sbot_client)
} }

View File

@ -51,3 +51,5 @@ vnstat_parse = "0.1.0"
xdg = "2.2" xdg = "2.2"
jsonrpc_client = { version = "0.7", features = ["macros", "reqwest"] } jsonrpc_client = { version = "0.7", features = ["macros", "reqwest"] }
reqwest = "0.11.24" reqwest = "0.11.24"
urlencoding = "2.1.3"
rpassword = "5.0"

View File

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

1
peach-web/src/cli/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod change_password;

View File

@ -6,6 +6,7 @@ use peach_lib::error::PeachError;
use peach_lib::{serde_json, serde_yaml}; use peach_lib::{serde_json, serde_yaml};
use serde_json::error::Error as JsonError; use serde_json::error::Error as JsonError;
use serde_yaml::Error as YamlError; use serde_yaml::Error as YamlError;
use peach_lib::tilde_client::TildeError;
/// Custom error type encapsulating all possible errors for the web application. /// Custom error type encapsulating all possible errors for the web application.
#[derive(Debug)] #[derive(Debug)]
@ -17,6 +18,8 @@ pub enum PeachWebError {
OsString, OsString,
PeachLib { source: PeachError, msg: String }, PeachLib { source: PeachError, msg: String },
Yaml(YamlError), Yaml(YamlError),
Tilde(TildeError),
InvalidPassword,
NotYetImplemented, NotYetImplemented,
} }
@ -25,11 +28,13 @@ impl std::error::Error for PeachWebError {
match *self { match *self {
PeachWebError::FailedToRegisterDynDomain(_) => None, PeachWebError::FailedToRegisterDynDomain(_) => None,
PeachWebError::HomeDir => None, PeachWebError::HomeDir => None,
PeachWebError::InvalidPassword => None,
PeachWebError::Io(ref source) => Some(source), PeachWebError::Io(ref source) => Some(source),
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::Yaml(ref source) => Some(source), PeachWebError::Yaml(ref source) => Some(source),
PeachWebError::Tilde(ref source) => Some(source),
PeachWebError::NotYetImplemented => None PeachWebError::NotYetImplemented => None
} }
} }
@ -45,6 +50,10 @@ impl std::fmt::Display for PeachWebError {
f, f,
"Filesystem error: failed to determine home directory path" "Filesystem error: failed to determine home directory path"
), ),
PeachWebError::InvalidPassword => write!(
f,
"Failed to change password via CLI"
),
PeachWebError::Io(ref source) => write!(f, "IO error: {}", source), PeachWebError::Io(ref source) => write!(f, "IO error: {}", source),
PeachWebError::Json(ref source) => write!(f, "Serde JSON error: {}", source), PeachWebError::Json(ref source) => write!(f, "Serde JSON error: {}", source),
PeachWebError::OsString => write!( PeachWebError::OsString => write!(
@ -53,6 +62,7 @@ impl std::fmt::Display for PeachWebError {
), ),
PeachWebError::PeachLib { ref source, .. } => write!(f, "{}", source), PeachWebError::PeachLib { ref source, .. } => write!(f, "{}", source),
PeachWebError::Yaml(ref source) => write!(f, "Serde YAML error: {}", source), PeachWebError::Yaml(ref source) => write!(f, "Serde YAML error: {}", source),
PeachWebError::Tilde(ref source) => write!(f, "Tilde error: {}", source),
PeachWebError::NotYetImplemented => write!(f, "Not yet implemented"), PeachWebError::NotYetImplemented => write!(f, "Not yet implemented"),
} }
} }
@ -84,3 +94,9 @@ impl From<YamlError> for PeachWebError {
PeachWebError::Yaml(err) PeachWebError::Yaml(err)
} }
} }
impl From<TildeError> for PeachWebError {
fn from(err: TildeError) -> PeachWebError {
PeachWebError::Tilde(err)
}
}

View File

@ -19,11 +19,9 @@ mod public_router;
mod routes; mod routes;
mod templates; mod templates;
pub mod utils; pub mod utils;
mod cli;
use std::{ use std::{collections::HashMap, env, sync::{Mutex, RwLock}};
collections::HashMap,
sync::{Mutex, RwLock},
};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::info; use log::info;
@ -52,9 +50,7 @@ pub struct SessionData {
} }
/// Launch the peach-web server. /// Launch the peach-web server.
fn main() { fn run_webserver() {
// initialize logger
env_logger::init();
// 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"
@ -121,3 +117,22 @@ fn main() {
}) })
}); });
} }
/// cli entry point
fn main() {
env_logger::init();
let mut args = env::args();
let _program = args.next(); // skip program name
match args.next().as_deref() {
Some("run") => run_webserver(),
Some("change-password") => {
cli::change_password::set_peach_web_password(args.next());
}
_ => {
eprintln!("Usage: peach-web <run|change-password>");
std::process::exit(1);
}
}
}

View File

@ -26,7 +26,7 @@ pub fn handle_route(request: &Request, session_data: &mut Option<SessionData>) -
} }
// set the `.ssb-go` path in order to mount the blob fileserver // set the `.ssb-go` path in order to mount the blob fileserver
let ssb_path = sbot::get_go_ssb_path().expect("define ssb-go dir path"); let ssb_path = sbot::get_tilde_ssb_path().expect("define ssb-go dir path");
let blobstore = format!("{}/blobs/sha256", ssb_path); let blobstore = format!("{}/blobs/sha256", ssb_path);
// blobstore file server // blobstore file server

View File

@ -41,7 +41,7 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
// render flash message if cookies were found in the request // render flash message if cookies were found in the request
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
}; };
@ -102,12 +102,12 @@ pub fn handle_form(request: &Request) -> Response {
) { ) {
Ok(_) => ( Ok(_) => (
// <cookie-name>=<cookie-value> // <cookie-name>=<cookie-value>
"flash_name=success".to_string(), "success".to_string(),
"flash_msg=New password has been saved".to_string(), "New password has been saved".to_string(),
), ),
Err(err) => ( Err(err) => (
"flash_name=error".to_string(), "error".to_string(),
format!("flash_msg=Failed to save new password: {}", err), format!("Failed to save new password: {}", err),
), ),
}; };

View File

@ -38,7 +38,7 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
// render flash message if cookies were found in the request // render flash message if cookies were found in the request
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
}; };

View File

@ -37,7 +37,7 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
// render flash message if cookies were found in the request // render flash message if cookies were found in the request
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
}; };
@ -76,8 +76,8 @@ pub fn handle_form(request: &Request, session_data: &mut Option<SessionData>) ->
debug!("Unsuccessful login attempt"); debug!("Unsuccessful login attempt");
let err_msg = format!("Invalid password: {}", err); let err_msg = format!("Invalid password: {}", err);
let (flash_name, flash_msg) = ( let (flash_name, flash_msg) = (
"flash_name=error".to_string(), "error".to_string(),
format!("flash_msg={}", err_msg), format!("{}", err_msg),
); );
// if unsuccessful login, render /login page again // if unsuccessful login, render /login page again

View File

@ -14,8 +14,8 @@ pub fn deauthenticate(session_data: &mut Option<SessionData>) -> Response {
*session_data = None; *session_data = None;
let (flash_name, flash_msg) = ( let (flash_name, flash_msg) = (
"flash_name=success".to_string(), "success".to_string(),
"flash_msg=Logged out".to_string(), "Logged out".to_string(),
); );
// set the flash cookie headers and redirect to the login page // set the flash cookie headers and redirect to the login page

View File

@ -41,7 +41,7 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
// render flash message if cookies were found in the request // render flash message if cookies were found in the request
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
}; };
@ -100,12 +100,12 @@ pub fn handle_form(request: &Request) -> Response {
&data.new_password2, &data.new_password2,
) { ) {
Ok(_) => ( Ok(_) => (
"flash_name=success".to_string(), "success".to_string(),
"flash_msg=New password has been saved. Return home to login".to_string(), "New password has been saved. Return home to login".to_string(),
), ),
Err(err) => ( Err(err) => (
"flash_name=error".to_string(), "error".to_string(),
format!("flash_msg=Failed to reset password: {}", err), format!("Failed to reset password: {}", err),
), ),
}; };

View File

@ -21,8 +21,8 @@ pub fn handle_form() -> Response {
Ok(_) => { Ok(_) => {
debug!("Sent temporary password to device admin(s)"); debug!("Sent temporary password to device admin(s)");
( (
"flash_name=success".to_string(), "success".to_string(),
"flash_msg=A temporary password has been sent to the admin(s) of this device" "A temporary password has been sent to the admin(s) of this device"
.to_string(), .to_string(),
) )
} }

View File

@ -29,67 +29,67 @@ 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 status go-sbot.service" } code { "systemctl status tilde-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."
} }
} }
(PreEscaped("<!-- BUG REPORTS -->")) // (PreEscaped("<!-- BUG REPORTS -->"))
details { // details {
summary class="card-text link" { "Submit a bug report" } // summary class="card-text link" { "Submit a bug report" }
p class="card-text" style="margin-top: 1rem; margin-bottom: 1rem;" { // p class="card-text" style="margin-top: 1rem; margin-bottom: 1rem;" {
"Bug reports can be submitted by " // "Bug reports can be submitted by "
strong { // strong {
a href="https://git.coopcloud.tech/PeachCloud/peach-workspace/issues/new?template=BUG_TEMPLATE.md" class="link font-gray" { // a href="https://git.coopcloud.tech/PeachCloud/peach-workspace/issues/new?template=BUG_TEMPLATE.md" class="link font-gray" {
"filing an issue" // "filing an issue"
} // }
} // }
" on the peach-workspace git repo. Before filing a report, first check to see if an issue already exists for the bug you've encountered. If not, you're invited to submit a new report; the template will guide you through several questions." // " on the peach-workspace git repo. Before filing a report, first check to see if an issue already exists for the bug you've encountered. If not, you're invited to submit a new report; the template will guide you through several questions."
} // }
} // }
(PreEscaped("<!-- REQUEST SUPPORT -->")) // (PreEscaped("<!-- REQUEST SUPPORT -->"))
details { // details {
summary class="card-text link" { "Share feedback & request support" } // summary class="card-text link" { "Share feedback & request support" }
p class="card-text" style="margin-top: 1rem; margin-bottom: 1rem;" { // p class="card-text" style="margin-top: 1rem; margin-bottom: 1rem;" {
"You're invited to share your thoughts and experiences of PeachCloud in the #peachcloud channel on Scuttlebutt. The channel is also a good place to ask for help." // "You're invited to share your thoughts and experiences of PeachCloud in the #peachcloud channel on Scuttlebutt. The channel is also a good place to ask for help."
} // }
p class="card-text" style="margin-top: 1rem; margin-bottom: 1rem;" { // p class="card-text" style="margin-top: 1rem; margin-bottom: 1rem;" {
"Alternatively, we have a " // "Alternatively, we have a "
strong { // strong {
a href="https://matrix.to/#/#peachcloud:matrix.org" class="link font-gray" { // a href="https://matrix.to/#/#peachcloud:matrix.org" class="link font-gray" {
"Matrix channel" // "Matrix channel"
} // }
} // }
" for discussion about PeachCloud and you can also reach out to @glyph " // " for discussion about PeachCloud and you can also reach out to @glyph "
strong { // strong {
a href="mailto:glyph@mycelial.technology" class="link font-gray" { // a href="mailto:glyph@mycelial.technology" class="link font-gray" {
"via email" // "via email"
} // }
} // }
"." // "."
} // }
} // }
(PreEscaped("<!-- CONTRIBUTE -->")) // (PreEscaped("<!-- CONTRIBUTE -->"))
details { // details {
summary class="card-text link" { "Contribute to PeachCloud" } // summary class="card-text link" { "Contribute to PeachCloud" }
p class="card-text" style="margin-top: 1rem; margin-bottom: 1rem;" { // p class="card-text" style="margin-top: 1rem; margin-bottom: 1rem;" {
"PeachCloud is free, open-source software and relies on donations and grants to fund develop. Donations can be made on our " // "PeachCloud is free, open-source software and relies on donations and grants to fund develop. Donations can be made on our "
strong { // strong {
a href="https://opencollective.com/peachcloud" class="link font-gray" { // a href="https://opencollective.com/peachcloud" class="link font-gray" {
"Open Collective" // "Open Collective"
} // }
} // }
" page." // " page."
} // }
p class="card-text" style="margin-top: 1rem; margin-bottom: 1rem;" { // p class="card-text" style="margin-top: 1rem; margin-bottom: 1rem;" {
"Programmers, designers, artists and writers are also welcome to contribute to the project. Please visit the " // "Programmers, designers, artists and writers are also welcome to contribute to the project. Please visit the "
strong { // strong {
a href="https://git.coopcloud.tech/PeachCloud/peach-workspace" class="link font-gray" { // a href="https://git.coopcloud.tech/PeachCloud/peach-workspace" class="link font-gray" {
"main PeachCloud git repository" // "main PeachCloud git repository"
} // }
} // }
" to find out more details or contact the team via Scuttlebutt, Matrix or email." // " to find out more details or contact the team via Scuttlebutt, Matrix or email."
} // }
} // }
} }
} }
}; };

View File

@ -21,17 +21,17 @@ pub fn handle_form(request: &Request) -> Response {
Ok(status) if status.state == Some("active".to_string()) => { Ok(status) if status.state == Some("active".to_string()) => {
match sbot::block_peer(&data.public_key) { match sbot::block_peer(&data.public_key) {
Ok(success_msg) => ( Ok(success_msg) => (
"flash_name=success".to_string(), "success".to_string(),
format!("flash_msg={}", success_msg), format!("{}", success_msg),
), ),
Err(error_msg) => ( Err(error_msg) => (
"flash_name=error".to_string(), "error".to_string(),
format!("flash_msg={}", error_msg), format!("{}", error_msg),
), ),
} }
} }
_ => ( _ => (
"flash_name=warning".to_string(), "warning".to_string(),
"Social interactions are unavailable.".to_string(), "Social interactions are unavailable.".to_string(),
), ),
}; };

View File

@ -21,17 +21,17 @@ pub fn handle_form(request: &Request) -> Response {
Ok(status) if status.state == Some("active".to_string()) => { Ok(status) if status.state == Some("active".to_string()) => {
match sbot::follow_peer(&data.public_key) { match sbot::follow_peer(&data.public_key) {
Ok(success_msg) => ( Ok(success_msg) => (
"flash_name=success".to_string(), "success".to_string(),
format!("flash_msg={}", success_msg), format!("{}", success_msg),
), ),
Err(error_msg) => ( Err(error_msg) => (
"flash_name=error".to_string(), "error".to_string(),
format!("flash_msg={}", error_msg), format!("{}", error_msg),
), ),
} }
} }
_ => ( _ => (
"flash_name=warning".to_string(), "warning".to_string(),
"Social interactions are unavailable.".to_string(), "Social interactions are unavailable.".to_string(),
), ),
}; };

View File

@ -1,5 +1,6 @@
use maud::{html, Markup, PreEscaped}; use maud::{html, Markup, PreEscaped};
use peach_lib::sbot::SbotStatus; use peach_lib::sbot::SbotStatus;
use peach_lib::config_manager;
use rouille::{post_input, try_or_400, Request, Response}; use rouille::{post_input, try_or_400, Request, Response};
use crate::{ use crate::{
@ -14,9 +15,9 @@ use crate::{
/// Render the invite form template. /// Render the invite form template.
fn invite_form_template( fn invite_form_template(
flash_name: Option<&str>, flash_name: Option<String>,
flash_msg: Option<&str>, flash_msg: Option<String>,
invite_code: Option<&str>, invite_code: Option<String>,
) -> Markup { ) -> Markup {
html! { html! {
(PreEscaped("<!-- SCUTTLEBUTT INVITE FORM -->")) (PreEscaped("<!-- SCUTTLEBUTT INVITE FORM -->"))
@ -40,7 +41,7 @@ fn invite_form_template(
// avoid displaying the invite code-containing flash msg // avoid displaying the invite code-containing flash msg
@if name != "code" { @if name != "code" {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
} }
@ -51,10 +52,11 @@ fn invite_form_template(
pub fn build_template(request: &Request) -> PreEscaped<String> { pub fn build_template(request: &Request) -> PreEscaped<String> {
// check for flash cookies; will be (None, None) if no flash cookies are found // check for flash cookies; will be (None, None) if no flash cookies are found
let (flash_name, flash_msg) = request.retrieve_flash(); let (flash_name, flash_msg) = request.retrieve_flash();
println!("build template invites!");
// if flash_name is "code" then flash_msg will be an invite code // if flash_name is "code" then flash_msg will be an invite code
let invite_code = if flash_name == Some("code") { let invite_code = if flash_name == Some("code".to_string()) {
flash_msg flash_msg.clone()
} else { } else {
None None
}; };
@ -89,9 +91,11 @@ pub fn handle_form(request: &Request) -> Response {
})); }));
let (flash_name, flash_msg) = match sbot::create_invite(data.uses) { let (flash_name, flash_msg) = match sbot::create_invite(data.uses) {
Ok(code) => ("flash_name=code".to_string(), format!("flash_msg={}", code)), Ok(code) => ("code".to_string(), format!("{}", code)),
Err(e) => ("flash_name=error".to_string(), format!("flash_msg={}", e)), Err(e) => ("error".to_string(), format!("{}", e)),
}; };
println!("invite flash name: {}, {}", flash_name, flash_msg);
Response::redirect_303("/scuttlebutt/invites").add_flash(flash_name, flash_msg) Response::redirect_303("/scuttlebutt/invites").add_flash(flash_name, flash_msg)
} }

View File

@ -17,10 +17,10 @@ pub fn build_template() -> PreEscaped<String> {
div class="card-container" { div class="card-container" {
(PreEscaped("<!-- BUTTONS -->")) (PreEscaped("<!-- BUTTONS -->"))
div id="buttons" { div id="buttons" {
a id="search" class="button button-primary center" href="/scuttlebutt/search" title="Search for a peer" { "Search" } // a id="search" class="button button-primary center" href="/scuttlebutt/search" title="Search for a peer" { "Search" }
a id="friends" class="button button-primary center" href="/scuttlebutt/friends" title="List friends" { "Friends" } // a id="friends" class="button button-primary center" href="/scuttlebutt/friends" title="List friends" { "Friends" }
a id="follows" class="button button-primary center" href="/scuttlebutt/follows" title="List follows" { "Follows" } // a id="follows" class="button button-primary center" href="/scuttlebutt/follows" title="List follows" { "Follows" }
a id="blocks" class="button button-primary center" href="/scuttlebutt/blocks" title="List blocks" { "Blocks" } // a id="blocks" class="button button-primary center" href="/scuttlebutt/blocks" title="List blocks" { "Blocks" }
a id="invites" class="button button-primary center" href="/scuttlebutt/invites" title="Create invites" { "Invites" } a id="invites" class="button button-primary center" href="/scuttlebutt/invites" title="Create invites" { "Invites" }
} }
} }

View File

@ -74,7 +74,7 @@ pub fn build_template(request: &Request, ssb_id: Option<String>) -> PreEscaped<S
// render flash message if cookies were found in the request // render flash message if cookies were found in the request
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
} }
@ -112,17 +112,17 @@ pub fn handle_form(request: &Request) -> Response {
Ok(status) if status.state == Some("active".to_string()) => { Ok(status) if status.state == Some("active".to_string()) => {
match sbot::publish_private_msg(data.text, recipients) { match sbot::publish_private_msg(data.text, recipients) {
Ok(success_msg) => ( Ok(success_msg) => (
"flash_name=success".to_string(), "success".to_string(),
format!("flash_msg={}", success_msg), format!("{}", success_msg),
), ),
Err(error_msg) => ( Err(error_msg) => (
"flash_name=error".to_string(), "error".to_string(),
format!("flash_msg={}", error_msg), format!("{}", error_msg),
), ),
} }
} }
_ => ( _ => (
"flash_name=warning".to_string(), "warning".to_string(),
"Private messaging is unavailable.".to_string(), "Private messaging is unavailable.".to_string(),
), ),
}; };

View File

@ -160,7 +160,7 @@ pub fn build_template(request: &Request, ssb_id: Option<String>) -> PreEscaped<S
// render flash message if cookies were found in the request // render flash message if cookies were found in the request
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
} }

View File

@ -81,7 +81,7 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
// render flash message if cookies were found in the request // render flash message if cookies were found in the request
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
} }
@ -139,17 +139,17 @@ pub fn handle_form(request: &Request) -> Response {
data.image, data.image,
) { ) {
Ok(success_msg) => ( Ok(success_msg) => (
"flash_name=success".to_string(), "success".to_string(),
format!("flash_msg={}", success_msg), format!("{}", success_msg),
), ),
Err(error_msg) => ( Err(error_msg) => (
"flash_name=error".to_string(), "error".to_string(),
format!("flash_msg={}", error_msg), format!("{}", error_msg),
), ),
} }
} }
_ => ( _ => (
"flash_name=warning".to_string(), "warning".to_string(),
"Profile is unavailable.".to_string(), "Profile is unavailable.".to_string(),
), ),
}; };
@ -165,7 +165,7 @@ pub fn handle_form(request: &Request) -> Response {
} }
Err(err) => { Err(err) => {
let (flash_name, flash_msg) = let (flash_name, flash_msg) =
("flash_name=error".to_string(), format!("flash_msg={}", err)); ("error".to_string(), format!("{}", err));
Response::redirect_303("/scuttlebutt/search").add_flash(flash_name, flash_msg) Response::redirect_303("/scuttlebutt/search").add_flash(flash_name, flash_msg)
} }
} }

View File

@ -22,17 +22,17 @@ pub fn handle_form(request: &Request) -> Response {
Ok(status) if status.state == Some("active".to_string()) => { Ok(status) if status.state == Some("active".to_string()) => {
match sbot::publish_public_post(data.text) { match sbot::publish_public_post(data.text) {
Ok(success_msg) => ( Ok(success_msg) => (
"flash_name=success".to_string(), "success".to_string(),
format!("flash_msg={}", success_msg), format!("{}", success_msg),
), ),
Err(error_msg) => ( Err(error_msg) => (
"flash_name=error".to_string(), "error".to_string(),
format!("flash_msg={}", error_msg), format!("{}", error_msg),
), ),
} }
} }
_ => ( _ => (
"flash_name=warning".to_string(), "warning".to_string(),
"Public posting is unavailable.".to_string(), "Public posting is unavailable.".to_string(),
), ),
}; };

View File

@ -29,7 +29,7 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
// render flash message if cookies were found in the request // render flash message if cookies were found in the request
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
} }
@ -62,7 +62,7 @@ pub fn handle_form(request: &Request) -> Response {
} }
Err(err) => { Err(err) => {
let (flash_name, flash_msg) = let (flash_name, flash_msg) =
("flash_name=error".to_string(), format!("flash_msg={}", err)); ("error".to_string(), format!("{}", err));
Response::redirect_303("/scuttlebutt/search").add_flash(flash_name, flash_msg) Response::redirect_303("/scuttlebutt/search").add_flash(flash_name, flash_msg)
} }
} }

View File

@ -21,17 +21,17 @@ pub fn handle_form(request: &Request) -> Response {
Ok(status) if status.state == Some("active".to_string()) => { Ok(status) if status.state == Some("active".to_string()) => {
match sbot::unblock_peer(&data.public_key) { match sbot::unblock_peer(&data.public_key) {
Ok(success_msg) => ( Ok(success_msg) => (
"flash_name=success".to_string(), "success".to_string(),
format!("flash_msg={}", success_msg), format!("{}", success_msg),
), ),
Err(error_msg) => ( Err(error_msg) => (
"flash_name=error".to_string(), "error".to_string(),
format!("flash_msg={}", error_msg), format!("{}", error_msg),
), ),
} }
} }
_ => ( _ => (
"flash_name=warning".to_string(), "warning".to_string(),
"Social interactions are unavailable.".to_string(), "Social interactions are unavailable.".to_string(),
), ),
}; };

View File

@ -21,17 +21,17 @@ pub fn handle_form(request: &Request) -> Response {
Ok(status) if status.state == Some("active".to_string()) => { Ok(status) if status.state == Some("active".to_string()) => {
match sbot::unfollow_peer(&data.public_key) { match sbot::unfollow_peer(&data.public_key) {
Ok(success_msg) => ( Ok(success_msg) => (
"flash_name=success".to_string(), "success".to_string(),
format!("flash_msg={}", success_msg), format!("{}", success_msg),
), ),
Err(error_msg) => ( Err(error_msg) => (
"flash_name=error".to_string(), "error".to_string(),
format!("flash_msg={}", error_msg), format!("{}", error_msg),
), ),
} }
} }
_ => ( _ => (
"flash_name=warning".to_string(), "warning".to_string(),
"Social interactions are unavailable.".to_string(), "Social interactions are unavailable.".to_string(),
), ),
}; };

View File

@ -21,12 +21,12 @@ pub fn handle_form(request: &Request) -> Response {
// save submitted admin id to file // save submitted admin id to file
let (flash_name, flash_msg) = match config_manager::add_ssb_admin_id(&data.ssb_id) { let (flash_name, flash_msg) = match config_manager::add_ssb_admin_id(&data.ssb_id) {
Ok(_) => ( Ok(_) => (
"flash_name=success".to_string(), "success".to_string(),
"flash_msg=Added SSB administrator".to_string(), "Added SSB administrator".to_string(),
), ),
Err(err) => ( Err(err) => (
"flash_name=error".to_string(), "error".to_string(),
format!("flash_msg=Failed to add new administrator: {}", err), format!("Failed to add new administrator: {}", err),
), ),
}; };

View File

@ -20,8 +20,8 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
// currently produces an error because we end up with Some(String) // currently produces an error because we end up with Some(String)
// instead of Some(str) // instead of Some(str)
Err(_err) => { Err(_err) => {
flash_name = Some("flash_name=error"); flash_name = Some("error".to_string());
flash_msg = Some("flash_msg=Failed to read PeachCloud configuration file"); flash_msg = Some("Failed to read PeachCloud configuration file".to_string());
None None
} }
}; };
@ -58,7 +58,7 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
// render flash message if cookies were found in the request // render flash message if cookies were found in the request
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
}; };

View File

@ -21,12 +21,12 @@ pub fn handle_form(request: &Request) -> Response {
let (flash_name, flash_msg) = match config_manager::delete_ssb_admin_id(&data.ssb_id) { let (flash_name, flash_msg) = match config_manager::delete_ssb_admin_id(&data.ssb_id) {
Ok(_) => ( Ok(_) => (
// <cookie-name>=<cookie-value> // <cookie-name>=<cookie-value>
"flash_name=success".to_string(), "success".to_string(),
"flash_msg=Removed SSB administrator".to_string(), "Removed SSB administrator".to_string(),
), ),
Err(err) => ( Err(err) => (
"flash_name=error".to_string(), "error".to_string(),
format!("flash_msg=Failed to remove administrator: {}", err), format!("Failed to remove administrator: {}", err),
), ),
}; };

View File

@ -51,7 +51,7 @@ pub fn build_template(request: &Request, selected_ap: Option<String>) -> PreEsca
} }
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
}; };
@ -91,7 +91,7 @@ pub fn handle_form(request: &Request) -> Response {
), ),
}; };
let (flash_name, flash_msg) = (format!("flash_name={}", name), format!("flash_msg={}", msg)); let (flash_name, flash_msg) = (format!("{}", name), format!("{}", msg));
Response::redirect_303("/settings/network/wifi/add").add_flash(flash_name, flash_msg) Response::redirect_303("/settings/network/wifi/add").add_flash(flash_name, flash_msg)
} }

View File

@ -180,7 +180,7 @@ pub fn build_template(request: &Request, selected_ap: String) -> PreEscaped<Stri
} }
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
}; };

View File

@ -111,7 +111,7 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
(render_save_button()) (render_save_button())
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
} }
@ -195,7 +195,7 @@ pub fn handle_form(request: &Request) -> Response {
), ),
}; };
let (flash_name, flash_msg) = (format!("flash_name={}", name), format!("flash_msg={}", msg)); let (flash_name, flash_msg) = (format!("{}", name), format!("{}", msg));
Response::redirect_303("/settings/network/dns").add_flash(flash_name, flash_msg) Response::redirect_303("/settings/network/dns").add_flash(flash_name, flash_msg)
} }

View File

@ -158,7 +158,7 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
} }
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
}; };
} }

View File

@ -52,7 +52,7 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
// render flash message if cookies were found in the request // render flash message if cookies were found in the request
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
}; };

View File

@ -52,7 +52,7 @@ pub fn build_template(request: &Request, selected_ap: Option<String>) -> PreEsca
// render flash message if cookies were found in the request // render flash message if cookies were found in the request
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
}; };
@ -99,7 +99,7 @@ pub fn handle_form(request: &Request) -> Response {
), ),
}; };
let (flash_name, flash_msg) = (format!("flash_name={}", name), format!("flash_msg={}", msg)); let (flash_name, flash_msg) = (format!("{}", name), format!("{}", msg));
Response::redirect_303("/settings/network/wifi/modify").add_flash(flash_name, flash_msg) Response::redirect_303("/settings/network/wifi/modify").add_flash(flash_name, flash_msg)
} }

View File

@ -23,7 +23,7 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
} }
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
} }

View File

@ -30,7 +30,7 @@ pub fn handle_reboot() -> Response {
), ),
}; };
let (flash_name, flash_msg) = (format!("flash_name={}", name), format!("flash_msg={}", msg)); let (flash_name, flash_msg) = (format!("{}", name), format!("{}", msg));
Response::redirect_303("/power").add_flash(flash_name, flash_msg) Response::redirect_303("/power").add_flash(flash_name, flash_msg)
} }

View File

@ -29,7 +29,7 @@ pub fn handle_shutdown() -> Response {
), ),
}; };
let (flash_name, flash_msg) = (format!("flash_name={}", name), format!("flash_msg={}", msg)); let (flash_name, flash_msg) = (format!("{}", name), format!("{}", msg));
Response::redirect_303("/power").add_flash(flash_name, flash_msg) Response::redirect_303("/power").add_flash(flash_name, flash_msg)
} }

View File

@ -33,16 +33,8 @@ fn read_status_and_config() -> (String, SbotConfig, String, String) {
Err(_) => SbotConfig::default(), Err(_) => SbotConfig::default(),
}; };
// split the listen address into ip and port let ip = sbot_config.ip.clone();
let (ip, port) = match sbot_config.lis.find(':') { let port = sbot_config.ssb_port.clone();
Some(index) => {
let (ip, port) = sbot_config.lis.split_at(index);
// remove the : from the port
(ip.to_string(), port.replace(':', ""))
}
// if no ':' separator is found, assume an ip has been configured (without port)
None => (sbot_config.lis.to_string(), String::new()),
};
(run_on_startup, sbot_config, ip, port) (run_on_startup, sbot_config, ip, port)
} }
@ -52,7 +44,7 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
// check for flash cookies; will be (None, None) if no flash cookies are found // check for flash cookies; will be (None, None) if no flash cookies are found
let (flash_name, flash_msg) = request.retrieve_flash(); let (flash_name, flash_msg) = request.retrieve_flash();
let (run_on_startup, sbot_config, ip, port) = read_status_and_config(); let (run_on_startup, sbot_config, ip, ssb_port) = read_status_and_config();
let menu_template = html! { let menu_template = html! {
(PreEscaped("<!-- SBOT CONFIGURATION FORM -->")) (PreEscaped("<!-- SBOT CONFIGURATION FORM -->"))
@ -62,42 +54,42 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
label for="hops" class="label-small font-gray" { "HOPS" } label for="hops" class="label-small font-gray" { "HOPS" }
div id="hops" style="display: flex; justify-content: space-evenly;" { div id="hops" style="display: flex; justify-content: space-evenly;" {
div { div {
@if sbot_config.hops == 0 { @if sbot_config.replication_hops == 0 {
input type="radio" id="hops_0" name="hops" value="0" checked; input type="radio" id="hops_0" name="replication_hops" value="0" checked;
} @else { } @else {
input type="radio" id="hops_0" name="hops" value="0"; input type="radio" id="hops_0" name="replication_hops" value="0";
} }
label class="font-normal" for="hops_0" { "0" } label class="font-normal" for="hops_0" { "0" }
} }
div { div {
@if sbot_config.hops == 1 { @if sbot_config.replication_hops == 1 {
input type="radio" id="hops_1" name="hops" value="1" checked; input type="radio" id="hops_1" name="replication_hops" value="1" checked;
} @else { } @else {
input type="radio" id="hops_1" name="hops" value="1"; input type="radio" id="hops_1" name="replication_hops" value="1";
} }
label class="font-normal" for="hops_1" { "1" } label class="font-normal" for="hops_1" { "1" }
} }
div { div {
@if sbot_config.hops == 2 { @if sbot_config.replication_hops == 2 {
input type="radio" id="hops_2" name="hops" value="2" checked; input type="radio" id="hops_2" name="replication_hops" value="2" checked;
} @else { } @else {
input type="radio" id="hops_2" name="hops" value="2"; input type="radio" id="hops_2" name="replication_hops" value="2";
} }
label class="font-normal" for="hops_2" { "2" } label class="font-normal" for="hops_2" { "2" }
} }
div { div {
@if sbot_config.hops == 3 { @if sbot_config.replication_hops == 3 {
input type="radio" id="hops_3" name="hops" value="3" checked; input type="radio" id="hops_3" name="replication_hops" value="3" checked;
} @else { } @else {
input type="radio" id="hops_3" name="hops" value="3"; input type="radio" id="hops_3" name="replication_hops" value="3";
} }
label class="font-normal" for="hops_3" { "3" } label class="font-normal" for="hops_3" { "3" }
} }
div { div {
@if sbot_config.hops == 4 { @if sbot_config.replication_hops == 4 {
input type="radio" id="hops_4" name="hops" value="4" checked; input type="radio" id="hops_4" name="replication_hops" value="4" checked;
} @else { } @else {
input type="radio" id="hops_4" name="hops" value="4"; input type="radio" id="hops_4" name="replication_hops" value="4";
} }
label class="font-normal" for="hops_4" { "4" } label class="font-normal" for="hops_4" { "4" }
} }
@ -106,11 +98,11 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
div class="center" style="display: flex; justify-content: space-between;" { div class="center" style="display: flex; justify-content: space-between;" {
div style="display: flex; flex-direction: column; width: 60%; margin-bottom: 2rem;" title="IP address on which the sbot runs" { div style="display: flex; flex-direction: column; width: 60%; margin-bottom: 2rem;" title="IP address on which the sbot runs" {
label for="ip" class="label-small font-gray" { "IP ADDRESS" } label for="ip" class="label-small font-gray" { "IP ADDRESS" }
input type="text" id="ip" name="lis_ip" value=(ip); input type="text" id="ip" name="ip" value=(ip);
} }
div style="display: flex; flex-direction: column; width: 20%; margin-bottom: 2rem;" title="Port on which the sbot runs" { div style="display: flex; flex-direction: column; width: 20%; margin-bottom: 2rem;" title="Port on which the sbot runs" {
label for="port" class="label-small font-gray" { "PORT" } label for="port" class="label-small font-gray" { "PORT" }
input type="text" id="port" name="lis_port" value=(port); input type="text" id="port" name="ssb_port" value=(ssb_port);
} }
} }
div class="center" style="display: flex; flex-direction: column; margin-bottom: 2rem;" title="Network key (aka 'caps key') to define the Scuttleverse in which the sbot operates in" { div class="center" style="display: flex; flex-direction: column; margin-bottom: 2rem;" title="Network key (aka 'caps key') to define the Scuttleverse in which the sbot operates in" {
@ -119,34 +111,35 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
} }
div class="center" style="display: flex; flex-direction: column; margin-bottom: 2rem;" title="Directory in which the sbot database is saved" { div class="center" style="display: flex; flex-direction: column; margin-bottom: 2rem;" title="Directory in which the sbot database is saved" {
label for="database_dir" class="label-small font-gray" { "DATABASE DIRECTORY" } label for="database_dir" class="label-small font-gray" { "DATABASE DIRECTORY" }
input type="text" id="database_dir" name="repo" value=(sbot_config.repo); input type="text" id="database_dir" name="database_directory" value=(sbot_config.database_directory);
} }
// TODO: re-add these checkboxes, if tilde adds them
div class="center" { div class="center" {
@if sbot_config.enable_ebt { // @if sbot_config.enable_ebt {
input type="checkbox" id="ebtReplication" style="margin-bottom: 1rem;" name="enable_ebt" checked; // input type="checkbox" id="ebtReplication" style="margin-bottom: 1rem;" name="enable_ebt" checked;
} @else { // } @else {
input type="checkbox" id="ebtReplication" style="margin-bottom: 1rem;" name="enable_ebt"; // 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" { // label class="font-normal" for="ebtReplication" title="Enable Epidemic Broadcast Tree (EBT) replication instead of legacy replication" {
"Enable EBT Replication" // "Enable EBT Replication"
} // }
br; // 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 {
input type="checkbox" id="lanBroadcast" style="margin-bottom: 1rem;" name="localadv"; // input type="checkbox" id="lanBroadcast" style="margin-bottom: 1rem;" name="localadv";
} // }
label class="font-normal" for="lanBroadcast" title="Broadcast the IP and port of this sbot instance so that local peers can discovery it and attempt to connect" { // label class="font-normal" for="lanBroadcast" title="Broadcast the IP and port of this sbot instance so that local peers can discovery it and attempt to connect" {
"Enable LAN Broadcasting" // "Enable LAN Broadcasting"
} // }
br; // br;
@if sbot_config.localdiscov { // @if sbot_config.localdiscov {
input type="checkbox" id="lanDiscovery" style="margin-bottom: 1rem;" name="localdiscov" checked; // input type="checkbox" id="lanDiscovery" style="margin-bottom: 1rem;" name="localdiscov" checked;
} @else { // } @else {
input type="checkbox" id="lanDiscovery" style="margin-bottom: 1rem;" name="localdiscov"; // input type="checkbox" id="lanDiscovery" style="margin-bottom: 1rem;" name="localdiscov";
} // }
label class="font-normal" for="lanDiscovery" title="Listen for the presence of local peers and attempt to connect if found" { "Enable LAN Discovery" } // label class="font-normal" for="lanDiscovery" title="Listen for the presence of local peers and attempt to connect if found" { "Enable LAN Discovery" }
br; // br;
@if run_on_startup == "enabled" { @if run_on_startup == "enabled" {
input type="checkbox" id="startup" style="margin-bottom: 1rem;" name="startup" checked; input type="checkbox" id="startup" style="margin-bottom: 1rem;" name="startup" checked;
} @else { } @else {
@ -154,18 +147,9 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
} }
label class="font-normal" for="startup" title="Run the pub automatically on system startup" { "Run pub when computer starts" } label class="font-normal" for="startup" title="Run the pub automatically on system startup" { "Run pub when computer starts" }
br; br;
@if sbot_config.repair {
input type="checkbox" id="repair" name="repair" checked;
} @else {
input type="checkbox" id="repair" name="repair";
}
label class="font-normal" for="repair" title="Attempt to repair the filesystem when starting the pub" { "Attempt filesystem repair when pub starts" }
} }
(PreEscaped("<!-- hidden input elements for all other config variables -->")) (PreEscaped("<!-- hidden input elements for all other config variables -->"))
input type="hidden" id="debugdir" name="debugdir" value=(sbot_config.debugdir);
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="debuglis" name="debuglis" value=(sbot_config.debuglis);
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 -->"))
@ -176,7 +160,7 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
// render flash message if cookies were found in the request // render flash message if cookies were found in the request
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
}; };
@ -196,61 +180,56 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
templates::base::build_template(body, theme) templates::base::build_template(body, theme)
} }
// use std::io::Read;
/// Parse the sbot configuration values and write to file. /// Parse the sbot configuration values and write to file.
pub fn handle_form(request: &Request, restart: bool) -> Response { pub fn handle_form(request: &Request, restart: bool) -> Response {
// query the request body for form data // query the request body for form data
// return a 400 error if the admin_id field is missing // return a 400 error if the admin_id field is missing
let data = try_or_400!(post_input!(request, { let data = try_or_400!(post_input!(request, {
repo: String, database_directory: String,
debugdir: String,
shscap: String, shscap: String,
hmac: String, hmac: String,
hops: i8, replication_hops: i8,
lis_ip: String, ip: String,
lis_port: String, ssb_port: String,
wslis: String,
debuglis: String,
localadv: bool, localadv: bool,
localdiscov: bool, localdiscov: bool,
enable_ebt: bool, enable_ebt: bool,
promisc: bool, promisc: bool,
nounixsock: bool, nounixsock: bool,
startup: bool, startup: bool,
repair: bool,
})); }));
// concat the ip and port for listen address // concat the ip and port for listen address
let lis = format!("{}:{}", data.lis_ip, data.lis_port); let lis = format!("{}:{}", data.ip, data.ssb_port);
// instantiate `SbotConfig` from form data // instantiate `SbotConfig` from form data
let config = SbotConfig { let config = SbotConfig {
lis, ip: data.ip,
hops: data.hops, ssb_port: data.ssb_port,
repo: data.repo, replication_hops: data.replication_hops,
debugdir: data.debugdir, database_directory: data.database_directory,
shscap: data.shscap, shscap: data.shscap,
localadv: data.localadv, localadv: data.localadv,
localdiscov: data.localdiscov, localdiscov: data.localdiscov,
hmac: data.hmac, hmac: data.hmac,
wslis: data.wslis,
debuglis: data.debuglis,
enable_ebt: data.enable_ebt, enable_ebt: data.enable_ebt,
promisc: data.promisc, promisc: data.promisc,
nounixsock: data.nounixsock, nounixsock: data.nounixsock,
repair: data.repair,
}; };
match data.startup { match data.startup {
true => { true => {
debug!("Enabling go-sbot.service"); debug!("Enabling tilde-sbot.service");
if let Err(e) = sbot::systemctl_sbot_cmd("enable") { if let Err(e) = sbot::systemctl_sbot_cmd("enable") {
warn!("Failed to enable go-sbot.service: {}", e) warn!("Failed to enable tilde-sbot.service: {}", e)
} }
} }
false => { false => {
debug!("Disabling go-sbot.service"); debug!("Disabling tilde-sbot.service");
if let Err(e) = sbot::systemctl_sbot_cmd("disable") { if let Err(e) = sbot::systemctl_sbot_cmd("disable") {
warn!("Failed to disable go-sbot.service: {}", e) warn!("Failed to disable tilde-sbot.service: {}", e)
} }
} }
}; };
@ -272,7 +251,7 @@ pub fn handle_form(request: &Request, restart: bool) -> Response {
), ),
}; };
let (flash_name, flash_msg) = (format!("flash_name={}", name), format!("flash_msg={}", msg)); let (flash_name, flash_msg) = (format!("{}", name), format!("{}", msg));
Response::redirect_303("/settings/scuttlebutt/configure").add_flash(flash_name, flash_msg) Response::redirect_303("/settings/scuttlebutt/configure").add_flash(flash_name, flash_msg)
} }

View File

@ -15,7 +15,7 @@ pub fn write_config() -> Response {
), ),
}; };
let (flash_name, flash_msg) = (format!("flash_name={}", name), format!("flash_msg={}", msg)); let (flash_name, flash_msg) = (format!("{}", name), format!("{}", msg));
Response::redirect_303("/settings/scuttlebutt/configure").add_flash(flash_name, flash_msg) Response::redirect_303("/settings/scuttlebutt/configure").add_flash(flash_name, flash_msg)
} }

View File

@ -48,7 +48,7 @@ pub fn build_template(request: &Request) -> PreEscaped<String> {
// render flash message if cookies were found in the request // render flash message if cookies were found in the request
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) { @if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
(PreEscaped("<!-- FLASH MESSAGE -->")) (PreEscaped("<!-- FLASH MESSAGE -->"))
(templates::flash::build_template(name, msg)) (templates::flash::build_template(&name, &msg))
} }
} }
}; };

View File

@ -14,17 +14,17 @@ pub fn restart_sbot() -> Response {
// 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 systemctl_sbot_cmd("start") {
Ok(_) => ( Ok(_) => (
"flash_name=success".to_string(), "success".to_string(),
"flash_msg=Sbot process has been restarted".to_string(), "Sbot process has been restarted".to_string(),
), ),
Err(e) => ( Err(e) => (
"flash_name=error".to_string(), "error".to_string(),
format!("flash_msg=Failed to start the sbot process: {}", e), format!("Failed to start the sbot process: {}", e),
), ),
}, },
Err(e) => ( Err(e) => (
"flash_name=error".to_string(), "error".to_string(),
format!("flash_msg=Failed to stop the sbot process: {}", e), format!("Failed to stop the sbot process: {}", e),
), ),
}; };

View File

@ -12,12 +12,12 @@ 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 systemctl_sbot_cmd("start") {
Ok(_) => ( Ok(_) => (
"flash_name=success".to_string(), "success".to_string(),
"flash_msg=Sbot process has been started".to_string(), "Sbot process has been started".to_string(),
), ),
Err(_) => ( Err(_) => (
"flash_name=error".to_string(), "error".to_string(),
"flash_msg=Failed to start the sbot process".to_string(), "Failed to start the sbot process".to_string(),
), ),
}; };

View File

@ -9,15 +9,15 @@ use crate::utils::{flash::FlashResponse, sbot::systemctl_sbot_cmd};
/// Redirect to the Scuttlebutt settings menu and communicate the outcome of /// Redirect to the Scuttlebutt settings menu and communicate the outcome of
/// 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 tilde-sbot.service");
let (flash_name, flash_msg) = match systemctl_sbot_cmd("stop") { let (flash_name, flash_msg) = match systemctl_sbot_cmd("stop") {
Ok(_) => ( Ok(_) => (
"flash_name=success".to_string(), "success".to_string(),
"flash_msg=Sbot process has been stopped".to_string(), "Sbot process has been stopped".to_string(),
), ),
Err(_) => ( Err(_) => (
"flash_name=error".to_string(), "error".to_string(),
"flash_msg=Failed to stop the sbot process".to_string(), "Failed to stop the sbot process".to_string(),
), ),
}; };

View File

@ -102,7 +102,7 @@ fn memory_element(memory: Option<u32>) -> Markup {
fn hops_element() -> Markup { fn hops_element() -> Markup {
// retrieve go-sbot systemd process status // retrieve go-sbot systemd process status
let hops = match SbotConfig::read() { let hops = match SbotConfig::read() {
Ok(conf) => conf.hops, Ok(conf) => conf.replication_hops,
_ => 0, _ => 0,
}; };

View File

@ -1,21 +1,21 @@
use rouille::{input, Request, Response}; use rouille::{input, Request, Response};
use urlencoding;
/// Flash message trait for `Request`. /// Flash message trait for `Request`.
pub trait FlashRequest { pub trait FlashRequest {
/// Retrieve the flash message cookie values from a `Request`. /// Retrieve the flash message cookie values from a `Request`.
fn retrieve_flash(&self) -> (Option<&str>, Option<&str>); fn retrieve_flash(&self) -> (Option<String>, Option<String>);
} }
impl FlashRequest for Request { impl FlashRequest for Request {
fn retrieve_flash(&self) -> (Option<&str>, Option<&str>) { fn retrieve_flash(&self) -> (Option<String>, Option<String>) {
// check for flash cookies
let flash_name = input::cookies(self) let flash_name = input::cookies(self)
.find(|&(n, _)| n == "flash_name") .find(|&(n, _)| n == "flash_name")
// return the value of the cookie (key is already known) // return the value of the cookie (key is already known)
.map(|key_val| key_val.1); .and_then(|(_, val)| urlencoding::decode(&val).ok().map(|s| s.into_owned()));
let flash_msg = input::cookies(self) let flash_msg = input::cookies(self)
.find(|&(n, _)| n == "flash_msg") .find(|&(n, _)| n == "flash_msg")
.map(|key_val| key_val.1); .and_then(|(_, val)| urlencoding::decode(&val).ok().map(|s| s.into_owned()));
(flash_name, flash_msg) (flash_name, flash_msg)
} }
@ -31,9 +31,12 @@ pub trait FlashResponse {
impl FlashResponse for Response { impl FlashResponse for Response {
fn add_flash(self, flash_name: String, flash_msg: String) -> Response { fn add_flash(self, flash_name: String, flash_msg: String) -> Response {
let flash_name = urlencoding::encode(&flash_name).into_owned();
let flash_msg = urlencoding::encode(&flash_msg).into_owned();
// set the flash cookie headers // set the flash cookie headers
self.with_additional_header("Set-Cookie", format!("{}; Max-Age=1", flash_name)) self.with_additional_header("Set-Cookie", format!("flash_name={}; Max-Age=1;", flash_name))
.with_additional_header("Set-Cookie", format!("{}; Max-Age=1", flash_msg)) .with_additional_header("Set-Cookie", format!("flash_msg={}; Max-Age=1;", flash_msg))
} }
fn reset_flash(self) -> Response { fn reset_flash(self) -> Response {

View File

@ -19,18 +19,21 @@ use peach_lib::ssb_messages::SsbMessageKVT;
use rouille::input::post::BufferedFile; use rouille::input::post::BufferedFile;
use temporary::Directory; use temporary::Directory;
use peach_lib::serde_json::json; use peach_lib::serde_json::json;
use peach_lib::tilde_client::TildeClient; use peach_lib::tilde_client::{TildeClient, TildeError};
use crate::{error::PeachWebError, utils::sbot}; use crate::{error::PeachWebError, utils::sbot};
// SBOT HELPER FUNCTIONS // SBOT HELPER FUNCTIONS
/// Executes a systemctl command for the solar-sbot.service process. /// Executes a systemctl command for the solar-sbot.service process.
pub fn systemctl_sbot_cmd(cmd: &str) -> Result<Output, PeachWebError> { pub fn systemctl_sbot_cmd(cmd: &str) -> Result<Output, PeachWebError> {
let output = Command::new("sudo") let mut command = Command::new("sudo");
command
.arg("systemctl") .arg("systemctl")
.arg(cmd) .arg(cmd)
.arg(config_manager::get_config_value("TILDE_SBOT_SERVICE")?) .arg(config_manager::get_config_value("TILDE_SBOT_SERVICE")?);
.output()?; println!("systemctl command: {:?}", command);
let output = command.output()?;
println!("systemctl output: {:?}", output);
Ok(output) Ok(output)
} }
@ -66,11 +69,6 @@ pub fn restart_sbot_process() -> (String, String) {
/// Initialise an sbot client with the given configuration parameters. /// Initialise an sbot client with the given configuration parameters.
pub async fn init_sbot_client() -> Result<TildeClient, PeachWebError> { pub async fn init_sbot_client() -> Result<TildeClient, 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
let key_path = format!(
"{}/secret.toml",
config_manager::get_config_value("TILDE_SBOT_DATADIR")?
);
let sbot_client = init_sbot().await?; let sbot_client = init_sbot().await?;
Ok(sbot_client) Ok(sbot_client)
} }
@ -114,14 +112,15 @@ pub fn validate_public_key(public_key: &str) -> Result<(), String> {
/// reverses the list and reads the sequence number of the most recently /// reverses the list and reads the sequence number of the most recently
/// authored message. This gives us the size of the database in terms of /// authored message. This gives us the size of the database in terms of
/// the total number of locally-authored messages. /// the total number of locally-authored messages.
pub fn latest_sequence_number() -> Result<u64, PeachWebError> { pub fn latest_sequence_number() -> Result<String, PeachWebError> {
// retrieve latest solar-sbot configuration parameters // retrieve latest solar-sbot configuration parameters
let sbot_config = SbotConfig::read().ok(); let sbot_config = SbotConfig::read().ok();
task::block_on(async { task::block_on(async {
let mut sbot_client = init_sbot_client().await?; let mut sbot_client = init_sbot_client().await?;
Err(PeachWebError::NotYetImplemented) let sequence_num = sbot_client.latest_sequence_number().await?;
Ok(sequence_num)
// retrieve the local id // retrieve the local id
// let id = sbot_client.whoami().await?; // let id = sbot_client.whoami().await?;
@ -150,17 +149,10 @@ pub fn create_invite(uses: u16) -> Result<String, PeachWebError> {
task::block_on(async { task::block_on(async {
let mut sbot_client = init_sbot_client().await?; let mut sbot_client = init_sbot_client().await?;
debug!("Generating Scuttlebutt invite code"); let external_domain = config_manager::get_config_value("EXTERNAL_DOMAIN").ok();
Err(PeachWebError::NotYetImplemented) let mut invite_code = sbot_client.create_invite(uses as i32, external_domain.as_deref()).await?;
// let mut invite_code = sbot_client.invite_create(uses).await?;
// Ok(invite_code)
// // 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)
}) })
} }
@ -279,67 +271,67 @@ pub fn update_profile_info(
let sbot_config = SbotConfig::read().ok(); let sbot_config = SbotConfig::read().ok();
task::block_on(async { task::block_on(async {
let mut sbot_client = init_sbot_client() let mut sbot_client = init_sbot_client().await?;
.await?;
Err(PeachWebError::NotYetImplemented)
// // track whether the name, description or image have been updated // // track whether the name, description or image have been updated
// let mut name_updated: bool = false; let mut name_updated: bool = false;
// let mut description_updated: bool = false; let mut description_updated: bool = false;
// let mut image_updated: bool = false; let mut image_updated: bool = false;
//
// // check if a new_name value has been submitted in the form // check if a new_name value has been submitted in the form
// if let Some(name) = new_name { if let Some(name) = new_name {
// // only update the name if it has changed // only update the name if it has changed
// if name != current_name { if name != current_name {
// debug!("Publishing a new Scuttlebutt profile name"); debug!("Publishing a new Scuttlebutt profile name");
// if let Err(e) = sbot_client.publish_name(&name).await { if let Err(e) = sbot_client.publish_name(&name).await {
// return Err(format!("Failed to update name: {}", e)); return Err(PeachWebError::Tilde(TildeError {message: (format!("Failed to update name: {}", e))}));
// } else { } else {
// name_updated = true name_updated = true;
// } }
// } }
// } }
//
// if let Some(description) = new_description { if let Some(description) = new_description {
// // only update the description if it has changed // only update the description if it has changed
// if description != current_description { if description != current_description {
// debug!("Publishing a new Scuttlebutt profile description"); debug!("Publishing a new Scuttlebutt profile description");
// if let Err(e) = sbot_client.publish_description(&description).await { if let Err(e) = sbot_client.publish_description(&description).await {
// return Err(format!("Failed to update description: {}", e)); return Err(PeachWebError::Tilde(TildeError {message: (format!("Failed to update description: {}", e))}));
// } else { } else {
// description_updated = true description_updated = true
// } }
// } }
// } }
//
// // only update the image if a file was uploaded // only update the image if a file was uploaded
// if let Some(img) = image { if let Some(img) = image {
// // only write the blob if it has a filename and data > 0 bytes // only write the blob if it has a filename and data > 0 bytes
// if img.filename.is_some() && !img.data.is_empty() { if img.filename.is_some() && !img.data.is_empty() {
// match write_blob_to_store(img).await { match write_blob_to_store(img).await {
// Ok(blob_id) => { Ok(blob_id) => {
// // if the file was successfully added to the blobstore, // if the file was successfully added to the blobstore,
// // publish an about image message with the blob id // publish an about image message with the blob id
// if let Err(e) = sbot_client.publish_image(&blob_id).await { if let Err(e) = sbot_client.publish_image(&blob_id).await {
// return Err(format!("Failed to update image: {}", e)); return Err(PeachWebError::Tilde(TildeError {message: (format!("Failed to update image: {}", e))}));
// } else { } else {
// image_updated = true image_updated = true
// } }
// } }
// Err(e) => return Err(format!("Failed to add image to blobstore: {}", e)), Err(e) => {
// } return Err(PeachWebError::Tilde(TildeError {message: (format!("Failed to add image to blob store: {}", e))}));
// } else { }
// image_updated = false }
// } } else {
// } image_updated = false
// }
// if name_updated || description_updated || image_updated { }
// Ok("Profile updated".to_string())
// } else { if name_updated || description_updated || image_updated {
// // no updates were made but no errors were encountered either Ok("Profile updated".to_string())
// Ok("Profile info unchanged".to_string()) } else {
// } // no updates were made but no errors were encountered either
Ok("Profile info unchanged".to_string())
}
}) })
} }
@ -445,21 +437,23 @@ pub fn get_blocks_list() -> Result<Vec<HashMap<String, String>>, Box<dyn Error>>
pub async fn get_peer_info(key: &str) -> Result<HashMap<String, String>, Box<dyn Error>> { pub async fn get_peer_info(key: &str) -> Result<HashMap<String, String>, Box<dyn Error>> {
let mut sbot_client = init_sbot_client().await?; let mut sbot_client = init_sbot_client().await?;
// key,value dict of info about this peer // key,value dict of info about this peer
let mut peer_info = HashMap::new(); let tilde_profile_info = sbot_client.get_profile_info(key).await.map_err(|err| {
// retrieve the profile info for the given peer println!("error getting profile info: {}", err);
// TODO: get all profile info not just latest_name err
// TODO: latest_name throws an error
// TODO: just show as "error" isntead of aborting, if some field doesn't fetch
// let latest_name = sbot_client.latest_name(&key).await?;
let latest_name = "latest name".to_string();
if let Some(latest_description) = sbot_client.latest_description(&key).await.ok() {
peer_info.insert("description".to_string(), latest_description);
} }
)?;
let mut peer_info = HashMap::new();
tilde_profile_info.get("name").and_then(|val| val.as_str()).map(|val| {
peer_info.insert("name".to_string(), val.to_string());
});
tilde_profile_info.get("description").and_then(|val| val.as_str()).map(|val| {
peer_info.insert("description".to_string(), val.to_string());
});
// insert the public key of the peer into the info hashmap // insert the public key of the peer into the info hashmap
peer_info.insert("id".to_string(), key.to_string()); peer_info.insert("id".to_string(), key.to_string());
peer_info.insert("name".to_string(), latest_name); // TODO: display profile photo blob
// retrieve the profile image blob id for the given peer // // retrieve the profile image blob id for the given peer
// TODO: blob support
// if let Some(blob_id) = peer_info.get("image") { // if let Some(blob_id) = peer_info.get("image") {
// // look-up the path for the image blob // // look-up the path for the image blob
// if let Ok(blob_path) = blobs::get_blob_path(blob_id) { // if let Ok(blob_path) = blobs::get_blob_path(blob_id) {
@ -585,30 +579,25 @@ pub fn publish_private_msg(text: String, recipients: Vec<String>) -> Result<Stri
let mut sbot_client = init_sbot_client() let mut sbot_client = init_sbot_client()
.await?; .await?;
Err(PeachWebError::NotYetImplemented) for recipient in &recipients {
// debug!("Publishing a new Scuttlebutt private message"); sbot_client.private_message(recipient, &text).await?;
// match sbot_client }
// .publish_private(text.to_string(), recipients) Ok("Published private message".to_string())
// .await
// {
// Ok(_) => Ok("Published private message".to_string()),
// Err(e) => Err(format!("Failed to publish private message: {}", e)),
// }
}) })
} }
// FILEPATH FUNCTIONS // FILEPATH FUNCTIONS
/// Return the path of the ssb-go directory. /// Return the path of the tilde-sbot directory.
pub fn get_go_ssb_path() -> Result<String, PeachWebError> { pub fn get_tilde_ssb_path() -> Result<String, PeachWebError> {
let go_ssb_path = match SbotConfig::read() { let tilde_ssb_path = match SbotConfig::read() {
Ok(conf) => conf.repo, Ok(conf) => conf.database_directory,
// return the default path if unable to read `config.toml` // return the default path if unable to read `config.toml`
Err(_) => { Err(_) => {
// determine the home directory // determine the home directory
let mut home_path = dirs::home_dir().ok_or(PeachWebError::HomeDir)?; let mut home_path = dirs::home_dir().ok_or(PeachWebError::HomeDir)?;
// add the go-ssb subdirectory // add the .ssb-tilde subdirectory
home_path.push(".ssb-go"); home_path.push(".ssb-tilde");
// convert the PathBuf to a String // convert the PathBuf to a String
home_path home_path
.into_os_string() .into_os_string()
@ -616,12 +605,12 @@ pub fn get_go_ssb_path() -> Result<String, PeachWebError> {
.map_err(|_| PeachWebError::OsString)? .map_err(|_| PeachWebError::OsString)?
} }
}; };
Ok(go_ssb_path) Ok(tilde_ssb_path)
} }
/// Check whether a blob is in the blobstore. /// Check whether a blob is in the blobstore.
pub async fn blob_is_stored_locally(blob_path: &str) -> Result<bool, PeachWebError> { pub async fn blob_is_stored_locally(blob_path: &str) -> Result<bool, PeachWebError> {
let go_ssb_path = get_go_ssb_path()?; let go_ssb_path = get_tilde_ssb_path()?;
let complete_path = format!("{}/blobs/sha256/{}", go_ssb_path, blob_path); let complete_path = format!("{}/blobs/sha256/{}", go_ssb_path, blob_path);
let blob_exists_locally = Path::new(&complete_path).exists(); let blob_exists_locally = Path::new(&complete_path).exists();
Ok(blob_exists_locally) Ok(blob_exists_locally)
@ -642,28 +631,13 @@ pub async fn write_blob_to_store(image: BufferedFile) -> Result<String, PeachWeb
// write file to temporary path // write file to temporary path
fs::write(&temp_path, &image.data)?; fs::write(&temp_path, &image.data)?;
// open the file and read it into a buffer // create blob from file
let mut file = File::open(&temp_path)?; let mut sbot_client = init_sbot_client().await?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
Err(PeachWebError::NotYetImplemented) let blob_path = temp_path.to_str().ok_or("Error storing blob to disk").map_err(|e| PeachWebError::Tilde(TildeError {
// TODO: not yet implemented message: format!("Error storing blob to disk: {}", e),
}))?;
let blob_id = sbot_client.store_blob(blob_path).await?;
// // hash the bytes representing the file Ok(blob_id)
// let (hex_hash, blob_id) = blobs::hash_blob(&buffer)?;
//
// // define the blobstore path and blob filename
// let (blob_dir, blob_filename) = hex_hash.split_at(2);
// let go_ssb_path = get_go_ssb_path()?;
// let blobstore_sub_dir = format!("{}/blobs/sha256/{}", go_ssb_path, blob_dir);
//
// // create the blobstore sub-directory
// fs::create_dir_all(&blobstore_sub_dir)?;
//
// // copy the file to the blobstore
// let blob_path = format!("{}/{}", blobstore_sub_dir, blob_filename);
// fs::copy(temp_path, blob_path)?;
//
// Ok(blob_id)
} }

43
run-tilde-sbot.sh Executable file
View File

@ -0,0 +1,43 @@
#!/bin/bash
# Exit on error
set -e
# Usage check
if [ "$#" -lt 2 ]; then
echo "Usage: $0 <CONFIG_FILE> <TILDEFRIENDS_PATH>"
exit 1
fi
CONFIG_FILE="$1"
TILDEFRIENDS_PATH="$2"
# Extract network_key (if it exists)
NETWORK_KEY=$(grep -v '^\s*#' "$CONFIG_FILE" | grep -E '^\s*network_key\s*=' | sed -E 's/.*=\s*"?(.*?)"?\s*/\1/')
DATABASE_DIRECTORY=$(grep -v '^\s*#' "$CONFIG_FILE" \
| grep -E '^\s*database_directory\s*=' \
| sed -E 's/.*=\s*"([^"]*)"\s*/\1/')
# Extract all other key-value pairs except network_key
ARGS=$(grep -v '^\s*#' "$CONFIG_FILE" \
| grep -E '^\s*[a-zA-Z0-9_.-]+\s*=' \
| grep -v '^\s*network_key\s*=' \
| sed -E 's/\s*=\s*/=/' \
| tr -d '"' \
| paste -sd, -)
echo "ARGS: $ARGS"
[ -n "$NETWORK_KEY" ] && echo "NETWORK_KEY: $NETWORK_KEY"
[ -n "$DATABASE_DIRECTORY" ] && echo "DATABASE_DIRECTORY: $DATABASE_DIRECTORY"
CMD="\"$TILDEFRIENDS_PATH\" run"
[ -n "$ARGS" ] && CMD="$CMD -a \"$ARGS\""
[ -n "$NETWORK_KEY" ] && CMD="$CMD -k \"$NETWORK_KEY\""
[ -n "$DATABASE_DIRECTORY" ] && CMD="$CMD -d \"$DATABASE_DIRECTORY/db.sqlite\""
echo "Running command:"
echo "$CMD"
# Execute the command
eval $CMD

3
tdeploy.sh Executable file
View File

@ -0,0 +1,3 @@
#! /bin/bash
cargo build --package peach-web --release
rsync -azvh /home/notplants/computer/projects/peachpub/peach-workspace/target/release/peach-web root@10.243.137.235:/var/www/peachpub_ynh/peach-web

View File

@ -6,7 +6,7 @@ use std::fmt;
/// all tilde client errors /// all tilde client errors
#[derive(Debug)] #[derive(Debug)]
pub struct TildeError { pub struct TildeError {
pub(crate) message: String, pub message: String,
} }
impl fmt::Display for TildeError { impl fmt::Display for TildeError {

View File

@ -1,51 +1,73 @@
// methods for interacting with tilde sbot // methods for interacting with tilde sbot
use crate::error::TildeError; use std::collections::HashMap;
pub use crate::error::TildeError;
use serde_json::Value; use serde_json::{json, Value};
use std::process::{Command, exit}; use std::process::{Command, exit};
mod error; mod error;
pub struct TildeClient { pub struct TildeClient {
name: String, pub ssb_port: String,
port: String pub tilde_binary_path: String,
pub tilde_database_path: String,
} }
pub fn init_sbot() { pub fn init_sbot() {
println!("++ init sbot!"); println!("++ init sbot!");
} }
pub fn get_sbot_client() -> TildeClient {
TildeClient {
name: "name".to_string(),
port: "8009".to_string()
}
}
impl TildeClient { impl TildeClient {
pub fn run_tilde_command(&self, command: &str) -> Result<String, TildeError> { pub fn run_tilde_command(&self, args: Vec<&str>) -> Result<String, TildeError> {
let output = Command::new("out/release/tildefriends.standalone") let mut command = Command::new(&self.tilde_binary_path);
.arg(command) let mut full_args = args.clone();
full_args.push("-d");
full_args.push(self.tilde_database_path.as_str());
command.args(full_args);
let output = command
.output().map_err(|e| TildeError { .output().map_err(|e| TildeError {
message: format!("Command execution failed: {}", e), message: format!("Command execution failed: {}", e),
})?; })?;
if !output.status.success() { if !output.status.success() {
println!("command: {:?}", command);
println!("stderr: {:?}", String::from_utf8_lossy(&output.stderr).to_string());
println!("stdout: {:?}", String::from_utf8_lossy(&output.stdout).to_string());
return Err(TildeError { message: format!("Command failed with status: {}", output.status) }) return Err(TildeError { message: format!("Command failed with status: {}", output.status) })
} }
let result = String::from_utf8_lossy(&output.stdout).to_string(); let result = String::from_utf8_lossy(&output.stdout).to_string();
println!("Command: {:?}", command);
println!("Command output: {}", result); println!("Command output: {}", result);
Ok(result) Ok(result)
} }
pub async fn latest_description(&self, key: &str) -> Result<String, TildeError> {
todo!(); pub async fn tilde_command_to_value(&self, args: Vec<&str>) -> Result<Value, TildeError> {
let result = self.run_tilde_command(args)?;
let value = serde_json::from_str(&result).map_err(|e| TildeError {
message: format!("Failed to parse JSON: {}", e),
})?;
Ok(value)
} }
pub async fn whoami(&self) -> Result<String, TildeError> { pub async fn whoami(&self) -> Result<String, TildeError> {
self.run_tilde_command("get_identity") self.run_tilde_command(vec!["get_identity"]).map(|val| val.trim_end().to_string())
}
pub async fn get_profile_info(&self, key: &str) -> Result<Value, TildeError> {
self.tilde_command_to_value(vec!["get_profile", "-i", key]).await
}
pub async fn latest_sequence_number(&self) -> Result<String, TildeError> {
let key = self.whoami().await?;
// let num = self.run_tilde_command(vec!["get_sequence", "-i", &key])?.parse::<u64>().map_err(|e| TildeError {
// message: format!("Failed to parse u64 from sequence number: {}", e),
// })?;
let num = self.run_tilde_command(vec!["get_sequence", "-i", &key])?;
println!("NUM: {}", num);
Ok(num)
} }
pub async fn is_following(&self, from_id: &str, to_id: &str) -> Result<bool, TildeError> { pub async fn is_following(&self, from_id: &str, to_id: &str) -> Result<bool, TildeError> {
@ -67,7 +89,54 @@ impl TildeClient {
todo!(); todo!();
} }
pub async fn publish(&self, post: Value) -> Result<Vec<String>, TildeError> { pub async fn publish(&self, post: Value) -> Result<String, TildeError> {
todo!(); let json_string = post.to_string();
let key = self.whoami().await?;
self.run_tilde_command(vec!["publish", "-u", ":admin", "-i", &key, "-c", &json_string])
}
pub async fn publish_name(&self, name: &str) -> Result<String, TildeError> {
let key = self.whoami().await?;
let about_post = json!({
"type": "about",
"about": key,
"name": name
});
self.publish(about_post).await
}
pub async fn publish_description(&self, description: &str) -> Result<String, TildeError> {
let key = self.whoami().await?;
let about_post = json!({
"type": "about",
"about": key,
"description": description
});
self.publish(about_post).await
}
pub async fn publish_image(&self, image_blob_id: &str) -> Result<String, TildeError> {
let key = self.whoami().await?;
let about_post = json!({
"type": "about",
"about": key,
"image": image_blob_id
});
self.publish(about_post).await
}
pub async fn store_blob(&self, blob_file_path: &str) -> Result<String, TildeError> {
self.run_tilde_command(vec!["store_blob", "-f", blob_file_path])
}
pub async fn private_message(&self, recipient_key: &str, message: &str) -> Result<String, TildeError> {
let self_key = self.whoami().await?;
self.run_tilde_command(vec!["private", "-u", ":admin", "-i", &self_key, "-r", recipient_key, "-t", message])
}
pub async fn create_invite(&self, num_uses: i32, external_domain: Option<&str>) -> Result<String, TildeError> {
let key = self.whoami().await?;
let address = external_domain.unwrap_or_else(|| "127.0.0.1");
self.run_tilde_command(vec!["create_invite", "-u", &num_uses.to_string(), "-i", &key, "-p", &self.ssb_port, "-a", address, "-e", "-1"])
} }
} }