diff --git a/gui/duniter-desktop b/gui/duniter-desktop
new file mode 100755
index 0000000000000000000000000000000000000000..6a0112e25bcaaf35967e8463dabd938fbf79f081
--- /dev/null
+++ b/gui/duniter-desktop
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+# Get the directory name of this script, following symlinks
+pushd . >/dev/null
+DUNITER_DIRECTORY="${BASH_SOURCE[0]}";
+while [[ -h "${DUNITER_DIRECTORY}" ]]; do
+    cd "$(dirname "${DUNITER_DIRECTORY}")" >/dev/null
+    DUNITER_DIRECTORY="$(readlink "$(basename "${DUNITER_DIRECTORY}")")"
+done
+cd "$(dirname "${DUNITER_DIRECTORY}")" >/dev/null
+DUNITER_DIRECTORY="${PWD}";
+popd >/dev/null
+
+# Execute NW
+"${DUNITER_DIRECTORY}"/node_modules/.bin/nw "${DUNITER_DIRECTORY}"
diff --git a/release/Makefile b/release/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..8e2b531c8b393b50f9365100e3c2a1fea5004ea2
--- /dev/null
+++ b/release/Makefile
@@ -0,0 +1,166 @@
+# If “make” is called from a directory controlled by git (the main directory), it will create a directory
+# named “work” with a sub-directory taken from the “DEST” property, if provided. It will then clone itself
+# there, and the command will be executed from the clone.
+# If “make” is not called from a directory controlled by git, it will be executed in place.
+#
+# Requires the following dependencies:
+#  * Python 2.7 (build only)
+#  * NodeJS >=10.x (build and runtime)
+#  * npm (build only)
+#  * Rust (build only)
+#
+# Following parameters may be provided:
+#  * ARCH: The target architecture (default: autodetect).
+#  * PLATFORM: The target platform (default: autodetect).
+#  * ADD_DEBUG: Set to Y to add debug information in result (default: Y).
+#  * DEST: The destination sub-folder (default: empty).
+#
+# Main targets are:
+#  * desktop (default): Build a full desktop application.
+#  * server-gui: Build a server with GUI.
+#  * server: Build a server without GUI.
+#  * base-gui: Prepare any application requiring GUI.
+#  * base: Prepare any application.
+#  * wipe: Remove files which should not be delivered.
+#  * clean (only in main directory): remove the Makefile from target directory.
+#  * mrproper (only in main directory): remove the target directory.
+
+# This will depend on the current version
+NW_VERSION = 0.33.1
+ADDON_VERSION = 64
+DUNITER_UI_VERSION = 1.7.x
+DEV_FILES = $(wildcard .eslint* .git* .npm* .prettierignore Cargo.* deny.toml gui license-header.txt npm* \
+	tsconfig* tslint* yarn*)
+
+# This may be overriden by caller
+ARCH = $(shell uname -m | sed -e "s/86_//")
+PLATFORM = $(shell uname -s | tr A-Z a-z)
+ADD_DEBUG = Y
+DEST =
+
+# Keep all intermediate targets
+.SECONDARY:
+
+######
+# We are on project copy
+######
+ifneq ($(wildcard package.json),)
+unexport MAKEFLAGS
+unexport ARCH
+unexport PLATFORM
+unexport ADD_DEBUG
+unexport DEST
+
+export npm_config_unsafe_perm = true
+
+simplename = $(patsubst node_%,%,$(basename $(notdir $(1))))
+nodedep = node_modules/$(1)/package.json
+leveldown.COMPILED = leveldown/lib/binding/Release/node-v$(ADDON_VERSION)-$(PLATFORM)-$(ARCH)/leveldown.node
+leveldown.PREPARE = cp release/resources/leveldown-fix.json node_modules/leveldown/package.json
+leveldown.INTERMEDIATE = build/Release
+sqlite3.COMPILED = sqlite3/lib/binding/node-v$(ADDON_VERSION)-$(PLATFORM)-$(ARCH)/node_sqlite3.node
+sqlite3.INTERMEDIATE = lib/binding/node-webkit-v${NW_VERSION}-$(PLATFORM)-$(ARCH)
+nw.VERSION = @$(NW_VERSION)
+duniter-ui.VERSION = @$(DUNITER_UI_VERSION)
+
+# Build the desktop
+.PHONY: desktop
+desktop: index.html
+	$(MAKE) ADD_DEBUG=$(ADD_DEBUG) wipe
+
+# Build the server with GUI
+.PHONY: server-gui
+server-gui: base-gui
+	$(MAKE) ADD_DEBUG=$(ADD_DEBUG) wipe
+
+# Build the server
+.PHONY: server
+server: base
+	$(MAKE) ADD_DEBUG=$(ADD_DEBUG) wipe
+
+# Build the base with GUI
+.PHONY: base-gui
+base-gui: $(call nodedep,duniter-ui) base
+
+# Build the base
+.PHONY: base
+base: | node_modules
+
+index.html: $(call nodedep,duniter-ui) $(addprefix node_modules/,$(leveldown.COMPILED) $(sqlite3.COMPILED))
+	@sed -i "s/\"main\": \"index.js\",/\"main\": \"index.html\",/" package.json
+	@mv gui/* .
+
+node_modules/%.node: $(call nodedep,nw-gyp) $(call nodedep,nw)
+	@$(if $($(call simplename,$@).PREPARE),$($(call simplename,$@).PREPARE))
+	@PATH=$(shell npm bin):${PATH} node-pre-gyp \
+		-C node_modules/$(call simplename,$@) \
+		--runtime=node-webkit \
+		--target=$(NW_VERSION) \
+		--target_arch=$(ARCH) \
+		--target_platform=$(PLATFORM) \
+		configure build
+	@mkdir -p "$(dir $@)"
+	@cp "node_modules/$(call simplename,$@)/$($(call simplename,$@).INTERMEDIATE)/$(notdir $@)" "$@"
+
+node_modules/%/package.json: | node_modules
+	@NEON_BUILD_RELEASE="true" npm add $*$($*.VERSION)
+
+node_modules:
+	@npm uninstall husky
+	@NEON_BUILD_RELEASE="true" npm install
+
+# Wipe the delivery result
+.PHONY: wipe
+wipe:
+	@npm uninstall nw-gyp
+	@PATH=$(shell npm bin):${PATH} npm prune --production
+	@find -name "*.ts" ! -name "*.d.ts" -delete
+ifneq ($(ADD_DEBUG),Y)
+	@find -name "*.d.ts" -delete -o -name "*.js.map" -delete
+endif
+	@rm -rf $(DEV_FILES)
+	@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
+
+######
+# We are on main project
+######
+else ifneq ($(wildcard ../package.json),)
+WORK = ../work
+TARGET_DIR = $(abspath $(WORK)/$(DEST)/)
+MAIN_ACTIONS = desktop server-gui server base-gui base wipe
+
+.PHONY: $(MAIN_ACTIONS)
+$(MAIN_ACTIONS): $(TARGET_DIR)/Makefile
+	@$(MAKE) -C $(TARGET_DIR) $@
+
+$(TARGET_DIR)/Makefile: | $(TARGET_DIR)
+	@find .. -mindepth 1 -maxdepth 1 ! -name work ! -name .git -print0 |\
+		xargs -0 -I{} cp -r {} "$(TARGET_DIR)" || exit 1
+	@cp Makefile "$(TARGET_DIR)"
+
+$(TARGET_DIR):
+	@mkdir -p "$(TARGET_DIR)"
+
+.PHONY: clean
+clean:
+	@rm "$(TARGET_DIR)/Makefile"
+
+.PHONY: mrproper
+mrproper:
+	@rm -rf "$(TARGET_DIR)"
+
+######
+# We are… lost?
+######
+else
+%:
+	$(error Cannot figure where I am)
+
+######
+# Main project or copy
+######
+endif