mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-11-21 12:36:24 +00:00
Compare commits
1 Commits
3da645f3b8
...
feature/ke
| Author | SHA1 | Date | |
|---|---|---|---|
| 6fcf6a1ab6 |
@@ -5,6 +5,6 @@ MODE_DUMMY: false # Executes dummy/test routines instead
|
|||||||
MODE_UPDATE: true # Executes updates
|
MODE_UPDATE: true # Executes updates
|
||||||
MODE_DEBUG: false # This enables debugging in ansible and in the apps, You SHOULD NOT enable this on production servers
|
MODE_DEBUG: false # This enables debugging in ansible and in the apps, You SHOULD NOT enable this on production servers
|
||||||
MODE_RESET: false # Cleans up all Infinito.Nexus files. It's necessary to run to whole playbook and not particial roles when using this function.
|
MODE_RESET: false # Cleans up all Infinito.Nexus files. It's necessary to run to whole playbook and not particial roles when using this function.
|
||||||
MODE_CLEANUP: true # Cleanup unused files and configurations
|
MODE_CLEANUP: "{{ MODE_DEBUG | bool }}" # Cleanup unused files and configurations
|
||||||
MODE_ASSERT: "{{ MODE_DEBUG | bool }}" # Executes validation tasks during the run.
|
MODE_ASSERT: "{{ MODE_DEBUG | bool }}" # Executes validation tasks during the run.
|
||||||
MODE_BACKUP: true # Executes the Backup before the deployment
|
MODE_BACKUP: true # Executes the Backup before the deployment
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
- name: Include dependencies
|
- name: Include dependencies
|
||||||
include_role:
|
include_role:
|
||||||
name: "sys-svc-msmtp"
|
name: '{{ item }}'
|
||||||
when: run_once_sys_svc_msmtp is not defined or run_once_sys_svc_msmtp is false
|
loop:
|
||||||
|
- sys-svc-msmtp
|
||||||
|
|
||||||
- include_role:
|
- include_role:
|
||||||
name: sys-service
|
name: sys-service
|
||||||
|
|||||||
@@ -39,18 +39,6 @@ if [ "$force_freeing" = true ]; then
|
|||||||
docker exec -u www-data $nextcloud_application_container /var/www/html/occ versions:cleanup || exit 6
|
docker exec -u www-data $nextcloud_application_container /var/www/html/occ versions:cleanup || exit 6
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Mastodon cleanup (remote media cache)
|
|
||||||
mastodon_application_container="{{ applications | get_app_conf('web-app-mastodon', 'docker.services.mastodon.name') }}"
|
|
||||||
mastodon_cleanup_days="1"
|
|
||||||
|
|
||||||
if [ -n "$mastodon_application_container" ] && docker ps -a --format '{% raw %}{{.Names}}{% endraw %}' | grep -qw "$mastodon_application_container"; then
|
|
||||||
echo "Cleaning up Mastodon media cache (older than ${mastodon_cleanup_days} days)" &&
|
|
||||||
docker exec -u root "$mastodon_application_container" bash -lc "bin/tootctl media remove --days=${mastodon_cleanup_days}" || exit 8
|
|
||||||
|
|
||||||
# Optional: additionally remove local thumbnail/cache files older than X days
|
|
||||||
# Warning: these will be regenerated when accessed, which may cause extra CPU/I/O load
|
|
||||||
# docker exec -u root "$mastodon_application_container" bash -lc "find /mastodon/public/system/cache -type f -mtime +${mastodon_cleanup_days} -delete" || exit 9
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if command -v pacman >/dev/null 2>&1 ; then
|
if command -v pacman >/dev/null 2>&1 ; then
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
- name: Include dependency 'web-svc-logout'
|
- name: Include dependency 'sys-svc-webserver-core'
|
||||||
include_role:
|
include_role:
|
||||||
name: web-svc-logout
|
name: sys-svc-webserver-core
|
||||||
when:
|
when:
|
||||||
- run_once_web_svc_logout is not defined
|
- run_once_sys_svc_webserver_core is not defined
|
||||||
|
|
||||||
- name: "deploy the logout.js"
|
- name: "deploy the logout.js"
|
||||||
include_tasks: "02_deploy.yml"
|
include_tasks: "02_deploy.yml"
|
||||||
|
|
||||||
- set_fact:
|
|
||||||
run_once_sys_front_inj_logout: true
|
|
||||||
changed_when: false
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
- name: "Load base for '{{ application_id }}'"
|
- block:
|
||||||
include_tasks: 01_core.yml
|
- include_tasks: 01_core.yml
|
||||||
|
- set_fact:
|
||||||
|
run_once_sys_front_inj_logout: true
|
||||||
when: run_once_sys_front_inj_logout is not defined
|
when: run_once_sys_front_inj_logout is not defined
|
||||||
|
|
||||||
- name: "Load logout code for '{{ application_id }}'"
|
- name: "Load logout code for '{{ application_id }}'"
|
||||||
|
|||||||
@@ -15,6 +15,3 @@
|
|||||||
- include_role:
|
- include_role:
|
||||||
name: sys-ctl-hlth-msmtp
|
name: sys-ctl-hlth-msmtp
|
||||||
when: run_once_sys_ctl_hlth_msmtp is not defined
|
when: run_once_sys_ctl_hlth_msmtp is not defined
|
||||||
|
|
||||||
- set_fact:
|
|
||||||
run_once_sys_svc_msmtp: true
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
- name: "Load MSMTP Core Once"
|
- block:
|
||||||
include_tasks: 01_core.yml
|
- include_tasks: 01_core.yml
|
||||||
when:
|
- set_fact:
|
||||||
- run_once_sys_svc_msmtp is not defined or run_once_sys_svc_msmtp is false
|
run_once_sys_svc_msmtp: true
|
||||||
# Just execute when mailu_token is defined
|
when: run_once_sys_svc_msmtp is not defined
|
||||||
- users['no-reply'].mailu_token is defined
|
|
||||||
@@ -5,7 +5,7 @@ users:
|
|||||||
username: "{{ PRIMARY_DOMAIN.split('.')[0] }}"
|
username: "{{ PRIMARY_DOMAIN.split('.')[0] }}"
|
||||||
tld:
|
tld:
|
||||||
description: "Auto Generated Account to reserve the TLD"
|
description: "Auto Generated Account to reserve the TLD"
|
||||||
username: "{{ PRIMARY_DOMAIN.split('.')[1] if (PRIMARY_DOMAIN is defined and (PRIMARY_DOMAIN.split('.') | length) > 1) else (PRIMARY_DOMAIN ~ '_tld ') }}"
|
username: "{{ PRIMARY_DOMAIN.split('.')[1] }}"
|
||||||
root:
|
root:
|
||||||
username: root
|
username: root
|
||||||
uid: 0
|
uid: 0
|
||||||
|
|||||||
@@ -43,10 +43,9 @@ plugins:
|
|||||||
enabled: true
|
enabled: true
|
||||||
discourse-akismet:
|
discourse-akismet:
|
||||||
enabled: true
|
enabled: true
|
||||||
# The following plugins moved to the default setup
|
discourse-cakeday:
|
||||||
# discourse-cakeday:
|
enabled: true
|
||||||
# enabled: true
|
# discourse-solved: Seems like this plugin is now also part of the default setup
|
||||||
# discourse-solved:
|
|
||||||
# enabled: true
|
# enabled: true
|
||||||
# discourse-voting:
|
# discourse-voting:
|
||||||
# enabled: true
|
# enabled: true
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# POSIX-safe entrypoint for EspoCRM container
|
set -euo pipefail
|
||||||
# Compatible with /bin/sh (dash/busybox). Avoids 'pipefail' and non-portable features.
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
log() { printf '%s %s\n' "[entrypoint]" "$*" >&2; }
|
log() { printf '%s %s\n' "[entrypoint]" "$*" >&2; }
|
||||||
|
|
||||||
# --- Simple boolean normalization --------------------------------------------
|
# --- Simple boolean normalization --------------------------------------------
|
||||||
bool_norm () {
|
bool_norm () {
|
||||||
v="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]' 2>/dev/null || true)"
|
v="$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')"
|
||||||
case "$v" in
|
case "$v" in
|
||||||
1|true|yes|on) echo "true" ;;
|
1|true|yes|on) echo "true" ;;
|
||||||
0|false|no|off|"") echo "false" ;;
|
0|false|no|off|"") echo "false" ;;
|
||||||
@@ -15,45 +13,30 @@ bool_norm () {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Environment initialization ----------------------------------------------
|
# Expected ENV (from env.j2)
|
||||||
MAINTENANCE="$(bool_norm "${ESPO_INIT_MAINTENANCE_MODE:-false}")"
|
MAINTENANCE="$(bool_norm "${ESPO_INIT_MAINTENANCE_MODE:-false}")"
|
||||||
CRON_DISABLED="$(bool_norm "${ESPO_INIT_CRON_DISABLED:-false}")"
|
CRON_DISABLED="$(bool_norm "${ESPO_INIT_CRON_DISABLED:-false}")"
|
||||||
USE_CACHE="$(bool_norm "${ESPO_INIT_USE_CACHE:-true}")"
|
USE_CACHE="$(bool_norm "${ESPO_INIT_USE_CACHE:-true}")"
|
||||||
|
|
||||||
APP_DIR="/var/www/html"
|
APP_DIR="/var/www/html"
|
||||||
|
SET_FLAGS_SCRIPT="${ESPOCRM_SET_FLAGS_SCRIPT}"
|
||||||
# Provided by env.j2 (fallback ensures robustness)
|
|
||||||
SET_FLAGS_SCRIPT="${ESPOCRM_SET_FLAGS_SCRIPT:-/usr/local/bin/set_flags.php}"
|
|
||||||
if [ ! -f "$SET_FLAGS_SCRIPT" ]; then
|
|
||||||
log "WARN: SET_FLAGS_SCRIPT '$SET_FLAGS_SCRIPT' not found; falling back to /usr/local/bin/set_flags.php"
|
|
||||||
SET_FLAGS_SCRIPT="/usr/local/bin/set_flags.php"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- Wait for bootstrap.php (max 60s, e.g. fresh volume) ----------------------
|
# --- Wait for bootstrap.php (max 60s, e.g. fresh volume) ----------------------
|
||||||
log "Waiting for ${APP_DIR}/bootstrap.php..."
|
log "Waiting for ${APP_DIR}/bootstrap.php..."
|
||||||
count=0
|
for i in $(seq 1 60); do
|
||||||
while [ $count -lt 60 ] && [ ! -f "${APP_DIR}/bootstrap.php" ]; do
|
[ -f "${APP_DIR}/bootstrap.php" ] && break
|
||||||
sleep 1
|
sleep 1
|
||||||
count=$((count + 1))
|
|
||||||
done
|
done
|
||||||
if [ ! -f "${APP_DIR}/bootstrap.php" ]; then
|
if [ ! -f "${APP_DIR}/bootstrap.php" ]; then
|
||||||
log "ERROR: bootstrap.php missing after 60s"
|
log "ERROR: bootstrap.php missing after 60s"; exit 1
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Apply config flags via set_flags.php ------------------------------------
|
# --- Apply config flags via set_flags.php ------------------------------------
|
||||||
log "Applying runtime flags via set_flags.php..."
|
log "Applying runtime flags via set_flags.php..."
|
||||||
if ! php "${SET_FLAGS_SCRIPT}"; then
|
php "${SET_FLAGS_SCRIPT}"
|
||||||
log "ERROR: set_flags.php execution failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- Clear cache (safe) -------------------------------------------------------
|
# --- Clear cache (safe) -------------------------------------------------------
|
||||||
if php "${APP_DIR}/clear_cache.php" 2>/dev/null; then
|
php "${APP_DIR}/clear_cache.php" || true
|
||||||
log "Cache cleared successfully."
|
|
||||||
else
|
|
||||||
log "WARN: Cache clearing skipped or failed (non-critical)."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- Hand off to CMD ----------------------------------------------------------
|
# --- Hand off to CMD ----------------------------------------------------------
|
||||||
if [ "$#" -gt 0 ]; then
|
if [ "$#" -gt 0 ]; then
|
||||||
@@ -73,6 +56,5 @@ for cmd in apache2-foreground httpd-foreground php-fpm php-fpm8.3 php-fpm8.2 sup
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# --- Fallback ---------------------------------------------------------------
|
|
||||||
log "No known server command found; tailing to keep container alive."
|
log "No known server command found; tailing to keep container alive."
|
||||||
exec tail -f /dev/null
|
exec tail -f /dev/null
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
load_dependencies: True # When set to false the dependencies aren't loaded. Helpful for developing
|
load_dependencies: True # When set to false the dependencies aren't loaded. Helpful for developing
|
||||||
actions:
|
actions:
|
||||||
import_realm: True # Import REALM
|
import_realm: True # Import REALM
|
||||||
|
create_automation_client: True
|
||||||
features:
|
features:
|
||||||
matomo: true
|
matomo: true
|
||||||
css: true
|
css: true
|
||||||
@@ -50,9 +51,3 @@ credentials:
|
|||||||
recaptcha:
|
recaptcha:
|
||||||
website_key: "" # Required if you enabled recaptcha:
|
website_key: "" # Required if you enabled recaptcha:
|
||||||
secret_key: "" # Required if you enabled recaptcha:
|
secret_key: "" # Required if you enabled recaptcha:
|
||||||
|
|
||||||
accounts:
|
|
||||||
bootstrap:
|
|
||||||
username: "administrator"
|
|
||||||
system:
|
|
||||||
username: "{{ SOFTWARE_NAME | replace('.', '_') | lower }}"
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
- name: "Wait until '{{ KEYCLOAK_CONTAINER }}' container is healthy"
|
|
||||||
community.docker.docker_container_info:
|
|
||||||
name: "{{ KEYCLOAK_CONTAINER }}"
|
|
||||||
register: kc_info
|
|
||||||
retries: 60
|
|
||||||
delay: 5
|
|
||||||
until: >
|
|
||||||
kc_info is succeeded and
|
|
||||||
(kc_info.container | default({})) != {} and
|
|
||||||
(kc_info.container.State | default({})) != {} and
|
|
||||||
(kc_info.container.State.Health | default({})) != {} and
|
|
||||||
(kc_info.container.State.Health.Status | default('')) == 'healthy'
|
|
||||||
|
|
||||||
- name: Ensure permanent Keycloak admin exists and can log in (container env only)
|
|
||||||
block:
|
|
||||||
|
|
||||||
- name: Try login with permanent admin (uses container ENV)
|
|
||||||
shell: |
|
|
||||||
{{ KEYCLOAK_EXEC_CONTAINER }} sh -lc '
|
|
||||||
{{ KEYCLOAK_KCADM }} config credentials \
|
|
||||||
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }} \
|
|
||||||
--realm master \
|
|
||||||
--user "$KEYCLOAK_PERMANENT_ADMIN_USERNAME" \
|
|
||||||
--password "$KEYCLOAK_PERMANENT_ADMIN_PASSWORD"
|
|
||||||
'
|
|
||||||
register: kc_login_perm
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
rescue:
|
|
||||||
|
|
||||||
- name: Login with bootstrap admin (uses container ENV)
|
|
||||||
shell: |
|
|
||||||
{{ KEYCLOAK_EXEC_CONTAINER }} sh -lc '
|
|
||||||
{{ KEYCLOAK_KCADM }} config credentials \
|
|
||||||
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }} \
|
|
||||||
--realm master \
|
|
||||||
--user "$KC_BOOTSTRAP_ADMIN_USERNAME" \
|
|
||||||
--password "$KC_BOOTSTRAP_ADMIN_PASSWORD"
|
|
||||||
'
|
|
||||||
register: kc_login_bootstrap
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Ensure permanent admin user exists (create if missing)
|
|
||||||
shell: |
|
|
||||||
{{ KEYCLOAK_EXEC_CONTAINER }} sh -lc '
|
|
||||||
{{ KEYCLOAK_KCADM }} create users -r master \
|
|
||||||
-s "username=$KEYCLOAK_PERMANENT_ADMIN_USERNAME" \
|
|
||||||
-s "enabled=true"
|
|
||||||
'
|
|
||||||
register: kc_create_perm_admin
|
|
||||||
failed_when: >
|
|
||||||
not (
|
|
||||||
kc_create_perm_admin.rc == 0 or
|
|
||||||
(kc_create_perm_admin.stderr is defined and
|
|
||||||
('User exists with same username' in kc_create_perm_admin.stderr))
|
|
||||||
)
|
|
||||||
changed_when: kc_create_perm_admin.rc == 0
|
|
||||||
|
|
||||||
- name: Set permanent admin password (by username, no ID needed)
|
|
||||||
shell: |
|
|
||||||
{{ KEYCLOAK_EXEC_CONTAINER }} sh -lc '
|
|
||||||
{{ KEYCLOAK_KCADM }} set-password -r master \
|
|
||||||
--username "$KEYCLOAK_PERMANENT_ADMIN_USERNAME" \
|
|
||||||
--new-password "$KEYCLOAK_PERMANENT_ADMIN_PASSWORD"
|
|
||||||
'
|
|
||||||
changed_when: true
|
|
||||||
|
|
||||||
- name: Grant global admin via master realm role 'admin'
|
|
||||||
shell: |
|
|
||||||
{{ KEYCLOAK_EXEC_CONTAINER }} sh -lc '
|
|
||||||
{{ KEYCLOAK_KCADM }} add-roles -r master \
|
|
||||||
--uusername "$KEYCLOAK_PERMANENT_ADMIN_USERNAME" \
|
|
||||||
--rolename admin
|
|
||||||
'
|
|
||||||
register: kc_grant_master_admin
|
|
||||||
changed_when: (kc_grant_master_admin.stderr is defined and kc_grant_master_admin.stderr | length > 0) or
|
|
||||||
(kc_grant_master_admin.stdout is defined and kc_grant_master_admin.stdout | length > 0)
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Verify login with permanent admin (after creation)
|
|
||||||
shell: |
|
|
||||||
{{ KEYCLOAK_EXEC_CONTAINER }} sh -lc '
|
|
||||||
{{ KEYCLOAK_KCADM }} config credentials \
|
|
||||||
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }} \
|
|
||||||
--realm master \
|
|
||||||
--user "$KEYCLOAK_PERMANENT_ADMIN_USERNAME" \
|
|
||||||
--password "$KEYCLOAK_PERMANENT_ADMIN_PASSWORD"
|
|
||||||
'
|
|
||||||
changed_when: false
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# --- Ensure RBAC client scope exists (idempotent) ---
|
||||||
- name: Ensure RBAC client scope exists
|
- name: Ensure RBAC client scope exists
|
||||||
shell: |
|
shell: |
|
||||||
cat <<'JSON' | {{ KEYCLOAK_EXEC_KCADM }} create client-scopes -r {{ KEYCLOAK_REALM }} -f -
|
cat <<'JSON' | {{ KEYCLOAK_EXEC_KCADM }} create client-scopes -r {{ KEYCLOAK_REALM }} -f -
|
||||||
@@ -15,10 +16,12 @@
|
|||||||
('already exists' not in (create_rbac_scope.stderr | lower))
|
('already exists' not in (create_rbac_scope.stderr | lower))
|
||||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||||
|
|
||||||
|
# --- Get the scope id we will attach to the client ---
|
||||||
- name: Get all client scopes
|
- name: Get all client scopes
|
||||||
shell: "{{ KEYCLOAK_EXEC_KCADM }} get client-scopes -r {{ KEYCLOAK_REALM }} --format json"
|
shell: "{{ KEYCLOAK_EXEC_KCADM }} get client-scopes -r {{ KEYCLOAK_REALM }} --format json"
|
||||||
register: all_scopes
|
register: all_scopes
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
failed_when: "'HTTP 401' in (all_scopes.stderr | default(''))"
|
||||||
|
|
||||||
- name: Extract RBAC scope id
|
- name: Extract RBAC scope id
|
||||||
set_fact:
|
set_fact:
|
||||||
63
roles/web-app-keycloak/tasks/05a_service_account.yml
Normal file
63
roles/web-app-keycloak/tasks/05a_service_account.yml
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Creates a confidential client with service account, fetches the secret,
|
||||||
|
# and grants realm-management/realm-admin to its service-account user.
|
||||||
|
|
||||||
|
- name: "Ensure automation client exists (confidential + service accounts)"
|
||||||
|
shell: |
|
||||||
|
{{ KEYCLOAK_EXEC_KCADM }} create clients -r {{ KEYCLOAK_REALM }} \
|
||||||
|
-s clientId={{ KEYCLOAK_AUTOMATION_CLIENT_ID }} \
|
||||||
|
-s protocol=openid-connect \
|
||||||
|
-s publicClient=false \
|
||||||
|
-s serviceAccountsEnabled=true \
|
||||||
|
-s directAccessGrantsEnabled=false
|
||||||
|
register: create_client
|
||||||
|
changed_when: create_client.rc == 0
|
||||||
|
failed_when: create_client.rc != 0 and ('already exists' not in (create_client.stderr | lower))
|
||||||
|
|
||||||
|
- name: "Resolve automation client id"
|
||||||
|
shell: >
|
||||||
|
{{ KEYCLOAK_EXEC_KCADM }} get clients -r {{ KEYCLOAK_REALM }}
|
||||||
|
--query 'clientId={{ KEYCLOAK_AUTOMATION_CLIENT_ID }}' --fields id --format json | jq -r '.[0].id'
|
||||||
|
register: auto_client_id_cmd
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: "Fail if client id could not be resolved"
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "(auto_client_id_cmd.stdout | trim) is match('^[0-9a-f-]+$')"
|
||||||
|
fail_msg: "Automation client id could not be resolved."
|
||||||
|
|
||||||
|
- name: "Read client secret"
|
||||||
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||||
|
shell: >
|
||||||
|
{{ KEYCLOAK_EXEC_KCADM }} get clients/{{ auto_client_id_cmd.stdout | trim }}/client-secret
|
||||||
|
-r {{ KEYCLOAK_REALM }} --format json | jq -r .value
|
||||||
|
register: auto_client_secret_cmd
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: "Expose client secret as a fact"
|
||||||
|
set_fact:
|
||||||
|
KEYCLOAK_AUTOMATION_CLIENT_SECRET: "{{ auto_client_secret_cmd.stdout | trim }}"
|
||||||
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||||
|
|
||||||
|
- name: "Grant {{ KEYCLOAK_AUTOMATION_GRANT_ROLE }} to service account"
|
||||||
|
shell: >
|
||||||
|
{{ KEYCLOAK_EXEC_KCADM }} add-roles -r {{ KEYCLOAK_REALM }}
|
||||||
|
--uusername service-account-{{ KEYCLOAK_AUTOMATION_CLIENT_ID }}
|
||||||
|
--cclientid realm-management
|
||||||
|
--rolename {{ KEYCLOAK_AUTOMATION_GRANT_ROLE }}
|
||||||
|
register: grant_role
|
||||||
|
changed_when: grant_role.rc == 0
|
||||||
|
failed_when: grant_role.rc != 0 and ('already exists' not in (grant_role.stderr | lower))
|
||||||
|
|
||||||
|
- name: "Verify client-credentials login works"
|
||||||
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||||
|
shell: >
|
||||||
|
{{ KEYCLOAK_EXEC_KCADM }} config credentials
|
||||||
|
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }}
|
||||||
|
--realm {{ KEYCLOAK_REALM }}
|
||||||
|
--client {{ KEYCLOAK_AUTOMATION_CLIENT_ID }}
|
||||||
|
--client-secret {{ KEYCLOAK_AUTOMATION_CLIENT_SECRET }} &&
|
||||||
|
{{ KEYCLOAK_EXEC_KCADM }} get realms/{{ KEYCLOAK_REALM }} --format json | jq -r '.realm'
|
||||||
|
register: verify_cc
|
||||||
|
changed_when: false
|
||||||
|
failed_when: (verify_cc.rc != 0) or ((verify_cc.stdout | trim) != (KEYCLOAK_REALM | trim))
|
||||||
@@ -13,21 +13,118 @@
|
|||||||
include_tasks: 04_dependencies.yml
|
include_tasks: 04_dependencies.yml
|
||||||
when: KEYCLOAK_LOAD_DEPENDENCIES | bool
|
when: KEYCLOAK_LOAD_DEPENDENCIES | bool
|
||||||
|
|
||||||
- name: "Load Login routines for '{{ application_id }}'"
|
- name: "Wait until '{{ KEYCLOAK_CONTAINER }}' container is healthy"
|
||||||
include_tasks: 05_login.yml
|
community.docker.docker_container_info:
|
||||||
|
name: "{{ KEYCLOAK_CONTAINER }}"
|
||||||
|
register: kc_info
|
||||||
|
retries: 60
|
||||||
|
delay: 5
|
||||||
|
until: >
|
||||||
|
kc_info is succeeded and
|
||||||
|
(kc_info.container | default({})) != {} and
|
||||||
|
(kc_info.container.State | default({})) != {} and
|
||||||
|
(kc_info.container.State.Health | default({})) != {} and
|
||||||
|
(kc_info.container.State.Health.Status | default('')) == 'healthy'
|
||||||
|
|
||||||
- name: "Load Client Update routines for '{{ application_id }}'"
|
- name: kcadm login (master)
|
||||||
include_tasks: update/01_client.yml
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||||
|
shell: >
|
||||||
|
{{ KEYCLOAK_EXEC_KCADM }} config credentials
|
||||||
|
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }}
|
||||||
|
--realm master
|
||||||
|
--user {{ KEYCLOAK_MASTER_API_USER_NAME }}
|
||||||
|
--password {{ KEYCLOAK_MASTER_API_USER_PASSWORD }}
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: "Load Mail Update routines for '{{ application_id }} - {{ KEYCLOAK_REALM }}'"
|
- name: Verify kcadm session works (quick read)
|
||||||
include_tasks: update/02_mail_realm.yml
|
shell: >
|
||||||
|
{{ KEYCLOAK_EXEC_KCADM }} get realms --format json | jq -r '.[0].realm' | head -n1
|
||||||
|
register: kcadm_verify
|
||||||
|
changed_when: false
|
||||||
|
failed_when: >
|
||||||
|
(kcadm_verify.rc != 0)
|
||||||
|
or ('HTTP 401' in (kcadm_verify.stderr | default('')))
|
||||||
|
or ((kcadm_verify.stdout | trim) == '')
|
||||||
|
|
||||||
- name: "Load Mail Update routines for '{{ application_id }} - master'"
|
# --- Create & grant automation service account (Option A) ---
|
||||||
include_tasks: update/03_mail_master.yml
|
- name: "Ensure automation service account client (Option A)"
|
||||||
|
include_tasks: 05a_service_account.yml
|
||||||
|
when: applications | get_app_conf(application_id, 'actions.create_automation_client', True)
|
||||||
|
|
||||||
- name: "Load RBAC Update routines for '{{ application_id }}'"
|
# --- Switch session to the service account for all subsequent API work ---
|
||||||
include_tasks: update/04_rbac_client_scope.yml
|
- name: kcadm login (realm) using service account
|
||||||
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||||
|
shell: >
|
||||||
|
{{ KEYCLOAK_EXEC_KCADM }} config credentials
|
||||||
|
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }}
|
||||||
|
--realm {{ KEYCLOAK_REALM }}
|
||||||
|
--client {{ KEYCLOAK_AUTOMATION_CLIENT_ID }}
|
||||||
|
--client-secret {{ KEYCLOAK_AUTOMATION_CLIENT_SECRET }}
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: "Load LDAP Update routines for '{{ application_id }}'"
|
- name: Verify kcadm session works (exact realm via service account)
|
||||||
include_tasks: update/05_ldap.yml
|
shell: >
|
||||||
|
{{ KEYCLOAK_EXEC_KCADM }} get realms/{{ KEYCLOAK_REALM }} --format json | jq -r '.realm'
|
||||||
|
register: kcadm_verify_sa
|
||||||
|
changed_when: false
|
||||||
|
failed_when: >
|
||||||
|
(kcadm_verify_sa.rc != 0)
|
||||||
|
or ('HTTP 401' in (kcadm_verify_sa.stderr | default('')))
|
||||||
|
or ((kcadm_verify_sa.stdout | trim) != (KEYCLOAK_REALM | trim))
|
||||||
|
|
||||||
|
- name: "Update Client settings"
|
||||||
|
vars:
|
||||||
|
kc_object_kind: "client"
|
||||||
|
kc_lookup_value: "{{ KEYCLOAK_CLIENT_ID }}"
|
||||||
|
kc_desired: >-
|
||||||
|
{{
|
||||||
|
KEYCLOAK_DICTIONARY_REALM.clients
|
||||||
|
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
|
||||||
|
| list | first
|
||||||
|
}}
|
||||||
|
kc_force_attrs:
|
||||||
|
publicClient: >-
|
||||||
|
{{
|
||||||
|
(KEYCLOAK_DICTIONARY_REALM.clients
|
||||||
|
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
|
||||||
|
| map(attribute='publicClient')
|
||||||
|
| first)
|
||||||
|
}}
|
||||||
|
serviceAccountsEnabled: >-
|
||||||
|
{{
|
||||||
|
(KEYCLOAK_DICTIONARY_REALM.clients
|
||||||
|
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
|
||||||
|
| map(attribute='serviceAccountsEnabled')
|
||||||
|
| first )
|
||||||
|
}}
|
||||||
|
frontchannelLogout: >-
|
||||||
|
{{
|
||||||
|
(KEYCLOAK_DICTIONARY_REALM.clients
|
||||||
|
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
|
||||||
|
| map(attribute='frontchannelLogout')
|
||||||
|
| first)
|
||||||
|
}}
|
||||||
|
attributes: >-
|
||||||
|
{{
|
||||||
|
( (KEYCLOAK_DICTIONARY_REALM.clients
|
||||||
|
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
|
||||||
|
| list | first | default({}) ).attributes | default({}) )
|
||||||
|
| combine({'frontchannel.logout.url': KEYCLOAK_FRONTCHANNEL_LOGOUT_URL}, recursive=True)
|
||||||
|
}}
|
||||||
|
include_tasks: _update.yml
|
||||||
|
|
||||||
|
- name: "Update REALM mail settings from realm dictionary (SPOT)"
|
||||||
|
include_tasks: _update.yml
|
||||||
|
vars:
|
||||||
|
kc_object_kind: "realm"
|
||||||
|
kc_lookup_field: "id"
|
||||||
|
kc_lookup_value: "{{ KEYCLOAK_REALM }}"
|
||||||
|
kc_desired:
|
||||||
|
smtpServer: "{{ KEYCLOAK_DICTIONARY_REALM.smtpServer | default({}, true) }}"
|
||||||
|
kc_merge_path: "smtpServer"
|
||||||
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||||
|
|
||||||
|
- include_tasks: 05_rbac_client_scope.yml
|
||||||
|
|
||||||
|
- include_tasks: 06_ldap.yml
|
||||||
when: KEYCLOAK_LDAP_ENABLED | bool
|
when: KEYCLOAK_LDAP_ENABLED | bool
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
- name: "Update Client settings"
|
|
||||||
vars:
|
|
||||||
kc_object_kind: "client"
|
|
||||||
kc_lookup_value: "{{ KEYCLOAK_CLIENT_ID }}"
|
|
||||||
kc_desired: >-
|
|
||||||
{{
|
|
||||||
KEYCLOAK_DICTIONARY_REALM.clients
|
|
||||||
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
|
|
||||||
| list | first
|
|
||||||
}}
|
|
||||||
kc_force_attrs:
|
|
||||||
publicClient: >-
|
|
||||||
{{
|
|
||||||
(KEYCLOAK_DICTIONARY_REALM.clients
|
|
||||||
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
|
|
||||||
| map(attribute='publicClient')
|
|
||||||
| first)
|
|
||||||
}}
|
|
||||||
serviceAccountsEnabled: >-
|
|
||||||
{{
|
|
||||||
(KEYCLOAK_DICTIONARY_REALM.clients
|
|
||||||
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
|
|
||||||
| map(attribute='serviceAccountsEnabled')
|
|
||||||
| first )
|
|
||||||
}}
|
|
||||||
frontchannelLogout: >-
|
|
||||||
{{
|
|
||||||
(KEYCLOAK_DICTIONARY_REALM.clients
|
|
||||||
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
|
|
||||||
| map(attribute='frontchannelLogout')
|
|
||||||
| first)
|
|
||||||
}}
|
|
||||||
attributes: >-
|
|
||||||
{{
|
|
||||||
( (KEYCLOAK_DICTIONARY_REALM.clients
|
|
||||||
| selectattr('clientId','equalto', KEYCLOAK_CLIENT_ID)
|
|
||||||
| list | first | default({}) ).attributes | default({}) )
|
|
||||||
| combine({'frontchannel.logout.url': KEYCLOAK_FRONTCHANNEL_LOGOUT_URL}, recursive=True)
|
|
||||||
}}
|
|
||||||
include_tasks: _update.yml
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
- name: "Update {{ KEYCLOAK_REALM }} REALM mail settings from realm dictionary"
|
|
||||||
include_tasks: _update.yml
|
|
||||||
vars:
|
|
||||||
kc_object_kind: "realm"
|
|
||||||
kc_lookup_field: "id"
|
|
||||||
kc_lookup_value: "{{ KEYCLOAK_REALM }}"
|
|
||||||
kc_desired:
|
|
||||||
smtpServer: "{{ KEYCLOAK_DICTIONARY_REALM.smtpServer | default({}, true) }}"
|
|
||||||
kc_merge_path: "smtpServer"
|
|
||||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
- name: "Update Master REALM mail settings from realm dictionary"
|
|
||||||
include_tasks: _update.yml
|
|
||||||
vars:
|
|
||||||
kc_object_kind: "realm"
|
|
||||||
kc_lookup_field: "id"
|
|
||||||
kc_lookup_value: "master"
|
|
||||||
kc_desired:
|
|
||||||
smtpServer: "{{ KEYCLOAK_DICTIONARY_REALM.smtpServer | default({}, true) }}"
|
|
||||||
kc_merge_path: "smtpServer"
|
|
||||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
|
||||||
@@ -10,21 +10,19 @@ KC_HTTP_ENABLED= true
|
|||||||
KC_HEALTH_ENABLED= {{ KEYCLOAK_HEALTH_ENABLED | lower }}
|
KC_HEALTH_ENABLED= {{ KEYCLOAK_HEALTH_ENABLED | lower }}
|
||||||
KC_METRICS_ENABLED= true
|
KC_METRICS_ENABLED= true
|
||||||
|
|
||||||
|
# Administrator
|
||||||
|
KEYCLOAK_ADMIN= "{{ KEYCLOAK_ADMIN }}"
|
||||||
|
KEYCLOAK_ADMIN_PASSWORD= "{{ KEYCLOAK_ADMIN_PASSWORD }}"
|
||||||
|
|
||||||
# Database
|
# Database
|
||||||
KC_DB= {{ database_type }}
|
KC_DB= {{ database_type }}
|
||||||
KC_DB_URL= {{ database_url_jdbc }}
|
KC_DB_URL= {{ database_url_jdbc }}
|
||||||
KC_DB_USERNAME= {{ database_username }}
|
KC_DB_USERNAME= {{ database_username }}
|
||||||
KC_DB_PASSWORD= {{ database_password }}
|
KC_DB_PASSWORD= {{ database_password }}
|
||||||
|
|
||||||
# Credentials
|
# If the initial administrator already exists and the environment variables are still present at startup, an error message stating the failed creation of the initial administrator is shown in the logs. Keycloak ignores the values and starts up correctly.
|
||||||
|
KC_BOOTSTRAP_ADMIN_USERNAME= "{{ KEYCLOAK_ADMIN }}"
|
||||||
## Bootstrap
|
KC_BOOTSTRAP_ADMIN_PASSWORD= "{{ KEYCLOAK_ADMIN_PASSWORD }}"
|
||||||
KC_BOOTSTRAP_ADMIN_USERNAME="{{ KEYCLOAK_BOOTSTRAP_ADMIN_USERNAME }}"
|
|
||||||
KC_BOOTSTRAP_ADMIN_PASSWORD="{{ KEYCLOAK_BOOTSTRAP_ADMIN_PASSWORD }}"
|
|
||||||
|
|
||||||
## Permanent
|
|
||||||
KEYCLOAK_PERMANENT_ADMIN_USERNAME="{{ KEYCLOAK_PERMANENT_ADMIN_USERNAME }}"
|
|
||||||
KEYCLOAK_PERMANENT_ADMIN_PASSWORD="{{ KEYCLOAK_PERMANENT_ADMIN_PASSWORD }}"
|
|
||||||
|
|
||||||
# Enable detailed logs
|
# Enable detailed logs
|
||||||
{% if MODE_DEBUG | bool %}
|
{% if MODE_DEBUG | bool %}
|
||||||
|
|||||||
3
roles/web-app-keycloak/users/main.yml
Normal file
3
roles/web-app-keycloak/users/main.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
users:
|
||||||
|
administrator:
|
||||||
|
username: "administrator"
|
||||||
@@ -29,22 +29,21 @@ KEYCLOAK_REALM_IMPORT_FILE_SRC: "import/realm.json.j2"
|
|||||||
KEYCLOAK_REALM_IMPORT_FILE_DST: "{{ [KEYCLOAK_REALM_IMPORT_DIR_HOST,'realm.json'] | path_join }}"
|
KEYCLOAK_REALM_IMPORT_FILE_DST: "{{ [KEYCLOAK_REALM_IMPORT_DIR_HOST,'realm.json'] | path_join }}"
|
||||||
|
|
||||||
## Credentials
|
## Credentials
|
||||||
|
KEYCLOAK_ADMIN: "{{ applications | get_app_conf(application_id, 'users.administrator.username') }}"
|
||||||
### Bootstrap
|
KEYCLOAK_ADMIN_PASSWORD: "{{ applications | get_app_conf(application_id, 'credentials.administrator_password') }}"
|
||||||
KEYCLOAK_BOOTSTRAP_ADMIN_USERNAME: "{{ applications | get_app_conf(application_id, 'accounts.bootstrap.username') }}"
|
|
||||||
KEYCLOAK_BOOTSTRAP_ADMIN_PASSWORD: "{{ applications | get_app_conf(application_id, 'credentials.administrator_password') }}"
|
|
||||||
|
|
||||||
### Permanent
|
|
||||||
KEYCLOAK_PERMANENT_ADMIN_USERNAME: "{{ applications | get_app_conf(application_id, 'accounts.system.username') }}"
|
|
||||||
KEYCLOAK_PERMANENT_ADMIN_PASSWORD: "{{ applications | get_app_conf(application_id, 'credentials.administrator_password') }}"
|
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
KEYCLOAK_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.name') }}"
|
KEYCLOAK_CONTAINER: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.name') }}" # Name of the keycloak docker container
|
||||||
KEYCLOAK_EXEC_CONTAINER: "docker exec -i {{ KEYCLOAK_CONTAINER }}"
|
KEYCLOAK_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.image') }}" # Keycloak docker image
|
||||||
KEYCLOAK_KCADM: "/opt/keycloak/bin/kcadm.sh"
|
KEYCLOAK_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.version') }}" # Keycloak docker version
|
||||||
KEYCLOAK_EXEC_KCADM: "{{ KEYCLOAK_EXEC_CONTAINER }} {{ KEYCLOAK_KCADM }}"
|
KEYCLOAK_KCADM_CONFIG: "/opt/keycloak/data/kcadm.config"
|
||||||
KEYCLOAK_IMAGE: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.image') }}"
|
KEYCLOAK_EXEC_KCADM: "docker exec -i {{ KEYCLOAK_CONTAINER }} /opt/keycloak/bin/kcadm.sh --config {{ KEYCLOAK_KCADM_CONFIG }}"
|
||||||
KEYCLOAK_VERSION: "{{ applications | get_app_conf(application_id, 'docker.services.keycloak.version') }}"
|
|
||||||
|
## Automation Service Account (Option A)
|
||||||
|
KEYCLOAK_AUTOMATION_CLIENT_ID: "infinito-automation"
|
||||||
|
KEYCLOAK_AUTOMATION_GRANT_ROLE: "realm-admin" # or granular roles if you prefer
|
||||||
|
# Will be discovered dynamically and set as a fact during the run:
|
||||||
|
# KEYCLOAK_AUTOMATION_CLIENT_SECRET
|
||||||
|
|
||||||
## Server
|
## Server
|
||||||
KEYCLOAK_SERVER_HOST: "127.0.0.1:{{ ports.localhost.http[application_id] }}"
|
KEYCLOAK_SERVER_HOST: "127.0.0.1:{{ ports.localhost.http[application_id] }}"
|
||||||
@@ -77,6 +76,11 @@ KEYCLOAK_LDAP_USER_OBJECT_CLASSES: >
|
|||||||
) | join(', ')
|
) | join(', ')
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
## API
|
||||||
|
KEYCLOAK_MASTER_API_USER: "{{ applications | get_app_conf(application_id, 'users.administrator') }}" # Master Administrator
|
||||||
|
KEYCLOAK_MASTER_API_USER_NAME: "{{ KEYCLOAK_MASTER_API_USER.username }}" # Master Administrator Username
|
||||||
|
KEYCLOAK_MASTER_API_USER_PASSWORD: "{{ KEYCLOAK_MASTER_API_USER.password }}" # Master Administrator Password
|
||||||
|
|
||||||
# Dictionaries
|
# Dictionaries
|
||||||
KEYCLOAK_DICTIONARY_REALM_RAW: "{{ lookup('template', 'import/realm.json.j2') }}"
|
KEYCLOAK_DICTIONARY_REALM_RAW: "{{ lookup('template', 'import/realm.json.j2') }}"
|
||||||
KEYCLOAK_DICTIONARY_REALM: >-
|
KEYCLOAK_DICTIONARY_REALM: >-
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
|
|
||||||
- name: "Create Mailu accounts"
|
- name: "Create Mailu accounts"
|
||||||
include_tasks: 02_manage_user.yml
|
include_tasks: 02_create-user.yml
|
||||||
vars:
|
vars:
|
||||||
MAILU_DOCKER_DIR: "{{ docker_compose.directories.instance }}"
|
MAILU_DOCKER_DIR: "{{ docker_compose.directories.instance }}"
|
||||||
mailu_api_base_url: "http://127.0.0.1:8080/api/v1"
|
mailu_api_base_url: "http://127.0.0.1:8080/api/v1"
|
||||||
|
|||||||
@@ -25,5 +25,5 @@
|
|||||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||||
|
|
||||||
- name: "Create Mailu API Token for {{ mailu_user_name }}"
|
- name: "Create Mailu API Token for {{ mailu_user_name }}"
|
||||||
include_tasks: 03a_manage_user_token.yml
|
include_tasks: 03_create-token.yml
|
||||||
when: "'mail-bot' in item.value.roles"
|
when: "{{ 'mail-bot' in item.value.roles }}"
|
||||||
@@ -1,3 +1,26 @@
|
|||||||
|
|
||||||
|
- name: "Fetch existing API tokens via curl inside admin container"
|
||||||
|
command: >-
|
||||||
|
{{ docker_compose_command_exec }} -T admin \
|
||||||
|
curl -s -X GET {{ mailu_api_base_url }}/token \
|
||||||
|
-H "Authorization: Bearer {{ MAILU_API_TOKEN }}"
|
||||||
|
args:
|
||||||
|
chdir: "{{ MAILU_DOCKER_DIR }}"
|
||||||
|
register: mailu_tokens_cli
|
||||||
|
changed_when: false
|
||||||
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||||
|
|
||||||
|
- name: "Extract existing token info for '{{ mailu_user_key }};{{ mailu_user_name }}'"
|
||||||
|
set_fact:
|
||||||
|
mailu_user_existing_token: >-
|
||||||
|
{{ (
|
||||||
|
mailu_tokens_cli.stdout
|
||||||
|
| default('[]')
|
||||||
|
| from_json
|
||||||
|
| selectattr('comment','equalto', mailu_user_key ~ " - ansible.infinito")
|
||||||
|
| list
|
||||||
|
).0 | default(None) }}
|
||||||
|
|
||||||
- name: "Delete existing API token for '{{ mailu_user_key }};{{ mailu_user_name }}' if local token missing but remote exists"
|
- name: "Delete existing API token for '{{ mailu_user_key }};{{ mailu_user_name }}' if local token missing but remote exists"
|
||||||
command: >-
|
command: >-
|
||||||
{{ docker_compose_command_exec }} -T admin \
|
{{ docker_compose_command_exec }} -T admin \
|
||||||
@@ -6,6 +29,7 @@
|
|||||||
args:
|
args:
|
||||||
chdir: "{{ MAILU_DOCKER_DIR }}"
|
chdir: "{{ MAILU_DOCKER_DIR }}"
|
||||||
when:
|
when:
|
||||||
|
- users[mailu_user_key].mailu_token is not defined
|
||||||
- mailu_user_existing_token is not none
|
- mailu_user_existing_token is not none
|
||||||
- mailu_user_existing_token.id is defined
|
- mailu_user_existing_token.id is defined
|
||||||
register: mailu_token_delete
|
register: mailu_token_delete
|
||||||
@@ -19,12 +43,13 @@
|
|||||||
-H "Authorization: Bearer {{ MAILU_API_TOKEN }}"
|
-H "Authorization: Bearer {{ MAILU_API_TOKEN }}"
|
||||||
-H "Content-Type: application/json"
|
-H "Content-Type: application/json"
|
||||||
-d '{{ {
|
-d '{{ {
|
||||||
"comment": SOFTWARE_NAME,
|
"comment": mailu_user_key ~ " - ansible.infinito",
|
||||||
"email": users[mailu_user_key].email,
|
"email": users[mailu_user_key].email,
|
||||||
"ip": mailu_token_ip
|
"ip": mailu_token_ip
|
||||||
} | to_json }}'
|
} | to_json }}'
|
||||||
args:
|
args:
|
||||||
chdir: "{{ MAILU_DOCKER_DIR }}"
|
chdir: "{{ MAILU_DOCKER_DIR }}"
|
||||||
|
when: users[mailu_user_key].mailu_token is not defined
|
||||||
register: mailu_token_creation
|
register: mailu_token_creation
|
||||||
# If curl sees 4xx/5xx it returns non-zero due to -f → fail the task.
|
# If curl sees 4xx/5xx it returns non-zero due to -f → fail the task.
|
||||||
failed_when:
|
failed_when:
|
||||||
@@ -50,16 +75,5 @@
|
|||||||
)
|
)
|
||||||
}, recursive=True)
|
}, recursive=True)
|
||||||
}}
|
}}
|
||||||
|
when: users[mailu_user_key].mailu_token is not defined
|
||||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||||
|
|
||||||
- name: "Reset MSMTP Configuration if No-Reply User Token changed"
|
|
||||||
when: users['no-reply'].username == mailu_user_name
|
|
||||||
block:
|
|
||||||
- name: "Set MSMTP run-once fact false"
|
|
||||||
set_fact:
|
|
||||||
run_once_sys_svc_msmtp: false
|
|
||||||
changed_when: false
|
|
||||||
|
|
||||||
- name: Reload MSMTP role
|
|
||||||
include_role:
|
|
||||||
name: "sys-svc-msmtp"
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
|
|
||||||
- name: "Fetch existing API tokens via curl inside admin container"
|
|
||||||
command: >-
|
|
||||||
{{ docker_compose_command_exec }} -T admin \
|
|
||||||
curl -s -X GET {{ mailu_api_base_url }}/token \
|
|
||||||
-H "Authorization: Bearer {{ MAILU_API_TOKEN }}"
|
|
||||||
args:
|
|
||||||
chdir: "{{ MAILU_DOCKER_DIR }}"
|
|
||||||
register: mailu_tokens_cli
|
|
||||||
changed_when: false
|
|
||||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
|
||||||
|
|
||||||
- name: "Extract existing token info for '{{ mailu_user_key }};{{ mailu_user_name }}'"
|
|
||||||
set_fact:
|
|
||||||
mailu_user_existing_token: >-
|
|
||||||
{{ (
|
|
||||||
mailu_tokens_cli.stdout
|
|
||||||
| default('[]')
|
|
||||||
| from_json
|
|
||||||
| selectattr('comment','equalto', SOFTWARE_NAME)
|
|
||||||
| list
|
|
||||||
).0 | default(None) }}
|
|
||||||
|
|
||||||
- name: "Start Mailu token procedures for undefined tokens"
|
|
||||||
when: users[mailu_user_key].mailu_token is not defined
|
|
||||||
include_tasks: 03b_create_user_token.yml
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
- name: "Execute migration for '{{ application_id }}'"
|
- name: "Execute migration for '{{ application_id }}'"
|
||||||
command:
|
command:
|
||||||
cmd: "docker exec {{ MASTODON_CONTAINER }} bundle exec rails db:migrate"
|
cmd: "docker exec {{ MASTODON_CONTAINER }} bundle exec rails db:migrate"
|
||||||
|
|
||||||
|
- name: "Include administrator routines for '{{ application_id }}'"
|
||||||
|
include_tasks: 02_administrator.yml
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
- name: Check health status of '{{ item }}' container
|
|
||||||
shell: |
|
|
||||||
cid=$(docker compose ps -q {{ item }})
|
|
||||||
docker inspect \
|
|
||||||
--format '{{ "{{.State.Health.Status}}" }}' \
|
|
||||||
$cid
|
|
||||||
args:
|
|
||||||
chdir: "{{ docker_compose.directories.instance }}"
|
|
||||||
register: healthcheck
|
|
||||||
retries: 60
|
|
||||||
delay: 5
|
|
||||||
until: healthcheck.stdout == "healthy"
|
|
||||||
loop:
|
|
||||||
- mastodon
|
|
||||||
- streaming
|
|
||||||
- sidekiq
|
|
||||||
loop_control:
|
|
||||||
label: "{{ item }}"
|
|
||||||
changed_when: false
|
|
||||||
@@ -1,5 +1,26 @@
|
|||||||
# Routines to create the administrator account
|
# Routines to create the administrator account
|
||||||
# @see https://chatgpt.com/share/67b9b12c-064c-800f-9354-8e42e6459764
|
# @see https://chatgpt.com/share/67b9b12c-064c-800f-9354-8e42e6459764
|
||||||
|
|
||||||
|
- name: Check health status of '{{ item }}' container
|
||||||
|
shell: |
|
||||||
|
cid=$(docker compose ps -q {{ item }})
|
||||||
|
docker inspect \
|
||||||
|
--format '{{ "{{.State.Health.Status}}" }}' \
|
||||||
|
$cid
|
||||||
|
args:
|
||||||
|
chdir: "{{ docker_compose.directories.instance }}"
|
||||||
|
register: healthcheck
|
||||||
|
retries: 60
|
||||||
|
delay: 5
|
||||||
|
until: healthcheck.stdout == "healthy"
|
||||||
|
loop:
|
||||||
|
- mastodon
|
||||||
|
- streaming
|
||||||
|
- sidekiq
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item }}"
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
- name: Remove line containing "- administrator" from config/settings.yml to allow creating administrator account
|
- name: Remove line containing "- administrator" from config/settings.yml to allow creating administrator account
|
||||||
command:
|
command:
|
||||||
cmd: "docker exec -u root {{ MASTODON_CONTAINER }} sed -i '/- administrator/d' config/settings.yml"
|
cmd: "docker exec -u root {{ MASTODON_CONTAINER }} sed -i '/- administrator/d' config/settings.yml"
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
---
|
|
||||||
# Cleanup routine for Mastodon
|
|
||||||
# Removes cached remote media older than 14 days when MODE_CLEANUP is enabled.
|
|
||||||
- name: "Cleanup Mastodon media cache older than 14 days"
|
|
||||||
command:
|
|
||||||
cmd: "docker exec -u root {{ MASTODON_CONTAINER }} bin/tootctl media remove --days=14"
|
|
||||||
register: mastodon_cleanup
|
|
||||||
changed_when: mastodon_cleanup.rc == 0
|
|
||||||
failed_when: mastodon_cleanup.rc != 0
|
|
||||||
@@ -18,15 +18,5 @@
|
|||||||
vars:
|
vars:
|
||||||
docker_compose_flush_handlers: true
|
docker_compose_flush_handlers: true
|
||||||
|
|
||||||
- name: "Wait for Mastodon"
|
|
||||||
include_tasks: 01_wait.yml
|
|
||||||
|
|
||||||
- name: "Cleanup Mastodon caches when MODE_CLEANUP is true"
|
|
||||||
include_tasks: 02_cleanup.yml
|
|
||||||
when: MODE_CLEANUP | bool
|
|
||||||
|
|
||||||
- name: "start setup procedures for mastodon"
|
- name: "start setup procedures for mastodon"
|
||||||
include_tasks: 03_setup.yml
|
include_tasks: 01_setup.yml
|
||||||
|
|
||||||
- name: "Include administrator routines for '{{ application_id }}'"
|
|
||||||
include_tasks: 04_administrator.yml
|
|
||||||
@@ -16,8 +16,7 @@ server:
|
|||||||
font-src:
|
font-src:
|
||||||
- https://cdnjs.cloudflare.com
|
- https://cdnjs.cloudflare.com
|
||||||
frame-src:
|
frame-src:
|
||||||
# Makes sense that all of the website content is available in the navigator
|
- "{{ WEB_PROTOCOL }}://*.{{ PRIMARY_DOMAIN }}" # Makes sense that all of the website content is available in the navigator
|
||||||
- "{{ WEB_PROTOCOL }}://*.{{ PRIMARY_DOMAIN }}"
|
|
||||||
flags:
|
flags:
|
||||||
style-src:
|
style-src:
|
||||||
unsafe-inline: true
|
unsafe-inline: true
|
||||||
|
|||||||
@@ -13,3 +13,4 @@
|
|||||||
{% include 'roles/docker-container/templates/networks.yml.j2' %}
|
{% include 'roles/docker-container/templates/networks.yml.j2' %}
|
||||||
|
|
||||||
{% include 'roles/docker-compose/templates/networks.yml.j2' %}
|
{% include 'roles/docker-compose/templates/networks.yml.j2' %}
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,10 @@ server:
|
|||||||
connect-src:
|
connect-src:
|
||||||
- "{{ WEB_PROTOCOL }}://*.{{ PRIMARY_DOMAIN }}"
|
- "{{ WEB_PROTOCOL }}://*.{{ PRIMARY_DOMAIN }}"
|
||||||
- "{{ WEB_PROTOCOL }}://{{ PRIMARY_DOMAIN }}"
|
- "{{ WEB_PROTOCOL }}://{{ PRIMARY_DOMAIN }}"
|
||||||
- "https://cdn.jsdelivr.net"
|
|
||||||
script-src-elem:
|
script-src-elem:
|
||||||
- "https://cdn.jsdelivr.net"
|
- https://cdn.jsdelivr.net
|
||||||
style-src-elem:
|
style-src-elem:
|
||||||
- "https://cdn.jsdelivr.net"
|
- https://cdn.jsdelivr.net
|
||||||
frame-ancestors:
|
frame-ancestors:
|
||||||
- "{{ WEB_PROTOCOL }}://<< defaults_applications[web-app-keycloak].server.domains.canonical[0] >>"
|
- "{{ WEB_PROTOCOL }}://<< defaults_applications[web-app-keycloak].server.domains.canonical[0] >>"
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,11 @@
|
|||||||
- name: "load docker, proxy for '{{ application_id }}'"
|
- name: "load docker, proxy for '{{ application_id }}'"
|
||||||
include_role:
|
include_role:
|
||||||
name: sys-stk-full-stateless
|
name: sys-stk-full-stateless
|
||||||
|
vars:
|
||||||
|
aca_origin: "'{{ domains | get_url('web-svc-logout', WEB_PROTOCOL) }}' always"
|
||||||
|
aca_credentials: "'true' always"
|
||||||
|
aca_methods: "'GET, OPTIONS' always"
|
||||||
|
aca_headers: "'Accept, Authorization' always"
|
||||||
|
|
||||||
- name: Create symbolic link from .env file to repository
|
- name: Create symbolic link from .env file to repository
|
||||||
file:
|
file:
|
||||||
|
|||||||
@@ -8,11 +8,7 @@ location = /logout {
|
|||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
|
|
||||||
{# CORS headers – allow your central page to call this #}
|
{# CORS headers – allow your central page to call this #}
|
||||||
{%- set aca_origin = "'{{ domains | get_url('web-svc-logout', WEB_PROTOCOL) }}' always" -%}
|
{% include 'roles/sys-svc-proxy/templates/headers/access_control_allow.conf.j2' %}
|
||||||
{%- set aca_credentials = "'true' always" -%}
|
|
||||||
{%- set aca_methods = "'GET, OPTIONS' always" -%}
|
|
||||||
{%- set aca_headers = "'Accept, Authorization' always" -%}
|
|
||||||
{%- include 'roles/sys-svc-proxy/templates/headers/access_control_allow.conf.j2' -%}
|
|
||||||
|
|
||||||
{# Disable caching absolutely #}
|
{# Disable caching absolutely #}
|
||||||
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
|
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
|
||||||
|
|||||||
@@ -28,17 +28,14 @@ BUILTIN_FILTERS: Set[str] = {
|
|||||||
"int", "join", "last", "length", "list", "lower", "map", "min", "max", "random",
|
"int", "join", "last", "length", "list", "lower", "map", "min", "max", "random",
|
||||||
"reject", "rejectattr", "replace", "reverse", "round", "safe", "select",
|
"reject", "rejectattr", "replace", "reverse", "round", "safe", "select",
|
||||||
"selectattr", "slice", "sort", "string", "striptags", "sum", "title", "trim",
|
"selectattr", "slice", "sort", "string", "striptags", "sum", "title", "trim",
|
||||||
"truncate", "unique", "upper", "urlencode", "urlize", "wordcount", "xmlattr","contains",
|
"truncate", "unique", "upper", "urlencode", "urlize", "wordcount", "xmlattr",
|
||||||
|
|
||||||
# Common Ansible filters (subset, extend as needed)
|
# Common Ansible filters (subset, extend as needed)
|
||||||
"b64decode", "b64encode", "basename", "dirname", "from_json", "to_json",
|
"b64decode", "b64encode", "basename", "dirname", "from_json", "to_json",
|
||||||
"from_yaml", "to_yaml", "combine", "difference", "intersect",
|
"from_yaml", "to_yaml", "combine", "difference", "intersect",
|
||||||
"flatten", "zip", "regex_search", "regex_replace", "bool",
|
"flatten", "zip", "regex_search", "regex_replace", "bool",
|
||||||
"type_debug", "json_query", "mandatory", "hash", "checksum",
|
"type_debug", "json_query", "mandatory", "hash", "checksum",
|
||||||
"lower", "upper", "capitalize", "unique", "dict2items", "items2dict",
|
"lower", "upper", "capitalize", "unique", "dict2items", "items2dict", "password_hash", "path_join", "product", "quote", "split", "ternary", "to_nice_yaml", "tojson",
|
||||||
"password_hash", "path_join", "product", "quote", "split", "ternary", "to_nice_yaml",
|
|
||||||
"tojson", "to_nice_json",
|
|
||||||
|
|
||||||
|
|
||||||
# Date/time-ish
|
# Date/time-ish
|
||||||
"strftime",
|
"strftime",
|
||||||
|
|||||||
Reference in New Issue
Block a user