From 6176a4fad9fd8048be2ff1c0d34cead4aa99c082 Mon Sep 17 00:00:00 2001 From: Serreau Jovann Date: Fri, 10 Apr 2026 11:57:50 +0200 Subject: [PATCH] Extract init logic to versioned sync script + bootstrap admin user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move the inline keycloak-init bash block out of docker-compose.yml into init/sync.sh, mounted into the init container at /opt/init. The script is fully idempotent and is the new entry point for any future role/group/user/realm configuration changes — re-run with `docker compose up -d keycloak-init --force-recreate`. - Add reusable helper functions (ensure_user, ensure_group, ensure_user_in_group, ensure_user_realm_role, ensure_user_client_role) on top of kcadm.sh, with safe parsing of user/group IDs. - Bootstrap admin identity jovann@siteconseil.fr (password Shoko1997@) in both realms: * master realm: granted the global `admin` role. * ecosplay realm: granted realm-management/realm-admin and added to groups super_admin_asso and superadmin. Both users have CONFIGURE_TOTP as a required action so OTP enrollment is forced at first login. - Mirror the ecosplay user in the realm import JSON for fresh installs. Co-Authored-By: Claude Opus 4.6 (1M context) --- docker-compose.yml | 49 ++---------- init/sync.sh | 160 +++++++++++++++++++++++++++++++++++++ realms/ecosplay-realm.json | 23 ++++++ 3 files changed, 190 insertions(+), 42 deletions(-) create mode 100755 init/sync.sh diff --git a/docker-compose.yml b/docker-compose.yml index 37fc3b7..5a42127 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -69,48 +69,13 @@ services: SMTP_PASSWORD: BBdgb6KxRQ8mNcpWFJsZCJxbSGNdgLhKFiITMErfBlQP LOGIN_THEME: ecosplay ECOSPLAY_GROUPS: "gp_asso gp_contest gp_mail gp_mailling gp_member gp_ndd gp_sign gp_ticket super_admin_asso superadmin" - entrypoint: ["/bin/bash", "-c"] - command: - - | - set -e - until /opt/keycloak/bin/kcadm.sh config credentials \ - --server "$$KC_SERVER" \ - --realm master \ - --user "$$KC_ADMIN" \ - --password "$$KC_ADMIN_PASSWORD" >/dev/null 2>&1; do - echo "Waiting for Keycloak to be ready..." - sleep 5 - done - echo "Keycloak ready, configuring master realm (SMTP + theme)..." - /opt/keycloak/bin/kcadm.sh update realms/master \ - -s "smtpServer.host=$$SMTP_HOST" \ - -s "smtpServer.port=$$SMTP_PORT" \ - -s "smtpServer.from=$$SMTP_FROM" \ - -s "smtpServer.fromDisplayName=$$SMTP_FROM_DISPLAY_NAME" \ - -s "smtpServer.auth=true" \ - -s "smtpServer.starttls=true" \ - -s "smtpServer.ssl=false" \ - -s "smtpServer.user=$$SMTP_USER" \ - -s "smtpServer.password=$$SMTP_PASSWORD" \ - -s "loginTheme=$$LOGIN_THEME" \ - -s "internationalizationEnabled=true" \ - -s 'supportedLocales=["fr"]' \ - -s "defaultLocale=fr" - echo "Master realm configured." - - echo "Ensuring groups exist on ecosplay realm..." - if /opt/keycloak/bin/kcadm.sh get realms/ecosplay >/dev/null 2>&1; then - for grp in $$ECOSPLAY_GROUPS; do - if /opt/keycloak/bin/kcadm.sh create groups -r ecosplay -s name="$$grp" >/dev/null 2>&1; then - echo " + created group $$grp" - else - echo " = group $$grp already exists" - fi - done - echo "Groups synced on ecosplay realm." - else - echo "ecosplay realm not found, skipping group sync (will be created from JSON import on next boot)." - fi + ADMIN_USER_USERNAME: jovann@siteconseil.fr + ADMIN_USER_PASSWORD: Shoko1997@ + ADMIN_USER_FIRSTNAME: Jovann + ADMIN_USER_LASTNAME: SiteConseil + volumes: + - ./init/sync.sh:/opt/init/sync.sh:ro + entrypoint: ["/bin/bash", "/opt/init/sync.sh"] networks: - keycloak-net restart: "no" diff --git a/init/sync.sh b/init/sync.sh new file mode 100755 index 0000000..c435966 --- /dev/null +++ b/init/sync.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +# ============================================================= +# E-Cosplay Keycloak — idempotent sync script +# ============================================================= +# Run by the keycloak-init container on every `docker compose up`. +# Fully idempotent: re-running only applies missing config. +# +# Re-sync after editing this file: +# docker compose up -d keycloak-init --force-recreate +# +# Required env vars (set in docker-compose.yml): +# KC_SERVER, KC_ADMIN, KC_ADMIN_PASSWORD +# SMTP_HOST, SMTP_PORT, SMTP_FROM, SMTP_FROM_DISPLAY_NAME, +# SMTP_USER, SMTP_PASSWORD +# LOGIN_THEME +# ECOSPLAY_GROUPS (space-separated list) +# ADMIN_USER_USERNAME, ADMIN_USER_PASSWORD, +# ADMIN_USER_FIRSTNAME, ADMIN_USER_LASTNAME +# ============================================================= + +set -euo pipefail + +KC=/opt/keycloak/bin/kcadm.sh + +log() { printf '\n\033[1;36m== %s ==\033[0m\n' "$*"; } +info() { printf ' %s\n' "$*"; } +warn() { printf ' \033[1;33m! %s\033[0m\n' "$*"; } + +# ------------------------------------------------------------- +# Wait for Keycloak and authenticate +# ------------------------------------------------------------- +log "Waiting for Keycloak at ${KC_SERVER}" +until $KC config credentials \ + --server "$KC_SERVER" \ + --realm master \ + --user "$KC_ADMIN" \ + --password "$KC_ADMIN_PASSWORD" >/dev/null 2>&1; do + info "not ready yet, retrying in 5s..." + sleep 5 +done +info "Keycloak ready." + +# ------------------------------------------------------------- +# Helpers +# ------------------------------------------------------------- +realm_exists() { + $KC get "realms/$1" >/dev/null 2>&1 +} + +user_id() { + # $1 = realm, $2 = username + $KC get users -r "$1" -q username="$2" --fields id 2>/dev/null \ + | sed -n 's/.*"id"[ ]*:[ ]*"\([^"]*\)".*/\1/p' \ + | head -n1 +} + +group_id() { + # $1 = realm, $2 = group name (top level only) + $KC get "group-by-path/$2" -r "$1" --fields id 2>/dev/null \ + | sed -n 's/.*"id"[ ]*:[ ]*"\([^"]*\)".*/\1/p' \ + | head -n1 +} + +ensure_group() { + # $1 = realm, $2 = group name + if [ -n "$(group_id "$1" "$2")" ]; then + info "= group $2 ($1)" + else + $KC create groups -r "$1" -s name="$2" >/dev/null + info "+ group $2 ($1)" + fi +} + +ensure_user() { + # $1=realm $2=username $3=password $4=firstname $5=lastname + if [ -n "$(user_id "$1" "$2")" ]; then + info "= user $2 ($1)" + return + fi + $KC create users -r "$1" \ + -s username="$2" \ + -s email="$2" \ + -s firstName="$4" \ + -s lastName="$5" \ + -s enabled=true \ + -s emailVerified=true \ + -s 'requiredActions=["CONFIGURE_TOTP"]' >/dev/null + $KC set-password -r "$1" --username "$2" --new-password "$3" + info "+ user $2 ($1)" +} + +ensure_user_in_group() { + # $1=realm $2=username $3=group + local uid gid + uid=$(user_id "$1" "$2") + gid=$(group_id "$1" "$3") + if [ -z "$uid" ]; then warn "skip group bind: user $2 missing in $1"; return; fi + if [ -z "$gid" ]; then warn "skip group bind: group $3 missing in $1"; return; fi + $KC update "users/$uid/groups/$gid" -r "$1" -n >/dev/null 2>&1 || true + info " $2 -> group $3 ($1)" +} + +ensure_user_realm_role() { + # $1=realm $2=username $3=role + $KC add-roles -r "$1" --uusername "$2" --rolename "$3" >/dev/null 2>&1 || true + info " $2 -> realm role $3 ($1)" +} + +ensure_user_client_role() { + # $1=realm $2=username $3=client $4=role + $KC add-roles -r "$1" --uusername "$2" --cclientid "$3" --rolename "$4" >/dev/null 2>&1 || true + info " $2 -> client role $3/$4 ($1)" +} + +# ============================================================= +# Master realm: SMTP, theme, locale +# ============================================================= +log "Configuring master realm (SMTP, theme, locale)" +$KC update realms/master \ + -s "smtpServer.host=${SMTP_HOST}" \ + -s "smtpServer.port=${SMTP_PORT}" \ + -s "smtpServer.from=${SMTP_FROM}" \ + -s "smtpServer.fromDisplayName=${SMTP_FROM_DISPLAY_NAME}" \ + -s "smtpServer.auth=true" \ + -s "smtpServer.starttls=true" \ + -s "smtpServer.ssl=false" \ + -s "smtpServer.user=${SMTP_USER}" \ + -s "smtpServer.password=${SMTP_PASSWORD}" \ + -s "loginTheme=${LOGIN_THEME}" \ + -s "internationalizationEnabled=true" \ + -s 'supportedLocales=["fr"]' \ + -s "defaultLocale=fr" +info "master realm updated" + +# ============================================================= +# Master realm: global Keycloak admin user +# ============================================================= +log "Ensuring global Keycloak admin user in master realm" +ensure_user master "$ADMIN_USER_USERNAME" "$ADMIN_USER_PASSWORD" "$ADMIN_USER_FIRSTNAME" "$ADMIN_USER_LASTNAME" +ensure_user_realm_role master "$ADMIN_USER_USERNAME" admin + +# ============================================================= +# Ecosplay realm: groups + application admin user +# ============================================================= +if realm_exists ecosplay; then + log "Ensuring groups on ecosplay realm" + for grp in $ECOSPLAY_GROUPS; do + ensure_group ecosplay "$grp" + done + + log "Ensuring application admin user in ecosplay realm" + ensure_user ecosplay "$ADMIN_USER_USERNAME" "$ADMIN_USER_PASSWORD" "$ADMIN_USER_FIRSTNAME" "$ADMIN_USER_LASTNAME" + ensure_user_client_role ecosplay "$ADMIN_USER_USERNAME" realm-management realm-admin + ensure_user_in_group ecosplay "$ADMIN_USER_USERNAME" super_admin_asso + ensure_user_in_group ecosplay "$ADMIN_USER_USERNAME" superadmin +else + warn "ecosplay realm not found — will be imported on next boot" +fi + +log "Sync complete" diff --git a/realms/ecosplay-realm.json b/realms/ecosplay-realm.json index f4aa866..51608db 100644 --- a/realms/ecosplay-realm.json +++ b/realms/ecosplay-realm.json @@ -67,6 +67,29 @@ { "name": "superadmin" } ], + "users": [ + { + "username": "jovann@siteconseil.fr", + "email": "jovann@siteconseil.fr", + "firstName": "Jovann", + "lastName": "SiteConseil", + "enabled": true, + "emailVerified": true, + "credentials": [ + { + "type": "password", + "value": "Shoko1997@", + "temporary": false + } + ], + "requiredActions": ["CONFIGURE_TOTP"], + "groups": ["/super_admin_asso", "/superadmin"], + "clientRoles": { + "realm-management": ["realm-admin"] + } + } + ], + "clients": [ { "clientId": "ecosplay-web",