diff --git a/.gitignore b/.gitignore
index 80cf8f117b17a8d04f327de92f3bf5b8c60c8614..f4a63d6d8e22c714a03563eb4b9be823fdff3d59 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,12 +19,16 @@ changelog.html
 obj/
 build/
 build-local/
-install-local/
+build-global/
+install/
 *.vcxproj
 *.vcxproj.filters
 *qmlcache.qrc
 .deploy.stamp
 
+*.log
+*.pid
+
 # auto-gen files
 src/app/resources.qrc
 src/app/qml.qrc
diff --git a/INSTALL.md b/INSTALL.md
index 1ea9a4712dcc820a6b7b5a9bc3d823b7fa5dc583..b47cdc02c989640e895bd8718e3eea91738c1caf 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -1,8 +1,8 @@
 # Build instructions
 
-There is basically two ways to build `client-qt`:
+There are essentially two ways to build `client-qt`:
 
-- Use `build.py` script which will build all Jami (daemon/client lib/client-qt)
+- Use `build.py` script which will build all of Jami (daemon and client)
 - Build only this client.
 
 ## Disclaimer
@@ -55,14 +55,14 @@ Then, you can build the project
 
 ### With build.py
 
-```bash
-git clone https://review.jami.net/jami-project
-```
-
-Jami installer uses **python3 (minimum v3.6)**. If it's not installed, please install it:
+The build.py Jami installer uses **python3 (minimum v3.6)**.  If it's not installed,
+please install it.  Then run the following to initialize and update
+the submodules to set them at the top of their latest commit (ideal
+for getting the latest development versions; otherwise, you can use
+`git submodule update --init` then checkout specific commits for each
+submodule).
 
 ```bash
-cd jami-project/
 ./build.py --init
 ```
 
@@ -71,21 +71,24 @@ Then you will need to install dependencies:
 - For GNU/Linux
 
 ```bash
-./build.py --dependencies --qt # needs sudo
+./build.py --dependencies # needs sudo
 ```
 
-Then, you can build daemon and the client with:
+Then, you can build daemon and the client using:
 
 ```bash
-./build.py --install --qt
+./build.py --install
 ```
 
-And you will have the daemon in `daemon/bin/jamid` and the client in `client-qt/build-local/jami`. You also can run it with
+If you use a Qt version that is not system-wide installed, you need to
+specify its path using the `--qt` flag, e.g.
+`./build.py --install --qt=/home/<username>/Qt/6.2.1/gcc_64`.
 
-If you use a Qt version that is not wide-system installed you need to specify its path after the `--qt` flag, i. e., `./build.py --install --qt /home/<username>/Qt/6.2.1/gcc_64
+Now you will have the daemon in `daemon/bin/jamid` and the client in
+`build/bin/jami`.  You can now run Jami using
 
 ```bash
-./build.py --run --qt
+./build.py --run
 ```
 
 Notes:
@@ -122,7 +125,7 @@ cmake can take some options:
 e.g. (with Qt version from https://jami.net)
 
 ```
-cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=jami-project/install/client-qt -DCMAKE_PREFIX_PATH=/usr/lib/libqt-jami
+cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=../install -DCMAKE_PREFIX_PATH=/usr/lib/libqt-jami
 ```
 
 After the build has finished, you are finally ready to launch jami in your build directory.
@@ -208,9 +211,8 @@ Only 64-bit MSVC build can be compiled.
 
 ```bash
     python build.py --install
-    cd client-qt
-    python build.py init
-    python build.py --qtver <your qt version>
+    python extras\scripts\build-windows.py init
+    python extras\scripts\build-windows.py --qtver <your qt version>
 ```
 
 - Then you should be able to use the Visual Studio Solution file in client-qt folder **(Configuration = Release, Platform = x64)**
@@ -221,15 +223,15 @@ Only 64-bit MSVC build can be compiled.
 
 **Daemon**
 
-- Make sure that dependencies is built by make-ring.py
-- On MSVC folder (jami-project\daemon\MSVC):
+- Make sure that dependencies is built by build.py
+- On MSVC folder (daemon\MSVC):
 
 ```sh
     cmake -DCMAKE_CONFIGURATION_TYPES="ReleaseLib_win32" -DCMAKE_VS_PLATFORM_NAME="x64" -G "Visual Studio 16 2019" -A x64 -T '$(DefaultPlatformToolset)' ..
     python winmake.py -b daemon
 ```
 
-- This will generate a `.lib` file in the path of ring-project\daemon\MSVC\x64\ReleaseLib_win32\bin
+- This will generate a `.lib` file in the path of daemon\MSVC\x64\ReleaseLib_win32\bin
 
 > Note: each dependencies contrib for daemon can also be updated individually <br>
 > For example:
@@ -240,15 +242,14 @@ Only 64-bit MSVC build can be compiled.
 
 **Jami**
 
-- Make sure that daemon, is built first
+- Make sure that daemon is built first.  Then,
 
 ```bash
-    cd client-qt
-    python build.py init
-    python build.py
+    python extras\scripts\build-windows.py init
+    python extras\scripts\build-windows.py
 ```
 
-Note: if your qt version is different than 6.2.3, you need to use `python build.py --qtver <your qt version>`.
+Note: if your qt version is different than 6.2.3, you need to use `python extras\scripts\build-windows.py --qtver <your qt version>`.
 
 ## Building On MacOS
 
@@ -272,33 +273,31 @@ Then, you can build the project
 **Build with build.py**
 
 ```bash
-git clone https://review.jami.net/jami-project
- cd jami-project
 ./build.py --init
-./build.py --dependencies --qt
-./build.py --install --qt
+./build.py --dependencies
+./build.py --install
 ```
 
 If you use a Qt version that is installed in a different than standard location you need to specify its path
 
 ```bash
-./build.py --install --qt QT_ROOT_DIRECTORY=your_qt_directory
+QT_ROOT_DIRECTORY=your_qt_directory ./build.py --install
 ```
 
-Built client could be find in `client-qt/build-local/Jami`
+Built client could be find in `build/Jami`
 
 ## Packaging On Native Windows
 
 - To be able to generate a msi package, first download and install [Wixtoolset](https://wixtoolset.org/releases/).
 - In Visual Studio, download WiX Toolset Visual Studio Extension.
-- Build client-qt project first, then the JamiInstaller project, msi package should be stored in jami-project\client-qt\JamiInstaller\bin\Release
+- Build client-qt project first, then the JamiInstaller project, msi package should be stored in JamiInstaller\bin\Release
 
 ## Testing for Client-qt on Windows
 
 - We currently use [GoogleTest](https://github.com/google/googletest) and [Qt Quick Test](https://doc.qt.io/qt-5/qtquicktest-index.html#introduction) in our product. To build and run tests, you could use the following command.
 
 ```
-    python build.py [runtests, pack]
+    python extras\scripts\build-windows.py [runtests, pack]
 ```
 
 - Note that, for tests, the path of local storage files for jami will be changed based on following environment variables.
@@ -309,11 +308,11 @@ Built client could be find in `client-qt/build-local/Jami`
     %JAMI_CACHE_HOME% = %TEMP% + '\\jami_test\\.cache'
 ```
 
-- These environment variables will be temporarily set when using make-client.py to run tests.
+- These environment variables will be temporarily set when using build-windows.py to run tests.
 
 ## Debugging
 
-Compile the client with `BUILD=Debug` and compile LibRingClient with
-`-DCMAKE_BUILD_TYPE=Debug`
+Compile the client with with `-DCMAKE_BUILD_TYPE=Debug`.
 
-Then, if you want to enable logging when running `jami` launch it with `-d` or `--debug`
+Then, if you want to enable logging when running `jami`, launch it
+with `-d` or `--debug`.
diff --git a/build.py b/build.py
new file mode 100755
index 0000000000000000000000000000000000000000..d7617e522b2b7bcbf8fdb81743df9cc60f8dba31
--- /dev/null
+++ b/build.py
@@ -0,0 +1,696 @@
+#!/usr/bin/env python3
+# build.py --- Convenience script for building and running Jami
+
+# Copyright (C) 2016-2022 Savoir-faire Linux Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+
+import argparse
+import contextlib
+import multiprocessing
+import os
+import platform
+import shlex
+import shutil
+import subprocess
+import sys
+import time
+
+OSX_DISTRIBUTION_NAME = "osx"
+WIN32_DISTRIBUTION_NAME = "win32"
+
+# vs vars
+win_sdk_default = '10.0.16299.0'
+win_toolset_default = '142'
+
+APT_BASED_DISTROS = [
+    'debian',
+    'linuxmint',
+    'raspbian',
+    'trisquel',
+    'ubuntu',
+]
+
+DNF_BASED_DISTROS = [
+    'fedora', 'rhel',
+]
+
+PACMAN_BASED_DISTROS = [
+    'arch', 'parabola',
+]
+
+ZYPPER_BASED_DISTROS = [
+    'opensuse-leap',
+]
+
+FLATPAK_BASED_RUNTIMES = [
+    'org.gnome.Platform',
+]
+
+APT_INSTALL_SCRIPT = [
+    'apt-get update',
+    'apt-get install %(packages)s'
+]
+
+BREW_UNLINK_SCRIPT = [
+    'brew unlink %(packages)s'
+]
+
+BREW_INSTALL_SCRIPT = [
+    'brew update',
+    'brew install %(packages)s',
+    'brew link --force --overwrite %(packages)s'
+]
+
+RPM_INSTALL_SCRIPT = [
+    'dnf update',
+    'dnf install %(packages)s'
+]
+
+PACMAN_INSTALL_SCRIPT = [
+    'pacman -Sy',
+    'pacman -S --asdeps --needed %(packages)s'
+]
+
+ZYPPER_INSTALL_SCRIPT = [
+    'zypper update',
+    'zypper install %(packages)s'
+]
+
+ZYPPER_DEPENDENCIES = [
+    # build system
+    'autoconf', 'autoconf-archive', 'automake', 'cmake', 'make', 'patch', 'gcc-c++',
+    'libtool', 'which', 'pandoc','nasm', 'doxygen', 'graphviz',
+    # contrib dependencies
+    'curl', 'gzip', 'bzip2',
+    # daemon
+    'speexdsp-devel', 'speex-devel', 'libdbus-c++-devel', 'jsoncpp-devel', 'yaml-cpp-devel',
+    'yasm', 'libuuid-devel', 'libnettle-devel', 'libopus-devel', 'libexpat-devel',
+    'libgnutls-devel', 'msgpack-devel', 'libavcodec-devel', 'libavdevice-devel', 'pcre-devel',
+    'alsa-devel', 'libpulse-devel', 'libudev-devel', 'libva-devel', 'libvdpau-devel',
+    'libopenssl-devel', 'libavutil-devel',
+]
+
+ZYPPER_CLIENT_DEPENDENCIES = [
+    # lrc
+    'qt6-core-devel', 'qt6-dbus-devel', 'qt6-linguist-devel',
+    # client-qt
+    'qt6-svg-devel', 'qt6-multimedia-devel', 'qt6-declarative-devel',
+    'qt6-quickcontrols2-devel',
+    'qrencode-devel', 'NetworkManager-devel'
+]
+
+ZYPPER_QT_WEBENGINE = [
+    'qt6-webenginecore-devel',
+    'qt6-webenginequick-devel',
+    'qt6-webenginewidgets-devel'
+]
+
+DNF_DEPENDENCIES = [
+    'autoconf', 'autoconf-archive', 'automake', 'cmake', 'make', 'speexdsp-devel', 'pulseaudio-libs-devel',
+    'libtool', 'dbus-devel', 'expat-devel', 'pcre-devel', 'doxygen', 'graphviz',
+    'yaml-cpp-devel', 'boost-devel', 'dbus-c++-devel', 'dbus-devel',
+    'libXext-devel', 'libXfixes-devel', 'yasm',
+    'speex-devel', 'chrpath', 'check', 'astyle', 'uuid-c++-devel', 'gettext-devel',
+    'gcc-c++', 'which', 'alsa-lib-devel', 'systemd-devel', 'libuuid-devel',
+    'uuid-devel', 'gnutls-devel', 'nettle-devel', 'opus-devel', 'speexdsp-devel',
+    'yaml-cpp-devel', 'swig', 'jsoncpp-devel',
+    'patch', 'libva-devel', 'openssl-devel', 'libvdpau-devel', 'msgpack-devel',
+    'sqlite-devel', 'openssl-static', 'pandoc', 'nasm',
+    'bzip2'
+]
+
+DNF_CLIENT_DEPENDENCIES = [
+    'libnotify-devel',
+    'qt6-qtbase-devel',
+    'qt6-qtsvg-devel', 'qt6-qtmultimedia-devel', 'qt6-qtdeclarative-devel',
+    'qrencode-devel', 'NetworkManager-libnm-devel'
+]
+
+DNF_QT_WEBENGINE = [ 'qt6-qtwebengine-devel' ]
+
+APT_DEPENDENCIES = [
+    'autoconf', 'autoconf-archive', 'autopoint', 'automake', 'cmake', 'make', 'dbus', 'doxygen', 'graphviz',
+    'g++', 'gettext', 'libasound2-dev', 'libavcodec-dev',
+    'libavdevice-dev', 'libavformat-dev', 'libboost-dev',
+    'libcppunit-dev', 'libdbus-1-dev',
+    'libdbus-c++-dev', 'libebook1.2-dev', 'libexpat1-dev', 'libgnutls28-dev',
+    'libgtk-3-dev', 'libjack-dev',
+    'libopus-dev', 'libpcre3-dev', 'libpulse-dev', 'libssl-dev',
+    'libspeex-dev', 'libspeexdsp-dev', 'libswscale-dev', 'libtool',
+    'libudev-dev', 'libyaml-cpp-dev', 'sip-tester', 'swig',
+    'uuid-dev', 'yasm', 'libjsoncpp-dev', 'libva-dev', 'libvdpau-dev', 'libmsgpack-dev',
+    'pandoc', 'nasm', 'dpkg-dev'
+]
+
+APT_CLIENT_DEPENDENCIES = [
+    'qt6-base-dev', 'qt6-tools-dev', 'qt6-tools-dev-tools',
+    'qt6-l10n-tools', 'libnotify-dev', 'libqt6sql6-sqlite',
+    'libqt6core5compat6-dev', 'libqt6networkauth6-dev',
+    'qt6-multimedia-dev', 'libqt6svg6-dev', 'qt6-declarative-dev',
+    'qml6-module-qt-labs-qmlmodels',
+    'qml6-module-qt5compat-graphicaleffects',
+    'qml6-module-qtqml-workerscript',
+    'qml6-module-qtmultimedia',
+    'qml6-module-qtquick', 'qml6-module-qtquick-controls',
+    'qml6-module-qtquick-dialogs', 'qml6-module-qtquick-layouts',
+    'qml6-module-qtquick-shapes', 'qml6-module-qtquick-window',
+    'qml6-module-qtquick-templates', 'qml6-module-qt-labs-platform',
+    'libqrencode-dev', 'libnm-dev'
+]
+
+APT_QT_WEBENGINE = [
+    'libqt6webengine6-data', 'libqt6webenginecore6-bin',
+    'qt6-webengine-dev', 'qt6-webengine-dev-tools',
+    'qml6-module-qtwebengine', 'qml6-module-qtwebchannel' ]
+
+PACMAN_DEPENDENCIES = [
+    'autoconf', 'autoconf-archive', 'gettext', 'cmake', 'dbus', 'doxygen', 'graphviz',
+    'gcc', 'ffmpeg', 'boost', 'cppunit', 'libdbus', 'dbus-c++', 'libe-book', 'expat',
+    'jack', 'opus', 'pcre', 'libpulse', 'speex', 'speexdsp', 'libtool', 'yaml-cpp',
+    'swig', 'yasm', 'make', 'patch', 'pkg-config',
+    'automake', 'libva', 'libvdpau', 'openssl', 'pandoc', 'nasm'
+]
+
+PACMAN_CLIENT_DEPENDENCIES = [
+    # lrc
+    'qt6-base',
+    # client-qt
+    'qt6-declarative', 'qt6-5compat', 'qt6-multimedia',
+    'qt6-networkauth', 'qt6-shadertools',
+    'qt6-svg', 'qt6-tools',
+    'qrencode', 'libnm'
+]
+
+PACMAN_QT_WEBENGINE = [ 'qt6-webengine' ]
+
+OSX_DEPENDENCIES = [
+    'autoconf', 'cmake', 'gettext', 'pkg-config', 'qt6',
+    'libtool', 'yasm', 'nasm', 'automake'
+]
+
+OSX_DEPENDENCIES_UNLINK = [
+    'autoconf*', 'cmake*', 'gettext*', 'pkg-config*', 'qt*', 'qt@6.*',
+    'libtool*', 'yasm*', 'nasm*', 'automake*', 'gnutls*', 'nettle*', 'msgpack*'
+]
+
+UNINSTALL_DAEMON_SCRIPT = [
+    'make -C daemon uninstall'
+]
+
+ASSUME_YES_FLAG = ' -y'
+ASSUME_YES_FLAG_PACMAN = ' --noconfirm'
+
+def run_powershell_cmd(cmd):
+    p = subprocess.Popen(["powershell.exe", cmd], stdout=sys.stdout)
+    p.communicate()
+    p.wait()
+    return
+
+
+def run_dependencies(args):
+    if args.distribution == WIN32_DISTRIBUTION_NAME:
+        run_powershell_cmd(
+            'Set-ExecutionPolicy Unrestricted; .\\extras\\scripts\\install-deps-windows.ps1')
+
+    elif args.distribution in APT_BASED_DISTROS:
+        if args.assume_yes:
+            for i, _ in enumerate(APT_INSTALL_SCRIPT):
+                APT_INSTALL_SCRIPT[i] += ASSUME_YES_FLAG
+        execute_script(
+            APT_INSTALL_SCRIPT,
+            {"packages": ' '.join(map(shlex.quote, APT_DEPENDENCIES))})
+        if not args.no_webengine:
+            APT_CLIENT_DEPENDENCIES.extend(APT_QT_WEBENGINE)
+        execute_script(
+            APT_INSTALL_SCRIPT,
+            {"packages": ' '.join(map(shlex.quote, APT_CLIENT_DEPENDENCIES))})
+
+    elif args.distribution in DNF_BASED_DISTROS:
+        if args.assume_yes:
+            for i, _ in enumerate(DNF_INSTALL_SCRIPT):
+                DNF_INSTALL_SCRIPT[i] += ASSUME_YES_FLAG
+        execute_script(
+            RPM_INSTALL_SCRIPT,
+            {"packages": ' '.join(map(shlex.quote, DNF_DEPENDENCIES))})
+        if not args.no_webengine:
+            DNF_CLIENT_DEPENDENCIES.extend(DNF_QT_WEBENGINE)
+        execute_script(
+            RPM_INSTALL_SCRIPT,
+            {"packages": ' '.join(map(shlex.quote, DNF_CLIENT_DEPENDENCIES))})
+
+    elif args.distribution in PACMAN_BASED_DISTROS:
+        if args.assume_yes:
+            for i, _ in enumerate(PACMAN_INSTALL_SCRIPT):
+                PACMAN_INSTALL_SCRIPT[i] += ASSUME_YES_FLAG_PACMAN
+        execute_script(
+            PACMAN_INSTALL_SCRIPT,
+            {"packages": ' '.join(map(shlex.quote, PACMAN_DEPENDENCIES))})
+        if not args.no_webengine:
+            PACMAN_CLIENT_DEPENDENCIES.extend(PACMAN_QT_WEBENGINE)
+        execute_script(
+            PACMAN_INSTALL_SCRIPT,
+            {"packages": ' '.join(map(shlex.quote, PACMAN_CLIENT_DEPENDENCIES))})
+
+    elif args.distribution in ZYPPER_BASED_DISTROS:
+        if args.assume_yes:
+            for i, _ in enumerate(ZYPPER_INSTALL_SCRIPT):
+                ZYPPER_INSTALL_SCRIPT[i] += ASSUME_YES_FLAG
+        execute_script(
+            ZYPPER_INSTALL_SCRIPT,
+            {"packages": ' '.join(map(shlex.quote, ZYPPER_DEPENDENCIES))})
+        if not args.no_webengine:
+            ZYPPER_CLIENT_DEPENDENCIES.extend(ZYPPER_QT_WEBENGINE)
+        execute_script(
+            ZYPPER_INSTALL_SCRIPT,
+            {"packages": ' '.join(map(shlex.quote, ZYPPER_CLIENT_DEPENDENCIES))})
+
+    elif args.distribution == OSX_DISTRIBUTION_NAME:
+        execute_script(
+            BREW_UNLINK_SCRIPT,
+            {"packages": ' '.join(map(shlex.quote, OSX_DEPENDENCIES_UNLINK))},
+            False
+        )
+        execute_script(
+            BREW_INSTALL_SCRIPT,
+            {"packages": ' '.join(map(shlex.quote, OSX_DEPENDENCIES))},
+            False
+        )
+
+    elif args.distribution == WIN32_DISTRIBUTION_NAME:
+        print("The win32 version does not install dependencies with this script.\nPlease continue with the --install instruction.")
+        sys.exit(1)
+    elif args.distribution == 'guix':
+        print("Building the profile defined in 'guix/manifest.scm'...")
+        execute_script(['guix shell --manifest=guix/manifest.scm -- true'])
+
+    else:
+        print("Not yet implemented for current distribution (%s). Please continue with the --install instruction. Note: You may need to install some dependencies manually." %
+              args.distribution)
+        sys.exit(1)
+
+
+def run_init():
+    # Extract modules path from '.gitmodules' file
+    module_names = []
+    with open('.gitmodules') as fd:
+        for line in fd.readlines():
+            if line.startswith('[submodule "'):
+                module_names.append(line[line.find('"')+1:line.rfind('"')])
+
+    subprocess.run(["git", "submodule", "update", "--init"], check=True)
+    subprocess.run(["git", "submodule", "foreach",
+                    "git checkout master && git pull"], check=True)
+
+    for name in module_names:
+        hooks_dir = f'.git/modules/{name}/hooks'
+        if not os.path.exists(hooks_dir):
+            os.makedirs(hooks_dir)
+        copy_file("./extras/scripts/commit-msg", f'{hooks_dir}/commit-msg')
+
+    module_names_to_format = ['daemon']
+    for name in module_names_to_format:
+        hooks_dir = f'.git/modules/{name}/hooks'
+        execute_script(['./extras/scripts/format.sh --install %(path)s'],
+                       {"path": hooks_dir})
+
+    subprocess.run(["git", "submodule", "update", "--recursive", "--init"],
+                   check=True)
+
+
+def copy_file(src, dest):
+    print(f'Copying: {src} to {dest}')
+    try:
+        shutil.copy2(src, dest)
+    # e.g. src and dest are the same file
+    except shutil.Error as e:
+        print(f'Error: {e}')
+    # e.g. source or destination doesn't exist
+    except IOError as e:
+        print(f'Error: {e.strerror}')
+
+
+@contextlib.contextmanager
+def cwd(path):
+    owd = os.getcwd()
+    os.chdir(path)
+    try:
+        yield
+    finally:
+        os.chdir(owd)
+
+
+def run_install(args):
+    # Platforms with special compilation scripts
+    if args.distribution == WIN32_DISTRIBUTION_NAME:
+        winmake = 'daemon/compat/msvc/winmake.py'
+        with cwd(os.path.dirname(winmake)):
+            execute_script(
+                f'python {winmake} -iv -t {args.toolset} -s {args.sdk} -b daemon')
+        build_windows = 'extras/scripts/build-windows.py'
+        execute_script(f'python {build_windows} init')
+        execute_script(f'python {build_windows}')
+        return True
+
+    # Unix-like platforms
+    environ = os.environ.copy()
+
+    install_args = ['-p', str(multiprocessing.cpu_count())]
+    if args.static:
+        install_args.append('-s')
+    if args.global_install:
+        install_args.append('-g')
+    if args.prefix:
+        install_args += ('-P', args.prefix)
+    if not args.priv_install:
+        install_args.append('-u')
+    if args.debug:
+        install_args.append('-d')
+    if args.no_libwrap:
+        install_args.append('-W')
+    if args.no_webengine:
+        install_args.append('-w')
+
+    if args.distribution == OSX_DISTRIBUTION_NAME:
+        # The `universal_newlines` parameter has been renamed to `text` in
+        # Python 3.7+ and triggering automatical binary to text conversion is
+        # what it actually does
+        proc = subprocess.run(["brew", "--prefix", "qt6"],
+                              stdout=subprocess.PIPE, check=True,
+                              universal_newlines=True)
+
+        environ['CMAKE_PREFIX_PATH'] = proc.stdout.rstrip("\n")
+        environ['CONFIGURE_FLAGS'] = '--without-dbus'
+        if not args.qt:
+            raise Exception('provide the Qt path using --qt=/qt/install/prefix')
+        install_args += ("-Q", args.qt)
+    else:
+        if args.distribution in ZYPPER_BASED_DISTROS:
+            # fix jsoncpp pkg-config bug, remove when jsoncpp package bumped
+            environ['JSONCPP_LIBS'] = "-ljsoncpp"
+        if args.qt:
+            install_args += ("-Q", args.qt)
+
+    command = ['extras/scripts/install.sh'] + install_args
+
+    if args.distribution == 'guix':
+        if args.global_install:
+            print('error: global install is not supported when using Guix.')
+            sys.exit(1)
+        # Run the build in an isolated container.
+        share_tarballs_args = []
+        if 'TARBALLS' in os.environ:
+            share_tarballs_args = ['--preserve=TARBALLS',
+                                   f'--share={os.environ["TARBALLS"]}']
+        else:
+            print('info: consider setting the TARBALLS environment variable '
+                  'to a stable writable location to avoid loosing '
+                  'cached tarballs')
+        # Note: we must expose /gnu/store because /etc/ssl/certs
+        # contains certs that are symlinks to store items.
+        command = ['guix', 'shell', '--manifest=guix/manifest.scm',
+                   '--expose=/gnu/store', '--expose=/etc/ssl/certs',
+                   '--expose=/usr/bin/env',
+                   '--container', '--network'] + share_tarballs_args \
+                   + ['--'] + command
+
+    print(f'info: Building/installing using the command: {" ".join(command)}')
+    return subprocess.run(command, env=environ, check=True)
+
+
+def run_uninstall(args):
+    execute_script(UNINSTALL_DAEMON_SCRIPT)
+
+    BUILD_DIR = 'build-global' if args.global_install else 'build'
+
+    if (os.path.exists(BUILD_DIR)):
+        UNINSTALL_CLIENT_SCRIPT = [
+             f'make -C {BUILD_DIR} uninstall',
+             f'rm -rf {BUILD_DIR}'
+        ]
+        execute_script(UNINSTALL_CLIENT_SCRIPT)
+
+
+def run_clean():
+    execute_script(['git clean -xfd',
+                    'git submodule foreach git clean -xfd'])
+
+
+def run_run(args):
+    run_env = os.environ
+    try:
+        if args.no_libwrap:
+            jamid_log = open("daemon.log", 'a')
+            jamid_log.write('=== Starting daemon (%s) ===' %
+                            time.strftime("%d/%m/%Y %H:%M:%S"))
+            jamid_process = subprocess.Popen(
+                ["./install/libexec/jamid", "-c", "-d"],
+                stdout=jamid_log,
+                stderr=jamid_log)
+
+            with open('daemon.pid', 'w') as f:
+                f.write(str(jamid_process.pid)+'\n')
+
+        client_log = open('jami.log', 'a')
+        client_log.write('=== Starting client (%s) ===' %
+                         time.strftime("%d/%m/%Y %H:%M:%S"))
+        client_process = subprocess.Popen(["./install/bin/jami", "-d"],
+                                          stdout=client_log,
+                                          stderr=client_log,
+                                          env=run_env)
+
+        with open('jami.pid', 'w') as f:
+            f.write(str(client_process.pid)+'\n')
+
+        if args.debug and args.no_libwrap:
+            subprocess.call(['gdb', './install/libexec/jamid'])
+
+        if not args.background:
+            if args.no_libwrap:
+                jamid_process.wait()
+            client_process.wait()
+
+    except KeyboardInterrupt:
+        print("\nCaught KeyboardInterrupt...")
+
+    finally:
+        if args.background == False:
+            try:
+                # Only kill the processes if they are running, as they
+                # could have been closed by the user.
+                print("Killing processes...")
+                if args.no_libwrap:
+                    jamid_log.close()
+                    if jamid_process.poll() is None:
+                        jamid_process.kill()
+                client_log.close()
+                if client_process.poll() is None:
+                    client_process.kill()
+            except UnboundLocalError:
+                # Its okay! We crashed before we could start a process
+                # or open a file.  All that matters is that we close
+                # files and kill processes in the right order.
+                pass
+    return True
+
+
+def run_stop(args):
+    STOP_SCRIPT = ['xargs kill < jami.pid',
+                   'xargs kill < daemon.pid']
+    execute_script(STOP_SCRIPT)
+
+
+def execute_script(script, settings=None, fail=True):
+    if settings is None:
+        settings = {}
+    for line in script:
+        line = line % settings
+        rv = os.system(line)
+        if rv and fail:
+            print('Error executing script! Exit code: %s' %
+                  rv, file=sys.stderr)
+            sys.exit(1)
+
+
+def has_guix():
+    """Check whether the 'guix' command is available."""
+    with open(os.devnull, 'w') as f:
+        try:
+            subprocess.run(["sh", "-c", "command -v guix"],
+                           check=True, stdout=f)
+        except subprocess.CalledProcessError:
+            return False
+        else:
+            return True
+
+
+def validate_args(parsed_args):
+    """Validate the args values, exit if error is found"""
+
+    # Filter unsupported distributions.
+    supported_distros = \
+        [ OSX_DISTRIBUTION_NAME, WIN32_DISTRIBUTION_NAME, 'guix'] + \
+        APT_BASED_DISTROS + DNF_BASED_DISTROS + PACMAN_BASED_DISTROS \
+        + ZYPPER_BASED_DISTROS + FLATPAK_BASED_RUNTIMES
+
+    if (parsed_args.distribution == 'no-check'
+            or 'JAMI_BUILD_NO_CHECK' in os.environ):
+        return
+
+    if parsed_args.distribution not in supported_distros:
+        print(f'WARNING: Distribution \'{parsed_args.distribution}\' is not '
+              f'supported. Choose one of: {", ".join(supported_distros)}. '
+              'Alternatively, you may force execution of this script '
+              'by providing the \'--distribution=no-check\' argument or by '
+              'exporting the JAMI_BUILD_NO_CHECK environment variable.',
+              file=sys.stderr)
+        sys.exit(1)
+
+    # On Windows, version 10 or later is needed to build Jami.
+    if parsed_args.distribution == WIN32_DISTRIBUTION_NAME:
+        if hasattr(sys, 'getwindowsversion') and sys.getwindowsversion()[0] < 10:
+            print('Windows 10 or later is needed to build Jami')
+            sys.exit(1)
+
+
+def parse_args():
+    ap = argparse.ArgumentParser(description="Jami build tool")
+
+    ga = ap.add_mutually_exclusive_group(required=True)
+    ga.add_argument(
+        '--init', action='store_true',
+        help='Init Jami repository')
+    ga.add_argument(
+        '--dependencies', action='store_true',
+        help='Install Jami build dependencies')
+    ga.add_argument(
+        '--install', action='store_true',
+        help='Build and install Jami')
+    ga.add_argument(
+        '--clean', action='store_true',
+        help='Call "git clean" on every repository of the project'
+    )
+    ga.add_argument(
+        '--uninstall', action='store_true',
+        help='Uninstall Jami')
+    ga.add_argument(
+        '--run', action='store_true',
+        help='Run the Jami daemon and client')
+    ga.add_argument(
+        '--stop', action='store_true',
+        help='Stop the Jami processes')
+
+    ap.add_argument('--distribution')
+    ap.add_argument('--prefix')
+    ap.add_argument('--static', default=False, action='store_true')
+    ap.add_argument('--global-install', default=False, action='store_true')
+    ap.add_argument('--debug', default=False, action='store_true',
+                    help='Build with debug support; run in GDB')
+    ap.add_argument('--background', default=False, action='store_true')
+    ap.add_argument('--no-priv-install', dest='priv_install',
+                    default=True, action='store_false')
+    ap.add_argument('--qt', type=str,
+                    help='Use the Qt path supplied')
+    ap.add_argument('--no-libwrap', dest='no_libwrap',
+                    default=False, action='store_true',
+                    help='Disable libwrap. Also set --disable-shared option to daemon configure')
+    ap.add_argument('-y', '--assume-yes', default=False, action='store_true',
+                    help='Assume yes (do not prompt user) for dependency installations through the system package manager')
+    ap.add_argument('--no-webengine', dest='no_webengine',
+                    default=False, action='store_true',
+                    help='Do not use Qt WebEngine.')
+
+    dist = choose_distribution()
+
+    if dist == WIN32_DISTRIBUTION_NAME:
+        ap.add_argument('--toolset', default=win_toolset_default, type=str,
+                        help='Windows use only, specify Visual Studio toolset version')
+        ap.add_argument('--sdk', default=win_sdk_default, type=str,
+                        help='Windows use only, specify Windows SDK version')
+
+    parsed_args = ap.parse_args()
+
+    if parsed_args.distribution:
+        parsed_args.distribution = parsed_args.distribution.lower()
+    else:
+        parsed_args.distribution = dist
+
+    validate_args(parsed_args)
+
+    return parsed_args
+
+
+def choose_distribution():
+    system = platform.system().lower()
+
+    if system == "linux" or system == "linux2":
+        if os.path.isfile("/etc/arch-release"):
+            return "arch"
+        try:
+            with open("/etc/os-release") as f:
+                for line in f:
+                    k, v = line.split("=")
+                    if k.strip() == 'ID':
+                        return v.strip().replace('"', '').split(' ')[0]
+        except FileNotFoundError:
+            if has_guix():
+                return 'guix'
+            return 'Unknown'
+    elif system == "darwin":
+        return OSX_DISTRIBUTION_NAME
+    elif system == "windows":
+        return WIN32_DISTRIBUTION_NAME
+
+    return 'Unknown'
+
+
+def main():
+    parsed_args = parse_args()
+
+    if parsed_args.dependencies:
+        run_dependencies(parsed_args)
+
+    elif parsed_args.init:
+        run_init()
+    elif parsed_args.clean:
+        run_clean()
+
+    elif parsed_args.install:
+        run_install(parsed_args)
+
+    elif parsed_args.uninstall:
+        run_uninstall(parsed_args)
+
+    elif parsed_args.run:
+        if (parsed_args.distribution == 'guix'
+                and 'GUIX_ENVIRONMENT' not in os.environ):
+            # Relaunch this script, this time in a pure Guix environment.
+            guix_args = ['shell', '--pure',
+                         # to allow pulseaudio to connect to an existing server
+                         "-E", "XAUTHORITY", "-E", "XDG_RUNTIME_DIR",
+                         '--manifest=guix/manifest.scm', '--']
+            args = sys.argv + ['--distribution=guix']
+            print('Running in a guix shell spawned with: guix {}'
+                  .format(str.join(' ', guix_args + args)))
+            os.execlp('guix', 'guix', *(guix_args + args))
+        else:
+            run_run(parsed_args)
+
+    elif parsed_args.stop:
+        run_stop(parsed_args)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/extras/scripts/commit-msg b/extras/scripts/commit-msg
new file mode 100755
index 0000000000000000000000000000000000000000..81edbe8c91aeaa3816d47cc127319f271414506c
--- /dev/null
+++ b/extras/scripts/commit-msg
@@ -0,0 +1,181 @@
+#!/bin/sh
+# From Gerrit Code Review 2.11.3
+#
+# Part of Gerrit Code Review (http://code.google.com/p/gerrit/)
+#
+# Copyright (C) 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+unset GREP_OPTIONS
+
+CHANGE_ID_AFTER="Bug|Issue"
+MSG="$1"
+
+# Check for, and add if missing, a unique Change-Id
+#
+add_ChangeId() {
+	clean_message=`sed -e '
+		/^diff --git .*/{
+			s///
+			q
+		}
+		/^Signed-off-by:/d
+		/^#/d
+	' "$MSG" | git stripspace`
+	if test -z "$clean_message"
+	then
+		return
+	fi
+
+	if test "false" = "`git config --bool --get gerrit.createChangeId`"
+	then
+		return
+	fi
+
+	# Does Change-Id: already exist? if so, exit (no change).
+	if grep -i '^Change-Id:' "$MSG" >/dev/null
+	then
+		return
+	fi
+
+	id=`_gen_ChangeId`
+	T="$MSG.tmp.$$"
+	AWK=awk
+	if [ -x /usr/xpg4/bin/awk ]; then
+		# Solaris AWK is just too broken
+		AWK=/usr/xpg4/bin/awk
+	fi
+
+	# How this works:
+	# - parse the commit message as (textLine+ blankLine*)*
+	# - assume textLine+ to be a footer until proven otherwise
+	# - exception: the first block is not footer (as it is the title)
+	# - read textLine+ into a variable
+	# - then count blankLines
+	# - once the next textLine appears, print textLine+ blankLine* as these
+	#   aren't footer
+	# - in END, the last textLine+ block is available for footer parsing
+	$AWK '
+	BEGIN {
+		# while we start with the assumption that textLine+
+		# is a footer, the first block is not.
+		isFooter = 0
+		footerComment = 0
+		blankLines = 0
+	}
+
+	# Skip lines starting with "#" without any spaces before it.
+	/^#/ { next }
+
+	# Skip the line starting with the diff command and everything after it,
+	# up to the end of the file, assuming it is only patch data.
+	# If more than one line before the diff was empty, strip all but one.
+	/^diff --git / {
+		blankLines = 0
+		while (getline) { }
+		next
+	}
+
+	# Count blank lines outside footer comments
+	/^$/ && (footerComment == 0) {
+		blankLines++
+		next
+	}
+
+	# Catch footer comment
+	/^\[[a-zA-Z0-9-]+:/ && (isFooter == 1) {
+		footerComment = 1
+	}
+
+	/]$/ && (footerComment == 1) {
+		footerComment = 2
+	}
+
+	# We have a non-blank line after blank lines. Handle this.
+	(blankLines > 0) {
+		print lines
+		for (i = 0; i < blankLines; i++) {
+			print ""
+		}
+
+		lines = ""
+		blankLines = 0
+		isFooter = 1
+		footerComment = 0
+	}
+
+	# Detect that the current block is not the footer
+	(footerComment == 0) && (!/^\[?[a-zA-Z0-9-]+:/ || /^[a-zA-Z0-9-]+:\/\//) {
+		isFooter = 0
+	}
+
+	{
+		# We need this information about the current last comment line
+		if (footerComment == 2) {
+			footerComment = 0
+		}
+		if (lines != "") {
+			lines = lines "\n";
+		}
+		lines = lines $0
+	}
+
+	# Footer handling:
+	# If the last block is considered a footer, splice in the Change-Id at the
+	# right place.
+	# Look for the right place to inject Change-Id by considering
+	# CHANGE_ID_AFTER. Keys listed in it (case insensitive) come first,
+	# then Change-Id, then everything else (eg. Signed-off-by:).
+	#
+	# Otherwise just print the last block, a new line and the Change-Id as a
+	# block of its own.
+	END {
+		unprinted = 1
+		if (isFooter == 0) {
+			print lines "\n"
+			lines = ""
+		}
+		changeIdAfter = "^(" tolower("'"$CHANGE_ID_AFTER"'") "):"
+		numlines = split(lines, footer, "\n")
+		for (line = 1; line <= numlines; line++) {
+			if (unprinted && match(tolower(footer[line]), changeIdAfter) != 1) {
+				unprinted = 0
+				print "Change-Id: I'"$id"'"
+			}
+			print footer[line]
+		}
+		if (unprinted) {
+			print "Change-Id: I'"$id"'"
+		}
+	}' "$MSG" > "$T" && mv "$T" "$MSG" || rm -f "$T"
+}
+_gen_ChangeIdInput() {
+	echo "tree `git write-tree`"
+	if parent=`git rev-parse "HEAD^0" 2>/dev/null`
+	then
+		echo "parent $parent"
+	fi
+	echo "author `git var GIT_AUTHOR_IDENT`"
+	echo "committer `git var GIT_COMMITTER_IDENT`"
+	echo
+	printf '%s' "$clean_message"
+}
+_gen_ChangeId() {
+	_gen_ChangeIdInput |
+	git hash-object -t commit --stdin
+}
+
+
+add_ChangeId
diff --git a/extras/scripts/format.sh b/extras/scripts/format.sh
new file mode 100755
index 0000000000000000000000000000000000000000..dfbe1d878b4b03323a0f9a7ae88170123eae5b18
--- /dev/null
+++ b/extras/scripts/format.sh
@@ -0,0 +1,103 @@
+#!/usr/bin/env bash
+# format.sh --- set up clang-format for source files
+
+# Copyright (C) 2020-2022 Savoir-faire Linux Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+
+set -e
+
+command_exists ()
+{
+    type "$1" &> /dev/null ;
+}
+
+CFVERSION="9"
+CLANGFORMAT=""
+if command_exists clang-format-${CFVERSION}; then
+    CLANGFORMAT=clang-format-${CFVERSION}
+else
+    if command_exists clang-format; then
+        CLANGFORMAT=clang-format
+    fi
+fi
+
+if ! command -v $CLANGFORMAT &> /dev/null; then
+    echo "Required version of clang-format not found"
+    exit 1
+fi
+
+format_file()
+{
+    if [ -f "${1}" ]; then
+        $CLANGFORMAT -i -style=file "${1}" || true
+    fi
+}
+
+format_files()
+{
+    for file in $1; do
+        echo -ne "Formatting: ${file}\\033[0K\\r"
+        format_file "${file}"
+    done
+}
+
+exit_if_no_files()
+{
+    echo No files to format
+    exit 0
+}
+
+install_hook()
+{
+    hooks_path=$1
+    if [ ! -d "$hooks_path" ]; then
+        echo "$hooks_path" path does not exist
+        exit 1
+    fi
+    echo Installing pre-commit hook in "$hooks_path"
+    echo "$(realpath $0)" > "$hooks_path"/pre-commit
+    chmod +x "$hooks_path"/pre-commit
+}
+
+display_help()
+{
+    echo "Usage: $0 [OPTION...] -- Clang format source files with a .clang-format file" >&2
+    echo
+    echo "   --all             format all files instead of only committed ones"
+    echo "   --install <path>  install a pre-commit hook to run this script"
+    echo
+}
+
+if [ "$1" == "--help" ]; then
+    display_help
+    exit 0
+fi
+
+case "${1}" in
+  --all )
+    files=$(find src -regex '.*\.\(cpp\|hpp\|cc\|cxx\|h\)$') || true
+    echo Formatting all source files...
+    format_files "$files"
+    ;;
+  --install )
+    install_hook "${2}"
+    ;;
+  * )
+    files=$(git diff-index --cached --name-only HEAD | grep -iE '\.(cpp|cxx|cc|h|hpp)$') || exit_if_no_files
+    echo Formatting committed source files...
+    format_files "$files"
+    ;;
+esac
diff --git a/extras/scripts/install-deps-windows.ps1 b/extras/scripts/install-deps-windows.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..6955b613cc7a19a3b0b0c1b15fc108c99ed83a00
--- /dev/null
+++ b/extras/scripts/install-deps-windows.ps1
@@ -0,0 +1,236 @@
+# Copyright (C) 2019-2022 Savoir-faire Linux Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+
+<#
+    This script should install dependencies required for building the Jami
+    Qt client on windows.
+
+    Required components not installed:
+    - Visual Studio
+    - build toolchains
+    - SDKs
+    - WiX + WiX Visual Studio extension
+    - Qt + Qt Visual Studio extension
+#>
+
+write-host "Installing jami-qt build dependencies for windows…" -ForegroundColor Green
+
+Set-ExecutionPolicy Bypass -Scope Process -Force
+
+$global:installed_packages = $null
+Function choco_check_package([String] $package, [String] $version = "") {
+    # Query a package listing once
+    if ($null -eq $global:installed_packages) {
+        write-host "Getting installed package list from Chocolatey…" -ForegroundColor DarkCyan
+        $global:installed_packages = choco list -lo
+    }
+    # Check installed packages
+    $result = $global:installed_packages | Where-object {
+        $_.ToLower().StartsWith($package.ToLower())
+    }
+    if ($null -eq $result) {
+        # We don't have the package
+        write-host $package "not found." -ForegroundColor Yellow
+        return $false
+    }
+    if ("" -eq $version) {
+        # We have the package and don't care what version it is
+        write-host $package "found." -ForegroundColor Cyan
+        return $true
+    }
+    # We now check the results for a package of the specified version
+    $parts = $result.Split(' ')
+    Foreach ($part in $parts) {
+        if ($part -eq $version) {
+            # We have the package of the specified version
+            write-host $package $version "found." -ForegroundColor Cyan
+            return $true
+        }
+    }
+    # We don't have the package of the specified version
+    write-host $package $version "not found." -ForegroundColor Yellow
+    return $false
+}
+
+Function install_chocolatey {
+    # Install Chocolatey if not installed already
+    if (!(Test-Path "$($env:ProgramData)\chocolatey\choco.exe")) {
+        Invoke-Expression ((New-Object System.Net.WebClient).DownloadString("https://chocolatey.org/install.ps1"))
+        if ( $LASTEXITCODE -eq 0 ) {
+            write-host "Chocolatey installation succeeded" -ForegroundColor Green
+        }
+        else {
+            write-host "Chocolatey installation failed" -ForegroundColor Red
+            exit $LASTEXITCODE
+        }
+    }
+    else {
+        write-host "Chocolatey already installed" -ForegroundColor DarkGreen
+    }
+}
+
+Function choco_install_package([String] $package, [String] $version = "") {
+    $package_installed = choco_check_package $package $version
+    if ($true -eq $package_installed) {
+        return
+    }
+    if ("" -ne $version) {
+        write-host "Installing" $package "@" $version
+        choco install -fy --allow-downgrade $package --version $version --acceptlicense
+    }
+    else {
+        write-host "Installing" $package
+        choco install -fy --allow-downgrade $package --acceptlicense
+    }
+    if ( $LASTEXITCODE -ne 0 ) {
+        write-host "Choco Packages Installation Failed" -ForegroundColor Red
+        exit 1
+    }
+}
+Function install_choco_packages($packages) {
+    Foreach ($i in $packages) {
+        choco_install_package $i.pkg $i.ver
+    }
+    write-host "Choco Packages Installation Succeeded" -ForegroundColor Green
+}
+
+Function download_file_to_temp($download_name, $url, $output_name) {
+    write-host "Downloading $download_name" -ForegroundColor DarkCyan
+    $output = $env:TEMP + "\$output_name"
+    (New-Object System.Net.WebClient).DownloadFile($url, $output)
+    if ( $LASTEXITCODE -eq 0 ) {
+        write-host "Download $download_name Succeeded" -ForegroundColor Green
+    }
+    else {
+        write-host "Download $download_name Failed" -ForegroundColor Red
+        exit $LASTEXITCODE
+    }
+}
+
+Function unzip_file_from_temp($unzip_name, $zip_file_name, $unzip_file_output_name) {
+    write-host "Unzipping $unzip_name" -ForegroundColor DarkCyan
+    $zip_path = $env:TEMP + "\$zip_file_name"
+    $unzip_path = $env:TEMP + "\$unzip_file_output_name"
+    Invoke-Expression("unzip -o $zip_path -d '$unzip_path'") | Out-Null
+    if ( $LASTEXITCODE -eq 0 ) {
+        write-host "Unzip $unzip_name Succeeded" -ForegroundColor Green
+    }
+    else {
+        write-host "Unzip $unzip_name Failed" -ForegroundColor Red
+        exit $LASTEXITCODE
+    }
+}
+
+Function run_batch($batch_cmd, $task_name) {
+    write-host $task_name -ForegroundColor DarkCyan
+    Start-Process "cmd.exe" $batch_cmd -Wait -NoNewWindow | Out-Null
+    if ( $LASTEXITCODE -eq 0 ) {
+        write-host "$task_name Succeeded" -ForegroundColor Green
+    }
+    else {
+        write-host "$task_name Failed" -ForegroundColor Red
+        exit $LASTEXITCODE
+    }
+}
+
+Function move_file_from_temp_to_msys64($file_name, $task_name) {
+    write-host $task_name -ForegroundColor DarkCyan
+    $file_path = $env:TEMP + "\$file_name"
+    Move-item -Path $file_path -Destination $msys2_path -Force
+    if ($LASTEXITCODE -eq 0) {
+        write-host "$task_name Succeeded" -ForegroundColor Green
+    }
+    else {
+        write-host "$task_name Failed" -ForegroundColor Red
+        exit $LASTEXITCODE
+    }
+}
+
+Function install_msys2_packages($packages) {
+    Foreach ($i in $packages) {
+        Invoke-Expression ("pacman -Q '$i'") | out-null
+        if ($LASTEXITCODE -eq 0) {
+            write-host $i "already installed" -ForegroundColor Cyan
+            continue
+        }
+        Invoke-Expression ("pacman -S '$i' --noconfirm")
+        if ($LASTEXITCODE -ne 0) {
+            write-host "Pacman Packages Installation Failed" -ForegroundColor Red
+            exit 1
+        }
+    }
+    write-host "Pacman Packages Installation Succeeded" -ForegroundColor Green
+}
+
+# Web installed msys2_64 bit to install make, gcc, perl, diffutils
+$msys_packages = @("make", "gcc", "perl", "diffutils")
+
+# Install 7zip, unzip, wget --version 1.19.4, cmake, git --version 2.10.2, pandoc, strawberryperl, msys2
+$choco_packages = @(
+    [pscustomobject]@{pkg = "wget"; ver = "1.19.4" }
+    [pscustomobject]@{pkg = "git.install"; ver = "2.10.2" }
+    [pscustomobject]@{pkg = "7zip"; ver = "" }
+    [pscustomobject]@{pkg = "unzip"; ver = "" }
+    [pscustomobject]@{pkg = "cmake"; ver = "" }
+    [pscustomobject]@{pkg = "pandoc"; ver = "" }
+    [pscustomobject]@{pkg = "strawberryperl"; ver = "" }
+    [pscustomobject]@{pkg = "msys2"; ver = "" }
+)
+
+install_chocolatey
+
+# Check for an existing msys2 install
+# Note that choco installs msys2 in C:/tools/
+if (!(Test-Path -Path "C:\msys64")) {
+    $Env:Path += ";C:\tools\msys64\usr\bin"
+    $msys2_path = "C:\tools\msys64\usr\bin"
+    if ((Test-Path -Path "C:\tools\msys64")) {
+        write-host "MSYS2 64 already installed" -ForegroundColor Green
+    }
+    else {
+        $choco_packages.Add([pscustomobject]@{pkg = "msys2"; ver = "" })
+    }
+}
+else {
+    write-host "MSYS2 64 already installed" -ForegroundColor Green
+    $Env:Path += ";C:\msys64\usr\bin"
+    $msys2_path = "C:\msys64\usr\bin"
+}
+
+install_choco_packages $choco_packages
+install_msys2_packages $msys_packages
+
+# Install VSNASM
+download_file_to_temp 'VSNASM' "https://github.com/ShiftMediaProject/VSNASM/releases/download/0.5/VSNASM.zip" 'VSNASM.zip'
+unzip_file_from_temp 'VSNASM' 'VSNASM.zip' 'VSNASM_UNZIP'
+$batch_path = "/c set ISINSTANCE=1 &&" + $env:TEMP + "\VSNASM_UNZIP\install_script.bat"
+run_batch $batch_path "Install VSNASM"
+
+# Install VSYASM
+download_file_to_temp 'VSYASM' "https://github.com/ShiftMediaProject/VSYASM/releases/download/0.4/VSYASM.zip" 'VSYASM.zip'
+unzip_file_from_temp 'VSYASM' 'VSYASM.zip' 'VSYASM_UNZIP'
+$batch_path = "/c set ISINSTANCE=1 &&" + $env:TEMP + "\VSYASM_UNZIP\install_script.bat"
+run_batch $batch_path "Install VSYASM"
+
+# Install yasm.exe (win64)
+download_file_to_temp 'yasm.exe (win64)' "http://www.tortall.net/projects/yasm/releases/yasm-1.3.0-win64.exe" 'yasm.exe'
+move_file_from_temp_to_msys64 'yasm.exe' 'Move yasm.exe (win64) to msys64 folder'
+
+# Install gas-preprocessor.pl
+download_file_to_temp 'gas-preprocessor.pl' "https://github.com/FFmpeg/gas-preprocessor/blob/master/gas-preprocessor.pl" 'gas-preprocessor.pl'
+move_file_from_temp_to_msys64 'gas-preprocessor.pl' 'Move gas-preprocessor.pl to msys64 folder'
+
+write-host "Done" -ForegroundColor Green
diff --git a/extras/scripts/install.sh b/extras/scripts/install.sh
new file mode 100755
index 0000000000000000000000000000000000000000..5c351318d0642d40b36c2377e19af45f22c9a27f
--- /dev/null
+++ b/extras/scripts/install.sh
@@ -0,0 +1,187 @@
+#!/usr/bin/env bash
+# install.sh --- build and install Jami daemon and client
+
+# Copyright (C) 2016-2022 Savoir-faire Linux Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
+
+# Build and install to a local prefix under this repository.
+export OSTYPE
+
+# Flags:
+
+  # -g: install globally instead for all users
+  # -s: link everything statically, no D-Bus communication. More likely to work!
+  # -p: number of processors to use
+  # -u: disable use of privileges (sudo) during install
+  # -W: disable libwrap and shared library
+  # -w: do not use Qt WebEngine
+
+set -ex
+
+# Qt_MIN_VER required for client-qt
+QT_MIN_VER="6.2"
+
+debug=
+global=false
+static=''
+qtpath=''
+proc='1'
+priv_install=true
+enable_libwrap=true
+enable_webengine=true
+
+while getopts gsc:dQ:P:p:uWw OPT; do
+  case "$OPT" in
+    g)
+      global='true'
+    ;;
+    s)
+      static='-DENABLE_STATIC=true'
+    ;;
+    d)
+      debug=true
+    ;;
+    Q)
+      qtpath="${OPTARG}"
+    ;;
+    P)
+      prefix="${OPTARG}"
+    ;;
+    p)
+      proc="${OPTARG}"
+    ;;
+    u)
+      priv_install='false'
+    ;;
+    W)
+      enable_libwrap='false'
+    ;;
+    w)
+      enable_webengine='false'
+    ;;
+    \?)
+      exit 1
+    ;;
+  esac
+done
+
+# $1: global-install?
+# $2: private-install?
+make_install() {
+  if [ "$1" = "true" ] && [ "$2" != "false" ]; then
+    sudo make install
+    # Or else the next non-sudo install will fail, because this generates some
+    # root owned files like install_manifest.txt under the build directory.
+    sudo chown -R "$USER" .
+  else
+    make install
+  fi
+}
+
+TOP="$(pwd)"
+INSTALL_DIR="${TOP}/install"    # local install directory
+
+if [ "${global}" = "true" ]; then
+    BUILD_DIR="build-global"
+else
+    BUILD_DIR="build"
+fi
+
+# jamid
+DAEMON="${TOP}/daemon"
+cd "$DAEMON"
+
+# Build the contribs.
+mkdir -p contrib/native
+(
+    cd contrib/native
+    ../bootstrap ${prefix:+"--prefix=$prefix"}
+    make -j"${proc}"
+)
+
+if [[ "${enable_libwrap}" != "true" ]]; then
+  # Disable shared if requested
+  if [[ "$OSTYPE" != "darwin"* ]]; then
+    CONFIGURE_FLAGS+=" --disable-shared"
+  fi
+fi
+
+BUILD_TYPE="Release"
+if [ "${debug}" = "true" ]; then
+  BUILD_TYPE="Debug"
+  CONFIGURE_FLAGS+=" --enable-debug"
+fi
+
+# Build the daemon itself.
+test -f configure || ./autogen.sh
+
+if [ "${global}" = "true" ]; then
+    ./configure ${CONFIGURE_FLAGS} ${prefix:+"--prefix=$prefix"}
+else
+    ./configure ${CONFIGURE_FLAGS} --prefix="${INSTALL_DIR}"
+fi
+make -j"${proc}" V=1
+make_install "${global}" "${priv_install}"
+
+# Verify system's version if no path provided.
+if [ -z "$qtpath" ]; then
+    sys_qtver=""
+    if command -v qmake6 &> /dev/null; then
+        sys_qtver=$(qmake6 -v)
+    elif command -v qmake-qt6 &> /dev/null; then
+        sys_qtver=$(qmake-qt6 -v) # Fedora
+    elif command -v qmake &> /dev/null; then
+        sys_qtver=$(qmake -v)
+    else
+        echo "No valid Qt found"; exit 1;
+    fi
+
+    sys_qtver=${sys_qtver#*Qt version}
+    sys_qtver=${sys_qtver%\ in\ *}
+
+    installed_qtver=$(echo "$sys_qtver" | cut -d'.' -f 2)
+    required_qtver=$(echo $QT_MIN_VER | cut -d'.' -f 2)
+
+    if [[ $installed_qtver -ge $required_qtver ]] ; then
+        # Set qtpath to empty in order to use system's Qt.
+        qtpath=""
+    else
+        echo "No valid Qt found"; exit 1;
+    fi
+fi
+
+# client
+cd "${TOP}"
+mkdir -p "${BUILD_DIR}"
+cd "${BUILD_DIR}"
+
+client_cmake_flags=(-DCMAKE_BUILD_TYPE="${BUILD_TYPE}"
+                    -DCMAKE_PREFIX_PATH="${qtpath}"
+                    -DENABLE_LIBWRAP="${enable_libwrap}"
+                    -DWITH_WEBENGINE="${enable_webengine}")
+
+if [ "${global}" = "true" ]; then
+    client_cmake_flags+=(${prefix:+"-DCMAKE_INSTALL_PREFIX=$prefix"}
+                         $static)
+else
+    client_cmake_flags+=(-DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}"
+                         -DLIBJAMI_BUILD_DIR="${DAEMON}/src")
+fi
+
+echo "info: Configuring $client client with flags: ${client_cmake_flags[*]}"
+cmake .. "${client_cmake_flags[@]}"
+make -j"${proc}" V=1
+make_install "${global}" "${priv_install}"