diff --git a/jami-dht/pom.xml b/jami-dht/pom.xml
index fb9ebf5324ce1af65023ea161bb96920628099d4..af19fca0f03453e6e329a36d54527fe36d59759a 100644
--- a/jami-dht/pom.xml
+++ b/jami-dht/pom.xml
@@ -25,7 +25,7 @@
         <dependency>
             <groupId>net.jami</groupId>
             <artifactId>jams-common</artifactId>
-            <version>2.0</version>
+            <version>${revision}</version>
             <scope>compile</scope>
         </dependency>
     </dependencies>
diff --git a/jams-common/src/main/java/module-info.java b/jams-common/src/main/java/module-info.java
index 4358de73c8dc950dee314af827dfc1df62752566..2bff57ef304db992ffb7668133b0b82f81cac2a3 100644
--- a/jams-common/src/main/java/module-info.java
+++ b/jams-common/src/main/java/module-info.java
@@ -57,5 +57,6 @@ module jams.common {
     requires java.sql;
     requires org.apache.xbean.classloader;
     requires jeromq;
+    requires java.naming;
 }
 
diff --git a/jams-launcher/pom.xml b/jams-launcher/pom.xml
index 9a63478b6d47c33f5454300045db1a48467958f2..1deb8d0b7b72a27dc01a9752ed93b22ac841f41e 100644
--- a/jams-launcher/pom.xml
+++ b/jams-launcher/pom.xml
@@ -36,11 +36,6 @@
             <artifactId>guava</artifactId>
             <version>20.0</version>
         </dependency>
-        <dependency>
-            <groupId>com.blockfeed</groupId>
-            <artifactId>messaging</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
     </dependencies>
 
     <build>
diff --git a/jams-launcher/src/main/java/launcher/AppStarter.java b/jams-launcher/src/main/java/launcher/AppStarter.java
index ad34217fa3b915c7295420ca34192744f906c291..89365bcb45f1562ea2f1927ea002163780eb7014 100644
--- a/jams-launcher/src/main/java/launcher/AppStarter.java
+++ b/jams-launcher/src/main/java/launcher/AppStarter.java
@@ -2,6 +2,7 @@ package launcher;
 
 import com.google.common.hash.HashCode;
 import com.google.common.hash.Hashing;
+import lombok.extern.slf4j.Slf4j;
 import org.zeromq.ZMQ;
 
 import java.io.BufferedReader;
@@ -10,14 +11,14 @@ import java.io.IOException;
 import java.io.InputStreamReader;
 import java.util.Timer;
 import java.util.TimerTask;
-import java.util.logging.Logger;
+import java.util.logging.log;
 
+@Slf4j
 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);
 
 
@@ -31,11 +32,10 @@ public class AppStarter extends Thread {
 
 
         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"));
+            log.info("md5 for all packages: ");
+            log.info("Core: " + checksum(System.getProperty("user.dir") + "/" + "jams-server.jar"));
+            log.info("AD Connector: " + checksum(System.getProperty("user.dir") + "/libs/" + "ad-connector.jar"));
+            log.info("LDAP Connector: " + checksum(System.getProperty("user.dir") + "/libs/" + "ldap-connector.jar"));
         } catch (IOException e) {
             e.printStackTrace();
         }
@@ -58,13 +58,13 @@ public class AppStarter extends Thread {
                 if (!isStillAlive(Long.toString(jamsPID))) {
 
                     try {
-                        logger.info("Process with pid: " + jamsPID + " found dead. Resurrecting...");
+                        log.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());
+                            log.fine("Directory: " + pb.directory().getAbsolutePath());
                             applicationProcess = pb.start();
                         }
 
@@ -72,9 +72,9 @@ public class AppStarter extends Thread {
                         if (applicationProcess.isAlive())
                             jamsPID = applicationProcess.pid();
 
-                        logger.info("New PID: " + jamsPID);
+                        log.info("New PID: " + jamsPID);
                     } catch (IOException e) {
-                        logger.severe("Exception thrown when attempting to startup jams process again!");
+                        log.severe("Exception thrown when attempting to startup jams process again!");
                     }
                 }
 
@@ -91,13 +91,13 @@ public class AppStarter extends Thread {
         String OS = System.getProperty("os.name").toLowerCase();
         String command;
         if (OS.indexOf("win") >= 0) {
-            logger.info("Check if pid " + pidStr + " is alive...");
+            log.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...");
+            log.info("Check if pid " + pidStr + " is alive...");
             command = "ps -p " + pidStr;
         } else {
-            logger.warning("Unsupported OS, failing gracefully... ");
+            log.warning("Unsupported OS, failing gracefully... ");
             return false;
         }
         return isProcessIdRunning(pidStr, command); // call generic implementation
@@ -119,7 +119,7 @@ public class AppStarter extends Thread {
 
             return false;
         } catch (Exception ex) {
-            logger.severe("Got exception using system command!");
+            log.severe("Got exception using system command!");
             return false;
         }
     }
@@ -131,7 +131,7 @@ public class AppStarter extends Thread {
             if (pb != null) {
                 pb.inheritIO();
                 pb.directory(new File(System.getProperty("user.dir")));
-                logger.info("Directory: " + pb.directory().getAbsolutePath());
+                log.info("Directory: " + pb.directory().getAbsolutePath());
                 applicationProcess = pb.start();
             }
 
@@ -141,7 +141,7 @@ public class AppStarter extends Thread {
 
         }
         catch (NullPointerException | IOException e) {
-            logger.severe("An exception occurred! " + e.toString());
+            log.severe("An exception occurred! " + e.toString());
         }
         UpdateThread updateThread = new UpdateThread(applicationProcess, parentArgs);
         updateThread.start();
diff --git a/jams-launcher/src/main/java/launcher/UpdateThread.java b/jams-launcher/src/main/java/launcher/UpdateThread.java
index 821a50f9115f85224655a9e950c95b05d3c46b11..8eecf729d054976fcb3223f3c577ace925651580 100644
--- a/jams-launcher/src/main/java/launcher/UpdateThread.java
+++ b/jams-launcher/src/main/java/launcher/UpdateThread.java
@@ -1,11 +1,7 @@
 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;
+<<<<<<< Updated upstream
 import org.apache.http.HttpResponse;
 import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.HttpGet;
@@ -15,399 +11,30 @@ 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", "") + "/libs");
-            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") + "/libs/" + 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") + "/libs/" + 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();
+        UpdateDaemon timer = new UpdateDaemon();
         // 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());
-                    }
-                }
-            };
+            UpdateCheckTask pollTask = new UpdateCheckTask();
             timer.scheduleAtFixedRate(pollTask, 15000, UPDATE_INTERVAL);
         } catch (Exception e) {
             logger.warning("error! " + e.toString());
diff --git a/jams-launcher/src/main/java/module-info.java b/jams-launcher/src/main/java/module-info.java
index a8b0509b3f6070fd92906c5615c6f4602f92022c..32a36368e867545021d6ed6944677092af229360 100644
--- a/jams-launcher/src/main/java/module-info.java
+++ b/jams-launcher/src/main/java/module-info.java
@@ -14,5 +14,6 @@ module jams.launcher {
     requires org.apache.httpcomponents.httpcore;
     requires org.apache.httpcomponents.httpclient;
     requires plexus.utils;
+    requires updater;
 }
 
diff --git a/jams-server/src/main/java/module-info.java b/jams-server/src/main/java/module-info.java
index b695686d7f4179a841768289a220bac0b94f7e9f..7ab7f5dd99500436d31941ced10ad7f15bf96377 100644
--- a/jams-server/src/main/java/module-info.java
+++ b/jams-server/src/main/java/module-info.java
@@ -18,6 +18,7 @@ module jams.server {
     requires java.naming;
     requires java.logging;
     requires javax.servlet.api;
+    requires org.json;
     exports net.jami.jams.server.servlets.general to org.apache.tomcat.embed.core;
     exports net.jami.jams.server.servlets.filters to org.apache.tomcat.embed.core;
     exports net.jami.jams.server.servlets.api.auth.login to org.apache.tomcat.embed.core;
diff --git a/jams-server/src/main/java/net/jami/jams/server/Server.java b/jams-server/src/main/java/net/jami/jams/server/Server.java
index ad882a203f2578ae7a07c59057c00f6bce0cffa9..df374d4627f78dc9c51ce7d3ea68fcd185623a30 100644
--- a/jams-server/src/main/java/net/jami/jams/server/Server.java
+++ b/jams-server/src/main/java/net/jami/jams/server/Server.java
@@ -15,6 +15,7 @@ import net.jami.jams.common.utils.UpdateInterface;
 import net.jami.jams.nameserver.LocalNameServer;
 import net.jami.jams.nameserver.PublicNameServer;
 import net.jami.jams.server.core.TomcatLauncher;
+import net.jami.jams.server.licensing.LicenseService;
 import net.jami.jams.server.startup.AuthModuleLoader;
 import net.jami.jams.server.startup.CryptoEngineLoader;
 
@@ -80,6 +81,9 @@ public class Server {
                     else nameServer = new LocalNameServer(dataStore,userAuthenticationModule,serverSettings.getServerPublicURI());
                 }
                 else nameServer = new LocalNameServer(dataStore,userAuthenticationModule,serverSettings.getServerPublicURI());
+
+                new LicenseService().loadLicense();
+                appUpdater = UpdaterLoader.loadUpdater();
                 log.info("All services are UP and ready for use...");
             }
             catch (Exception e){
diff --git a/jams-server/src/main/java/net/jami/jams/server/licensing/LicenseService.java b/jams-server/src/main/java/net/jami/jams/server/licensing/LicenseService.java
index 1e0f30935cfbc0a0deb2e834faba4d43aadafa00..0b91c031008879bbc25afa0c3dc77d2fb6a38cd1 100644
--- a/jams-server/src/main/java/net/jami/jams/server/licensing/LicenseService.java
+++ b/jams-server/src/main/java/net/jami/jams/server/licensing/LicenseService.java
@@ -64,14 +64,14 @@ public class LicenseService {
             try{
                 ((X509Certificate) c).checkValidity();
                 c.verify(ca.getPublicKey());
-                Server.setActivated(true);
+                Server.activated.set(true);
                 if (array != null) {
                     JSONObject jsonObject = new JSONObject(new String(array));
                     licenseType = (String) jsonObject.get("type");
                 }
             }
             catch (Exception e){
-                Server.setActivated(false);
+                Server.activated.set(false);
                 licenseType = "COMMUNITY";
                 log.warn("Your license is no longer valid or has been tampered with - " + e.toString());
             }
diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/update/LicenseServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/update/LicenseServlet.java
index de2518e1b2e002f8e5be1458852755164d38260d..0f78fc043f24f1c222d106b6c4d2c4d82f889317 100644
--- a/jams-server/src/main/java/net/jami/jams/server/servlets/api/update/LicenseServlet.java
+++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/update/LicenseServlet.java
@@ -26,7 +26,7 @@ public class LicenseServlet extends HttpServlet {
         try {
             resp.setStatus(200);
             HashMap<String,Object> payload = new HashMap<>();
-            payload.put("isActive", Server.isActivated());
+            payload.put("isActive", Server.activated.get());
             payload.put("licenseType", new LicenseService().getLicenseType());
             payload.put("currentVersion", LicenseUtils.checkVersion(System.getProperty("user.dir") + "/tmpjar/", System.getProperty("user.dir") + "/jams-server.jar"));
             resp.getOutputStream().write(JsonStream.serialize(payload).getBytes());
diff --git a/pom.xml b/pom.xml
index e55eb9726a217afc9d8e262b4cb5eb0eacd5ff85..5d2948267ac00c703dba9620d5d742629898b9b4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,6 +24,9 @@
     <properties>
         <revision>2.0</revision>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+        <revision>0.5A</revision>
         <maven.compiler.version>3.8.1</maven.compiler.version>
         <java.version>11</java.version>
         <bouncy.castle.version>1.65</bouncy.castle.version>
diff --git a/updater/pom.xml b/updater/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5e4e56891f9cbd9e19ca32e76b873731e3bd9dcf
--- /dev/null
+++ b/updater/pom.xml
@@ -0,0 +1,104 @@
+<?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>updater</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>net.jami</groupId>
+            <artifactId>jams-common</artifactId>
+            <version>${revision}</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore</artifactId>
+            <version>${apache.httpcore.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>${apache.httpclient.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>20.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.plexus</groupId>
+            <artifactId>plexus-utils</artifactId>
+            <version>3.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.zeromq</groupId>
+            <artifactId>jeromq</artifactId>
+            <version>${jeromq.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.zeromq</groupId>
+            <artifactId>jeromq</artifactId>
+            <version>0.5.2</version>
+        </dependency>
+        <dependency>
+            <groupId>net.jami</groupId>
+            <artifactId>jams-common</artifactId>
+            <version>0.5A</version>
+            <scope>compile</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>${maven.shade.version}</version>
+                <executions>
+                    <!-- Run shade goal on package phase -->
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <outputFile>../jams/libs/${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>net.jami.jams.server.Server</Main-Class>
+                                        <Implementation-Title>${project.artifactId}</Implementation-Title>
+                                        <Implementation-Version>${project.version}</Implementation-Version>
+                                        <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/updater/src/main/java/module-info.java b/updater/src/main/java/module-info.java
new file mode 100644
index 0000000000000000000000000000000000000000..205d6d9200703e631b5c0efb4308dbc47a2305d0
--- /dev/null
+++ b/updater/src/main/java/module-info.java
@@ -0,0 +1,13 @@
+module updater {
+    exports net.jami.jams.updater;
+    requires jams.common;
+    requires lombok;
+    requires org.slf4j;
+    requires jsoniter;
+    requires guava;
+    requires org.apache.httpcomponents.httpclient;
+    requires org.apache.httpcomponents.httpcore;
+    requires plexus.utils;
+    requires jeromq;
+
+}
\ No newline at end of file
diff --git a/updater/src/main/java/net/jami/jams/updater/JAMSUpdater.java b/updater/src/main/java/net/jami/jams/updater/JAMSUpdater.java
new file mode 100644
index 0000000000000000000000000000000000000000..7df011d43d97b6f758b1ca3982bc73e60551d257
--- /dev/null
+++ b/updater/src/main/java/net/jami/jams/updater/JAMSUpdater.java
@@ -0,0 +1,236 @@
+package net.jami.jams.updater;
+
+import net.jami.jams.common.updater.AppUpdater;
+import org.apache.http.ssl.SSLContexts;
+import org.zeromq.SocketType;
+import org.zeromq.ZMQ;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+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.Base64;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+public class JAMSUpdater implements AppUpdater {
+
+    UpdateDownloader updateDownloader = new UpdateDownloader();
+    private SSLSocketFactory sslSocketFactory;
+    private SSLContext sslContext = null;
+    private ZMQ.Socket publisher;
+    private ZMQ.Socket receiver;
+    private MessageReceiver messageReceiver;
+    private AtomicBoolean doUpdate = new AtomicBoolean(false);
+
+    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<>();
+
+    public JAMSUpdater() {
+
+        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) {
+            log.warn("Could not create and bind publisher and/or receiver! Please contact software developer");
+            System.exit(-1);
+        }
+    }
+
+    //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;
+    }
+
+    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());
+            }
+        }
+    }
+
+    @Override
+    public String getCurrentVersions(String folder) {
+
+        try {
+            File folderExec = new File(folder.replace("/jams-launcher", ""));
+            File folderLibs = new File(folder.replace("/jams-launcher", "") + "/libs");
+            discoverVersions(folderExec);
+            discoverVersions(folderLibs);
+        } catch (Exception e) {
+            log.warn(e.toString());
+        }
+    }
+
+    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 = this.getClass().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) {
+                log.warn("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) {
+            log.warn("Could not read license file with error " + e.toString());
+            return false;
+        }
+        return true;
+    }
+
+    //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.
+        getCurrentVersions(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());
+        }
+    }
+
+
+
+    @Override
+    public String getLatestVersion() {
+        return null;
+    }
+
+    @Override
+    public boolean downloadUpdates() {
+        return updateDownloader.doUpdate();
+    }
+}
diff --git a/jams-launcher/src/main/java/launcher/MessageReceiver.java b/updater/src/main/java/net/jami/jams/updater/MessageReceiver.java
similarity index 95%
rename from jams-launcher/src/main/java/launcher/MessageReceiver.java
rename to updater/src/main/java/net/jami/jams/updater/MessageReceiver.java
index 8c153312537536d9660afe06effeec1cdc77ea6f..e25571d4ba778dd462b716f7229d1d0d945f039b 100644
--- a/jams-launcher/src/main/java/launcher/MessageReceiver.java
+++ b/updater/src/main/java/net/jami/jams/updater/MessageReceiver.java
@@ -1,4 +1,4 @@
-package launcher;
+package net.jami.jams.updater;
 
 import org.zeromq.ZMQ;
 
diff --git a/updater/src/main/java/net/jami/jams/updater/UpdateCheckTask.java b/updater/src/main/java/net/jami/jams/updater/UpdateCheckTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..94ca31d924f7d80af620ef6103f9b6b6fcca5a8f
--- /dev/null
+++ b/updater/src/main/java/net/jami/jams/updater/UpdateCheckTask.java
@@ -0,0 +1,63 @@
+package net.jami.jams.updater;
+
+import com.jsoniter.JsonIterator;
+import com.jsoniter.any.Any;
+import lombok.extern.slf4j.Slf4j;
+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 javax.net.ssl.HttpsURLConnection;
+import java.net.URL;
+import java.util.List;
+import java.util.TimerTask;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static net.jami.jams.updater.UpdateDaemon.UPDATE_SERVER_URI;
+
+@Slf4j
+public class UpdateCheckTask extends TimerTask {
+
+    // private final List<String> files;
+    private AtomicBoolean doUpdate = new AtomicBoolean(false);
+
+    public UpdateCheckTask() {
+        // this.files = files;
+    }
+
+    @Override
+    public void run() {
+
+        try {
+            JAMSUpdater updater = new JAMSUpdater();
+            updater.loadLicense();
+            updater.checkUpdate();
+            if(doUpdate.get()) {
+                doUpdate();
+            }
+        } catch (Exception e) {
+            System.out.println("Exception thrown when attempting to update! " + e.toString());
+        }
+
+    }
+
+    // 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) {
+            log.warn("Could not establish connection to JAMS Update Center with error: " + e.toString());
+        }
+    }
+}
diff --git a/updater/src/main/java/net/jami/jams/updater/UpdateDaemon.java b/updater/src/main/java/net/jami/jams/updater/UpdateDaemon.java
new file mode 100644
index 0000000000000000000000000000000000000000..1fba028ecd97dcde7f612825b67eb390b0cfae77
--- /dev/null
+++ b/updater/src/main/java/net/jami/jams/updater/UpdateDaemon.java
@@ -0,0 +1,30 @@
+package net.jami.jams.updater;
+
+import com.jsoniter.JsonIterator;
+import com.jsoniter.any.Any;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+
+@Getter
+@Setter
+public class UpdateDaemon extends Timer {
+
+    private static volatile String UPDATE_SERVER_URL;
+    private static volatile Long UPDATE_INTERVAL;
+    public static final List<String> updateFiles = new ArrayList<>();
+
+    public UpdateDaemon() {
+
+        InputStream input = this.getClass().getClassLoader().getResourceAsStream("oem/config.json");
+        Any any = JsonIterator.deserialize(input.readAllBytes());
+        UPDATE_SERVER_URL = any.get("UPDATE_URL").toString();
+        UPDATE_INTERVAL = any.get("UPDATE_INTERVAL").toLong();
+        this.schedule(new UpdateCheckTask(updateFiles),0,UPDATE_INTERVAL);
+    }
+
+}
diff --git a/updater/src/main/java/net/jami/jams/updater/UpdateDownloader.java b/updater/src/main/java/net/jami/jams/updater/UpdateDownloader.java
new file mode 100644
index 0000000000000000000000000000000000000000..f7a4784328ce07935788074c5cdaf9860f61edeb
--- /dev/null
+++ b/updater/src/main/java/net/jami/jams/updater/UpdateDownloader.java
@@ -0,0 +1,165 @@
+package net.jami.jams.updater;
+
+import com.google.common.hash.HashCode;
+import com.google.common.hash.Hashing;
+import com.jsoniter.JsonIterator;
+import com.jsoniter.any.Any;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+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 javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import java.io.*;
+import java.util.HashMap;
+
+@Slf4j
+public class UpdateDownloader {
+    private SSLSocketFactory sslSocketFactory;
+    private SSLContext sslContext = null;
+    private Process applicationProcess;
+
+
+    private static volatile String CORE_PACKAGE_MAIN_CLASS_NAME;
+    private static volatile String UPDATE_SERVER_URL;
+    private static volatile Long UPDATE_INTERVAL;
+
+
+    public UpdateDownloader() {
+        try {
+            InputStream input = this.getClass().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) {
+            log.warn("Missing OEM configuration! Please contact software developer");
+            System.exit(-1);
+        }
+    }
+
+    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) {
+                    log.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))) {
+                            log.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))) {
+                            log.info("Successfully downloaded a library package!");
+                            remoteChecksums.remove(remoteChecksums.get(k));
+                        }
+                    }
+
+                } else {
+                    log.warn("The server declared an update but does not have the required files?!");
+                }
+            } catch (Exception e1) {
+                log.warn("Could not download an update with error " + e1.toString());
+            }
+        });
+    }
+
+    private static String checksum(String filepath) throws IOException {
+
+        HashCode hash = com.google.common.io.Files
+                .hash(new File(filepath), Hashing.md5());
+
+        log.warn("Calculated md5: " + hash.toString());
+        return hash.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) -> {
+                log.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))))
+                        log.warn("An error occurred while attempting to move the file!");
+                    else
+                        f.delete();
+                } else {
+                    File f = new File(System.getProperty("user.dir") + "/libs/" + 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") + "/libs/" + remoteExecs.get(k))))
+                        log.warn("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) {
+            log.warn("An error occurred while attempting to delete /tmp/ folder!");
+        }
+    }
+
+
+}