diff --git a/Cargo.lock b/Cargo.lock index 4b0fd97e404f619cd7211138dddcd08333497002..ba44a36760f1d0d359f25be4ba99bf92a0711a9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1164,7 +1164,9 @@ dependencies = [ "log", "logwatcher", "nix 0.17.0", + "read_input", "rusty-hook", + "serde_json", "structopt", ] @@ -2933,6 +2935,12 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "read_input" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b57518cc6538a2eb7dce826e24fa51d0b7cf8e744ee10c7f56259cdec40050e5" + [[package]] name = "redox_syscall" version = "0.1.57" diff --git a/Cargo.toml b/Cargo.toml index 57b22316f6bbbf3e9285006bd68595cd47ded856..c671f85a12b45215f0b35d8185a459439e0cced6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,8 @@ dirs = "3.0.1" log = "0.4.11" logwatcher = "0.1.1" nix = "0.17.0" +read_input = "0.8.4" +serde_json = "1.0.53" structopt = "0.3.18" [dev-dependencies] diff --git a/app/lib/dto/ConfDTO.ts b/app/lib/dto/ConfDTO.ts index b80564a06834daa699c15a152aab5d86b04b3066..69325df7b8a4b8a77ed390d61268249b0e57fae1 100644 --- a/app/lib/dto/ConfDTO.ts +++ b/app/lib/dto/ConfDTO.ts @@ -181,6 +181,7 @@ export class ConfDTO public nonWoTPeersLimit: number, public proxiesConf: ProxiesConf | undefined, public gva?: { + enabled: boolean; host?: string; port?: number; path?: string; diff --git a/app/modules/config.ts b/app/modules/config.ts index 2dfe10269ade0665d43e9f1ae8f39781df5af346..27925692eee116e30a076bcfea985575e7299bba 100644 --- a/app/modules/config.ts +++ b/app/modules/config.ts @@ -21,16 +21,7 @@ import { ProgramOptions } from "../lib/common-libs/programOptions"; module.exports = { duniter: { - cliOptions: [ - { - value: "--gva", - desc: "Enable gva API and database.", - }, - { - value: "--no-gva", - desc: "Disable gva API and database.", - }, - ], + cliOptions: [], config: { onLoading: async (conf: ConfDTO, program: ProgramOptions) => { @@ -38,31 +29,6 @@ module.exports = { conf.sigReplay = conf.msPeriod; conf.switchOnHeadAdvance = CommonConstants.SWITCH_ON_BRANCH_AHEAD_BY_X_BLOCKS; - - // Gva - if ( - program.gva && - program.gva === true && - (!program.noGva || program.noGva === undefined) - ) { - // Fill with default conf if needed - if (conf.gva) { - conf.gva.host = conf.gva.host || "localhost"; - conf.gva.port = conf.gva.port || 30901; - conf.gva.path = conf.gva.path || "gva"; - conf.gva.subscriptionsPath = - conf.gva.subscriptionsPath || "gva-sub"; - } else { - conf.gva = { - host: "localhost", - port: 30901, - path: "gva", - subscriptionsPath: "gva-sub", - }; - } - } else if (program.noGva) { - conf.gva = undefined; - } }, beforeSave: async (conf: ConfDTO) => { conf.msPeriod = conf.msWindow; diff --git a/doc/use/configure.md b/doc/use/configure.md index 4d3d1a371a5975ee59a7fff7fd0a7e9248998251..5a394554ead86c338a5737c92606dcc875fb6819 100644 --- a/doc/use/configure.md +++ b/doc/use/configure.md @@ -183,9 +183,11 @@ This mode is optional if only because technically it is sometimes difficult or e ### Configuring GVA -GVA is still disabled by default. To enable it you have to add the option `--gva` during synchronization and at each start of your duniter node (`duniter start --gva`). +GVA is still disabled by default, to enable it you need to use wizard command: -GVA is not yet configurable from the command line, but it is possible to manually edit `conf.json` file : +`duniter wizard gva` + +It is also possible to manually edit `conf.json` file : | parameter | type | default value | |:-:|:-:|:-:| @@ -211,8 +213,6 @@ duniter sync DUNITER_NODE_HOST:DUNITER_NODE_PORT For Ğ1, if you don't know any node you can choose the official node `g1.duniter.org:443`. -NB: If you want to enable GVA you have to synchronize with the `--gva` option. - ## Launch There are four different commands depending on whether or not you want to demonize your Duniter instance and whether or not you want to use the web-ui: diff --git a/rust-bins/duniter-launcher/src/duniter_ts_args.rs b/rust-bins/duniter-launcher/src/duniter_ts_args.rs index 1b3724526b790688363c4817b4af0ca823cebd64..6a7e59c9b61a6650fafc4651ce7c65551be216d5 100644 --- a/rust-bins/duniter-launcher/src/duniter_ts_args.rs +++ b/rust-bins/duniter-launcher/src/duniter_ts_args.rs @@ -16,9 +16,6 @@ use crate::*; fn gen_start_args(args: &DuniterStartArgs, duniter_ts_args: &mut Vec<String>) { - if args.gva { - duniter_ts_args.push("--gva".to_owned()); - } if let Some(ref keyfile) = args.keyfile { duniter_ts_args.push("--keyfile".to_owned()); duniter_ts_args.push( @@ -130,6 +127,7 @@ pub(crate) fn gen_duniter_ts_args(args: &DuniterArgs, duniter_js_exe: String) -> duniter_ts_args.push(p.to_string()); } } + WizardCommand::Gva { .. } => unreachable!(), } } DuniterCommand::WS2P(ref ws2p_command) => { diff --git a/rust-bins/duniter-launcher/src/main.rs b/rust-bins/duniter-launcher/src/main.rs index 67865daecf49f20eb74d552a2ecaa85f4b4bc65f..0c93e331ba835a7462e43bf4d756722a1e54a686 100644 --- a/rust-bins/duniter-launcher/src/main.rs +++ b/rust-bins/duniter-launcher/src/main.rs @@ -28,6 +28,7 @@ mod config; mod daemon; mod duniter_ts_args; mod sync; +mod wizard_gva; use anyhow::{anyhow, Result}; use daemonize_me::Daemon; @@ -168,7 +169,9 @@ enum WizardCommand { #[structopt(short)] p: Option<usize>, }, - #[structopt(display_order(1), alias = "network")] + #[structopt(display_order(1))] + Gva, + #[structopt(display_order(2), alias = "network")] Bma, } @@ -186,9 +189,6 @@ enum WS2PCommand { #[derive(StructOpt)] struct DuniterStartArgs { - /// Enable GVA API - #[structopt(long)] - gva: bool, /// Force to use the keypair of the given YAML file. File must contain `pub:` and `sec:` fields. #[structopt(long, parse(from_os_str), env("DUNITER_KEYFILE"))] keyfile: Option<PathBuf>, @@ -213,41 +213,45 @@ fn main() -> Result<()> { } else { let profile_path = get_profile_path(args.profile.as_deref())?; - let current_exe = std::env::current_exe()?; - let prod = current_exe == PathBuf::from(DUNITER_EXE_LINK_PATH) - || current_exe == PathBuf::from(DUNITER_EXE_PATH); + if let DuniterCommand::Wizard(WizardCommand::Gva) = args.command { + wizard_gva::wizard_gva(args.profile.as_deref(), profile_path) + } else { + let current_exe = std::env::current_exe()?; + let prod = current_exe == PathBuf::from(DUNITER_EXE_LINK_PATH) + || current_exe == PathBuf::from(DUNITER_EXE_PATH); - let duniter_ts_args = duniter_ts_args::gen_duniter_ts_args(&args, duniter_js_exe()?); + let duniter_ts_args = duniter_ts_args::gen_duniter_ts_args(&args, duniter_js_exe()?); - match args.command { - DuniterCommand::Restart => { - daemon::start(prod, &profile_path, &daemon::stop(&profile_path)?) - } - DuniterCommand::Start(_) | DuniterCommand::Webstart { .. } => { - daemon::start(prod, &profile_path, &duniter_ts_args) - } - DuniterCommand::Status => daemon::status(&profile_path), - DuniterCommand::Stop => { - daemon::stop(&profile_path)?; - Ok(()) - } - DuniterCommand::Logs => watch_logs(profile_path), - _ => { - ctrlc::set_handler(move || { - // This empty handler is necessary otherwise the Rust process is stopped immediately - // without waiting for the child process (duniter_js) to finish stopping. - })?; - let mut duniter_js_command = Command::new(get_node_path()?); - if prod { - duniter_js_command.current_dir(DUNITER_JS_CURRENT_DIR); + match args.command { + DuniterCommand::Restart => { + daemon::start(prod, &profile_path, &daemon::stop(&profile_path)?) + } + DuniterCommand::Start(_) | DuniterCommand::Webstart { .. } => { + daemon::start(prod, &profile_path, &duniter_ts_args) } - //println!("TMP duniter_ts_args={:?}", duniter_ts_args); - let exit_code_opt = duniter_js_command.args(duniter_ts_args).status()?.code(); - if let Some(exit_code) = exit_code_opt { - std::process::exit(exit_code); - } else { + DuniterCommand::Status => daemon::status(&profile_path), + DuniterCommand::Stop => { + daemon::stop(&profile_path)?; Ok(()) } + DuniterCommand::Logs => watch_logs(profile_path), + _ => { + ctrlc::set_handler(move || { + // This empty handler is necessary otherwise the Rust process is stopped immediately + // without waiting for the child process (duniter_js) to finish stopping. + })?; + let mut duniter_js_command = Command::new(get_node_path()?); + if prod { + duniter_js_command.current_dir(DUNITER_JS_CURRENT_DIR); + } + //println!("TMP duniter_ts_args={:?}", duniter_ts_args); + let exit_code_opt = duniter_js_command.args(duniter_ts_args).status()?.code(); + if let Some(exit_code) = exit_code_opt { + std::process::exit(exit_code); + } else { + Ok(()) + } + } } } } diff --git a/rust-bins/duniter-launcher/src/sync.rs b/rust-bins/duniter-launcher/src/sync.rs index ccedf568dad70d8c8be7763cac1e20c708b439b6..5e908cafa1adda896136d9bed9cd35b62a51409f 100644 --- a/rust-bins/duniter-launcher/src/sync.rs +++ b/rust-bins/duniter-launcher/src/sync.rs @@ -20,9 +20,6 @@ pub(crate) struct DuniterSyncArgs { /// Check all DUPB rules (very long). #[structopt(hidden(true), long)] cautious: bool, - /// Populate GVA DB (Necessary for the GVA api to work) - #[structopt(long)] - gva: bool, /// Allow to synchronize on nodes with local network IP address. #[structopt(hidden(true), long)] localsync: bool, @@ -63,9 +60,6 @@ pub(crate) fn gen_args(args: &DuniterSyncArgs, duniter_ts_args: &mut Vec<String> if args.cautious { duniter_ts_args.push("--cautious".into()); } - if args.gva { - duniter_ts_args.push("--gva".into()); - } if args.localsync { duniter_ts_args.push("--localsync".into()); } diff --git a/rust-bins/duniter-launcher/src/wizard_gva.rs b/rust-bins/duniter-launcher/src/wizard_gva.rs new file mode 100644 index 0000000000000000000000000000000000000000..27e9e237c86b8805319f407635450a927bda81f5 --- /dev/null +++ b/rust-bins/duniter-launcher/src/wizard_gva.rs @@ -0,0 +1,172 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +use crate::*; +use read_input::prelude::*; +use std::{ + net::{Ipv4Addr, Ipv6Addr}, + str::FromStr, +}; + +/* +struct GvaConf { + host: Option<String>, + port: Option<u16>, + path: Option<String>, + subscriptions_path: Option<String>, + remote_host: Option<String>, + remote_port: Option<u16>, + remote_path: Option<String>, + remote_subscriptions_path: Option<String>, + remote_tls: Option<bool>, +} +*/ + +pub(crate) fn wizard_gva(profile_name_opt: Option<&str>, profile_path: PathBuf) -> Result<()> { + let file_path = profile_path.join("conf.json"); + + if !file_path.exists() { + if let Some(profile_name) = profile_name_opt { + Command::new(duniter_js_exe()?) + .args(&["--mdb", profile_name, "config"]) + .status()?; + } else { + Command::new(duniter_js_exe()?).arg("config").status()?; + } + } + + let mut file = File::open(file_path.as_path())?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + + let mut conf_json = if contents.is_empty() { + serde_json::Value::Object(serde_json::Map::new()) + } else { + serde_json::Value::from_str(&contents)? + }; + + let conf_json_obj = conf_json + .as_object_mut() + .ok_or_else(|| anyhow::Error::msg("json conf must be an object"))?; + + let mut gva_conf = serde_json::Map::new(); + + // Enable GVA API? + let res = input().msg("Enable GVA API? [Y/n]").default('Y').get(); + let gva_enabled = res != 'n'; + gva_conf.insert("enabled".to_owned(), serde_json::Value::Bool(gva_enabled)); + + if gva_enabled { + // ip4 + let ip4 = input() + .msg("Listen to ip v4 ? [127.0.0.1]") + .default(Ipv4Addr::LOCALHOST) + .get(); + gva_conf.insert("ip4".to_owned(), serde_json::Value::String(ip4.to_string())); + // ip6 + let res = input().msg("Listen to ip v6? [Y/n]").default('Y').get(); + if res != 'n' { + let ip6 = input() + .msg("Enter ip v6: [::1]") + .default(Ipv6Addr::LOCALHOST) + .get(); + gva_conf.insert("ip6".to_owned(), serde_json::Value::String(ip6.to_string())); + } + // port + let port = input() + .msg("Listen to port ? [30901]") + .default(30901u16) + .get(); + gva_conf.insert( + "port".to_owned(), + serde_json::Value::Number(serde_json::Number::from(port)), + ); + // path + let path = input().msg("Path ? [gva]").default("gva".to_owned()).get(); + gva_conf.insert("path".to_owned(), serde_json::Value::String(path)); + // subscriptionsPath + let subscriptions_path = input() + .msg("Subscriptions path ? [gva-sub]") + .default("gva-sub".to_owned()) + .get(); + gva_conf.insert( + "subscriptionsPath".to_owned(), + serde_json::Value::String(subscriptions_path), + ); + // remoteHost + let res = input() + .msg("Define a remote host? [y/N]") + .default('N') + .get(); + if res == 'y' || res == 'Y' { + let remote_host = input().msg("Enter remote host:").get(); + gva_conf.insert( + "remoteHost".to_owned(), + serde_json::Value::String(remote_host), + ); + } + // remotePort + let res = input() + .msg("Define a remote port? [y/N]") + .default('N') + .get(); + if res == 'y' || res == 'Y' { + let remote_port = input() + .msg("Enter remote port ? [443]") + .default(443u16) + .get(); + gva_conf.insert( + "remotePort".to_owned(), + serde_json::Value::Number(serde_json::Number::from(remote_port)), + ); + } + // remotePath + let res = input() + .msg("Define a remote path? [y/N]") + .default('N') + .get(); + if res == 'y' || res == 'Y' { + let remote_path = input().msg("Enter remote path:").get(); + gva_conf.insert( + "remotePath".to_owned(), + serde_json::Value::String(remote_path), + ); + } + // remoteSubscriptionsPath + let res = input() + .msg("Define a remote subscriptions path? [y/N]") + .default('N') + .get(); + if res == 'y' || res == 'Y' { + let remote_path = input().msg("Enter remote subscriptions path:").get(); + gva_conf.insert( + "remoteSubscriptionsPath".to_owned(), + serde_json::Value::String(remote_path), + ); + } + } + + // Insert GVA json conf in global json conf + conf_json_obj.insert("gva".to_owned(), serde_json::Value::Object(gva_conf)); + + // Write new_conf + let new_conf_str = serde_json::to_string_pretty(&conf_json)?; + let mut file = File::create(file_path.as_path())?; + file.write_all(new_conf_str.as_bytes())?; + + println!("Configuration successfully updated."); + + Ok(()) +}