diff --git a/jams-launcher/pom.xml b/jams-launcher/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..54ecb9f5d32d9e26da7c18bd05fb75afbe9b5883
--- /dev/null
+++ b/jams-launcher/pom.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>jams3-parent</artifactId>
+        <groupId>net.jami</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>jams-launcher</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-model</artifactId>
+            <version>3.2.5</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore</artifactId>
+            <version>4.4.12</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.10</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.12</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>20.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.zeromq</groupId>
+            <artifactId>jeromq</artifactId>
+            <version>0.5.2</version>
+        </dependency>
+        <dependency>
+            <groupId>com.blockfeed</groupId>
+            <artifactId>messaging</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>jams-launcher</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>2.3</version>
+                <executions>
+                    <!-- Run shade goal on package phase -->
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <outputFile>../jams/${project.artifactId}.jar</outputFile>
+                            <filters>
+                                <filter>
+                                    <artifact>*:*</artifact>
+                                    <excludes>
+                                        <exclude>META-INF/*.SF</exclude>
+                                        <exclude>META-INF/*.DSA</exclude>
+                                        <exclude>META-INF/*.RSA</exclude>
+                                    </excludes>
+                                </filter>
+                            </filters>
+                            <transformers>
+                                <!-- add Main-Class to manifest file -->
+                                <transformer
+                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                                    <manifestEntries>
+                                        <Main-Class>launcher.AppStarter</Main-Class>
+                                        <Class-Path>.</Class-Path>
+                                    </manifestEntries>
+                                </transformer>
+                                <transformer
+                                        implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
+                            </transformers>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+
+        </plugins>
+    </build>
+
+
+</project>
\ No newline at end of file
diff --git a/jams-launcher/src/main/java/launcher/AppStarter.java b/jams-launcher/src/main/java/launcher/AppStarter.java
new file mode 100644
index 0000000000000000000000000000000000000000..ad34217fa3b915c7295420ca34192744f906c291
--- /dev/null
+++ b/jams-launcher/src/main/java/launcher/AppStarter.java
@@ -0,0 +1,190 @@
+package launcher;
+
+import com.google.common.hash.HashCode;
+import com.google.common.hash.Hashing;
+import org.zeromq.ZMQ;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.logging.Logger;
+
+public class AppStarter extends Thread {
+
+    private static Process applicationProcess;
+    private static long jamsPID;
+    private static Timer timer;
+    private final static Logger logger = Logger.getLogger(AppStarter.class.getName());
+    public static ZMQ.Context context = ZMQ.context(1);
+
+
+    /**
+     *  The launcher is essentially responsible of firing up the main application JAR.
+     *  The problematic here is whenever we will fire up a JAR (but then we need to be able to
+     *  kill and restart his PID, or whenever we fork via a main).
+     */
+    public static void main(String[] args) {
+        timer = startProcessCheckTimer(5000, args);
+
+
+        try {
+            logger.info("md5 for all packages: ");
+            logger.info("Core: " + checksum(System.getProperty("user.dir") + "/" + "jams-server.jar"));
+            logger.info("AD Connector: " + checksum(System.getProperty("user.dir") + "/libs/" + "ad-connector.jar"));
+            logger.info("HSQL Connector: " + checksum(System.getProperty("user.dir") + "/libs/" + "hsqlconnector.jar"));
+            logger.info("LDAP Connector: " + checksum(System.getProperty("user.dir") + "/libs/" + "ldap-connector.jar"));
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+            System.out.println("Shutting down...");
+            if (applicationProcess != null)
+                applicationProcess.destroy();
+        }));
+        startProcess(args);
+    }
+
+    public static Timer startProcessCheckTimer(long delay, String[] parentArgs) {
+        TimerTask pollTask = new TimerTask() {
+            @Override
+            public void run() {
+                // check if jams process is alive. If it's not, create another jams process and record it's pid.
+                // Then keep monitoring.
+
+                if (!isStillAlive(Long.toString(jamsPID))) {
+
+                    try {
+                        logger.info("Process with pid: " + jamsPID + " found dead. Resurrecting...");
+                        ProcessBuilder pb = startAccountManagementServer(parentArgs);
+
+                        if (pb != null) {
+                            pb.inheritIO();
+                            pb.directory(new File(System.getProperty("user.dir")));
+                            logger.fine("Directory: " + pb.directory().getAbsolutePath());
+                            applicationProcess = pb.start();
+                        }
+
+                        // keep pid to monitor in memory
+                        if (applicationProcess.isAlive())
+                            jamsPID = applicationProcess.pid();
+
+                        logger.info("New PID: " + jamsPID);
+                    } catch (IOException e) {
+                        logger.severe("Exception thrown when attempting to startup jams process again!");
+                    }
+                }
+
+            }
+        };
+        Timer timer = new Timer();
+        timer.scheduleAtFixedRate(pollTask, delay,
+                25000);
+
+        return timer;
+    }
+
+    private static boolean isStillAlive(String pidStr) {
+        String OS = System.getProperty("os.name").toLowerCase();
+        String command;
+        if (OS.indexOf("win") >= 0) {
+            logger.info("Check if pid " + pidStr + " is alive...");
+            command = "cmd /c tasklist /FI \"PID eq " + pidStr + "\"";
+        } else if (OS.indexOf("nix") >= 0 || OS.indexOf("nux") >= 0 || OS.contains("mac")) {
+            logger.info("Check if pid " + pidStr + " is alive...");
+            command = "ps -p " + pidStr;
+        } else {
+            logger.warning("Unsupported OS, failing gracefully... ");
+            return false;
+        }
+        return isProcessIdRunning(pidStr, command); // call generic implementation
+    }
+
+    private static boolean isProcessIdRunning(String pid, String command) {
+        try {
+            Runtime rt = Runtime.getRuntime();
+            Process pr = rt.exec(command);
+
+            InputStreamReader isReader = new InputStreamReader(pr.getInputStream());
+            BufferedReader bReader = new BufferedReader(isReader);
+            String strLine;
+            while ((strLine= bReader.readLine()) != null) {
+                if (strLine.contains(pid)) {
+                    return true;
+                }
+            }
+
+            return false;
+        } catch (Exception ex) {
+            logger.severe("Got exception using system command!");
+            return false;
+        }
+    }
+
+    private static void startProcess(String[] parentArgs) {
+        try {
+            ProcessBuilder pb = startAccountManagementServer(parentArgs);
+
+            if (pb != null) {
+                pb.inheritIO();
+                pb.directory(new File(System.getProperty("user.dir")));
+                logger.info("Directory: " + pb.directory().getAbsolutePath());
+                applicationProcess = pb.start();
+            }
+
+            // keep pid to monitor in memory
+            if (applicationProcess.isAlive())
+                jamsPID = applicationProcess.pid();
+
+        }
+        catch (NullPointerException | IOException e) {
+            logger.severe("An exception occurred! " + e.toString());
+        }
+        UpdateThread updateThread = new UpdateThread(applicationProcess, parentArgs);
+        updateThread.start();
+    }
+
+    public static ProcessBuilder startAccountManagementServer(String[] parentArgs) {
+        ProcessBuilder pb;
+
+        switch(parentArgs.length) {
+            case 1:
+                pb = new ProcessBuilder("java", "-jar", "jams-server.jar",
+                        parentArgs[0]);
+                break;
+            case 2:
+                pb = new ProcessBuilder("java", "-jar", "jams-server.jar",
+                        parentArgs[0], parentArgs[1]);
+                break;
+            case 3:
+                pb = new ProcessBuilder("java", "-jar", "jams-server.jar",
+                        parentArgs[0], parentArgs[1], parentArgs[2]);
+                break;
+            default:
+                pb = new ProcessBuilder("java", "-jar", System.getProperty("user.dir") + File.separator + "jams-server.jar");
+                break;
+        }
+
+        return pb;
+    }
+
+    private static String checksum(String filepath) throws IOException {
+
+        HashCode hash = com.google.common.io.Files
+                .hash(new File(filepath), Hashing.md5());
+
+        return hash.toString();
+    }
+
+
+    public static Timer getTimer() { return timer; }
+
+    public static void setTimer(Timer timer) { AppStarter.timer = timer; }
+
+    public static long getJamsPID() { return jamsPID; }
+
+    public static void setJamsPID(long jamsPID) { AppStarter.jamsPID = jamsPID; }
+}
diff --git a/jams-launcher/src/main/java/launcher/MessageReceiver.java b/jams-launcher/src/main/java/launcher/MessageReceiver.java
new file mode 100644
index 0000000000000000000000000000000000000000..8c153312537536d9660afe06effeec1cdc77ea6f
--- /dev/null
+++ b/jams-launcher/src/main/java/launcher/MessageReceiver.java
@@ -0,0 +1,29 @@
+package launcher;
+
+import org.zeromq.ZMQ;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class MessageReceiver extends Thread {
+
+    private ZMQ.Socket socket;
+    private AtomicBoolean atomicBoolean;
+
+    public MessageReceiver(ZMQ.Socket socket, AtomicBoolean atomicBoolean) {
+        this.socket = socket;
+        this.atomicBoolean = atomicBoolean;
+    }
+
+    @Override
+    public void run() {
+        while(true){
+            try{
+                String message = socket.recvStr();
+                if(message.equals("DO-UPDATE")) atomicBoolean.set(true);
+            }
+            catch (Exception e){
+                System.out.println("Some exception occurred!");
+            }
+        }
+    }
+}
diff --git a/jams-launcher/src/main/java/launcher/UpdateThread.java b/jams-launcher/src/main/java/launcher/UpdateThread.java
new file mode 100644
index 0000000000000000000000000000000000000000..5742fdd320c49eba6bf2698190ebd6e07eb22fdb
--- /dev/null
+++ b/jams-launcher/src/main/java/launcher/UpdateThread.java
@@ -0,0 +1,416 @@
+package launcher;
+
+import com.google.common.hash.HashCode;
+import com.google.common.hash.Hashing;
+import com.jsoniter.JsonIterator;
+import com.jsoniter.any.Any;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.ssl.SSLContexts;
+import org.codehaus.plexus.util.FileUtils;
+import org.zeromq.SocketType;
+import org.zeromq.ZMQ;
+
+import javax.net.ssl.SSLContext;
+import java.io.*;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyFactory;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.logging.Logger;
+
+@Getter
+@Setter
+public class UpdateThread extends Thread {
+
+    private HashMap<String, String> localVersions = new HashMap<>();
+    private HashMap<String, String> localExecs = new HashMap<>();
+    private HashMap<String, String> remoteVersions = new HashMap<>();
+    private HashMap<String, String> remoteExecs = new HashMap<>();
+    private HashMap<String, String> remoteChecksums = new HashMap<>();
+
+    private String[] parentArgs;
+    private final static Logger logger = Logger.getLogger(UpdateThread.class.getName());
+    private static volatile String CORE_PACKAGE_MAIN_CLASS_NAME;
+    private static volatile String UPDATE_SERVER_URL;
+    private static volatile Long UPDATE_INTERVAL;
+    private SSLContext sslContext = null;
+    private Process applicationProcess;
+    private ZMQ.Socket publisher;
+    private ZMQ.Socket receiver;
+    private AtomicBoolean doUpdate = new AtomicBoolean(false);
+    private MessageReceiver messageReceiver;
+
+    public UpdateThread() {
+    }
+
+    public UpdateThread(Process applicationProcess, String[] parentArgs) {
+        try {
+            //Use this to notify
+            publisher = AppStarter.context.socket(SocketType.PUB);
+            publisher.bind("tcp://*:4572");
+            //Use this to updater.
+            receiver = AppStarter.context.socket(SocketType.REP);
+            receiver.bind("tcp://*:4573");
+            messageReceiver = new MessageReceiver(receiver,doUpdate);
+            messageReceiver.start();
+        } catch (Exception e) {
+            logger.warning("Could not create and bind publisher and/or receiver! Please contact software developer");
+            System.exit(-1);
+        }
+
+        try {
+            InputStream input = AppStarter.class.getClassLoader().getResourceAsStream("oem/config.json");
+            Any any = JsonIterator.deserialize(input.readAllBytes());
+            CORE_PACKAGE_MAIN_CLASS_NAME = any.get("CORE_PACKAGE_MAIN_CLASS_NAME").toString();
+            UPDATE_SERVER_URL = any.get("UPDATE_URL").toString();
+            UPDATE_INTERVAL = any.get("UPDATE_INTERVAL").toLong();
+
+        } catch (IOException e) {
+            logger.warning("Missing OEM configuration! Please contact software developer");
+            System.exit(-1);
+        }
+
+        System.out.println("Update thread created...");
+        this.parentArgs = parentArgs;
+        this.applicationProcess = applicationProcess;
+    }
+
+    //It is a VERY bad idea to use version numbers inside file names as this will break at some point and cause MAJOR
+    //user problems, we can therefore by-pass this by doing the following.
+
+    public static String[] unzipJar(String destinationDir, String jarPath) throws IOException {
+        String[] resp = new String[2];
+        File file = new File(jarPath);
+        JarFile jar = new JarFile(file);
+
+        // fist get all directories,
+        // then make those directory on the destination Path
+        for (Enumeration<JarEntry> enums = jar.entries(); enums.hasMoreElements(); ) {
+            JarEntry entry = (JarEntry) enums.nextElement();
+
+            String fileName = destinationDir + File.separator + entry.getName();
+            File f = new File(fileName);
+
+            if (fileName.endsWith("/")) {
+                f.mkdirs();
+            }
+        }
+
+        //now create all files
+        for (Enumeration<JarEntry> enums = jar.entries(); enums.hasMoreElements(); ) {
+            JarEntry entry = (JarEntry) enums.nextElement();
+            if (entry.toString().contains("META-INF/MANIFEST.MF")) {
+                String fileName = destinationDir + File.separator + entry.getName();
+                File f = new File(fileName);
+
+                if (!fileName.endsWith("/")) {
+                    InputStream is = jar.getInputStream(entry);
+                    FileOutputStream fos = new FileOutputStream(f);
+
+                    // write contents of 'is' to 'fos'
+                    while (is.available() > 0) {
+                        fos.write(is.read());
+                    }
+
+                    fos.close();
+                    is.close();
+                }
+            }
+        }
+
+        String manifestPath = destinationDir + "/META-INF/MANIFEST.MF";
+        Manifest manifest = new Manifest(new URL("file:///" + manifestPath).openStream());
+        resp[0] = manifest.getMainAttributes().getValue("Implementation-Version");
+        resp[1] = manifest.getMainAttributes().getValue("Main-Class");
+        return resp;
+    }
+
+    public void detectCurrentVersions(String folder) {
+        try {
+            File folderExec = new File(folder.replace("/jams-launcher", ""));
+            File folderLibs = new File(folder.replace("/jams-launcher", "") + "/lib");
+            discoverVersions(folderExec);
+            discoverVersions(folderLibs);
+        } catch (Exception e) {
+            logger.warning(e.toString());
+        }
+    }
+
+    private void discoverVersions(File folderLibs) throws IOException {
+        for (File f : Objects.requireNonNull(folderLibs.listFiles())) {
+            if (f.isFile() && f.getName().contains(".jar") && !f.getName().contains("jams-launcher")) {
+                String[] resp = unzipJar(System.getProperty("user.dir") + "/tmpjar/" + f.getName(), f.getAbsolutePath());
+                localVersions.put(resp[1], resp[0]);
+                localExecs.put(resp[1], f.getName());
+            }
+        }
+    }
+
+    // This reads a file on the server which contains some basic info
+    // about what new versions of WHAT are available.
+    private void getLatestVersion() {
+        try {
+            //Step 1: Download a file called versions.json
+            HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
+            HttpResponse response = httpClient.execute(new HttpGet(UPDATE_SERVER_URL + "/versions.json"));
+            //Step 2: Load the file into the hashmaps
+            Any any = JsonIterator.deserialize(response.getEntity().getContent().readAllBytes());
+            any.asMap().forEach((k, v) -> {
+                remoteVersions.put(k, v.get("version").toString());
+                remoteExecs.put(k, v.get("filename").toString());
+                remoteChecksums.put(k, v.get("md5").toString());
+            });
+        } catch (Exception e) {
+            logger.warning("Could not establish connection to JAMS Update Center with error: " + e.toString());
+        }
+    }
+
+    //You only need to load the keystore once, never do it again, you
+    //can theoretically put it as timer if you don't want users to
+    //restart the app.
+    public boolean loadLicense() {
+        try {
+            //Because apparently we should display versions even though the user has no license, we have to
+            //do this in two steps:
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            InputStream input = AppStarter.class.getClassLoader().getResourceAsStream("oem/ca.crt");
+            if (input == null) {
+                System.out.println("No CA Found... this is critical!");
+                System.exit(-1);
+            }
+            Certificate ca = cf.generateCertificate(input);
+            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+            trustStore.load(null, null);
+            trustStore.setCertificateEntry("ca", ca);
+            //Inject the SSL Connection here for a first time.
+            sslContext = SSLContexts.custom().loadTrustMaterial(trustStore, null)
+                    .build();
+            //The below will either pass or fail..
+            KeyStore ks = KeyStore.getInstance("JKS");
+            ks.load(null);
+            if(!Files.exists(Path.of(System.getProperty("user.dir") + File.separator + "license.dat"))){
+                System.out.println("You do not have a valid license - ignoring the rest of this function.");
+                System.out.println("You will still be notified about updates!");
+                return false;
+            }
+            //Assuming the file exists, we just read the file.
+            String b64License = new String(Files.readAllBytes(Path.of(System.getProperty("user.dir") + File.separator + "license.dat")));
+            //Since this is base64, we need to decode it.
+            String strLicense = new String(Base64.getDecoder().decode(b64License));
+            //Now we need to split it. This is actually easy.
+            int cutPoint = strLicense.indexOf("-----BEGIN PRIVATE KEY-----");
+
+            String publicKey = strLicense.substring(0, cutPoint);
+            String privateKey = strLicense.substring(cutPoint);
+
+            privateKey = privateKey.replaceAll("\\n", "").replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "");
+            KeyFactory kf = KeyFactory.getInstance("RSA");
+            PKCS8EncodedKeySpec keysp = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
+            PrivateKey pvk = kf.generatePrivate(keysp);
+
+            InputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(Charset.forName("UTF-8")));
+            Certificate c = cf.generateCertificate(inputStream);
+            Certificate[] chain = new Certificate[]{c};
+
+            String alias1 = ((X509Certificate) c).getSubjectX500Principal().getName();
+            ks.setCertificateEntry(alias1, c);
+            ks.setKeyEntry("", pvk, "".toCharArray(), chain);
+            try {
+                ((X509Certificate) c).checkValidity();
+                c.verify(ca.getPublicKey());
+            } catch (Exception e) {
+                logger.warning("Your license is no longer valid or has been tampered with - " + e.toString());
+                return false;
+            }
+            sslContext = SSLContexts.custom().loadKeyMaterial(ks, "".toCharArray()).loadTrustMaterial(trustStore, null)
+                    .build();
+        } catch (Exception e) {
+            logger.warning("Could not read license file with error " + e.toString());
+            return false;
+        }
+        return true;
+    }
+
+    public void downloadFiles(HashMap<String, String> files) {
+        //I know this contradicts my dogma, but this really would have been an overkill for this project,
+        //I just claim that everything which is not core gets dumped to the lib directory.
+
+        // temp folder for safe download and integrity check
+        File tmpFolder = new File(System.getProperty("user.dir") + "/tmp/");
+        tmpFolder.mkdirs();
+
+        files.forEach((k, v) -> {
+            try {
+                HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
+                HttpResponse httpResponse = httpClient.execute(new HttpGet(UPDATE_SERVER_URL + "/updates/" + v));
+                if (httpResponse.getStatusLine().getStatusCode() == 200) {
+                    logger.info(tmpFolder.getPath() + "/" + files.get(k));
+                    FileOutputStream fos = new FileOutputStream(tmpFolder.getPath() + "/" + files.get(k));
+                    if (k.equals(CORE_PACKAGE_MAIN_CLASS_NAME)) {
+                        httpResponse.getEntity().writeTo(fos);
+                        fos.close();
+
+                        if (checksum(tmpFolder.getPath() + "/" + files.get(k)).equals(remoteChecksums.get(k))) {
+                            logger.info("Successfully downloaded the core package!");
+                            remoteChecksums.remove(CORE_PACKAGE_MAIN_CLASS_NAME);
+                        }
+
+                    } else {
+                        httpResponse.getEntity().writeTo(fos);
+                        fos.close();
+
+                        if (checksum(tmpFolder.getPath() + "/" + files.get(k)).equals(remoteChecksums.get(k))) {
+                            logger.info("Successfully downloaded a library package!");
+                            remoteChecksums.remove(remoteChecksums.get(k));
+                        }
+                    }
+
+                } else {
+                    logger.warning("The server declared an update but does not have the required files?!");
+                }
+            } catch (Exception e1) {
+                logger.warning("Could not download an update with error " + e1.toString());
+            }
+        });
+    }
+
+    //If the user clicks start update then kill, download, start
+    public void checkUpdate(){
+        //Step 1: download the JSON file description (this would also contain the signatures).
+        getLatestVersion();
+        //Step 2: compare the versions - we assume no admin is stupid enough to regress versions.
+        detectCurrentVersions(System.getProperty("user.dir"));
+        localVersions.forEach((k, v) -> {
+            if (remoteVersions.containsKey(k) && remoteVersions.get(k).equals(v)) {
+                remoteVersions.remove(k);
+                remoteExecs.remove(k);
+            }
+        });
+        //Step 3: If there are no valid remote versions available, send an empty JSON.
+        if (remoteVersions.size() == 0){
+            publisher.sendMore("UPDATE");
+            publisher.send("{}");
+            return;
+        }
+        else {
+            //In the new paradigm, we notify the server via zmq.
+            publisher.sendMore("UPDATE");
+            publisher.send(remoteVersions.toString());
+        }
+    }
+
+    public void doUpdate() throws IOException{
+        //Nothing to do here if the size was 0.
+        if(remoteVersions.size() == 0) return;
+        downloadFiles(remoteExecs);
+        if (!remoteChecksums.containsKey(CORE_PACKAGE_MAIN_CLASS_NAME)) {
+
+            // stop AppStarter timer
+            AppStarter.getTimer().cancel();
+
+            // Stop current process
+            this.applicationProcess.destroy();
+
+            if (this.applicationProcess.isAlive())
+                this.applicationProcess.destroyForcibly();
+
+            System.out.println("Murdered process with pid: " + AppStarter.getJamsPID());
+            // delete old files
+            remoteExecs.forEach((k, v) -> {
+                logger.info(remoteExecs.get(k));
+
+                if (k.equals(CORE_PACKAGE_MAIN_CLASS_NAME)) {
+                    File f = new File(System.getProperty("user.dir") + "/" + remoteExecs.get(k));
+                    f.delete();
+                    f = new File(System.getProperty("user.dir") + "/tmp/" + remoteExecs.get(k));
+                    if (!f.renameTo(new File(System.getProperty("user.dir") + "/" + remoteExecs.get(k))))
+                        logger.warning("An error occurred while attempting to move the file!");
+                    else
+                        f.delete();
+                } else {
+                    File f = new File(System.getProperty("user.dir") + "/lib/" + remoteExecs.get(k));
+                    f.delete();
+                    f = new File(System.getProperty("user.dir") + "/tmp/" + remoteExecs.get(k));
+                    if (!f.renameTo(new File(System.getProperty("user.dir") + "/lib/" + remoteExecs.get(k))))
+                        logger.warning("An error occurred while attempting to move the file!");
+                    else
+                        f.delete();
+                }
+            });
+
+            // Finally, start main jar
+
+            ProcessBuilder pb = AppStarter.startAccountManagementServer(parentArgs);
+            pb.inheritIO();
+            pb.directory(new File(System.getProperty("user.dir")));
+            this.applicationProcess = pb.start();
+            AppStarter.setJamsPID(this.applicationProcess.pid());
+            //Start process check timer again
+            AppStarter.setTimer(AppStarter.startProcessCheckTimer(20000, parentArgs));
+
+            System.out.println("New PID: " + AppStarter.getJamsPID());
+            System.out.println("Update complete. Now running newest version");
+
+        } else
+            System.out.println("An error occurred during the core package update process, the application has not been restarted.");
+
+        // delete tmp folder
+        try {
+            FileUtils.deleteDirectory(System.getProperty("user.dir") + "/tmp/");
+        } catch (IOException e) {
+            logger.warning("An error occurred while attempting to delete /tmp/ folder!");
+        }
+    }
+
+    private static String checksum(String filepath) throws IOException {
+
+        HashCode hash = com.google.common.io.Files
+                .hash(new File(filepath), Hashing.md5());
+
+        logger.info("Calculated md5: " + hash.toString());
+        return hash.toString();
+    }
+
+    @Override
+    public void run() {
+        Timer timer = new Timer();
+        // check if license is valid, then attempt SSL handshake with update server.
+        try {
+            System.out.println("Update timer started");
+            TimerTask pollTask = new TimerTask() {
+                @Override
+                public void run() {
+                    try {
+                        loadLicense();
+                        checkUpdate();
+                        if(doUpdate.get()) {
+                            doUpdate();
+                        }
+                    } catch (Exception e) {
+                        System.out.println("Exception thrown when attempting to update! " + e.toString());
+                    }
+                }
+            };
+            timer.scheduleAtFixedRate(pollTask, 15000, UPDATE_INTERVAL);
+        } catch (Exception e) {
+            logger.warning("error! " + e.toString());
+        }
+    }
+}
\ No newline at end of file
diff --git a/jams-launcher/src/main/java/module-info.java b/jams-launcher/src/main/java/module-info.java
new file mode 100644
index 0000000000000000000000000000000000000000..a8b0509b3f6070fd92906c5615c6f4602f92022c
--- /dev/null
+++ b/jams-launcher/src/main/java/module-info.java
@@ -0,0 +1,18 @@
+
+module jams.launcher {
+    opens com.google.guava;
+    opens org.zeromq.ZMQ;
+    requires lombok;
+    requires org.slf4j;
+    requires jsoniter;
+    requires javassist;
+    requires jdk.crypto.cryptoki;
+    requires java.base;
+    requires java.sql;
+    requires jeromq;
+    requires guava;
+    requires org.apache.httpcomponents.httpcore;
+    requires org.apache.httpcomponents.httpclient;
+    requires plexus.utils;
+}
+
diff --git a/jams-launcher/src/main/resources/oem/ca.crt b/jams-launcher/src/main/resources/oem/ca.crt
new file mode 100644
index 0000000000000000000000000000000000000000..4e4a1fdcdc732799485e301a3686560a26e1ef96
--- /dev/null
+++ b/jams-launcher/src/main/resources/oem/ca.crt
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+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
+-----END CERTIFICATE-----
diff --git a/jams-launcher/src/main/resources/oem/config.json b/jams-launcher/src/main/resources/oem/config.json
new file mode 100644
index 0000000000000000000000000000000000000000..80336b76a6fa4e6bcd65b870533b00429a1bc05d
--- /dev/null
+++ b/jams-launcher/src/main/resources/oem/config.json
@@ -0,0 +1,5 @@
+{
+  "CORE_PACKAGE_MAIN_CLASS_NAME": "net.jami.jams.server.Server",
+  "UPDATE_URL": "https://updates.jami.net",
+  "UPDATE_INTERVAL": 20000
+}
diff --git a/pom.xml b/pom.xml
index 13a992de4de6e5deabcf53c954f071704cc6a6d6..e55eb9726a217afc9d8e262b4cb5eb0eacd5ff85 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,6 +18,7 @@
         <module>jami-dht</module>
         <module>authentication-module</module>
         <module>jami-nameserver</module>
+        <module>jams-launcher</module>
     </modules>
 
     <properties>