diff --git a/.github/actions/ssh/action.yml b/.github/actions/ssh/action.yml new file mode 100644 index 00000000..5c4886ab --- /dev/null +++ b/.github/actions/ssh/action.yml @@ -0,0 +1,42 @@ +name: SSH setup +description: Set up the SSH agent + +inputs: + config: + description: The SSH configuration + required: true + key: + description: The private SSH key + required: true + known-hosts: + description: The list of known hosts + required: true + +runs: + using: composite + + steps: + - name: Configure SSH + shell: sh + env: + CONFIG: ${{ inputs.config }} + KNOWN_HOSTS: ${{ inputs.known-hosts }} + run: | + mkdir -p ~/.ssh + echo "${CONFIG}" > ~/.ssh/config + echo "${KNOWN_HOSTS}" > ~/.ssh/known_hosts + + - name: Start SSH agent + shell: bash + env: + SOCKET: /tmp/ssh-agent.sock + run: | + echo "SSH_AUTH_SOCK=${SOCKET}" >> $GITHUB_ENV + ssh-agent -a ${SOCKET} > /dev/null + + - name: Add SSH key + shell: bash + env: + KEY: ${{ inputs.key }} + run: | + ssh-add - <<< "${KEY}" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..cdad2c55 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,15 @@ +name: Deploy (Production) + +on: workflow_dispatch + +jobs: + deploy: + runs-on: antville + + environment: + name: weblogs.at + url: https://weblogs.at + + steps: + - name: Copy files to production server + run: ssh staging-server deploy-helma diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 35333503..b189f260 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ permissions: jobs: build: - runs-on: ubuntu-latest + runs-on: antville env: GH_TOKEN: ${{ github.token }} @@ -19,27 +19,20 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Java - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: 21 - - - name: Set up Gradle - uses: gradle/actions/setup-gradle@v3 - - name: Build with Gradle run: ./gradlew assembleDist - name: Create release + # FIXME: Currently only outputs gh command; adapt for Forgejo run: | - gh release create "$GITHUB_REF_NAME" \ + echo gh release create "$GITHUB_REF_NAME" \ --repo "$GITHUB_REPOSITORY" \ --title "$(date +'%d %b %Y')" \ --generate-notes - name: Upload assets + # FIXME: Currently only outputs gh command; adapt for Forgejo run: | - gh release upload "$GITHUB_REF_NAME" \ + echo gh release upload "$GITHUB_REF_NAME" \ build/distributions/helma-*.* \ --clobber diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml new file mode 100644 index 00000000..847df5c7 --- /dev/null +++ b/.github/workflows/renovate.yml @@ -0,0 +1,40 @@ +name: Run Renovate + +on: + schedule: + - cron: "13 * * * *" + workflow_dispatch: + +jobs: + renovate: + runs-on: antville + + steps: + - uses: actions/checkout@v4 + + - name: Run Renovate + # See + # debug | info | warn | error | fatal + run: LOG_LEVEL=info npx renovate + env: + # Renovate is using this token to retrieve release notes + GITHUB_COM_TOKEN: ${{ secrets.renovate_github_com_token }} + # Autodiscover is better suited for an extra repo running Renovate on all desired repos + #RENOVATE_AUTODISCOVER: 'true' + RENOVATE_CONFIG_FILE: renovate.json + RENOVATE_ENDPOINT: ${{ github.api_url }} + RENOVATE_LOG_FILE: renovate-log.ndjson + RENOVATE_LOG_FILE_LEVEL: debug + RENOVATE_PLATFORM: gitea + RENOVATE_REPOSITORIES: ${{ github.repository }} + RENOVATE_REPOSITORY_CACHE: 'enabled' + # github.token is not working here, it lacks some permissions required by Renovate + RENOVATE_TOKEN: ${{ secrets.renovate_token }} + + - name: Save log file + # FIXME: v4 of this action causes an error on Forgejo (“You must configure a GitHub token”) + uses: actions/upload-artifact@v3 + if: always() + with: + name: renovate-log.ndjson + path: renovate-log.ndjson diff --git a/.github/workflows/stage.yml b/.github/workflows/stage.yml new file mode 100644 index 00000000..17e693d4 --- /dev/null +++ b/.github/workflows/stage.yml @@ -0,0 +1,32 @@ +name: Deploy (Staging) + +on: workflow_dispatch + +jobs: + stage: + runs-on: antville + + environment: + name: stage + url: ${{ vars.stage_url }} + + steps: + - uses: actions/checkout@v4 + + - name: Build with Gradle + run: ./gradlew installDist + + - name: Publish to staging server + run: | + rsync ./build/install/helma/ staging-server:./ \ + --verbose --archive --delete --compress \ + --filter '+ /bin' \ + --filter '+ /extras' \ + --filter '+ /launcher.jar' \ + --filter '- /lib/ext' \ + --filter '+ /lib' \ + --filter '+ /modules' \ + --filter '- /*' + + - name: Restart Helma + run: ssh staging-server restart diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml deleted file mode 100644 index 254d9892..00000000 --- a/.github/workflows/staging.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Staging - -on: - workflow_dispatch - -env: - SSH_AUTH_SOCK: /tmp/ssh-agent.sock - -jobs: - install: - runs-on: ubuntu-latest - - environment: - name: staging - url: https://antville-test.click - - steps: - - uses: actions/checkout@v4 - - - name: Set up Java - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: 21 - - - name: Set up Gradle - uses: gradle/actions/setup-gradle@v3 - - - name: Build with Gradle - run: ./gradlew installDist - - - name: Set up SSH agent - run: | - ssh-agent -a $SSH_AUTH_SOCK > /dev/null - ssh-add - <<< "${{ secrets.SSH_PRIVATE_KEY }}" - mkdir -p ~/.ssh - echo '${{ vars.SSH_CONFIG }}' > ~/.ssh/config - echo '${{ vars.KNOWN_HOSTS }}' > ~/.ssh/known_hosts - - - name: Publish to staging server - run: | - rsync build/install/helma/ antville.dev:/ \ - --verbose --archive --delete --compress \ - --filter '+ launcher.jar' \ - --filter '+ lib' \ - --filter '+ *.jar' \ - --filter '- *' \ - - - name: Restart Helma - run: ssh antville.dev restart diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 0d95f065..c52f6863 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,6 @@ { "recommendations": [ - "vscjava.vscode-java-pack" + "vscjava.vscode-java-pack", + "vscjava.vscode-gradle" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 8310c621..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "java", - "name": "Current File", - "request": "launch", - "mainClass": "${file}" - }, - { - "type": "java", - "name": "ImageInfo", - "request": "launch", - "mainClass": "helma.image.ImageInfo", - "projectName": "helma_" - }, - { - "type": "java", - "name": "CommandlineRunner", - "request": "launch", - "mainClass": "helma.main.CommandlineRunner", - "projectName": "helma_" - }, - { - "type": "java", - "name": "Server", - "request": "launch", - "mainClass": "helma.main.Server", - "projectName": "helma_" - }, - { - "type": "java", - "name": "XmlConverter", - "request": "launch", - "mainClass": "helma.objectmodel.dom.XmlConverter", - "projectName": "helma_" - }, - { - "type": "java", - "name": "Crypt", - "request": "launch", - "mainClass": "helma.util.Crypt", - "projectName": "helma_" - }, - { - "type": "java", - "name": "HtmlEncoder", - "request": "launch", - "mainClass": "helma.util.HtmlEncoder", - "projectName": "helma_" - }, - { - "type": "java", - "name": "Logo", - "request": "launch", - "mainClass": "helma.util.Logo", - "projectName": "helma_" - }, - { - "type": "java", - "name": "MarkdownProcessor", - "request": "launch", - "mainClass": "helma.util.MarkdownProcessor", - "projectName": "helma_" - }, - { - "type": "java", - "name": "Commandline", - "request": "launch", - "mainClass": "helma.main.launcher.Commandline", - "projectName": "launcher" - }, - { - "type": "java", - "name": "Main", - "request": "launch", - "mainClass": "helma.main.launcher.Main", - "projectName": "launcher" - } - ] -} \ No newline at end of file diff --git a/README.md b/README.md index 4046dbc5..e93af8d1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## TL;DR -- Make sure you have Java 17 or higher installed +- Make sure you have Java 11 or higher installed - Download and unpack the [latest release](https://github.com/antville/helma/releases) - Invoke `./bin/helma`, resp. `./bin/helma.bat`, depending on your platform - Direct your web browser to @@ -21,9 +21,9 @@ Although Helma became a Grande Dame of server-side JavaScript already decades ag ## System Requirements -You need a Java virtual machine version 17 or higher to run Helma. +You need a Java virtual machine version 11 or higher to run Helma. -Please consult the documentation of your platform on how to obtain and install Java. +Please consult the documentation of your platform how to obtain and install Java. You also can directly download a [Java runtime or development kit](https://www.oracle.com/java/technologies/javase-downloads.html#javasejdk) from Oracle. @@ -33,10 +33,12 @@ Helma is built with [Gradle](https://gradle.org), the build task depends on the ### Additional Prerequisites -* [Rsync](https://rsync.samba.org) version ≥ 3.1.0 * [Node.js](https://nodejs.org) LTS version +* [Rsync](https://rsync.samba.org) version ≥ 3.1.0 -Clone this repository to your machine and start the build process with `./gradlew install`. The build script is going to ask you if you want to update the installation, enter `yes` or `no`. +Clone this repository to your machine and run Helma with `./gradlew run`. + +To update the installation from a build, run `./gradlew update` and enter `yes` at the prompt. > ⚠️ > Please be aware that this step is going to overwrite files in the installation directory – escpecially at a later time when there might be substantial changes. Should this happen by accident you find the previous installation in the `backups` directory. diff --git a/build.gradle b/build.gradle index 99434d44..abf4287f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id 'application' - id 'com.github.jk1.dependency-license-report' version '2.7' + id 'com.github.jk1.dependency-license-report' version '2.9' } import org.apache.tools.ant.filters.FixCrLfFilter @@ -17,9 +17,9 @@ def textFiles = ['**/*.hac', '**/.html', '**/*.js', '**/*.md', '**/*.properties' allprojects { apply plugin: 'java' - compileJava { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } repositories { @@ -27,7 +27,7 @@ allprojects { } } -version = new Date().format("yyyyMMdd") +version = new Date().format("yy.M.d") tasks.build.dependsOn javadoc, 'jsdoc', 'generateLicenseReport' tasks.compileJava.dependsOn 'processSource' @@ -59,16 +59,16 @@ configurations { dependencies { implementation 'com.google.code.gson:gson:2.11.0' - implementation 'commons-codec:commons-codec:1.17.0' + implementation 'commons-codec:commons-codec:1.17.1' implementation 'commons-fileupload:commons-fileupload:1.5' - implementation 'commons-logging:commons-logging:1.3.2' - implementation 'commons-net:commons-net:3.10.0' + implementation 'commons-logging:commons-logging:1.3.4' + implementation 'commons-net:commons-net:3.11.1' implementation 'com.sun.mail:javax.mail:1.6.2' implementation 'javax.servlet:javax.servlet-api:4.0.1' implementation 'org.ccil.cowan.tagsoup:tagsoup:1.2.1' - implementation 'org.eclipse.jetty:jetty-servlet:9.4.54.v20240208' - implementation 'org.eclipse.jetty:jetty-xml:9.4.54.v20240208' - implementation 'org.mozilla:rhino:1.7.13' + implementation 'org.eclipse.jetty:jetty-servlet:9.4.56.v20240826' + implementation 'org.eclipse.jetty:jetty-xml:9.4.56.v20240826' + implementation 'org.mozilla:rhino-all:1.8.0' implementation 'org.sejda.imageio:webp-imageio:0.1.6' implementation 'xerces:xercesImpl:2.12.2' implementation 'xmlrpc:xmlrpc:2.0.1' @@ -78,6 +78,37 @@ def rhinoJar = configurations.library.files.find { jar -> jar.name.startsWith('rhino') } +run { + jvmArgs jettyLogLevel, suppressMacosDockIcon + classpath += fileTree(dir: 'lib/ext', include: '*.jar') +} + +application { + mainClass = 'helma.main.Server' + + applicationDistribution.from(projectDir) { + include 'modules/**' + include 'LICENSE.md' + include 'README.md' + include 'start.*' + } + + applicationDistribution.from(javadoc.destinationDir) { + include '**' + into 'docs/javadoc' + } + + applicationDistribution.from("${project.buildDir}/docs/jsdoc") { + include '**' + into 'docs/jsdoc' + } + + applicationDistribution.from("${project.buildDir}/reports/dependency-license") { + include '**' + into 'licenses' + } +} + startScripts { applicationName = 'helma' classpath = files('../launcher.jar') @@ -103,30 +134,6 @@ distributions { } } -application { - applicationDistribution.from(projectDir) { - include 'modules/**' - include 'LICENSE.md' - include 'README.md' - include 'start.*' - } - - applicationDistribution.from(javadoc.destinationDir) { - include '**' - into 'docs/javadoc' - } - - applicationDistribution.from("${project.buildDir}/docs/jsdoc") { - include '**' - into 'docs/jsdoc' - } - - applicationDistribution.from("${project.buildDir}/reports/dependency-license") { - include '**' - into 'licenses' - } -} - distTar { dependsOn ':generateLicenseReport', ':javadoc', ':jsdoc' @@ -147,42 +154,31 @@ distZip { installDist { dependsOn build - - if (!System.getenv('CI')) { - finalizedBy 'update' - } } -run { - classpath = files('launcher.jar') - jvmArgs jettyLogLevel, suppressMacosDockIcon -} - -task processSource(type: Sync) { - def date = new Date().format("MMMM dd, yyyy") +tasks.register('processSource', Sync) { def gitOutput = new ByteArrayOutputStream() exec { - commandLine 'git', 'describe' + commandLine 'git', 'rev-parse', '--short', 'HEAD' standardOutput = gitOutput errorOutput = new ByteArrayOutputStream() ignoreExitValue = true } - def description = date - def tag = gitOutput.toString().trim() - - // TODO: Implement extended description in Java code - if (tag) description = "$tag; $description" - from 'src' filter { - line -> line.replaceAll('__builddate__', date) + line -> line + .replaceAll('__builddate__', new Date().format("d MMM yyyy")) + .replaceAll('__commithash__', gitOutput.toString().trim()) + .replaceAll('__version__', version) } into "${project.buildDir}/src" } -task update { +tasks.register('update') { + dependsOn installDist + def rsyncArgs = ['--archive', '--filter', '- backups'] def confirm = { @@ -226,7 +222,7 @@ task update { } } -task jsdoc(type: Exec) { +tasks.register('jsdoc', Exec) { description 'Generates JSDoc API documentation for the included JavaScript modules.' group 'Documentation' @@ -240,7 +236,7 @@ task jsdoc(type: Exec) { args = ['jsdoc', '-d', "$destination"].plus(sources) } -task xgettext(type: JavaExec) { +tasks.register('xgettext', JavaExec) { description 'Extracts translatable message strings from source code.' group 'i18n' @@ -257,7 +253,7 @@ task xgettext(type: JavaExec) { ] } -task po2js(type: JavaExec) { +tasks.register('po2js', JavaExec) { description 'Converts translated message strings from PO format to JavaScript.' group 'i18n' @@ -272,7 +268,7 @@ task po2js(type: JavaExec) { ] } -task rhinoShell(type: JavaExec) { +tasks.register('rhinoShell', JavaExec) { description 'Runs the interactive Rhino JavaScript shell.' group 'Application' @@ -284,7 +280,7 @@ task rhinoShell(type: JavaExec) { // Call this task with a function definition using the `-P` parameter, e.g. // `./gradlew commandLine -Pfunction=manage.getAllApplications` -task commandLine(type: JavaExec) { +tasks.register('commandLine', JavaExec) { description 'Runs a function in a Helma application with `-Pfunction=app.functionName`.' group 'Application' @@ -292,3 +288,11 @@ task commandLine(type: JavaExec) { mainClass = 'helma.main.launcher.Commandline' args '-h', projectDir, function } + +tasks.register('debug', JavaExec) { + group = 'application' + main = 'helma.main.Server' + classpath = sourceSets.main.runtimeClasspath + jvmArgs = ['-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005'] + classpath += fileTree(dir: 'lib/ext', include: '*.jar') +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136..a4b76b95 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b82aa23a..cea7a793 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a42..f3b75f3b 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 7101f8e4..9b42019c 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/lib/ext/.keep b/lib/ext/.keep new file mode 100644 index 00000000..e69de29b diff --git a/modules/helma/build.gradle b/modules/helma/build.gradle index f78f66d4..401dd406 100644 --- a/modules/helma/build.gradle +++ b/modules/helma/build.gradle @@ -12,7 +12,7 @@ processResources.enabled = false processTestResources.enabled = false test.enabled = false -task deps(type: Copy) { +tasks.register('deps', Copy) { from sourceSets.main.runtimeClasspath into '.' } diff --git a/modules/jala/build.gradle b/modules/jala/build.gradle index e1ef800f..3b9b6684 100644 --- a/modules/jala/build.gradle +++ b/modules/jala/build.gradle @@ -15,7 +15,7 @@ processResources.enabled = false processTestResources.enabled = false test.enabled = false -task deps(type: Copy) { +tasks.register('deps', Copy) { from sourceSets.main.runtimeClasspath into 'lib' } diff --git a/modules/jala/util/HopKit/build.gradle b/modules/jala/util/HopKit/build.gradle index f6b40342..f8c9778e 100644 --- a/modules/jala/util/HopKit/build.gradle +++ b/modules/jala/util/HopKit/build.gradle @@ -10,7 +10,7 @@ processResources.enabled = false processTestResources.enabled = false test.enabled = false -task deps(type: Copy) { +tasks.register('deps', Copy) { from sourceSets.main.runtimeClasspath into 'lib' } diff --git a/modules/jala/util/Test/build.gradle b/modules/jala/util/Test/build.gradle index 91015efc..7e596efa 100644 --- a/modules/jala/util/Test/build.gradle +++ b/modules/jala/util/Test/build.gradle @@ -10,7 +10,7 @@ processResources.enabled = false processTestResources.enabled = false test.enabled = false -task deps(type: Copy) { +tasks.register('deps', Copy) { from sourceSets.main.runtimeClasspath into 'code' } diff --git a/src/dist/extras/helma.service b/src/dist/extras/helma.service index 308093aa..7d42310b 100644 --- a/src/dist/extras/helma.service +++ b/src/dist/extras/helma.service @@ -18,7 +18,7 @@ ExecStart = /usr/bin/java -server \ -jar launcher.jar \ -w 8080 -x 8081 -ExecReload = touch apps.properties && touch server.properties +ExecReload = /bin/sh -c 'touch apps.properties && touch server.properties' ExecStop = /bin/kill -15 $MAINPID [Install] diff --git a/src/main/java/helma/main/Server.java b/src/main/java/helma/main/Server.java index 4966551c..6155de3b 100644 --- a/src/main/java/helma/main/Server.java +++ b/src/main/java/helma/main/Server.java @@ -36,7 +36,13 @@ import helma.util.ResourceProperties; */ public class Server implements Runnable { // version string - public static final String version = "🐜 (__builddate__)"; + public static final String version = "__version__"; + + // build date + public static final String buildDate = "__builddate__"; + + // commit hash + public static final String commitHash = "__commithash__"; // static server instance private static Server server; @@ -145,13 +151,8 @@ public class Server implements Runnable { public static void checkJavaVersion() { String javaVersion = System.getProperty("java.version"); - if ((javaVersion == null) || javaVersion.startsWith("1.5") - || javaVersion.startsWith("1.4") - || javaVersion.startsWith("1.3") - || javaVersion.startsWith("1.2") - || javaVersion.startsWith("1.1") - || javaVersion.startsWith("1.0")) { - System.err.println("This version of Helma requires Java 1.6 or greater."); + if ((javaVersion == null) || !javaVersion.startsWith("11")) { + System.err.println("This version of Helma requires Java 11 or greater."); if (javaVersion == null) { // don't think this will ever happen, but you never know System.err.println("Your Java Runtime did not provide a version number. Please update to a more recent version.");