From 6c0bfd7c103a18c0466a1bce1746cb7c4f4010a3 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 28 Feb 2024 15:52:59 -0800 Subject: [PATCH 01/12] Remove 'server' directory from embedded labkey --- .../embedded/src/org/labkey/embedded/EmbeddedExtractor.java | 6 +++--- server/embedded/src/org/labkey/embedded/LabKeyServer.java | 2 +- .../embedded/LabKeyTomcatServletWebServerFactory.java | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java index b1662bda78..52f2ac398b 100644 --- a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java +++ b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java @@ -76,11 +76,11 @@ private void extractExecutableJar(File jarFilePath, File destDirectory, boolean } } - public void extractExecutableJarFromDir(File currentDir, File destDir, boolean remotePipeline) throws ConfigException + public void extractExecutableJarFromDir(File currentDir, boolean remotePipeline) throws ConfigException { File[] files = currentDir.listFiles(file -> { String name = file.getName().toLowerCase(); - return name.endsWith(".jar") && !name.contains("labkeybootstrap"); + return name.endsWith(".jar") && name.contains("labkeyserver"); }); if (files == null) @@ -91,7 +91,7 @@ public void extractExecutableJarFromDir(File currentDir, File destDir, boolean r // only 1 jar should be there if (files.length == 1) { - extractExecutableJar(files[0], destDir, remotePipeline); + extractExecutableJar(files[0], currentDir, remotePipeline); } else { diff --git a/server/embedded/src/org/labkey/embedded/LabKeyServer.java b/server/embedded/src/org/labkey/embedded/LabKeyServer.java index e6b2574533..7b98307bb2 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyServer.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyServer.java @@ -40,7 +40,7 @@ public static void main(String[] args) if (args.length > 0 && args[0].equalsIgnoreCase("-extract")) { File currentDir = new File("").getAbsoluteFile(); - new EmbeddedExtractor().extractExecutableJarFromDir(currentDir, currentDir, true); + new EmbeddedExtractor().extractExecutableJarFromDir(currentDir, true); return; } diff --git a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java index 22058d4e64..340c651ae1 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java @@ -66,13 +66,12 @@ protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) if (!webAppLocationPresent) { final var currentPath = new File("").getAbsoluteFile(); - var destDirectory = new File(currentPath, "server"); - webAppLocation = new File(destDirectory, "labkeywebapp"); + webAppLocation = new File(currentPath, "labkeywebapp"); if (!webAppLocation.exists()) { EmbeddedExtractor extractor = new EmbeddedExtractor(); - extractor.extractExecutableJarFromDir(currentPath, destDirectory, false); + extractor.extractExecutableJarFromDir(currentPath, false); } } else From 88b7f50967ea148fa93de8cdd0b25db0dff09bfa Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Wed, 28 Feb 2024 16:47:11 -0800 Subject: [PATCH 02/12] Create separate application.properties for distributions --- gradle.properties | 2 +- server/configs/application.properties | 132 ++++----------------- webapps/application.properties | 163 ++++++++++++++++++++++++++ 3 files changed, 187 insertions(+), 110 deletions(-) create mode 100644 webapps/application.properties diff --git a/gradle.properties b/gradle.properties index 0c03336608..7016f8e85e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -62,7 +62,7 @@ windowsProteomicsBinariesVersion=1.0 # The current version numbers for the gradle plugins. artifactoryPluginVersion=4.31.9 gradleNodePluginVersion=3.5.1 -gradlePluginsVersion=2.3.0 +gradlePluginsVersion=2.5.0-embeddedUpgrade-SNAPSHOT owaspDependencyCheckPluginVersion=8.4.3 versioningPluginVersion=1.1.2 diff --git a/server/configs/application.properties b/server/configs/application.properties index b7495f4ae1..5e248d09e3 100644 --- a/server/configs/application.properties +++ b/server/configs/application.properties @@ -1,3 +1,7 @@ +## These properties are used for development and test deployments. +## Many properties here will be filled in and uncommented by the Gradle 'pickPg' and 'pickMssql' tasks +## See '/webapps/application.properties' for more examples + server.port=@@serverPort@@ ## To use ssl, update the properties below for your local installation @@ -8,16 +12,16 @@ server.port=@@serverPort@@ #server.ssl.key-alias=tomcat #server.ssl.key-store=@@keyStore@@ #server.ssl.key-store-password=@@keyStorePassword@@ -# Typically either PKCS12 or JKS +## Typically either PKCS12 or JKS #server.ssl.key-store-type=PKCS12 #server.ssl.ciphers=HIGH:!ADH:!EXP:!SSLv2:!SSLv3:!MEDIUM:!LOW:!NULL:!aNULL -# HTTP-only port for servers that need to handle both HTTPS (configure via server.port and server.ssl above) and HTTP +## HTTP-only port for servers that need to handle both HTTPS (configure via server.port and server.ssl above) and HTTP #context.httpPort=8080 -# Database connections. All deployments need a labkeyDataSource as their primary database. Add additional external -# data sources by specifying the required properties (at least driverClassName, url, username, and password) -# with a prefix of context.resources.jdbc.. +## Database connections. All deployments need a labkeyDataSource as their primary database. Add additional external +## data sources by specifying the required properties (at least driverClassName, url, username, and password) +## with a prefix of context.resources.jdbc.. context.resources.jdbc.labkeyDataSource.type=javax.sql.DataSource context.resources.jdbc.labkeyDataSource.driverClassName=@@jdbcDriverClassName@@ context.resources.jdbc.labkeyDataSource.url=@@jdbcURL@@ @@ -39,19 +43,6 @@ context.resources.jdbc.labkeyDataSource.validationQuery=SELECT 1 #useLocalBuild#context.webAppLocation=@@pathToServer@@/build/deploy/labkeyWebapp context.encryptionKey=@@encryptionKey@@ -# By default, we deploy to the root context path. However, some servers have historically used /labkey or even /cpas -#context.contextPath=/labkey - -# Using a legacy context path provides backwards compatibility with old deployments. A typical use case would be to -# deploy to the root context (the default) and configure /labkey as the legacy path. GETs will be redirected. -# All other methods (POSTs, PUTs, etc) will be handled server-side via a servlet forward. -#context.legacyContextPath=/labkey - -# Other webapps to be deployed, most commonly to deliver a set of static files. The context path to deploy into is the -# property name after the "context.additionalWebapps." prefix, and the value is the location of the webapp on disk -#context.additionalWebapps.firstContextPath=/my/webapp/path -#context.additionalWebapps.secondContextPath=/my/other/webapp/path - #context.oldEncryptionKey= #context.requiredModules= #context.pipelineConfig=/path/to/pipeline/config/dir @@ -62,107 +53,30 @@ context.encryptionKey=@@encryptionKey@@ mail.smtpHost=@@smtpHost@@ mail.smtpPort=@@smtpPort@@ mail.smtpUser=@@smtpUser@@ -# mail.smtpFrom=@@smtpFrom@@ -# mail.smtpPassword=@@smtpPassword@@ -# mail.startTlsEnable=@@smtpStartTlsEnable@@ -# mail.smtpSocketFactoryClass=@@smtpSocketFactoryClass@@ -# mail.smtpAuth=@@smtpAuth@@ - -# Optional - JMS configuration for remote ActiveMQ message management for distributed pipeline jobs -# https://www.labkey.org/Documentation/wiki-page.view?name=jmsQueue -#context.resources.jms.ConnectionFactory.type=org.apache.activemq.ActiveMQConnectionFactory -#context.resources.jms.ConnectionFactory.factory=org.apache.activemq.jndi.JNDIReferenceFactory -#context.resources.jms.ConnectionFactory.description=JMS Connection Factory -# Use an in-process ActiveMQ queue -#context.resources.jms.ConnectionFactory.brokerURL=vm://localhost?broker.persistent=false&broker.useJmx=false -# Use an out-of-process ActiveMQ queue -#context.resources.jms.ConnectionFactory.brokerURL=tcp://localhost:61616 -#context.resources.jms.ConnectionFactory.brokerName=LocalActiveMQBroker - -# Optional - LDAP configuration for LDAP group/user synchronization -# https://www.labkey.org/Documentation/wiki-page.view?name=LDAP_sync -#context.resources.ldap.ConfigFactory.type=org.labkey.premium.ldap.LdapConnectionConfigFactory -#context.resources.ldap.ConfigFactory.factory=org.labkey.premium.ldap.LdapConnectionConfigFactory -#context.resources.ldap.ConfigFactory.host=myldap.mydomain.com -#context.resources.ldap.ConfigFactory.port=389 -#context.resources.ldap.ConfigFactory.principal=cn=read_user -#context.resources.ldap.ConfigFactory.credentials=read_user_password -#context.resources.ldap.ConfigFactory.useTls=false -#context.resources.ldap.ConfigFactory.useSsl=false -#context.resources.ldap.ConfigFactory.sslProtocol=SSLv3 +#mail.smtpFrom=@@smtpFrom@@ +#mail.smtpPassword=@@smtpPassword@@ +#mail.startTlsEnable=@@smtpStartTlsEnable@@ +#mail.smtpSocketFactoryClass=@@smtpSocketFactoryClass@@ +#mail.smtpAuth=@@smtpAuth@@ #useLocalBuild#spring.devtools.restart.additional-paths=@@pathToServer@@/build/deploy/modules,@@pathToServer@@/build/deploy/embedded/config # HTTP session timeout for users - defaults to 30 minutes #server.servlet.session.timeout=30m - -#Enable shutdown endpoint +## Enable shutdown endpoint management.endpoint.shutdown.enabled=true -# turn off other endpoints +## turn off other endpoints management.endpoints.enabled-by-default=false -# allow access via http +## allow access via http management.endpoints.web.exposure.include=* -# Use a separate port for management endpoints. Required if LabKey is using default (ROOT) context path +## Use a separate port for management endpoints. Required if LabKey is using default (ROOT) context path management.server.port=@@shutdownPort@@ -# Don't show the Spring banner on startup +## Don't show the Spring banner on startup spring.main.banner-mode=off -#logging.config=path/to/alternative/log4j2.xml - -# Optional - JMS configuration for remote ActiveMQ message management for distributed pipeline jobs -# https://www.labkey.org/Documentation/wiki-page.view?name=jmsQueue -#context.resources.jms.name=jms/ConnectionFactory -#context.resources.jms.type=org.apache.activemq.ActiveMQConnectionFactory -#context.resources.jms.factory=org.apache.activemq.jndi.JNDIReferenceFactory -#context.resources.jms.description=JMS Connection Factory -#context.resources.jms.brokerURL=vm://localhost?broker.persistent=false&broker.useJmx=false -#context.resources.jms.brokerName=LocalActiveMQBroker - -# Turn on JSON-formatted HTTP access logging to stdout. See issue 48565 -# https://tomcat.apache.org/tomcat-9.0-doc/config/valve.html#JSON_Access_Log_Valve -#jsonaccesslog.enabled=true - -# Optional configuration, modeled on the non-JSON Spring Boot properties -# https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.server.server.tomcat.accesslog.buffered -#jsonaccesslog.pattern=%h %t %m %U %s %b %D %S "%{Referer}i" "%{User-Agent}i" %{LABKEY.username}s -#jsonaccesslog.condition-if=attributeName -#jsonaccesslog.condition-unless=attributeName - -# Define one or both of 'csp.report' and 'csp.enforce' to enable Content Security Policy (CSP) headers -# Do not copy-and-paste these examples for any production environment without understanding the meaning of each directive! - -# example usage 1 - very strict, disallows 'external' websites, disallows unsafe-inline, but only reports violations (does not enforce) - -#csp.report=\ -# default-src 'self';\ -# connect-src 'self' ${LABKEY.ALLOWED.CONNECTIONS} ;\ -# object-src 'none' ;\ -# style-src 'self' 'unsafe-inline' ;\ -# img-src 'self' data: ;\ -# font-src 'self' data: ;\ -# script-src 'unsafe-eval' 'strict-dynamic' 'nonce-${REQUEST.SCRIPT.NONCE}';\ -# base-uri 'self' ;\ -# upgrade-insecure-requests ;\ -# frame-ancestors 'self' ;\ -# report-uri https://www.labkey.org/admin-contentsecuritypolicyreport.api?${CSP.REPORT.PARAMS} ; - -# example usage 2 - less strict but enforces directives, (NOTE: unsafe-inline is still required for many modules) - -#csp.enforce=\ -# default-src 'self' https: ;\ -# connect-src 'self' https: ${LABKEY.ALLOWED.CONNECTIONS};\ -# object-src 'none' ;\ -# style-src 'self' https: 'unsafe-inline' ;\ -# img-src 'self' data: ;\ -# font-src 'self' data: ;\ -# script-src 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' 'nonce-${REQUEST.SCRIPT.NONCE}';\ -# base-uri 'self' ;\ -# upgrade-insecure-requests ;\ -# frame-ancestors 'self' ;\ -# report-uri https://www.labkey.org/admin-contentsecuritypolicyreport.api?${CSP.REPORT.PARAMS} ; - -# Default CSP for TeamCity and dev deployments + +## Default CSP for TeamCity and dev deployments csp.report=\ default-src 'self' https: http: ;\ connect-src 'self' localhost:* ws: ${LABKEY.ALLOWED.CONNECTIONS} ;\ @@ -175,10 +89,10 @@ csp.report=\ frame-ancestors 'self' ;\ report-uri /admin-contentsecuritypolicyreport.api?${CSP.REPORT.PARAMS} ; -# Use a non-temp directory for tomcat +## Use a non-temp directory for tomcat server.tomcat.basedir=. -# Enable tomcat access log +## Enable tomcat access log server.tomcat.accesslog.enabled=true server.tomcat.accesslog.directory=logs server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %S %I "%{Referrer}i" "%{User-Agent}i" %{LABKEY.username}s diff --git a/webapps/application.properties b/webapps/application.properties new file mode 100644 index 0000000000..da0edaf9ea --- /dev/null +++ b/webapps/application.properties @@ -0,0 +1,163 @@ +server.port=8080 + +## To use ssl, update the properties below for your local installation + +#server.ssl.enabled=true +#server.ssl.enabled-protocols=TLSv1.3,TLSv1.2,TLSv1.1 +#server.ssl.protocol=TLS +#server.ssl.key-alias=tomcat +#server.ssl.key-store=@@keyStore@@ +#server.ssl.key-store-password=@@keyStorePassword@@ +## Typically either PKCS12 or JKS +#server.ssl.key-store-type=PKCS12 +#server.ssl.ciphers=HIGH:!ADH:!EXP:!SSLv2:!SSLv3:!MEDIUM:!LOW:!NULL:!aNULL + +## HTTP-only port for servers that need to handle both HTTPS (configure via server.port and server.ssl above) and HTTP +#context.httpPort=8080 + +## Database connections. All deployments need a labkeyDataSource as their primary database. Add additional external +## data sources by specifying the required properties (at least driverClassName, url, username, and password) +## with a prefix of context.resources.jdbc.. +context.resources.jdbc.labkeyDataSource.type=javax.sql.DataSource +context.resources.jdbc.labkeyDataSource.driverClassName=@@jdbcDriverClassName@@ +context.resources.jdbc.labkeyDataSource.url=@@jdbcURL@@ +context.resources.jdbc.labkeyDataSource.username=@@jdbcUser@@ +context.resources.jdbc.labkeyDataSource.password=@@jdbcPassword@@ +context.resources.jdbc.labkeyDataSource.maxTotal=50 +context.resources.jdbc.labkeyDataSource.maxIdle=10 +context.resources.jdbc.labkeyDataSource.maxWaitMillis=120000 +context.resources.jdbc.labkeyDataSource.accessToUnderlyingConnectionAllowed=true +context.resources.jdbc.labkeyDataSource.validationQuery=SELECT 1 +#context.resources.jdbc.labkeyDataSource.logQueries=true +#context.resources.jdbc.labkeyDataSource.displayName=Alternate Display Name + +#context.resources.jdbc.@@extraJdbcDataSource@@.driverClassName=@@extraJdbcDriverClassName@@ +#context.resources.jdbc.@@extraJdbcDataSource@@.url=@@extraJdbcUrl@@ +#context.resources.jdbc.@@extraJdbcDataSource@@.username=@@extraJdbcUsername@@ +#context.resources.jdbc.@@extraJdbcDataSource@@.password=@@extraJdbcPassword@@ + +context.encryptionKey=@@encryptionKey@@ + +## By default, we deploy to the root context path. However, some servers have historically used /labkey or even /cpas +#context.contextPath=/labkey + +## Using a legacy context path provides backwards compatibility with old deployments. A typical use case would be to +## deploy to the root context (the default) and configure /labkey as the legacy path. GETs will be redirected. +## All other methods (POSTs, PUTs, etc) will be handled server-side via a servlet forward. +#context.legacyContextPath=/labkey + +## Other webapps to be deployed, most commonly to deliver a set of static files. The context path to deploy into is the +## property name after the "context.additionalWebapps." prefix, and the value is the location of the webapp on disk +#context.additionalWebapps.firstContextPath=/my/webapp/path +#context.additionalWebapps.secondContextPath=/my/other/webapp/path + +#context.oldEncryptionKey= +#context.requiredModules= +#context.pipelineConfig=/path/to/pipeline/config/dir +#context.serverGUID= +#context.bypass2FA=true +#context.workDirLocation=/path/to/desired/workDir + +mail.smtpHost=@@smtpHost@@ +mail.smtpPort=@@smtpPort@@ +mail.smtpUser=@@smtpUser@@ +#mail.smtpFrom=@@smtpFrom@@ +#mail.smtpPassword=@@smtpPassword@@ +#mail.startTlsEnable=@@smtpStartTlsEnable@@ +#mail.smtpSocketFactoryClass=@@smtpSocketFactoryClass@@ +#mail.smtpAuth=@@smtpAuth@@ + +## Optional - JMS configuration for remote ActiveMQ message management for distributed pipeline jobs +## https://www.labkey.org/Documentation/wiki-page.view?name=jmsQueue +#context.resources.jms.ConnectionFactory.type=org.apache.activemq.ActiveMQConnectionFactory +#context.resources.jms.ConnectionFactory.factory=org.apache.activemq.jndi.JNDIReferenceFactory +#context.resources.jms.ConnectionFactory.description=JMS Connection Factory +## Use an in-process ActiveMQ queue +#context.resources.jms.ConnectionFactory.brokerURL=vm://localhost?broker.persistent=false&broker.useJmx=false +## Use an out-of-process ActiveMQ queue +#context.resources.jms.ConnectionFactory.brokerURL=tcp://localhost:61616 +#context.resources.jms.ConnectionFactory.brokerName=LocalActiveMQBroker + +## Optional - LDAP configuration for LDAP group/user synchronization +## https://www.labkey.org/Documentation/wiki-page.view?name=LDAP_sync +#context.resources.ldap.ConfigFactory.type=org.labkey.premium.ldap.LdapConnectionConfigFactory +#context.resources.ldap.ConfigFactory.factory=org.labkey.premium.ldap.LdapConnectionConfigFactory +#context.resources.ldap.ConfigFactory.host=myldap.mydomain.com +#context.resources.ldap.ConfigFactory.port=389 +#context.resources.ldap.ConfigFactory.principal=cn=read_user +#context.resources.ldap.ConfigFactory.credentials=read_user_password +#context.resources.ldap.ConfigFactory.useTls=false +#context.resources.ldap.ConfigFactory.useSsl=false +#context.resources.ldap.ConfigFactory.sslProtocol=SSLv3 + +## HTTP session timeout for users - defaults to 30 minutes +#server.servlet.session.timeout=30m + +## Enable shutdown endpoint. Allows server to be shutdown with a POST to 'localhost:8081/actuator/shutdown +#management.endpoint.shutdown.enabled=true +#management.endpoints.enabled-by-default=false +#management.endpoints.web.exposure.include=* +#management.server.port=8081 + +## Don't show the Spring banner on startup +spring.main.banner-mode=off +#logging.config=path/to/alternative/log4j2.xml + +## Optional - JMS configuration for remote ActiveMQ message management for distributed pipeline jobs +## https://www.labkey.org/Documentation/wiki-page.view?name=jmsQueue +#context.resources.jms.name=jms/ConnectionFactory +#context.resources.jms.type=org.apache.activemq.ActiveMQConnectionFactory +#context.resources.jms.factory=org.apache.activemq.jndi.JNDIReferenceFactory +#context.resources.jms.description=JMS Connection Factory +#context.resources.jms.brokerURL=vm://localhost?broker.persistent=false&broker.useJmx=false +#context.resources.jms.brokerName=LocalActiveMQBroker + +## Turn on JSON-formatted HTTP access logging to stdout. See issue 48565 +## https://tomcat.apache.org/tomcat-9.0-doc/config/valve.html#JSON_Access_Log_Valve +#jsonaccesslog.enabled=true + +## Optional configuration, modeled on the non-JSON Spring Boot properties +## https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.server.server.tomcat.accesslog.buffered +#jsonaccesslog.pattern=%h %t %m %U %s %b %D %S "%{Referer}i" "%{User-Agent}i" %{LABKEY.username}s +#jsonaccesslog.condition-if=attributeName +#jsonaccesslog.condition-unless=attributeName + +## Define one or both of 'csp.report' and 'csp.enforce' to enable Content Security Policy (CSP) headers +## Do not use these examples for any production environment without understanding the meaning of each directive! + +## example usage 1 - very strict, disallows 'external' websites, disallows unsafe-inline, but only reports violations (does not enforce) + +#csp.report=\ +# default-src 'self';\ +# connect-src 'self' ${LABKEY.ALLOWED.CONNECTIONS} ;\ +# object-src 'none' ;\ +# style-src 'self' 'unsafe-inline' ;\ +# img-src 'self' data: ;\ +# font-src 'self' data: ;\ +# script-src 'unsafe-eval' 'strict-dynamic' 'nonce-${REQUEST.SCRIPT.NONCE}';\ +# base-uri 'self' ;\ +# upgrade-insecure-requests ;\ +# frame-ancestors 'self' ;\ +# report-uri https://www.labkey.org/admin-contentsecuritypolicyreport.api?${CSP.REPORT.PARAMS} ; + +## example usage 2 - less strict but enforces directives, (NOTE: unsafe-inline is still required for many modules) + +#csp.enforce=\ +# default-src 'self' https: ;\ +# connect-src 'self' https: ${LABKEY.ALLOWED.CONNECTIONS};\ +# object-src 'none' ;\ +# style-src 'self' https: 'unsafe-inline' ;\ +# img-src 'self' data: ;\ +# font-src 'self' data: ;\ +# script-src 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' 'nonce-${REQUEST.SCRIPT.NONCE}';\ +# base-uri 'self' ;\ +# upgrade-insecure-requests ;\ +# frame-ancestors 'self' ;\ +# report-uri https://www.labkey.org/admin-contentsecuritypolicyreport.api?${CSP.REPORT.PARAMS} ; + + +## Enable tomcat access log +#server.tomcat.basedir=. +#server.tomcat.accesslog.enabled=true +#server.tomcat.accesslog.directory=logs +#server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %S %I "%{Referrer}i" "%{User-Agent}i" %{LABKEY.username}s From 7ac3bad7309f76363486a0321d216bb5eb9ec3e7 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Thu, 29 Feb 2024 08:10:33 -0800 Subject: [PATCH 03/12] Changelist missed a line --- server/configs/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/configs/application.properties b/server/configs/application.properties index 5e248d09e3..f25838ee52 100644 --- a/server/configs/application.properties +++ b/server/configs/application.properties @@ -61,7 +61,7 @@ mail.smtpUser=@@smtpUser@@ #useLocalBuild#spring.devtools.restart.additional-paths=@@pathToServer@@/build/deploy/modules,@@pathToServer@@/build/deploy/embedded/config -# HTTP session timeout for users - defaults to 30 minutes +## HTTP session timeout for users - defaults to 30 minutes #server.servlet.session.timeout=30m ## Enable shutdown endpoint From 357ff38905edb2cd0020861ce966aa16fdc4a355 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 1 Mar 2024 11:07:59 -0800 Subject: [PATCH 04/12] Start comparing VERSIONs --- .../labkey/embedded/EmbeddedExtractor.java | 96 ++++++++++++++----- .../src/org/labkey/embedded/LabKeyServer.java | 5 +- .../LabKeyTomcatServletWebServerFactory.java | 24 +++-- 3 files changed, 84 insertions(+), 41 deletions(-) diff --git a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java index 52f2ac398b..bb81956a51 100644 --- a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java +++ b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java @@ -1,5 +1,7 @@ package org.labkey.embedded; +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.core.util.VersionUtil; import org.labkey.bootstrap.ConfigException; import java.io.BufferedOutputStream; @@ -7,6 +9,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; import java.util.Arrays; import java.util.jar.JarFile; import java.util.zip.ZipEntry; @@ -16,11 +19,67 @@ public class EmbeddedExtractor { private static final int BUFFER_SIZE = 1024 * 64; - private void extractExecutableJar(File jarFilePath, File destDirectory, boolean remotePipeline) + private final File currentDir = new File("").getAbsoluteFile(); + private final File labkeyServerJar; + + public EmbeddedExtractor() + { + File[] files = currentDir.listFiles(file -> { + String name = file.getName().toLowerCase(); + return name.endsWith(".jar") && name.contains("labkeyserver"); + }); + + if (files == null || files.length == 0) + { + labkeyServerJar = null; + } + else if (files.length > 1) + { + throw new ConfigException("Multiple jars found - " + Arrays.asList(files) + ". Must provide only one jar."); + } + else + { + labkeyServerJar = files[0]; + } + } + + public File getLabkeyServerJar() + { + return labkeyServerJar; + } + + public boolean shouldUpgrade(File webAppLocation) throws IOException + { + File existingVersionFile = new File(webAppLocation, "WEB-INF/classes/VERSION"); + + // Upgrade from standalone Tomcat installation + if (!existingVersionFile.exists()) + return true; + + String existingVersion = Files.readString(existingVersionFile.toPath()).trim(); + String newVersion = getNewVersion(); + + Version v1 = getLabKeyVersion(existingVersion); + Version v2 = getLabKeyVersion(newVersion); + + return v1.compareTo(v2) > 0; + } + + private String getNewVersion() + { + return ""; + } + + public void extractExecutableJar(File destDirectory, boolean remotePipeline) { + if (labkeyServerJar == null) + { + throw new ConfigException("Executable jar not found in " + currentDir); + } + try { - try (JarFile jar = new JarFile(jarFilePath)) + try (JarFile jar = new JarFile(labkeyServerJar)) { boolean foundDistributionZip = false; var entries = jar.entries(); @@ -76,29 +135,6 @@ private void extractExecutableJar(File jarFilePath, File destDirectory, boolean } } - public void extractExecutableJarFromDir(File currentDir, boolean remotePipeline) throws ConfigException - { - File[] files = currentDir.listFiles(file -> { - String name = file.getName().toLowerCase(); - return name.endsWith(".jar") && name.contains("labkeyserver"); - }); - - if (files == null) - { - throw new ConfigException("Executable jar not found in " + currentDir); - } - - // only 1 jar should be there - if (files.length == 1) - { - extractExecutableJar(files[0], currentDir, remotePipeline); - } - else - { - throw new ConfigException("Multiple jars found - " + Arrays.asList(files) + ". Must provide only one jar."); - } - } - private void extractZip(InputStream zipInputStream, File destDir) throws IOException { //noinspection SSBasedInspection @@ -146,4 +182,14 @@ private static void extractFile(InputStream zipIn, File filePath) throws IOExcep } } + private Version getLabKeyVersion(String versionString) + { + Version v = VersionUtil.parseVersion(versionString, null, null); + if (v.isSnapshot()) + { + // SNAPSHOTs should be assumed to be newer than non-SNAPSHOTs of the same version + v = new Version(v.getMajorVersion(), v.getMinorVersion(), Integer.MAX_VALUE, "SNAPSHOT", null, null); + } + return v; + } } diff --git a/server/embedded/src/org/labkey/embedded/LabKeyServer.java b/server/embedded/src/org/labkey/embedded/LabKeyServer.java index 7b98307bb2..18a71b16da 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyServer.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyServer.java @@ -2,8 +2,6 @@ import jakarta.validation.constraints.NotNull; import org.apache.catalina.connector.Connector; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.labkey.bootstrap.PipelineBootstrapConfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -40,7 +38,8 @@ public static void main(String[] args) if (args.length > 0 && args[0].equalsIgnoreCase("-extract")) { File currentDir = new File("").getAbsoluteFile(); - new EmbeddedExtractor().extractExecutableJarFromDir(currentDir, true); + EmbeddedExtractor embeddedExtractor = new EmbeddedExtractor(); + embeddedExtractor.extractExecutableJar(currentDir, true); return; } diff --git a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java index 340c651ae1..5f93d85872 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java @@ -57,28 +57,26 @@ protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) // Get the context properties from Spring injection LabKeyServer.ContextProperties contextProperties = _server.contextSource(); - // for development, point to the local deploy/labkeyWebapp directory in configs/application.properties - boolean webAppLocationPresent = contextProperties.getWebAppLocation() != null; - File webAppLocation; - try { - if (!webAppLocationPresent) - { - final var currentPath = new File("").getAbsoluteFile(); - webAppLocation = new File(currentPath, "labkeywebapp"); + final File currentDir = new File("").getAbsoluteFile(); + final File webAppLocation; - if (!webAppLocation.exists()) - { - EmbeddedExtractor extractor = new EmbeddedExtractor(); - extractor.extractExecutableJarFromDir(currentPath, false); - } + if (contextProperties.getWebAppLocation() == null) + { + webAppLocation = new File(currentDir, "labkeywebapp"); } else { webAppLocation = new File(contextProperties.getWebAppLocation()); } + EmbeddedExtractor extractor = new EmbeddedExtractor(); + if (contextProperties.getWebAppLocation() == null || extractor.getLabkeyServerJar() != null) + { + extractor.extractExecutableJar(webAppLocation.getParentFile(), false); + } // else, probably a local build deployment + // Turn off the default web.xml behavior so that we don't stomp over customized values // from application.properties, such as session timeouts tomcat.setAddDefaultWebXmlToWebapp(false); From eb6424208c7c6346ebacc5e7610c06c9bae55709 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 1 Mar 2024 17:09:50 -0800 Subject: [PATCH 05/12] Compare distribution VERSION --- .../labkey/embedded/EmbeddedExtractor.java | 72 +++++++++++++++++-- .../LabKeyTomcatServletWebServerFactory.java | 3 +- 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java index bb81956a51..3b1e35edad 100644 --- a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java +++ b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java @@ -3,12 +3,14 @@ import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.core.util.VersionUtil; import org.labkey.bootstrap.ConfigException; +import org.springframework.util.StreamUtils; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; import java.nio.file.Files; import java.util.Arrays; import java.util.jar.JarFile; @@ -48,7 +50,7 @@ public File getLabkeyServerJar() return labkeyServerJar; } - public boolean shouldUpgrade(File webAppLocation) throws IOException + public boolean shouldUpgrade(File webAppLocation) { File existingVersionFile = new File(webAppLocation, "WEB-INF/classes/VERSION"); @@ -56,7 +58,16 @@ public boolean shouldUpgrade(File webAppLocation) throws IOException if (!existingVersionFile.exists()) return true; - String existingVersion = Files.readString(existingVersionFile.toPath()).trim(); + String existingVersion; + try + { + existingVersion = Files.readString(existingVersionFile.toPath()).trim(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + String newVersion = getNewVersion(); Version v1 = getLabKeyVersion(existingVersion); @@ -67,15 +78,60 @@ public boolean shouldUpgrade(File webAppLocation) throws IOException private String getNewVersion() { - return ""; + verifyJar(); + + try + { + try (JarFile jar = new JarFile(labkeyServerJar)) + { + var entries = jar.entries(); + while (entries.hasMoreElements()) + { + var entry = entries.nextElement(); + var entryName = entry.getName(); + + if ("labkey/distribution.zip".equals(entryName)) + { + try (ZipInputStream zipIn = new ZipInputStream(jar.getInputStream(entry))) + { + ZipEntry zipEntry = zipIn.getNextEntry(); + // iterates over entries in the zip file + while (zipEntry != null) + { + if (!zipEntry.isDirectory() && zipEntry.getName().equals("labkeywebapp/WEB-INF/classes/VERSION")) + { + return StreamUtils.copyToString(zipIn, Charset.defaultCharset()); + } + zipIn.closeEntry(); + zipEntry = zipIn.getNextEntry(); + } + } + throw new ConfigException("Unable to determine version of distribution."); + } + } + + throw new ConfigException("Unable to find distribution zip required to run LabKey Server."); + } + } + catch (IOException | ConfigException e) + { + throw new RuntimeException(e); + } } - public void extractExecutableJar(File destDirectory, boolean remotePipeline) + private void verifyJar() { if (labkeyServerJar == null) { throw new ConfigException("Executable jar not found in " + currentDir); } + } + + public void extractExecutableJar(File destDirectory, boolean remotePipeline) + { + verifyJar(); + + backupExistingDeployment(destDirectory); try { @@ -182,10 +238,16 @@ private static void extractFile(InputStream zipIn, File filePath) throws IOExcep } } + //TODO: backup or delete existing files + private void backupExistingDeployment(File deployDir) + { + File webappDir = new File(deployDir, "labkeywebapp"); + } + private Version getLabKeyVersion(String versionString) { Version v = VersionUtil.parseVersion(versionString, null, null); - if (v.isSnapshot()) + if (versionString.endsWith("-SNAPSHOT")) // `v.isSnapshot()` doesn't work { // SNAPSHOTs should be assumed to be newer than non-SNAPSHOTs of the same version v = new Version(v.getMajorVersion(), v.getMinorVersion(), Integer.MAX_VALUE, "SNAPSHOT", null, null); diff --git a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java index 5f93d85872..2ed76ad1b4 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java @@ -74,7 +74,8 @@ protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) EmbeddedExtractor extractor = new EmbeddedExtractor(); if (contextProperties.getWebAppLocation() == null || extractor.getLabkeyServerJar() != null) { - extractor.extractExecutableJar(webAppLocation.getParentFile(), false); + if (extractor.shouldUpgrade(webAppLocation)) + extractor.extractExecutableJar(webAppLocation.getParentFile(), false); } // else, probably a local build deployment // Turn off the default web.xml behavior so that we don't stomp over customized values From f6a5c5878fc034137bc738c32e17d4c37d03da18 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Mon, 4 Mar 2024 13:42:31 -0800 Subject: [PATCH 06/12] Backup existing installation --- server/embedded/build.gradle | 1 + .../labkey/embedded/EmbeddedExtractor.java | 74 +++++++++++++------ .../LabKeyTomcatServletWebServerFactory.java | 6 +- 3 files changed, 57 insertions(+), 24 deletions(-) diff --git a/server/embedded/build.gradle b/server/embedded/build.gradle index eeb198af75..d3e2e09cf5 100644 --- a/server/embedded/build.gradle +++ b/server/embedded/build.gradle @@ -71,6 +71,7 @@ dependencies { runtimeOnly group: "org.apache.tomcat", name: "tomcat-dbcp", version: "${springBootTomcatVersion}" runtimeOnly "org.postgresql:postgresql:${postgresqlDriverVersion}" runtimeOnly "org.apache.logging.log4j:log4j-slf4j2-impl:${log4j2Version}" + implementation "commons-io:commons-io:${commonsIoVersion}" implementation "org.apache.logging.log4j:log4j-core:${log4j2Version}" developmentOnly("org.springframework.boot:spring-boot-devtools") diff --git a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java index 3b1e35edad..1c7d9e775c 100644 --- a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java +++ b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.core.util.VersionUtil; +import org.apache.commons.io.FileUtils; import org.labkey.bootstrap.ConfigException; import org.springframework.util.StreamUtils; @@ -24,6 +25,8 @@ public class EmbeddedExtractor private final File currentDir = new File("").getAbsoluteFile(); private final File labkeyServerJar; + private String labkeyWebappDirName = null; + public EmbeddedExtractor() { File[] files = currentDir.listFiles(file -> { @@ -45,8 +48,18 @@ else if (files.length > 1) } } - public File getLabkeyServerJar() + public boolean foundLabkeyServerJar() + { + return labkeyServerJar != null; + } + + private File verifyJar() { + if (labkeyServerJar == null) + { + throw new ConfigException("Executable jar not found in " + currentDir); + } + return labkeyServerJar; } @@ -78,11 +91,9 @@ public boolean shouldUpgrade(File webAppLocation) private String getNewVersion() { - verifyJar(); - try { - try (JarFile jar = new JarFile(labkeyServerJar)) + try (JarFile jar = new JarFile(verifyJar())) { var entries = jar.entries(); while (entries.hasMoreElements()) @@ -119,23 +130,18 @@ private String getNewVersion() } } - private void verifyJar() + public void extractDistribution(File webAppLocation) { - if (labkeyServerJar == null) - { - throw new ConfigException("Executable jar not found in " + currentDir); - } + labkeyWebappDirName = webAppLocation.getName(); + backupExistingDistribution(webAppLocation); + extractExecutableJar(webAppLocation.getParentFile(), false); } public void extractExecutableJar(File destDirectory, boolean remotePipeline) { - verifyJar(); - - backupExistingDeployment(destDirectory); - try { - try (JarFile jar = new JarFile(labkeyServerJar)) + try (JarFile jar = new JarFile(verifyJar())) { boolean foundDistributionZip = false; var entries = jar.entries(); @@ -149,7 +155,7 @@ public void extractExecutableJar(File destDirectory, boolean remotePipeline) foundDistributionZip = true; try (var distInputStream = jar.getInputStream(entry)) { - extractZip(distInputStream, destDirectory); + extractDistributionZip(distInputStream, destDirectory); } } if (remotePipeline) @@ -191,7 +197,7 @@ public void extractExecutableJar(File destDirectory, boolean remotePipeline) } } - private void extractZip(InputStream zipInputStream, File destDir) throws IOException + private void extractDistributionZip(InputStream zipInputStream, File destDir) throws IOException { //noinspection SSBasedInspection if (!destDir.exists() && !destDir.mkdirs()) @@ -204,7 +210,10 @@ private void extractZip(InputStream zipInputStream, File destDir) throws IOExcep // iterates over entries in the zip file while (entry != null) { - File filePath = new File(destDir, entry.getName()); + String entryName = labkeyWebappDirName == null + ? entry.getName() + : entry.getName().replaceFirst("^labkeywebapp", labkeyWebappDirName); + File filePath = new File(destDir, entryName); if (!entry.isDirectory()) { // if the entry is a file, extracts it @@ -212,6 +221,10 @@ private void extractZip(InputStream zipInputStream, File destDir) throws IOExcep } else { + if (filePath.exists() && filePath.getParentFile().equals(destDir)) + { + throw new ConfigException("Delete or backup existing LabKey deployment at: " + filePath.getAbsolutePath()); + } // if the entry is a directory, make the directory //noinspection SSBasedInspection if (!filePath.exists() && !filePath.mkdirs()) @@ -238,18 +251,35 @@ private static void extractFile(InputStream zipIn, File filePath) throws IOExcep } } - //TODO: backup or delete existing files - private void backupExistingDeployment(File deployDir) + private void backupExistingDistribution(File webAppLocation) { - File webappDir = new File(deployDir, "labkeywebapp"); + try + { + if (webAppLocation.exists()) + { + File backupDir = new File(verifyJar().getParentFile(), "backup"); + FileUtils.forceDelete(backupDir); // Delete existing backup + + FileUtils.moveToDirectory(webAppLocation, backupDir, true); + File modulesDir = new File(webAppLocation.getParentFile(), "modules"); + if (modulesDir.exists()) + { + FileUtils.moveToDirectory(modulesDir, backupDir, false); + } + } + } + catch (IOException e) + { + throw new RuntimeException("Failed to backup existing LabKey installation", e); + } } private Version getLabKeyVersion(String versionString) { Version v = VersionUtil.parseVersion(versionString, null, null); - if (versionString.endsWith("-SNAPSHOT")) // `v.isSnapshot()` doesn't work + if (versionString.endsWith("-SNAPSHOT")) // `VersionUtil.parseVersion` doesn't recognize our 'SNAPSHOT' pattern { - // SNAPSHOTs should be assumed to be newer than non-SNAPSHOTs of the same version + // SNAPSHOTs should be assumed to be newer than non-SNAPSHOTs of the same version. `Version.compareTo` does the opposite v = new Version(v.getMajorVersion(), v.getMinorVersion(), Integer.MAX_VALUE, "SNAPSHOT", null, null); } return v; diff --git a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java index 2ed76ad1b4..7afda40600 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java @@ -72,10 +72,12 @@ protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) } EmbeddedExtractor extractor = new EmbeddedExtractor(); - if (contextProperties.getWebAppLocation() == null || extractor.getLabkeyServerJar() != null) + if (contextProperties.getWebAppLocation() == null || extractor.foundLabkeyServerJar()) { if (extractor.shouldUpgrade(webAppLocation)) - extractor.extractExecutableJar(webAppLocation.getParentFile(), false); + { + extractor.extractDistribution(webAppLocation); + } } // else, probably a local build deployment // Turn off the default web.xml behavior so that we don't stomp over customized values From 73716827d4b30a1278317b9490092a17c2c1e108 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Tue, 5 Mar 2024 10:35:47 -0800 Subject: [PATCH 07/12] include application.properties --- webapps/embedded/README.txt | 7 +++++++ webapps/{ => embedded/config}/application.properties | 0 2 files changed, 7 insertions(+) create mode 100644 webapps/embedded/README.txt rename webapps/{ => embedded/config}/application.properties (100%) diff --git a/webapps/embedded/README.txt b/webapps/embedded/README.txt new file mode 100644 index 0000000000..57e7aac252 --- /dev/null +++ b/webapps/embedded/README.txt @@ -0,0 +1,7 @@ +Thank you for downloading LabKey Server. For more information about... + +- Installing LabKey Server. See https://www.labkey.org/Documentation/wiki-page.view?name=embeddedConfig + +- Upgrading LabKey Server. See https://www.labkey.org/Documentation/wiki-page.view?name=embeddedUpgrade + +- Using LabKey Server. See https://www.labkey.org/Documentation/project-begin.view diff --git a/webapps/application.properties b/webapps/embedded/config/application.properties similarity index 100% rename from webapps/application.properties rename to webapps/embedded/config/application.properties From 76833579e4a7528c948249a0ad6313a48dd2274e Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Tue, 5 Mar 2024 11:45:37 -0800 Subject: [PATCH 08/12] Put config files in a different location --- {webapps => server/configs/webapps}/embedded/README.txt | 0 .../configs/webapps}/embedded/config/application.properties | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {webapps => server/configs/webapps}/embedded/README.txt (100%) rename {webapps => server/configs/webapps}/embedded/config/application.properties (100%) diff --git a/webapps/embedded/README.txt b/server/configs/webapps/embedded/README.txt similarity index 100% rename from webapps/embedded/README.txt rename to server/configs/webapps/embedded/README.txt diff --git a/webapps/embedded/config/application.properties b/server/configs/webapps/embedded/config/application.properties similarity index 100% rename from webapps/embedded/config/application.properties rename to server/configs/webapps/embedded/config/application.properties From 2b4aefa19789c68c9cc70460eafec504509aaa3a Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 8 Mar 2024 17:00:57 -0800 Subject: [PATCH 09/12] Add service scripts to distributions --- server/configs/webapps/service.bat | 28 ++++++++++++++++++++++++ server/configs/webapps/tomcat_lk.service | 28 ++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 server/configs/webapps/service.bat create mode 100644 server/configs/webapps/tomcat_lk.service diff --git a/server/configs/webapps/service.bat b/server/configs/webapps/service.bat new file mode 100644 index 0000000000..3e2683775f --- /dev/null +++ b/server/configs/webapps/service.bat @@ -0,0 +1,28 @@ +set LABKEY_HOME=C:\labkey\labkey +set JAVA_HOME=C:\labkey\apps\java\jdk-17.0.9+9 + +prunsrv.exe //IS//tc10embedded ^ + --DisplayName "LabKey Tomcat 10 Embedded - tc10embedded" ^ + --Description "LabKey Tomcat 10 Embedded" ^ + --Install "%LABKEY_HOME%\prunsrv.exe" ^ + --LogPath "%LABKEY_HOME%\logs" ^ + --StdOutput auto ^ + --StdError auto ^ + --Classpath "%LABKEY_HOME%\labkeyServer.jar" ^ + --Jvm "%JAVA_HOME%\bin\server\jvm.dll" ^ + --StartMode jvm ^ + --StopMode jvm ^ + --StartPath "%LABKEY_HOME%" ^ + --StopPath "%LABKEY_HOME%" ^ + --StartParams start ^ + --StartClass "org.springframework.boot.loader.launch.JarLauncher" ^ + --StopParams stop ^ + --StopMethod stop ^ + --StopClass "java.lang.System" ^ + --StopTimeout 60 ^ + --Startup manual ^ + --LogLevel Debug ^ + --JvmOptions "-Djava.io.tmpdir=%LABKEY_HOME%\tomcat-tmp;-XX:+HeapDumpOnOutOfMemoryError;-XX:HeapDumpPath=%LABKEY_HOME%\tomcat-tmp;-DterminateOnStartupFailure=true;%JvmArgs%" ^ + --JvmOptions9 "--add-opens=java.base/java.lang=ALL-UNNAMED#--add-opens=java.base/java.io=ALL-UNNAMED#--add-opens=java.base/java.util=ALL-UNNAMED#--add-opens=java.base/java.util.concurrent=ALL-UNNAMED#--add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED" ^ + --JvmMs 2048 ^ + --JvmMx 2048 \ No newline at end of file diff --git a/server/configs/webapps/tomcat_lk.service b/server/configs/webapps/tomcat_lk.service new file mode 100644 index 0000000000..f53c9fba97 --- /dev/null +++ b/server/configs/webapps/tomcat_lk.service @@ -0,0 +1,28 @@ +# Systemd unit file for tomcat_lk + +[Unit] +Description=lk Apache Tomcat Application +After=syslog.target network.target + +[Service] +Type=simple +Environment="LABKEY_HOME=/labkey/labkey" +Environment="JAVA_HOME=/usr/lib/jvm/jdk-17.0.10+7" +Environment="JAVA_PRE_JAR_OPS=-Duser.timezone=America/Los_Angeles -Djava.library.path=/usr/lib/x86_64-linux-gnu -Djava.awt.headless=true -Xms1932M -Xmx1932M -Djava.security.egd=file:/dev/./urandom" +Environment="JAVA_MID_JAR_OPS=-XX:+HeapDumpOnOutOfMemoryError -XX:+UseContainerSupport -XX:HeapDumpPath=$LABKEY_HOME/tomcat-tmp -Djava.net.preferIPv4Stack=true" +Environment="LABKEY_JAR_OPS=-Dlabkey.home=$LABKEY_HOME -Dlabkey.log.home=$LABKEY_HOME/logs -Djava.io.tmpdir=$LABKEY_HOME/tomcat-tmp" +Environment="JAVA_LOG_JAR_OPS=-XX:ErrorFile=$LABKEY_HOME/logs/error_%p.log -Dlog4j.configurationFile=log4j2.xml" +Environment="JAVA_FLAGS_JAR_OPS=-Dorg.apache.catalina.startup.EXIT_ON_INIT_FAILURE=true -DsynchronousStartup=true -DterminateOnStartupFailure=true" +WorkingDirectory=$LABKEY_HOME +OOMScoreAdjust=-500 + +ExecStart=$JAVA_HOME/bin/java $JAVA_PRE_JAR_OPS $JAVA_MID_JAR_OPS $LABKEY_JAR_OPS $JAVA_LOG_JAR_OPS $JAVA_FLAGS_JAR_OPS -jar $LABKEY_HOME/labkeyServer.jar +SuccessExitStatus=0 143 +Restart=on-failure +RestartSec=15 + +User=tomcat +Group=tomcat + +[Install] +WantedBy=multi-user.target \ No newline at end of file From b1a0d58ee6e998fef875ec7d653d4f418e7b749a Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Fri, 8 Mar 2024 17:01:23 -0800 Subject: [PATCH 10/12] Update version comparison --- .../labkey/embedded/EmbeddedExtractor.java | 132 ++++++++++++++---- .../LabKeyTomcatServletWebServerFactory.java | 5 +- 2 files changed, 105 insertions(+), 32 deletions(-) diff --git a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java index 1c7d9e775c..6b960a0102 100644 --- a/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java +++ b/server/embedded/src/org/labkey/embedded/EmbeddedExtractor.java @@ -1,7 +1,5 @@ package org.labkey.embedded; -import com.fasterxml.jackson.core.Version; -import com.fasterxml.jackson.core.util.VersionUtil; import org.apache.commons.io.FileUtils; import org.labkey.bootstrap.ConfigException; import org.springframework.util.StreamUtils; @@ -11,9 +9,11 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.Arrays; +import java.util.List; +import java.util.Objects; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -63,12 +63,13 @@ private File verifyJar() return labkeyServerJar; } - public boolean shouldUpgrade(File webAppLocation) + private boolean shouldExtract(File webAppLocation) { File existingVersionFile = new File(webAppLocation, "WEB-INF/classes/VERSION"); + File existingDistributionFile = new File(webAppLocation, "WEB-INF/classes/distribution"); - // Upgrade from standalone Tomcat installation - if (!existingVersionFile.exists()) + // Likely upgrading from standalone Tomcat installation or webAppLocation doesn't exist. + if (!existingVersionFile.exists() || !existingDistributionFile.exists()) return true; String existingVersion; @@ -81,16 +82,32 @@ public boolean shouldUpgrade(File webAppLocation) throw new RuntimeException(e); } - String newVersion = getNewVersion(); + String existingDistributionName; + try + { + existingDistributionName = Files.readString(existingDistributionFile.toPath()).trim(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } - Version v1 = getLabKeyVersion(existingVersion); - Version v2 = getLabKeyVersion(newVersion); + LabKeyDistributionInfo existingDistribution = new LabKeyDistributionInfo(existingVersion, existingDistributionName); + LabKeyDistributionInfo incomingDistribution = getDistributionInfo(); - return v1.compareTo(v2) > 0; + return !existingDistribution.equals(incomingDistribution) || + incomingDistribution.buildUrl == null; // Always redeploy distributions that aren't from TeamCity } - private String getNewVersion() + /** + * Extract distribution info from bundled distribution.zip + * @return A list containing the version string + */ + private LabKeyDistributionInfo getDistributionInfo() { + String version = null; + String distributionName = null; + try { try (JarFile jar = new JarFile(verifyJar())) @@ -111,13 +128,25 @@ private String getNewVersion() { if (!zipEntry.isDirectory() && zipEntry.getName().equals("labkeywebapp/WEB-INF/classes/VERSION")) { - return StreamUtils.copyToString(zipIn, Charset.defaultCharset()); + version = StreamUtils.copyToString(zipIn, StandardCharsets.UTF_8); + } + if (!zipEntry.isDirectory() && zipEntry.getName().equals("labkeywebapp/WEB-INF/classes/distribution")) + { + distributionName = StreamUtils.copyToString(zipIn, StandardCharsets.UTF_8); } zipIn.closeEntry(); zipEntry = zipIn.getNextEntry(); } } - throw new ConfigException("Unable to determine version of distribution."); + if (version == null) + { + throw new ConfigException("Unable to determine version of distribution."); + } + if (distributionName == null) + { + throw new ConfigException("Unable to determine name of distribution."); + } + return new LabKeyDistributionInfo(version, distributionName); } } @@ -132,9 +161,12 @@ private String getNewVersion() public void extractDistribution(File webAppLocation) { - labkeyWebappDirName = webAppLocation.getName(); - backupExistingDistribution(webAppLocation); - extractExecutableJar(webAppLocation.getParentFile(), false); + if (shouldExtract(webAppLocation)) + { + labkeyWebappDirName = webAppLocation.getName(); + backupExistingDistribution(webAppLocation); + extractExecutableJar(webAppLocation.getParentFile(), false); + } } public void extractExecutableJar(File destDirectory, boolean remotePipeline) @@ -255,16 +287,19 @@ private void backupExistingDistribution(File webAppLocation) { try { - if (webAppLocation.exists()) + List toBackup = List.of( + webAppLocation, + new File(webAppLocation.getParentFile(), "modules") + ); + + if (toBackup.stream().anyMatch(File::exists)) { File backupDir = new File(verifyJar().getParentFile(), "backup"); FileUtils.forceDelete(backupDir); // Delete existing backup - FileUtils.moveToDirectory(webAppLocation, backupDir, true); - File modulesDir = new File(webAppLocation.getParentFile(), "modules"); - if (modulesDir.exists()) + for (File f : toBackup) { - FileUtils.moveToDirectory(modulesDir, backupDir, false); + FileUtils.moveToDirectory(f, backupDir, true); } } } @@ -274,14 +309,55 @@ private void backupExistingDistribution(File webAppLocation) } } - private Version getLabKeyVersion(String versionString) +} + +class LabKeyDistributionInfo +{ + final String version; + final String buildUrl; + final String distributionName; + + /** + * 'VERSION' file is expected to contain one or two lines. The LabKey version (e.g. 24.3-SNAPSHOT) is the first line. + * The TeamCity BUILD_URL is the second line if the distribution was produced by TeamCity + * 'distribution' file is expected to contain the name of the deployed distribution + * @param versionFileContents contents of 'labkeywebapp/WEB-INF/classes/VERSION' + * @param distributionFileContents contents of 'labkeywebapp/WEB-INF/classes/distribution' + */ + public LabKeyDistributionInfo(String versionFileContents, String distributionFileContents) { - Version v = VersionUtil.parseVersion(versionString, null, null); - if (versionString.endsWith("-SNAPSHOT")) // `VersionUtil.parseVersion` doesn't recognize our 'SNAPSHOT' pattern + String[] splitVersion = versionFileContents.trim().split("\\n"); + version = splitVersion[0]; + if (splitVersion.length > 1) { - // SNAPSHOTs should be assumed to be newer than non-SNAPSHOTs of the same version. `Version.compareTo` does the opposite - v = new Version(v.getMajorVersion(), v.getMinorVersion(), Integer.MAX_VALUE, "SNAPSHOT", null, null); + buildUrl = splitVersion[1]; } - return v; + else + { + buildUrl = null; + } + distributionName = distributionFileContents; } -} + + @Override + public boolean equals(Object o) + { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + LabKeyDistributionInfo that = (LabKeyDistributionInfo) o; + + if (!version.equals(that.version)) return false; + if (!Objects.equals(buildUrl, that.buildUrl)) return false; + return distributionName.equals(that.distributionName); + } + + @Override + public int hashCode() + { + int result = version.hashCode(); + result = 31 * result + (buildUrl != null ? buildUrl.hashCode() : 0); + result = 31 * result + distributionName.hashCode(); + return result; + } +} \ No newline at end of file diff --git a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java index 7afda40600..0bb3f48eda 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java @@ -74,10 +74,7 @@ protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) EmbeddedExtractor extractor = new EmbeddedExtractor(); if (contextProperties.getWebAppLocation() == null || extractor.foundLabkeyServerJar()) { - if (extractor.shouldUpgrade(webAppLocation)) - { - extractor.extractDistribution(webAppLocation); - } + extractor.extractDistribution(webAppLocation); } // else, probably a local build deployment // Turn off the default web.xml behavior so that we don't stomp over customized values From 34f06fc8ff9ec95987815df7a52c0beb0ebc38b6 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Mon, 11 Mar 2024 11:18:26 -0700 Subject: [PATCH 11/12] Update plugins --- gradle.properties | 2 +- server/configs/application.properties | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index b97becd85b..e00166d1ad 100644 --- a/gradle.properties +++ b/gradle.properties @@ -62,7 +62,7 @@ windowsProteomicsBinariesVersion=1.0 # The current version numbers for the gradle plugins. artifactoryPluginVersion=4.31.9 gradleNodePluginVersion=3.5.1 -gradlePluginsVersion=2.5.0-embeddedUpgrade-SNAPSHOT +gradlePluginsVersion=2.6.0-embeddedUpgrade-SNAPSHOT owaspDependencyCheckPluginVersion=8.4.3 versioningPluginVersion=1.1.2 diff --git a/server/configs/application.properties b/server/configs/application.properties index f25838ee52..bbc8b753ad 100644 --- a/server/configs/application.properties +++ b/server/configs/application.properties @@ -59,7 +59,9 @@ mail.smtpUser=@@smtpUser@@ #mail.smtpSocketFactoryClass=@@smtpSocketFactoryClass@@ #mail.smtpAuth=@@smtpAuth@@ -#useLocalBuild#spring.devtools.restart.additional-paths=@@pathToServer@@/build/deploy/modules,@@pathToServer@@/build/deploy/embedded/config +#useLocalBuild#spring.devtools.restart.additional-paths=@@pathToServer@@/build/deploy/modules +# Use a trigger file for smoother restart behavior +#useLocalBuild#spring.devtools.restart.trigger-file=.restartTrigger ## HTTP session timeout for users - defaults to 30 minutes #server.servlet.session.timeout=30m From 1e5704e120ce045cbe8345ba3df9309a7c62952c Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Tue, 12 Mar 2024 08:14:20 -0700 Subject: [PATCH 12/12] Update gradle plugin to release version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e00166d1ad..2dc5ef90d8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -62,7 +62,7 @@ windowsProteomicsBinariesVersion=1.0 # The current version numbers for the gradle plugins. artifactoryPluginVersion=4.31.9 gradleNodePluginVersion=3.5.1 -gradlePluginsVersion=2.6.0-embeddedUpgrade-SNAPSHOT +gradlePluginsVersion=2.6.0 owaspDependencyCheckPluginVersion=8.4.3 versioningPluginVersion=1.1.2