From 9c59055d251217a0b6e9a6f44fa03b81ef01a437 Mon Sep 17 00:00:00 2001 From: Rich Turner <7072278+richturner@users.noreply.github.com> Date: Wed, 13 Jul 2022 22:44:30 +0100 Subject: [PATCH] Updated to Keycloak 18+ --- Dockerfile | 94 +++---- README.md | 16 +- build.gradle | 13 +- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 55741 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 53 ++-- gradlew.bat | 43 ++- module.xml | 11 - scripts/disable-theme-cache.cli | 5 - .../keycloak/theme/CustomThemeProvider.java | 61 ----- .../theme/CustomThemeProviderFactory.java | 65 ----- .../resources/META-INF/keycloak-themes.json | 6 + .../org.keycloak.theme.ThemeProviderFactory | 1 - .../theme}/openremote/account/account.ftl | 0 .../openremote/account/applications.ftl | 0 .../openremote/account/federatedIdentity.ftl | 0 .../theme}/openremote/account/log.ftl | 0 .../theme}/openremote/account/password.ftl | 0 .../resources/css/MaterialIcons-Regular.eot | Bin .../resources/css/MaterialIcons-Regular.ijmap | 0 .../resources/css/MaterialIcons-Regular.svg | 0 .../resources/css/MaterialIcons-Regular.ttf | Bin .../resources/css/MaterialIcons-Regular.woff | Bin .../resources/css/MaterialIcons-Regular.woff2 | Bin .../account/resources/css/materialize.min.css | 0 .../account/resources/css/styles.css | 0 .../account/resources/img/favicon.png | Bin .../account/resources/js/materialize.min.js | 0 .../theme}/openremote/account/sessions.ftl | 0 .../theme}/openremote/account/template.ftl | 0 .../openremote/account/theme.properties | 0 .../theme}/openremote/account/totp.ftl | 0 .../openremote/email/html/password-reset.ftl | 0 .../theme}/openremote/email/theme.properties | 0 .../theme}/openremote/login/error.ftl | 0 .../openremote/login/login-reset-password.ftl | 28 +- .../login/login-update-password.ftl | 72 +++++ .../theme}/openremote/login/login.ftl | 21 +- .../login/messages/messages_en.properties | 0 .../theme}/openremote/login/register.ftl | 0 .../resources/css/MaterialIcons-Regular.eot | Bin .../resources/css/MaterialIcons-Regular.ijmap | 0 .../resources/css/MaterialIcons-Regular.svg | 0 .../resources/css/MaterialIcons-Regular.ttf | Bin .../resources/css/MaterialIcons-Regular.woff | Bin .../resources/css/MaterialIcons-Regular.woff2 | Bin .../login/resources/css/materialize.min.css | 0 .../openremote/login/resources/css/styles.css | 0 .../login/resources/img/favicon.png | Bin .../login/resources/js/materialize.min.js | 0 .../theme}/openremote/login/template.ftl | 16 +- .../theme}/openremote/login/theme.properties | 0 .../login/login-update-password.ftl | 75 ----- tools/autorun.sh | 21 -- tools/build-keycloak.sh | 105 ------- tools/cli/databases/h2/change-database.cli | 9 - .../databases/h2/standalone-configuration.cli | 3 - .../h2/standalone-ha-configuration.cli | 3 - .../cli/databases/mariadb/change-database.cli | 9 - .../mariadb/standalone-configuration.cli | 3 - .../mariadb/standalone-ha-configuration.cli | 3 - tools/cli/databases/mssql/change-database.cli | 11 - .../mssql/standalone-configuration.cli | 3 - .../mssql/standalone-ha-configuration.cli | 3 - tools/cli/databases/mysql/change-database.cli | 9 - .../mysql/standalone-configuration.cli | 3 - .../mysql/standalone-ha-configuration.cli | 3 - .../cli/databases/oracle/change-database.cli | 9 - .../oracle/standalone-configuration.cli | 3 - .../oracle/standalone-ha-configuration.cli | 3 - .../databases/postgres/change-database.cli | 11 - .../postgres/standalone-configuration.cli | 3 - .../postgres/standalone-ha-configuration.cli | 3 - tools/cli/files-plaintext-vault.cli | 7 - tools/cli/hostname.cli | 2 - tools/cli/infinispan/cache-owners.cli | 11 - tools/cli/jgroups/discovery/JDBC_PING.cli | 11 - tools/cli/jgroups/discovery/default.cli | 11 - tools/cli/loglevel.cli | 9 - tools/cli/metrics/db.cli | 5 - tools/cli/metrics/http.cli | 5 - tools/cli/metrics/jgroups.cli | 5 - tools/cli/proxy.cli | 2 - tools/cli/standalone-configuration.cli | 6 - tools/cli/standalone-ha-configuration.cli | 6 - tools/cli/theme.cli | 2 - tools/cli/x509-keystore.cli | 9 - tools/cli/x509-truststore.cli | 25 -- tools/databases/change-database.sh | 11 - tools/databases/mariadb/module.xml | 31 --- tools/databases/mssql/module.xml | 13 - tools/databases/mysql/module.xml | 31 --- tools/databases/oracle/module.xml | 31 --- tools/databases/postgres/module.xml | 31 --- tools/docker-entrypoint.sh | 257 ------------------ tools/infinispan.sh | 14 - tools/jgroups.sh | 30 -- tools/statistics.sh | 12 - tools/vault.sh | 13 - tools/x509.sh | 115 -------- 100 files changed, 213 insertions(+), 1294 deletions(-) delete mode 100644 module.xml delete mode 100644 scripts/disable-theme-cache.cli delete mode 100644 src/main/java/org/openremote/keycloak/theme/CustomThemeProvider.java delete mode 100644 src/main/java/org/openremote/keycloak/theme/CustomThemeProviderFactory.java create mode 100644 src/main/resources/META-INF/keycloak-themes.json delete mode 100644 src/main/resources/META-INF/services/org.keycloak.theme.ThemeProviderFactory rename {themes => src/main/resources/theme}/openremote/account/account.ftl (100%) rename {themes => src/main/resources/theme}/openremote/account/applications.ftl (100%) rename {themes => src/main/resources/theme}/openremote/account/federatedIdentity.ftl (100%) rename {themes => src/main/resources/theme}/openremote/account/log.ftl (100%) rename {themes => src/main/resources/theme}/openremote/account/password.ftl (100%) rename {themes => src/main/resources/theme}/openremote/account/resources/css/MaterialIcons-Regular.eot (100%) rename {themes => src/main/resources/theme}/openremote/account/resources/css/MaterialIcons-Regular.ijmap (100%) rename {themes => src/main/resources/theme}/openremote/account/resources/css/MaterialIcons-Regular.svg (100%) rename {themes => src/main/resources/theme}/openremote/account/resources/css/MaterialIcons-Regular.ttf (100%) rename {themes => src/main/resources/theme}/openremote/account/resources/css/MaterialIcons-Regular.woff (100%) rename {themes => src/main/resources/theme}/openremote/account/resources/css/MaterialIcons-Regular.woff2 (100%) rename {themes => src/main/resources/theme}/openremote/account/resources/css/materialize.min.css (100%) rename {themes => src/main/resources/theme}/openremote/account/resources/css/styles.css (100%) rename {themes => src/main/resources/theme}/openremote/account/resources/img/favicon.png (100%) rename {themes => src/main/resources/theme}/openremote/account/resources/js/materialize.min.js (100%) rename {themes => src/main/resources/theme}/openremote/account/sessions.ftl (100%) rename {themes => src/main/resources/theme}/openremote/account/template.ftl (100%) rename {themes => src/main/resources/theme}/openremote/account/theme.properties (100%) rename {themes => src/main/resources/theme}/openremote/account/totp.ftl (100%) rename {themes => src/main/resources/theme}/openremote/email/html/password-reset.ftl (100%) rename {themes => src/main/resources/theme}/openremote/email/theme.properties (100%) rename {themes => src/main/resources/theme}/openremote/login/error.ftl (100%) rename {themes => src/main/resources/theme}/openremote/login/login-reset-password.ftl (57%) create mode 100644 src/main/resources/theme/openremote/login/login-update-password.ftl rename {themes => src/main/resources/theme}/openremote/login/login.ftl (76%) rename {themes => src/main/resources/theme}/openremote/login/messages/messages_en.properties (100%) rename {themes => src/main/resources/theme}/openremote/login/register.ftl (100%) rename {themes => src/main/resources/theme}/openremote/login/resources/css/MaterialIcons-Regular.eot (100%) rename {themes => src/main/resources/theme}/openremote/login/resources/css/MaterialIcons-Regular.ijmap (100%) rename {themes => src/main/resources/theme}/openremote/login/resources/css/MaterialIcons-Regular.svg (100%) rename {themes => src/main/resources/theme}/openremote/login/resources/css/MaterialIcons-Regular.ttf (100%) rename {themes => src/main/resources/theme}/openremote/login/resources/css/MaterialIcons-Regular.woff (100%) rename {themes => src/main/resources/theme}/openremote/login/resources/css/MaterialIcons-Regular.woff2 (100%) rename {themes => src/main/resources/theme}/openremote/login/resources/css/materialize.min.css (100%) rename {themes => src/main/resources/theme}/openremote/login/resources/css/styles.css (100%) rename {themes => src/main/resources/theme}/openremote/login/resources/img/favicon.png (100%) rename {themes => src/main/resources/theme}/openremote/login/resources/js/materialize.min.js (100%) rename {themes => src/main/resources/theme}/openremote/login/template.ftl (90%) rename {themes => src/main/resources/theme}/openremote/login/theme.properties (100%) delete mode 100644 themes/openremote/login/login-update-password.ftl delete mode 100644 tools/autorun.sh delete mode 100644 tools/build-keycloak.sh delete mode 100644 tools/cli/databases/h2/change-database.cli delete mode 100644 tools/cli/databases/h2/standalone-configuration.cli delete mode 100644 tools/cli/databases/h2/standalone-ha-configuration.cli delete mode 100644 tools/cli/databases/mariadb/change-database.cli delete mode 100644 tools/cli/databases/mariadb/standalone-configuration.cli delete mode 100644 tools/cli/databases/mariadb/standalone-ha-configuration.cli delete mode 100644 tools/cli/databases/mssql/change-database.cli delete mode 100644 tools/cli/databases/mssql/standalone-configuration.cli delete mode 100644 tools/cli/databases/mssql/standalone-ha-configuration.cli delete mode 100644 tools/cli/databases/mysql/change-database.cli delete mode 100644 tools/cli/databases/mysql/standalone-configuration.cli delete mode 100644 tools/cli/databases/mysql/standalone-ha-configuration.cli delete mode 100644 tools/cli/databases/oracle/change-database.cli delete mode 100644 tools/cli/databases/oracle/standalone-configuration.cli delete mode 100644 tools/cli/databases/oracle/standalone-ha-configuration.cli delete mode 100644 tools/cli/databases/postgres/change-database.cli delete mode 100644 tools/cli/databases/postgres/standalone-configuration.cli delete mode 100644 tools/cli/databases/postgres/standalone-ha-configuration.cli delete mode 100644 tools/cli/files-plaintext-vault.cli delete mode 100644 tools/cli/hostname.cli delete mode 100644 tools/cli/infinispan/cache-owners.cli delete mode 100644 tools/cli/jgroups/discovery/JDBC_PING.cli delete mode 100644 tools/cli/jgroups/discovery/default.cli delete mode 100644 tools/cli/loglevel.cli delete mode 100644 tools/cli/metrics/db.cli delete mode 100644 tools/cli/metrics/http.cli delete mode 100644 tools/cli/metrics/jgroups.cli delete mode 100644 tools/cli/proxy.cli delete mode 100644 tools/cli/standalone-configuration.cli delete mode 100644 tools/cli/standalone-ha-configuration.cli delete mode 100644 tools/cli/theme.cli delete mode 100644 tools/cli/x509-keystore.cli delete mode 100644 tools/cli/x509-truststore.cli delete mode 100644 tools/databases/change-database.sh delete mode 100644 tools/databases/mariadb/module.xml delete mode 100644 tools/databases/mssql/module.xml delete mode 100644 tools/databases/mysql/module.xml delete mode 100644 tools/databases/oracle/module.xml delete mode 100644 tools/databases/postgres/module.xml delete mode 100644 tools/docker-entrypoint.sh delete mode 100644 tools/infinispan.sh delete mode 100644 tools/jgroups.sh delete mode 100644 tools/statistics.sh delete mode 100644 tools/vault.sh delete mode 100644 tools/x509.sh diff --git a/Dockerfile b/Dockerfile index 85d6784..028b05c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,68 +1,62 @@ # ------------------------------------------------------------------------------------ -# Keycloak image built for aarch64 and also adds a custom provider for resolving -# themes that fallsback to the default openremote theme rather than just breaking. -# See this issue for aarch64 support: -# -# https://github.com/keycloak/keycloak-containers/issues/341 +# Keycloak image built for postgresql support with theme handling customisation +# to always fallback to standard openremote theme. # ------------------------------------------------------------------------------------ -FROM registry.access.redhat.com/ubi8/openjdk-11-runtime +ARG VERSION=18.0.2 +FROM quay.io/keycloak/keycloak:${VERSION} as builder MAINTAINER support@openremote.io # Add git commit label must be specified at build time using --build-arg GIT_COMMIT=dadadadadad ARG GIT_COMMIT=unknown LABEL git-commit=$GIT_COMMIT -ENV KEYCLOAK_VERSION 16.1.1 -ENV JDBC_POSTGRES_VERSION 42.2.5 -ENV JDBC_MYSQL_VERSION 8.0.22 -ENV JDBC_MARIADB_VERSION 2.5.4 -ENV JDBC_MSSQL_VERSION 8.2.2.jre11 +# Configure build options +ENV KC_HEALTH_ENABLED=true +ENV KC_METRICS_ENABLED=true +ENV KC_FEATURES=token-exchange +ENV KC_DB=postgres +ENV KC_HTTP_RELATIVE_PATH=/auth -ENV LAUNCH_JBOSS_IN_BACKGROUND 1 -ENV JBOSS_HOME /opt/jboss/keycloak -ENV LANG en_US.UTF-8 +# Install openremote theme +ADD build/image/openremote-theme.jar /opt/keycloak/providers -ENV DB_VENDOR ${DB_VENDOR:-postgres} -ENV DB_ADDR ${DB_ADDR:-postgresql} -ENV DB_PORT ${DB_PORT:-5432} -ENV DB_DATABASE ${DB_DATABASE:-openremote} -ENV DB_USER ${DB_USER:-postgres} -ENV DB_PASSWORD ${DB_PASSWORD:-postgres} -ENV DB_SCHEMA ${DB_SCHEMA:-public} -ENV KEYCLOAK_USER ${KEYCLOAK_USER:-admin} -ENV KEYCLOAK_PASSWORD ${SETUP_ADMIN_PASSWORD:-secret} -ENV PROXY_ADDRESS_FORWARDING ${PROXY_ADDRESS_FORWARDING:-true} -ENV HTTP_ENABLED ${HTTP_ENABLED:-true} -ENV HTTPS_ENABLED ${HTTPS_ENABLED:-false} -ENV KEYCLOAK_FRONTEND_URL ${KEYCLOAK_FRONTEND_URL:-} -ENV TZ ${TZ:-Europe/Amsterdam} +# Install keycloak metrics provider +RUN curl -sL https://github.com/aerogear/keycloak-metrics-spi/releases/download/2.5.3/keycloak-metrics-spi-2.5.3.jar -o /opt/keycloak/providers/keycloak-metrics-spi-2.5.3.jar -ARG GIT_REPO -ARG GIT_BRANCH -ARG KEYCLOAK_DIST=https://github.com/keycloak/keycloak/releases/download/$KEYCLOAK_VERSION/keycloak-$KEYCLOAK_VERSION.tar.gz +# Build custom image and copy into this new image +RUN /opt/keycloak/bin/kc.sh build +FROM quay.io/keycloak/keycloak:${VERSION} +COPY --from=builder /opt/keycloak/ /opt/keycloak/ -USER root - -RUN chown jboss:jboss /home/jboss -RUN microdnf update -y && microdnf install -y glibc-langpack-en gzip hostname openssl tar which && microdnf clean all - -ADD tools /opt/jboss/tools -RUN chmod -R +x /opt/jboss/tools -RUN /opt/jboss/tools/build-keycloak.sh - -RUN mkdir -p /opt/jboss/keycloak/providers +# Create standard deployment path and symlink themes (cannot --spi-theme-dir=/deployment/keycloak/themes) +USER 0 +RUN rm -r /opt/keycloak/themes RUN mkdir -p /deployment/keycloak/themes -ADD themes /opt/jboss/keycloak/themes -ADD module.xml /opt/jboss/keycloak/providers -ADD build/image/openremote-keycloak.jar /opt/jboss/keycloak/providers +RUN ln -s /deployment/keycloak/themes /opt/keycloak +USER 1000 -HEALTHCHECK --interval=3s --timeout=3s --start-period=30s --retries=120 CMD curl --fail --silent http://localhost:8080/auth || exit 1 +WORKDIR /opt/keycloak + +# Configure runtime options +ENV TZ=Europe/Amsterdam +ENV KC_DB_URL_HOST=postgresql +ENV KC_DB_URL_PORT=5432 +ENV KC_DB_URL_DATABASE=openremote +ENV KC_DB_SCHEMA=public +ENV KC_DB_USERNAME=postgres +ENV KC_DB_PASSWORD=postgres +ENV KC_HOSTNAME=localhost +ENV KC_PROXY=edge +ENV KEYCLOAK_ADMIN=admin +ENV KEYCLOAK_ADMIN_PASSWORD=secret +ENV KC_LOG_LEVEL=info +ENV KEYCLOAK_DEFAULT_THEME=openremote +ENV KEYCLOAK_ACCOUNT_THEME=openremote +ENV KEYCLOAK_WELCOME_THEME=keycloak +ENV KEYCLOAK_START_COMMAND=start -USER 1000 +HEALTHCHECK --interval=3s --timeout=3s --start-period=30s --retries=120 CMD curl --fail --silent http://localhost:8080/auth || exit 1 EXPOSE 8080 -EXPOSE 8443 - -ENTRYPOINT [ "/opt/jboss/tools/docker-entrypoint.sh" ] -CMD ["-b", "0.0.0.0"] +ENTRYPOINT /opt/keycloak/bin/kc.sh ${KEYCLOAK_START_COMMAND:-start} --spi-theme-default=${KEYCLOAK_DEFAULT_THEME:-openremote} --spi-theme-account-theme=${KEYCLOAK_ACCOUNT_THEME:-openremote} --spi-theme-welcome-theme=${KEYCLOAK_WELCOME_THEME:-keycloak} ${KEYCLOAK_START_OPTS:-} diff --git a/README.md b/README.md index 1eac7e5..2c95d7a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,19 @@ -# keycloak +# Keycloak [![Docker Image](https://github.com/openremote/keycloak/actions/workflows/keycloak.yml/badge.svg)](https://github.com/openremote/keycloak/actions/workflows/keycloak.yml) -Keycloak docker image with openremote theme and env variables that supports `amd64` and `arm64`. +Keycloak docker image built for `postgres` with openremote theme embedded and set as default and also sets the request path to `/auth` (like older versions of Keycloak to simplify usage behind a reverse proxy). -This image doesn't use the `jboss/keycloak` image as a base as at the time of writing this it doesn't support `arm64`, a feature request has been created on the `keycloak` issue tracker: +## Working on the OpenRemote theme +The openremote theme template files are located in `src/main/resources/theme/openremote`; to work on the OpenRemote theme use: -https://issues.redhat.com/browse/KEYCLOAK-17359 +```shell +docker run --rm -p 8081:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=secret -e KEYCLOAK_DEFAULT_THEME=dev -e KC_HOSTNAME_PORT=8081 -e KEYCLOAK_START_COMMAND=start-dev -e KEYCLOAK_START_OPTS="--spi-theme-static-max-age=-1 --spi-theme-cache-themes=false --spi-theme-cache-templates=false" --mount type=bind,src=$PWD/src/main/resources/theme/openremote,dst=/deployment/keycloak/themes/dev openremote/keycloak:latest +``` +Then access http://localhost:8081/ and any changes made to the template files can be reloaded in realtime by just refreshing the window. +To get the standard themes for reference use the following (replace `${VERSION}` with actual keycloak version used): +```shell +docker cp ID:/opt/keycloak/lib/lib/main/org.keycloak.keycloak-themes-${VERSION}.jar ./ +``` diff --git a/build.gradle b/build.gradle index 4355a52..75bca9d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,19 +1,8 @@ apply plugin: "java" version = "" -repositories { - mavenCentral() -} - -dependencies { - compile "org.keycloak:keycloak-core:12.0.1" - compile "org.keycloak:keycloak-services:12.0.1" - compile "org.keycloak:keycloak-server-spi:12.0.1" - compile "org.keycloak:keycloak-server-spi-private:12.0.1" -} - jar { - archivesBaseName = "openremote-${project.name}" + archivesBaseName = "openremote-theme" } task installDist(type: Copy) { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..457aad0d98108420a977756b7145c93c8910b076 100644 GIT binary patch delta 29482 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 Xl$U}8nGXl$9Q3CH>hr|=o}d07!s7Wl delta 32671 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 442d913..84d1f85 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0..af6708f 100644 --- a/gradlew +++ b/gradlew @@ -1,21 +1,5 @@ #!/usr/bin/env sh -# -# Copyright 2015 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - ############################################################################## ## ## Gradle start up script for UN*X @@ -44,7 +28,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +DEFAULT_JVM_OPTS='"-Xmx64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -82,7 +66,6 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -126,11 +109,10 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -156,19 +138,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then else eval `echo args$i`="\"$arg\"" fi - i=`expr $i + 1` + i=$((i+1)) done case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -177,9 +159,14 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=`save "$@"` +APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 107acd3..6d57edc 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,19 +1,3 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -29,18 +13,15 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" +set DEFAULT_JVM_OPTS="-Xmx64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +35,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto execute +if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,14 +45,28 @@ echo location of your Java installation. goto fail +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell diff --git a/module.xml b/module.xml deleted file mode 100644 index 7107dfa..0000000 --- a/module.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/scripts/disable-theme-cache.cli b/scripts/disable-theme-cache.cli deleted file mode 100644 index fde02b1..0000000 --- a/scripts/disable-theme-cache.cli +++ /dev/null @@ -1,5 +0,0 @@ -embed-server --std-out=echo --server-config=standalone-ha.xml -/subsystem=keycloak-server/theme=defaults/:write-attribute(name=cacheThemes,value=false) -/subsystem=keycloak-server/theme=defaults/:write-attribute(name=cacheTemplates,value=false) -/subsystem=keycloak-server/theme=defaults/:write-attribute(name=staticMaxAge,value=-1) -stop-embedded-server diff --git a/src/main/java/org/openremote/keycloak/theme/CustomThemeProvider.java b/src/main/java/org/openremote/keycloak/theme/CustomThemeProvider.java deleted file mode 100644 index bd39322..0000000 --- a/src/main/java/org/openremote/keycloak/theme/CustomThemeProvider.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2021, OpenRemote Inc. - * - * See the CONTRIBUTORS.txt file in the distribution for a - * full listing of individual contributors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package org.openremote.keycloak.theme; - -import org.keycloak.theme.FolderThemeProvider; -import org.keycloak.theme.Theme; - -import java.io.File; -import java.io.IOException; - -/** - * This theme provider will fallback to the openremote theme in the fallback dir if the custom theme cannot be found - */ -public class CustomThemeProvider extends FolderThemeProvider { - - FolderThemeProvider fallbackProvider = null; - - public CustomThemeProvider(File themesDir) { - super(themesDir); - } - - @Override - public int getProviderPriority() { - return super.getProviderPriority() - 10; - } - - @Override - public Theme getTheme(String name, Theme.Type type) throws IOException { - if (!super.hasTheme(name, type) && fallbackProvider != null) { - return fallbackProvider.getTheme("openremote", type); - } - - return super.getTheme(name, type); - } - - @Override - public boolean hasTheme(String name, Theme.Type type) { - if (!super.hasTheme(name, type) && fallbackProvider != null) { - return fallbackProvider.hasTheme("openremote", type); - } - - return true; - } -} diff --git a/src/main/java/org/openremote/keycloak/theme/CustomThemeProviderFactory.java b/src/main/java/org/openremote/keycloak/theme/CustomThemeProviderFactory.java deleted file mode 100644 index 29dcb02..0000000 --- a/src/main/java/org/openremote/keycloak/theme/CustomThemeProviderFactory.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2021, OpenRemote Inc. - * - * See the CONTRIBUTORS.txt file in the distribution for a - * full listing of individual contributors. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package org.openremote.keycloak.theme; - -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.theme.FolderThemeProvider; -import org.keycloak.theme.ThemeProvider; -import org.keycloak.theme.ThemeProviderFactory; - -import java.io.File; - -/** - * A theme provider to load custom themes from the deployment directory, the openremote theme is baked - * into the standard theme directory. - */ -public class CustomThemeProviderFactory implements ThemeProviderFactory { - - protected CustomThemeProvider themeProvider; - - @Override - public ThemeProvider create(KeycloakSession session) { - if (themeProvider.fallbackProvider == null) { - themeProvider.fallbackProvider = (FolderThemeProvider) session.getProvider(ThemeProvider.class, "folder"); - } - return themeProvider; - } - - @Override - public void init(Config.Scope config) { - File rootDir = new File("/deployment/keycloak/themes"); - themeProvider = new CustomThemeProvider(rootDir); - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - - @Override - public void close() { - } - - @Override - public String getId() { - return "openremote-custom-folder"; - } -} diff --git a/src/main/resources/META-INF/keycloak-themes.json b/src/main/resources/META-INF/keycloak-themes.json new file mode 100644 index 0000000..3d4887b --- /dev/null +++ b/src/main/resources/META-INF/keycloak-themes.json @@ -0,0 +1,6 @@ +{ + "themes": [{ + "name" : "openremote", + "types": [ "login", "email", "account" ] + }] +} diff --git a/src/main/resources/META-INF/services/org.keycloak.theme.ThemeProviderFactory b/src/main/resources/META-INF/services/org.keycloak.theme.ThemeProviderFactory deleted file mode 100644 index 52f404d..0000000 --- a/src/main/resources/META-INF/services/org.keycloak.theme.ThemeProviderFactory +++ /dev/null @@ -1 +0,0 @@ -org.openremote.keycloak.theme.CustomThemeProviderFactory diff --git a/themes/openremote/account/account.ftl b/src/main/resources/theme/openremote/account/account.ftl similarity index 100% rename from themes/openremote/account/account.ftl rename to src/main/resources/theme/openremote/account/account.ftl diff --git a/themes/openremote/account/applications.ftl b/src/main/resources/theme/openremote/account/applications.ftl similarity index 100% rename from themes/openremote/account/applications.ftl rename to src/main/resources/theme/openremote/account/applications.ftl diff --git a/themes/openremote/account/federatedIdentity.ftl b/src/main/resources/theme/openremote/account/federatedIdentity.ftl similarity index 100% rename from themes/openremote/account/federatedIdentity.ftl rename to src/main/resources/theme/openremote/account/federatedIdentity.ftl diff --git a/themes/openremote/account/log.ftl b/src/main/resources/theme/openremote/account/log.ftl similarity index 100% rename from themes/openremote/account/log.ftl rename to src/main/resources/theme/openremote/account/log.ftl diff --git a/themes/openremote/account/password.ftl b/src/main/resources/theme/openremote/account/password.ftl similarity index 100% rename from themes/openremote/account/password.ftl rename to src/main/resources/theme/openremote/account/password.ftl diff --git a/themes/openremote/account/resources/css/MaterialIcons-Regular.eot b/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.eot similarity index 100% rename from themes/openremote/account/resources/css/MaterialIcons-Regular.eot rename to src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.eot diff --git a/themes/openremote/account/resources/css/MaterialIcons-Regular.ijmap b/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.ijmap similarity index 100% rename from themes/openremote/account/resources/css/MaterialIcons-Regular.ijmap rename to src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.ijmap diff --git a/themes/openremote/account/resources/css/MaterialIcons-Regular.svg b/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.svg similarity index 100% rename from themes/openremote/account/resources/css/MaterialIcons-Regular.svg rename to src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.svg diff --git a/themes/openremote/account/resources/css/MaterialIcons-Regular.ttf b/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.ttf similarity index 100% rename from themes/openremote/account/resources/css/MaterialIcons-Regular.ttf rename to src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.ttf diff --git a/themes/openremote/account/resources/css/MaterialIcons-Regular.woff b/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.woff similarity index 100% rename from themes/openremote/account/resources/css/MaterialIcons-Regular.woff rename to src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.woff diff --git a/themes/openremote/account/resources/css/MaterialIcons-Regular.woff2 b/src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.woff2 similarity index 100% rename from themes/openremote/account/resources/css/MaterialIcons-Regular.woff2 rename to src/main/resources/theme/openremote/account/resources/css/MaterialIcons-Regular.woff2 diff --git a/themes/openremote/account/resources/css/materialize.min.css b/src/main/resources/theme/openremote/account/resources/css/materialize.min.css similarity index 100% rename from themes/openremote/account/resources/css/materialize.min.css rename to src/main/resources/theme/openremote/account/resources/css/materialize.min.css diff --git a/themes/openremote/account/resources/css/styles.css b/src/main/resources/theme/openremote/account/resources/css/styles.css similarity index 100% rename from themes/openremote/account/resources/css/styles.css rename to src/main/resources/theme/openremote/account/resources/css/styles.css diff --git a/themes/openremote/account/resources/img/favicon.png b/src/main/resources/theme/openremote/account/resources/img/favicon.png similarity index 100% rename from themes/openremote/account/resources/img/favicon.png rename to src/main/resources/theme/openremote/account/resources/img/favicon.png diff --git a/themes/openremote/account/resources/js/materialize.min.js b/src/main/resources/theme/openremote/account/resources/js/materialize.min.js similarity index 100% rename from themes/openremote/account/resources/js/materialize.min.js rename to src/main/resources/theme/openremote/account/resources/js/materialize.min.js diff --git a/themes/openremote/account/sessions.ftl b/src/main/resources/theme/openremote/account/sessions.ftl similarity index 100% rename from themes/openremote/account/sessions.ftl rename to src/main/resources/theme/openremote/account/sessions.ftl diff --git a/themes/openremote/account/template.ftl b/src/main/resources/theme/openremote/account/template.ftl similarity index 100% rename from themes/openremote/account/template.ftl rename to src/main/resources/theme/openremote/account/template.ftl diff --git a/themes/openremote/account/theme.properties b/src/main/resources/theme/openremote/account/theme.properties similarity index 100% rename from themes/openremote/account/theme.properties rename to src/main/resources/theme/openremote/account/theme.properties diff --git a/themes/openremote/account/totp.ftl b/src/main/resources/theme/openremote/account/totp.ftl similarity index 100% rename from themes/openremote/account/totp.ftl rename to src/main/resources/theme/openremote/account/totp.ftl diff --git a/themes/openremote/email/html/password-reset.ftl b/src/main/resources/theme/openremote/email/html/password-reset.ftl similarity index 100% rename from themes/openremote/email/html/password-reset.ftl rename to src/main/resources/theme/openremote/email/html/password-reset.ftl diff --git a/themes/openremote/email/theme.properties b/src/main/resources/theme/openremote/email/theme.properties similarity index 100% rename from themes/openremote/email/theme.properties rename to src/main/resources/theme/openremote/email/theme.properties diff --git a/themes/openremote/login/error.ftl b/src/main/resources/theme/openremote/login/error.ftl similarity index 100% rename from themes/openremote/login/error.ftl rename to src/main/resources/theme/openremote/login/error.ftl diff --git a/themes/openremote/login/login-reset-password.ftl b/src/main/resources/theme/openremote/login/login-reset-password.ftl similarity index 57% rename from themes/openremote/login/login-reset-password.ftl rename to src/main/resources/theme/openremote/login/login-reset-password.ftl index 01b1c97..fcb1581 100644 --- a/themes/openremote/login/login-reset-password.ftl +++ b/src/main/resources/theme/openremote/login/login-reset-password.ftl @@ -9,29 +9,27 @@
<#if auth?has_content && auth.showUsername()> - + <#else> - + + <#if messagesPerField.existsError('username')> - - ${kcSanitize(messagesPerField.get('username'))?no_esc} - + - -
+
+
+ +
-
- -
- - <#elseif section = "info" > diff --git a/src/main/resources/theme/openremote/login/login-update-password.ftl b/src/main/resources/theme/openremote/login/login-update-password.ftl new file mode 100644 index 0000000..7c1ceec --- /dev/null +++ b/src/main/resources/theme/openremote/login/login-update-password.ftl @@ -0,0 +1,72 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('password','password-confirm'); section> + <#if section = "header"> + ${msg("updatePasswordTitle")} + <#elseif section = "form"> +
+
+ + + +
+ + + <#if messagesPerField.existsError('password-confirm','password-confirm')> + + +
+ +
+ + + <#if messagesPerField.existsError('password-confirm','password-confirm')> + + +
+
+ +
+
+
+
+ <#if isAppInitiatedAction??> +
+ +
+ +
+
+ +
+ <#if isAppInitiatedAction??> + + + + + + <#else> + + +
+
+
+
+ + diff --git a/themes/openremote/login/login.ftl b/src/main/resources/theme/openremote/login/login.ftl similarity index 76% rename from themes/openremote/login/login.ftl rename to src/main/resources/theme/openremote/login/login.ftl index 6eb060d..7a47532 100644 --- a/themes/openremote/login/login.ftl +++ b/src/main/resources/theme/openremote/login/login.ftl @@ -3,7 +3,7 @@ <#if section = "title"> ${msg("loginTitle",(realm.displayName!''))} <#elseif section = "header"> - ${msg("loginTitleHtml",(realm.displayNameHtml!''))} + ${kcSanitize(msg("loginTitleHtml",(realm.displayNameHtml!'')))?no_esc} <#elseif section = "form"> <#if realm.password>
@@ -18,18 +18,33 @@ <#else> + <#if messagesPerField.existsError('username','password')> + +
- + + <#if messagesPerField.existsError('username','password')> + +
<#if realm.rememberMe && !usernameEditDisabled??> diff --git a/themes/openremote/login/messages/messages_en.properties b/src/main/resources/theme/openremote/login/messages/messages_en.properties similarity index 100% rename from themes/openremote/login/messages/messages_en.properties rename to src/main/resources/theme/openremote/login/messages/messages_en.properties diff --git a/themes/openremote/login/register.ftl b/src/main/resources/theme/openremote/login/register.ftl similarity index 100% rename from themes/openremote/login/register.ftl rename to src/main/resources/theme/openremote/login/register.ftl diff --git a/themes/openremote/login/resources/css/MaterialIcons-Regular.eot b/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.eot similarity index 100% rename from themes/openremote/login/resources/css/MaterialIcons-Regular.eot rename to src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.eot diff --git a/themes/openremote/login/resources/css/MaterialIcons-Regular.ijmap b/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.ijmap similarity index 100% rename from themes/openremote/login/resources/css/MaterialIcons-Regular.ijmap rename to src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.ijmap diff --git a/themes/openremote/login/resources/css/MaterialIcons-Regular.svg b/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.svg similarity index 100% rename from themes/openremote/login/resources/css/MaterialIcons-Regular.svg rename to src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.svg diff --git a/themes/openremote/login/resources/css/MaterialIcons-Regular.ttf b/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.ttf similarity index 100% rename from themes/openremote/login/resources/css/MaterialIcons-Regular.ttf rename to src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.ttf diff --git a/themes/openremote/login/resources/css/MaterialIcons-Regular.woff b/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.woff similarity index 100% rename from themes/openremote/login/resources/css/MaterialIcons-Regular.woff rename to src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.woff diff --git a/themes/openremote/login/resources/css/MaterialIcons-Regular.woff2 b/src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.woff2 similarity index 100% rename from themes/openremote/login/resources/css/MaterialIcons-Regular.woff2 rename to src/main/resources/theme/openremote/login/resources/css/MaterialIcons-Regular.woff2 diff --git a/themes/openremote/login/resources/css/materialize.min.css b/src/main/resources/theme/openremote/login/resources/css/materialize.min.css similarity index 100% rename from themes/openremote/login/resources/css/materialize.min.css rename to src/main/resources/theme/openremote/login/resources/css/materialize.min.css diff --git a/themes/openremote/login/resources/css/styles.css b/src/main/resources/theme/openremote/login/resources/css/styles.css similarity index 100% rename from themes/openremote/login/resources/css/styles.css rename to src/main/resources/theme/openremote/login/resources/css/styles.css diff --git a/themes/openremote/login/resources/img/favicon.png b/src/main/resources/theme/openremote/login/resources/img/favicon.png similarity index 100% rename from themes/openremote/login/resources/img/favicon.png rename to src/main/resources/theme/openremote/login/resources/img/favicon.png diff --git a/themes/openremote/login/resources/js/materialize.min.js b/src/main/resources/theme/openremote/login/resources/js/materialize.min.js similarity index 100% rename from themes/openremote/login/resources/js/materialize.min.js rename to src/main/resources/theme/openremote/login/resources/js/materialize.min.js diff --git a/themes/openremote/login/template.ftl b/src/main/resources/theme/openremote/login/template.ftl similarity index 90% rename from themes/openremote/login/template.ftl rename to src/main/resources/theme/openremote/login/template.ftl index 3b76c06..5bd2e06 100644 --- a/themes/openremote/login/template.ftl +++ b/src/main/resources/theme/openremote/login/template.ftl @@ -1,9 +1,8 @@ -<#macro registrationLayout displayInfo=false displayMessage=true> +<#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true displayRequiredFields=false> - + - ${msg("applicationName")} - + @@ -13,11 +12,11 @@ - + ${msg("applicationName")} - + @@ -48,7 +47,7 @@ - <#if realm.internationalizationEnabled> + <#if realm.internationalizationEnabled && locale.supported?size gt 1>
${locale.current} @@ -61,7 +60,7 @@
- <#if displayMessage && message?has_content> + <#if displayMessage && message?has_content && (message.type != 'warning' || !isAppInitiatedAction??)>
<#if message.type=='success' >check_circle
- diff --git a/themes/openremote/login/theme.properties b/src/main/resources/theme/openremote/login/theme.properties similarity index 100% rename from themes/openremote/login/theme.properties rename to src/main/resources/theme/openremote/login/theme.properties diff --git a/themes/openremote/login/login-update-password.ftl b/themes/openremote/login/login-update-password.ftl deleted file mode 100644 index 382feab..0000000 --- a/themes/openremote/login/login-update-password.ftl +++ /dev/null @@ -1,75 +0,0 @@ -<#import "template.ftl" as layout> -<@layout.registrationLayout displayMessage=!messagesPerField.existsError('password','password-confirm'); section> - <#if section = "header"> - ${msg("updatePasswordTitle")} - <#elseif section = "form"> - - - - -
-
- -
-
- - - <#if messagesPerField.existsError('password')> - - ${kcSanitize(messagesPerField.get('password'))?no_esc} - - -
-
- -
-
- -
-
- - - <#if messagesPerField.existsError('password-confirm')> - - ${kcSanitize(messagesPerField.get('password-confirm'))?no_esc} - - - -
-
- -
-
-
- <#if isAppInitiatedAction??> -
- -
- -
-
- -
- <#if isAppInitiatedAction??> - - - <#else> - - -
-
- - - \ No newline at end of file diff --git a/tools/autorun.sh b/tools/autorun.sh deleted file mode 100644 index c3282b1..0000000 --- a/tools/autorun.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -e -cd /opt/jboss/keycloak - -ENTRYPOINT_DIR=/opt/jboss/startup-scripts - -if [[ -d "$ENTRYPOINT_DIR" ]]; then - # First run cli autoruns - for f in "$ENTRYPOINT_DIR"/*; do - if [[ "$f" == *.cli ]]; then - echo "Executing cli script: $f" - bin/jboss-cli.sh --file="$f" - elif [[ -d "$f" ]]; then - echo "Skipping execution of directory: $f" - elif [[ -x "$f" ]]; then - echo "Executing: $f" - "$f" - else - echo "Ignoring file in $ENTRYPOINT_DIR (not *.cli or executable): $f" - fi - done -fi diff --git a/tools/build-keycloak.sh b/tools/build-keycloak.sh deleted file mode 100644 index a3598c5..0000000 --- a/tools/build-keycloak.sh +++ /dev/null @@ -1,105 +0,0 @@ -#!/bin/bash -e - -########################### -# Build/download Keycloak # -########################### - -if [ "$GIT_REPO" != "" ]; then - if [ "$GIT_BRANCH" == "" ]; then - GIT_BRANCH="main" - fi - - # Install Git - microdnf install -y git - - # Install Maven - cd /opt/jboss - curl -s https://apache.uib.no/maven/maven-3/3.5.4/binaries/apache-maven-3.5.4-bin.tar.gz | tar xz - mv apache-maven-3.5.4 /opt/jboss/maven - export M2_HOME=/opt/jboss/maven - - # Clone repository - git clone --depth 1 https://github.com/$GIT_REPO.git -b $GIT_BRANCH /opt/jboss/keycloak-source - - # Build - cd /opt/jboss/keycloak-source - - MAIN_HEAD=`git log -n1 --format="%H"` - echo "Keycloak from [build]: $GIT_REPO/$GIT_BRANCH/commit/$MAIN_HEAD" - - $M2_HOME/bin/mvn -Pdistribution -pl distribution/server-dist -am -Dmaven.test.skip clean install - - cd /opt/jboss - - tar xfz /opt/jboss/keycloak-source/distribution/server-dist/target/keycloak-*.tar.gz - - # Remove temporary files - rm -rf /opt/jboss/maven - rm -rf /opt/jboss/keycloak-source - rm -rf $HOME/.m2/repository - - mv /opt/jboss/keycloak-* /opt/jboss/keycloak -else - echo "Keycloak from [download]: $KEYCLOAK_DIST" - - cd /opt/jboss/ - curl -L $KEYCLOAK_DIST | tar zx - mv /opt/jboss/keycloak-* /opt/jboss/keycloak -fi - -##################### -# Create DB modules # -##################### - -mkdir -p /opt/jboss/keycloak/modules/system/layers/base/com/mysql/jdbc/main -cd /opt/jboss/keycloak/modules/system/layers/base/com/mysql/jdbc/main -curl -O https://repo1.maven.org/maven2/mysql/mysql-connector-java/$JDBC_MYSQL_VERSION/mysql-connector-java-$JDBC_MYSQL_VERSION.jar -cp /opt/jboss/tools/databases/mysql/module.xml . -sed "s/JDBC_MYSQL_VERSION/$JDBC_MYSQL_VERSION/" /opt/jboss/tools/databases/mysql/module.xml > module.xml - -mkdir -p /opt/jboss/keycloak/modules/system/layers/base/org/postgresql/jdbc/main -cd /opt/jboss/keycloak/modules/system/layers/base/org/postgresql/jdbc/main -curl -L https://repo1.maven.org/maven2/org/postgresql/postgresql/$JDBC_POSTGRES_VERSION/postgresql-$JDBC_POSTGRES_VERSION.jar > postgres-jdbc.jar -cp /opt/jboss/tools/databases/postgres/module.xml . - -mkdir -p /opt/jboss/keycloak/modules/system/layers/base/org/mariadb/jdbc/main -cd /opt/jboss/keycloak/modules/system/layers/base/org/mariadb/jdbc/main -curl -L https://repo1.maven.org/maven2/org/mariadb/jdbc/mariadb-java-client/$JDBC_MARIADB_VERSION/mariadb-java-client-$JDBC_MARIADB_VERSION.jar > mariadb-jdbc.jar -cp /opt/jboss/tools/databases/mariadb/module.xml . - -mkdir -p /opt/jboss/keycloak/modules/system/layers/base/com/oracle/jdbc/main -cd /opt/jboss/keycloak/modules/system/layers/base/com/oracle/jdbc/main -cp /opt/jboss/tools/databases/oracle/module.xml . - -mkdir -p /opt/jboss/keycloak/modules/system/layers/keycloak/com/microsoft/sqlserver/jdbc/main -cd /opt/jboss/keycloak/modules/system/layers/keycloak/com/microsoft/sqlserver/jdbc/main -curl -L https://repo1.maven.org/maven2/com/microsoft/sqlserver/mssql-jdbc/$JDBC_MSSQL_VERSION/mssql-jdbc-$JDBC_MSSQL_VERSION.jar > mssql-jdbc.jar -cp /opt/jboss/tools/databases/mssql/module.xml . - -###################### -# Configure Keycloak # -###################### - -cd /opt/jboss/keycloak - -bin/jboss-cli.sh --file=/opt/jboss/tools/cli/standalone-configuration.cli -rm -rf /opt/jboss/keycloak/standalone/configuration/standalone_xml_history - -bin/jboss-cli.sh --file=/opt/jboss/tools/cli/standalone-ha-configuration.cli -rm -rf /opt/jboss/keycloak/standalone/configuration/standalone_xml_history - -########### -# Garbage # -########### - -rm -rf /opt/jboss/keycloak/standalone/tmp/auth -rm -rf /opt/jboss/keycloak/domain/tmp/auth - -################### -# Set permissions # -################### - -echo "jboss:x:0:root" >> /etc/group -echo "jboss:x:1000:0:JBoss user:/opt/jboss:/sbin/nologin" >> /etc/passwd -chown -R jboss:root /opt/jboss -chmod -R g+rwX /opt/jboss \ No newline at end of file diff --git a/tools/cli/databases/h2/change-database.cli b/tools/cli/databases/h2/change-database.cli deleted file mode 100644 index 21809d7..0000000 --- a/tools/cli/databases/h2/change-database.cli +++ /dev/null @@ -1,9 +0,0 @@ -/subsystem=datasources/data-source=KeycloakDS: remove() -/subsystem=datasources/data-source=KeycloakDS: add(jndi-name=java:jboss/datasources/KeycloakDS,enabled=true,use-java-context=true,use-ccm=true, connection-url=jdbc:h2:tcp://${env.DB_ADDR:h2}:${env.DB_PORT:9092}/${env.DB_DATABASE:keycloak};schema\=${env.DB_SCHEMA:public}${env.JDBC_PARAMS:}, driver-name=h2tcp) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=user-name, value=${env.DB_USER:keycloak}) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=password, value=${env.DB_PASSWORD:password}) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=check-valid-connection-sql, value="SELECT 1") -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=background-validation, value=true) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=background-validation-millis, value=60000) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=flush-strategy, value=IdleConnections) -/subsystem=datasources/jdbc-driver=h2tcp:add(driver-name=h2tcp, driver-module-name=com.h2database.h2, driver-xa-datasource-class-name=org.h2.jdbcx.JdbcDataSource) diff --git a/tools/cli/databases/h2/standalone-configuration.cli b/tools/cli/databases/h2/standalone-configuration.cli deleted file mode 100644 index d47881c..0000000 --- a/tools/cli/databases/h2/standalone-configuration.cli +++ /dev/null @@ -1,3 +0,0 @@ -embed-server --server-config=standalone.xml --std-out=echo -run-batch --file=/opt/jboss/tools/cli/databases/h2/change-database.cli -stop-embedded-server diff --git a/tools/cli/databases/h2/standalone-ha-configuration.cli b/tools/cli/databases/h2/standalone-ha-configuration.cli deleted file mode 100644 index 55ea4e2..0000000 --- a/tools/cli/databases/h2/standalone-ha-configuration.cli +++ /dev/null @@ -1,3 +0,0 @@ -embed-server --server-config=standalone-ha.xml --std-out=echo -run-batch --file=/opt/jboss/tools/cli/databases/h2/change-database.cli -stop-embedded-server diff --git a/tools/cli/databases/mariadb/change-database.cli b/tools/cli/databases/mariadb/change-database.cli deleted file mode 100644 index 2f099f2..0000000 --- a/tools/cli/databases/mariadb/change-database.cli +++ /dev/null @@ -1,9 +0,0 @@ -/subsystem=datasources/data-source=KeycloakDS: remove() -/subsystem=datasources/data-source=KeycloakDS: add(jndi-name=java:jboss/datasources/KeycloakDS,enabled=true,use-java-context=true,use-ccm=true, connection-url=jdbc:mariadb://${env.DB_ADDR:mariadb}:${env.DB_PORT:3306}/${env.DB_DATABASE:keycloak}${env.JDBC_PARAMS:}, driver-name=mariadb) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=user-name, value=${env.DB_USER:keycloak}) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=password, value=${env.DB_PASSWORD:password}) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=check-valid-connection-sql, value="SELECT 1") -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=background-validation, value=true) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=background-validation-millis, value=60000) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=flush-strategy, value=IdleConnections) -/subsystem=datasources/jdbc-driver=mariadb:add(driver-name=mariadb, driver-module-name=org.mariadb.jdbc, driver-xa-datasource-class-name=org.mariadb.jdbc.MySQLDataSource) diff --git a/tools/cli/databases/mariadb/standalone-configuration.cli b/tools/cli/databases/mariadb/standalone-configuration.cli deleted file mode 100644 index 60c0453..0000000 --- a/tools/cli/databases/mariadb/standalone-configuration.cli +++ /dev/null @@ -1,3 +0,0 @@ -embed-server --server-config=standalone.xml --std-out=echo -run-batch --file=/opt/jboss/tools/cli/databases/mariadb/change-database.cli -stop-embedded-server diff --git a/tools/cli/databases/mariadb/standalone-ha-configuration.cli b/tools/cli/databases/mariadb/standalone-ha-configuration.cli deleted file mode 100644 index de59136..0000000 --- a/tools/cli/databases/mariadb/standalone-ha-configuration.cli +++ /dev/null @@ -1,3 +0,0 @@ -embed-server --server-config=standalone-ha.xml --std-out=echo -run-batch --file=/opt/jboss/tools/cli/databases/mariadb/change-database.cli -stop-embedded-server diff --git a/tools/cli/databases/mssql/change-database.cli b/tools/cli/databases/mssql/change-database.cli deleted file mode 100644 index bdb87fc..0000000 --- a/tools/cli/databases/mssql/change-database.cli +++ /dev/null @@ -1,11 +0,0 @@ -/subsystem=datasources/data-source=KeycloakDS: remove() -/subsystem=datasources/data-source=KeycloakDS: add(jndi-name=java:jboss/datasources/KeycloakDS,enabled=true,use-java-context=true,use-ccm=true, connection-url="jdbc:sqlserver://${env.DB_ADDR:mssql}:${env.DB_PORT:1433};databaseName=${env.DB_DATABASE:keycloak};sendStringParametersAsUnicode=false;${env.JDBC_PARAMS:}", driver-name=sqlserver) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=user-name, value=${env.DB_USER:keycloak}) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=password, value=${env.DB_PASSWORD:password}) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=check-valid-connection-sql, value="SELECT 1") -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=background-validation, value=true) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=background-validation-millis, value=60000) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=flush-strategy, value=IdleConnections) -/subsystem=datasources/jdbc-driver=sqlserver:add(driver-name=sqlserver,driver-module-name=com.microsoft.sqlserver.jdbc,driver-xa-datasource-class-name=com.microsoft.sqlserver.jdbc.SQLServerXADataSource) - -/subsystem=keycloak-server/spi=connectionsJpa/provider=default:write-attribute(name=properties.schema,value=${env.DB_SCHEMA:dbo}) \ No newline at end of file diff --git a/tools/cli/databases/mssql/standalone-configuration.cli b/tools/cli/databases/mssql/standalone-configuration.cli deleted file mode 100644 index 8a616ca..0000000 --- a/tools/cli/databases/mssql/standalone-configuration.cli +++ /dev/null @@ -1,3 +0,0 @@ -embed-server --server-config=standalone.xml --std-out=echo -run-batch --file=/opt/jboss/tools/cli/databases/mssql/change-database.cli -stop-embedded-server diff --git a/tools/cli/databases/mssql/standalone-ha-configuration.cli b/tools/cli/databases/mssql/standalone-ha-configuration.cli deleted file mode 100644 index 5057630..0000000 --- a/tools/cli/databases/mssql/standalone-ha-configuration.cli +++ /dev/null @@ -1,3 +0,0 @@ -embed-server --server-config=standalone-ha.xml --std-out=echo -run-batch --file=/opt/jboss/tools/cli/databases/mssql/change-database.cli -stop-embedded-server diff --git a/tools/cli/databases/mysql/change-database.cli b/tools/cli/databases/mysql/change-database.cli deleted file mode 100644 index e709697..0000000 --- a/tools/cli/databases/mysql/change-database.cli +++ /dev/null @@ -1,9 +0,0 @@ -/subsystem=datasources/data-source=KeycloakDS: remove() -/subsystem=datasources/data-source=KeycloakDS: add(jndi-name=java:jboss/datasources/KeycloakDS,enabled=true,use-java-context=true,use-ccm=true, connection-url=jdbc:mysql://${env.DB_ADDR:mysql}:${env.DB_PORT:3306}/${env.DB_DATABASE:keycloak}${env.JDBC_PARAMS:}, driver-name=mysql) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=user-name, value=${env.DB_USER:keycloak}) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=password, value=${env.DB_PASSWORD:password}) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=check-valid-connection-sql, value="SELECT 1") -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=background-validation, value=true) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=background-validation-millis, value=60000) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=flush-strategy, value=IdleConnections) -/subsystem=datasources/jdbc-driver=mysql:add(driver-name=mysql, driver-module-name=com.mysql.jdbc, driver-xa-datasource-class-name=com.mysql.cj.jdbc.MysqlXADataSource) diff --git a/tools/cli/databases/mysql/standalone-configuration.cli b/tools/cli/databases/mysql/standalone-configuration.cli deleted file mode 100644 index 00370f6..0000000 --- a/tools/cli/databases/mysql/standalone-configuration.cli +++ /dev/null @@ -1,3 +0,0 @@ -embed-server --server-config=standalone.xml --std-out=echo -run-batch --file=/opt/jboss/tools/cli/databases/mysql/change-database.cli -stop-embedded-server diff --git a/tools/cli/databases/mysql/standalone-ha-configuration.cli b/tools/cli/databases/mysql/standalone-ha-configuration.cli deleted file mode 100644 index 5787e8a..0000000 --- a/tools/cli/databases/mysql/standalone-ha-configuration.cli +++ /dev/null @@ -1,3 +0,0 @@ -embed-server --server-config=standalone-ha.xml --std-out=echo -run-batch --file=/opt/jboss/tools/cli/databases/mysql/change-database.cli -stop-embedded-server diff --git a/tools/cli/databases/oracle/change-database.cli b/tools/cli/databases/oracle/change-database.cli deleted file mode 100644 index 3ea85bf..0000000 --- a/tools/cli/databases/oracle/change-database.cli +++ /dev/null @@ -1,9 +0,0 @@ -/subsystem=datasources/data-source=KeycloakDS: remove() -/subsystem=datasources/data-source=KeycloakDS: add(jndi-name=java:jboss/datasources/KeycloakDS,enabled=true,use-java-context=true,use-ccm=true, connection-url=jdbc:oracle:thin:@${env.DB_ADDR:oracle}:${env.DB_PORT:1521}:${env.DB_DATABASE:XE}${env.JDBC_PARAMS:}, driver-name=oracle) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=user-name, value=${env.DB_USER:SYSTEM}) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=password, value=${env.DB_PASSWORD:oracle}) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=check-valid-connection-sql, value="SELECT 1 FROM dual") -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=background-validation, value=true) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=background-validation-millis, value=60000) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=flush-strategy, value=IdleConnections) -/subsystem=datasources/jdbc-driver=oracle:add(driver-name=oracle, driver-module-name=com.oracle.jdbc, driver-xa-datasource-class-name=oracle.jdbc.xa.client.OracleXADataSource) diff --git a/tools/cli/databases/oracle/standalone-configuration.cli b/tools/cli/databases/oracle/standalone-configuration.cli deleted file mode 100644 index 4f1f3dc..0000000 --- a/tools/cli/databases/oracle/standalone-configuration.cli +++ /dev/null @@ -1,3 +0,0 @@ -embed-server --server-config=standalone.xml --std-out=echo -run-batch --file=/opt/jboss/tools/cli/databases/oracle/change-database.cli -stop-embedded-server diff --git a/tools/cli/databases/oracle/standalone-ha-configuration.cli b/tools/cli/databases/oracle/standalone-ha-configuration.cli deleted file mode 100644 index 57762b8..0000000 --- a/tools/cli/databases/oracle/standalone-ha-configuration.cli +++ /dev/null @@ -1,3 +0,0 @@ -embed-server --server-config=standalone-ha.xml --std-out=echo -run-batch --file=/opt/jboss/tools/cli/databases/oracle/change-database.cli -stop-embedded-server diff --git a/tools/cli/databases/postgres/change-database.cli b/tools/cli/databases/postgres/change-database.cli deleted file mode 100644 index f6b7042..0000000 --- a/tools/cli/databases/postgres/change-database.cli +++ /dev/null @@ -1,11 +0,0 @@ -/subsystem=datasources/data-source=KeycloakDS: remove() -/subsystem=datasources/data-source=KeycloakDS: add(jndi-name=java:jboss/datasources/KeycloakDS,enabled=true,use-java-context=true,use-ccm=true, connection-url=jdbc:postgresql://${env.DB_ADDR:postgres}/${env.DB_DATABASE:keycloak}${env.JDBC_PARAMS:}, driver-name=postgresql) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=user-name, value=${env.DB_USER:keycloak}) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=password, value=${env.DB_PASSWORD:password}) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=check-valid-connection-sql, value="SELECT 1") -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=background-validation, value=true) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=background-validation-millis, value=60000) -/subsystem=datasources/data-source=KeycloakDS: write-attribute(name=flush-strategy, value=IdleConnections) -/subsystem=datasources/jdbc-driver=postgresql:add(driver-name=postgresql, driver-module-name=org.postgresql.jdbc, driver-xa-datasource-class-name=org.postgresql.xa.PGXADataSource) - -/subsystem=keycloak-server/spi=connectionsJpa/provider=default:write-attribute(name=properties.schema,value=${env.DB_SCHEMA:public}) diff --git a/tools/cli/databases/postgres/standalone-configuration.cli b/tools/cli/databases/postgres/standalone-configuration.cli deleted file mode 100644 index e10ff84..0000000 --- a/tools/cli/databases/postgres/standalone-configuration.cli +++ /dev/null @@ -1,3 +0,0 @@ -embed-server --server-config=standalone.xml --std-out=echo -run-batch --file=/opt/jboss/tools/cli/databases/postgres/change-database.cli -stop-embedded-server diff --git a/tools/cli/databases/postgres/standalone-ha-configuration.cli b/tools/cli/databases/postgres/standalone-ha-configuration.cli deleted file mode 100644 index e95f344..0000000 --- a/tools/cli/databases/postgres/standalone-ha-configuration.cli +++ /dev/null @@ -1,3 +0,0 @@ -embed-server --server-config=standalone-ha.xml --std-out=echo -run-batch --file=/opt/jboss/tools/cli/databases/postgres/change-database.cli -stop-embedded-server diff --git a/tools/cli/files-plaintext-vault.cli b/tools/cli/files-plaintext-vault.cli deleted file mode 100644 index 1b076c2..0000000 --- a/tools/cli/files-plaintext-vault.cli +++ /dev/null @@ -1,7 +0,0 @@ -embed-server --server-config=$configuration_file --std-out=discard -echo ** Adding vault spi ** -/subsystem=keycloak-server/spi=vault/:add -/subsystem=keycloak-server/spi=vault/provider=files-plaintext/:add(enabled=true,properties={dir => $plaintext_vault_provider_dir}) -/subsystem=keycloak-server/spi=vault:write-attribute(name=default-provider,value=files-plaintext) -stop-embedded-server - diff --git a/tools/cli/hostname.cli b/tools/cli/hostname.cli deleted file mode 100644 index c9e82e1..0000000 --- a/tools/cli/hostname.cli +++ /dev/null @@ -1,2 +0,0 @@ -/subsystem=keycloak-server/spi=hostname:write-attribute(name=default-provider, value="${keycloak.hostname.provider:default}") -/subsystem=keycloak-server/spi=hostname/provider=fixed/:add(properties={hostname => "${keycloak.hostname.fixed.hostname:localhost}",httpPort => "${keycloak.hostname.fixed.httpPort:-1}",httpsPort => "${keycloak.hostname.fixed.httpsPort:-1}",alwaysHttps => "${keycloak.hostname.fixed.alwaysHttps:false}"},enabled=true) diff --git a/tools/cli/infinispan/cache-owners.cli b/tools/cli/infinispan/cache-owners.cli deleted file mode 100644 index dc207e7..0000000 --- a/tools/cli/infinispan/cache-owners.cli +++ /dev/null @@ -1,11 +0,0 @@ -embed-server --server-config=standalone-ha.xml --std-out=echo -batch -/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions: write-attribute(name=owners, value=${env.CACHE_OWNERS_COUNT:1}) -/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions: write-attribute(name=owners, value=${env.CACHE_OWNERS_COUNT:1}) -/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures: write-attribute(name=owners, value=${env.CACHE_OWNERS_COUNT:1}) -/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions: write-attribute(name=owners, value=${env.CACHE_OWNERS_COUNT:1}) -/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions: write-attribute(name=owners, value=${env.CACHE_OWNERS_COUNT:1}) -/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens: write-attribute(name=owners, value=${env.CACHE_OWNERS_COUNT:1}) -/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions: write-attribute(name=owners, value=${env.CACHE_OWNERS_AUTH_SESSIONS_COUNT:1}) -run-batch -stop-embedded-server \ No newline at end of file diff --git a/tools/cli/jgroups/discovery/JDBC_PING.cli b/tools/cli/jgroups/discovery/JDBC_PING.cli deleted file mode 100644 index bd4c8ee..0000000 --- a/tools/cli/jgroups/discovery/JDBC_PING.cli +++ /dev/null @@ -1,11 +0,0 @@ -embed-server --server-config=standalone-ha.xml --std-out=echo -batch -/subsystem=jgroups/stack=udp/protocol=PING:remove() -/subsystem=jgroups/stack=udp/protocol=JDBC_PING:add(add-index=0, data-source=KeycloakDS, properties=$keycloak_jgroups_discovery_protocol_properties) - -/subsystem=jgroups/stack=tcp/protocol=MPING:remove() -/subsystem=jgroups/stack=tcp/protocol=JDBC_PING:add(add-index=0, data-source=KeycloakDS, properties=$keycloak_jgroups_discovery_protocol_properties) - -/subsystem=jgroups/channel=ee:write-attribute(name="stack", value=$keycloak_jgroups_transport_stack) -run-batch -stop-embedded-server diff --git a/tools/cli/jgroups/discovery/default.cli b/tools/cli/jgroups/discovery/default.cli deleted file mode 100644 index 68da05a..0000000 --- a/tools/cli/jgroups/discovery/default.cli +++ /dev/null @@ -1,11 +0,0 @@ -embed-server --server-config=standalone-ha.xml --std-out=echo -batch -/subsystem=jgroups/stack=udp/protocol=PING:remove() -/subsystem=jgroups/stack=udp/protocol=$keycloak_jgroups_discovery_protocol:add(add-index=0, properties=$keycloak_jgroups_discovery_protocol_properties) - -/subsystem=jgroups/stack=tcp/protocol=MPING:remove() -/subsystem=jgroups/stack=tcp/protocol=$keycloak_jgroups_discovery_protocol:add(add-index=0, properties=$keycloak_jgroups_discovery_protocol_properties) - -/subsystem=jgroups/channel=ee:write-attribute(name="stack", value=$keycloak_jgroups_transport_stack) -run-batch -stop-embedded-server diff --git a/tools/cli/loglevel.cli b/tools/cli/loglevel.cli deleted file mode 100644 index c6adb88..0000000 --- a/tools/cli/loglevel.cli +++ /dev/null @@ -1,9 +0,0 @@ -/subsystem=logging/logger=org.keycloak:add -/subsystem=logging/logger=org.keycloak:write-attribute(name=level,value=${env.KEYCLOAK_LOGLEVEL:INFO}) - -/subsystem=logging/root-logger=ROOT:change-root-log-level(level=${env.ROOT_LOGLEVEL:INFO}) - -/subsystem=logging/root-logger=ROOT:remove-handler(name="FILE") -/subsystem=logging/periodic-rotating-file-handler=FILE:remove - -/subsystem=logging/console-handler=CONSOLE:undefine-attribute(name=level) diff --git a/tools/cli/metrics/db.cli b/tools/cli/metrics/db.cli deleted file mode 100644 index 7524657..0000000 --- a/tools/cli/metrics/db.cli +++ /dev/null @@ -1,5 +0,0 @@ -embed-server --server-config=standalone-ha.xml --std-out=echo -batch -/subsystem=datasources/data-source=KeycloakDS:write-attribute(name=statistics-enabled, value=true) -run-batch -stop-embedded-server \ No newline at end of file diff --git a/tools/cli/metrics/http.cli b/tools/cli/metrics/http.cli deleted file mode 100644 index 322c7db..0000000 --- a/tools/cli/metrics/http.cli +++ /dev/null @@ -1,5 +0,0 @@ -embed-server --server-config=standalone-ha.xml --std-out=echo -batch -/subsystem=undertow:write-attribute(name=statistics-enabled,value=true) -run-batch -stop-embedded-server \ No newline at end of file diff --git a/tools/cli/metrics/jgroups.cli b/tools/cli/metrics/jgroups.cli deleted file mode 100644 index dac4cb5..0000000 --- a/tools/cli/metrics/jgroups.cli +++ /dev/null @@ -1,5 +0,0 @@ -embed-server --server-config=standalone-ha.xml --std-out=echo -batch -/subsystem=jgroups/channel=ee:write-attribute(name=statistics-enabled, value=true) -run-batch -stop-embedded-server \ No newline at end of file diff --git a/tools/cli/proxy.cli b/tools/cli/proxy.cli deleted file mode 100644 index 3c1984b..0000000 --- a/tools/cli/proxy.cli +++ /dev/null @@ -1,2 +0,0 @@ -/subsystem=undertow/server=default-server/http-listener=default: write-attribute(name=proxy-address-forwarding, value=${env.PROXY_ADDRESS_FORWARDING:false}) -/subsystem=undertow/server=default-server/https-listener=https: write-attribute(name=proxy-address-forwarding, value=${env.PROXY_ADDRESS_FORWARDING:false}) diff --git a/tools/cli/standalone-configuration.cli b/tools/cli/standalone-configuration.cli deleted file mode 100644 index 6e47c46..0000000 --- a/tools/cli/standalone-configuration.cli +++ /dev/null @@ -1,6 +0,0 @@ -embed-server --server-config=standalone.xml --std-out=echo -run-batch --file=/opt/jboss/tools/cli/loglevel.cli -run-batch --file=/opt/jboss/tools/cli/proxy.cli -run-batch --file=/opt/jboss/tools/cli/hostname.cli -run-batch --file=/opt/jboss/tools/cli/theme.cli -stop-embedded-server diff --git a/tools/cli/standalone-ha-configuration.cli b/tools/cli/standalone-ha-configuration.cli deleted file mode 100644 index 33e1440..0000000 --- a/tools/cli/standalone-ha-configuration.cli +++ /dev/null @@ -1,6 +0,0 @@ -embed-server --server-config=standalone-ha.xml --std-out=echo -run-batch --file=/opt/jboss/tools/cli/loglevel.cli -run-batch --file=/opt/jboss/tools/cli/proxy.cli -run-batch --file=/opt/jboss/tools/cli/hostname.cli -run-batch --file=/opt/jboss/tools/cli/theme.cli -stop-embedded-server diff --git a/tools/cli/theme.cli b/tools/cli/theme.cli deleted file mode 100644 index dba1937..0000000 --- a/tools/cli/theme.cli +++ /dev/null @@ -1,2 +0,0 @@ -/subsystem=keycloak-server/theme=defaults:write-attribute(name=welcomeTheme,value=${env.KEYCLOAK_WELCOME_THEME:keycloak}) -/subsystem=keycloak-server/theme=defaults:write-attribute(name=default,value=${env.KEYCLOAK_DEFAULT_THEME:keycloak}) diff --git a/tools/cli/x509-keystore.cli b/tools/cli/x509-keystore.cli deleted file mode 100644 index 270a700..0000000 --- a/tools/cli/x509-keystore.cli +++ /dev/null @@ -1,9 +0,0 @@ -embed-server --server-config=$configuration_file --std-out=discard -/subsystem=elytron/key-store=kcKeyStore:add(path=$keycloak_tls_keystore_file,type=JKS,credential-reference={clear-text=$keycloak_tls_keystore_password}) -/subsystem=elytron/key-manager=kcKeyManager:add(key-store=kcKeyStore,credential-reference={clear-text=$keycloak_tls_keystore_password}) -/subsystem=elytron/server-ssl-context=kcSSLContext:add(key-manager=kcKeyManager) -batch -/subsystem=undertow/server=default-server/https-listener=https:undefine-attribute(name=security-realm) -/subsystem=undertow/server=default-server/https-listener=https:write-attribute(name=ssl-context,value=kcSSLContext) -run-batch -stop-embedded-server diff --git a/tools/cli/x509-truststore.cli b/tools/cli/x509-truststore.cli deleted file mode 100644 index 79f94db..0000000 --- a/tools/cli/x509-truststore.cli +++ /dev/null @@ -1,25 +0,0 @@ -embed-server --server-config=$configuration_file --std-out=discard -/subsystem=elytron/key-store=kcTrustStore:add(path=$keycloak_tls_truststore_file,type=JKS,credential-reference={clear-text=$keycloak_tls_truststore_password}) -/subsystem=elytron/trust-manager=kcTrustManager:add(key-store=kcTrustStore) -if (outcome != success) of /subsystem=elytron/server-ssl-context=kcSSLContext:read-resource - # Since WF requires a Key Manager for creating /subsystem=elytron/server-ssl-context, there's nothing we can do at this point. - # We can not automatically generate a self-signed key (Elytron doesn't support this, see https://docs.wildfly.org/13/WildFly_Elytron_Security.html#configure-ssltls), - # and we don't have anything else at hand. - # However, there is no big harm here - the Trust Store is more needed by Keycloak Truststore SPI. - echo "WARNING! There is no Key Manager (No Key Store specified). Skipping HTTPS Listener configuration..." -else - # The SSL Context has been added by keystore, not much to do - just append trust store and we are done. - /subsystem=elytron/server-ssl-context=kcSSLContext:write-attribute(name=trust-manager, value=kcTrustManager) - /subsystem=elytron/server-ssl-context=kcSSLContext:write-attribute(name=want-client-auth, value=true) -end-if - -if (outcome != success) of /subsystem=keycloak-server/spi=truststore:read-resource - /subsystem=keycloak-server/spi=truststore/:add -end-if -/subsystem=keycloak-server/spi=truststore/provider=file/:add(enabled=true,properties={ \ - file => $keycloak_tls_truststore_file, \ - password => $keycloak_tls_truststore_password, \ - hostname-verification-policy => "WILDCARD", \ -disabled => "false"}) - -stop-embedded-server \ No newline at end of file diff --git a/tools/databases/change-database.sh b/tools/databases/change-database.sh deleted file mode 100644 index 55a4a8e..0000000 --- a/tools/databases/change-database.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -e - -DB_VENDOR=$1 - -cd /opt/jboss/keycloak - -bin/jboss-cli.sh --file=/opt/jboss/tools/cli/databases/$DB_VENDOR/standalone-configuration.cli -rm -rf /opt/jboss/keycloak/standalone/configuration/standalone_xml_history - -bin/jboss-cli.sh --file=/opt/jboss/tools/cli/databases/$DB_VENDOR/standalone-ha-configuration.cli -rm -rf standalone/configuration/standalone_xml_history/current/* \ No newline at end of file diff --git a/tools/databases/mariadb/module.xml b/tools/databases/mariadb/module.xml deleted file mode 100644 index a3f6f96..0000000 --- a/tools/databases/mariadb/module.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - diff --git a/tools/databases/mssql/module.xml b/tools/databases/mssql/module.xml deleted file mode 100644 index 23574b8..0000000 --- a/tools/databases/mssql/module.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/tools/databases/mysql/module.xml b/tools/databases/mysql/module.xml deleted file mode 100644 index 600bded..0000000 --- a/tools/databases/mysql/module.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - diff --git a/tools/databases/oracle/module.xml b/tools/databases/oracle/module.xml deleted file mode 100644 index 8720a08..0000000 --- a/tools/databases/oracle/module.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - diff --git a/tools/databases/postgres/module.xml b/tools/databases/postgres/module.xml deleted file mode 100644 index 2180e59..0000000 --- a/tools/databases/postgres/module.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - diff --git a/tools/docker-entrypoint.sh b/tools/docker-entrypoint.sh deleted file mode 100644 index 6633743..0000000 --- a/tools/docker-entrypoint.sh +++ /dev/null @@ -1,257 +0,0 @@ -#!/bin/bash -set -eou pipefail - -# usage: file_env VAR [DEFAULT] -# ie: file_env 'XYZ_DB_PASSWORD' 'example' -# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of -# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) -file_env() { - local var="$1" - local fileVar="${var}_FILE" - local def="${2:-}" - if [[ ${!var:-} && ${!fileVar:-} ]]; then - echo >&2 "error: both $var and $fileVar are set (but are exclusive)" - exit 1 - fi - local val="$def" - if [[ ${!var:-} ]]; then - val="${!var}" - elif [[ ${!fileVar:-} ]]; then - val="$(< "${!fileVar}")" - fi - - if [[ -n $val ]]; then - export "$var"="$val" - fi - - unset "$fileVar" -} - -SYS_PROPS="" - -################## -# Add admin user # -################## - -file_env 'KEYCLOAK_USER' -file_env 'KEYCLOAK_PASSWORD' - -if [[ -n ${KEYCLOAK_USER:-} && -n ${KEYCLOAK_PASSWORD:-} ]]; then - /opt/jboss/keycloak/bin/add-user-keycloak.sh --user "$KEYCLOAK_USER" --password "$KEYCLOAK_PASSWORD" -fi - -############ -# Hostname # -############ - -if [[ -n ${KEYCLOAK_FRONTEND_URL:-} ]]; then - SYS_PROPS+="-Dkeycloak.frontendUrl=$KEYCLOAK_FRONTEND_URL" -fi - -if [[ -n ${KEYCLOAK_HOSTNAME:-} ]]; then - SYS_PROPS+=" -Dkeycloak.hostname.provider=fixed -Dkeycloak.hostname.fixed.hostname=$KEYCLOAK_HOSTNAME" - - if [[ -n ${KEYCLOAK_HTTP_PORT:-} ]]; then - SYS_PROPS+=" -Dkeycloak.hostname.fixed.httpPort=$KEYCLOAK_HTTP_PORT" - fi - - if [[ -n ${KEYCLOAK_HTTPS_PORT:-} ]]; then - SYS_PROPS+=" -Dkeycloak.hostname.fixed.httpsPort=$KEYCLOAK_HTTPS_PORT" - fi - - if [[ -n ${KEYCLOAK_ALWAYS_HTTPS:-} ]]; then - SYS_PROPS+=" -Dkeycloak.hostname.fixed.alwaysHttps=$KEYCLOAK_ALWAYS_HTTPS" - fi -fi - -################ -# Realm import # -################ - -if [[ -n ${KEYCLOAK_IMPORT:-} ]]; then - SYS_PROPS+=" -Dkeycloak.import=$KEYCLOAK_IMPORT" -fi - -######################## -# JGroups bind options # -######################## - -if [[ -z ${BIND:-} ]]; then - BIND=$(hostname --all-ip-addresses) -fi -if [[ -z ${BIND_OPTS:-} ]]; then - for BIND_IP in $BIND - do - BIND_OPTS+=" -Djboss.bind.address=$BIND_IP -Djboss.bind.address.private=$BIND_IP " - done -fi -SYS_PROPS+=" $BIND_OPTS" - -######################################### -# Expose management console for metrics # -######################################### - -if [[ -n ${KEYCLOAK_STATISTICS:-} ]] ; then - SYS_PROPS+=" -Djboss.bind.address.management=0.0.0.0" -fi - -################# -# Configuration # -################# - -# If the server configuration parameter is not present, append the HA profile. -if echo "$@" | grep -E -v -- '-c |-c=|--server-config |--server-config='; then - SYS_PROPS+=" -c=standalone-ha.xml" -fi - -# Adding support for JAVA_OPTS_APPEND -sed -i '$a\\n# Append to JAVA_OPTS. Necessary to prevent some values being omitted if JAVA_OPTS is defined directly\nJAVA_OPTS=\"\$JAVA_OPTS \$JAVA_OPTS_APPEND\"' /opt/jboss/keycloak/bin/standalone.conf - -############ -# DB setup # -############ - -file_env 'DB_USER' -file_env 'DB_PASSWORD' -# Lower case DB_VENDOR -if [[ -n ${DB_VENDOR:-} ]]; then - DB_VENDOR=$(echo "$DB_VENDOR" | tr "[:upper:]" "[:lower:]") -fi - -# Detect DB vendor from default host names -if [[ -z ${DB_VENDOR:-} ]]; then - if (getent hosts postgres &>/dev/null); then - export DB_VENDOR="postgres" - elif (getent hosts mysql &>/dev/null); then - export DB_VENDOR="mysql" - elif (getent hosts mariadb &>/dev/null); then - export DB_VENDOR="mariadb" - elif (getent hosts oracle &>/dev/null); then - export DB_VENDOR="oracle" - elif (getent hosts mssql &>/dev/null); then - export DB_VENDOR="mssql" - elif (getent hosts h2 &>/dev/null); then - export DB_VENDOR="h2" - export DB_ADDR="h2" - fi -fi - -# Detect DB vendor from legacy `*_ADDR` environment variables -if [[ -z ${DB_VENDOR:-} ]]; then - if (printenv | grep '^POSTGRES_ADDR=' &>/dev/null); then - export DB_VENDOR="postgres" - elif (printenv | grep '^MYSQL_ADDR=' &>/dev/null); then - export DB_VENDOR="mysql" - elif (printenv | grep '^MARIADB_ADDR=' &>/dev/null); then - export DB_VENDOR="mariadb" - elif (printenv | grep '^ORACLE_ADDR=' &>/dev/null); then - export DB_VENDOR="oracle" - elif (printenv | grep '^MSSQL_ADDR=' &>/dev/null); then - export DB_VENDOR="mssql" - elif (printenv | grep '^H2_ADDR=' &>/dev/null); then - export DB_VENDOR="h2" - export DB_ADDR="h2" - fi -fi - -# Default to H2 if DB type not detected -if [[ -z ${DB_VENDOR:-} ]]; then - export DB_VENDOR="h2" -fi - -# if the DB_VENDOR is postgres then append port to the DB_ADDR -function append_port_db_addr() { - local db_host_regex='^[a-zA-Z0-9]([a-zA-Z0-9]|-|.)*:[0-9]{4,5}$' - IFS=',' read -ra addresses <<< "$DB_ADDR" - DB_ADDR="" - for i in "${addresses[@]}"; do - if [[ $i =~ $db_host_regex ]]; then - DB_ADDR+=$i; - else - DB_ADDR+="${i}:${DB_PORT}"; - fi - DB_ADDR+="," - done - DB_ADDR=$(echo $DB_ADDR | sed 's/.$//') # remove the last comma -} -# Set DB name -case "$DB_VENDOR" in - postgres) - DB_NAME="PostgreSQL" - if [[ -z ${DB_PORT:-} ]] ; then - DB_PORT="5432" - fi - append_port_db_addr - ;; - mysql) - DB_NAME="MySQL";; - mariadb) - DB_NAME="MariaDB";; - mssql) - DB_NAME="Microsoft SQL Server";; - oracle) - DB_NAME="Oracle";; - h2) - if [[ -z ${DB_ADDR:-} ]] ; then - DB_NAME="Embedded H2" - else - DB_NAME="H2" - fi;; - *) - echo "Unknown DB vendor $DB_VENDOR" - exit 1 -esac - -if [ "$DB_VENDOR" != "mssql" ] && [ "$DB_VENDOR" != "h2" ]; then - # Append '?' in the beginning of the string if JDBC_PARAMS value isn't empty - JDBC_PARAMS=$(echo "${JDBC_PARAMS:-}" | sed '/^$/! s/^/?/') -else - JDBC_PARAMS=${JDBC_PARAMS:-} -fi - -export JDBC_PARAMS - -# Convert deprecated DB specific variables -function set_legacy_vars() { - local suffixes=(ADDR DATABASE USER PASSWORD PORT) - for suffix in "${suffixes[@]}"; do - local varname="$1_$suffix" - if [[ -n ${!varname:-} ]]; then - echo WARNING: "$varname" variable name is DEPRECATED replace with DB_"$suffix" - export DB_"$suffix=${!varname}" - fi - done -} -set_legacy_vars "$(echo "$DB_VENDOR" | tr "[:upper:]" "[:lower:]")" - -# Configure DB - -echo "=========================================================================" -echo "" -echo " Using $DB_NAME database" -echo "" -echo "=========================================================================" -echo "" - -configured_file="/opt/jboss/configured" -if [ ! -e "$configured_file" ]; then - touch "$configured_file" - - if [ "$DB_NAME" != "Embedded H2" ]; then - /bin/sh /opt/jboss/tools/databases/change-database.sh $DB_VENDOR - fi - - /opt/jboss/tools/x509.sh - /opt/jboss/tools/jgroups.sh - /opt/jboss/tools/infinispan.sh - /opt/jboss/tools/statistics.sh - /opt/jboss/tools/vault.sh - /opt/jboss/tools/autorun.sh -fi - -################## -# Start Keycloak # -################## - -exec /opt/jboss/keycloak/bin/standalone.sh $SYS_PROPS $@ -exit $? diff --git a/tools/infinispan.sh b/tools/infinispan.sh deleted file mode 100644 index be15edf..0000000 --- a/tools/infinispan.sh +++ /dev/null @@ -1,14 +0,0 @@ -# How many owners / replicas should our distributed caches have. If <2 any node that is removed from the cluster will cause a data-loss! -# As it is only sensible to replicate AuthenticationSessions for certain cases, their replication factor can be configured independently - -if [ -n "$CACHE_OWNERS_COUNT" ]; then - echo "Setting cache owners to $CACHE_OWNERS_COUNT replicas" - - # Check and log the replication factor of AuthenticationSessions, otherwise this is set to 1 by default - if [ -n "$CACHE_OWNERS_AUTH_SESSIONS_COUNT" ]; then - echo "Enabling replication of AuthenticationSessions with ${CACHE_OWNERS_AUTH_SESSIONS_COUNT} replicas" - else - echo "AuthenticationSessions will NOT be replicated, set CACHE_OWNERS_AUTH_SESSIONS_COUNT to configure this" - fi -$JBOSS_HOME/bin/jboss-cli.sh --file="/opt/jboss/tools/cli/infinispan/cache-owners.cli" >& /dev/null -fi diff --git a/tools/jgroups.sh b/tools/jgroups.sh deleted file mode 100644 index 36f34a5..0000000 --- a/tools/jgroups.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -# If JGROUPS_DISCOVERY_PROPERTIES is set, it must be in the following format: PROP1=FOO,PROP2=BAR -# If JGROUPS_DISCOVERY_PROPERTIES_DIRECT is set, it must be in the following format: {PROP1=>FOO,PROP2=>BAR} -# It's a configuration error to set both of these variables - -if [ -n "$JGROUPS_DISCOVERY_PROTOCOL" ]; then - if [ -n "$JGROUPS_DISCOVERY_PROPERTIES" ] && [ -n "$JGROUPS_DISCOVERY_PROPERTIES_DIRECT" ]; then - echo >&2 "error: both JGROUPS_DISCOVERY_PROPERTIES and JGROUPS_DISCOVERY_PROPERTIES_DIRECT are set (but are exclusive)" - exit 1 - fi - - if [ -n "$JGROUPS_DISCOVERY_PROPERTIES_DIRECT" ]; then - JGROUPS_DISCOVERY_PROPERTIES_PARSED="$JGROUPS_DISCOVERY_PROPERTIES_DIRECT" - else - JGROUPS_DISCOVERY_PROPERTIES_PARSED=`echo $JGROUPS_DISCOVERY_PROPERTIES | sed "s/=/=>/g"` - JGROUPS_DISCOVERY_PROPERTIES_PARSED="{$JGROUPS_DISCOVERY_PROPERTIES_PARSED}" - fi - - echo "Setting JGroups discovery to $JGROUPS_DISCOVERY_PROTOCOL with properties $JGROUPS_DISCOVERY_PROPERTIES_PARSED" - echo "set keycloak_jgroups_discovery_protocol=${JGROUPS_DISCOVERY_PROTOCOL}" >> "$JBOSS_HOME/bin/.jbossclirc" - echo "set keycloak_jgroups_discovery_protocol_properties=${JGROUPS_DISCOVERY_PROPERTIES_PARSED}" >> "$JBOSS_HOME/bin/.jbossclirc" - echo "set keycloak_jgroups_transport_stack=${JGROUPS_TRANSPORT_STACK:-tcp}" >> "$JBOSS_HOME/bin/.jbossclirc" - # If there's a specific CLI file for given protocol - execute it. If not, we should be good with the default one. - if [ -f "/opt/jboss/tools/cli/jgroups/discovery/$JGROUPS_DISCOVERY_PROTOCOL.cli" ]; then - $JBOSS_HOME/bin/jboss-cli.sh --file="/opt/jboss/tools/cli/jgroups/discovery/$JGROUPS_DISCOVERY_PROTOCOL.cli" >& /dev/null - else - $JBOSS_HOME/bin/jboss-cli.sh --file="/opt/jboss/tools/cli/jgroups/discovery/default.cli" >& /dev/null - fi -fi diff --git a/tools/statistics.sh b/tools/statistics.sh deleted file mode 100644 index 5c90f00..0000000 --- a/tools/statistics.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -if [ -n "$KEYCLOAK_STATISTICS" ]; then - IFS=',' read -ra metrics <<< "$KEYCLOAK_STATISTICS" - for file in /opt/jboss/tools/cli/metrics/*.cli; do - name=${file##*/} - base=${name%.cli} - if [[ $KEYCLOAK_STATISTICS == *"$base"* ]] || [[ $KEYCLOAK_STATISTICS == *"all"* ]]; then - $JBOSS_HOME/bin/jboss-cli.sh --file="$file" >& /dev/null - fi - done -fi diff --git a/tools/vault.sh b/tools/vault.sh deleted file mode 100644 index 77e86ee..0000000 --- a/tools/vault.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -if [ -d "$JBOSS_HOME/secrets" ]; then - echo "set plaintext_vault_provider_dir=${JBOSS_HOME}/secrets" >> "$JBOSS_HOME/bin/.jbossclirc" - - echo "set configuration_file=standalone.xml" >> "$JBOSS_HOME/bin/.jbossclirc" - $JBOSS_HOME/bin/jboss-cli.sh --file=/opt/jboss/tools/cli/files-plaintext-vault.cli - sed -i '$ d' "$JBOSS_HOME/bin/.jbossclirc" - - echo "set configuration_file=standalone-ha.xml" >> "$JBOSS_HOME/bin/.jbossclirc" - $JBOSS_HOME/bin/jboss-cli.sh --file=/opt/jboss/tools/cli/files-plaintext-vault.cli - sed -i '$ d' "$JBOSS_HOME/bin/.jbossclirc" -fi diff --git a/tools/x509.sh b/tools/x509.sh deleted file mode 100644 index e4dfde8..0000000 --- a/tools/x509.sh +++ /dev/null @@ -1,115 +0,0 @@ -#!/bin/bash - -function autogenerate_keystores() { - # Keystore infix notation as used in templates to keystore name mapping - declare -A KEYSTORES=( ["https"]="HTTPS" ) - - local KEYSTORES_STORAGE="${JBOSS_HOME}/standalone/configuration/keystores" - if [ ! -d "${KEYSTORES_STORAGE}" ]; then - mkdir -p "${KEYSTORES_STORAGE}" - fi - - # Auto-generate the HTTPS keystore if volumes for OpenShift's - # serving x509 certificate secrets service were properly mounted - for KEYSTORE_TYPE in "${!KEYSTORES[@]}"; do - - local X509_KEYSTORE_DIR="/etc/x509/${KEYSTORE_TYPE}" - local X509_CRT="tls.crt" - local X509_KEY="tls.key" - local NAME="keycloak-${KEYSTORE_TYPE}-key" - local PASSWORD=$(openssl rand -base64 32 2>/dev/null) - local JKS_KEYSTORE_FILE="${KEYSTORE_TYPE}-keystore.jks" - local PKCS12_KEYSTORE_FILE="${KEYSTORE_TYPE}-keystore.pk12" - - if [ -f "${X509_KEYSTORE_DIR}/${X509_KEY}" ] && [ -f "${X509_KEYSTORE_DIR}/${X509_CRT}" ]; then - - echo "Creating ${KEYSTORES[$KEYSTORE_TYPE]} keystore via OpenShift's service serving x509 certificate secrets.." - - openssl pkcs12 -export \ - -name "${NAME}" \ - -inkey "${X509_KEYSTORE_DIR}/${X509_KEY}" \ - -in "${X509_KEYSTORE_DIR}/${X509_CRT}" \ - -out "${KEYSTORES_STORAGE}/${PKCS12_KEYSTORE_FILE}" \ - -password pass:"${PASSWORD}" >& /dev/null - - keytool -importkeystore -noprompt \ - -srcalias "${NAME}" -destalias "${NAME}" \ - -srckeystore "${KEYSTORES_STORAGE}/${PKCS12_KEYSTORE_FILE}" \ - -srcstoretype pkcs12 \ - -destkeystore "${KEYSTORES_STORAGE}/${JKS_KEYSTORE_FILE}" \ - -storepass "${PASSWORD}" -srcstorepass "${PASSWORD}" >& /dev/null - - if [ -f "${KEYSTORES_STORAGE}/${JKS_KEYSTORE_FILE}" ]; then - echo "${KEYSTORES[$KEYSTORE_TYPE]} keystore successfully created at: ${KEYSTORES_STORAGE}/${JKS_KEYSTORE_FILE}" - else - echo "${KEYSTORES[$KEYSTORE_TYPE]} keystore not created at: ${KEYSTORES_STORAGE}/${JKS_KEYSTORE_FILE} (check permissions?)" - fi - - echo "set keycloak_tls_keystore_password=${PASSWORD}" >> "$JBOSS_HOME/bin/.jbossclirc" - echo "set keycloak_tls_keystore_file=${KEYSTORES_STORAGE}/${JKS_KEYSTORE_FILE}" >> "$JBOSS_HOME/bin/.jbossclirc" - echo "set configuration_file=standalone.xml" >> "$JBOSS_HOME/bin/.jbossclirc" - $JBOSS_HOME/bin/jboss-cli.sh --file=/opt/jboss/tools/cli/x509-keystore.cli >& /dev/null - sed -i '$ d' "$JBOSS_HOME/bin/.jbossclirc" - echo "set configuration_file=standalone-ha.xml" >> "$JBOSS_HOME/bin/.jbossclirc" - $JBOSS_HOME/bin/jboss-cli.sh --file=/opt/jboss/tools/cli/x509-keystore.cli >& /dev/null - sed -i '$ d' "$JBOSS_HOME/bin/.jbossclirc" - fi - - done - - # Auto-generate the Keycloak truststore if X509_CA_BUNDLE was provided - local -r X509_CRT_DELIMITER="/-----BEGIN CERTIFICATE-----/" - local JKS_TRUSTSTORE_FILE="truststore.jks" - local JKS_TRUSTSTORE_PATH="${KEYSTORES_STORAGE}/${JKS_TRUSTSTORE_FILE}" - local PASSWORD=$(openssl rand -base64 32 2>/dev/null) - local TEMPORARY_CERTIFICATE="temporary_ca.crt" - if [ -n "${X509_CA_BUNDLE}" ]; then - pushd /tmp >& /dev/null - echo "Creating Keycloak truststore.." - # We use cat here, so that users could specify multiple CA Bundles using space or even wildcard: - # X509_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/*.crt - # Note, that there is no quotes here, that's intentional. Once can use spaces in the $X509_CA_BUNDLE like this: - # X509_CA_BUNDLE=/ca.crt /ca2.crt - cat ${X509_CA_BUNDLE} > ${TEMPORARY_CERTIFICATE} - csplit -s -z -f crt- "${TEMPORARY_CERTIFICATE}" "${X509_CRT_DELIMITER}" '{*}' - for CERT_FILE in crt-*; do - keytool -import -noprompt -keystore "${JKS_TRUSTSTORE_PATH}" -file "${CERT_FILE}" \ - -storepass "${PASSWORD}" -alias "service-${CERT_FILE}" >& /dev/null - done - - if [ -f "${JKS_TRUSTSTORE_PATH}" ]; then - echo "Keycloak truststore successfully created at: ${JKS_TRUSTSTORE_PATH}" - else - echo "Keycloak truststore not created at: ${JKS_TRUSTSTORE_PATH}" - fi - - # Import existing system CA certificates into the newly generated truststore - local SYSTEM_CACERTS=$(readlink -e $(dirname $(readlink -e $(which keytool)))"/../lib/security/cacerts") - if keytool -v -list -keystore "${SYSTEM_CACERTS}" -storepass "changeit" > /dev/null; then - echo "Importing certificates from system's Java CA certificate bundle into Keycloak truststore.." - keytool -importkeystore -noprompt \ - -srckeystore "${SYSTEM_CACERTS}" \ - -destkeystore "${JKS_TRUSTSTORE_PATH}" \ - -srcstoretype jks -deststoretype jks \ - -storepass "${PASSWORD}" -srcstorepass "changeit" >& /dev/null - if [ "$?" -eq "0" ]; then - echo "Successfully imported certificates from system's Java CA certificate bundle into Keycloak truststore at: ${JKS_TRUSTSTORE_PATH}" - else - echo "Failed to import certificates from system's Java CA certificate bundle into Keycloak truststore!" - fi - fi - - echo "set keycloak_tls_truststore_password=${PASSWORD}" >> "$JBOSS_HOME/bin/.jbossclirc" - echo "set keycloak_tls_truststore_file=${KEYSTORES_STORAGE}/${JKS_TRUSTSTORE_FILE}" >> "$JBOSS_HOME/bin/.jbossclirc" - echo "set configuration_file=standalone.xml" >> "$JBOSS_HOME/bin/.jbossclirc" - $JBOSS_HOME/bin/jboss-cli.sh --file=/opt/jboss/tools/cli/x509-truststore.cli >& /dev/null - sed -i '$ d' "$JBOSS_HOME/bin/.jbossclirc" - echo "set configuration_file=standalone-ha.xml" >> "$JBOSS_HOME/bin/.jbossclirc" - $JBOSS_HOME/bin/jboss-cli.sh --file=/opt/jboss/tools/cli/x509-truststore.cli >& /dev/null - sed -i '$ d' "$JBOSS_HOME/bin/.jbossclirc" - - popd >& /dev/null - fi -} - -autogenerate_keystores