diff --git a/.cargo/config b/.cargo/config
new file mode 100644
index 0000000000000000000000000000000000000000..35049cbcb13c204648d1f7897162492f05123199
--- /dev/null
+++ b/.cargo/config
@@ -0,0 +1,2 @@
+[alias]
+xtask = "run --package xtask --"
diff --git a/.gitignore b/.gitignore
index cfedee3dd3822a2fc47107f75cbb9168265ce59f..4124c0cfecf5d467fb75cc0617ee53b1a2dcce0d 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 be19655040e58ee3a9f2af912eb0af957821cacc..d16501420b4fc77e537fc5ab64511ef4fbf28b40 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 7bc868a6f41d30797ce095ef2245bcc86b775ad4..52f1bfa493790a0edfbcc582c366e7d88593f116 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/.rusty-hook.toml b/.rusty-hook.toml
new file mode 100644
index 0000000000000000000000000000000000000000..4bc5c8e22cc6446b8c89d3a139d8f9a99801fe39
--- /dev/null
+++ b/.rusty-hook.toml
@@ -0,0 +1,5 @@
+[hooks]
+pre-commit = "cargo fmt -- --check && npm run format:check"
+
+[logging]
+verbose = true
diff --git a/Cargo.lock b/Cargo.lock
index 013c04009950466aea988230c5484adb0a4a70dc..850a71c53174102dfcd5684dcebda283c89c1b3a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -24,6 +24,44 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
 [[package]]
 name = "autocfg"
 version = "1.0.0"
@@ -50,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"
@@ -66,6 +110,23 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "bitflags"
+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"
@@ -123,6 +184,36 @@ version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
 
+[[package]]
+name = "ci_info"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24f638c70e8c5753795cc9a8c07c44da91554a09e4cf11a7326e8161b0a3c45e"
+dependencies = [
+ "envmnt",
+]
+
+[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "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"
@@ -191,6 +282,27 @@ 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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "078464a47febb5786c43fc1f65fd1a58dae38f0544912dd855cbdde2e2bdf197"
+dependencies = [
+ "libc",
+ "nix",
+ "snafu",
+]
+
 [[package]]
 name = "digest"
 version = "0.8.1"
@@ -200,6 +312,32 @@ 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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
+
 [[package]]
 name = "dubp-common"
 version = "0.2.0"
@@ -275,6 +413,21 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "duniter-launcher"
+version = "1.9.0-alpha1"
+dependencies = [
+ "anyhow",
+ "ctrlc",
+ "daemonize-me",
+ "dirs",
+ "log",
+ "logwatcher",
+ "nix",
+ "rusty-hook",
+ "structopt",
+]
+
 [[package]]
 name = "duniteroxyde"
 version = "0.3.0"
@@ -299,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",
@@ -315,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",
@@ -332,6 +485,16 @@ version = "1.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
 
+[[package]]
+name = "envmnt"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2d328fc287c61314c4a61af7cfdcbd7e678e39778488c7cb13ec133ce0f4059"
+dependencies = [
+ "fsio",
+ "indexmap",
+]
+
 [[package]]
 name = "error-chain"
 version = "0.12.4"
@@ -360,6 +523,12 @@ dependencies = [
  "miniz_oxide",
 ]
 
+[[package]]
+name = "fsio"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1fd087255f739f4f1aeea69f11b72f8080e9c2e7645cd06955dad4a178a49e3"
+
 [[package]]
 name = "generic-array"
 version = "0.12.3"
@@ -369,6 +538,15 @@ dependencies = [
  "typenum",
 ]
 
+[[package]]
+name = "getopts"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
+dependencies = [
+ "unicode-width",
+]
+
 [[package]]
 name = "getrandom"
 version = "0.1.15"
@@ -387,6 +565,21 @@ version = "0.22.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
 
+[[package]]
+name = "hashbrown"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
+
+[[package]]
+name = "heck"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
+dependencies = [
+ "unicode-segmentation",
+]
+
 [[package]]
 name = "hermit-abi"
 version = "0.1.12"
@@ -396,6 +589,16 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "indexmap"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
 [[package]]
 name = "itoa"
 version = "0.4.6"
@@ -437,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"
@@ -535,6 +744,25 @@ dependencies = [
  "regex",
 ]
 
+[[package]]
+name = "nias"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab250442c86f1850815b5d268639dff018c0627022bc1940eb2d642ca1ce12f0"
+
+[[package]]
+name = "nix"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363"
+dependencies = [
+ "bitflags",
+ "cc",
+ "cfg-if",
+ "libc",
+ "void",
+]
+
 [[package]]
 name = "num"
 version = "0.2.1"
@@ -670,6 +898,30 @@ dependencies = [
  "sha-1",
 ]
 
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.19"
@@ -712,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"
@@ -745,12 +1014,36 @@ 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"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
 
+[[package]]
+name = "rusty-hook"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96cee9be61be7e1cbadd851e58ed7449c29c620f00b23df937cb9cbc04ac21a3"
+dependencies = [
+ "ci_info",
+ "getopts",
+ "nias",
+ "toml",
+]
+
 [[package]]
 name = "ryu"
 version = "1.0.5"
@@ -830,12 +1123,63 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "snafu"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c4e6046e4691afe918fd1b603fd6e515bcda5388a1092a9edbada307d159f09"
+dependencies = [
+ "doc-comment",
+ "snafu-derive",
+]
+
+[[package]]
+name = "snafu-derive"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7073448732a89f2f3e6581989106067f403d378faeafb4a50812eb814170d3e5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "spin"
 version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
 
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "structopt"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a33f6461027d7f08a13715659b2948e1602c31a3756aeae9378bfe7518c72e82"
+dependencies = [
+ "clap",
+ "lazy_static",
+ "structopt-derive",
+]
+
+[[package]]
+name = "structopt-derive"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c92e775028122a4b3dd55d58f14fc5120289c69bee99df1d117ae30f84b225c9"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "syn"
 version = "1.0.39"
@@ -859,6 +1203,15 @@ dependencies = [
  "unicode-xid",
 ]
 
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
 [[package]]
 name = "thiserror"
 version = "1.0.20"
@@ -888,6 +1241,15 @@ dependencies = [
  "lazy_static",
 ]
 
+[[package]]
+name = "toml"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "typenum"
 version = "1.12.0"
@@ -900,6 +1262,18 @@ version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
 
+[[package]]
+name = "unicode-segmentation"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+
 [[package]]
 name = "unicode-xid"
 version = "0.2.0"
@@ -918,12 +1292,24 @@ version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7e33648dd74328e622c7be51f3b40a303c63f93e6fa5f08778b6203a4c25c20f"
 
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
 [[package]]
 name = "version_check"
 version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
 
+[[package]]
+name = "void"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+
 [[package]]
 name = "wasi"
 version = "0.9.0+wasi-snapshot-preview1"
@@ -1016,6 +1402,14 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
+[[package]]
+name = "xtask"
+version = "0.1.0"
+dependencies = [
+ "structopt",
+ "version_check",
+]
+
 [[package]]
 name = "zeroize"
 version = "1.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index 56e722261f26245bae3bb5dcdf8452e9816e76d0..5cf345d3082f7b3a11ca1cde64e73d76526077a2 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 7604e6043c8ae898edeaca2af200896205cbc53f..ffd19949dcd525a81ec7fc9ee3a9fbee3dae5a6a 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/keypair/index.ts b/app/modules/keypair/index.ts
index a809f75fd2b2af3d665f6fa4443f7ad5a119748c..e3d971d00c921440025e526ba41cd4368ba22e71 100644
--- a/app/modules/keypair/index.ts
+++ b/app/modules/keypair/index.ts
@@ -27,11 +27,6 @@ export const KeypairDependency = {
     },
 
     cliOptions: [
-      { value: "--salt <salt>", desc: "Salt to generate the keypair" },
-      {
-        value: "--passwd <password>",
-        desc: "Password to generate the keypair",
-      },
       {
         value: "--keyN <N>",
         desc:
@@ -98,22 +93,6 @@ export const KeypairDependency = {
         logger: any,
         confDAL: any
       ) => {
-        if (
-          (program.keyN || program.keyr || program.keyp) &&
-          !(program.salt && program.passwd)
-        ) {
-          throw Error(
-            "Missing --salt and --passwd options along with --keyN|keyr|keyp option"
-          );
-        }
-
-        // If we have salt and password, convert it to keypair
-        if (program.salt || program.passwd) {
-          const salt = program.salt || "";
-          const key = program.passwd || "";
-          conf.pair = await Scrypt(salt, key);
-        }
-
         // If no keypair has been loaded, try the default .yml file
         if (!conf.pair || !conf.pair.pub || !conf.pair.sec) {
           const ymlContent = await confDAL.coreFS.read("keyring.yml");
@@ -223,6 +202,12 @@ async function promptKey(conf: KeypairConfDTO, program: any) {
       obfuscatedPasswd.length > 0 && obfuscatedPasswd == answersPasswd.passwd;
     const salt = keepOldSalt ? program.salt : answersSalt.salt;
     const passwd = keepOldPasswd ? program.passwd : answersPasswd.passwd;
-    conf.pair = await Scrypt(salt, passwd);
+    conf.pair = await Scrypt(
+      salt,
+      passwd,
+      program.keyN || 4096,
+      program.keyr || 16,
+      program.keyp || 1
+    );
   }
 }
diff --git a/app/modules/ws2p/index.ts b/app/modules/ws2p/index.ts
index d46d3d3bbd0525ea8cbe52fa3cf6703213b0b16f..be8575a57faf58fec9ccedfb59498dc434bbbbfb 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 ba8c5e547339ff8b3f768b3ffa546de7e8c23455..002b58f1dc4ed5e86eaafadc48ae7c5b418c5900 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/advanced-commands.md b/doc/use/advanced-commands.md
new file mode 100644
index 0000000000000000000000000000000000000000..b7abedecc6c30a13945a9a9a27c8cf30e1f6b0f2
--- /dev/null
+++ b/doc/use/advanced-commands.md
@@ -0,0 +1,188 @@
+Title: Advanced commands
+Order: 9
+Date: 2022-10-04
+Slug: advanced_commands
+Authors: elois
+
+# Advanced commands
+
+Before reading this page, first read the on-board help in Duniter, it meets the most common needs:
+
+```bash
+duniter help
+```
+
+If you are new to Duniter, or/and you just want to configure your network properly, read this page instead: [Configure Duniter server variant](./configure.md).
+
+This page is intended for advanced users, advanced testers and developers.
+
+Some advanced options are hidden in the embedded help (duniter help) because they are rarely used and/or are mainly intended for advanced testers and developers.
+
+Some commands are not available via the Duniter binary and need to call directly the NodeJs part.
+
+## Hidden options
+
+This part lists the options accessible via the Duniter binary but hidden in the help.
+
+### Hidden `sync` options
+
+These options must be used with the `sync` sub command.
+
+#### `--cautious`
+
+Check all DUPB rules (very long).
+
+#### `--localsync`
+
+Allow to synchronize on nodes with local network IP address.
+
+### Hidden `config` options
+
+These options must be used with the `config` sub command.
+
+#### `--nb-cores`
+
+Number of cores uses for proof-of-work computation
+
+### `---addep`
+
+Add given endpoint to the list of endpoints of this node.
+
+### `---remep`
+
+Remove given endpoint to the list of endpoints of this node.
+
+### WS2P preferred/privileged options
+
+These options are described in a dedicated page: [WS2P: preferred and privileged nodes](./ws2p_preferred_privileged.md)
+
+#### `--ws2p-no-private`
+
+Disable WS2P Private.
+
+#### `--ws2p-private`
+
+Enable WS2P Private.
+
+#### `--ws2p-socks-proxy <host:port>`
+
+Use Socks Proxy for WS2P Private
+
+#### `--rm-proxies`
+
+Remove all proxies options
+
+### WS2P TOR options
+
+To have your Duniter node use Tor for outgoing connections it makes with other nodes, just set up a Tor proxy and choose a policy for normal ws2p access points (the `--reaching-clear-ep` option):
+
+```bash
+duniter config --tor-proxy localhost:9050 --reaching-clear-ep tor
+```
+
+You will also need to install the *Tor Browser* or *Tor Standalone* on the same machine. By default, Tor listens on localhost on port 9050. If you change your Tor configuration you will obviously need to change the Duniter configuration accordingly.
+
+You can also choose a mixed node, which will contact the regular access points in plain text, so it will only use Tor to contact .onion access points:
+
+```bash
+duniter config --tor-proxy localhost:9050 --reaching-clear-ep clear
+```
+
+Finally 3rd choice, you can choose to contact only the .onion access points, the clear access points will never be contacted:
+
+```bash
+duniter config --tor-proxy localhost:9050 --reaching-clear-ep none
+```
+
+/!\ Each time you change one of these two options you must repeat the other one at the same time or it will be reset!
+
+Finally, to remove your Tor configuration and return to a classic node:
+
+```bash
+duniter --rm-proxies
+```
+
+You can also decide to encapsulate Duniter in a Tor VM as whonix, in which case you will need to inform Duniter that it will be able to contact .onion access points by enabling the `--force-tor' option:
+
+```bash
+duniter config --force-tor --reaching-clear-ep tor|none
+```
+
+#### Hidden service
+
+Just enter the .onion address of your hidden service in the `--ws2p-remote-host` option.
+
+## Hidden commands and more hidden options
+
+The following commands and options are not accessible via the `duniter` binary. The NodeJs code must be called directly for them.
+
+For this use the embedded NodeJs :
+
+```bash
+/opt/duniter/node/bin/node duniter_js <hidden-command>
+```
+
+If you compiled Duniter yourself, you don't have any NodeJs embedded, then use the version of NodeJs you used to compile Duniter.
+
+Not all commands and options are listed here, if you don't find what you need:
+
+1. Search in `duniter_js --help`.
+
+2. Search into the Duniter's code itself.
+
+3. Ask for help on the [Duniter forum](https://forum.duniter.org).
+
+### sync `--memory`
+
+Perform synchronization in memory only. Synchronization will not be stored on the hard disk. This option is useful for a quick blockchain integrity check when combined with the --cautious option.
+
+`duniter_js sync g1.duniter.org 443 --cautious --memory`
+
+### Generate a block manually
+
+Generates the next block from the pool data and the current block, performs the working proof for the requested difficulty and then submits the resulting block to a node.
+
+`duniter_js gen-next g1.duniter.org 10901 74`
+
+This command generates the next block, sends it to node `g1.duniter.org:10901` and calculates the working proof with 74 trouble (footprint beginning with 4 zeros).
+
+#### Option `--show`
+
+Displays the calculated block **before** the proof of work and submission to the network. Controls its contents.
+
+`duniter_js gen-next g1.duniter.org 10901 74 --show`
+
+#### Option `--check`
+
+Modifies the behaviour of the command: it no longer produces proof of work nor submits the block to the network. Instead, it generates the block and checks if it is acceptable by a node.
+
+`duniter_js gen-next --show --check`
+
+### Generate genesis block
+
+Generates block #0 automatically, including a maximum number of members:
+
+`duniter_js gen-root duniter.org 10901 74`
+
+Generates block #0 by manually selecting members to include:
+
+```bash
+duniter_js gen-root-choose duniter.org 10901 74
+? Newcomers to add:
+ â—¯ john
+ â—¯ dude404
+ â—‰ sinogeek
+❯◉ deviantime
+ â—¯ kernel
+ â—¯
+```
+
+## Not listed
+
+Not all commands and options are listed on this page, if you don't find what you need:
+
+1. Search in `duniter_js --help`.
+
+2. Search into the Duniter's code itself.
+
+3. Ask for help on the [Duniter forum](https://forum.duniter.org).
diff --git a/doc/use/configure.md b/doc/use/configure.md
new file mode 100644
index 0000000000000000000000000000000000000000..80d404a421224dff353e8e8f5341ae6f77bc7b04
--- /dev/null
+++ b/doc/use/configure.md
@@ -0,0 +1,216 @@
+Title: Configure Duniter server variant
+Order: 9
+Date: 2017-09-22
+Slug: configurer
+Authors: elois
+
+# Configure Duniter server variant
+
+Here is a short tutorial to configure your newly installed Duniter server node.  
+Warning: this tutorial is for server variant users only and in **version 1.9.x or higher**.
+
+## Configuring the cryptographic keypair
+
+All duniter nodes have a cryptographic keypair, which they use to sign the information they transmit over the network. There are two types of duniter nodes:
+
+**1. member nodes:** If the node's keypair corresponds to a member identity, then the node is a "member" node, and 
+will automatically take part in the calculation of the blocks.
+
+**2. Mirror nodes:** If the node's keypair does not match a member identity, then the node is "mirror" type, it will not be able to write a block, but will still be useful for network resilience and for responding to client requests.
+
+By default this keypair is random, so the duniter node is a mirror node. You can change the node's keypair with this command:
+
+```bash
+duniter wizard key
+```
+
+Please note that the keypair filled in with this command will be stored in clear on the disk!
+To avoid this you can choose to set the keypair to be inserted only at the start of the node so that your keypair will be stored only in RAM, so add the option `--keyprompt` to the node start command.
+
+### Having several nodes with the same keypair
+
+It is possible to have several member nodes with your member keypair but in this case you must assign a unique identifier to each of your nodes, this unique identifier is named **prefix** because its unique role is to prefix the nonce of the blocks you are calculating in order to prevent two of your nodes from calculating the same proof.
+  
+On your 1st node: You don't have to do anything, the prefix is `1` by default.
+
+On your second node:
+
+```bash
+duniter config --prefix 2
+```
+
+On your third node:
+
+```bash
+duniter config --prefix 3
+```
+
+etc
+
+The prefix must be an integer between `1` and `899`.
+
+## Configuring the network
+
+### The APIs
+
+  In version `1.9.x` there are two APIs (Application Programming Interface) allowing your node to communicate with other programs.
+
+1. WS2P (WebSocketToPeer): this API is dedicated to inter-node communication, i.e. between your duniter node and other nodes of the same currency. **WS2P is enabled by default** on your duniter node.
+2. BMA (Basic Merkled Api) : this old API is dedicated to the communication with client software (Cesium, Sakia, Silkaj), it can also be used by any external program wishing to request the network (a website that would like to check the presence of a blockchain transaction for example). BMA is looking forward to developing a new client API to replace it. **BMA is disabled by default** on your duniter node.
+
+### Configuring WS2P
+
+#### Notion of WS2P Public and WS2P Private
+
+WS2P Private = outgoing WS2P connections.
+WS2P public = incoming WS2P connections.
+
+A WS2P connection between two duniter nodes always has a direction, it is initiated by one of the nodes which is therefore the initiator and the other is the acceptor. Connections that your duniter node initiates with other duniter nodes are outbound, depending on your private WS2P configuration. On the other hand, connections that your duniter node accepts from another node are incoming, depending on your public WS2P configuration.
+
+#### WS2P Private
+
+This mode is enabled by default and configured automatically.
+
+To change the maximum number of simultaneous outgoing WS2P connections:
+
+```bash
+duniter config --ws2p-max-private <count>
+```
+
+#### WS2P Public
+
+This mode is disabled by default, in order for it to work you must configure a WS2P endpoint that other duniter nodes will be able to use to reach you.
+
+First enable the public WS2P mode:
+
+```bash
+duniter config --ws2p-public
+```
+
+##### Configure a WS2P endpoint
+
+For WS2P Public to work you must configure a WS2P endpoint that other duniter nodes will be able to use to reach you. There are three possible cases:
+
+1. You host your node at home and you want to use UPnP (enabled by default) and then you don't have to do anything, duniter will automatically control your box to configure an endpoint.
+
+2. You host your node at home but you don't want to use UPnP (For security reasons for example).
+
+3. You host your node on a dedicated server on a hosting provider,  so you don't have UPnP.
+
+If you are in case 2 or 3, you must configure an access point manually:
+
+```bash
+duniter config --ws2p-noupnp --ws2p-port PORT --ws2p-host HOST --ws2p-remote-port REMOTE_PORT --ws2p-remote-host REMOTE_HOST
+```
+
+*The "remote" options correspond, for example, to a box that would do a NAT to your machine, or to a nginx/apache that would do a reverse proxy to your Duniter instance.*
+If your duniter node is connected to the internet through a box, you will need to configure port forwarding on your box by redirecting the port of your choice to the machine running your duniter node. In addition, in order to keep the local IP of this machine unchanged, you must ask your box to assign a permanent DHCP lease to it.
+
+##### Maximum number of public WS2P connections
+
+To change the maximum number of simultaneous incoming WS2P connections :
+
+```bash
+duniter config --ws2p-max-public <count>
+```
+
+#### Check your WS2P configuration
+
+```bash
+duniter ws2p show-conf
+```
+
+#### Define a path for your WS2P endpoint
+
+```bash
+duniter config --ws2p-remote-path <path>
+```
+
+Use only if you want to place your duniter node behind a reverse proxy.
+This option allows you to add a path for your public WS2P access point.
+Your WS2P access point will then be: `ws://host:port/path`.
+
+Note that `ws://` will be replaced by `wss://` if you set the remote port to 443.
+
+#### WS2P preferred/privileged nodes
+
+See [WS2P: preferred and privileged nodes](ws2p_preferred_privileged.md)
+
+### Configuring BMA
+
+The only thing you need to configure is an access point. Answer the questions in the following interactive command:
+
+```bash
+duniter wizard bma
+```
+
+Here is an example with my own configuration at home:
+
+```text
+2017-10-01T19:02:09+02:00 - debug: Plugging file system...
+2017-10-01T19:02:09+02:00 - debug: Loading conf...
+2017-10-01T19:02:10+02:00 - debug: Configuration saved.
+? IPv4 interface eth0 192.168.0.11
+? IPv6 interface None
+? Port 10901
+? Remote IPv4 81.64.137.147
+? Remote port 10901
+? Does this server has a DNS name? No
+2017-10-01T19:02:33+02:00 - debug: Configuration saved.
+```
+
+I'm wired, otherwise in wifi you have to choose the wlan0 option.
+Unfortunately my box does not support IPv6, so no IPv6 interface: None
+I don't use UPnP so I manually set a port (here 10901).
+Remote IPv4 corresponds to the public ip of my box, you can know it by visiting the site [https://www.myip.com/](https://www.myip.com/)
+Here I have not configured a DNS domain pointing to my duniter node.
+
+If like me you don't use UPnP, you have to manually configure a port forwarding on your box.
+
+### What is UPnP
+
+Universal Plug and Play (UPnP) is a protocol that, if enabled on your Internet box, allows the programs you use to configure the network themselves by " controlling " your box.  
+UPnP has the advantage of being practical, because it avoids you having to configure the network yourself, but in return you have to trust the programs you use because a malicious program can use UPnP to open your network in an unwanted way.  
+If you are not afraid of the command line and you are demanding on the security of your local network, we recommend you to disable UPnP.
+
+If you install duniter on a VPS or a dedicated server you will have to do without UPnP anyway.
+
+### Note on public WS2P (recommended)
+
+Nodes with public WS2P are necessary for the duniter network to work, and the more nodes with public WS2P, the more decentralized the network is.  
+This mode is optional if only because technically it is sometimes difficult or even impossible to be accessible from the outside (node behind a 4G router for example).
+
+## Synchronize your node
+
+To join the network of a currency you must synchronize with a node already on this network:
+
+```bash
+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`.
+
+## 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:
+
+```bash
+duniter start
+duniter direct_start
+duniter webstart
+duniter direct_webstart
+```
+
+The `direct_` prefix cancels the demonization, but then you will have to leave your terminal open or use nohup or screen or similar.
+
+In addition, the `direct_start` and `direct_webstart` commands accept the `--keyprompt` option (see the keypair section).
+
+## Tracking logs
+
+```bash
+duniter logs
+```
+
+## Go further
+
+See the [advanced commands](./advanced-commands.md).
diff --git a/doc/use/index.md b/doc/use/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..bb193e7c51136049eb005d96d0ca2df223036518
--- /dev/null
+++ b/doc/use/index.md
@@ -0,0 +1,13 @@
+Title: User documentation for Duniter Server
+Order: 9
+Date: 2017-09-22
+Slug: configurer
+Authors: elois
+
+# User documentation for Duniter Server
+
+- [Use Duniter Docker image](./docker.md)
+- [Configure Duniter server variant](./configure.md)
+- [WS2P: preferred and privileged nodes](./ws2p_preferred_privileged.md)
+- [Compile Duniter manually from source code](./manual_compilation.md)
+- [Advanced commands](./advanced-commands.md)
diff --git a/doc/use/install.md b/doc/use/install.md
new file mode 100644
index 0000000000000000000000000000000000000000..6bbdba2beefecbd08ed423bc4712f897e4d5de62
--- /dev/null
+++ b/doc/use/install.md
@@ -0,0 +1,54 @@
+Title: Install Duniter Server
+Order: 9
+Date: 2017-09-22
+Slug: configurer
+Authors: elois
+
+# Install Duniter Server
+
+## Distribution based on debian (including Ubuntu and some others)
+
+A debian package is provided for the `x86_64` and `armv7l` architectures. If you don't know what it is, you probably need package for `x86_64` architecture.
+
+If your architecture is different, you can try to [compile Duniter yourself](manual_compilation.md) or [install Duniter via Docker](docker.md).
+
+Go to the [release page](https://git.duniter.org/nodes/typescript/duniter/-/releases) to get a link to the last stable release.
+
+Download the debian package and install it:
+
+```bash
+sudo dpkg -i duniter-*-linux-x64.deb
+```
+
+Then [configure your Duniter node](configure.md).
+
+## Gentoo 64 bits
+
+In order to install Duniter on Gentoo, there is a package in the overlay [sveyret-overlay](https://github.com/sveyret/sveyret-overlay). A README file can be found in this overlay to help you and add it to the Portage tree.
+
+You will then be able to install the package net-p2p/duniter:
+
+`emerge -av net-p2p/duniter`
+
+The following USE flags allow you to decide what will be built:
+Flag Description
+desktop Build and install the desktop version instead of the server one
+gui Add the GUI (mandatory for desktop version, add the web interface for server version)
+
+The server version node for Gentoo can also be automatically started.
+
+## YunoHost
+
+A [YunoHost package](https://github.com/duniter/duniter_ynh) is available.
+
+## Other distributions
+
+You need to [compile Duniter yourself](manual_compilation.md) or [install Duniter via Docker](docker.md).
+
+## Mac
+
+In theory, duniter-server should compile under Mac, but this has never been tested. You can try to [compile it from source](manual_compilation.md) and let us know the result on the [Duniter forum](https://forum.duniter.org).
+
+## Windows
+
+Duniter-server does not support Windows.
diff --git a/doc/use/manual_compilation.md b/doc/use/manual_compilation.md
index e3be4c9aa420cf7a3b6b5bb7fb1c69a528b92ce0..14767b8e343a05e8f718ec42a96f7ae1384a542b 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/doc/use/ws2p_preferred_privileged.md b/doc/use/ws2p_preferred_privileged.md
new file mode 100644
index 0000000000000000000000000000000000000000..27b99fd6884c3d2623ec08cbe6f5ac315c5ecc74
--- /dev/null
+++ b/doc/use/ws2p_preferred_privileged.md
@@ -0,0 +1,51 @@
+Title: WS2P: preferred and privileged nodes
+Order: 9
+Date: 2020-10-04
+Slug: ws2p_preferred_privileged
+Authors: elois
+
+# WS2P: preferred and privileged nodes
+
+## Preferred nodes
+
+List of preferred public keys, your node will connect in priority to duniter nodes whose public key is in your preferred public key list.
+
+To add a public key to your list of preferred keys:
+
+```bash
+duniter config --ws2p-prefered-add <pubkey>
+```
+
+To remove a key from your preferred key list :
+
+```bash
+duniter config --ws2p-prefered-rm <pubkey>
+```
+
+To consult the list of your preferred keys:
+
+```bash
+duniter ws2p list-prefered
+```
+
+## Privileged nodes
+
+Just as you can set preferred keys for your outgoing WS2P connections, you can set privileged keys for your incoming WS2P connections. That is, if you receive more connection requests than the maximum number you have configured, connections initiated by nodes whose public key is part of your privileged keys will be given priority.
+
+To add a key to your privileged key list :
+
+```bash
+duniter config --ws2p-privileged-add <pubkey>
+```
+
+To remove a key from your privileged key list:
+
+```bash
+duniter config --ws2p-privileged-rm <pubkey>
+```
+
+To consult the list of your privileged keys:
+
+```bash
+duniter ws2p list-privileged
+```
diff --git a/duniter.sh b/duniter.sh
deleted file mode 100755
index 9fd048d494fa890a39eb74ec4a16d33104613706..0000000000000000000000000000000000000000
--- 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/format.sh b/format.sh
deleted file mode 100755
index eb54ab98b909362664f61801fa2b4e7be9a71f0f..0000000000000000000000000000000000000000
--- a/format.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/sh
-
-if [ "${1}" = "check" ]
-then
-    $HOME/.cargo/bin/cargo fmt -- --check && prettier --list-different "app/**/*.{ts,json}"
-elif [ "${1}" = "all" ]
-then
-    $HOME/.cargo/bin/cargo fmt && prettier --write "app/**/*.{ts,json}"
-else
-    echo  "first argument must be \"check\" or \"all\"."
-fi
diff --git a/neon/build.sh b/neon/build.sh
index a71a80952b88a5eddfde8e327a81717be7627ff9..c14123d4785e5ac5fa9230195c1293e4ea019e06 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 837b6fb098085aa2c03e9f16f2d3012e99586b67..5c5d995ccb8bd479fbfa44e430c87c86136aec34 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-lock.json b/package-lock.json
index 1b922eef5499e2fc7fbd62f9abb75a1834c270d9..52943a1c0cfc5395c16d105509eaa4f823830104 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -821,12 +821,6 @@
       "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
       "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
     },
-    "ci-info": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
-      "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
-      "dev": true
-    },
     "cjson": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/cjson/-/cjson-0.3.0.tgz",
@@ -997,12 +991,6 @@
         "graceful-readlink": ">= 1.0.0"
       }
     },
-    "compare-versions": {
-      "version": "3.6.0",
-      "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz",
-      "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==",
-      "dev": true
-    },
     "component-emitter": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
@@ -2084,15 +2072,6 @@
         "path-exists": "^4.0.0"
       }
     },
-    "find-versions": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz",
-      "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==",
-      "dev": true,
-      "requires": {
-        "semver-regex": "^2.0.0"
-      }
-    },
     "flat-cache": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
@@ -2486,76 +2465,6 @@
       "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==",
       "dev": true
     },
-    "husky": {
-      "version": "4.2.5",
-      "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.5.tgz",
-      "integrity": "sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ==",
-      "dev": true,
-      "requires": {
-        "chalk": "^4.0.0",
-        "ci-info": "^2.0.0",
-        "compare-versions": "^3.6.0",
-        "cosmiconfig": "^6.0.0",
-        "find-versions": "^3.2.0",
-        "opencollective-postinstall": "^2.0.2",
-        "pkg-dir": "^4.2.0",
-        "please-upgrade-node": "^3.2.0",
-        "slash": "^3.0.0",
-        "which-pm-runs": "^1.0.0"
-      },
-      "dependencies": {
-        "ansi-styles": {
-          "version": "4.2.1",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
-          "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
-          "dev": true,
-          "requires": {
-            "@types/color-name": "^1.1.1",
-            "color-convert": "^2.0.1"
-          }
-        },
-        "chalk": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
-          "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
-          "dev": true,
-          "requires": {
-            "ansi-styles": "^4.1.0",
-            "supports-color": "^7.1.0"
-          }
-        },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "dev": true,
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
-        "color-name": {
-          "version": "1.1.4",
-          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-          "dev": true
-        },
-        "has-flag": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-          "dev": true
-        },
-        "supports-color": {
-          "version": "7.1.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
-          "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
-          "dev": true,
-          "requires": {
-            "has-flag": "^4.0.0"
-          }
-        }
-      }
-    },
     "iconv-lite": {
       "version": "0.4.24",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -5438,12 +5347,6 @@
         "mimic-fn": "^2.1.0"
       }
     },
-    "opencollective-postinstall": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz",
-      "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==",
-      "dev": true
-    },
     "optimist": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
@@ -5612,15 +5515,6 @@
       "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
       "dev": true
     },
-    "pkg-dir": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
-      "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
-      "dev": true,
-      "requires": {
-        "find-up": "^4.0.0"
-      }
-    },
     "please-upgrade-node": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz",
@@ -5990,12 +5884,6 @@
       "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
       "dev": true
     },
-    "semver-regex": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz",
-      "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==",
-      "dev": true
-    },
     "send": {
       "version": "0.17.1",
       "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
@@ -6152,12 +6040,6 @@
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
       "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
     },
-    "slash": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
-      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
-      "dev": true
-    },
     "slice-ansi": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
@@ -6959,12 +6841,6 @@
       "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
       "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
     },
-    "which-pm-runs": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz",
-      "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=",
-      "dev": true
-    },
     "wide-align": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
diff --git a/package.json b/package.json
index 20e2cc5199ff30d8e87339205a21a0cdef0b9102..31b742349335208a5e70728f4af08d4f74a47a27 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,16 +28,14 @@
     "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",
     "prettier": "prettier --write app/**/*/*.ts",
-    "format:check": "./format.sh check",
-    "format:all": "./format.sh all"
+    "format:check": "prettier --list-different 'app/**/*.{ts,json}'",
+    "format:all": "prettier --write 'app/**/*.{ts,json}'"
   },
   "nyc": {
     "require": [
@@ -124,7 +122,6 @@
     "coveralls": "3.0.11",
     "eslint": "6.8.0",
     "eslint-plugin-mocha": "6.3.0",
-    "husky": ">=4",
     "lint-staged": ">=10",
     "mocha": "3.4.2",
     "mocha-eslint": "0.1.7",
@@ -139,12 +136,7 @@
   },
   "peerDependencies": {},
   "bin": {
-    "duniter": "./bin/duniter"
-  },
-  "husky": {
-    "hooks": {
-      "pre-commit": "cargo fmt -- --check && lint-staged"
-    }
+    "duniter": "./bin/duniter_js"
   },
   "lint-staged": {
     "app/**/*.{js,jsx,ts,tsx,md,html,css,scss}": [
diff --git a/release/Makefile b/release/Makefile
index 6d32bbb7e8e05133a10e980a3ce809593e59a127..3b9617d920f7f310ffbc30860498e89b61820849 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 c7b903b6a4d474027938603bb29916e013ba9e44..0c28b41fade02e4f1176cadaa74d77e48252cba1 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 d0487395e2ffe0a17575815e2c92bd32a0134b97..d72647d690a36c2ed83ad16e6630a2032d55464a 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 270d084bbf80834941b6d941221b9e10b53fa64d..e27a0d1534cbcae85cbbd44bdcfc5f9fb30893d0 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 9333250dfbe0aa44c0b43d7c5bf8d57dd7ed6dec..d6572f2af2eb6b78932caf294218ac89d74a16da 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 0880595b79963dc979553d50bd814080dcf49240..930075ae959f26d3cf897736fc9a50f1631eca39 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 cfa07fcd9d0255c85ea3ed5e5679b316eff1d3eb..3a6bf2c7830cd3a129d6cb03d8167ecbfa8e4953 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 ef29b84ee24f5bd2c1aa6ba410d365139628e395..5a13c8cb508dabd7623ee926afb3234ed1051d56 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 88cbefcf14ccf30ed971ba0ef5b0312958bc2481..f8c1426efb26fea1d75e04d83fd3b9ad99c9ccdb 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 0000000000000000000000000000000000000000..25acdd272dbbeec20fb573ab48fac0f65b18c63c
--- /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 0000000000000000000000000000000000000000..2eae921c4ba079010de43bc4735c1f5e7a1b9984
--- /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 0000000000000000000000000000000000000000..5158c83bfbefab00dae7b138aec25a662dea7d2e
--- /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 0000000000000000000000000000000000000000..bdc246701f04f70091dcda3ee9aca755fba9081f
--- /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 0000000000000000000000000000000000000000..1e66d25133280ec015173bb8176aacf6f0ba232a
--- /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 0000000000000000000000000000000000000000..95175adcd830ca2ab4dc820ce54f387d14d76a73
--- /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 0000000000000000000000000000000000000000..91f45095ac8cb54a17c0f69046d5054a09107bed
--- /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 ee920422d4f933b3e1751ea3de88649581c7b8ee..6f8ed1ea1d43915cabd4d8efa80b6f198ec137f1 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/fast/modules/keypair/keypair-module-test.ts b/test/fast/modules/keypair/keypair-module-test.ts
index 45998ad1e6b71463fa2f53ed9783c624435b67c5..1d755d1f53cf609363890a4af10550aee7b004fe 100644
--- a/test/fast/modules/keypair/keypair-module-test.ts
+++ b/test/fast/modules/keypair/keypair-module-test.ts
@@ -18,19 +18,6 @@ const should = require('should');
 
 describe('Module usage', () => {
 
-  it('wrong options should throw', async () => {
-    let errMessage;
-    try {
-      const stack = Statics.minimalStack();
-      stack.registerDependency(KeypairDependency, 'duniter-keypair');
-      await stack.executeStack(['node', 'index.js', 'config', '--memory', '--keyN', '2048']);
-    } catch (e) {
-      errMessage = e.message;
-    }
-    should.exist(errMessage);
-    should.equal(errMessage, 'Missing --salt and --passwd options along with --keyN|keyr|keyp option');
-  })
-
   it('no options on brand new node should generate random key', async () => {
     const stack = Statics.minimalStack();
     stack.registerDependency(KeypairDependency, 'duniter-keypair');
diff --git a/test/integration/misc/cli.ts b/test/integration/misc/cli.ts
index 070d6671c11a71abccda391d7e4f428a345da674..9f142e4b321f4eb7faabce5a0c65327867009d56 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 4f22f777415af6717a372c8936e7a2fc7ceacc82..0000000000000000000000000000000000000000
--- 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