diff --git a/buildScript/dependencies.gradle b/buildScript/dependencies.gradle
index 63b88a606..700b94a1b 100644
--- a/buildScript/dependencies.gradle
+++ b/buildScript/dependencies.gradle
@@ -108,6 +108,11 @@ dependencies {
implementation 'org.asdf-format:asdf-core:0.1-alpha-10'
implementation 'edu.stsci:roman-datamodels:0.1-alpha-3'
+ // tomcat for standalone
+ implementation 'org.apache.tomcat.embed:tomcat-embed-core:11.0.21'
+ implementation 'org.apache.tomcat.embed:tomcat-embed-websocket:11.0.21'
+ implementation 'org.apache.tomcat.embed:tomcat-embed-jasper:11.0.21'
+
}
diff --git a/config/web.xml b/config/web.xml
index 0bce30626..fec09b187 100644
--- a/config/web.xml
+++ b/config/web.xml
@@ -89,29 +89,6 @@
/CmdSrv/async/*
-
- H2Console
- org.h2.server.web.WebServlet
-
- -webAllowOthers
- true
-
- 2
-
-
- H2Console
- /admin/db/*
-
-
-
- alertviewer
- /alertviewer.html
-
-
- alertviewer
- /alertviewer/*
-
-
diff --git a/settings.gradle b/settings.gradle
index ccc464db0..d0393cb12 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,7 +1,8 @@
rootProject.name = 'firefly_root'
-include 'firefly', 'firefly_data'
+include 'firefly', 'firefly_data', "firefly_standalone"
project(":firefly").projectDir = file('src/firefly')
project(":firefly_data").projectDir = file('src/firefly_data')
+project(":firefly_standalone").projectDir = file('src/firefly_standalone')
diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/util/VersionUtil.java b/src/firefly/java/edu/caltech/ipac/firefly/server/util/VersionUtil.java
index a340ecb39..718deb2eb 100644
--- a/src/firefly/java/edu/caltech/ipac/firefly/server/util/VersionUtil.java
+++ b/src/firefly/java/edu/caltech/ipac/firefly/server/util/VersionUtil.java
@@ -64,32 +64,35 @@ public static void initVersion(ServletContext context) {
}
public static void ingestVersion(ServletContext context) {
-
- _version.setAppName(context.getServletContextName());
- _version.setConfigLastModTime(ServerContext.getConfigLastModTime());
-
- File confDir = ServerContext.getWebappConfigDir();
- Properties props = new Properties();
try {
+ _version.setAppName(context.getServletContextName());
+ _version.setConfigLastModTime(ServerContext.getConfigLastModTime());
+
+ File confDir = ServerContext.getWebappConfigDir();
+ Properties props = new Properties();
props.load(new FileInputStream(new File(confDir, VERSION_FILE)));
- _version.setMajor(getNum(props.getProperty(MAJOR)));
- _version.setMinor(getNum(props.getProperty(MINOR)));
- _version.setRev(props.getProperty(REV));
- _version.setVersionType(Version.convertVersionType(props.getProperty(TYPE)));
- _version.setBuild(getNum(props.getProperty(BUILD_NUMBER)));
- _version.setBuildDate(props.getProperty(BUILD_DATE));
- _version.setBuildTime(props.getProperty(BUILD_TIME));
- _version.setBuildTag(props.getProperty(BUILD_TAG));
- _version.setBuildCommit(props.getProperty(BUILD_COMMIT));
- _version.setBuildCommitFirefly(props.getProperty(BUILD_COMMIT_FIREFLY));
- _version.setBuildFireflyTag(props.getProperty(BUILD_FIREFLY_TAG));
- _version.setBuildFireflyBranch(props.getProperty(BUILD_FIREFLY_BRANCH));
- _version.setDevCycleTag(props.getProperty(DEV_CYCLE_TAG));
+ ingestVersion(props);
} catch (IOException e) {
// just ignore
}
}
+ public static void ingestVersion(Properties props) {
+ _version.setMajor(getNum(props.getProperty(MAJOR)));
+ _version.setMinor(getNum(props.getProperty(MINOR)));
+ _version.setRev(props.getProperty(REV));
+ _version.setVersionType(Version.convertVersionType(props.getProperty(TYPE)));
+ _version.setBuild(getNum(props.getProperty(BUILD_NUMBER)));
+ _version.setBuildDate(props.getProperty(BUILD_DATE));
+ _version.setBuildTime(props.getProperty(BUILD_TIME));
+ _version.setBuildTag(props.getProperty(BUILD_TAG));
+ _version.setBuildCommit(props.getProperty(BUILD_COMMIT));
+ _version.setBuildCommitFirefly(props.getProperty(BUILD_COMMIT_FIREFLY));
+ _version.setBuildFireflyTag(props.getProperty(BUILD_FIREFLY_TAG));
+ _version.setBuildFireflyBranch(props.getProperty(BUILD_FIREFLY_BRANCH));
+ _version.setDevCycleTag(props.getProperty(DEV_CYCLE_TAG));
+ }
+
public static Version getAppVersion() { return _version; }
private static int getNum(String s) {
diff --git a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisContext.java b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisContext.java
index 7f2bdc7ee..a63cbd0ed 100644
--- a/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisContext.java
+++ b/src/firefly/java/edu/caltech/ipac/firefly/server/visualize/VisContext.java
@@ -27,7 +27,9 @@ public class VisContext {
static public void init() {
if (_initialized) return;
- System.setProperty("java.awt.headless", "true");
+// System.setProperty("java.awt.headless", "true");
+ boolean desktop= AppProperties.getBooleanProperty("runAsDesktopApplication", false);
+ System.setProperty("java.awt.headless", desktop ? "false" : "true"); //todo make this smart depending on context, no PR until it is done!!!!
initFootprints();
initCounters();
_initialized = true;
diff --git a/src/firefly/java/edu/caltech/ipac/util/StringUtils.java b/src/firefly/java/edu/caltech/ipac/util/StringUtils.java
index 4584c8364..bdfd01e4a 100644
--- a/src/firefly/java/edu/caltech/ipac/util/StringUtils.java
+++ b/src/firefly/java/edu/caltech/ipac/util/StringUtils.java
@@ -4,7 +4,6 @@
package edu.caltech.ipac.util;
import edu.caltech.ipac.firefly.core.Util;
-import edu.caltech.ipac.firefly.server.util.Logger;
import javax.validation.constraints.NotNull;
import java.net.MalformedURLException;
@@ -45,8 +44,6 @@ public static enum Align {LEFT, RIGHT, MIDDLE}
public static final long GIG_HUNDREDTH= GIG / 100;
public static final long K = 1024;
- private static final Logger.LoggerImpl logger = Logger.getLogger();
-
public static String[] groupMatch(String regex, String val) {
return groupMatch(regex, val, 0);
}
diff --git a/src/firefly/java/edu/caltech/ipac/util/download/URLDownload.java b/src/firefly/java/edu/caltech/ipac/util/download/URLDownload.java
index 123e6462a..e21bfc040 100644
--- a/src/firefly/java/edu/caltech/ipac/util/download/URLDownload.java
+++ b/src/firefly/java/edu/caltech/ipac/util/download/URLDownload.java
@@ -58,7 +58,6 @@
public class URLDownload {
private static final int BUFFER_SIZE = FileUtil.BUFFER_SIZE;
- private static final Logger.LoggerImpl _log = Logger.getLogger();
private static final int MAX_REDIRECT= 2;
static {
@@ -83,7 +82,7 @@ public void checkServerTrusted(X509Certificate[] arg0, String arg1) { }
sc.init(null, trustAllCerts, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (KeyManagementException | NoSuchAlgorithmException e) {
- _log.error(e);
+ loggerError(e);
}
}
@@ -200,11 +199,13 @@ public static Map buildReqHeaders(URL url, Map r
if (requestHeaders== null) requestHeaders= Collections.emptyMap();
Map h = new HashMap<>(requestHeaders);
if (ops==null || ops.useCredentials) {
- var inputs= new HttpServiceInput(url.toString());
- var credentials= inputs.getHeaders();
- if (credentials!=null && !credentials.isEmpty()) {
- if (!credentials.keySet().stream().allMatch(h::containsKey)) h.putAll(credentials);
- }
+ try {
+ var inputs= new HttpServiceInput(url.toString());
+ var credentials= inputs.getHeaders();
+ if (credentials!=null && !credentials.isEmpty()) {
+ if (!credentials.keySet().stream().allMatch(h::containsKey)) h.putAll(credentials);
+ }
+ } catch (NoClassDefFoundError ignore) { }
}
return h;
}
@@ -247,7 +248,11 @@ private static String sendHeadersToCompactStr(Map> sendHeade
}
}
else {
- workBuff.append(sanitizeHeader(se.getKey(), String.valueOf(se.getValue())));
+ try {
+ workBuff.append(sanitizeHeader(se.getKey(), String.valueOf(se.getValue())));
+ } catch (NoClassDefFoundError e) {
+ workBuff.append(se.getKey()+"="+se.getValue());
+ }
}
outStr.append(workBuff.toString());
}
@@ -306,7 +311,7 @@ public static URLConnection makeConnection(URL url,
}
return conn;
} catch (IOException e) {
- logError(url,null,e);
+ loggerError(url,null,e);
throw e;
}
}
@@ -390,10 +395,10 @@ public static HttpResultInfo getDataFromURL(URL url,
if (!ops.logErrorsOnly) logSuccess(result,url,dlSeconds,reqProp, postData);
return result;
} catch (SSLException | SocketTimeoutException | UnknownHostException e) {
- logError(url, postData, e);
+ loggerError(url, postData, e);
return exceptionToResponse(e,requestHeaders);
} catch (IOException e) {
- logError(url, postData, e);
+ loggerError(url, postData, e);
throw new FailedRequestException(ResponseMessage.getNetworkCallFailureMessage(e), e, getResponseCode(conn));
}
}
@@ -418,7 +423,7 @@ public static HttpResultInfo getHeader(URL url,
} catch (SSLException | SocketTimeoutException | UnknownHostException e) {
return exceptionToResponse(e,h);
} catch (IOException e) {
- logError(url, null, e);
+ loggerError(url, null, e);
throw new FailedRequestException(ResponseMessage.getNetworkCallFailureMessage(e), e, -1);
}
}
@@ -476,10 +481,10 @@ private static HttpResultInfo getHeaderFromConnection(HttpURLConnection conn,
}
return result;
} catch (SSLException | SocketTimeoutException | UnknownHostException e) {
- logError(conn.getURL(), null , e);
+ loggerError(conn.getURL(), null , e);
return exceptionToResponse(e,requestHeaders);
} catch (IOException e) {
- logError(conn.getURL(), null, e);
+ loggerError(conn.getURL(), null, e);
throw new FailedRequestException(ResponseMessage.getNetworkCallFailureMessage(e), e, getResponseCode(conn));
}
}
@@ -505,7 +510,7 @@ public static FileInfo getDataToFileUsingPost(URL url, Map postData,
Options ops= new Options(true, true, 0L, false, false, timeoutInSec, dl, false, false);
return getDataToFile(makeURLConnection(url, cookies, requestHeader), outfile, ops, postData,0);
} catch (IOException e) {
- logError(url, postData, e);
+ loggerError(url, postData, e);
throw new FailedRequestException(ResponseMessage.getNetworkCallFailureMessage(e), e);
}
}
@@ -636,7 +641,7 @@ public static FileInfo getDataToFile(HttpURLConnection conn,
} catch (SSLException | SocketTimeoutException | UnknownHostException e) {
return exceptionToFileInfo(e);
} catch (IOException e) {
- logError(conn.getURL(), null, e);
+ loggerError(conn.getURL(), null, e);
throw new FailedRequestException(ResponseMessage.getNetworkCallFailureMessage(e),e, getResponseCode(conn));
}
}
@@ -760,7 +765,7 @@ private static FileInfo checkAlreadyDownloaded(HttpURLConnection urlConn, File o
urlConn.setIfModifiedSince(outfile.lastModified());
if (getResponseCode(urlConn) == HttpURLConnection.HTTP_NOT_MODIFIED) {
String urlStr= urlConn.getURL().toString();
- _log.info(outfile.getName() + ": Not downloading, already have current version, from "+urlStr);
+ loggerInfo(outfile.getName() + ": Not downloading, already have current version, from "+urlStr);
retval = new FileInfo(outfile, getSuggestedFileName(urlConn), HttpURLConnection.HTTP_NOT_MODIFIED,
ResponseMessage.getHttpResponseMessage(HttpURLConnection.HTTP_NOT_MODIFIED));
retval.putAttribute(FileInfo.FILE_DOWNLOADED,false+"");
@@ -774,7 +779,7 @@ private static FileInfo checkAlreadyDownloaded(HttpURLConnection urlConn, File o
}
- private static void logError(URL url, Map postData, Exception e) {
+ private static void loggerError(URL url, Map postData, Exception e) {
List strList = new ArrayList<>(6);
strList.add("----------Network Error-----------");
if (url != null) {
@@ -788,7 +793,7 @@ private static void logError(URL url, Map postData, Exception e) {
strList.add(StringUtils.pad(20,"----------Exception "));
strList.add(e.toString());
}
- _log.warn(strList.toArray(new String[0]));
+ loggerInfo(strList.toArray(new String[0]));
}
private static void logHeaderForError(String originalUrl, Map postData, HttpURLConnection conn, Map> sendHeaders) {
@@ -861,9 +866,9 @@ private static void logHeaderForError(String originalUrl, Map postData
else {
outStr.add("No headers or status received, invalid http response, using work around");
}
- _log.info(outStr.toArray(new String[0]));
+ loggerInfo(outStr.toArray(new String[0]));
} catch (Exception e) {
- _log.info(e.getMessage() + ":" + " url=" + (conn.getURL()!=null ? conn.getURL().toString() : "none"));
+ loggerInfo(e.getMessage() + ":" + " url=" + (conn.getURL()!=null ? conn.getURL().toString() : "none"));
}
}
@@ -880,7 +885,7 @@ private static void logSuccess(FileInfo fileInfo, File outfile, URL url, double
"more response headers: "+otherHeadersToStr(fileInfo)
));
if (postData!=null) strList.add("post data: " +postDataLogString(postData));
- _log.info(strList.toArray(new String[0]));
+ loggerInfo(strList.toArray(new String[0]));
}
private static void logSuccess(HttpResultInfo r, URL url, double dSeconds, Map> sendHeaders, Map postData) {
@@ -889,7 +894,7 @@ private static void logSuccess(HttpResultInfo r, URL url, double dSeconds, Map values) {
return workBuff.toString();
}
+ private static void loggerInfo(String ...msgs) {
+ try {
+ Logger.getLogger().info(msgs);
+ } catch (NoClassDefFoundError t) {
+ Arrays.stream(msgs).forEach(System.out::println);
+ }
+ }
+
+ private static void loggerError(Throwable t, String ...msgs) {
+ try {
+ Logger.getLogger().error(t, msgs);
+ } catch (NoClassDefFoundError e) {
+ System.out.println(t.getMessage());
+ Arrays.stream(msgs).forEach(System.out::println);
+ }
+ }
+
public static class Options {
private boolean onlyIfModified;
private boolean uncompress;
diff --git a/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsRead.java b/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsRead.java
index 490466605..b39cfb8ba 100644
--- a/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsRead.java
+++ b/src/firefly/java/edu/caltech/ipac/visualize/plot/plotdata/FitsRead.java
@@ -249,6 +249,7 @@ public static void setDefaultFutureStretch(RangeValues defaultRangeValues) {
* @param lsstMasks mask array
* @deprecated
*/
+ @Deprecated
public synchronized void doStretchMask(
byte[] pixelData,
int startPixel,
diff --git a/src/firefly_standalone/assets/fireflyDockIcon.png b/src/firefly_standalone/assets/fireflyDockIcon.png
new file mode 100644
index 000000000..9df5ee29d
Binary files /dev/null and b/src/firefly_standalone/assets/fireflyDockIcon.png differ
diff --git a/src/firefly_standalone/assets/fireflySplash.png b/src/firefly_standalone/assets/fireflySplash.png
new file mode 100644
index 000000000..3281c98ca
Binary files /dev/null and b/src/firefly_standalone/assets/fireflySplash.png differ
diff --git a/src/firefly_standalone/assets/javaInstaller.sh b/src/firefly_standalone/assets/javaInstaller.sh
new file mode 100755
index 000000000..bc5f5e0f2
--- /dev/null
+++ b/src/firefly_standalone/assets/javaInstaller.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+
+
+getJreKey() {
+ arch=$(uname -a)
+ name=$(uname)
+ jarKey="unknown"
+
+ if [[ "$name" == "Darwin" ]]; then
+ if [[ "$arch" == "arm64" ]]; then
+ jreKey="macOSArm64"
+ else
+ jreKey="macOSIntel"
+ fi
+ else
+ if [[ "$arch" == "aarch64" ]]; then
+ jreKey="linuxArm64"
+ elif [[ "$arch" == "x86_64" ]]; then
+ jreKey="linux64"
+ else
+ jreKey=
+ fi
+ fi
+
+ echo "$jreKey"
+}
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+INSTALL_DIR=$(cd "${SCRIPT_DIR}/../.." && pwd)
+javaInstallation="${INSTALL_DIR}/javaInstallation"
+jreJsonFile="${INSTALL_DIR}/application/current/jreVersion.json"
+
+
+#jreKey=$($INSTALL_DIR/application/current/jreKey.sh)
+jreKey=$(getJreKey)
+
+jreUrl=$(cat "$jreJsonFile" | jq -r ".$jreKey.url")
+javaPath=$(cat "$jreJsonFile" | jq -r ".$jreKey.java")
+JAVA="$javaInstallation/$javaPath"
+
+if [ -f "$JAVA" ]; then
+ echo $JAVA
+ exit 0
+fi
+
+mkdir "$javaInstallation"
+curl -L "$jreUrl" > "$javaInstallation/jre.tar.gz"
+(cd "$javaInstallation" && tar -xzvf jre.tar.gz &> $javaInstallation/jre_tar_expand.log)
+echo $JAVA
+
diff --git a/src/firefly_standalone/assets/jreVersion.json b/src/firefly_standalone/assets/jreVersion.json
new file mode 100644
index 000000000..e8c6676a2
--- /dev/null
+++ b/src/firefly_standalone/assets/jreVersion.json
@@ -0,0 +1,23 @@
+{
+ "javaVersion" : "21.0.11",
+ "linux64" : {
+ "url": "https://api.adoptium.net/v3/binary/latest/21/ga/linux/x64/jre/hotspot/normal/eclipse",
+ "java" : "jdk-21.0.11+10-jre/Contents/Home/bin/java"
+ },
+ "linuxArm64" : {
+ "url": "https://api.adoptium.net/v3/binary/latest/21/ga/linux/aarch64/jre/hotspot/normal/eclipse",
+ "java" : "jdk-21.0.11+10-jre/Contents/Home/bin/java"
+ },
+ "macOSIntel" : {
+ "url": "https://api.adoptium.net/v3/binary/latest/21/ga/mac/x64/jre/hotspot/normal/eclipse",
+ "java" : "jdk-21.0.11+10-jre/Contents/Home/bin/java"
+ },
+ "macOSArm64" : {
+ "url": "https://api.adoptium.net/v3/binary/latest/21/ga/mac/aarch64/jre/hotspot/normal/eclipse",
+ "java" : "jdk-21.0.11+10-jre/Contents/Home/bin/java"
+ },
+ "window" : {
+ "url": "https://api.adoptium.net/v3/binary/latest/21/ga/windows/x64/jre/hotspot/normal/eclipse",
+ "java" : "don't know"
+ }
+}
\ No newline at end of file
diff --git a/src/firefly_standalone/assets/package-files.txt b/src/firefly_standalone/assets/package-files.txt
new file mode 100644
index 000000000..5bc73bcdf
--- /dev/null
+++ b/src/firefly_standalone/assets/package-files.txt
@@ -0,0 +1,6 @@
+
+firefly.war
+tomcat-annotations-api-11.0.21.jar
+tomcat-embed-core-11.0.21.jar
+tomcat-embed-websocket-11.0.21.jar
+standalone.jar
diff --git a/src/firefly_standalone/assets/standalone_cleanup.sh b/src/firefly_standalone/assets/standalone_cleanup.sh
new file mode 100644
index 000000000..92f53b5ba
--- /dev/null
+++ b/src/firefly_standalone/assets/standalone_cleanup.sh
@@ -0,0 +1,46 @@
+
+cleanupMinutes=1440
+logfileMaxAge=7200
+rm_cmd=/bin/rm
+rm_cmd="echo will remove:"
+workarea_dir="${HOME}/.firefly/workarea/firefly"
+shared_workarea="${HOME}/firefly/shared-workarea"
+find_cmd="/usr/bin/find"
+
+function doCleanup() {
+ workarea="${1}"
+ log_dir="${workarea}/cleanup_logs"
+ mkdir -p "${log_dir}"
+
+ # cleanup old log files
+ ${find_cmd} "${log_dir}" -type f -mmin +${logfileMaxAge} -exec $rm_cmd '{}' \+
+
+ # cleanup old work files
+ timestamp=$(date +20%y%m%dT%H%M%S)
+ log_file="${log_dir}/cleanup.${timestamp}.log"
+ clean_dirs=("${workarea}/temp_files" "${workarea}/visualize/fits-cache" "${workarea}/visualize/users")
+ dirs_to_clear=("${workarea}/visualize/users" "${workarea}/temp_files")
+ echo "Cleanup: " $workarea
+ echo 'Cleanup: log file: ' "${log_file}"
+ {
+ echo "Cleaning up work files older that ${cleanupMinutes} minutes, dir: ${workarea}"
+ [[ -d "${workarea}/HiPS" ]] && ${find_cmd} "${workarea}/HiPS" -type f -mtime +90 -exec $rm_cmd '{}' \+ -print
+ [[ -d "${workarea}/stage" ]] && ${find_cmd} "${workarea}/stage" -type f -mtime +7 -exec $rm_cmd '{}' \+ -print
+ [[ -d "${workarea}/upload" ]] && ${find_cmd} "${workarea}/upload" -type f -mtime +7 -exec $rm_cmd '{}' \+ -print
+ [[ -d "${workarea}/perm_files" ]] && ${find_cmd} "${workarea}/perm_files" -type f -atime +1 -exec $rm_cmd '{}' \+ -print
+ for dir in "${clean_dirs[@]}"; do
+ if [ -d "${dir}" ]; then
+ ${find_cmd} "${dir}" -type f -amin +${cleanupMinutes} -exec $rm_cmd '{}' \+ -print
+ fi
+ done
+ for dir in "${dirs_to_clear[@]}"; do # remove empty directories excluding those at the starting level
+ [[ -d "${dir}" ]] && ${find_cmd} "${dir}" -mindepth 1 -depth -type d -empty -print -exec $rm_cmd '{}' \;
+ done
+ } > "${log_file}" 2>&1
+}
+
+
+# Remove temporary products for each Firefly workarea
+# Find app directories (should be only one, but loop for safety)
+doCleanup "${workarea_dir}"
+
diff --git a/src/firefly_standalone/assets/startFirefly b/src/firefly_standalone/assets/startFirefly
new file mode 100644
index 000000000..d2dddb892
--- /dev/null
+++ b/src/firefly_standalone/assets/startFirefly
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+
+fireflyDir="${HOME}/.firefly"
+INSTALL_DIR=$(cat "$fireflyDir/applicationPath.txt")
+applicationDir="${INSTALL_DIR}/application/current"
+"$applicationDir/startFireflyServer.sh" "$@"
diff --git a/src/firefly_standalone/assets/startFireflyServer.sh b/src/firefly_standalone/assets/startFireflyServer.sh
new file mode 100755
index 000000000..a6e884af2
--- /dev/null
+++ b/src/firefly_standalone/assets/startFireflyServer.sh
@@ -0,0 +1,141 @@
+#!/bin/bash
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+INSTALL_DIR=$(cd "${SCRIPT_DIR}/../.." && pwd)
+fireflyDir="${HOME}/.firefly"
+applicationDir="${INSTALL_DIR}/application/current"
+appNew="${INSTALL_DIR}/application/new"
+applicationJars="${applicationDir}/jars"
+appLog="${fireflyDir}/logs/application.log"
+userOpsFile="${fireflyDir}/user_ops.sh"
+ADMIN_USER="admin"
+ADMIN_PASSWORD="admin"
+MIN_JVM_SIZE=1G
+MAX_JVM_SIZE=10G
+
+# todo - i think we can remove serverConfigDir
+serverConfigDir="${HOME}/config"
+
+debugParams=
+loggingLevel="INFO"
+doClean="FALSE"
+verbose="FALSE"
+doExit="FALSE"
+
+for arg in "$@"; do
+ if [[ "$arg" == "-debug" || "$arg" == "-d" ]]; then
+ debugParams="-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005"
+ fi
+ if [[ "$arg" == "-logDebug" || "$arg" == "-ld" ]]; then
+ loggingLevel="DEBUG"
+ fi
+ if [ "$arg" == "-verbose" ]; then
+ verbose="TRUE"
+ fi
+ if [ "$arg" == "-clean" ]; then
+ doClean="TRUE"
+ fi
+ if [ "$arg" == "-cleanAndExit" ]; then
+ doClean="TRUE"
+ doExit="TRUE"
+ fi
+done
+
+
+
+if [ "$doClean" == "TRUE" ]; then
+ /bin/rm -rf "${fireflyDir}/workarea"
+ echo "removing: ${fireflyDir}/workarea"
+ /bin/rm -rf "${fireflyDir}/temp"
+ echo "removing: ${fireflyDir}/temp"
+ /bin/rm -rf "${fireflyDir}/logs"
+ echo "removing: ${fireflyDir}/logs"
+ /bin/rm -rf "${fireflyDir}/webapps"
+ echo "removing: ${fireflyDir}/webapps"
+ /bin/rm -rf "${fireflyDir}/work"
+ echo "removing: ${fireflyDir}/work"
+ if [ "$doExit" == "TRUE" ]; then
+ exit 0;
+ fi
+ fi
+
+if [[ -d "$appNew" && -f "$appNew/complete" ]]; then
+ if [[ -f "$INSTALL_DIR/disableUpdate" ]]; then
+ echo ">>>>>>>>> Update available but disabled"
+ else
+ echo ">>>>>>>>> updating..."
+ exec "$appNew/../updater.sh"
+ fi
+fi
+
+if [ ! -f "$applicationDir/jars/firefly.jar" ]; then
+ FILES_FROM_WAR="WEB-INF/lib/firefly.jar WEB-INF/lib/json-simple-1.1.1.jar WEB-INF/config/version.tag"
+ (cd $applicationDir && unzip -oj firefly.war ${FILES_FROM_WAR} )
+fi
+
+
+
+[ ! -d "${applicationJars}" ] && mkdir "$applicationJars"
+[ ! -d "${fireflyDir}/temp" ] && mkdir "${fireflyDir}/temp"
+[ ! -d "${fireflyDir}/logs" ] && mkdir "${fireflyDir}/logs"
+if [ "$verbose" == "TRUE" ]; then
+ echo "$applicationDir/*.jar" to "$applicationJars"
+fi
+if ls $applicationDir/*.jar >/dev/null 2>&1; then
+ /bin/mv $applicationDir/*.jar "$applicationJars"
+fi
+
+
+
+JAVA_OPS=
+if [ -f "$userOpsFile" ]; then
+ source $userOpsFile
+ if [ "$verbose" == "TRUE" ]; then
+ echo JAVA_OPS = $JAVA_OPS
+ fi
+fi
+
+PROPS=" \
+ -Xms${MIN_JVM_SIZE} -Xmx${MAX_JVM_SIZE} \
+ --add-opens java.base/java.util=ALL-UNNAMED \
+ -XX:+UnlockExperimentalVMOptions \
+ -XX:TrimNativeHeapInterval=30000 \
+ -XX:+UseZGC \
+ -Dnet.sf.ehcache.enableShutdownHook=true \
+ -Dlogging.level=${loggingLevel} \
+ -Djava.net.preferIPv4Stack=true \
+ -Dwork.directory=${fireflyDir}/workarea \
+ -DrunAsDesktopApplication=true \
+ -Dvisualize.fits.search.path=${HOME} \
+ -Dredis.db.dir=${fireflyDir}/temp/redis \
+ -Djava.io.tmpdir=${fireflyDir}/temp \
+ -Dalerts.dir=${fireflyDir}/alerts \
+ -Dserver_config_dir=${serverConfigDir} \
+ -DADMIN_USER=${ADMIN_USER} \
+ -DADMIN_PASSWORD=${ADMIN_PASSWORD} \
+ -DADMIN_PROTECTED= \
+ ${JAVA_OPS}
+ "
+
+splash="-splash:${applicationDir}/fireflySplash.png"
+dockIcon="-Xdock:icon=${applicationDir}/fireflyDockIcon.png"
+nameParam='-Xdock:name=Firefly Server'
+
+JAVA=$("$applicationDir"/javaInstaller.sh)
+
+export CLASSPATH=${applicationJars}'/*'
+if [ "$verbose" == "TRUE" ]; then
+ echo
+ echo Using classpath
+ echo $CLASSPATH
+ echo
+fi
+
+{
+echo "------------------------------------------------"
+echo "---------- Starting Firefly server"
+echo "---------- $(date)"
+echo "------------------------------------------------"
+echo ${JAVA} ${debugParams} ${splash} ${dockIcon} "${nameParam}" ${PROPS} edu.caltech.ipac.app.FireflyApplication >> "$appLog"
+} >> "$appLog"
+(cd "$applicationDir" && ${JAVA} ${debugParams} ${splash} ${dockIcon} "${nameParam}" ${PROPS} edu.caltech.ipac.app.FireflyApplication)
diff --git a/src/firefly_standalone/assets/updater.sh b/src/firefly_standalone/assets/updater.sh
new file mode 100644
index 000000000..4bf9539dc
--- /dev/null
+++ b/src/firefly_standalone/assets/updater.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+INSTALL_DIR=$(cd "${SCRIPT_DIR}/.." && pwd)
+applicationRoot="${INSTALL_DIR}/application"
+appNew="${applicationRoot}/new"
+appCurrent="${applicationRoot}/current"
+appOld="${applicationRoot}/old"
+
+
+if [[ -d "$appNew" && -d "$appCurrent" && -f "$appNew/complete" ]]; then
+ /bin/rm -rf "$appOld"
+ /bin/mv "$appCurrent" "$appOld"
+ /bin/mv "$appNew" "$appCurrent"
+fi
+
+exec "$appCurrent/startFireflyServer.sh"
+
diff --git a/src/firefly_standalone/build.gradle b/src/firefly_standalone/build.gradle
new file mode 100644
index 000000000..fe32d250d
--- /dev/null
+++ b/src/firefly_standalone/build.gradle
@@ -0,0 +1,59 @@
+group 'edu.caltech.ipac'
+
+ext["app-name"] = 'firefly_standalone'
+
+configurations {
+ bundled
+}
+
+sourceSets {
+ main.java.srcDir '.'
+ main.resources {
+ srcDirs "."
+ exclude "**/*.gradle"
+ }
+}
+
+dependencies {
+ implementation ':firefly'
+ bundled 'org.apache.tomcat.embed:tomcat-embed-core:11.0.21'
+ bundled 'org.apache.tomcat.embed:tomcat-embed-websocket:11.0.21'
+ bundled 'org.apache.tomcat.embed:tomcat-embed-jasper:11.0.21'
+}
+
+// sourceSets {
+// main.java {
+// srcDir '**/firefly_standalone/**'
+// }
+// }
+
+jar {
+ archiveFileName = 'firefly_standalone.jar'
+ include "**/*"
+ from sourceSets.main.allJava
+}
+
+
+
+def stageTarget= file("${buildDir}/zipFiles")
+def fireflyWar = project(':firefly').tasks.named('war')
+
+
+task stageFiles(type: Copy) {
+// dependsOn jar, fireflyWar
+ dependsOn jar
+
+ into stageTarget
+
+ from { fireflyWar.get().archivePath }
+ from { jar.archivePath }
+ from configurations.bundled
+
+ from("${rootDir}/src/firefly_standalone/assets")
+ }
+
+task zip(type: Jar, dependsOn: stageFiles) {
+ archiveFileName = "standalone.zip"
+ from "${buildDir}/zipFiles"
+ destinationDirectory = file ("$rootDir/build/dist")
+}
diff --git a/src/firefly_standalone/java/edu/caltech/ipac/app/FireflyApplication.java b/src/firefly_standalone/java/edu/caltech/ipac/app/FireflyApplication.java
new file mode 100644
index 000000000..659f2e1ae
--- /dev/null
+++ b/src/firefly_standalone/java/edu/caltech/ipac/app/FireflyApplication.java
@@ -0,0 +1,438 @@
+package edu.caltech.ipac.app;
+
+import edu.caltech.ipac.firefly.server.util.VersionUtil;
+import edu.caltech.ipac.util.FileUtil;
+import edu.caltech.ipac.util.StringUtils;
+import edu.caltech.ipac.util.download.FailedRequestException;
+import edu.caltech.ipac.util.download.URLDownload;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.startup.Tomcat;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+
+import javax.swing.*;
+import java.awt.AWTException;
+import java.awt.Color;
+import java.awt.Desktop;
+import java.awt.Image;
+import java.awt.MenuItem;
+import java.awt.PopupMenu;
+import java.awt.SplashScreen;
+import java.awt.SystemTray;
+import java.awt.Toolkit;
+import java.awt.TrayIcon;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.net.MalformedURLException;
+import java.net.ServerSocket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Properties;
+import java.util.logging.FileHandler;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;
+
+
+public class FireflyApplication {
+
+ private static final File pwd = new File(System.getProperty("user.dir"));
+ private static final File ffDir = new File(System.getProperty("user.home"), ".firefly");
+ private static final File installDir = new File(pwd, "../..").getAbsoluteFile();
+ private static final File tomcatDir = ffDir;
+ private static final File tomcatTmp = new File(tomcatDir, "temp");
+ private static final File tomcatLogs = new File(tomcatDir, "logs");
+ private static final File applicationRoot = new File(installDir, "application");
+ private static final File applicationDir = new File(applicationRoot, "current");
+ private static final File cleanupScript= new File(applicationDir,"standalone_cleanup.sh");
+ private static final File installScript= new File(applicationDir,"install.sh");
+ private static final File portFile = new File(ffDir, "port.txt");
+ private static final File dockIconFile= new File(applicationDir,"fireflyDockIcon.png");
+ private static final File applicationLogFile= new File(tomcatLogs, "application.log");
+ private static final File fireflyWarDir= new File(applicationDir, "firefly-war");
+ private static final File versionTagPropFile= new File(applicationDir, "version.tag");
+ private static final File versionTextOutFile= new File(ffDir, "version.txt");
+ private static final String compressibleMimeType= String.join(",", Arrays.asList(
+ "text/html", "text/plain", "text/css", "text/javascript",
+ "application/javascript", "application/json", "application/xml",
+ "text/xml", "application/x-votable+xml", "application/x-yaml", "application/ld+json",
+ "image/svg+xml", "text/csv", "application/xhtml+xml",
+ "application/rss+xml", "application/atom+xml", "application/x-font-ttf",
+ "font/otf", "font/woff", "font/woff2",
+ "application/octet-stream"
+ ));
+ private static final boolean useLogFile= true;
+ private static final int DEFAULT_PORT= 8888;
+ private static String fireflyVersion;
+ private static String javaVersion;
+ private static boolean updateAvailable= false;
+ private static PrintStream terminalOut= null;
+ private static boolean initComplete= false;
+
+
+
+ public static void start() throws LifecycleException, URISyntaxException, IOException, InterruptedException {
+ String terminalDevice = System.getProperty("os.name").toLowerCase().contains("win")
+ ? "CON" : "/dev/tty";
+ fireflyVersion= saveVersion();
+ javaVersion = System.getProperty("java.version");
+ terminalOut = new PrintStream(new FileOutputStream(terminalDevice));
+ ensureFireflyDir();
+ var port= getPort();
+
+
+ if (useLogFile) setupLogger();
+
+ Tomcat tomcat = new Tomcat();
+ tomcat.setBaseDir(tomcatDir.getAbsolutePath());
+ tomcat.setPort(port);
+
+
+ boolean tomcatStarted = false;
+ if (!isRunning(port)) {
+ setupUI(tomcat,port);
+ tomcat.addUser("admin", "admin");
+ tomcat.addWebapp("/firefly", fireflyWarDir.getAbsolutePath());
+ terminalOut.println("Firefly server starting (is takes a few seconds)...");
+ Connector connector= tomcat.getConnector();
+ connector.setPort(port);
+ connector.setProperty("compression", "on");
+ connector.setProperty("useSendfile", "false");
+ connector.setProperty("compressibleMimeType", compressibleMimeType);
+ tomcat.start();
+ tomcatStarted = true;
+ }
+
+ if (!tomcatStarted) {
+ terminalOut.println("Firefly is already running");
+ openBrowser(port);
+ fireflyReadyMessage(port);
+ System.exit(0);
+ }
+
+
+ hideSplash();
+ initComplete= true;
+ openBrowser(port);
+ fireflyReadyMessage(port);
+ Thread.sleep(5 * 1000); // 5 seconds
+ updateAvailable= doAutoUpdateCheck();
+ doWorkAreaCleanup();
+ while (tomcat.getServer().getState().isAvailable()) {
+ Thread.sleep(3600 * 1000); // 1 hour
+ if (!updateAvailable) updateAvailable= doAutoUpdateCheck();
+ doWorkAreaCleanup();
+ }
+
+ }
+
+ public static boolean doAutoUpdateCheck() {
+ boolean updateAvailable= false;
+ try {
+ var result= URLDownload.getDataFromURL(new URI("https://api.github.com/repos/Caltech-IPAC/firefly/releases/latest").toURL(),null,null);
+ var obj= (JSONObject) new JSONParser().parse(result.getResultAsString());
+ var availableVersion= (String) obj.get("name");
+ var newVerAvailable= isNewVersionAvailable(fireflyVersion,availableVersion);
+
+ String urlStr= null;
+ var assetsAry= (JSONArray)obj.get("assets");
+ if (newVerAvailable && assetsAry!=null && !assetsAry.isEmpty()) {
+ for(Object entry: assetsAry){
+ JSONObject asset= (JSONObject)entry;
+ if (StringUtils.areEqual((String)asset.get("name"),"standalone.zip")) {
+ urlStr= (String)asset.get("url");
+ }
+ }
+ }
+ String overrideUrlStr= null;
+// overrideUrlStr= "/Users/roby/dev/firefly/build/dist/standalone.zip"; // todo remove after testing
+ if (overrideUrlStr!=null) urlStr= overrideUrlStr;
+ updateAvailable= urlStr!=null;
+ if (updateAvailable) doUpdateInstall(urlStr);
+
+ String updateMsg= updateAvailable ? ", Update available (relaunch Firefly to finish update)" : "";
+
+ terminalOut.println("**** Update Check: Current Version: "+fireflyVersion+
+ ", Available version: "+ availableVersion +
+ ", Java Version: "+ javaVersion +
+ updateMsg);
+
+ } catch (FailedRequestException | MalformedURLException | URISyntaxException | ParseException e) {
+ System.out.println(e.toString());
+ }
+ return updateAvailable;
+ }
+
+ public static boolean isNewVersionAvailable(String currVer, String availableVer) {
+ if (currVer==null) currVer= "0,0.0";
+ if (availableVer==null) availableVer= "0,0.0";
+ var cVer= currVer.split("\\.");
+ var nVer= availableVer.split("\\.");
+ if (cVer.length!=3 || nVer.length!=3) return false;
+ var curr= Arrays.stream(cVer).map((s) -> StringUtils.getInt(s,0)).toList();
+ var next= Arrays.stream(nVer).map((s) -> StringUtils.getInt(s,0)).toList();
+ return (next.get(0)>curr.get(0) || next.get(1)>curr.get(1) || next.get(2)>curr.get(2));
+ }
+
+ public static String saveVersion() {
+ try {
+ Properties props = new Properties();
+ props.load(new FileInputStream(versionTagPropFile));
+ VersionUtil.ingestVersion(props);
+ var vInfo= VersionUtil.getVersionInfo();
+ var fVerList= vInfo.stream().filter(kv -> kv.getKey().equals("Firefly Version")).toList();
+ if (fVerList.size()==1) {
+ var vStr= fVerList.getFirst().getValue();
+
+ String major="0";
+ String minor="0";
+ String rev="0";
+ var phase1= vStr.split("-");
+ var realVStr= phase1[0];
+ var parts= realVStr.split("\\.");
+ if (parts.length>1) {
+ major= parts[0];
+ minor= parts[1];
+ if (parts.length>2) rev= parts[2];
+ }
+ var version= major+"."+minor+"."+rev;
+ FileUtil.writeStringToFile(versionTextOutFile, version);
+ return version;
+ }
+ } catch (IOException e) {
+ System.out.println("failed to get version: " + e.toString());
+ }
+ return null;
+ }
+
+ public static void doUpdateInstall(String packageUrl) {
+ ProcessBuilder pb = new ProcessBuilder(installScript.getAbsolutePath(),
+ "-url", packageUrl, "-asUpdate",
+ "-installDir", installDir.getAbsolutePath() );
+ try {
+ Process process = pb.start();
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ System.out.println(line);
+ }
+ }
+ int exitCode = process.waitFor();
+ if (exitCode != 0) System.out.println("auto update job failed with code: " + exitCode);
+
+ } catch (IOException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void setupLogger() throws IOException {
+ Logger logger= Logger.getLogger("");
+ for (Handler h : logger.getHandlers()) {
+ logger.removeHandler(h);
+ }
+
+
+ //todo - determine if there is a downside to both writing to application.log
+
+ // setup logger
+ Handler fileHandler = new FileHandler(applicationLogFile.getAbsolutePath(), true);
+ fileHandler.setFormatter(new SimpleFormatter());
+ fileHandler.setLevel(Level.ALL);
+ logger.addHandler(fileHandler);
+
+ // set system out for stuff that logger misses
+ System.setOut(new PrintStream(new FileOutputStream(applicationLogFile,true)));
+ }
+
+ public static void doWorkAreaCleanup() {
+ ProcessBuilder pb = new ProcessBuilder(cleanupScript.getAbsolutePath()," --once");
+ try {
+ Process process = pb.start();
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ System.out.println(line);
+ }
+ }
+ int exitCode = process.waitFor();
+ if (exitCode != 0) System.out.println("clean up job failed with code: " + exitCode);
+
+ } catch (IOException | InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ public static void openBrowser(int port) throws InterruptedException {
+ if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
+ try {
+ Thread.sleep(500);
+ Desktop.getDesktop().browse(new URI(makeUrlString(port)));
+ } catch (URISyntaxException | InterruptedException | IOException ignore) {
+ System.out.println("Could not open browser");
+ }
+ }
+ }
+
+ public static String makeUrlString(int port) { return "http://localhost:"+port+"/firefly/";}
+
+ public static void ensureFireflyDir() {
+ confirmDirOrExit(ffDir);
+ confirmDirOrExit(tomcatDir);
+ confirmDirOrExit(tomcatTmp);
+ confirmDirOrExit(tomcatLogs);
+ }
+
+ public static int getPort() {
+ try {
+ if (!portFile.canRead()) return DEFAULT_PORT;
+ String pStr= FileUtil.readFile(portFile);
+ if (pStr==null) return DEFAULT_PORT;
+ return Integer.parseInt(pStr.split("\\s+")[0]);
+ } catch (IOException | NumberFormatException e) {
+ return DEFAULT_PORT;
+ }
+ }
+
+ private static void confirmDirOrExit(File dir) {
+ boolean exists = true;
+ if (!dir.exists()) {
+ exists = dir.mkdir();
+ }
+ if (!exists || !dir.canWrite()) {
+ System.out.println("Can't write to " + dir.getAbsolutePath() + " directory");
+ System.exit(0);
+ }
+ }
+
+ private static void setupUI(Tomcat tomcat, int port) {
+ System.setProperty("apple.awt.UIElement", "false");
+ System.setProperty("apple.laf.useScreenMenuBar", "true");
+ System.setProperty("com.apple.mrj.application.apple.menu.about.name", "Firefly");
+ setupTray(tomcat, port);
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ try {
+ if (tomcat.getServer().getState().isAvailable()) {
+ System.out.println("Shutting down Firefly...");
+ tomcat.stop();
+ tomcat.destroy();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ finally {
+ Runtime.getRuntime().halt(0);
+ }
+ }));
+ if (Desktop.isDesktopSupported()) {
+ Desktop desktop = Desktop.getDesktop();
+ if (desktop.isSupported(Desktop.Action.APP_ABOUT)) {
+ desktop.setAboutHandler(e -> showAboutDialog(port) );
+ }
+ }
+ }
+
+ public static void stopFireflyServer(Tomcat tomcat) {
+ try {
+ if (tomcat.getServer().getState().isAvailable()) {
+ System.out.println("Shutting down Firefly Server...");
+ tomcat.stop();
+ tomcat.destroy();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ finally {
+ Runtime.getRuntime().halt(0);
+ }
+ }
+
+ public static void showAboutDialog(int port) {
+ String outstr= String.format("Firefly Version: %s
Java Version: %s
",
+ fireflyVersion, javaVersion);
+ outstr+= String.format("To load Firefly: %s",makeUrlString(port), makeUrlString(port));
+ if (updateAvailable) outstr+= "
"+"Update available (relaunch Firefly to finish update)";
+ if (!initComplete)outstr+= "
"+"Server Initializing...";
+ JLabel label= new JLabel(outstr);
+ label.setForeground(Color.black);
+ label.setText(outstr);
+ label.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ try {
+ Desktop.getDesktop().browse(new URI(makeUrlString(port)));
+ } catch (Exception ignore) { }
+ }
+ });
+ JOptionPane.showMessageDialog(null, label, "Firefly", JOptionPane.INFORMATION_MESSAGE);
+
+ }
+
+ public static boolean isRunning(int port) {
+ try (ServerSocket serverSocket = new ServerSocket(port)) {
+ return false; // Port is available
+ } catch (IOException e) {
+ return true; // Port is in use
+ }
+ }
+
+ public static void hideSplash() {
+ SplashScreen splash = SplashScreen.getSplashScreen();
+ if (splash != null) splash.close();
+ }
+
+ public static void setupTray(Tomcat tomcat, int port) {
+ System.setProperty("apple.awt.enableTemplateImages", "false");
+ if (!SystemTray.isSupported()) {
+ System.out.println("SystemTray is not supported on this platform.");
+ return;
+ }
+ SystemTray tray = SystemTray.getSystemTray();
+ Image image = Toolkit.getDefaultToolkit().getImage(dockIconFile.getAbsolutePath());
+
+ // Create a popup menu for the icon
+ PopupMenu popup = new PopupMenu();
+ MenuItem exitItem = new MenuItem("Shutdown Firefly Server");
+ MenuItem aboutItem = new MenuItem("About Firefly");
+ popup.add(aboutItem);
+ popup.add(exitItem);
+ exitItem.addActionListener(e -> stopFireflyServer(tomcat));
+ aboutItem.addActionListener(e -> showAboutDialog(port) );
+ TrayIcon trayIcon = new TrayIcon(image, "Firefly Server", popup);
+ trayIcon.setImageAutoSize(true); // Automatically scale the image
+
+ try {
+ tray.add(trayIcon);
+ } catch (AWTException e) {
+ System.err.println("TrayIcon could not be added.");
+ }
+ }
+
+
+ public static void fireflyReadyMessage(int port) throws IOException {
+ terminalOut.println("\n---------------------------------");
+ terminalOut.println("Firefly ready: use URL: " + makeUrlString(port));
+ terminalOut.println("---------------------------------\n");
+ }
+
+ public static void main(String[] args) {
+ try {
+ FireflyApplication.start();
+ } catch (Exception e) {
+ System.out.println("Error starting Firefly Application: " + e.getMessage());
+ }
+ }
+}
+
diff --git a/standalone/install.sh b/standalone/install.sh
new file mode 100755
index 000000000..b4173a797
--- /dev/null
+++ b/standalone/install.sh
@@ -0,0 +1,107 @@
+#!/bin/bash
+
+INSTALL_DIR="$PWD/firefly"
+fireflyDir="${HOME}/.firefly"
+applicationPath="current"
+url=
+#TMP_PACKAGE_URL=file:///Users/roby/dev/firefly/build/dist/standalone.zip
+
+#todo change asset name to: standalone.jar
+
+startScript="startFirefly"
+altUrl=
+installJre="TRUE"
+initialInstall="TRUE"
+installType="installing"
+#if [ -n "$1" ]; then
+# altUrl=$1
+#fi
+
+echo "params: " $*
+
+while [ $# -gt 0 ]; do
+ if [[ "$1" == "-url" ]]; then
+ shift
+ altUrl=$1
+ fi
+ if [[ "$1" == "-installDir" ]]; then
+ shift
+ INSTALL_DIR=$1
+ fi
+ if [[ "$1" == "-asUpdate" ]]; then
+ installJre="FALSE"
+ initialInstall="FALSE"
+ applicationPath="new"
+ installType="updating"
+ fi
+ shift
+done
+
+echo "$installType in $INSTALL_DIR"
+
+PACKAGE_ASSET_NAME="standalone.zip"
+#todo remove next line before PR merge
+PACKAGE_ASSET_NAME="test_standalone.zip"
+applicationRoot="${INSTALL_DIR}/application"
+applicationDir="${applicationRoot}/${applicationPath}"
+
+
+targetPackageFile="${applicationDir}/standalone.zip"
+
+packageUrl=$(curl -s "https://api.github.com/repos/Caltech-IPAC/firefly/releases/latest" | \
+jq -r '.assets[] | [.name, .browser_download_url] | @tsv' | \
+while IFS=$'\t' read -r asset_name download_url; do
+ if [ "$asset_name" == $PACKAGE_ASSET_NAME ]; then
+ echo "$download_url"
+ fi
+done)
+if [ -z "$altUrl" ]; then
+ url=$packageUrl
+else
+ url=$altUrl
+fi
+
+if [ -z "$url" ]; then
+ echo "No package defined to download, could not find it as a github asset https://github.com/Caltech-IPAC/firefly/releases"
+ exit 0
+fi
+
+mkdir "$fireflyDir"
+mkdir -p "$applicationDir"
+echo "$INSTALL_DIR" > "$fireflyDir/applicationPath.txt"
+rm -f "$applicationDir"/complete
+echo "install from: $url"
+if [[ "$url" == http* ]]; then
+ curl -sL "$url" > "${targetPackageFile}"
+else
+ cp "$url" "${targetPackageFile}"
+fi
+(cd "$applicationDir" && unzip -o "${targetPackageFile}")
+mkdir "$applicationDir/firefly-war"
+(cd "$applicationDir/firefly-war" && unzip -o "${applicationDir}/firefly.war")
+
+scriptPath=$(realpath "$0")
+cp "$scriptPath" "$applicationDir/install.sh"
+chmod 775 "$applicationDir/standalone_cleanup.sh" \
+ "$applicationDir/$startScript" \
+ "$applicationDir/startFireflyServer.sh" \
+ "$applicationDir/javaInstaller.sh" \
+ "$applicationDir/jreKey.sh" \
+ "$applicationDir/updater.sh" \
+ "$applicationDir/install.sh"
+/bin/mv "$applicationDir/updater.sh" "$applicationRoot"
+
+if [ "$initialInstall" == "TRUE" ]; then
+ cp "$applicationDir/$startScript" .
+ chmod +x "./$startScript"
+ echo "8888" > "$fireflyDir/port.txt"
+fi
+
+if [ ! -f "$fireflyDir/user_ops.sh" ]; then
+ echo "JAVA_OPS=" > "$fireflyDir/user_ops.sh"
+fi
+
+if [ "$installJre" == "TRUE" ]; then
+ "$applicationDir"/javaInstaller.sh
+fi
+touch "$applicationDir"/complete
\ No newline at end of file
diff --git a/standalone/remoteInstall.sh b/standalone/remoteInstall.sh
new file mode 100644
index 000000000..210ea7896
--- /dev/null
+++ b/standalone/remoteInstall.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+INSTALL_SCRIPT="https://raw.githubusercontent.com/Caltech-IPAC/firefly/refs/heads/dev/standalone/install.sh"
+#todo remove next line before PR merge
+INSTALL_SCRIPT="https://raw.githubusercontent.com/Caltech-IPAC/firefly/refs/heads/FIREFLY-1980-standalone/standalone/install.sh"
+
+curl -s ${INSTALL_SCRIPT} > ./install.sh
+chmod +x ./install.sh
+./install.sh "$@"
+/bin/rm -f ./install.sh
\ No newline at end of file