use log::{debug, warn}; use maud::{html, PreEscaped}; use peach_lib::sbot::{SbotConfig, SbotStatus}; use rouille::{post_input, try_or_400, Request, Response}; use crate::{ templates, utils::{ flash::{FlashRequest, FlashResponse}, sbot, theme, }, }; /// Read the status and configuration of the sbot. /// Define fallback values if an error is returned from either read function. fn read_status_and_config() -> (String, SbotConfig, String, String) { // retrieve go-sbot systemd process status let run_on_startup = if let Ok(status) = SbotStatus::read() { // if the read is ok, return the value or "disabled" if no value is set match status.boot_state { Some(state) => state, None => "disabled".to_string(), } } else { // if the read returns an error, set the value to "disabled" "disabled".to_string() }; // retrieve sbot config parameters let sbot_config = match SbotConfig::read() { Ok(config) => config, // build default config if an error is returned from the read attempt Err(_) => SbotConfig::default(), }; // split the listen address into ip and port let (ip, port) = match sbot_config.lis.find(':') { 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) } /// Scuttlebutt settings menu template builder. pub fn build_template(request: &Request) -> PreEscaped { // check for flash cookies; will be (None, None) if no flash cookies are found let (flash_name, flash_msg) = request.retrieve_flash(); let (run_on_startup, sbot_config, ip, port) = read_status_and_config(); let menu_template = html! { (PreEscaped("")) div class="card center" { form id="sbotConfig" class="center" action="/settings/scuttlebutt/configure" method="post" { div class="center" style="display: flex; flex-direction: column; margin-bottom: 2rem;" title="Number of hops to replicate" { label for="hops" class="label-small font-gray" { "HOPS" } div id="hops" style="display: flex; justify-content: space-evenly;" { div { @if sbot_config.hops == 0 { input type="radio" id="hops_0" name="hops" value="0" checked; } @else { input type="radio" id="hops_0" name="hops" value="0"; } label class="font-normal" for="hops_0" { "0" } } div { @if sbot_config.hops == 1 { input type="radio" id="hops_1" name="hops" value="1" checked; } @else { input type="radio" id="hops_1" name="hops" value="1"; } label class="font-normal" for="hops_1" { "1" } } div { @if sbot_config.hops == 2 { input type="radio" id="hops_2" name="hops" value="2" checked; } @else { input type="radio" id="hops_2" name="hops" value="2"; } label class="font-normal" for="hops_2" { "2" } } div { @if sbot_config.hops == 3 { input type="radio" id="hops_3" name="hops" value="3" checked; } @else { input type="radio" id="hops_3" name="hops" value="3"; } label class="font-normal" for="hops_3" { "3" } } div { @if sbot_config.hops == 4 { input type="radio" id="hops_4" name="hops" value="4" checked; } @else { input type="radio" id="hops_4" name="hops" value="4"; } label class="font-normal" for="hops_4" { "4" } } } } 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" { label for="ip" class="label-small font-gray" { "IP ADDRESS" } input type="text" id="ip" name="lis_ip" value=(ip); } 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" } input type="text" id="port" name="lis_port" value=(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" { label for="network_key" class="label-small font-gray" { "NETWORK KEY" } input type="text" id="network_key" name="shscap" value=(sbot_config.shscap); } 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" } input type="text" id="database_dir" name="repo" value=(sbot_config.repo); } div class="center" { @if sbot_config.enable_ebt { input type="checkbox" id="ebtReplication" style="margin-bottom: 1rem;" name="enable_ebt" checked; } @else { input type="checkbox" id="ebtReplication" style="margin-bottom: 1rem;" name="enable_ebt"; } label class="font-normal" for="ebtReplication" title="Enable Epidemic Broadcast Tree (EBT) replication instead of legacy replication" { "Enable EBT Replication" } br; @if sbot_config.localadv { input type="checkbox" id="lanBroadcast" style="margin-bottom: 1rem;" name="localadv" checked; } @else { 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" { "Enable LAN Broadcasting" } br; @if sbot_config.localdiscov { input type="checkbox" id="lanDiscovery" style="margin-bottom: 1rem;" name="localdiscov" checked; } @else { 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" } br; @if run_on_startup == "enabled" { input type="checkbox" id="startup" style="margin-bottom: 1rem;" name="startup" checked; } @else { input type="checkbox" id="startup" style="margin-bottom: 1rem;" name="startup"; } label class="font-normal" for="startup" title="Run the pub automatically on system startup" { "Run pub when computer starts" } 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("")) 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="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="nounixsock" name="nounixsock" value=(sbot_config.nounixsock); (PreEscaped("")) input id="saveConfig" class="button button-primary center" style="margin-top: 2rem;" type="submit" title="Save configuration parameters to file" value="Save"; input id="saveRestartConfig" class="button button-primary center" type="submit" title="Save configuration parameters to file and then (re)start the pub" value="Save & Restart" formaction="/settings/scuttlebutt/configure/restart"; a id="restoreDefaults" class="button button-warning center" href="/settings/scuttlebutt/configure/default" title="Restore default configuration parameters and save them to file" { "Restore Defaults" } } // render flash message if cookies were found in the request @if let (Some(name), Some(msg)) = (flash_name, flash_msg) { (PreEscaped("")) (templates::flash::build_template(name, msg)) } } }; // wrap the nav bars around the settings menu template content // parameters are template, title and back url let body = templates::nav::build_template( menu_template, "Scuttlebutt Settings", Some("/settings/scuttlebutt"), ); // query the current theme so we can pass it into the base template builder let theme = theme::get_theme(); // render the base template with the provided body templates::base::build_template(body, theme) } /// Parse the sbot configuration values and write to file. pub fn handle_form(request: &Request, restart: bool) -> Response { // query the request body for form data // return a 400 error if the admin_id field is missing let data = try_or_400!(post_input!(request, { repo: String, debugdir: String, shscap: String, hmac: String, hops: u8, lis_ip: String, lis_port: String, wslis: String, debuglis: String, localadv: bool, localdiscov: bool, enable_ebt: bool, promisc: bool, nounixsock: bool, startup: bool, repair: bool, })); // concat the ip and port for listen address let lis = format!("{}:{}", data.lis_ip, data.lis_port); // instantiate `SbotConfig` from form data let config = SbotConfig { lis, hops: data.hops, repo: data.repo, debugdir: data.debugdir, shscap: data.shscap, localadv: data.localadv, localdiscov: data.localdiscov, hmac: data.hmac, wslis: data.wslis, debuglis: data.debuglis, enable_ebt: data.enable_ebt, promisc: data.promisc, nounixsock: data.nounixsock, repair: data.repair, }; match data.startup { true => { debug!("Enabling go-sbot.service"); if let Err(e) = sbot::system_sbot_cmd("enable") { warn!("Failed to enable go-sbot.service: {}", e) } } false => { debug!("Disabling go-sbot.service"); if let Err(e) = sbot::system_sbot_cmd("disable") { warn!("Failed to disable go-sbot.service: {}", e) } } }; // write config to file let (name, msg) = match SbotConfig::write(config) { Ok(_) => { // if `restart` query parameter is `true`, attempt sbot process (re)start if restart { // returns a tuple of (name, msg) based on the outcome (success or error) sbot::restart_sbot_process() } else { ("success".to_string(), "Updated configuration".to_string()) } } Err(err) => ( "error".to_string(), format!("Failed to update configuration: {}", err), ), }; let (flash_name, flash_msg) = (format!("flash_name={}", name), format!("flash_msg={}", msg)); Response::redirect_303("/settings/scuttlebutt/configure").add_flash(flash_name, flash_msg) }