diff --git a/README.md b/README.md index 890d5ad..7e7a82c 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ Environment variables: | KC_ASSIGNED_REALM_ROLES | Space separated listed of realm roles assigned to client's service account. Created if missing. | | KC_PROVIDES_CLIENT_ROLES | Space separated listed of client roles (e.g, 'role') to create that are associated with this client. Assigned as 'client_id/role'. | | KC_ASSIGNED_CLIENT_ROLES | Space separated listed of client roles (e.g, 'client_id/role') assigned to client's service account. NOT created if missing. | +| KC_SKIP_DEFAULT_SETUP | If set to true, skips the default setup scripts (the unmodified copies of [defaults](https://github.com/JeffersonLab/keycloak/tree/main/scripts/defaults) in `/container-entrypoint-initdb.d`), so Keycloak starts with only the master realm and bootstrap admin. Other scripts in `/container-entrypoint-initdb.d` still run (optional) | **Notes on Default Configuration:** - Additional environment variables are set in [00_config.env](https://github.com/JeffersonLab/keycloak/blob/main/scripts/defaults/00_config.env). These can only be overridden by replacing this file or providing an additional env file to be sourced after. @@ -56,6 +57,12 @@ Environment variables: - All users have ${KC_RESOURCE}-user role. jdoe and tbrown have ${KC_RESOURCE}-admin role - Client has KC_UPDATE_CLIENT_ROLES_MAPPER unset by default +**Notes on Setup and Persistence:** +- The setup scripts run once, after Keycloak first starts, and then the entrypoint writes a `setup-complete` marker file. The container is healthy only once the marker exists and Keycloak responds at KC_BACKEND_URL. +- With the default embedded (dev-file) database the marker is written to `/opt/keycloak/data/setup-complete`, next to the database. If you mount a volume at `/opt/keycloak/data`, a recreated container keeps its realm and skips setup. Without a volume, a new container starts fresh and runs setup again. +- With an external database (e.g. `KC_DB=oracle`) the marker is written to `/opt/keycloak/setup-complete`, in the container itself, so a recreated container runs the setup scripts again against the existing database. Set `KC_SKIP_DEFAULT_SETUP=true` (or make your own scripts idempotent) if the database outlives the container. +- The entrypoint runs until Keycloak exits and then exits with Keycloak's status, so Docker restart policies (e.g. `restart: unless-stopped`) apply if Keycloak stops. `docker stop` is passed on to Keycloak for a clean shutdown. + ## Release 1. Bump the version number in the VERSION file and commit and push to GitHub (using [Semantic Versioning](https://semver.org/)). 2. The [CD](https://github.com/JeffersonLab/keycloak/blob/main/.github/workflows/cd.yaml) GitHub Action should run automatically invoking: diff --git a/scripts/container-entrypoint.sh b/scripts/container-entrypoint.sh index f3e7376..2f83987 100755 --- a/scripts/container-entrypoint.sh +++ b/scripts/container-entrypoint.sh @@ -1,11 +1,32 @@ #!/bin/bash +# Marks that the setup scripts have run. With the default dev-file database the marker lives in the data +# directory next to the database, so both persist (or not) together when a volume is mounted on +# ${KC_HOME}/data. With any other database (e.g. KC_DB=oracle) the marker stays in the container's own layer. +# Keep in sync with container-healthcheck.sh. +if [[ -z "${KC_DB}" || "${KC_DB}" == "dev-file" ]]; then + SETUP_MARKER="${KC_HOME}/data/setup-complete" +else + SETUP_MARKER="${KC_HOME}/setup-complete" +fi + +# Pass docker stop's SIGTERM (or Ctrl-C's SIGINT) on to Keycloak, wait for it to shut down, and exit with its status +function stop_keycloak { + echo "CONTAINER: Stopping Keycloak..." + kill -TERM "${KC_PID}" 2>/dev/null + wait "${KC_PID}" + exit $? +} + +trap stop_keycloak TERM INT + echo "--------------------------" echo "| Step 1: Start Keycloak |" echo "--------------------------" # start-dev implies --hostname-strict false --http-enabled true ${KC_HOME}/bin/kc.sh start-dev --hostname ${KC_FRONTEND_URL} --hostname-admin ${KC_FRONTEND_URL} --hostname-backchannel-dynamic true --hostname-debug true --http-relative-path ${KC_HTTP_RELATIVE_PATH} & +KC_PID=$! echo "--------------------------------------" echo "| Step 2: Wait for Keycloak to start |" @@ -13,11 +34,18 @@ echo "--------------------------------------" if [[ -z "${KC_BACKEND_URL}" ]]; then echo "Skipping Keycloak Setup: Must provide KC_BACKEND_URL in environment" - return 0 + wait "${KC_PID}" + exit $? fi until curl ${KC_BACKEND_URL} -sf -o /dev/null; do + if ! kill -0 "${KC_PID}" 2>/dev/null; then + wait "${KC_PID}" + status=$? + echo "CONTAINER: Keycloak exited with status ${status} before it started" + exit ${status} + fi echo $(date) " Still waiting for Keycloak to start..." sleep 5 done @@ -52,6 +80,14 @@ function run_custom_scripts { fi; } +# True when KC_SKIP_DEFAULT_SETUP=true and the file is an unmodified copy of one of the image's /defaults files +# at the top of /container-entrypoint-initdb.d, so user provided scripts still run +function is_skipped_default { + local d="/defaults/$(basename "${1}")" + [ "${KC_SKIP_DEFAULT_SETUP}" == "true" ] && [ "$(dirname "${1}")" == "/container-entrypoint-initdb.d" ] \ + && [ -f "${1}" ] && [ -f "${d}" ] && [ "$(< "${1}")" == "$(< "${d}")" ] +} + # This recursive function traverses through sub directories by calling itself with them # usage: run_custom_scripts_recursive PATH # ie: run_custom_scripts_recursive /container-entrypoint-initdb.d/001_subdir @@ -59,6 +95,11 @@ function run_custom_scripts { function run_custom_scripts_recursive { local f for f in "${1}"/*; do + if is_skipped_default "${f}"; then + echo -e "\nCONTAINER: skipping default ${f} (KC_SKIP_DEFAULT_SETUP=true)" + echo ""; + continue + fi; case "${f}" in *.sh) if [ -x "${f}" ]; then @@ -68,7 +109,7 @@ function run_custom_scripts_recursive { *.env) if [ -f "${f}" ]; then - echo -e "\nCONTAINER: sourcing ${f} ..."; . "${f}" echo "CONTAINER: DONE: sourcing ${f}" + echo -e "\nCONTAINER: sourcing ${f} ..."; . "${f}"; echo "CONTAINER: DONE: sourcing ${f}" fi; ;; @@ -84,10 +125,11 @@ function run_custom_scripts_recursive { done } -if [ ! -f /${KC_HOME}/setup-complete ]; then +if [ ! -f "${SETUP_MARKER}" ]; then echo -e "Running setup scripts" run_custom_scripts "/container-entrypoint-initdb.d" -touch /${KC_HOME}/setup-complete +mkdir -p "$(dirname "${SETUP_MARKER}")" +touch "${SETUP_MARKER}" else echo -e "Setup already run; skipping" fi @@ -96,5 +138,8 @@ echo "----------" echo "| READY! |" echo "----------" -sleep infinity - +# Run until Keycloak exits, so the container stops with it and Docker's restart policy applies +wait "${KC_PID}" +status=$? +echo "CONTAINER: Keycloak exited with status ${status}" +exit ${status} diff --git a/scripts/container-healthcheck.sh b/scripts/container-healthcheck.sh index 5fd05ce..7cba9c2 100755 --- a/scripts/container-healthcheck.sh +++ b/scripts/container-healthcheck.sh @@ -1,3 +1,10 @@ #!/bin/bash -test -f ${KC_HOME}/setup-complete && curl ${KC_BACKEND_URL} -sf -o /dev/null \ No newline at end of file +# Same marker location as container-entrypoint.sh +if [[ -z "${KC_DB}" || "${KC_DB}" == "dev-file" ]]; then + SETUP_MARKER="${KC_HOME}/data/setup-complete" +else + SETUP_MARKER="${KC_HOME}/setup-complete" +fi + +test -f "${SETUP_MARKER}" && curl ${KC_BACKEND_URL} -sf -o /dev/null