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>