From f9c10ece4e6ff836dbb29cd354b9045083604e5e Mon Sep 17 00:00:00 2001 From: librelois <c@elo.tf> Date: Thu, 1 Oct 2020 23:34:40 +0200 Subject: [PATCH] [feat] create duniter launcher --- .cargo/config | 2 + .gitignore | 6 +- .gitlab-ci.yml | 6 +- .gitlab/test/check_indexes.sh | 6 +- Cargo.lock | 122 ++++++- Cargo.toml | 29 ++ app/modules/daemon.ts | 120 +------ app/modules/ws2p/index.ts | 6 +- bin/{duniter => duniter_js} | 0 doc/dev/setup_env_dev.md | 61 ++-- doc/use/manual_compilation.md | 29 +- duniter.sh | 52 --- neon/build.sh | 14 - neon/native/artifacts.json | 2 +- package.json | 10 +- release/Makefile | 6 +- release/arch/arm/build-arm.sh | 2 +- release/arch/linux/build-lin.sh | 6 +- release/docker/duniter.sh | 10 +- .../extra/completion/duniter_completion.bash | 2 - release/extra/debian/package/DEBIAN/postinst | 8 +- release/extra/debian/package/DEBIAN/prerm | 1 + release/extra/openrc/duniter.initd | 6 +- release/extra/systemd/duniter.service | 6 +- rust-bins/duniter-launcher/src/config.rs | 270 ++++++++++++++ rust-bins/duniter-launcher/src/daemon.rs | 112 ++++++ .../duniter-launcher/src/duniter_ts_args.rs | 145 ++++++++ rust-bins/duniter-launcher/src/main.rs | 334 ++++++++++++++++++ rust-bins/duniter-launcher/src/sync.rs | 85 +++++ rust-bins/xtask/Cargo.toml | 17 + rust-bins/xtask/src/main.rs | 145 ++++++++ server.ts | 4 +- test/integration/misc/cli.ts | 37 -- test/run.sh | 11 - 34 files changed, 1342 insertions(+), 330 deletions(-) create mode 100644 .cargo/config rename bin/{duniter => duniter_js} (100%) delete mode 100755 duniter.sh create mode 100644 rust-bins/duniter-launcher/src/config.rs create mode 100644 rust-bins/duniter-launcher/src/daemon.rs create mode 100644 rust-bins/duniter-launcher/src/duniter_ts_args.rs create mode 100644 rust-bins/duniter-launcher/src/main.rs create mode 100644 rust-bins/duniter-launcher/src/sync.rs create mode 100644 rust-bins/xtask/Cargo.toml create mode 100644 rust-bins/xtask/src/main.rs delete mode 100755 test/run.sh diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 000000000..35049cbcb --- /dev/null +++ b/.cargo/config @@ -0,0 +1,2 @@ +[alias] +xtask = "run --package xtask --" diff --git a/.gitignore b/.gitignore index cfedee3dd..4124c0cfe 100644 --- a/.gitignore +++ b/.gitignore @@ -46,14 +46,16 @@ app/**/*.d.ts neon/lib/*.d.ts test/**/*.d.ts -# files generated by neon tests -test2.bin.gz +# files generated by neon +neon/native/artifacts.json # rust binaries +bin/duniter neon/native/index.node target # files generated by rust tests neon/native/tests/*.txt neon/native/tests/wotb-* +test2.bin.gz **/*.wot diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index be1965504..d16501420 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,7 +11,7 @@ workflow: - changes: - .gitlab/**/* - app/**/* - - bin/duniter + - bin/duniter_js - neon/**/* - releases/**/* - rust-libs/**/* @@ -27,11 +27,13 @@ workflow: tags: - redshift before_script: + - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + - export PATH="$HOME/.cargo/bin:$PATH" + - export RUSTFLAGS="-D warnings" - export NVM_DIR="$HOME/.nvm" - . "$NVM_DIR/nvm.sh" - nvm install 10 - nvm use 10 - - export RUSTFLAGS="-D warnings" .cached_nvm: &cached_nvm diff --git a/.gitlab/test/check_indexes.sh b/.gitlab/test/check_indexes.sh index 7bc868a6f..52f1bfa49 100755 --- a/.gitlab/test/check_indexes.sh +++ b/.gitlab/test/check_indexes.sh @@ -18,7 +18,7 @@ checksum_test() { local correct_hash=$2 local db=$3 echo "Checking $table's checksum..." - bin/duniter --mdb ${db} dump table "$table" > "$DUMP_DIR/$table" + bin/duniter_js --mdb ${db} dump table "$table" > "$DUMP_DIR/$table" result_hash=`sha1sum "$DUMP_DIR/$table" | grep -Po ".* " | grep -Po "[a-f0-9]+"` # rm -f "$DUMP_DIR/$table" if [ "$result_hash" == "$correct_hash" ]; then @@ -33,8 +33,8 @@ sync_data() { local db=$1 local target=$2 local target_block=$3 - local reset_data="bin/duniter --mdb ${db} reset all" - local sync="bin/duniter --mdb ${db} sync ${target} --nointeractive ${target_block}" + local reset_data="bin/duniter_js --mdb ${db} reset all" + local sync="bin/duniter_js --mdb ${db} sync ${target} --nointeractive ${target_block}" echo "$reset_data" ${reset_data} echo "$sync" diff --git a/Cargo.lock b/Cargo.lock index 5a170c12c..850a71c53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "anyhow" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" + [[package]] name = "atty" version = "0.2.14" @@ -70,6 +88,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + [[package]] name = "beef" version = "0.4.4" @@ -92,6 +116,17 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "blake2b_simd" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.7.3" @@ -173,6 +208,12 @@ dependencies = [ "vec_map", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "crc32fast" version = "1.2.0" @@ -241,6 +282,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "697c714f50560202b1f4e2e09cd50a421881c83e9025db75d15f276616f04f40" +[[package]] +name = "ctrlc" +version = "3.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0b676fa23f995faf587496dcd1c80fead847ed58d2da52ac1caca9a72790dd2" +dependencies = [ + "nix", + "winapi", +] + [[package]] name = "daemonize-me" version = "0.3.1" @@ -261,6 +312,26 @@ dependencies = [ "generic-array", ] +[[package]] +name = "dirs" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -344,9 +415,15 @@ dependencies = [ [[package]] name = "duniter-launcher" -version = "0.1.0" +version = "1.9.0-alpha1" dependencies = [ + "anyhow", + "ctrlc", "daemonize-me", + "dirs", + "log", + "logwatcher", + "nix", "rusty-hook", "structopt", ] @@ -375,7 +452,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7315898eda74bf7c9e825f9afae1c9f16debb401fdb7d658fb851fb00a6260c" dependencies = [ - "base64", + "base64 0.11.0", "bs58", "byteorder", "ring", @@ -391,7 +468,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "287b23c4281016278b47b80baf67186ce756dde738070cc7a41297da5e81dec1" dependencies = [ - "base64", + "base64 0.11.0", "bs58", "byteorder", "cryptoxide", @@ -563,13 +640,19 @@ checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" [[package]] name = "log" -version = "0.4.8" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ "cfg-if", ] +[[package]] +name = "logwatcher" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0925aed5b12ed59857f438d25a910cf051dbcd4107907be1e7abf6c44ec903" + [[package]] name = "maplit" version = "1.0.2" @@ -881,6 +964,23 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "redox_users" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +dependencies = [ + "getrandom", + "redox_syscall", + "rust-argon2", +] + [[package]] name = "regex" version = "1.3.7" @@ -914,6 +1014,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "rust-argon2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" +dependencies = [ + "base64 0.12.3", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + [[package]] name = "rustc-demangle" version = "0.1.16" diff --git a/Cargo.toml b/Cargo.toml index 56e722261..5cf345d30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,35 @@ +[package] +authors = ["elois <elois@duniter.org>"] +description = "Duniter launcher." +edition = "2018" +keywords = ["duniter", "launcher"] +license = "AGPL-3.0" +name = "duniter-launcher" +repository = "https://git.duniter.org/nodes/typescript/duniter" +version = "1.9.0-alpha1" + +[[bin]] +bench = false +path = "rust-bins/duniter-launcher/src/main.rs" +name = "duniter" + +[dependencies] +anyhow = "1.0.32" +ctrlc = "3.1.6" +daemonize-me = "0.3.1" +dirs = "3.0.1" +log = "0.4.11" +logwatcher = "0.1.1" +nix = "0.17.0" +structopt = "0.3.18" + +[dev-dependencies] +rusty-hook = "0.11.2" + [workspace] members = [ "neon/native", + "rust-bins/xtask", "rust-libs/dubp-wot" ] diff --git a/app/modules/daemon.ts b/app/modules/daemon.ts index 7604e6043..ffd19949d 100644 --- a/app/modules/daemon.ts +++ b/app/modules/daemon.ts @@ -13,12 +13,8 @@ import { ConfDTO } from "../lib/dto/ConfDTO"; import { Server } from "../../server"; -import { Directory, RealFS } from "../lib/system/directory"; import { ExitCodes } from "../lib/common-libs/exit-codes"; -const constants = require("../lib/constants"); -const Tail = require("tail").Tail; - module.exports = { duniter: { cliOptions: [ @@ -48,51 +44,6 @@ module.exports = { }, cli: [ - { - name: "start", - desc: "Starts Duniter as a daemon (background task).", - logs: false, - onConfiguredExecute: async ( - server: Server, - conf: ConfDTO, - program: any, - params: any - ) => { - await server.checkConfig(); - const daemon = server.getDaemon("direct_start", "start"); - await startDaemon(daemon); - }, - }, - { - name: "stop", - desc: "Stops Duniter daemon if it is running.", - logs: false, - onConfiguredExecute: async ( - server: Server, - conf: ConfDTO, - program: any, - params: any - ) => { - const daemon = server.getDaemon(); - await stopDaemon(daemon); - }, - }, - { - name: "restart", - desc: "Stops Duniter daemon and restart it.", - logs: false, - onConfiguredExecute: async ( - server: Server, - conf: ConfDTO, - program: any, - params: any - ) => { - await server.checkConfig(); - const daemon = server.getDaemon("direct_start", "restart"); - await stopDaemon(daemon); - await startDaemon(daemon); - }, - }, { name: "status", desc: "Get Duniter daemon status.", @@ -114,24 +65,6 @@ module.exports = { } }, }, - { - name: "logs", - desc: "Follow duniter logs.", - logs: false, - onConfiguredExecute: async ( - server: Server, - conf: ConfDTO, - program: any, - params: any - ) => { - printTailAndWatchFile( - Directory.INSTANCE_HOMELOG_FILE, - constants.NB_INITIAL_LINES_TO_SHOW - ); - // Never ending command - return new Promise((res) => null); - }, - }, { name: "direct_start", desc: "Start Duniter node with direct output, non-daemonized.", @@ -142,6 +75,7 @@ module.exports = { params: any, startServices: any ) => { + process.title = program.mdb || "duniter_default"; const logger = server.logger; logger.info(">> Server starting..."); @@ -171,55 +105,3 @@ function ServerService(server: Server) { server.stopService = () => Promise.resolve(); return server; } - -function startDaemon(daemon: any) { - return new Promise((resolve, reject) => - daemon.start((err: any) => { - if (err) return reject(err); - resolve(); - }) - ); -} - -function stopDaemon(daemon: any) { - return new Promise((resolve, reject) => - daemon.stop((err: any) => { - err && console.error(err); - if (err) return reject(err); - resolve(); - }) - ); -} - -async function printTailAndWatchFile(file: any, tailSize: number) { - const fs = RealFS(); - if (await fs.fsExists(file)) { - const content = await fs.fsReadFile(file); - const lines = content.split("\n"); - const from = Math.max(0, lines.length - tailSize); - const lastLines = lines.slice(from).join("\n"); - console.log(lastLines); - } - watchFile(file); -} - -function watchFile(file: any) { - const tail = new Tail(file); - - // Specific errors handling - process.on("uncaughtException", (err: any) => { - if (err.code === "ENOENT") { - console.error("EXCEPTION: ", err.message); - setTimeout(() => watchFile(file), 1000); // Wait a second - } - }); - - // On new line - tail.on("line", function (data: any) { - console.log(data); - }); - - tail.on("error", function (error: any) { - console.error("ERROR: ", error); - }); -} diff --git a/app/modules/ws2p/index.ts b/app/modules/ws2p/index.ts index d46d3d3bb..be8575a57 100644 --- a/app/modules/ws2p/index.ts +++ b/app/modules/ws2p/index.ts @@ -64,8 +64,8 @@ export const WS2PDependency = { }, { value: "--ws2p-prefered-rm <pubkey>", desc: "Remove prefered node." }, { - value: "--ws2p-prefered-only <pubkey>", - desc: "Only connect to prefered node.", + value: "--ws2p-prefered-only", + desc: "Only connect to prefered nodes.", }, { value: "--ws2p-privileged-add <pubkey>", @@ -73,7 +73,7 @@ export const WS2PDependency = { }, { value: "--ws2p-privileged-rm <pubkey>", desc: "Remove a privileged." }, { - value: "--ws2p-privileged-only <pubkey>", + value: "--ws2p-privileged-only", desc: "Accept only connections from a privileged node.", }, ], diff --git a/bin/duniter b/bin/duniter_js similarity index 100% rename from bin/duniter rename to bin/duniter_js diff --git a/doc/dev/setup_env_dev.md b/doc/dev/setup_env_dev.md index ba8c5e547..002b58f1d 100644 --- a/doc/dev/setup_env_dev.md +++ b/doc/dev/setup_env_dev.md @@ -10,14 +10,12 @@ Authors: elois ## In a post-it ```bash -nvm use 10 git clone git@git.duniter.org:nodes/typescript/duniter.git cd duniter -npm install -bin/duniter start +cargo xtask build +./target/release/duniter start ``` - ## Step by step ### Prerequisites @@ -26,31 +24,35 @@ To develop on Duniter, there is currently the following requirement: - A computer with GNU/Linux or Mac as operating system - Build essential tools -- curl +- wget - git (apt-get install git) -- Nvm +- Rust And preferably an IDE that supports [Typescript] and [Rust] well. #### Install Prerequisites -Nvm: - - wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash +##### Rust -### Build the project +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +``` -#### Clone de repository +Them add `~/.cargo/bin` on your PATH. - git clone https://git.duniter.org/nodes/typescript/duniter +If you use `bash`: -#### Install and use the correct version of nodejs +```bash +echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> .bashrc +``` -Check the required node version in the `package.json` file on line 5. +### Build the project -If, for example, version 10 is expected, install and select it with the following command: +#### Clone the repository - nvm install 10 && nvm use 10 +```bash +git clone https://git.duniter.org/nodes/typescript/duniter +``` #### Build the project and play automated tests @@ -58,22 +60,27 @@ If, for example, version 10 is expected, install and select it with the followin Command to compile : - npm install +```bash +cargo xtask build +``` + +The binary to run duniter-server is then found here: `target/release/duniter` **WARNING**: playing automated tests takes a lot of resources on your machine and those for several minutes, don't do anything else at the same time! Command to play automated tests: - npm test +```bash +cargo xtask test +``` If all the tests are successful, the command ends like this: - ```bash - 777 passing (40s) - 19 pending - - Done in 43.80s. - ``` +```bash + 777 passing (40s) + 19 pending +Done in 43.80s. +``` ### Configure your IDE @@ -88,14 +95,16 @@ For the Rust part, I strongly recommend the following plugins: - CodeLLDB (identifier: `vadimcn.vscode-lldb`) - rust-analyzer (identifier: `matklad.rust-analyzer`) -The recommended ide configuration can be found in `doc/dev/vscode/settings.json`. +The recommended IDE configuration can be found in `doc/dev/vscode/settings.json`. ##### Debugger configuration The recommended debugger configuration can be found in `doc/dev/.vscode/launch.json`. For import it: - cp doc/dev/vscode/launch.json .vscode/launch.json +```bash +cp doc/dev/vscode/launch.json .vscode/launch.json +``` You can then adapt it according to your preferences for use :) diff --git a/doc/use/manual_compilation.md b/doc/use/manual_compilation.md index e3be4c9aa..14767b8e3 100644 --- a/doc/use/manual_compilation.md +++ b/doc/use/manual_compilation.md @@ -15,7 +15,7 @@ To compile Duniter manually, there is currently the following requirement: - Build essential tools - curl - tar or unzip or git (to download and extract source code) -- Nvm +- Rust ### Get source code @@ -60,44 +60,25 @@ Depend on your distribution: TODO: If you know how to install build essential tools for other gnu/linux distributions or for mac, you can complete this documentation and submit a merge request. -#### Nvm +#### Rust -Nvm: - - wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y ## Build the project Go to the root of the folder where you extracted the source code (or possibly cloned from git). -### Install and use the correct version of nodejs - -Check the required node version in the `package.json` file on line 5. - -If, for example, version 10 is expected, install and select it with the following command: - - nvm install 10 && nvm use 10 - -### Optionally add GUI - -The graphical user interface (GUI) is optional because it is possible to do everything from the command line. -If you wish to have the GUI, you must add it (before compiling) with the following command: - - npm add duniter-ui - -### Compile - **WARNING**: the compilation of the project requires a lot of resources on your machine, and several long minutes, don't do anything else at the same time! Command to compile : - NEON_BUILD_RELEASE=true npm install && npm prune --production + cargo xtask build --production ### Set autocompletion To install or update Duniter's command auto-completion: - cp release/extra/completion/duniter_completion.bash /etc/bash_completion.d/ + bin/duniter completions bash > /etc/bash_completion.d/duniter_completion.bash ### Run on command line diff --git a/duniter.sh b/duniter.sh deleted file mode 100755 index 9fd048d49..000000000 --- a/duniter.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash - -########################## -# DUNITER EXECUTABLE -# -# Wraps bin/duniter that is called with Node.js -# - -DEB_PACKAGING= - -if [[ $DEB_PACKAGING ]]; then - DUNITER_DIR=/opt/duniter/ -fi - -duniter() { - - local NODE - local LOGS_FILE - - if [ -z "$DEV_MODE" ]; then - - ### Production mode - if [[ -d $DUNITER_DIR/node ]]; then - NODE=$DUNITER_DIR/node/bin/node - else - echo "Node.js is not embedded in this version of Duniter" - return - fi; - else - - ### Cheating with DEV mode - DUNITER_DIR=`pwd` - NODE=node - fi - - VERSION=`$NODE -v` - - if [[ $VERSION != v10* ]]; then - echo "$NODE v10 is required"; - else - - # Calls duniter JS command - cd $DUNITER_DIR - $NODE "$DUNITER_DIR/bin/duniter" "$@" - - fi; -} - -# If the script was launched with parameters, try to launch the Duniter command -if [ ! -z $1 ]; then - duniter "$@" -fi diff --git a/neon/build.sh b/neon/build.sh index a71a80952..c14123d47 100755 --- a/neon/build.sh +++ b/neon/build.sh @@ -1,19 +1,5 @@ #!/bin/sh -if [ -z "${DUNITER_FAST_BUILD}" ]; then - if [ "$(command -v rustup)" ]; then - rustup update stable - elif [ ! "$(command -v cargo)" ]; then - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - export PATH="$HOME/.cargo/bin:$PATH" - fi -else - echo "WARNING: you have disabled the automatic update of Rust, remember to update Rust regularly with command \"rustup update\"." -fi - -rustc --version -cargo --version - cd neon if [ "${NEON_BUILD_RELEASE}" = "true" ] || [ "${NODE_ENV}" = "production" ]; then diff --git a/neon/native/artifacts.json b/neon/native/artifacts.json index 837b6fb09..5c5d995cc 100644 --- a/neon/native/artifacts.json +++ b/neon/native/artifacts.json @@ -1 +1 @@ -{"active":"debug","targets":{"debug":{"rustc":"","env":{"npm_config_target":null,"npm_config_arch":null,"npm_config_target_arch":null,"npm_config_disturl":null,"npm_config_runtime":null,"npm_config_build_from_source":null,"npm_config_devdir":null}},"release":{"rustc":"","env":{"npm_config_target":null,"npm_config_arch":null,"npm_config_target_arch":null,"npm_config_disturl":null,"npm_config_runtime":null,"npm_config_build_from_source":null,"npm_config_devdir":null}}}} \ No newline at end of file +{"active":"debug","targets":{"debug":{"rustc":"","env":{"npm_config_target":null,"npm_config_arch":null,"npm_config_target_arch":null,"npm_config_disturl":null,"npm_config_runtime":null,"npm_config_build_from_source":null,"npm_config_devdir":null}}}} \ No newline at end of file diff --git a/package.json b/package.json index f42fe4445..31b742349 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "private": false, "description": "Crypto-currency software allowing to build P2P free currencies", "main": "index.js", - "node-main": "./bin/duniter", + "node-main": "./bin/duniter_js", "window": { "icon": "duniter.png", "title": "v1.8.1", @@ -28,10 +28,8 @@ "tsc": "tsc", "tscw": "tsc -w", "doc": "typedoc --out typedoc/ index.ts app/ --mode file --readme README.md --includeDeclarations --sourcefile-url-prefix \"https://git.duniter.org/nodes/typescript/duniter/blob/loki/\"", - "test": "./test/run.sh", - "test:rs": "./test/run.sh rs", - "test:ts": "./test/run.sh ts", - "start": "node bin/duniter start", + "test": "nyc --reporter html mocha", + "start": "cargo run -- start", "build": "./neon/build.sh && cd.. && tsc && cd \"../node_modules/duniter-ui\" && npm install && npm run build", "install": "./neon/build.sh", "lint": "lint-staged", @@ -138,7 +136,7 @@ }, "peerDependencies": {}, "bin": { - "duniter": "./bin/duniter" + "duniter": "./bin/duniter_js" }, "lint-staged": { "app/**/*.{js,jsx,ts,tsx,md,html,css,scss}": [ diff --git a/release/Makefile b/release/Makefile index 6d32bbb7e..3b9617d92 100644 --- a/release/Makefile +++ b/release/Makefile @@ -85,6 +85,8 @@ base-gui: $(call nodedep,duniter-ui) base # Build the base .PHONY: base base: | node_modules + @cargo build --release -p duniter-launcher + @mv target/release/duniter bin/duniter index.html: $(call nodedep,duniter-ui) $(addprefix node_modules/,$(leveldown.COMPILED) $(sqlite3.COMPILED)) @sed -i "s/\"main\": \"index.js\",/\"main\": \"index.html\",/" package.json @@ -121,10 +123,12 @@ endif @rm -rf $(DEV_FILES) @mv release/extra . @rm -rf coverage coverage.* release test - @rm -rf target @rm -rf neon/native/target @rm -rf node_modules/sqlite3/build @rm -rf node_modules/duniter-ui/node_modules + @rm -rf rust-bins + @rm -rf rust-libs + @rm -rf target ###### # We are on main project diff --git a/release/arch/arm/build-arm.sh b/release/arch/arm/build-arm.sh index c7b903b6a..0c28b41fa 100755 --- a/release/arch/arm/build-arm.sh +++ b/release/arch/arm/build-arm.sh @@ -64,7 +64,7 @@ mkdir -p "$RELEASES" cd "$DOWNLOADS/duniter" echo "Build Duniter server with GUI..." -make -C release server-gui clean +make -C release ADD_DEBUG=N server-gui clean mv "$DOWNLOADS/duniter/work" "$RELEASES/duniter" cd ${RELEASES}/duniter echo "Copying Nodejs" diff --git a/release/arch/linux/build-lin.sh b/release/arch/linux/build-lin.sh index d0487395e..d72647d69 100755 --- a/release/arch/linux/build-lin.sh +++ b/release/arch/linux/build-lin.sh @@ -116,11 +116,11 @@ cp -r ~/.nvm/versions/node/${NVER}/ node-${NVER}-linux-x64 # ----------- pushd "${ROOT}" -make -C release DEST="${RELEASES_SUBDIR}/duniter" base-gui || exit 1 +make -C release ADD_DEBUG=N DEST="${RELEASES_SUBDIR}/duniter" base-gui || exit 1 cp -pr "${RELEASES}/duniter" "${RELEASES}/desktop_" || exit 1 -make -C release DEST="${RELEASES_SUBDIR}/desktop_" desktop clean || exit 1 +make -C release ADD_DEBUG=N DEST="${RELEASES_SUBDIR}/desktop_" desktop clean || exit 1 cp -pr "${RELEASES}/duniter" "${RELEASES}/server_" || exit 1 -make -C release DEST="${RELEASES_SUBDIR}/server_" server-gui clean || exit 1 +make -C release ADD_DEBUG=N DEST="${RELEASES_SUBDIR}/server_" server-gui clean || exit 1 popd # -------------------------------- diff --git a/release/docker/duniter.sh b/release/docker/duniter.sh index 270d084bb..e27a0d153 100644 --- a/release/docker/duniter.sh +++ b/release/docker/duniter.sh @@ -6,16 +6,14 @@ if [[ -z ${1} ]]; then fi # Options -DUNITER_OPTS= -DUNITER_OPTS="${DUNITER_OPTS} --webmhost 0.0.0.0" -DUNITER_OPTS="${DUNITER_OPTS} --home /var/lib/duniter" -DUNITER_OPTS="${DUNITER_OPTS} --mdb duniter_default" +export DUNITER_WEB_UI_HOST = "0.0.0.0" + # Key file found if [[ -f /etc/duniter/key.yml ]]; then - DUNITER_OPTS="${DUNITER_OPTS} --keyfile /etc/duniter/keys.yml" + export DUNITER_KEYFILE="/etc/duniter/keys.yml" fi # Start duniter cd /duniter/duniter/ -bin/duniter ${DUNITER_OPTS} "$@" +bin/duniter --home /var/lib/duniter "$@" diff --git a/release/extra/completion/duniter_completion.bash b/release/extra/completion/duniter_completion.bash index 9333250df..d6572f2af 100644 --- a/release/extra/completion/duniter_completion.bash +++ b/release/extra/completion/duniter_completion.bash @@ -85,8 +85,6 @@ direct_webstart \ --submit-host \ --submit-port \ --at \ ---salt \ ---passwd \ --keyN \ --keyr \ --keyp \ diff --git a/release/extra/debian/package/DEBIAN/postinst b/release/extra/debian/package/DEBIAN/postinst index 0880595b7..930075ae9 100755 --- a/release/extra/debian/package/DEBIAN/postinst +++ b/release/extra/debian/package/DEBIAN/postinst @@ -13,7 +13,6 @@ fi # Duniter-Desktop if [[ -f $DUN_SOURCES/duniter-desktop ]]; then ln -s $DUN_SOURCES/duniter-desktop /usr/bin/duniter-desktop - sed -i "s/DEB_PACKAGING=.*/DEB_PACKAGING=true/g" $DUN_SOURCES/duniter.sh # Links for Node + NPM cd $DUN_SOURCES cd bin @@ -26,9 +25,10 @@ fi # Duniter CLI executes with embedded node if [[ -d $DUN_SOURCES/node ]]; then chmod 755 $DUN_SOURCES/bin/duniter - sed -i "s/usr\/bin\/env node/opt\/duniter\/node\/bin\/node/g" $DUN_SOURCES/bin/duniter - sed -i "s/DEB_PACKAGING=.*/DEB_PACKAGING=true/g" $DUN_SOURCES/duniter.sh - ln -s $DUN_SOURCES/duniter.sh /usr/bin/duniter -f + chmod 755 $DUN_SOURCES/bin/duniter_js + sed -i "s/usr\/bin\/env node/opt\/duniter\/node\/bin\/node/g" $DUN_SOURCES/bin/duniter_js + ln -s $DUN_SOURCES/bin/duniter /usr/bin/duniter -f + ln -s $DUN_SOURCES/bin/duniter_js /usr/bin/duniter_js -f cd $DUN_SOURCES cd node/bin/ ln -s ../lib/node_modules/npm/bin/npm-cli.js ./npm -f diff --git a/release/extra/debian/package/DEBIAN/prerm b/release/extra/debian/package/DEBIAN/prerm index cfa07fcd9..3a6bf2c78 100755 --- a/release/extra/debian/package/DEBIAN/prerm +++ b/release/extra/debian/package/DEBIAN/prerm @@ -1,5 +1,6 @@ #!/bin/bash [[ -f /usr/bin/duniter ]] && rm /usr/bin/duniter +[[ -f /usr/bin/duniter_js ]] && rm /usr/bin/duniter_js [[ -f /usr/bin/duniter-desktop ]] && rm -f /usr/bin/duniter-desktop [[ -d /opt/duniter ]] && rm -Rf /opt/duniter diff --git a/release/extra/openrc/duniter.initd b/release/extra/openrc/duniter.initd index ef29b84ee..5a13c8cb5 100644 --- a/release/extra/openrc/duniter.initd +++ b/release/extra/openrc/duniter.initd @@ -21,7 +21,7 @@ fi if [[ ! -z ${DUNITER_KEYS} ]] && [[ -r ${DUNITER_KEYS} ]]; then command_args="${command_args} --keyfile \"${DUNITER_KEYS}\"" fi -command_args="${command_args} --home \"${DUNITER_HOME}\" --mdb \"${DUNITER_DATA}\"" +command_args="--home \"${DUNITER_HOME}\" --mdb \"${DUNITER_DATA}\" ${command_args}" start_stop_daemon_args="--user \"${DUNITER_USER}\":\"${DUNITER_GROUP}\" ${DUNITER_SSD_OPTIONS}" description="Duniter node" @@ -30,7 +30,7 @@ depend() { } status() { - if ${command} status --home "${DUNITER_HOME}" --mdb "${DUNITER_DATA}" | grep -q "is running"; then + if ${command} --home "${DUNITER_HOME}" --mdb "${DUNITER_DATA}" status | grep -q "is running"; then einfo "status: started" return 0 else @@ -46,5 +46,5 @@ status() { } stop() { - ${command} stop --home "${DUNITER_HOME}" --mdb "${DUNITER_DATA}" + ${command} --home "${DUNITER_HOME}" --mdb "${DUNITER_DATA}" stop } diff --git a/release/extra/systemd/duniter.service b/release/extra/systemd/duniter.service index 88cbefcf1..f8c1426ef 100644 --- a/release/extra/systemd/duniter.service +++ b/release/extra/systemd/duniter.service @@ -13,9 +13,9 @@ Environment="DUNITER_OPTS=" Group=duniter User=duniter Type=forking -ExecStart=/usr/bin/duniter ${DUNITER_WEB}start --home ${DUNITER_HOME} --mdb ${DUNITER_DATA} $DUNITER_OPTS -ExecReload=/usr/bin/duniter ${DUNITER_WEB}restart --home ${DUNITER_HOME} --mdb ${DUNITER_DATA} $DUNITER_OPTS -ExecStop=/usr/bin/duniter stop --home ${DUNITER_HOME} --mdb ${DUNITER_DATA} +ExecStart=/usr/bin/duniter --home ${DUNITER_HOME} --mdb ${DUNITER_DATA} ${DUNITER_WEB}start $DUNITER_OPTS +ExecReload=/usr/bin/duniter --home ${DUNITER_HOME} --mdb ${DUNITER_DATA} ${DUNITER_WEB}restart $DUNITER_OPTS +ExecStop=/usr/bin/duniter --home ${DUNITER_HOME} --mdb ${DUNITER_DATA} stop Restart=on-failure [Install] diff --git a/rust-bins/duniter-launcher/src/config.rs b/rust-bins/duniter-launcher/src/config.rs new file mode 100644 index 000000000..25acdd272 --- /dev/null +++ b/rust-bins/duniter-launcher/src/config.rs @@ -0,0 +1,270 @@ +// 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 std::str::FromStr; + +use crate::*; + +#[derive(Debug)] +struct Percent(pub usize); + +impl FromStr for Percent { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let usize_: usize = s.parse()?; + if usize_ <= 100 { + Ok(Self(usize_)) + } else { + Err(anyhow!("A percentage should be <= 100 !")) + } + } +} + +#[derive(StructOpt)] +pub(crate) struct DuniterConfigArgs { + /// Percent of CPU usage for proof-of-work computation + #[structopt(long)] + cpu: Option<Percent>, + /// Prefix node id for the first character of nonce + #[structopt(long)] + prefix: Option<usize>, + /// Enable BMA API + #[structopt(long, display_order(0))] + bma: bool, + /// Disable BMA API + #[structopt(long, display_order(1), alias = "nobma", conflicts_with("bma"))] + no_bma: bool, + /// Use UPnP to open BMA remote port. + #[structopt(long, display_order(2), alias = "upnp")] + bma_upnp: bool, + /// Do not use UPnP to open BMA remote port. + #[structopt(long, display_order(3), alias = "noupnp", conflicts_with("bma-upnp"))] + bma_no_upnp: bool, + /// Enable WS2P Public access. + #[structopt(long, display_order(4))] + ws2p_public: bool, + /// Disable WS2P Public access. + #[structopt( + long, + display_order(5), + alias = "ws2p-nopublic", + conflicts_with("ws2p-public") + )] + ws2p_no_public: bool, + /// Use UPnP to open WS2P remote port. + #[structopt(long, display_order(6))] + ws2p_upnp: bool, + /// Do not use UPnP to open WS2P remote port. + #[structopt( + long, + display_order(7), + alias = "ws2p-noupnp", + conflicts_with("ws2p-upnp") + )] + ws2p_no_upnp: bool, + /// WS2P host to listen to. + #[structopt(long)] + ws2p_host: Option<String>, + /// WS2P port to listen to. + #[structopt(long)] + ws2p_port: Option<u16>, + /// WS2P availabily host. + #[structopt(long)] + ws2p_remote_host: Option<String>, + /// WS2P availabily port. + #[structopt(long)] + ws2p_remote_port: Option<u16>, + /// WS2P availabily path. + #[structopt(long)] + ws2p_remote_path: Option<String>, + /// Maximum outcoming connections count. + #[structopt(long)] + ws2p_max_private: Option<u8>, + /// Maximum incoming connections count. + #[structopt(long)] + ws2p_max_public: Option<u8>, + + // Hidden options + /// Number of cores uses for proof-of-work computation + #[structopt(long, hidden(true))] + nb_cores: Option<usize>, + /// Enable WS2P Private. + #[structopt(long, hidden(true))] + ws2p_private: bool, + /// Disable WS2P Private. + #[structopt( + long, + hidden(true), + alias = "ws2p-noprivate", + conflicts_with("ws2p-private") + )] + ws2p_no_private: bool, + /// Add a preferred node to connect to through private access. + #[structopt(long, hidden(true))] + ws2p_prefered_add: Option<String>, + /// Remove preferred node. + #[structopt(long, hidden(true))] + ws2p_prefered_rm: Option<String>, + /// Only connect to preferred nodes. + #[structopt(long, hidden(true))] + ws2p_prefered_only: bool, + /// Add a privileged node to for our public access. + #[structopt(long, hidden(true))] + ws2p_privileged_add: Option<String>, + /// Remove privileged node. + #[structopt(long, hidden(true))] + ws2p_privileged_rm: Option<String>, + /// Accept only connections from a privileged node. + #[structopt(long, hidden(true))] + ws2p_privileged_only: bool, + /// Add given endpoint to the list of endpoints of this node. + #[structopt(long, hidden(true))] + addep: Option<String>, + /// Remove given endpoint to the list of endpoints of this node. + #[structopt(long, hidden(true))] + remep: Option<String>, + /// Use Socks Proxy for WS2P Private + #[structopt(long, hidden(true), alias = "socks-proxy")] + ws2p_socks_proxy: Option<String>, + /// Use Tor Socks Proxy + #[structopt(long, hidden(true))] + tor_proxy: Option<String>, + /// Method for reaching an clear endpoint + #[structopt(long, hidden(true), possible_values = &["clear", "tor", "none"])] + reaching_clear_ep: Option<String>, + /// Remove all proxies + #[structopt(long, hidden(true))] + rm_proxies: bool, + /// Force duniter to contact endpoint tor (if you redirect the traffic to tor yourself) + #[structopt(long, hidden(true))] + force_tor: bool, +} + +pub(crate) fn gen_args(args: &DuniterConfigArgs, duniter_js_args: &mut Vec<String>) { + if let Some(Percent(cpu_percent)) = args.cpu { + duniter_js_args.push("--cpu".into()); + duniter_js_args.push(cpu_percent.to_string()); + } + if let Some(nb_cores) = args.nb_cores { + duniter_js_args.push("--nb-cores".into()); + duniter_js_args.push(nb_cores.to_string()); + } + if let Some(prefix) = args.prefix { + duniter_js_args.push("--prefix".into()); + duniter_js_args.push(prefix.to_string()); + } + if args.bma { + duniter_js_args.push("--bma".into()); + } else if args.no_bma { + duniter_js_args.push("--nobma".into()); + } + if args.bma_upnp { + duniter_js_args.push("--upnp".into()); + } else if args.bma_no_upnp { + duniter_js_args.push("--noupnp".into()); + } + if args.ws2p_upnp { + duniter_js_args.push("--ws2p-upnp".into()); + } else if args.ws2p_no_upnp { + duniter_js_args.push("--ws2p-noupnp".into()); + } + if args.ws2p_private { + duniter_js_args.push("--ws2p-private".into()); + } else if args.ws2p_no_private { + duniter_js_args.push("--ws2p-noprivate".into()); + } + if args.ws2p_public { + duniter_js_args.push("--ws2p-public".into()); + } else if args.ws2p_no_public { + duniter_js_args.push("--ws2p-nopublic".into()); + } + if let Some(ref ws2p_host) = args.ws2p_host { + duniter_js_args.push("--ws2p-host".into()); + duniter_js_args.push(ws2p_host.into()); + } + if let Some(ws2p_port) = args.ws2p_port { + duniter_js_args.push("--ws2p-port".into()); + duniter_js_args.push(ws2p_port.to_string()); + } + if let Some(ref ws2p_remote_host) = args.ws2p_remote_host { + duniter_js_args.push("--ws2p-remote-host".into()); + duniter_js_args.push(ws2p_remote_host.into()); + } + if let Some(ws2p_remote_port) = args.ws2p_remote_port { + duniter_js_args.push("--ws2p-remote-port".into()); + duniter_js_args.push(ws2p_remote_port.to_string()); + } + if let Some(ref ws2p_remote_path) = args.ws2p_remote_path { + duniter_js_args.push("--ws2p-remote-path".into()); + duniter_js_args.push(ws2p_remote_path.into()); + } + if let Some(ref ws2p_max_private) = args.ws2p_max_private { + duniter_js_args.push("--ws2p-max-private".into()); + duniter_js_args.push(ws2p_max_private.to_string()); + } + if let Some(ref ws2p_max_public) = args.ws2p_max_public { + duniter_js_args.push("--ws2p-max-public".into()); + duniter_js_args.push(ws2p_max_public.to_string()); + } + if let Some(ref ws2p_prefered_add) = args.ws2p_prefered_add { + duniter_js_args.push("--ws2p-prefered-add".into()); + duniter_js_args.push(ws2p_prefered_add.to_string()); + } + if let Some(ref ws2p_prefered_rm) = args.ws2p_prefered_rm { + duniter_js_args.push("--ws2p-prefered-rm".into()); + duniter_js_args.push(ws2p_prefered_rm.to_string()); + } + if args.ws2p_prefered_only { + duniter_js_args.push("--ws2p-prefered-only".into()); + } + if let Some(ref ws2p_privileged_add) = args.ws2p_privileged_add { + duniter_js_args.push("--ws2p-privileged-add".into()); + duniter_js_args.push(ws2p_privileged_add.to_string()); + } + if let Some(ref ws2p_privileged_rm) = args.ws2p_privileged_rm { + duniter_js_args.push("--ws2p-privileged-rm".into()); + duniter_js_args.push(ws2p_privileged_rm.to_string()); + } + if args.ws2p_privileged_only { + duniter_js_args.push("--ws2p-privileged-only".into()); + } + if let Some(ref addep) = args.addep { + duniter_js_args.push("--addep".into()); + duniter_js_args.push(addep.into()); + } + if let Some(ref remep) = args.remep { + duniter_js_args.push("--remep".into()); + duniter_js_args.push(remep.into()); + } + if let Some(ref ws2p_socks_proxy) = args.ws2p_socks_proxy { + duniter_js_args.push("--socks-proxy".into()); + duniter_js_args.push(ws2p_socks_proxy.into()); + } + if let Some(ref tor_proxy) = args.tor_proxy { + duniter_js_args.push("--tor-proxy".into()); + duniter_js_args.push(tor_proxy.into()); + } + if let Some(ref reaching_clear_ep) = args.reaching_clear_ep { + duniter_js_args.push("--reaching-clear-ep".into()); + duniter_js_args.push(reaching_clear_ep.into()); + } + if args.rm_proxies { + duniter_js_args.push("--rm-proxies".into()); + } + if args.force_tor { + duniter_js_args.push("--force-tor".into()); + } +} diff --git a/rust-bins/duniter-launcher/src/daemon.rs b/rust-bins/duniter-launcher/src/daemon.rs new file mode 100644 index 000000000..2eae921c4 --- /dev/null +++ b/rust-bins/duniter-launcher/src/daemon.rs @@ -0,0 +1,112 @@ +// 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::*; + +pub fn start(prod: bool, profile_path: &Path, duniter_js_args: &[String]) -> Result<()> { + let stdout = OpenOptions::new() + .append(true) + .create(true) + .open(profile_path.join(LOG_FILE))?; + let mut duniter_js_command = Command::new(get_node_path()?); + if prod { + duniter_js_command.current_dir(DUNITER_JS_CURRENT_DIR); + } + let mut child = duniter_js_command + .args(duniter_js_args) + .stdout(stdout) + .stderr(Stdio::null()) + .spawn()?; + + let pid = child.id(); + + // Write pid on file + { + let mut pid_file = File::create(profile_path.join("app.pid"))?; + pid_file.write_all(format!("{}\n{}", pid, duniter_js_args.join(" ")).as_bytes())?; + } + + println!("Duniter daemon launched (pid: {}).", pid); + + let daemon = Daemon::new().umask(0o000).start(); + + if let Err(e) = daemon { + eprintln!("Error, {}", e); + } + + let status = child.wait().expect("fail to wait child"); + + std::process::exit(status.code().unwrap_or_default()) +} + +pub fn status(profile_path: &Path) -> Result<()> { + let mut pid_file = File::open(profile_path.join("app.pid"))?; + let mut pid_file_content = String::new(); + pid_file.read_to_string(&mut pid_file_content)?; + let mut lines = pid_file_content.split('\n'); + let pid = lines + .next() + .expect("corrupted pid file") + .parse::<i32>() + .expect("invalid pid"); + + match nix::sys::wait::waitpid(Some(Pid::from_raw(pid)), Some(WaitPidFlag::WNOHANG)) { + Ok(WaitStatus::StillAlive) => { + println!("Duniter is running using PID {}.", pid); + Ok(()) + } + Ok(_) | Err(Error::Sys(Errno::ESRCH)) => { + println!("Duniter is not running."); + std::process::exit(EXIT_CODE_DUNITER_NOT_RUNNING); + } + Err(e) => Err(e.into()), + } +} + +pub fn stop(profile_path: &Path) -> Result<Vec<String>> { + let mut pid_file = File::open(profile_path.join("app.pid"))?; + let mut pid_file_content = String::new(); + pid_file.read_to_string(&mut pid_file_content)?; + let mut lines = pid_file_content.split('\n'); + let pid = lines + .next() + .expect("corrupted pid file") + .parse::<i32>() + .expect("invalid pid"); + let duniter_args: Vec<String> = lines + .next() + .expect("corrupted pid file") + .split(' ') + .map(ToOwned::to_owned) + .collect(); + + match nix::sys::signal::kill(Pid::from_raw(pid), Some(Signal::SIGINT)) { + Err(Error::Sys(Errno::ESRCH)) => { + println!("Duniter is not running."); + Ok(duniter_args) + } + Err(e) => Err(e.into()), + Ok(()) => { + println!("Stopping Duniter daemon …"); + match nix::sys::wait::waitpid(Some(Pid::from_raw(pid)), Some(WaitPidFlag::WSTOPPED)) { + Ok(_) | Err(Error::Sys(Errno::ECHILD)) => { + println!("Duniter daemon stopped."); + Ok(duniter_args) + } + Err(e) => Err(e.into()), + } + } + } +} diff --git a/rust-bins/duniter-launcher/src/duniter_ts_args.rs b/rust-bins/duniter-launcher/src/duniter_ts_args.rs new file mode 100644 index 000000000..5158c83bf --- /dev/null +++ b/rust-bins/duniter-launcher/src/duniter_ts_args.rs @@ -0,0 +1,145 @@ +// 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::*; + +fn gen_start_args(args: &DuniterStartArgs, duniter_ts_args: &mut Vec<String>) { + if let Some(ref keyfile) = args.keyfile { + duniter_ts_args.push("--keyfile".to_owned()); + duniter_ts_args.push( + keyfile + .to_str() + .expect("keyfile path is invalid") + .to_owned(), + ); + } +} + +fn gen_webstart_args(args: &DuniterWebstartArgs, duniter_ts_args: &mut Vec<String>) { + if let Some(ref web_ui_host) = args.web_ui_host { + duniter_ts_args.push("--webmhost".to_owned()); + duniter_ts_args.push(web_ui_host.into()); + } + if let Some(ref web_ui_port) = args.web_ui_port { + duniter_ts_args.push("--webmport".to_owned()); + duniter_ts_args.push(web_ui_port.into()); + } +} + +pub(crate) fn gen_duniter_ts_args(args: &DuniterArgs, duniter_js_exe: String) -> Vec<String> { + let mut duniter_ts_args = Vec::new(); + duniter_ts_args.push(duniter_js_exe); + if let Some(ref home) = args.home { + duniter_ts_args.push("--mdb".to_owned()); + duniter_ts_args.push(home.to_str().expect("invalid home path").to_owned()); + } + if let Some(ref log_level) = args.log { + duniter_ts_args.push("--loglevel".to_owned()); + duniter_ts_args.push(log_level.to_string().to_lowercase()); + } + if let Some(ref profile) = args.profile { + duniter_ts_args.push("--mdb".to_owned()); + duniter_ts_args.push(profile.clone()); + } + match args.command { + DuniterCommand::Completions { .. } => unreachable!(), + DuniterCommand::DirectStart { + keyprompt, + ref start_args, + } => { + duniter_ts_args.push("direct_start".to_owned()); + if keyprompt { + duniter_ts_args.push("--keyprompt".to_owned()); + } + gen_start_args(start_args, &mut duniter_ts_args); + } + DuniterCommand::DirectWebstart { + keyprompt, + ref start_args, + ref webstart_args, + } => { + duniter_ts_args.push("direct_webstart".to_owned()); + if keyprompt { + duniter_ts_args.push("--keyprompt".to_owned()); + } + gen_start_args(start_args, &mut duniter_ts_args); + gen_webstart_args(webstart_args, &mut duniter_ts_args); + } + DuniterCommand::Start(ref start_args) => { + duniter_ts_args.push("direct_start".to_owned()); + gen_start_args(start_args, &mut duniter_ts_args); + } + DuniterCommand::Webstart { + ref start_args, + ref webstart_args, + } => { + duniter_ts_args.push("direct_webstart".to_owned()); + gen_start_args(start_args, &mut duniter_ts_args); + gen_webstart_args(webstart_args, &mut duniter_ts_args); + } + DuniterCommand::Stop => duniter_ts_args.push("stop".to_owned()), + DuniterCommand::Sync(ref sync_args) => { + duniter_ts_args.push("--store-txs".to_owned()); + duniter_ts_args.push("sync".to_owned()); + sync::gen_args(sync_args, &mut duniter_ts_args); + } + DuniterCommand::Config(ref config_args) => { + duniter_ts_args.push("config".to_owned()); + config::gen_args(config_args, &mut duniter_ts_args); + } + DuniterCommand::Reset(ref reset_command) => { + duniter_ts_args.push("reset".to_owned()); + match reset_command { + ResetCommand::Config => duniter_ts_args.push("config".to_owned()), + ResetCommand::Data => duniter_ts_args.push("data".to_owned()), + ResetCommand::Peers => duniter_ts_args.push("peers".to_owned()), + ResetCommand::Stats => duniter_ts_args.push("stats".to_owned()), + ResetCommand::All => duniter_ts_args.push("all".to_owned()), + } + } + DuniterCommand::Wizard(ref wizard_command) => { + duniter_ts_args.push("wizard".to_owned()); + match wizard_command { + WizardCommand::Bma => duniter_ts_args.push("network".to_owned()), + WizardCommand::Key { n, r, p } => { + duniter_ts_args.push("key".to_owned()); + if let Some(n) = n { + duniter_ts_args.push("--keyN".to_owned()); + duniter_ts_args.push(n.to_string()); + } + if let Some(r) = r { + duniter_ts_args.push("--keyr".to_owned()); + duniter_ts_args.push(r.to_string()); + } + if let Some(p) = p { + duniter_ts_args.push("--keyp".to_owned()); + duniter_ts_args.push(p.to_string()); + } + } + } + } + DuniterCommand::WS2P(ref ws2p_command) => { + duniter_ts_args.push("ws2p".to_owned()); + match ws2p_command { + WS2PCommand::ListNodes => duniter_ts_args.push("list-nodes".to_owned()), + WS2PCommand::ListPrefered => duniter_ts_args.push("list-prefered".to_owned()), + WS2PCommand::ListPrivileged => duniter_ts_args.push("list-privileged".to_owned()), + WS2PCommand::ShowConf => duniter_ts_args.push("show-conf".to_owned()), + } + } + DuniterCommand::Logs | DuniterCommand::Restart | DuniterCommand::Status => {} + } + duniter_ts_args +} diff --git a/rust-bins/duniter-launcher/src/main.rs b/rust-bins/duniter-launcher/src/main.rs new file mode 100644 index 000000000..bdc246701 --- /dev/null +++ b/rust-bins/duniter-launcher/src/main.rs @@ -0,0 +1,334 @@ +// 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/>. + +#![deny( + clippy::unwrap_used, + missing_debug_implementations, + missing_copy_implementations, + trivial_casts, + trivial_numeric_casts, + unsafe_code, + unstable_features, + unused_import_braces +)] + +mod config; +mod daemon; +mod duniter_ts_args; +mod sync; + +use anyhow::{anyhow, Result}; +use daemonize_me::Daemon; +use logwatcher::{LogWatcher, LogWatcherAction}; +use nix::{ + errno::Errno, + sys::{signal::Signal, wait::WaitPidFlag, wait::WaitStatus}, + unistd::Pid, + Error, +}; +use std::{ + fs::{File, OpenOptions}, + io::prelude::*, + path::Path, + path::PathBuf, + process::Command, + process::Output, + process::Stdio, +}; +use structopt::{clap::Shell, StructOpt}; + +const APP_NAME: &str = "duniter"; +const DEFAULT_PORT: u16 = 443; +const DEFAULT_PROFILE: &str = "duniter_default"; +const DUNITER_EXE_PATH: &str = "/opt/duniter/bin/duniter"; +const DUNITER_EXE_LINK_PATH: &str = "/usr/bin/duniter"; +const DUNITER_JS_CURRENT_DIR: &str = "/opt/duniter/"; +const DUNITER_JS_PATH: &str = "/opt/duniter/bin/duniter_js"; +const DUNITER_JS_DEV_PATH: &str = "bin/duniter_js"; +const EMBEDDED_NODE_PATH: &str = "/opt/duniter/node/bin/node"; +const EXIT_CODE_DUNITER_NOT_RUNNING: i32 = 4; +const LOG_FILE: &str = "duniter.log"; +const NODE_VERSION_BEGIN: &str = "v10."; + +#[derive(StructOpt)] +#[structopt(name = APP_NAME, about = "Crypto-currency software to operate Ğ1 libre currency.")] +struct DuniterArgs { + /// Path to Duniter HOME (defaults to "$HOME/.config/duniter"). + #[structopt(short, long, parse(from_os_str))] + home: Option<PathBuf>, + /// Logs level (If not specified, use the logs level defined in the configuration or INFO by default). + #[structopt(short, long, alias("loglevel"), case_insensitive(true), possible_values = &["ERROR", "WARN", "INFO", "DEBUG", "TRACE"])] + log: Option<log::Level>, + /// Profile name (defauld "duniter_default") + #[structopt(short, long, alias("mdb"))] + profile: Option<String>, + #[structopt(subcommand)] + command: DuniterCommand, +} + +#[derive(StructOpt)] +#[structopt(rename_all = "snake")] +enum DuniterCommand { + /// Duniter configuration options. + #[structopt( + display_order(0), + after_help("Some advanced options are hidden for readability.") + )] + Config(Box<config::DuniterConfigArgs>), + /// Launch the configuration wizard. + #[structopt(display_order(1))] + Wizard(WizardCommand), + /// WS2P operations for configuration and diagnosis tasks. + #[structopt(display_order(2))] + WS2P(WS2PCommand), + /// Synchronize blockchain from a remote Duniter node. + #[structopt(display_order(3))] + Sync(sync::DuniterSyncArgs), + /// Start Duniter node with direct output, non-daemonized. + #[structopt(display_order(4))] + DirectStart { + /// Force to use the keypair given by user prompt. + #[structopt(long)] + keyprompt: bool, + #[structopt(flatten)] + start_args: DuniterStartArgs, + }, + /// Start Duniter node with its web interface with direct output, non-daemonized. + #[structopt(display_order(5))] + DirectWebstart { + /// Force to use the keypair given by user prompt. + #[structopt(long)] + keyprompt: bool, + #[structopt(flatten)] + start_args: DuniterStartArgs, + #[structopt(flatten)] + webstart_args: DuniterWebstartArgs, + }, + /// Starts Duniter as a daemon (background task). + #[structopt(display_order(6))] + Start(DuniterStartArgs), + /// Starts Duniter (with its web interface) as a daemon (background task). + #[structopt(display_order(7))] + Webstart { + #[structopt(flatten)] + start_args: DuniterStartArgs, + #[structopt(flatten)] + webstart_args: DuniterWebstartArgs, + }, + /// Get Duniter daemon status. + #[structopt(display_order(8))] + Status, + /// Follow duniter logs. + #[structopt(display_order(9))] + Logs, + /// Stops Duniter daemon and restart it. + #[structopt(display_order(10), alias = "webrestart")] + Restart, + /// Stops Duniter daemon if it is running. + #[structopt(display_order(11))] + Stop, + /// Reset configuration, data, peers, transactions or everything in the database + #[structopt(display_order(12))] + Reset(ResetCommand), + /// Generate tab-completion script for your shell + #[structopt(display_order(13))] + Completions { + #[structopt(case_insensitive(true), possible_values = &["BASH, FISH, ZSH"])] + shell: Shell, + }, +} + +#[derive(StructOpt)] +enum ResetCommand { + #[structopt(display_order(0))] + Config, + #[structopt(display_order(1))] + Data, + #[structopt(display_order(2))] + Peers, + #[structopt(display_order(3))] + Stats, + #[structopt(display_order(4))] + All, +} + +#[derive(StructOpt)] +enum WizardCommand { + #[structopt(display_order(0))] + Key { + /// Scrypt `N` CPU/memory cost parameter. Must be a power of 2. Defaults to 4096. + #[structopt(short)] + n: Option<usize>, + /// "Scrypt `r` The blocksize parameter, which fine-tunes sequential memory read size and performance. Defaults to 16." + #[structopt(short)] + r: Option<usize>, + /// Scrypt `p` Parallelization parameter. Defaults to 1. + #[structopt(short)] + p: Option<usize>, + }, + #[structopt(display_order(1), alias = "network")] + Bma, +} + +#[derive(StructOpt)] +enum WS2PCommand { + #[structopt(display_order(0))] + ListNodes, + #[structopt(display_order(0))] + ListPrefered, + #[structopt(display_order(0))] + ListPrivileged, + #[structopt(display_order(0))] + ShowConf, +} + +#[derive(StructOpt)] +struct DuniterStartArgs { + /// 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>, +} + +#[derive(StructOpt)] +struct DuniterWebstartArgs { + /// Web user interface host (IP) to listen to. + #[structopt(long, alias = "webmhost", env("DUNITER_WEB_UI_HOST"))] + web_ui_host: Option<String>, + /// Web user interface port (IP) to listen to. + #[structopt(long, alias = "webmport")] + web_ui_port: Option<String>, +} + +fn main() -> Result<()> { + let args = DuniterArgs::from_args(); + + if let DuniterCommand::Completions { shell } = args.command { + DuniterArgs::clap().gen_completions_to(APP_NAME, shell, &mut std::io::stdout()); + Ok(()) + } 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); + + 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); + } + 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(()) + } + } + } + } +} + +fn duniter_js_exe() -> Result<String> { + let current_exe = std::env::current_exe()?; + Ok( + if current_exe == PathBuf::from(DUNITER_EXE_LINK_PATH) + || current_exe == PathBuf::from(DUNITER_EXE_PATH) + { + DUNITER_JS_PATH.to_owned() + } else { + DUNITER_JS_DEV_PATH.to_owned() + }, + ) +} + +pub(crate) fn get_node_path() -> Result<&'static str> { + let current_exe = std::env::current_exe()?; + if current_exe == PathBuf::from(DUNITER_EXE_LINK_PATH) + || current_exe == PathBuf::from(DUNITER_EXE_PATH) + { + let node_path = PathBuf::from(EMBEDDED_NODE_PATH); + if node_path.exists() { + Ok(EMBEDDED_NODE_PATH) + } else { + eprintln!("Node.js is not embedded in this version of Duniter"); + std::process::exit(1); + } + } else if get_node_version("node")?.starts_with(NODE_VERSION_BEGIN) { + Ok("node") + } else { + eprintln!("Duniter require Node.js v10.x"); + std::process::exit(1); + } +} + +pub(crate) fn get_node_version(node_path: &str) -> Result<String> { + let Output { + status, + stdout, + stderr, + } = Command::new(node_path).arg("-v").output()?; + + if status.success() { + Ok(String::from_utf8(stdout) + .unwrap_or_else(|_| "Output is not a valid utf8 string".to_owned())) + } else { + eprintln!( + "{}", + String::from_utf8(stderr) + .unwrap_or_else(|_| "Error message is not a valid utf8 string".to_owned()) + ); + std::process::exit(1); + } +} + +fn get_profile_path(profile: Option<&str>) -> Result<PathBuf> { + let mut profile_path = dirs::config_dir().expect("unsupported operating system"); + profile_path.push(APP_NAME); + profile_path.push(profile.unwrap_or(DEFAULT_PROFILE)); + if !profile_path.exists() { + std::fs::create_dir_all(&profile_path)?; + } + Ok(profile_path) +} + +fn watch_logs(profile_path: PathBuf) -> Result<()> { + let mut log_watcher = LogWatcher::register(profile_path.join(LOG_FILE))?; + + log_watcher.watch(&mut move |line: String| { + println!("{}", line); + LogWatcherAction::None + }); + + Ok(()) +} diff --git a/rust-bins/duniter-launcher/src/sync.rs b/rust-bins/duniter-launcher/src/sync.rs new file mode 100644 index 000000000..1e66d2513 --- /dev/null +++ b/rust-bins/duniter-launcher/src/sync.rs @@ -0,0 +1,85 @@ +// 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::*; + +#[derive(StructOpt)] +pub(crate) struct DuniterSyncArgs { + /// Check all DUPB rules (very long). + #[structopt(hidden(true), long)] + cautious: bool, + /// Allow to synchronize on nodes with local network IP address. + #[structopt(hidden(true), long)] + localsync: bool, + /// Disable interactive sync UI. + #[structopt(long, alias = "nointeractive")] + no_interactive: bool, + /// Do not retrieve peers. + #[structopt(long, alias = "nopeers")] + no_peers: bool, + /// Disables P2P downloading of blocs. + #[structopt(long, alias = "nop2p")] + no_p2p: bool, + /// Do not retrieve sandboxes during sync. + #[structopt(long, alias = "nosbx")] + no_sandboxes: bool, + /// Will only try to sync peers. + #[structopt(long, alias = "onlypeers")] + only_peers: bool, + /// Download slowly the blokchcain (for low connnections). + #[structopt(long)] + slow: bool, + // Host or directory + source: String, + /// Port + port: Option<u16>, +} + +pub(crate) fn gen_args(args: &DuniterSyncArgs, duniter_ts_args: &mut Vec<String>) { + if args.source.contains(':') || args.source.contains('/') { + duniter_ts_args.push(args.source.clone()); + } else { + duniter_ts_args.push(format!( + "{}:{}", + args.source, + args.port.unwrap_or(DEFAULT_PORT) + )); + } + if args.cautious { + duniter_ts_args.push("--cautious".into()); + } + if args.localsync { + duniter_ts_args.push("--localsync".into()); + } + if args.no_interactive { + duniter_ts_args.push("--nointeractive".into()); + } + if args.no_peers { + duniter_ts_args.push("--nopeers".into()); + } + if args.no_p2p { + duniter_ts_args.push("--nop2p".into()); + } + if args.no_sandboxes { + duniter_ts_args.push("--nosbx".into()); + } + if args.only_peers { + duniter_ts_args.push("--onlypeers".into()); + } + if args.slow { + duniter_ts_args.push("--slow".into()); + } + todo!() +} diff --git a/rust-bins/xtask/Cargo.toml b/rust-bins/xtask/Cargo.toml new file mode 100644 index 000000000..95175adcd --- /dev/null +++ b/rust-bins/xtask/Cargo.toml @@ -0,0 +1,17 @@ +[package] +authors = ["elois <elois@duniter.org>"] +description = "Duniter xtask." +edition = "2018" +license = "AGPL-3.0" +name = "xtask" +repository = "https://git.duniter.org/nodes/typescript/duniter" +version = "0.1.0" + +[[bin]] +bench = false +path = "src/main.rs" +name = "xtask" + +[dependencies] +structopt = "0.3.18" +version_check = "0.9.2" diff --git a/rust-bins/xtask/src/main.rs b/rust-bins/xtask/src/main.rs new file mode 100644 index 000000000..91f45095a --- /dev/null +++ b/rust-bins/xtask/src/main.rs @@ -0,0 +1,145 @@ +// 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 std::{ + io::Result, + process::{Command, Output}, +}; +use structopt::StructOpt; + +const MIN_RUST_VERTION: &str = "1.46.0"; +const NODE_VERSION: &str = "10.22.1"; +const NVM_VERSION: &str = "0.35.3"; + +#[derive(StructOpt)] +struct DuniterXTask { + #[structopt(long)] + skip_npm: bool, + #[structopt(subcommand)] + command: DuniterXTaskCommand, +} + +#[derive(StructOpt)] +enum DuniterXTaskCommand { + Build { + #[structopt(long)] + production: bool, + }, + Test, +} + +fn main() -> Result<()> { + let args = DuniterXTask::from_args(); + + if !version_check::is_min_version(MIN_RUST_VERTION).unwrap_or(false) + && exec_should_success(Command::new("rustup").args(&["update", "stable"])).is_err() + { + eprintln!( + "Duniter requires Rust {} or higher. If you installed the Rust toolchain via rustup, please execute the command `rustup update stable`.", + MIN_RUST_VERTION + ); + std::process::exit(1); + } + Command::new("rustc").arg("--version").status()?; + Command::new("cargo").arg("--version").status()?; + + if !args.skip_npm { + println!("Check node version …"); + if exec_and_get_stdout(Command::new("node").arg("-v")) + .unwrap_or_default() + .trim_end() + != format!("v{}", NODE_VERSION) + { + println!("Install node v{} …", NODE_VERSION); + install_and_use_node_version()?; + } else { + println!("Node v{} already installed ✔", NODE_VERSION); + } + } + match args.command { + DuniterXTaskCommand::Build { production } => build(args.skip_npm, production), + DuniterXTaskCommand::Test => test(args.skip_npm), + } +} + +fn install_and_use_node_version() -> Result<()> { + if exec_should_success(Command::new("nvm").arg("--version")).is_err() { + println!("Install nvm v{} …", NVM_VERSION); + let nvm_install_script = exec_and_get_stdout(Command::new("wget").args(&[ + "-qO-", + &format!( + "https://raw.githubusercontent.com/nvm-sh/nvm/v{}/install.sh", + NVM_VERSION + ), + ]))?; + exec_should_success(Command::new("bash").arg(nvm_install_script))?; + } + exec_should_success(Command::new("nvm").args(&["install", NODE_VERSION]))?; + exec_should_success(Command::new("nvm").args(&["use", NODE_VERSION])) +} + +fn exec_and_get_stdout(command: &mut Command) -> Result<String> { + let Output { + status, + stdout, + stderr, + } = command.output()?; + + if status.success() { + Ok(String::from_utf8(stdout) + .unwrap_or_else(|_| "Output is not a valid utf8 string".to_owned())) + } else { + eprintln!( + "{}", + String::from_utf8(stderr) + .unwrap_or_else(|_| "Error message is not a valid utf8 string".to_owned()) + ); + std::process::exit(1); + } +} + +fn exec_should_success(command: &mut Command) -> Result<()> { + if !command.status()?.success() { + std::process::exit(1); + } else { + Ok(()) + } +} + +fn build(skip_npm: bool, production: bool) -> Result<()> { + if !skip_npm { + exec_should_success(Command::new("npm").args(&["add", "duniter-ui"]))?; + exec_should_success( + Command::new("npm") + .env("NEON_BUILD_RELEASE", "true") + .arg("install"), + )?; + if production { + exec_should_success(Command::new("npm").args(&["prune", "--production"]))?; + } + } + println!("Build duniter-launcher …"); + exec_should_success(Command::new("cargo").args(&["build", "--release"]))?; + std::fs::copy("target/release/duniter", "bin/duniter")?; + Ok(()) +} + +fn test(skip_npm: bool) -> Result<()> { + exec_should_success(Command::new("cargo").args(&["test", "--all"]))?; + if !skip_npm { + exec_should_success(Command::new("npm").arg("test"))?; + } + Ok(()) +} diff --git a/server.ts b/server.ts index ee920422d..6f8ed1ea1 100644 --- a/server.ts +++ b/server.ts @@ -585,7 +585,7 @@ export class Server extends stream.Duplex implements HookableServer { * the script arguments. * * Ex: - * * process.argv: ['/usr/bin/node', '/opt/duniter/sources/bin/duniter', 'restart', '--mdb', 'g1'] + * * process.argv: ['/usr/bin/node', '/opt/duniter/sources/bin/duniter_js', 'restart', '--mdb', 'g1'] * * Then `getCommand('direct_start', 'restart') will return: * @@ -593,7 +593,7 @@ export class Server extends stream.Duplex implements HookableServer { * * This new array is what will be given to a *fork* of current script, resulting in a new process with: * - * * process.argv: ['/usr/bin/node', '/opt/duniter/sources/bin/duniter', 'direct_start', '--mdb', 'g1'] + * * process.argv: ['/usr/bin/node', '/opt/duniter/sources/bin/duniter_js', 'direct_start', '--mdb', 'g1'] * * @param cmd * @param insteadOfCmd diff --git a/test/integration/misc/cli.ts b/test/integration/misc/cli.ts index 070d6671c..9f142e4b3 100644 --- a/test/integration/misc/cli.ts +++ b/test/integration/misc/cli.ts @@ -131,23 +131,6 @@ describe("CLI", function() { res[res.length - 1].should.have.property('number').equal(7); res.should.have.length(7 + 1); // blocks #0..#7 }) - - // it('sync 4 blocks (cautious)', async () => { - // await execute(['sync', fakeServer.host + ':' + String(fakeServer.port), '--nointeractive', '11']); - // const res = await execute(['export-bc', '--nostdout']); - // res[res.length - 1].should.have.property('number').equal(11); - // res.should.have.length(11 + 1); - // }) - // - // it('[spawn] reset data', async () => { - // await executeSpawn(['reset', 'data']); - // const res = await executeSpawn(['export-bc']); - // JSON.parse(res).should.have.length(0); - // }) - // - // it('[spawn] sync 10 first blocks --memory', async () => { - // await execute(['sync', fakeServer.host + ':' + String(fakeServer.port), '--memory', '--cautious', '--nointeractive', '10']); - // }) }); /** @@ -161,23 +144,3 @@ async function execute(args:(string)[]) { // Executes the command return stack.executeStack(finalArgs); } - -/** - * Executes a duniter command, as a command line utility. - * @param command Array of arguments. - * @returns {*|Promise} Returns the command output. - */ -async function executeSpawn(command:string[]): Promise<string> { - const finalArgs = [path.join(__dirname, '../../../bin/duniter')].concat(command).concat(['--mdb', DB_NAME]); - const duniterCmd = spawn(process.argv[0], finalArgs); - return new Promise<string>((resolve, reject) => { - let res = ""; - duniterCmd.stdout.on('data', (data:any) => { - res += data.toString('utf8').replace(/\n/, ''); - }); - duniterCmd.stderr.on('data', (err:any) => { - console.log(err.toString('utf8').replace(/\n/, '')); - }); - duniterCmd.on('close', (code:any) => code ? reject(code) : resolve(res) ); - }); -} diff --git a/test/run.sh b/test/run.sh deleted file mode 100755 index 4f22f7774..000000000 --- a/test/run.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -if [ "${1}" = "rs" ] -then - $HOME/.cargo/bin/cargo test --all -elif [ "${1}" = "ts" ] -then - nyc --reporter html mocha -else - $HOME/.cargo/bin/cargo test --all && nyc --reporter html mocha -fi -- GitLab