diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f3e8935f1bf..83e88360ba5 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -91,12 +91,7 @@ { "label": "Run Backend CE", "type": "shell", - "windows": { - "command": "./run-cloudbeaver-server.bat" - }, - "osx": { - "command": "./run-cloudbeaver-server.sh" - }, + "command": "java CloudBeaverLauncher.java", "options": { "cwd": "${workspaceFolder}/deploy/cloudbeaver" } diff --git a/deploy/build-frontend.sh b/deploy/build-frontend.sh index 1d8f4430bcd..a18c77976de 100755 --- a/deploy/build-frontend.sh +++ b/deploy/build-frontend.sh @@ -36,4 +36,4 @@ echo "Copy static content" cp -rp ../webapp/packages/product-default/lib/* cloudbeaver/web -echo "Cloudbeaver is ready. Run run-cloudbeaver-server.sh in cloudbeaver folder to start the server." +echo "Cloudbeaver is ready. Run 'java CloudBeaverLauncher.java' in cloudbeaver folder to start the server." diff --git a/deploy/build.bat b/deploy/build.bat index bb03f68ef0f..0e2960ffb22 100644 --- a/deploy/build.bat +++ b/deploy/build.bat @@ -74,6 +74,6 @@ echo "Copy static content" xcopy /E /Q ..\webapp\packages\product-default\lib cloudbeaver\web >NUL -echo "Cloudbeaver is ready. Run run-cloudbeaver-server.bat in cloudbeaver folder to start the server." +echo "Cloudbeaver is ready. Run 'java CloudBeaverLauncher.java' in cloudbeaver folder to start the server." pause diff --git a/deploy/docker/cloudbeaver-ce-distroless/Containerfile b/deploy/docker/cloudbeaver-ce-distroless/Containerfile new file mode 100644 index 00000000000..c8f97689b83 --- /dev/null +++ b/deploy/docker/cloudbeaver-ce-distroless/Containerfile @@ -0,0 +1,20 @@ +FROM eclipse-temurin:25-jdk-noble AS builder + +COPY scripts/CloudBeaverLauncher.java /CloudBeaverLauncher.java +RUN javac --release 25 CloudBeaverLauncher.java + +### Actual image start here +FROM gcr.io/distroless/java25-debian13:nonroot + +LABEL org.opencontainers.image.authors="DBeaver Corp, devops@dbeaver.com" + +COPY --chown=8978:8978 cloudbeaver /opt/cloudbeaver +COPY --from=builder --chown=8978:8978 /CloudBeaverLauncher.class /opt/cloudbeaver/launcher/ +USER 8978:8978 +WORKDIR /opt/cloudbeaver +EXPOSE 8978 + +# UID 8978 has no passwd entry in distroless, so set a writable home for both the launcher and server JVMs. +ENV HOME=/opt/cloudbeaver +ENV JAVA_TOOL_OPTIONS="-Duser.home=/opt/cloudbeaver" +ENTRYPOINT ["/usr/bin/java", "-cp", "/opt/cloudbeaver/launcher", "CloudBeaverLauncher"] diff --git a/deploy/docker/cloudbeaver-ce/Dockerfile b/deploy/docker/cloudbeaver-ce/Dockerfile index a359ed8e9b4..8d8716c2714 100644 --- a/deploy/docker/cloudbeaver-ce/Dockerfile +++ b/deploy/docker/cloudbeaver-ce/Dockerfile @@ -11,11 +11,14 @@ RUN groupadd -g $DBEAVER_GID dbeaver && \ COPY cloudbeaver /opt/cloudbeaver COPY scripts/launch-product.sh /opt/cloudbeaver/launch-product.sh +COPY scripts/CloudBeaverLauncher.java /opt/cloudbeaver/CloudBeaverLauncher.java EXPOSE 8978 RUN find /opt/cloudbeaver -type d -exec chmod 775 {} \; WORKDIR /opt/cloudbeaver/ -RUN chmod +x "run-cloudbeaver-server.sh" "/opt/cloudbeaver/launch-product.sh" +RUN javac --release 25 CloudBeaverLauncher.java && \ + rm CloudBeaverLauncher.java && \ + chmod +x "/opt/cloudbeaver/launch-product.sh" ENTRYPOINT ["./launch-product.sh"] diff --git a/deploy/scripts/CloudBeaverLauncher.java b/deploy/scripts/CloudBeaverLauncher.java new file mode 100644 index 00000000000..05bad2f8aca --- /dev/null +++ b/deploy/scripts/CloudBeaverLauncher.java @@ -0,0 +1,120 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2026 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public final class CloudBeaverLauncher { + static void main(String[] args) throws Exception { + initializeWorkspace(); + + var command = new ArrayList(); + command.add(Path.of(System.getProperty("java.home", "/usr"), "bin", javaExecutable()).toString()); + addJavaOptions(command); + command.addAll(List.of( + "-Dfile.encoding=UTF-8", + "--enable-native-access=ALL-UNNAMED", + "--add-modules=ALL-DEFAULT", + "--add-opens=java.base/java.io=ALL-UNNAMED", + "--add-opens=java.base/java.lang=ALL-UNNAMED", + "--add-opens=java.base/java.lang.reflect=ALL-UNNAMED", + "--add-opens=java.base/java.net=ALL-UNNAMED", + "--add-opens=java.base/java.nio=ALL-UNNAMED", + "--add-opens=java.base/java.nio.charset=ALL-UNNAMED", + "--add-opens=java.base/java.text=ALL-UNNAMED", + "--add-opens=java.base/java.time=ALL-UNNAMED", + "--add-opens=java.base/java.util=ALL-UNNAMED", + "--add-opens=java.base/java.util.concurrent=ALL-UNNAMED", + "--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED", + "--add-opens=java.base/jdk.internal.vm=ALL-UNNAMED", + "--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED", + "--add-opens=java.base/sun.nio.ch=ALL-UNNAMED", + "--add-opens=java.base/sun.security.ssl=ALL-UNNAMED", + "--add-opens=java.base/sun.security.util=ALL-UNNAMED", + "--add-opens=java.security.jgss/sun.security.jgss=ALL-UNNAMED", + "--add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED", + "--add-opens=java.sql/java.sql=ALL-UNNAMED" + )); + command.add("-jar"); + command.add(findLauncherJar().toString()); + command.addAll(Arrays.asList(args)); + command.addAll(List.of( + "-product", + "io.cloudbeaver.product.ce.product", + "-web-config", + Path.of("conf", "cloudbeaver.conf").toString(), + "-nl", + "en", + "-registryMultiLanguage" + )); + + System.err.println("Starting CloudBeaver Server"); + var process = new ProcessBuilder(command).inheritIO().start(); + var shutdownHook = Thread.ofPlatform().name("cloudbeaver-shutdown").unstarted(() -> stop(process)); + Runtime.getRuntime().addShutdownHook(shutdownHook); + int exitCode = process.waitFor(); + Runtime.getRuntime().removeShutdownHook(shutdownHook); + System.exit(exitCode); + } + + private static void initializeWorkspace() throws Exception { + var workspace = Path.of("workspace"); + if (Files.notExists(workspace.resolve(".metadata"))) { + Files.createDirectories(workspace.resolve(".metadata")); + var dataSources = workspace.resolve("GlobalConfiguration").resolve(".dbeaver").resolve("data-sources.json"); + Files.createDirectories(dataSources.getParent()); + if (Files.notExists(dataSources)) { + Files.copy(Path.of("conf", "initial-data-sources.conf"), dataSources); + } + } + } + + private static Path findLauncherJar() throws Exception { + try (var plugins = Files.list(Path.of("server", "plugins"))) { + return plugins + .filter(Files::isRegularFile) + .filter(path -> path.getFileName().toString().startsWith("org.jkiss.dbeaver.launcher")) + .filter(path -> path.getFileName().toString().endsWith(".jar")) + .sorted() + .findFirst() + .orElseThrow(() -> new IllegalStateException("CloudBeaver launcher JAR not found")); + } + } + + private static void addJavaOptions(Collection command) { + var javaOptions = System.getenv("JAVA_OPTS"); + if (javaOptions != null && !javaOptions.isBlank()) { + command.addAll(Arrays.asList(javaOptions.trim().split("\\s+"))); + } + } + + private static String javaExecutable() { + return System.getProperty("os.name", "").startsWith("Windows") ? "java.exe" : "java"; + } + + private static void stop(Process process) { + process.destroy(); + try { + if (!process.waitFor(10, TimeUnit.SECONDS)) { + process.destroyForcibly(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + process.destroyForcibly(); + } + } + + private CloudBeaverLauncher() { + // Intentionally + } +} diff --git a/deploy/scripts/cbvr.sh b/deploy/scripts/cbvr.sh index a2c8aef6918..b81c0dfe364 100755 --- a/deploy/scripts/cbvr.sh +++ b/deploy/scripts/cbvr.sh @@ -6,4 +6,4 @@ if [[ $# -eq 0 ]] ; fi set -- "$@" -cli-mode -source run-cloudbeaver-server.sh "$@" \ No newline at end of file +exec java CloudBeaverLauncher.java "$@" diff --git a/deploy/scripts/launch-product.sh b/deploy/scripts/launch-product.sh index 96f73661588..4f5bd50d5c8 100644 --- a/deploy/scripts/launch-product.sh +++ b/deploy/scripts/launch-product.sh @@ -11,9 +11,8 @@ if [ "$(id -u)" -eq 0 ]; then TARGET_UID=${TARGET_UID:-$DBEAVER_UID} TARGET_GID=${TARGET_GID:-$DBEAVER_GID} - chown -R $DBEAVER_UID:$DBEAVER_GID $PWD/workspace - # Execute run-cloudbeaver-server.sh as the dbeaver user with the JAVA_HOME and PATH environment variables - exec su "$TARGET_USER" -c "JAVA_HOME=$JAVA_HOME PATH=$PATH ./run-cloudbeaver-server.sh $*" + chown -R "$DBEAVER_UID":"$DBEAVER_GID" "$PWD/workspace" + exec su "$TARGET_USER" -s /bin/bash -c 'exec "$JAVA_HOME/bin/java" -cp /opt/cloudbeaver CloudBeaverLauncher "$@"' -- "$@" else - exec ./run-cloudbeaver-server.sh "$@" + exec "$JAVA_HOME/bin/java" -cp /opt/cloudbeaver CloudBeaverLauncher "$@" fi diff --git a/deploy/scripts/run-cloudbeaver-server.bat b/deploy/scripts/run-cloudbeaver-server.bat deleted file mode 100644 index 1cbb98f6d15..00000000000 --- a/deploy/scripts/run-cloudbeaver-server.bat +++ /dev/null @@ -1,47 +0,0 @@ -@echo off -for /f %%a in ('dir /B /S server\plugins\org.jkiss.dbeaver.launcher*.jar') do SET launcherJar="%%a" - -echo "Starting Cloudbeaver Server" - -IF NOT EXIST workspace\.metadata ( - IF NOT EXIST workspace\GlobalConfiguration\.dbeaver ( - mkdir workspace\GlobalConfiguration\.dbeaver\ - copy conf\initial-data-sources.conf workspace\GlobalConfiguration\.dbeaver\data-sources.json - ) -) - -SET VMARGS_OPTS = "" -If Not Defined JAVA_OPTS ( - SET VMARGS_OPTS = "-Xmx2048M" -) - -java %JAVA_OPTS% ^ - -Dfile.encoding=UTF-8 ^ - --enable-native-access=ALL-UNNAMED ^ - --add-modules=ALL-DEFAULT ^ - --add-opens=java.base/java.io=ALL-UNNAMED ^ - --add-opens=java.base/java.lang=ALL-UNNAMED ^ - --add-opens=java.base/java.lang.reflect=ALL-UNNAMED ^ - --add-opens=java.base/java.net=ALL-UNNAMED ^ - --add-opens=java.base/java.nio=ALL-UNNAMED ^ - --add-opens=java.base/java.nio.charset=ALL-UNNAMED ^ - --add-opens=java.base/java.text=ALL-UNNAMED ^ - --add-opens=java.base/java.time=ALL-UNNAMED ^ - --add-opens=java.base/java.util=ALL-UNNAMED ^ - --add-opens=java.base/java.util.concurrent=ALL-UNNAMED ^ - --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED ^ - --add-opens=java.base/jdk.internal.vm=ALL-UNNAMED ^ - --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED ^ - --add-opens=java.base/sun.nio.ch=ALL-UNNAMED ^ - --add-opens=java.base/sun.security.ssl=ALL-UNNAMED ^ - --add-opens=java.base/sun.security.util=ALL-UNNAMED ^ - --add-opens=java.security.jgss/sun.security.jgss=ALL-UNNAMED ^ - --add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED ^ - --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED ^ - --add-opens=java.sql/java.sql=ALL-UNNAMED ^ - -jar %launcherJar% ^ - -product io.cloudbeaver.product.ce.product ^ - -data %workspacePath% ^ - -web-config conf/cloudbeaver.conf ^ - -nl en ^ - -registryMultiLanguage diff --git a/deploy/scripts/run-cloudbeaver-server.sh b/deploy/scripts/run-cloudbeaver-server.sh deleted file mode 100755 index 4c2c2064038..00000000000 --- a/deploy/scripts/run-cloudbeaver-server.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -launcherJar=( server/plugins/org.jkiss.dbeaver.launcher*.jar ) - -echo "Starting Cloudbeaver Server" - -[ ! -d "workspace/.metadata" ] && mkdir -p workspace/.metadata \ - && mkdir -p workspace/GlobalConfiguration/.dbeaver \ - && [ ! -f "workspace/GlobalConfiguration/.dbeaver/data-sources.json" ] \ - && cp conf/initial-data-sources.conf workspace/GlobalConfiguration/.dbeaver/data-sources.json - -exec java ${JAVA_OPTS} \ - -Dfile.encoding=UTF-8 \ - --enable-native-access=ALL-UNNAMED \ - --add-modules=ALL-DEFAULT \ - --add-opens=java.base/java.io=ALL-UNNAMED \ - --add-opens=java.base/java.lang=ALL-UNNAMED \ - --add-opens=java.base/java.lang.reflect=ALL-UNNAMED \ - --add-opens=java.base/java.net=ALL-UNNAMED \ - --add-opens=java.base/java.nio=ALL-UNNAMED \ - --add-opens=java.base/java.nio.charset=ALL-UNNAMED \ - --add-opens=java.base/java.text=ALL-UNNAMED \ - --add-opens=java.base/java.time=ALL-UNNAMED \ - --add-opens=java.base/java.util=ALL-UNNAMED \ - --add-opens=java.base/java.util.concurrent=ALL-UNNAMED \ - --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED \ - --add-opens=java.base/jdk.internal.vm=ALL-UNNAMED \ - --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED \ - --add-opens=java.base/sun.nio.ch=ALL-UNNAMED \ - --add-opens=java.base/sun.security.ssl=ALL-UNNAMED \ - --add-opens=java.base/sun.security.util=ALL-UNNAMED \ - --add-opens=java.security.jgss/sun.security.jgss=ALL-UNNAMED \ - --add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED \ - --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED \ - --add-opens=java.sql/java.sql=ALL-UNNAMED \ - -jar ${launcherJar} \ - -product io.cloudbeaver.product.ce.product \ - "$@" \ - -web-config conf/cloudbeaver.conf \ - -nl en \ - -registryMultiLanguage diff --git a/server/features/io.cloudbeaver.product.ce.feature/root/readme.txt b/server/features/io.cloudbeaver.product.ce.feature/root/readme.txt index 03eabf90535..d2453f8e368 100644 --- a/server/features/io.cloudbeaver.product.ce.feature/root/readme.txt +++ b/server/features/io.cloudbeaver.product.ce.feature/root/readme.txt @@ -10,7 +10,7 @@ Running server ======================== 1. View and modify server configuration before the first start. Validate init-data.conf - you won't be able to change these parameters after the first server start. - 2. Execute run-cloudbeaver-server.sh script in the root directory. + 2. Execute "java CloudBeaverLauncher.java" in the root directory. Command line parameters ======================== diff --git a/webapp/readme.md b/webapp/readme.md index 779ea8bb3db..01ff09a5685 100644 --- a/webapp/readme.md +++ b/webapp/readme.md @@ -51,16 +51,11 @@ Welcome! Follow these steps to install and run the frontend dev server. 3. **Run the Backend Server** - Start the backend server by running the execution script. + Start the backend server by running the launcher. - *For macOS/Linux:* - ```bash - ./run-cloudbeaver-server.sh - ``` - - *For Windows:* + *For all platforms:* ```bash - ./run-cloudbeaver-server.bat + java CloudBeaverLauncher.java ``` 4. **Navigate to the Webapp Directory**