From 5a6913f9266716eb5331444088b791659ab1c736 Mon Sep 17 00:00:00 2001 From: roby Date: Tue, 19 May 2026 10:40:32 -0600 Subject: [PATCH] Firefly-1980: firefly standalone installation --- buildScript/dependencies.gradle | 5 + config/web.xml | 23 - settings.gradle | 3 +- .../ipac/firefly/server/util/VersionUtil.java | 41 +- .../firefly/server/visualize/VisContext.java | 4 +- .../edu/caltech/ipac/util/StringUtils.java | 3 - .../ipac/util/download/URLDownload.java | 72 ++- .../visualize/plot/plotdata/FitsRead.java | 1 + .../assets/fireflyDockIcon.png | Bin 0 -> 7908 bytes .../assets/fireflySplash.png | Bin 0 -> 19748 bytes .../assets/javaInstaller.sh | 50 ++ src/firefly_standalone/assets/jreVersion.json | 23 + .../assets/package-files.txt | 6 + .../assets/standalone_cleanup.sh | 46 ++ src/firefly_standalone/assets/startFirefly | 7 + .../assets/startFireflyServer.sh | 141 ++++++ src/firefly_standalone/assets/updater.sh | 19 + src/firefly_standalone/build.gradle | 59 +++ .../caltech/ipac/app/FireflyApplication.java | 438 ++++++++++++++++++ standalone/install.sh | 107 +++++ standalone/remoteInstall.sh | 9 + 21 files changed, 985 insertions(+), 72 deletions(-) create mode 100644 src/firefly_standalone/assets/fireflyDockIcon.png create mode 100644 src/firefly_standalone/assets/fireflySplash.png create mode 100755 src/firefly_standalone/assets/javaInstaller.sh create mode 100644 src/firefly_standalone/assets/jreVersion.json create mode 100644 src/firefly_standalone/assets/package-files.txt create mode 100644 src/firefly_standalone/assets/standalone_cleanup.sh create mode 100644 src/firefly_standalone/assets/startFirefly create mode 100755 src/firefly_standalone/assets/startFireflyServer.sh create mode 100644 src/firefly_standalone/assets/updater.sh create mode 100644 src/firefly_standalone/build.gradle create mode 100644 src/firefly_standalone/java/edu/caltech/ipac/app/FireflyApplication.java create mode 100755 standalone/install.sh create mode 100644 standalone/remoteInstall.sh diff --git a/buildScript/dependencies.gradle b/buildScript/dependencies.gradle index 63b88a6061..700b94a1b0 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 0bce306268..fec09b1874 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 ccc464db07..d0393cb12b 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 a340ecb394..718deb2eb4 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 7f2bdc7ee8..a63cbd0ede 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 4584c83640..bdfd01e4ac 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 123e6462ae..e21bfc0403 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 4904666054..b39cfb8bad 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 0000000000000000000000000000000000000000..9df5ee29d946dcc97a5334a0c2b31955b63c268a GIT binary patch literal 7908 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

jn&`;^ygAZPnKF)yZy$^RliwPqMB+JuojPb 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 literal 0 HcmV?d00001 diff --git a/src/firefly_standalone/assets/javaInstaller.sh b/src/firefly_standalone/assets/javaInstaller.sh new file mode 100755 index 0000000000..bc5f5e0f28 --- /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 0000000000..e8c6676a2e --- /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 0000000000..5bc73bcdfc --- /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 0000000000..92f53b5bab --- /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 0000000000..d2dddb892e --- /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 0000000000..a6e884af21 --- /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 0000000000..4bf9539dc4 --- /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 0000000000..fe32d250d5 --- /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 0000000000..659f2e1ae6 --- /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 0000000000..b4173a7979 --- /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 0000000000..210ea78966 --- /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