mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
fix(web-app): gate manager-only role bodies behind IS_STACK_HOST
Across roles/web-app-*, the typical body shape is: - include_role: sys-stk-full (or sys-svc-compose) cluster prereqs - <host-fs writes, container exec, compose exec> stack-host only Under swarm-mode delegation the second group only makes sense on the host that holds the compose stack (manager), but the role plays on every host in groups[<role>] so workers abort on missing paths. Two workflow sweeps (gate-manager-ops + gate-manager-ops-01core, 58 agents total, discover/plan/adversarial-verify per role) plus three manual fixes surfaced 27 roles to refactor. Each role keeps sys-stk-full ungated so worker-side prereqs (sys-svc-container, NFS client) still bootstrap; the trailing manager-only block either moves into a new tasks/02_manager_ops.yml included with when: IS_STACK_HOST | bool (SUB_FILE pattern) or gets the gate inlined on the one or two trailing tasks (INLINE pattern). In compose mode IS_STACK_HOST stays true so single-host deploys are unchanged. Manual catches the workflow agents missed: - web-app-fediwall: utils/once/flag.yml lives AT THE END of 01_core, not at the start; preserved verbatim outside the gate so workers still flip the run_once_<role> fact. - web-app-xwiki: a pre-existing block(...) wraps sys-stk-full plus _flush_config.yml; gated only the _flush_config.yml include so sys-stk-full keeps running on workers. - web-app-dashboard: 02_javascript.yml (creates /opt/compose/dashboard/ JS dirs) ran BEFORE sys-stk-full and was ungated; added the gate. Roles touched (INLINE): akaunting, jenkins, magento, mobilizon Roles touched (SUB_FILE): baserow, dashboard, decidim, erpnext, fediwall, friendica, gitea, jitsi, keycloak, listmonk, littlejs, mailu, matomo, moodle, odoo, openproject, opentalk, pixelfed, shopware, suitecrm, taiga, xwiki, zammad
This commit is contained in:
50 files changed
+1439
-1359
No files matched your search
@@ -22,4 +22,6 @@
|
||||
path: "{{ AKAUNTING_SETUP_MARKER }}"
|
||||
state: touch
|
||||
mode: "0644"
|
||||
when: akaunting_setup_enabled | bool
|
||||
when:
|
||||
- akaunting_setup_enabled | bool
|
||||
- IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
- name: "Run Baserow Django migrations before bootstrapping users"
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
compose exec -T
|
||||
-e DJANGO_SETTINGS_MODULE={{ BASEROW_DJANGO_SETTINGS_MODULE }}
|
||||
-e SECRET_KEY={{ BASEROW_SECRET_KEY }}
|
||||
-e BASEROW_SECRET_KEY={{ BASEROW_SECRET_KEY }}
|
||||
{{ BASEROW_SERVICE_NAME }}
|
||||
{{ BASEROW_PYTHON }} {{ BASEROW_MANAGE_PY }} migrate --noinput
|
||||
chdir: "{{ lookup('container', application_id, 'directories.instance') }}"
|
||||
register: baserow_migrate_result
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
changed_when: >
|
||||
'No migrations to apply' not in
|
||||
((baserow_migrate_result.stdout | default('')) ~ (baserow_migrate_result.stderr | default('')))
|
||||
failed_when: baserow_migrate_result.rc != 0
|
||||
# The Baserow all-in-one container can still be finishing startup while
|
||||
# Ansible continues after compose up. Run migrations explicitly so Playwright
|
||||
# never reaches an app with missing auth_user/core_workspace tables.
|
||||
retries: 60
|
||||
delay: 5
|
||||
until: baserow_migrate_result.rc == 0
|
||||
|
||||
- name: "Restart Baserow application after applying migrations"
|
||||
ansible.builtin.command: "compose restart {{ BASEROW_SERVICE_NAME }}"
|
||||
args:
|
||||
chdir: "{{ lookup('container', application_id, 'directories.instance') }}"
|
||||
register: baserow_restart_after_migrate
|
||||
changed_when: baserow_restart_after_migrate.rc == 0
|
||||
when: baserow_migrate_result.changed
|
||||
|
||||
- name: "Bootstrap Baserow Django superuser '{{ BASEROW_BOOTSTRAP_ADMIN_USERNAME }}' (idempotent)"
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- compose
|
||||
- exec
|
||||
- -T
|
||||
- -e
|
||||
- "DJANGO_SETTINGS_MODULE={{ BASEROW_DJANGO_SETTINGS_MODULE }}"
|
||||
- -e
|
||||
- "SECRET_KEY={{ BASEROW_SECRET_KEY }}"
|
||||
- -e
|
||||
- "BASEROW_SECRET_KEY={{ BASEROW_SECRET_KEY }}"
|
||||
- -e
|
||||
- "BOOTSTRAP_ADMIN_USERNAME={{ BASEROW_BOOTSTRAP_ADMIN_USERNAME }}"
|
||||
- -e
|
||||
- "BOOTSTRAP_ADMIN_EMAIL={{ BASEROW_BOOTSTRAP_ADMIN_EMAIL }}"
|
||||
- -e
|
||||
- "BOOTSTRAP_ADMIN_PASSWORD={{ BASEROW_BOOTSTRAP_ADMIN_PASSWORD }}"
|
||||
- "{{ BASEROW_SERVICE_NAME }}"
|
||||
- "{{ BASEROW_PYTHON }}"
|
||||
- "{{ BASEROW_MANAGE_PY }}"
|
||||
- shell
|
||||
- -c
|
||||
- "{{ lookup('file', role_path ~ '/files/bootstrap_admin.py') }}"
|
||||
chdir: "{{ lookup('container', application_id, 'directories.instance') }}"
|
||||
register: baserow_bootstrap_admin_result
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
changed_when: "'created' in baserow_bootstrap_admin_result.stdout"
|
||||
failed_when: baserow_bootstrap_admin_result.rc != 0
|
||||
# Retry while Baserow's container entrypoint is still running Django
|
||||
# migrations on a fresh DB. Without this the very first deploy races the
|
||||
# auth_user table creation and fails with UndefinedTable.
|
||||
retries: 60
|
||||
delay: 5
|
||||
until: baserow_bootstrap_admin_result.rc == 0
|
||||
@@ -3,71 +3,8 @@
|
||||
include_role:
|
||||
name: sys-stk-full
|
||||
vars:
|
||||
compose_handlers_flush: true # Required so that new db pw is used
|
||||
compose_handlers_flush: true
|
||||
|
||||
- name: "Run Baserow Django migrations before bootstrapping users"
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
compose exec -T
|
||||
-e DJANGO_SETTINGS_MODULE={{ BASEROW_DJANGO_SETTINGS_MODULE }}
|
||||
-e SECRET_KEY={{ BASEROW_SECRET_KEY }}
|
||||
-e BASEROW_SECRET_KEY={{ BASEROW_SECRET_KEY }}
|
||||
{{ BASEROW_SERVICE_NAME }}
|
||||
{{ BASEROW_PYTHON }} {{ BASEROW_MANAGE_PY }} migrate --noinput
|
||||
chdir: "{{ lookup('container', application_id, 'directories.instance') }}"
|
||||
register: baserow_migrate_result
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
changed_when: >
|
||||
'No migrations to apply' not in
|
||||
((baserow_migrate_result.stdout | default('')) ~ (baserow_migrate_result.stderr | default('')))
|
||||
failed_when: baserow_migrate_result.rc != 0
|
||||
# The Baserow all-in-one container can still be finishing startup while
|
||||
# Ansible continues after compose up. Run migrations explicitly so Playwright
|
||||
# never reaches an app with missing auth_user/core_workspace tables.
|
||||
retries: 60
|
||||
delay: 5
|
||||
until: baserow_migrate_result.rc == 0
|
||||
|
||||
- name: "Restart Baserow application after applying migrations"
|
||||
ansible.builtin.command: "compose restart {{ BASEROW_SERVICE_NAME }}"
|
||||
args:
|
||||
chdir: "{{ lookup('container', application_id, 'directories.instance') }}"
|
||||
register: baserow_restart_after_migrate
|
||||
changed_when: baserow_restart_after_migrate.rc == 0
|
||||
when: baserow_migrate_result.changed
|
||||
|
||||
- name: "Bootstrap Baserow Django superuser '{{ BASEROW_BOOTSTRAP_ADMIN_USERNAME }}' (idempotent)"
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- compose
|
||||
- exec
|
||||
- -T
|
||||
- -e
|
||||
- "DJANGO_SETTINGS_MODULE={{ BASEROW_DJANGO_SETTINGS_MODULE }}"
|
||||
- -e
|
||||
- "SECRET_KEY={{ BASEROW_SECRET_KEY }}"
|
||||
- -e
|
||||
- "BASEROW_SECRET_KEY={{ BASEROW_SECRET_KEY }}"
|
||||
- -e
|
||||
- "BOOTSTRAP_ADMIN_USERNAME={{ BASEROW_BOOTSTRAP_ADMIN_USERNAME }}"
|
||||
- -e
|
||||
- "BOOTSTRAP_ADMIN_EMAIL={{ BASEROW_BOOTSTRAP_ADMIN_EMAIL }}"
|
||||
- -e
|
||||
- "BOOTSTRAP_ADMIN_PASSWORD={{ BASEROW_BOOTSTRAP_ADMIN_PASSWORD }}"
|
||||
- "{{ BASEROW_SERVICE_NAME }}"
|
||||
- "{{ BASEROW_PYTHON }}"
|
||||
- "{{ BASEROW_MANAGE_PY }}"
|
||||
- shell
|
||||
- -c
|
||||
- "{{ lookup('file', role_path ~ '/files/bootstrap_admin.py') }}"
|
||||
chdir: "{{ lookup('container', application_id, 'directories.instance') }}"
|
||||
register: baserow_bootstrap_admin_result
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
changed_when: "'created' in baserow_bootstrap_admin_result.stdout"
|
||||
failed_when: baserow_bootstrap_admin_result.rc != 0
|
||||
# Retry while Baserow's container entrypoint is still running Django
|
||||
# migrations on a fresh DB. Without this the very first deploy races the
|
||||
# auth_user table creation and fails with UndefinedTable.
|
||||
retries: 60
|
||||
delay: 5
|
||||
until: baserow_bootstrap_admin_result.rc == 0
|
||||
- name: "Baserow manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
- name: "Include JS routines"
|
||||
include_tasks: "02_javascript.yml"
|
||||
when: IS_STACK_HOST | bool
|
||||
|
||||
- name: "load docker, proxy for '{{ application_id }}'"
|
||||
include_role:
|
||||
@@ -11,120 +12,6 @@
|
||||
compose_handlers_flush: false
|
||||
compose_repository_pull: false
|
||||
|
||||
- name: "Check if host-specific config.yaml exists in {{ DASHBOARD_CONFIG_INV_PATH }}"
|
||||
stat:
|
||||
path: "{{ DASHBOARD_CONFIG_INV_PATH }}"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
register: config_file
|
||||
|
||||
- name: Load menu categories
|
||||
include_vars:
|
||||
file: "menu_categories.yml"
|
||||
|
||||
- name: Load docker cards
|
||||
set_fact:
|
||||
portfolio_cards: "{{ lookup('docker_cards', 'roles') }}"
|
||||
|
||||
- name: "Load Dashboard Brand logos"
|
||||
set_fact:
|
||||
portfolio_cards: "{{ portfolio_cards | add_simpleicon_source(DASHBOARD_SIMPLEICONS_SYNC_URL_BASE, public_url_base=DASHBOARD_SIMPLEICONS_URL_BASE) }}"
|
||||
when: DASHBOARD_SIMPLEICONS_ENABLED | bool
|
||||
changed_when: false
|
||||
|
||||
- name: Group docker cards
|
||||
set_fact:
|
||||
portfolio_menu_data: "{{ lookup('docker_cards_grouped', portfolio_cards, portfolio_menu_categories) }}"
|
||||
|
||||
- name: "Render Dashboard config.yaml (local + swarm peers)"
|
||||
ansible.builtin.include_tasks:
|
||||
file: "{{ [playbook_dir, 'roles/sys-svc-compose/tasks/utils/render_replicated_templates.yml'] | path_join }}"
|
||||
vars:
|
||||
role_templates:
|
||||
- src: "{{ DASHBOARD_CONFIG_INV_PATH if config_file.stat.exists else DASHBOARD_CONFIG_YML_SRC_FILE }}"
|
||||
dest: "{{ DASHBOARD_CONFIG_YML_DEST }}"
|
||||
|
||||
- name: Load rendered dashboard config before first compose start
|
||||
slurp:
|
||||
src: "{{ DASHBOARD_CONFIG_YML_DEST }}"
|
||||
register: dashboard_rendered_config_raw
|
||||
|
||||
- name: Collect branding asset URLs served by the shared file service
|
||||
# Reads the *actually deployed* config.yaml (which may be a
|
||||
# host-specific override from DASHBOARD_CONFIG_INV_PATH, not the
|
||||
# role-default template). SPOTs in vars/main.yml drive the default
|
||||
# render path; this slurp covers the override case so the wait list
|
||||
# always reflects what port-ui will source on startup.
|
||||
set_fact:
|
||||
dashboard_branding_asset_urls: >-
|
||||
{{
|
||||
[
|
||||
(
|
||||
dashboard_platform_logo_source
|
||||
if dashboard_platform_logo_source.startswith(dashboard_asset_url_base)
|
||||
else ''
|
||||
),
|
||||
(
|
||||
dashboard_platform_favicon_source
|
||||
if dashboard_platform_favicon_source.startswith(dashboard_asset_url_base)
|
||||
else ''
|
||||
),
|
||||
(
|
||||
dashboard_company_logo_source
|
||||
if dashboard_company_logo_source.startswith(dashboard_asset_url_base)
|
||||
else ''
|
||||
)
|
||||
]
|
||||
| select('string')
|
||||
| reject('equalto', '')
|
||||
| unique
|
||||
| list
|
||||
}}
|
||||
vars:
|
||||
dashboard_rendered_config: "{{ dashboard_rendered_config_raw.content | b64decode | from_yaml }}"
|
||||
# Dashboard branding should only depend on the asset abstraction, not the
|
||||
# underlying web-svc-file host that serves /assets.
|
||||
dashboard_asset_url_base: "{{ lookup('config', 'web-svc-asset', 'services.asset.url') }}"
|
||||
dashboard_platform_logo_source: "{{ ((dashboard_rendered_config.get('platform') or {}).get('logo') or {}).get('source', '') }}"
|
||||
dashboard_platform_favicon_source: "{{ ((dashboard_rendered_config.get('platform') or {}).get('favicon') or {}).get('source', '') }}"
|
||||
dashboard_company_logo_source: "{{ ((dashboard_rendered_config.get('company') or {}).get('logo') or {}).get('source', '') }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Wait for dashboard branding assets before first compose start
|
||||
uri_retry:
|
||||
url: "{{ item }}"
|
||||
status_code: 200
|
||||
validate_certs: false
|
||||
register: dashboard_branding_asset_http
|
||||
retries: 60
|
||||
delay: 5
|
||||
until: dashboard_branding_asset_http.status == 200
|
||||
loop: "{{ dashboard_branding_asset_urls }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
|
||||
- name: "Flush compose handlers"
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "Ensure Simple Icons is reachable and refresh dashboard cards"
|
||||
when: DASHBOARD_SIMPLEICONS_ENABLED | bool
|
||||
block:
|
||||
- name: Ensure shared Simple Icons service is loaded before refresh
|
||||
include_tasks: utils/load_app.yml
|
||||
vars:
|
||||
load_app_id: web-svc-simpleicons
|
||||
|
||||
- name: Flush handlers after loading shared Simple Icons service
|
||||
meta: flush_handlers
|
||||
|
||||
- name: Refresh dashboard cards after Simple Icons becomes reachable
|
||||
include_tasks: "03_simpleicons_refresh.yml"
|
||||
|
||||
- name: Wait for Dashboard HTTP endpoint (required so all logos can be downloaded during initialization)
|
||||
uri_retry:
|
||||
url: "http://{{ DOCKER_REACH_HOST }}:{{ http_port }}/"
|
||||
status_code: 200
|
||||
register: dashboard_http
|
||||
retries: 60
|
||||
delay: 5
|
||||
until: dashboard_http.status == 200
|
||||
- name: "Dashboard manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
- name: "Check if host-specific config.yaml exists in {{ DASHBOARD_CONFIG_INV_PATH }}"
|
||||
stat:
|
||||
path: "{{ DASHBOARD_CONFIG_INV_PATH }}"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
register: config_file
|
||||
|
||||
- name: Load menu categories
|
||||
include_vars:
|
||||
file: "menu_categories.yml"
|
||||
|
||||
- name: Load docker cards
|
||||
set_fact:
|
||||
portfolio_cards: "{{ lookup('docker_cards', 'roles') }}"
|
||||
|
||||
- name: "Load Dashboard Brand logos"
|
||||
set_fact:
|
||||
portfolio_cards: "{{ portfolio_cards | add_simpleicon_source(DASHBOARD_SIMPLEICONS_SYNC_URL_BASE, public_url_base=DASHBOARD_SIMPLEICONS_URL_BASE) }}"
|
||||
when: DASHBOARD_SIMPLEICONS_ENABLED | bool
|
||||
changed_when: false
|
||||
|
||||
- name: Group docker cards
|
||||
set_fact:
|
||||
portfolio_menu_data: "{{ lookup('docker_cards_grouped', portfolio_cards, portfolio_menu_categories) }}"
|
||||
|
||||
- name: "Render Dashboard config.yaml (local + swarm peers)"
|
||||
ansible.builtin.include_tasks:
|
||||
file: "{{ [playbook_dir, 'roles/sys-svc-compose/tasks/utils/render_replicated_templates.yml'] | path_join }}"
|
||||
vars:
|
||||
role_templates:
|
||||
- src: "{{ DASHBOARD_CONFIG_INV_PATH if config_file.stat.exists else DASHBOARD_CONFIG_YML_SRC_FILE }}"
|
||||
dest: "{{ DASHBOARD_CONFIG_YML_DEST }}"
|
||||
|
||||
- name: Load rendered dashboard config before first compose start
|
||||
slurp:
|
||||
src: "{{ DASHBOARD_CONFIG_YML_DEST }}"
|
||||
register: dashboard_rendered_config_raw
|
||||
|
||||
- name: Collect branding asset URLs served by the shared file service
|
||||
set_fact:
|
||||
dashboard_branding_asset_urls: >-
|
||||
{{
|
||||
[
|
||||
(
|
||||
dashboard_platform_logo_source
|
||||
if dashboard_platform_logo_source.startswith(dashboard_asset_url_base)
|
||||
else ''
|
||||
),
|
||||
(
|
||||
dashboard_platform_favicon_source
|
||||
if dashboard_platform_favicon_source.startswith(dashboard_asset_url_base)
|
||||
else ''
|
||||
),
|
||||
(
|
||||
dashboard_company_logo_source
|
||||
if dashboard_company_logo_source.startswith(dashboard_asset_url_base)
|
||||
else ''
|
||||
)
|
||||
]
|
||||
| select('string')
|
||||
| reject('equalto', '')
|
||||
| unique
|
||||
| list
|
||||
}}
|
||||
vars:
|
||||
dashboard_rendered_config: "{{ dashboard_rendered_config_raw.content | b64decode | from_yaml }}"
|
||||
dashboard_asset_url_base: "{{ lookup('config', 'web-svc-asset', 'services.asset.url') }}"
|
||||
dashboard_platform_logo_source: "{{ ((dashboard_rendered_config.get('platform') or {}).get('logo') or {}).get('source', '') }}"
|
||||
dashboard_platform_favicon_source: "{{ ((dashboard_rendered_config.get('platform') or {}).get('favicon') or {}).get('source', '') }}"
|
||||
dashboard_company_logo_source: "{{ ((dashboard_rendered_config.get('company') or {}).get('logo') or {}).get('source', '') }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Wait for dashboard branding assets before first compose start
|
||||
uri_retry:
|
||||
url: "{{ item }}"
|
||||
status_code: 200
|
||||
validate_certs: false
|
||||
register: dashboard_branding_asset_http
|
||||
retries: 60
|
||||
delay: 5
|
||||
until: dashboard_branding_asset_http.status == 200
|
||||
loop: "{{ dashboard_branding_asset_urls }}"
|
||||
loop_control:
|
||||
label: "{{ item }}"
|
||||
|
||||
- name: "Flush compose handlers"
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "Ensure Simple Icons is reachable and refresh dashboard cards"
|
||||
when: DASHBOARD_SIMPLEICONS_ENABLED | bool
|
||||
block:
|
||||
- name: Ensure shared Simple Icons service is loaded before refresh
|
||||
include_tasks: utils/load_app.yml
|
||||
vars:
|
||||
load_app_id: web-svc-simpleicons
|
||||
|
||||
- name: Flush handlers after loading shared Simple Icons service
|
||||
meta: flush_handlers
|
||||
|
||||
- name: Refresh dashboard cards after Simple Icons becomes reachable
|
||||
include_tasks: "03_simpleicons_refresh.yml"
|
||||
|
||||
- name: Wait for Dashboard HTTP endpoint (required so all logos can be downloaded during initialization)
|
||||
uri_retry:
|
||||
url: "http://{{ DOCKER_REACH_HOST }}:{{ http_port }}/"
|
||||
status_code: 200
|
||||
register: dashboard_http
|
||||
retries: 60
|
||||
delay: 5
|
||||
until: dashboard_http.status == 200
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
- name: "Render allowed_hosts initializer for {{ application_id }}"
|
||||
ansible.builtin.template:
|
||||
src: "allowed_hosts.rb.j2"
|
||||
dest: "{{ lookup('container', application_id, 'directories.instance') }}allowed_hosts.rb"
|
||||
mode: "0644"
|
||||
notify: compose up
|
||||
|
||||
- name: "Deploy '{{ DECIDIM_PATCH_SCRIPT_HOST_ABS }}'"
|
||||
copy:
|
||||
src: "{{ DECIDIM_PATCH_SCRIPT_FILE }}"
|
||||
dest: "{{ DECIDIM_PATCH_SCRIPT_HOST_ABS }}"
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "Deploy '{{ DECIDIM_PATCH_RUBY_HOST_ABS }}'"
|
||||
copy:
|
||||
src: "{{ DECIDIM_PATCH_RUBY_DIR }}/"
|
||||
dest: "{{ DECIDIM_PATCH_RUBY_HOST_ABS }}/"
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "flush handlers of compose"
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "Run Decidim setup routines"
|
||||
include_tasks: 01_setup.yml
|
||||
@@ -5,31 +5,6 @@
|
||||
vars:
|
||||
compose_handlers_flush: false
|
||||
|
||||
- name: "Render allowed_hosts initializer for {{ application_id }}"
|
||||
ansible.builtin.template:
|
||||
src: "allowed_hosts.rb.j2"
|
||||
dest: "{{ lookup('container', application_id, 'directories.instance') }}allowed_hosts.rb"
|
||||
mode: "0644"
|
||||
notify: compose up
|
||||
|
||||
- name: "Deploy '{{ DECIDIM_PATCH_SCRIPT_HOST_ABS }}'"
|
||||
copy:
|
||||
src: "{{ DECIDIM_PATCH_SCRIPT_FILE }}"
|
||||
dest: "{{ DECIDIM_PATCH_SCRIPT_HOST_ABS }}"
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "Deploy '{{ DECIDIM_PATCH_RUBY_HOST_ABS }}'"
|
||||
copy:
|
||||
src: "{{ DECIDIM_PATCH_RUBY_DIR }}/"
|
||||
dest: "{{ DECIDIM_PATCH_RUBY_HOST_ABS }}/"
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "flush handlers of compose"
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "Run Decidim setup routines"
|
||||
include_tasks: 01_setup.yml
|
||||
- name: "Decidim manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -7,28 +7,6 @@
|
||||
vars:
|
||||
compose_handlers_flush: true
|
||||
|
||||
- name: "wait for {{ ERPNEXT_BACKEND_CONTAINER }} gunicorn TCP port"
|
||||
ansible.builtin.shell: |
|
||||
set -eo pipefail
|
||||
container exec {{ ERPNEXT_BACKEND_CONTAINER }} \
|
||||
bash -c 'exec 3<>/dev/tcp/localhost/{{ ERPNEXT_BACKEND_PORT }} && exec 3<&- 3>&-'
|
||||
register: erpnext_backend_ready
|
||||
changed_when: false
|
||||
retries: 60
|
||||
delay: 5
|
||||
until: erpnext_backend_ready.rc == 0
|
||||
|
||||
- name: "bench bootstrap"
|
||||
include_tasks: 02_bench_bootstrap.yml
|
||||
|
||||
- name: "configure OIDC"
|
||||
include_tasks: 03_oidc.yml
|
||||
when: ERPNEXT_OIDC_ENABLED
|
||||
|
||||
- name: "configure LDAP"
|
||||
include_tasks: 04_ldap.yml
|
||||
when: ERPNEXT_LDAP_ENABLED
|
||||
|
||||
- name: "configure outbound email"
|
||||
include_tasks: 05_email.yml
|
||||
when: ERPNEXT_EMAIL_ENABLED
|
||||
- name: "ERPNext manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
- name: "wait for {{ ERPNEXT_BACKEND_CONTAINER }} gunicorn TCP port"
|
||||
ansible.builtin.shell: |
|
||||
set -eo pipefail
|
||||
container exec {{ ERPNEXT_BACKEND_CONTAINER }} \
|
||||
bash -c 'exec 3<>/dev/tcp/localhost/{{ ERPNEXT_BACKEND_PORT }} && exec 3<&- 3>&-'
|
||||
register: erpnext_backend_ready
|
||||
changed_when: false
|
||||
retries: 60
|
||||
delay: 5
|
||||
until: erpnext_backend_ready.rc == 0
|
||||
|
||||
- name: "bench bootstrap"
|
||||
include_tasks: 02_bench_bootstrap.yml
|
||||
|
||||
- name: "configure OIDC"
|
||||
include_tasks: 03_oidc.yml
|
||||
when: ERPNEXT_OIDC_ENABLED
|
||||
|
||||
- name: "configure LDAP"
|
||||
include_tasks: 04_ldap.yml
|
||||
when: ERPNEXT_LDAP_ENABLED
|
||||
|
||||
- name: "configure outbound email"
|
||||
include_tasks: 05_email.yml
|
||||
when: ERPNEXT_EMAIL_ENABLED
|
||||
@@ -6,86 +6,8 @@
|
||||
compose_handlers_flush: false
|
||||
compose_repository_pull: false
|
||||
|
||||
- name: "Ensure Fediwall build dir '{{ FEDIWALL_BUILD_DIR_ABS }}' exists"
|
||||
file:
|
||||
path: "{{ FEDIWALL_BUILD_DIR_ABS }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: "Download Fediwall {{ FEDIWALL_UPSTREAM_VERSION }} release tarball"
|
||||
get_url_retry:
|
||||
url: "{{ FEDIWALL_UPSTREAM_TARBALL_URL }}"
|
||||
dest: "{{ FEDIWALL_TARBALL_FILE }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "Ensure Fediwall upstream dir '{{ FEDIWALL_UPSTREAM_DIR_ABS }}' exists"
|
||||
file:
|
||||
path: "{{ FEDIWALL_UPSTREAM_DIR_ABS }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: "Extract Fediwall tarball into '{{ FEDIWALL_UPSTREAM_DIR_ABS }}'"
|
||||
unarchive:
|
||||
src: "{{ FEDIWALL_TARBALL_FILE }}"
|
||||
dest: "{{ FEDIWALL_UPSTREAM_DIR_ABS }}"
|
||||
remote_src: true
|
||||
extra_opts:
|
||||
- --strip-components=1
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "Wipe Fediwall dist tree '{{ FEDIWALL_DIST_DIR_ABS }}' (so removed slugs disappear)"
|
||||
file:
|
||||
path: "{{ FEDIWALL_DIST_DIR_ABS }}"
|
||||
state: absent
|
||||
|
||||
- name: "Recreate Fediwall dist tree '{{ FEDIWALL_DIST_DIR_ABS }}'"
|
||||
file:
|
||||
path: "{{ FEDIWALL_DIST_DIR_ABS }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: "Stage upstream Fediwall files into per-slug folder '{{ wall.slug }}'"
|
||||
copy:
|
||||
src: "{{ FEDIWALL_UPSTREAM_DIR_ABS }}/"
|
||||
dest: "{{ [ FEDIWALL_DIST_DIR_ABS, wall.slug ] | path_join }}/"
|
||||
remote_src: true
|
||||
mode: preserve
|
||||
loop: "{{ FEDIWALL_WALLS }}"
|
||||
loop_control:
|
||||
loop_var: wall
|
||||
label: "{{ wall.slug }}"
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "Render per-slug Fediwall wall-config.json for '{{ wall.slug }}'"
|
||||
template:
|
||||
src: "wall-config.json.j2"
|
||||
dest: "{{ [ FEDIWALL_DIST_DIR_ABS, wall.slug, 'wall-config.json' ] | path_join }}"
|
||||
mode: "0644"
|
||||
loop: "{{ FEDIWALL_WALLS }}"
|
||||
loop_control:
|
||||
loop_var: wall
|
||||
label: "{{ wall.slug }}"
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "Render Fediwall root link list at '{{ FEDIWALL_DIST_DIR_ABS }}/index.html'"
|
||||
template:
|
||||
src: "index.html.j2"
|
||||
dest: "{{ [ FEDIWALL_DIST_DIR_ABS, 'index.html' ] | path_join }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "flush compose for '{{ application_id }}'"
|
||||
meta: flush_handlers
|
||||
- name: "Fediwall manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
|
||||
- include_tasks: utils/once/flag.yml
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
- name: "Ensure Fediwall build dir '{{ FEDIWALL_BUILD_DIR_ABS }}' exists"
|
||||
file:
|
||||
path: "{{ FEDIWALL_BUILD_DIR_ABS }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: "Download Fediwall {{ FEDIWALL_UPSTREAM_VERSION }} release tarball"
|
||||
get_url_retry:
|
||||
url: "{{ FEDIWALL_UPSTREAM_TARBALL_URL }}"
|
||||
dest: "{{ FEDIWALL_TARBALL_FILE }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "Ensure Fediwall upstream dir '{{ FEDIWALL_UPSTREAM_DIR_ABS }}' exists"
|
||||
file:
|
||||
path: "{{ FEDIWALL_UPSTREAM_DIR_ABS }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: "Extract Fediwall tarball into '{{ FEDIWALL_UPSTREAM_DIR_ABS }}'"
|
||||
unarchive:
|
||||
src: "{{ FEDIWALL_TARBALL_FILE }}"
|
||||
dest: "{{ FEDIWALL_UPSTREAM_DIR_ABS }}"
|
||||
remote_src: true
|
||||
extra_opts:
|
||||
- --strip-components=1
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "Wipe Fediwall dist tree '{{ FEDIWALL_DIST_DIR_ABS }}' (so removed slugs disappear)"
|
||||
file:
|
||||
path: "{{ FEDIWALL_DIST_DIR_ABS }}"
|
||||
state: absent
|
||||
|
||||
- name: "Recreate Fediwall dist tree '{{ FEDIWALL_DIST_DIR_ABS }}'"
|
||||
file:
|
||||
path: "{{ FEDIWALL_DIST_DIR_ABS }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: "Stage upstream Fediwall files into per-slug folder '{{ wall.slug }}'"
|
||||
copy:
|
||||
src: "{{ FEDIWALL_UPSTREAM_DIR_ABS }}/"
|
||||
dest: "{{ [ FEDIWALL_DIST_DIR_ABS, wall.slug ] | path_join }}/"
|
||||
remote_src: true
|
||||
mode: preserve
|
||||
loop: "{{ FEDIWALL_WALLS }}"
|
||||
loop_control:
|
||||
loop_var: wall
|
||||
label: "{{ wall.slug }}"
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "Render per-slug Fediwall wall-config.json for '{{ wall.slug }}'"
|
||||
template:
|
||||
src: "wall-config.json.j2"
|
||||
dest: "{{ [ FEDIWALL_DIST_DIR_ABS, wall.slug, 'wall-config.json' ] | path_join }}"
|
||||
mode: "0644"
|
||||
loop: "{{ FEDIWALL_WALLS }}"
|
||||
loop_control:
|
||||
loop_var: wall
|
||||
label: "{{ wall.slug }}"
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "Render Fediwall root link list at '{{ FEDIWALL_DIST_DIR_ABS }}/index.html'"
|
||||
template:
|
||||
src: "index.html.j2"
|
||||
dest: "{{ [ FEDIWALL_DIST_DIR_ABS, 'index.html' ] | path_join }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "flush compose for '{{ application_id }}'"
|
||||
meta: flush_handlers
|
||||
@@ -7,36 +7,6 @@
|
||||
vars:
|
||||
compose_handlers_flush: false
|
||||
|
||||
- name: Integrate LDAP
|
||||
include_tasks: 02_ldap.yml
|
||||
when: FRIENDICA_LDAP_ENABLED | bool
|
||||
|
||||
# General
|
||||
- name: "flush handlers to ensure that '{{ FRIENDICA_CONTAINER }}' is up before executing container tasks"
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "Remove stale ldapauth.config.php leftover when LDAP is disabled"
|
||||
command: >
|
||||
container exec --user root {{ FRIENDICA_CONTAINER }}
|
||||
rm -f {{ FRIENDICA_LDAP_CONFIG_DOCKER }}
|
||||
register: friendica_ldap_config_cleanup
|
||||
changed_when: friendica_ldap_config_cleanup.rc == 0
|
||||
when: not (FRIENDICA_LDAP_ENABLED | bool)
|
||||
|
||||
- name: Check if Friendica local.config.php exists
|
||||
command: >
|
||||
container exec --user {{ FRIENDICA_USER }} {{ FRIENDICA_CONTAINER }} test -f {{ FRIENDICA_CONFIG_FILE }}
|
||||
register: friendica_config_exists
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Patch Friendica local.config.php with updated DB credentials
|
||||
include_tasks: 03_patch_config.yml
|
||||
when: friendica_config_exists.rc == 0
|
||||
|
||||
- name: Add Friendica Add Ons
|
||||
include_tasks: 04_addons.yml
|
||||
|
||||
- name: Bootstrap Friendica administrator user (only when no LDAP provider is wired to provision them)
|
||||
include_tasks: 06_user_bootstrap.yml
|
||||
when: not (FRIENDICA_LDAP_ENABLED | bool)
|
||||
- name: "Friendica manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
- name: Integrate LDAP
|
||||
include_tasks: 02_ldap.yml
|
||||
when: FRIENDICA_LDAP_ENABLED | bool
|
||||
|
||||
- name: "flush handlers to ensure that '{{ FRIENDICA_CONTAINER }}' is up before executing container tasks"
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "Remove stale ldapauth.config.php leftover when LDAP is disabled"
|
||||
command: >
|
||||
container exec --user root {{ FRIENDICA_CONTAINER }}
|
||||
rm -f {{ FRIENDICA_LDAP_CONFIG_DOCKER }}
|
||||
register: friendica_ldap_config_cleanup
|
||||
changed_when: friendica_ldap_config_cleanup.rc == 0
|
||||
when: not (FRIENDICA_LDAP_ENABLED | bool)
|
||||
|
||||
- name: Check if Friendica local.config.php exists
|
||||
command: >
|
||||
container exec --user {{ FRIENDICA_USER }} {{ FRIENDICA_CONTAINER }} test -f {{ FRIENDICA_CONFIG_FILE }}
|
||||
register: friendica_config_exists
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Patch Friendica local.config.php with updated DB credentials
|
||||
include_tasks: 03_patch_config.yml
|
||||
when: friendica_config_exists.rc == 0
|
||||
|
||||
- name: Add Friendica Add Ons
|
||||
include_tasks: 04_addons.yml
|
||||
|
||||
- name: Bootstrap Friendica administrator user (only when no LDAP provider is wired to provision them)
|
||||
include_tasks: 06_user_bootstrap.yml
|
||||
when: not (FRIENDICA_LDAP_ENABLED | bool)
|
||||
@@ -7,67 +7,6 @@
|
||||
vars:
|
||||
compose_handlers_flush: true
|
||||
|
||||
- name: Wait for Gitea HTTP endpoint
|
||||
wait_for:
|
||||
host: "{{ DOCKER_REACH_HOST }}"
|
||||
port: "{{ lookup('config', application_id, 'services.gitea.ports.local.http') }}"
|
||||
delay: 5
|
||||
timeout: 300
|
||||
|
||||
- name: Patch Gitea database settings in app.ini
|
||||
include_tasks: 02_database.yml
|
||||
|
||||
- name: "Run DB migrations inside Gitea container"
|
||||
shell: |
|
||||
container exec -i --user {{ GITEA_USER }} {{ GITEA_CONTAINER }} \
|
||||
/app/gitea/gitea migrate
|
||||
args:
|
||||
chdir: "{{ lookup('container', application_id, 'directories.instance') }}"
|
||||
register: migrate
|
||||
changed_when: "'migrations completed' in migrate.stdout"
|
||||
|
||||
- name: "Create initial admin user"
|
||||
shell: |
|
||||
container exec -i --user {{ GITEA_USER }} {{ GITEA_CONTAINER }} \
|
||||
/app/gitea/gitea admin user create \
|
||||
--admin \
|
||||
--username "{{ lookup('users', 'administrator').username }}" \
|
||||
--password {{ lookup('users', 'administrator').password | quote }} \
|
||||
--email "{{ lookup('users', 'administrator').email }}" \
|
||||
-c {{ GITEA_CONFIG }}
|
||||
args:
|
||||
chdir: "{{ lookup('container', application_id, 'directories.instance') }}"
|
||||
register: create_admin
|
||||
changed_when: "'has been successfully created' in create_admin.stdout"
|
||||
failed_when: create_admin.rc != 0 and 'user already exists' not in create_admin.stderr
|
||||
|
||||
- name: "Wait until Gitea setup and migrations are ready"
|
||||
uri_retry:
|
||||
url: "http://{{ DOCKER_REACH_HOST }}:{{ lookup('config', application_id, 'services.gitea.ports.local.http') }}/api/v1/version"
|
||||
method: GET
|
||||
status_code: 200
|
||||
return_content: false
|
||||
register: gitea_ready
|
||||
until: gitea_ready.status == 200
|
||||
retries: 20
|
||||
delay: 5
|
||||
when: (GITEA_OIDC_ENABLED | bool) or (GITEA_LDAP_ENABLED | bool)
|
||||
|
||||
- name: Execute Setup Routines
|
||||
include_tasks: 03_setup.yml
|
||||
|
||||
- name: Execute Cleanup Routines
|
||||
include_tasks: 04_cleanup.yml
|
||||
when: MODE_CLEANUP | bool
|
||||
|
||||
- name: Include DNS role to register Gitea domain(s)
|
||||
include_role:
|
||||
name: sys-dns-cloudflare-records
|
||||
vars:
|
||||
cloudflare_records:
|
||||
- zone: "{{ lookup('domain',application_id) | to_zone }}"
|
||||
type: A
|
||||
name: "{{ lookup('domain',application_id) }}"
|
||||
content: "{{ networks.internet.ip4 }}"
|
||||
proxied: false # Necessary for SSH port
|
||||
when: DNS_PROVIDER == 'cloudflare'
|
||||
- name: "Gitea manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
- name: Wait for Gitea HTTP endpoint
|
||||
wait_for:
|
||||
host: "{{ DOCKER_REACH_HOST }}"
|
||||
port: "{{ lookup('config', application_id, 'services.gitea.ports.local.http') }}"
|
||||
delay: 5
|
||||
timeout: 300
|
||||
|
||||
- name: Patch Gitea database settings in app.ini
|
||||
include_tasks: 02_database.yml
|
||||
|
||||
- name: "Run DB migrations inside Gitea container"
|
||||
shell: |
|
||||
container exec -i --user {{ GITEA_USER }} {{ GITEA_CONTAINER }} \
|
||||
/app/gitea/gitea migrate
|
||||
args:
|
||||
chdir: "{{ lookup('container', application_id, 'directories.instance') }}"
|
||||
register: migrate
|
||||
changed_when: "'migrations completed' in migrate.stdout"
|
||||
|
||||
- name: "Create initial admin user"
|
||||
shell: |
|
||||
container exec -i --user {{ GITEA_USER }} {{ GITEA_CONTAINER }} \
|
||||
/app/gitea/gitea admin user create \
|
||||
--admin \
|
||||
--username "{{ lookup('users', 'administrator').username }}" \
|
||||
--password {{ lookup('users', 'administrator').password | quote }} \
|
||||
--email "{{ lookup('users', 'administrator').email }}" \
|
||||
-c {{ GITEA_CONFIG }}
|
||||
args:
|
||||
chdir: "{{ lookup('container', application_id, 'directories.instance') }}"
|
||||
register: create_admin
|
||||
changed_when: "'has been successfully created' in create_admin.stdout"
|
||||
failed_when: create_admin.rc != 0 and 'user already exists' not in create_admin.stderr
|
||||
|
||||
- name: "Wait until Gitea setup and migrations are ready"
|
||||
uri_retry:
|
||||
url: "http://{{ DOCKER_REACH_HOST }}:{{ lookup('config', application_id, 'services.gitea.ports.local.http') }}/api/v1/version"
|
||||
method: GET
|
||||
status_code: 200
|
||||
return_content: false
|
||||
register: gitea_ready
|
||||
until: gitea_ready.status == 200
|
||||
retries: 20
|
||||
delay: 5
|
||||
when: (GITEA_OIDC_ENABLED | bool) or (GITEA_LDAP_ENABLED | bool)
|
||||
|
||||
- name: Execute Setup Routines
|
||||
include_tasks: 03_setup.yml
|
||||
|
||||
- name: Execute Cleanup Routines
|
||||
include_tasks: 04_cleanup.yml
|
||||
when: MODE_CLEANUP | bool
|
||||
|
||||
- name: Include DNS role to register Gitea domain(s)
|
||||
include_role:
|
||||
name: sys-dns-cloudflare-records
|
||||
vars:
|
||||
cloudflare_records:
|
||||
- zone: "{{ lookup('domain',application_id) | to_zone }}"
|
||||
type: A
|
||||
name: "{{ lookup('domain',application_id) }}"
|
||||
content: "{{ networks.internet.ip4 }}"
|
||||
proxied: false
|
||||
when: DNS_PROVIDER == 'cloudflare'
|
||||
@@ -14,6 +14,7 @@
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose up
|
||||
when: IS_STACK_HOST | bool
|
||||
|
||||
# `sys-svc-compose` auto-discovers and lays down the Dockerfile, but
|
||||
# the Dockerfile `COPY` lines reference sidecar artefacts (plugins.txt,
|
||||
@@ -32,3 +33,4 @@
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -7,23 +7,6 @@
|
||||
vars:
|
||||
compose_handlers_flush: false
|
||||
|
||||
- name: "ensure prosody plugin + config override dirs exist (templates need them before containers come up)"
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ JITSI_PROSODY_PLUGINS_HOST_PATH }}"
|
||||
- "{{ JITSI_PROSODY_CONFIG_HOST_PATH }}"
|
||||
- "{{ JITSI_PROSODY_CONFIG_HOST_PATH }}/conf.d"
|
||||
|
||||
- name: "stage prosody LDAP authentication override (mod_auth_ldap2)"
|
||||
ansible.builtin.template:
|
||||
src: prosody-ldap.cfg.lua.j2
|
||||
dest: "{{ JITSI_PROSODY_CONFIG_HOST_PATH }}/conf.d/ldap.cfg.lua"
|
||||
mode: "0644"
|
||||
notify: compose up
|
||||
when: JITSI_LDAP_ENABLED
|
||||
|
||||
- name: "flush handlers so Jitsi containers start with rendered env"
|
||||
meta: flush_handlers
|
||||
- name: "Jitsi manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
- name: "ensure prosody plugin + config override dirs exist (templates need them before containers come up)"
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ JITSI_PROSODY_PLUGINS_HOST_PATH }}"
|
||||
- "{{ JITSI_PROSODY_CONFIG_HOST_PATH }}"
|
||||
- "{{ JITSI_PROSODY_CONFIG_HOST_PATH }}/conf.d"
|
||||
|
||||
- name: "stage prosody LDAP authentication override (mod_auth_ldap2)"
|
||||
ansible.builtin.template:
|
||||
src: prosody-ldap.cfg.lua.j2
|
||||
dest: "{{ JITSI_PROSODY_CONFIG_HOST_PATH }}/conf.d/ldap.cfg.lua"
|
||||
mode: "0644"
|
||||
notify: compose up
|
||||
when: JITSI_LDAP_ENABLED
|
||||
|
||||
- name: "flush handlers so Jitsi containers start with rendered env"
|
||||
meta: flush_handlers
|
||||
@@ -14,63 +14,6 @@
|
||||
vars:
|
||||
compose_handlers_flush: true
|
||||
|
||||
- name: "Load Login routines for '{{ application_id }}'"
|
||||
include_tasks: 04_login.yml
|
||||
|
||||
- name: "Load one-shot realm import routines for '{{ application_id }}'"
|
||||
include_tasks: 05_realm_import.yml
|
||||
|
||||
- name: "Load Client Update routines for '{{ application_id }}'"
|
||||
include_tasks: update/01_client.yml
|
||||
|
||||
- name: "Load Mail Update routines for '{{ application_id }} - {{ KEYCLOAK_REALM }}'"
|
||||
include_tasks: update/02_mail_realm.yml
|
||||
|
||||
- name: "Load Mail Update routines for '{{ application_id }} - master'"
|
||||
include_tasks: update/03_mail_master.yml
|
||||
|
||||
- name: Ensure jq is installed
|
||||
become: true
|
||||
package:
|
||||
name: jq
|
||||
state: present
|
||||
|
||||
- name: "Load RBAC Update routines for '{{ application_id }}'"
|
||||
include_tasks: update/04_rbac_client_scope.yml
|
||||
|
||||
- block:
|
||||
- name: "Load LDAP Update routines for '{{ application_id }}'"
|
||||
include_tasks: update/05_ldap.yml
|
||||
|
||||
- name: Assert Keycloak LDAP configuration
|
||||
include_tasks: assert/05_ldap.yml
|
||||
when: MODE_ASSERT | bool
|
||||
|
||||
- name: "Load per-app group-ldap-mapper routines for '{{ application_id }}'"
|
||||
include_tasks: update/05c_per_app_mappers.yml
|
||||
|
||||
when:
|
||||
- KEYCLOAK_LDAP_ENABLED | bool
|
||||
- KEYCLOAK_LDAP_SHARED | bool
|
||||
|
||||
- name: "Remove LDAP federation routines for '{{ application_id }}' when LDAP is disabled"
|
||||
include_tasks: update/05_ldap_disabled.yml
|
||||
when: not KEYCLOAK_LDAP_ENABLED | bool
|
||||
|
||||
- name: "Load reCAPTCHA Update routines for '{{ application_id }}'"
|
||||
include_tasks: update/06_recaptcha.yml
|
||||
when: KEYCLOAK_RECAPTCHA_ENABLED | bool
|
||||
|
||||
- name: "Load UserProfile (declarative) Update routines for '{{ application_id }}'"
|
||||
include_tasks: update/07_userprofile.yml
|
||||
|
||||
- name: "Load Realm User provisioning routines for '{{ application_id }}'"
|
||||
include_tasks: update/08_users_realm.yml
|
||||
when: not KEYCLOAK_LDAP_ENABLED | bool
|
||||
|
||||
- name: "Load Users Update routines for '{{ application_id }}' (emailVerified)"
|
||||
include_tasks: update/08_users_email_verified.yml
|
||||
when: KEYCLOAK_USER_EMAILS_VERIFIED | bool
|
||||
|
||||
- name: "Flush handlers before leaving Keycloak scope"
|
||||
meta: flush_handlers
|
||||
- name: "Keycloak manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
- name: "Load Login routines for '{{ application_id }}'"
|
||||
include_tasks: 04_login.yml
|
||||
|
||||
- name: "Load one-shot realm import routines for '{{ application_id }}'"
|
||||
include_tasks: 05_realm_import.yml
|
||||
|
||||
- name: "Load Client Update routines for '{{ application_id }}'"
|
||||
include_tasks: update/01_client.yml
|
||||
|
||||
- name: "Load Mail Update routines for '{{ application_id }} - {{ KEYCLOAK_REALM }}'"
|
||||
include_tasks: update/02_mail_realm.yml
|
||||
|
||||
- name: "Load Mail Update routines for '{{ application_id }} - master'"
|
||||
include_tasks: update/03_mail_master.yml
|
||||
|
||||
- name: Ensure jq is installed
|
||||
become: true
|
||||
package:
|
||||
name: jq
|
||||
state: present
|
||||
|
||||
- name: "Load RBAC Update routines for '{{ application_id }}'"
|
||||
include_tasks: update/04_rbac_client_scope.yml
|
||||
|
||||
- block:
|
||||
- name: "Load LDAP Update routines for '{{ application_id }}'"
|
||||
include_tasks: update/05_ldap.yml
|
||||
|
||||
- name: Assert Keycloak LDAP configuration
|
||||
include_tasks: assert/05_ldap.yml
|
||||
when: MODE_ASSERT | bool
|
||||
|
||||
- name: "Load per-app group-ldap-mapper routines for '{{ application_id }}'"
|
||||
include_tasks: update/05c_per_app_mappers.yml
|
||||
|
||||
when:
|
||||
- KEYCLOAK_LDAP_ENABLED | bool
|
||||
- KEYCLOAK_LDAP_SHARED | bool
|
||||
|
||||
- name: "Remove LDAP federation routines for '{{ application_id }}' when LDAP is disabled"
|
||||
include_tasks: update/05_ldap_disabled.yml
|
||||
when: not KEYCLOAK_LDAP_ENABLED | bool
|
||||
|
||||
- name: "Load reCAPTCHA Update routines for '{{ application_id }}'"
|
||||
include_tasks: update/06_recaptcha.yml
|
||||
when: KEYCLOAK_RECAPTCHA_ENABLED | bool
|
||||
|
||||
- name: "Load UserProfile (declarative) Update routines for '{{ application_id }}'"
|
||||
include_tasks: update/07_userprofile.yml
|
||||
|
||||
- name: "Load Realm User provisioning routines for '{{ application_id }}'"
|
||||
include_tasks: update/08_users_realm.yml
|
||||
when: not KEYCLOAK_LDAP_ENABLED | bool
|
||||
|
||||
- name: "Load Users Update routines for '{{ application_id }}' (emailVerified)"
|
||||
include_tasks: update/08_users_email_verified.yml
|
||||
when: KEYCLOAK_USER_EMAILS_VERIFIED | bool
|
||||
|
||||
- name: "Flush handlers before leaving Keycloak scope"
|
||||
meta: flush_handlers
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
- name: add config.toml
|
||||
template:
|
||||
src: "config.toml.j2"
|
||||
dest: "{{ LISTMONK_CONFIG_HOST }}"
|
||||
notify: compose up
|
||||
|
||||
- meta: flush_handlers
|
||||
|
||||
- name: Include database setup routine for Listmonk
|
||||
include_tasks: "01_database.yml"
|
||||
|
||||
- name: Build OIDC settings JSON
|
||||
set_fact:
|
||||
oidc_settings_json: >-
|
||||
{{ {
|
||||
"enabled": True,
|
||||
"client_id": OIDC.CLIENT.ID,
|
||||
"provider_url": OIDC.CLIENT.ISSUER_URL,
|
||||
"client_secret": OIDC.CLIENT.SECRET
|
||||
} | to_json }}
|
||||
|
||||
- name: Update administrator email and password login in Listmonk (as superuser)
|
||||
shell: |
|
||||
{{ LISTMONK_EXEC_DATABASE }} -v ON_ERROR_STOP=1 << 'EOSQL'
|
||||
UPDATE users
|
||||
SET email = '{{ lookup('users', 'administrator').email }}',
|
||||
password_login = {{ 'false' if lookup('config',application_id, 'services.sso.enabled') else 'true' }}
|
||||
WHERE username = 'administrator';
|
||||
EOSQL
|
||||
args:
|
||||
executable: /bin/bash
|
||||
|
||||
- name: Apply all Listmonk settings
|
||||
shell: |
|
||||
{{ LISTMONK_EXEC_DATABASE }} -v ON_ERROR_STOP=1 << 'EOSQL'
|
||||
UPDATE settings
|
||||
SET value = '{{ item.value }}'::jsonb
|
||||
WHERE key = '{{ item.key }}';
|
||||
EOSQL
|
||||
args:
|
||||
executable: /bin/bash
|
||||
loop: "{{ LISTMONK_SETTINGS }}"
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
when: (item.when | default(true)) | bool
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
|
||||
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
|
||||
@@ -11,51 +11,6 @@
|
||||
""
|
||||
{% endif %}
|
||||
|
||||
- name: add config.toml
|
||||
template:
|
||||
src: "config.toml.j2"
|
||||
dest: "{{ LISTMONK_CONFIG_HOST }}"
|
||||
notify: compose up
|
||||
|
||||
- meta: flush_handlers
|
||||
|
||||
- name: Include database setup routine for Listmonk
|
||||
include_tasks: "01_database.yml"
|
||||
|
||||
- name: Build OIDC settings JSON
|
||||
set_fact:
|
||||
oidc_settings_json: >-
|
||||
{{ {
|
||||
"enabled": True,
|
||||
"client_id": OIDC.CLIENT.ID,
|
||||
"provider_url": OIDC.CLIENT.ISSUER_URL,
|
||||
"client_secret": OIDC.CLIENT.SECRET
|
||||
} | to_json }}
|
||||
|
||||
- name: Update administrator email and password login in Listmonk (as superuser)
|
||||
shell: |
|
||||
{{ LISTMONK_EXEC_DATABASE }} -v ON_ERROR_STOP=1 << 'EOSQL'
|
||||
UPDATE users
|
||||
SET email = '{{ lookup('users', 'administrator').email }}',
|
||||
password_login = {{ 'false' if lookup('config',application_id, 'services.sso.enabled') else 'true' }}
|
||||
WHERE username = 'administrator';
|
||||
EOSQL
|
||||
args:
|
||||
executable: /bin/bash
|
||||
|
||||
- name: Apply all Listmonk settings
|
||||
shell: |
|
||||
{{ LISTMONK_EXEC_DATABASE }} -v ON_ERROR_STOP=1 << 'EOSQL'
|
||||
UPDATE settings
|
||||
SET value = '{{ item.value }}'::jsonb
|
||||
WHERE key = '{{ item.key }}';
|
||||
EOSQL
|
||||
args:
|
||||
executable: /bin/bash
|
||||
loop: "{{ LISTMONK_SETTINGS }}"
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
when: (item.when | default(true)) | bool
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
async: "{{ ASYNC_TIME if ASYNC_ENABLED | bool else omit }}"
|
||||
poll: "{{ ASYNC_POLL if ASYNC_ENABLED | bool else omit }}"
|
||||
- name: "Listmonk manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -8,29 +8,6 @@
|
||||
compose_repository_pull: true
|
||||
compose_repository_ref: "{{ LITTLEJS_REF }}"
|
||||
|
||||
- name: "Load LittleJS example metadata"
|
||||
include_vars:
|
||||
file: "examples.yml"
|
||||
|
||||
- name: "Render LittleJS index.html"
|
||||
template:
|
||||
src: "html/index.html.j2"
|
||||
dest: "{{ LITTLEJS_INDEX_HOST_ABS }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "Render LittleJS run.html for shorts"
|
||||
template:
|
||||
src: "html/run.html.j2"
|
||||
dest: "{{ LITTLEJS_RUN_HOST_ABS }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "flush compose for '{{ application_id }}'"
|
||||
meta: flush_handlers
|
||||
|
||||
- include_tasks: utils/once/flag.yml
|
||||
- name: "LittleJS manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
- name: "Load LittleJS example metadata"
|
||||
include_vars:
|
||||
file: "examples.yml"
|
||||
|
||||
- name: "Render LittleJS index.html"
|
||||
template:
|
||||
src: "html/index.html.j2"
|
||||
dest: "{{ LITTLEJS_INDEX_HOST_ABS }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "Render LittleJS run.html for shorts"
|
||||
template:
|
||||
src: "html/run.html.j2"
|
||||
dest: "{{ LITTLEJS_RUN_HOST_ABS }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose build
|
||||
- compose up
|
||||
|
||||
- name: "flush compose for '{{ application_id }}'"
|
||||
meta: flush_handlers
|
||||
|
||||
- include_tasks: utils/once/flag.yml
|
||||
@@ -21,6 +21,7 @@
|
||||
'
|
||||
args:
|
||||
creates: "{{ [ (MAGENTO_VOLUME | docker_volume_path), 'bin/magento' ] | path_join }}"
|
||||
when: IS_STACK_HOST | bool
|
||||
|
||||
- name: "Run Magento setup:install (in container)"
|
||||
command: >
|
||||
@@ -48,5 +49,6 @@
|
||||
(magento_install.stdout is defined and
|
||||
('Magento installation complete' in magento_install.stdout
|
||||
or 'successfully installed' in magento_install.stdout))
|
||||
when: IS_STACK_HOST | bool
|
||||
|
||||
- include_tasks: utils/once/flag.yml
|
||||
@@ -36,45 +36,6 @@
|
||||
webserver_vhost_flavour: "basic"
|
||||
domain: "{{ MAILU_HOSTNAME }}"
|
||||
|
||||
- name: "Flush Docker Compose handlers"
|
||||
meta: flush_handlers
|
||||
|
||||
- name: Wait for Mailu MariaDB
|
||||
include_tasks: 02_wait_for_mariadb.yml
|
||||
|
||||
- name: Wait for Mailu Redis
|
||||
include_tasks: 03_wait_for_redis.yml
|
||||
|
||||
- name: "Create Mailu accounts"
|
||||
include_tasks: 04_manage_user.yml
|
||||
vars:
|
||||
mailu_action: >-
|
||||
{{
|
||||
(
|
||||
'administrator' in (user_item.value.get('roles', []))
|
||||
)
|
||||
| ternary('admin','user')
|
||||
}}
|
||||
mailu_user_key: "{{ user_item.key }}"
|
||||
mailu_user_name: "{{ user_item.value.username }}"
|
||||
mailu_user_password: "{{ user_item.value.password }}"
|
||||
mailu_token_ip: "{{ user_item.value.ip | default(networks.internet.ip4) }}"
|
||||
mailu_token_name: "{{ SOFTWARE_NAME ~ ' Token for ' ~ user_item.value.username }}"
|
||||
mailu_user_roles: "{{ user_item.value.roles | default([]) }}"
|
||||
loop: "{{ lookup('users') | dict2items }}"
|
||||
loop_control:
|
||||
loop_var: user_item
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
|
||||
- name: "Include Cert deploy service for '{{ role_name }}'"
|
||||
include_role:
|
||||
name: sys-ctl-mtn-cert-deploy
|
||||
when: lookup('tls', application_id, 'enabled') | bool
|
||||
# Alarm mail setup reaches sys-svc-mail and therefore requires the Mailu
|
||||
# API token that is created in 04_manage_user.yml.
|
||||
|
||||
- name: Generate DKIM public key
|
||||
include_tasks: 05_generate-and-read-dkim.yml
|
||||
|
||||
- name: Set Mailu DNS records
|
||||
include_tasks: 06_dns-records.yml
|
||||
- name: "Mailu manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
- name: "Flush Docker Compose handlers"
|
||||
meta: flush_handlers
|
||||
|
||||
- name: Wait for Mailu MariaDB
|
||||
include_tasks: 02_wait_for_mariadb.yml
|
||||
|
||||
- name: Wait for Mailu Redis
|
||||
include_tasks: 03_wait_for_redis.yml
|
||||
|
||||
- name: "Create Mailu accounts"
|
||||
include_tasks: 04_manage_user.yml
|
||||
vars:
|
||||
mailu_action: >-
|
||||
{{
|
||||
(
|
||||
'administrator' in (user_item.value.get('roles', []))
|
||||
)
|
||||
| ternary('admin','user')
|
||||
}}
|
||||
mailu_user_key: "{{ user_item.key }}"
|
||||
mailu_user_name: "{{ user_item.value.username }}"
|
||||
mailu_user_password: "{{ user_item.value.password }}"
|
||||
mailu_token_ip: "{{ user_item.value.ip | default(networks.internet.ip4) }}"
|
||||
mailu_token_name: "{{ SOFTWARE_NAME ~ ' Token for ' ~ user_item.value.username }}"
|
||||
mailu_user_roles: "{{ user_item.value.roles | default([]) }}"
|
||||
loop: "{{ lookup('users') | dict2items }}"
|
||||
loop_control:
|
||||
loop_var: user_item
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
|
||||
- name: "Include Cert deploy service for '{{ role_name }}'"
|
||||
include_role:
|
||||
name: sys-ctl-mtn-cert-deploy
|
||||
when: lookup('tls', application_id, 'enabled') | bool
|
||||
|
||||
- name: Generate DKIM public key
|
||||
include_tasks: 05_generate-and-read-dkim.yml
|
||||
|
||||
- name: Set Mailu DNS records
|
||||
include_tasks: 06_dns-records.yml
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
# Required to be set on the top so global frontend service loading can rely on run_once guards.
|
||||
- include_tasks: utils/once/flag.yml
|
||||
|
||||
- name: "Matomo | Resolve token from store/config"
|
||||
@@ -9,76 +8,8 @@
|
||||
include_role:
|
||||
name: sys-stk-full
|
||||
vars:
|
||||
# Suppress flushing of handlers, because bootstrap file needs to be deployed first
|
||||
compose_handlers_flush: false
|
||||
|
||||
- name: "Deploy '{{ MATOMO_BOOTSTRAP_ENV_FILE }}'"
|
||||
template:
|
||||
src: "templates/bootstrap.env.j2"
|
||||
dest: "{{ MATOMO_BOOTSTRAP_ENV_FILE }}"
|
||||
mode: "0640"
|
||||
force: true
|
||||
notify: compose up
|
||||
|
||||
- name: flush docker service
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "Matomo install | Wait for Matomo container to be running (any state)"
|
||||
ansible.builtin.command: >
|
||||
container inspect -f '{{ "{{" }}.State.Running{{ "}}" }}' {{ MATOMO_NAME }}
|
||||
register: matomo_running
|
||||
changed_when: false
|
||||
retries: 30
|
||||
delay: 2
|
||||
until: >
|
||||
matomo_running.rc == 0 and (matomo_running.stdout | trim) == 'true'
|
||||
|
||||
- name: "Matomo install | Check whether Matomo is already installed (config.ini.php exists)"
|
||||
command:
|
||||
argv:
|
||||
- container
|
||||
- exec
|
||||
- "{{ MATOMO_NAME }}"
|
||||
- sh
|
||||
- -lc
|
||||
- 'test -f "{{ MATOMO_CONFIG }}"'
|
||||
register: matomo_config_exists
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: "Bootstrap Matomo (Initialisation and setting of API token if not defined)"
|
||||
include_tasks: 03a_bootstrap.yml
|
||||
when: >
|
||||
(matomo_config_exists.rc != 0) or
|
||||
((lookup('users', 'administrator').tokens['web-app-matomo'] | default('') | string | trim) == "")
|
||||
|
||||
# `04_patch_database.yml` rewrites the DB host/name/user/password lines in
|
||||
# `config.ini.php` to whatever the current deploy renders. Without this, a
|
||||
# matrix-deploy second round restarts the matomo container against the
|
||||
# round-2 MariaDB user/password while `config.ini.php` still carries the
|
||||
# round-1 credentials, and the healthcheck loops forever on
|
||||
# "Database access denied". The patch MUST run before the wait-for-healthy
|
||||
# probe so the container has the correct credentials by the time the
|
||||
# probe gates the rest of the play.
|
||||
- name: "Patch database credentials for {{ application_id }}"
|
||||
include_tasks: 04_patch_database.yml
|
||||
when: matomo_config_exists.rc == 0
|
||||
|
||||
- name: "Patch Trusted Hosts for {{ application_id }}"
|
||||
include_tasks: 05_patch_trusted_hosts.yml
|
||||
|
||||
- name: flush docker service
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "Matomo install | Wait for Matomo container to be healthy"
|
||||
ansible.builtin.command: >
|
||||
container inspect -f '{{ "{{" }}.State.Health.Status{{ "}}" }}' {{ MATOMO_NAME }}
|
||||
register: matomo_health
|
||||
changed_when: false
|
||||
retries: 60
|
||||
delay: 2
|
||||
until: >
|
||||
matomo_health.rc == 0 and (matomo_health.stdout | trim) == 'healthy'
|
||||
|
||||
- name: "Config '{{ application_id }}' via API"
|
||||
include_tasks: 06_config.yml
|
||||
- name: "Matomo manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
- name: "Deploy '{{ MATOMO_BOOTSTRAP_ENV_FILE }}'"
|
||||
template:
|
||||
src: "templates/bootstrap.env.j2"
|
||||
dest: "{{ MATOMO_BOOTSTRAP_ENV_FILE }}"
|
||||
mode: "0640"
|
||||
force: true
|
||||
notify: compose up
|
||||
|
||||
- name: flush docker service
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "Matomo install | Wait for Matomo container to be running (any state)"
|
||||
ansible.builtin.command: >
|
||||
container inspect -f '{{ "{{" }}.State.Running{{ "}}" }}' {{ MATOMO_NAME }}
|
||||
register: matomo_running
|
||||
changed_when: false
|
||||
retries: 30
|
||||
delay: 2
|
||||
until: >
|
||||
matomo_running.rc == 0 and (matomo_running.stdout | trim) == 'true'
|
||||
|
||||
- name: "Matomo install | Check whether Matomo is already installed (config.ini.php exists)"
|
||||
command:
|
||||
argv:
|
||||
- container
|
||||
- exec
|
||||
- "{{ MATOMO_NAME }}"
|
||||
- sh
|
||||
- -lc
|
||||
- 'test -f "{{ MATOMO_CONFIG }}"'
|
||||
register: matomo_config_exists
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: "Bootstrap Matomo (Initialisation and setting of API token if not defined)"
|
||||
include_tasks: 03a_bootstrap.yml
|
||||
when: >
|
||||
(matomo_config_exists.rc != 0) or
|
||||
((lookup('users', 'administrator').tokens['web-app-matomo'] | default('') | string | trim) == "")
|
||||
|
||||
# `04_patch_database.yml` rewrites the DB host/name/user/password lines in
|
||||
# `config.ini.php` to whatever the current deploy renders. Without this, a
|
||||
# matrix-deploy second round restarts the matomo container against the
|
||||
# round-2 MariaDB user/password while `config.ini.php` still carries the
|
||||
# round-1 credentials, and the healthcheck loops forever on
|
||||
# "Database access denied". The patch MUST run before the wait-for-healthy
|
||||
# probe so the container has the correct credentials by the time the
|
||||
# probe gates the rest of the play.
|
||||
- name: "Patch database credentials for {{ application_id }}"
|
||||
include_tasks: 04_patch_database.yml
|
||||
when: matomo_config_exists.rc == 0
|
||||
|
||||
- name: "Patch Trusted Hosts for {{ application_id }}"
|
||||
include_tasks: 05_patch_trusted_hosts.yml
|
||||
|
||||
- name: flush docker service
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "Matomo install | Wait for Matomo container to be healthy"
|
||||
ansible.builtin.command: >
|
||||
container inspect -f '{{ "{{" }}.State.Health.Status{{ "}}" }}' {{ MATOMO_NAME }}
|
||||
register: matomo_health
|
||||
changed_when: false
|
||||
retries: 60
|
||||
delay: 2
|
||||
until: >
|
||||
matomo_health.rc == 0 and (matomo_health.stdout | trim) == 'healthy'
|
||||
|
||||
- name: "Config '{{ application_id }}' via API"
|
||||
include_tasks: 06_config.yml
|
||||
@@ -10,3 +10,4 @@
|
||||
src: "config.exs.j2"
|
||||
dest: "{{ mobilizon_host_conf_exs_file }}"
|
||||
notify: compose up
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,85 @@
|
||||
---
|
||||
- name: "Populate Dockerfile build context (entrypoint, config.php, install scripts, php conf)"
|
||||
include_tasks: 01_build_context.yml
|
||||
|
||||
- name: "Ensure nginx vhost dir exists in compose dir"
|
||||
ansible.builtin.file:
|
||||
path: "{{ docker_compose.directories.instance | default(lookup('container', application_id, 'directories.instance')) }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: "Render nginx vhost into compose dir"
|
||||
ansible.builtin.template:
|
||||
src: "nginx-moodle.conf.j2"
|
||||
dest: "{{ docker_compose.directories.instance | default(lookup('container', application_id, 'directories.instance')) }}/nginx.conf"
|
||||
mode: "0640"
|
||||
notify: compose up
|
||||
|
||||
- name: flush docker service
|
||||
meta: flush_handlers
|
||||
|
||||
- name: Wait for Moodle container to become healthy
|
||||
block:
|
||||
- name: Wait until the Moodle container is healthy
|
||||
ansible.builtin.shell: >
|
||||
container inspect --format '{% raw %}{{.State.Health.Status}}{% endraw %}' {{ MOODLE_CONTAINER }}
|
||||
register: health_check
|
||||
until: health_check.stdout.strip() == "healthy"
|
||||
retries: 120
|
||||
delay: 5
|
||||
|
||||
rescue:
|
||||
- name: Show Moodle container status
|
||||
ansible.builtin.shell: >
|
||||
container inspect {{ MOODLE_CONTAINER }}
|
||||
register: moodle_inspect
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Show Moodle container logs
|
||||
ansible.builtin.shell: >
|
||||
container logs --tail=200 {{ MOODLE_CONTAINER }}
|
||||
register: moodle_logs
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Fail with diagnostic output
|
||||
ansible.builtin.fail:
|
||||
msg: |
|
||||
❌ Moodle container did not become healthy in time.
|
||||
|
||||
--- container inspect ---
|
||||
{{ moodle_inspect.stdout | default('no output') }}
|
||||
|
||||
--- container logs (last 200 lines) ---
|
||||
{{ moodle_logs.stdout | default('no output') }}
|
||||
|
||||
- name: "Run Moodle install (idempotent)"
|
||||
command: >
|
||||
container exec --user {{ MOODLE_RUNTIME_USER }} {{ MOODLE_CONTAINER }}
|
||||
php {{ MOODLE_CLI_DIR }}/install_database.php
|
||||
--agree-license
|
||||
--adminuser={{ lookup('config', application_id, 'users.administrator.username') }}
|
||||
--adminpass={{ lookup('config', application_id, 'credentials.user_password') | quote }}
|
||||
--adminemail={{ lookup('config', application_id, 'users.administrator.email') }}
|
||||
--fullname="{{ MOODLE_SITE_TITLE }}"
|
||||
--shortname=Academy
|
||||
register: moodle_install
|
||||
changed_when: "'Moodle has been installed' in moodle_install.stdout"
|
||||
failed_when:
|
||||
- moodle_install.rc != 0
|
||||
- "'Moodle is already installed' not in moodle_install.stderr"
|
||||
- "'already installed' not in moodle_install.stdout"
|
||||
- "'Database tables already present' not in moodle_install.stderr"
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
|
||||
- name: "Include ownership settings tasks for moodle"
|
||||
include_tasks: 02_ownership.yml
|
||||
|
||||
- name: "Configure OIDC login for Moodle (variant 0 only)"
|
||||
include_tasks: 03_oidc.yml
|
||||
when: MOODLE_OIDC_ENABLED | bool
|
||||
|
||||
- name: "Configure auth_ldap (sync-only in variant 0, primary login in variant 1)"
|
||||
include_tasks: 04_ldap_sync.yml
|
||||
when: MOODLE_LDAP_ENABLED | bool
|
||||
@@ -5,87 +5,6 @@
|
||||
vars:
|
||||
compose_handlers_flush: false
|
||||
|
||||
- name: "Populate Dockerfile build context (entrypoint, config.php, install scripts, php conf)"
|
||||
include_tasks: 01_build_context.yml
|
||||
|
||||
- name: "Ensure nginx vhost dir exists in compose dir"
|
||||
ansible.builtin.file:
|
||||
path: "{{ docker_compose.directories.instance | default(lookup('container', application_id, 'directories.instance')) }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: "Render nginx vhost into compose dir"
|
||||
ansible.builtin.template:
|
||||
src: "nginx-moodle.conf.j2"
|
||||
dest: "{{ docker_compose.directories.instance | default(lookup('container', application_id, 'directories.instance')) }}/nginx.conf"
|
||||
mode: "0640"
|
||||
notify: compose up
|
||||
|
||||
- name: flush docker service
|
||||
meta: flush_handlers
|
||||
|
||||
- name: Wait for Moodle container to become healthy
|
||||
block:
|
||||
- name: Wait until the Moodle container is healthy
|
||||
ansible.builtin.shell: >
|
||||
container inspect --format '{% raw %}{{.State.Health.Status}}{% endraw %}' {{ MOODLE_CONTAINER }}
|
||||
register: health_check
|
||||
until: health_check.stdout.strip() == "healthy"
|
||||
retries: 120
|
||||
delay: 5
|
||||
|
||||
rescue:
|
||||
- name: Show Moodle container status
|
||||
ansible.builtin.shell: >
|
||||
container inspect {{ MOODLE_CONTAINER }}
|
||||
register: moodle_inspect
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Show Moodle container logs
|
||||
ansible.builtin.shell: >
|
||||
container logs --tail=200 {{ MOODLE_CONTAINER }}
|
||||
register: moodle_logs
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Fail with diagnostic output
|
||||
ansible.builtin.fail:
|
||||
msg: |
|
||||
❌ Moodle container did not become healthy in time.
|
||||
|
||||
--- container inspect ---
|
||||
{{ moodle_inspect.stdout | default('no output') }}
|
||||
|
||||
--- container logs (last 200 lines) ---
|
||||
{{ moodle_logs.stdout | default('no output') }}
|
||||
|
||||
- name: "Run Moodle install (idempotent)"
|
||||
command: >
|
||||
container exec --user {{ MOODLE_RUNTIME_USER }} {{ MOODLE_CONTAINER }}
|
||||
php {{ MOODLE_CLI_DIR }}/install_database.php
|
||||
--agree-license
|
||||
--adminuser={{ lookup('config', application_id, 'users.administrator.username') }}
|
||||
--adminpass={{ lookup('config', application_id, 'credentials.user_password') | quote }}
|
||||
--adminemail={{ lookup('config', application_id, 'users.administrator.email') }}
|
||||
--fullname="{{ MOODLE_SITE_TITLE }}"
|
||||
--shortname=Academy
|
||||
register: moodle_install
|
||||
changed_when: "'Moodle has been installed' in moodle_install.stdout"
|
||||
failed_when:
|
||||
- moodle_install.rc != 0
|
||||
- "'Moodle is already installed' not in moodle_install.stderr"
|
||||
- "'already installed' not in moodle_install.stdout"
|
||||
- "'Database tables already present' not in moodle_install.stderr"
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
|
||||
- name: "Include ownership settings tasks for moodle"
|
||||
include_tasks: 02_ownership.yml
|
||||
|
||||
- name: "Configure OIDC login for Moodle (variant 0 only)"
|
||||
include_tasks: 03_oidc.yml
|
||||
when: MOODLE_OIDC_ENABLED | bool
|
||||
|
||||
- name: "Configure auth_ldap (sync-only in variant 0, primary login in variant 1)"
|
||||
include_tasks: 04_ldap_sync.yml
|
||||
when: MOODLE_LDAP_ENABLED | bool
|
||||
- name: "web-app-moodle manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
- name: "Create Odoo addons directory"
|
||||
file:
|
||||
path: "{{ ODOO_ADDONS_HOST }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: "Deploy Odoo configuration file"
|
||||
template:
|
||||
src: "odoo.conf.j2"
|
||||
dest: "{{ ODOO_CONF_FILE_HOST }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose up
|
||||
|
||||
- name: "Copy custom addons"
|
||||
copy:
|
||||
src: "addons/"
|
||||
dest: "{{ ODOO_ADDONS_HOST }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose up
|
||||
- compose build
|
||||
|
||||
- name: "Docker Compose Up for '{{ application_id }}'"
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "Wait for Odoo to be ready"
|
||||
uri_retry:
|
||||
url: "http://127.0.0.1:{{ lookup('config', application_id, 'services.odoo.ports.local.http') }}/web/health"
|
||||
status_code: 200
|
||||
timeout: 10
|
||||
register: odoo_health
|
||||
until: odoo_health.status == 200
|
||||
retries: 30
|
||||
delay: 10
|
||||
ignore_errors: true
|
||||
|
||||
- name: "Initialize Odoo database"
|
||||
include_tasks: 01_init_database.yml
|
||||
|
||||
- name: "Install core modules"
|
||||
include_tasks: 02_install_modules.yml
|
||||
|
||||
- name: "Create administrator user"
|
||||
include_tasks: 05_admin.yml
|
||||
|
||||
- name: "Configure OIDC provider"
|
||||
include_tasks: 03_oidc.yml
|
||||
when: ODOO_OIDC_ENABLED | bool
|
||||
|
||||
- name: "Configure LDAP authentication"
|
||||
include_tasks: 04_ldap.yml
|
||||
when: ODOO_LDAP_ENABLED | bool
|
||||
@@ -5,56 +5,6 @@
|
||||
vars:
|
||||
compose_handlers_flush: false
|
||||
|
||||
- name: "Create Odoo addons directory"
|
||||
file:
|
||||
path: "{{ ODOO_ADDONS_HOST }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: "Deploy Odoo configuration file"
|
||||
template:
|
||||
src: "odoo.conf.j2"
|
||||
dest: "{{ ODOO_CONF_FILE_HOST }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose up
|
||||
|
||||
- name: "Copy custom addons"
|
||||
copy:
|
||||
src: "addons/"
|
||||
dest: "{{ ODOO_ADDONS_HOST }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose up
|
||||
- compose build
|
||||
|
||||
- name: "Docker Compose Up for '{{ application_id }}'"
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "Wait for Odoo to be ready"
|
||||
uri_retry:
|
||||
url: "http://127.0.0.1:{{ lookup('config', application_id, 'services.odoo.ports.local.http') }}/web/health"
|
||||
status_code: 200
|
||||
timeout: 10
|
||||
register: odoo_health
|
||||
until: odoo_health.status == 200
|
||||
retries: 30
|
||||
delay: 10
|
||||
ignore_errors: true
|
||||
|
||||
- name: "Initialize Odoo database"
|
||||
include_tasks: 01_init_database.yml
|
||||
|
||||
- name: "Install core modules"
|
||||
include_tasks: 02_install_modules.yml
|
||||
|
||||
- name: "Create administrator user"
|
||||
include_tasks: 05_admin.yml
|
||||
|
||||
- name: "Configure OIDC provider"
|
||||
include_tasks: 03_oidc.yml
|
||||
when: ODOO_OIDC_ENABLED | bool
|
||||
|
||||
- name: "Configure LDAP authentication"
|
||||
include_tasks: 04_ldap.yml
|
||||
when: ODOO_LDAP_ENABLED | bool
|
||||
- name: "web-app-odoo manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
- name: "Create {{ OPENPROJECT_PLUGINS_FOLDER }}"
|
||||
file:
|
||||
path: "{{ OPENPROJECT_PLUGINS_FOLDER }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: "Transferring Gemfile to {{ OPENPROJECT_PLUGINS_FOLDER }} as Gemfile.plugins"
|
||||
copy:
|
||||
src: Gemfile
|
||||
dest: "{{ [OPENPROJECT_PLUGINS_FOLDER,'Gemfile.plugins'] | path_join }}"
|
||||
notify:
|
||||
- compose up
|
||||
- compose build
|
||||
|
||||
- name: "create {{ OPENPROJECT_DUMMY_DIR }}"
|
||||
file:
|
||||
path: "{{ OPENPROJECT_DUMMY_DIR }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: flush docker service
|
||||
meta: flush_handlers
|
||||
|
||||
- name: Deploy Settings
|
||||
include_tasks: 01_settings.yml
|
||||
|
||||
- name: Setup LDAP
|
||||
include_tasks: 02_ldap.yml
|
||||
when: OPENPROJECT_LDAP_ENABLED | bool
|
||||
|
||||
- name: Create OpenProject Administrator
|
||||
include_tasks: 03_admin.yml
|
||||
@@ -10,35 +10,6 @@
|
||||
compose_handlers_flush: false
|
||||
application_id: "web-app-openproject"
|
||||
|
||||
- name: "Create {{ OPENPROJECT_PLUGINS_FOLDER }}"
|
||||
file:
|
||||
path: "{{ OPENPROJECT_PLUGINS_FOLDER }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: "Transferring Gemfile to {{ OPENPROJECT_PLUGINS_FOLDER }} as Gemfile.plugins"
|
||||
copy:
|
||||
src: Gemfile
|
||||
dest: "{{ [OPENPROJECT_PLUGINS_FOLDER,'Gemfile.plugins'] | path_join }}"
|
||||
notify:
|
||||
- compose up
|
||||
- compose build
|
||||
|
||||
- name: "create {{ OPENPROJECT_DUMMY_DIR }}"
|
||||
file:
|
||||
path: "{{ OPENPROJECT_DUMMY_DIR }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: flush docker service
|
||||
meta: flush_handlers
|
||||
|
||||
- name: Deploy Settings
|
||||
include_tasks: 01_settings.yml
|
||||
|
||||
- name: Setup LDAP
|
||||
include_tasks: 02_ldap.yml
|
||||
when: OPENPROJECT_LDAP_ENABLED | bool
|
||||
|
||||
- name: Create OpenProject Administrator
|
||||
include_tasks: 03_admin.yml
|
||||
- name: "OpenProject manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,154 @@
|
||||
---
|
||||
- name: "Render OpenTalk controller config '{{ application_id }}'"
|
||||
ansible.builtin.template:
|
||||
src: controller.toml.j2
|
||||
dest: "{{ [lookup('container', application_id, 'directories.volumes'), 'controller.toml'] | path_join }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose up
|
||||
|
||||
- name: "Render LiveKit config '{{ application_id }}'"
|
||||
ansible.builtin.template:
|
||||
src: livekit.yaml.j2
|
||||
dest: "{{ [lookup('container', application_id, 'directories.volumes'), 'livekit.yaml'] | path_join }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose up
|
||||
|
||||
- name: "Render OpenTalk recorder config '{{ application_id }}'"
|
||||
ansible.builtin.template:
|
||||
src: recorder.toml.j2
|
||||
dest: "{{ [lookup('container', application_id, 'directories.volumes'), 'recorder.toml'] | path_join }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose up
|
||||
|
||||
- name: "Flush handlers for '{{ application_id }}' so compose up sees rendered configs"
|
||||
meta: flush_handlers
|
||||
|
||||
# The shared `infinito.nexus` realm client is public, so it cannot
|
||||
# introspect access tokens. Provision a dedicated confidential client
|
||||
# (`opentalk-controller`) with a service account that can call the
|
||||
# Keycloak admin API and the OIDC introspection endpoint.
|
||||
- name: "Authenticate kcadm against the master realm (for OpenTalk client provisioning)"
|
||||
ansible.builtin.shell: |-
|
||||
{{ KEYCLOAK_EXEC_CONTAINER }} sh -lc '
|
||||
set -e
|
||||
{{ KEYCLOAK_KCADM }} config credentials \
|
||||
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }} \
|
||||
--realm master \
|
||||
--user "$KEYCLOAK_PERMANENT_ADMIN_USERNAME" \
|
||||
--password "$KEYCLOAK_PERMANENT_ADMIN_PASSWORD"
|
||||
'
|
||||
vars:
|
||||
KEYCLOAK_EXEC_CONTAINER: "container exec -i {{ lookup('config', 'web-app-keycloak', 'services.keycloak.name') }}"
|
||||
KEYCLOAK_KCADM: "/opt/keycloak/bin/kcadm.sh"
|
||||
KEYCLOAK_SERVER_INTERNAL_URL: "http://127.0.0.1:8080"
|
||||
changed_when: false
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
|
||||
- name: "Upsert dedicated confidential Keycloak client for the OpenTalk controller"
|
||||
keycloak_kcadm_update:
|
||||
object_kind: "client"
|
||||
lookup_value: "{{ OPENTALK_CTRL_OIDC_CLIENT_ID }}"
|
||||
desired:
|
||||
clientId: "{{ OPENTALK_CTRL_OIDC_CLIENT_ID }}"
|
||||
name: "OpenTalk Controller"
|
||||
description: "Confidential client used by web-app-opentalk for token introspection and Keycloak admin API user search."
|
||||
enabled: true
|
||||
protocol: "openid-connect"
|
||||
publicClient: false
|
||||
bearerOnly: false
|
||||
standardFlowEnabled: false
|
||||
implicitFlowEnabled: false
|
||||
directAccessGrantsEnabled: false
|
||||
serviceAccountsEnabled: true
|
||||
clientAuthenticatorType: "client-secret"
|
||||
secret: "{{ OPENTALK_CTRL_OIDC_CLIENT_SECRET }}"
|
||||
attributes:
|
||||
"client.secret.creation.time": "0"
|
||||
"oauth2.device.authorization.grant.enabled": "false"
|
||||
"use.refresh.tokens": "true"
|
||||
force_attrs:
|
||||
publicClient: false
|
||||
serviceAccountsEnabled: true
|
||||
secret: "{{ OPENTALK_CTRL_OIDC_CLIENT_SECRET }}"
|
||||
kcadm_exec: "container exec -i {{ lookup('config', 'web-app-keycloak', 'services.keycloak.name') }} /opt/keycloak/bin/kcadm.sh"
|
||||
realm: "{{ OIDC.CLIENT.REALM }}"
|
||||
assert_mode: "{{ MODE_ASSERT | bool }}"
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
|
||||
- name: "Upsert dedicated confidential Keycloak client for the OpenTalk recorder"
|
||||
keycloak_kcadm_update:
|
||||
object_kind: "client"
|
||||
lookup_value: "{{ OPENTALK_RECORDER_OIDC_CLIENT_ID }}"
|
||||
desired:
|
||||
clientId: "{{ OPENTALK_RECORDER_OIDC_CLIENT_ID }}"
|
||||
name: "OpenTalk Recorder"
|
||||
description: "Confidential service-account client used by the OpenTalk recorder to authenticate against the controller."
|
||||
enabled: true
|
||||
protocol: "openid-connect"
|
||||
publicClient: false
|
||||
bearerOnly: false
|
||||
standardFlowEnabled: false
|
||||
implicitFlowEnabled: false
|
||||
directAccessGrantsEnabled: false
|
||||
serviceAccountsEnabled: true
|
||||
clientAuthenticatorType: "client-secret"
|
||||
secret: "{{ OPENTALK_RECORDER_OIDC_CLIENT_SECRET }}"
|
||||
attributes:
|
||||
"client.secret.creation.time": "0"
|
||||
"oauth2.device.authorization.grant.enabled": "false"
|
||||
"use.refresh.tokens": "true"
|
||||
force_attrs:
|
||||
publicClient: false
|
||||
serviceAccountsEnabled: true
|
||||
secret: "{{ OPENTALK_RECORDER_OIDC_CLIENT_SECRET }}"
|
||||
kcadm_exec: "container exec -i {{ lookup('config', 'web-app-keycloak', 'services.keycloak.name') }} /opt/keycloak/bin/kcadm.sh"
|
||||
realm: "{{ OIDC.CLIENT.REALM }}"
|
||||
assert_mode: "{{ MODE_ASSERT | bool }}"
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
|
||||
- name: "Grant the OpenTalk controller's service account the realm-management `view-users` role"
|
||||
ansible.builtin.shell: |-
|
||||
{{ KCEXEC }} sh -lc '
|
||||
set -e
|
||||
KCADM={{ KEYCLOAK_KCADM }}
|
||||
REALM={{ OIDC.CLIENT.REALM | quote }}
|
||||
CLIENT_ID={{ OPENTALK_CTRL_OIDC_CLIENT_ID | quote }}
|
||||
|
||||
SA_USER_ID=$($KCADM get clients/$($KCADM get clients -r $REALM -q clientId=$CLIENT_ID --fields id --format csv --noquotes | tail -n1)/service-account-user -r $REALM --fields id --format csv --noquotes | tail -n1)
|
||||
|
||||
RM_CLIENT_ID=$($KCADM get clients -r $REALM -q clientId=realm-management --fields id --format csv --noquotes | tail -n1)
|
||||
$KCADM add-roles -r $REALM --uusername service-account-$CLIENT_ID --cclientid realm-management --rolename view-users --rolename query-users || true
|
||||
'
|
||||
vars:
|
||||
KCEXEC: "container exec -i {{ lookup('config', 'web-app-keycloak', 'services.keycloak.name') }}"
|
||||
KEYCLOAK_KCADM: "/opt/keycloak/bin/kcadm.sh"
|
||||
changed_when: false
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
|
||||
- name: "Restart the OpenTalk controller so it picks up the refreshed introspection client"
|
||||
ansible.builtin.command: "container restart {{ OPENTALK_CTRL_CONTAINER }}"
|
||||
changed_when: true
|
||||
|
||||
- name: "Wait for MinIO to accept the configured root credentials"
|
||||
ansible.builtin.shell: >
|
||||
container exec {{ OPENTALK_MINIO_CONTAINER }}
|
||||
mc alias set local http://127.0.0.1:9000
|
||||
{{ OPENTALK_MINIO_USER | quote }}
|
||||
{{ OPENTALK_MINIO_PASSWORD | quote }}
|
||||
register: minio_ready
|
||||
retries: 60
|
||||
delay: 3
|
||||
until: minio_ready.rc == 0
|
||||
changed_when: false
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
|
||||
- name: "Ensure OpenTalk MinIO bucket exists"
|
||||
ansible.builtin.shell: >
|
||||
container exec {{ OPENTALK_MINIO_CONTAINER }}
|
||||
mc mb local/{{ OPENTALK_MINIO_BUCKET }}
|
||||
register: minio_bucket
|
||||
changed_when: "'Bucket created successfully' in minio_bucket.stdout"
|
||||
failed_when: minio_bucket.rc != 0 and 'already own it' not in minio_bucket.stderr
|
||||
@@ -5,158 +5,6 @@
|
||||
vars:
|
||||
compose_handlers_flush: false
|
||||
|
||||
- name: "Render OpenTalk controller config '{{ application_id }}'"
|
||||
ansible.builtin.template:
|
||||
src: controller.toml.j2
|
||||
dest: "{{ [lookup('container', application_id, 'directories.volumes'), 'controller.toml'] | path_join }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose up
|
||||
|
||||
- name: "Render LiveKit config '{{ application_id }}'"
|
||||
ansible.builtin.template:
|
||||
src: livekit.yaml.j2
|
||||
dest: "{{ [lookup('container', application_id, 'directories.volumes'), 'livekit.yaml'] | path_join }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose up
|
||||
|
||||
- name: "Render OpenTalk recorder config '{{ application_id }}'"
|
||||
ansible.builtin.template:
|
||||
src: recorder.toml.j2
|
||||
dest: "{{ [lookup('container', application_id, 'directories.volumes'), 'recorder.toml'] | path_join }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose up
|
||||
|
||||
- name: "Flush handlers for '{{ application_id }}' so compose up sees rendered configs"
|
||||
meta: flush_handlers
|
||||
|
||||
# The shared `infinito.nexus` realm client is public, so it cannot
|
||||
# introspect access tokens. Provision a dedicated confidential client
|
||||
# (`opentalk-controller`) with a service account that can call the
|
||||
# Keycloak admin API and the OIDC introspection endpoint.
|
||||
- name: "Authenticate kcadm against the master realm (for OpenTalk client provisioning)"
|
||||
ansible.builtin.shell: |-
|
||||
{{ KEYCLOAK_EXEC_CONTAINER }} sh -lc '
|
||||
set -e
|
||||
{{ KEYCLOAK_KCADM }} config credentials \
|
||||
--server {{ KEYCLOAK_SERVER_INTERNAL_URL }} \
|
||||
--realm master \
|
||||
--user "$KEYCLOAK_PERMANENT_ADMIN_USERNAME" \
|
||||
--password "$KEYCLOAK_PERMANENT_ADMIN_PASSWORD"
|
||||
'
|
||||
vars:
|
||||
KEYCLOAK_EXEC_CONTAINER: "container exec -i {{ lookup('config', 'web-app-keycloak', 'services.keycloak.name') }}"
|
||||
KEYCLOAK_KCADM: "/opt/keycloak/bin/kcadm.sh"
|
||||
KEYCLOAK_SERVER_INTERNAL_URL: "http://127.0.0.1:8080"
|
||||
changed_when: false
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
|
||||
- name: "Upsert dedicated confidential Keycloak client for the OpenTalk controller"
|
||||
keycloak_kcadm_update:
|
||||
object_kind: "client"
|
||||
lookup_value: "{{ OPENTALK_CTRL_OIDC_CLIENT_ID }}"
|
||||
desired:
|
||||
clientId: "{{ OPENTALK_CTRL_OIDC_CLIENT_ID }}"
|
||||
name: "OpenTalk Controller"
|
||||
description: "Confidential client used by web-app-opentalk for token introspection and Keycloak admin API user search."
|
||||
enabled: true
|
||||
protocol: "openid-connect"
|
||||
publicClient: false
|
||||
bearerOnly: false
|
||||
standardFlowEnabled: false
|
||||
implicitFlowEnabled: false
|
||||
directAccessGrantsEnabled: false
|
||||
serviceAccountsEnabled: true
|
||||
clientAuthenticatorType: "client-secret"
|
||||
secret: "{{ OPENTALK_CTRL_OIDC_CLIENT_SECRET }}"
|
||||
attributes:
|
||||
"client.secret.creation.time": "0"
|
||||
"oauth2.device.authorization.grant.enabled": "false"
|
||||
"use.refresh.tokens": "true"
|
||||
force_attrs:
|
||||
publicClient: false
|
||||
serviceAccountsEnabled: true
|
||||
secret: "{{ OPENTALK_CTRL_OIDC_CLIENT_SECRET }}"
|
||||
kcadm_exec: "container exec -i {{ lookup('config', 'web-app-keycloak', 'services.keycloak.name') }} /opt/keycloak/bin/kcadm.sh"
|
||||
realm: "{{ OIDC.CLIENT.REALM }}"
|
||||
assert_mode: "{{ MODE_ASSERT | bool }}"
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
|
||||
- name: "Upsert dedicated confidential Keycloak client for the OpenTalk recorder"
|
||||
keycloak_kcadm_update:
|
||||
object_kind: "client"
|
||||
lookup_value: "{{ OPENTALK_RECORDER_OIDC_CLIENT_ID }}"
|
||||
desired:
|
||||
clientId: "{{ OPENTALK_RECORDER_OIDC_CLIENT_ID }}"
|
||||
name: "OpenTalk Recorder"
|
||||
description: "Confidential service-account client used by the OpenTalk recorder to authenticate against the controller."
|
||||
enabled: true
|
||||
protocol: "openid-connect"
|
||||
publicClient: false
|
||||
bearerOnly: false
|
||||
standardFlowEnabled: false
|
||||
implicitFlowEnabled: false
|
||||
directAccessGrantsEnabled: false
|
||||
serviceAccountsEnabled: true
|
||||
clientAuthenticatorType: "client-secret"
|
||||
secret: "{{ OPENTALK_RECORDER_OIDC_CLIENT_SECRET }}"
|
||||
attributes:
|
||||
"client.secret.creation.time": "0"
|
||||
"oauth2.device.authorization.grant.enabled": "false"
|
||||
"use.refresh.tokens": "true"
|
||||
force_attrs:
|
||||
publicClient: false
|
||||
serviceAccountsEnabled: true
|
||||
secret: "{{ OPENTALK_RECORDER_OIDC_CLIENT_SECRET }}"
|
||||
kcadm_exec: "container exec -i {{ lookup('config', 'web-app-keycloak', 'services.keycloak.name') }} /opt/keycloak/bin/kcadm.sh"
|
||||
realm: "{{ OIDC.CLIENT.REALM }}"
|
||||
assert_mode: "{{ MODE_ASSERT | bool }}"
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
|
||||
- name: "Grant the OpenTalk controller's service account the realm-management `view-users` role"
|
||||
ansible.builtin.shell: |-
|
||||
{{ KCEXEC }} sh -lc '
|
||||
set -e
|
||||
KCADM={{ KEYCLOAK_KCADM }}
|
||||
REALM={{ OIDC.CLIENT.REALM | quote }}
|
||||
CLIENT_ID={{ OPENTALK_CTRL_OIDC_CLIENT_ID | quote }}
|
||||
|
||||
# Resolve the service-account user for the controller client.
|
||||
SA_USER_ID=$($KCADM get clients/$($KCADM get clients -r $REALM -q clientId=$CLIENT_ID --fields id --format csv --noquotes | tail -n1)/service-account-user -r $REALM --fields id --format csv --noquotes | tail -n1)
|
||||
|
||||
# Resolve the realm-management client id and the view-users role payload.
|
||||
RM_CLIENT_ID=$($KCADM get clients -r $REALM -q clientId=realm-management --fields id --format csv --noquotes | tail -n1)
|
||||
$KCADM add-roles -r $REALM --uusername service-account-$CLIENT_ID --cclientid realm-management --rolename view-users --rolename query-users || true
|
||||
'
|
||||
vars:
|
||||
KCEXEC: "container exec -i {{ lookup('config', 'web-app-keycloak', 'services.keycloak.name') }}"
|
||||
KEYCLOAK_KCADM: "/opt/keycloak/bin/kcadm.sh"
|
||||
changed_when: false
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
|
||||
- name: "Restart the OpenTalk controller so it picks up the refreshed introspection client"
|
||||
ansible.builtin.command: "container restart {{ OPENTALK_CTRL_CONTAINER }}"
|
||||
changed_when: true
|
||||
|
||||
- name: "Wait for MinIO to accept the configured root credentials"
|
||||
ansible.builtin.shell: >
|
||||
container exec {{ OPENTALK_MINIO_CONTAINER }}
|
||||
mc alias set local http://127.0.0.1:9000
|
||||
{{ OPENTALK_MINIO_USER | quote }}
|
||||
{{ OPENTALK_MINIO_PASSWORD | quote }}
|
||||
register: minio_ready
|
||||
retries: 60
|
||||
delay: 3
|
||||
until: minio_ready.rc == 0
|
||||
changed_when: false
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
|
||||
- name: "Ensure OpenTalk MinIO bucket exists"
|
||||
ansible.builtin.shell: >
|
||||
container exec {{ OPENTALK_MINIO_CONTAINER }}
|
||||
mc mb local/{{ OPENTALK_MINIO_BUCKET }}
|
||||
register: minio_bucket
|
||||
changed_when: "'Bucket created successfully' in minio_bucket.stdout"
|
||||
failed_when: minio_bucket.rc != 0 and 'already own it' not in minio_bucket.stderr
|
||||
- name: "OpenTalk manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -7,34 +7,6 @@
|
||||
vars:
|
||||
compose_handlers_flush: false
|
||||
|
||||
# Keep the custom entrypoint on disk before the first compose up. If this is
|
||||
# moved below the flush, Pixelfed can boot once with the upstream entrypoint and
|
||||
# get stuck in the half-initialized state this role is meant to recover from.
|
||||
- name: "Deploy '{{ PIXELFED_ENTRYPOINT_HOST_ABS }}'"
|
||||
ansible.builtin.copy:
|
||||
src: "{{ PIXELFED_ENTRYPOINT_FILE }}"
|
||||
dest: "{{ PIXELFED_ENTRYPOINT_HOST_ABS }}"
|
||||
mode: "0755"
|
||||
notify: compose up
|
||||
|
||||
- name: "Ensure Pixelfed .env ownership and permissions for www-data"
|
||||
ansible.builtin.file:
|
||||
path: "{{ PIXELFED_ENV_FILE }}"
|
||||
owner: "{{ PIXELFED_APP_USER }}"
|
||||
group: "{{ PIXELFED_APP_USER }}"
|
||||
mode: "0660"
|
||||
|
||||
# Flush here on purpose: the application container must start with the
|
||||
# repo-managed entrypoint before 02_bootstrap.yml runs any artisan bootstrap.
|
||||
- name: "Start Pixelfed containers with the repo-managed entrypoint"
|
||||
ansible.builtin.meta: flush_handlers
|
||||
|
||||
- name: "Bootstrap the Pixelfed application"
|
||||
ansible.builtin.import_tasks: 02_bootstrap.yml
|
||||
|
||||
- name: "Bootstrap the Pixelfed administrator"
|
||||
ansible.builtin.import_tasks: 03_administrator.yml
|
||||
|
||||
- name: "Bootstrap the Pixelfed administrator OIDC mapping"
|
||||
ansible.builtin.import_tasks: 04_oidc_mapping.yml
|
||||
when: PIXELFED_OIDC_ENABLED | bool
|
||||
- name: "Pixelfed manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
# Keep the custom entrypoint on disk before the first compose up. If this is
|
||||
# moved below the flush, Pixelfed can boot once with the upstream entrypoint and
|
||||
# get stuck in the half-initialized state this role is meant to recover from.
|
||||
- name: "Deploy '{{ PIXELFED_ENTRYPOINT_HOST_ABS }}'"
|
||||
ansible.builtin.copy:
|
||||
src: "{{ PIXELFED_ENTRYPOINT_FILE }}"
|
||||
dest: "{{ PIXELFED_ENTRYPOINT_HOST_ABS }}"
|
||||
mode: "0755"
|
||||
notify: compose up
|
||||
|
||||
- name: "Ensure Pixelfed .env ownership and permissions for www-data"
|
||||
ansible.builtin.file:
|
||||
path: "{{ PIXELFED_ENV_FILE }}"
|
||||
owner: "{{ PIXELFED_APP_USER }}"
|
||||
group: "{{ PIXELFED_APP_USER }}"
|
||||
mode: "0660"
|
||||
|
||||
# Flush here on purpose: the application container must start with the
|
||||
# repo-managed entrypoint before 02_bootstrap.yml runs any artisan bootstrap.
|
||||
- name: "Start Pixelfed containers with the repo-managed entrypoint"
|
||||
ansible.builtin.meta: flush_handlers
|
||||
|
||||
- name: "Bootstrap the Pixelfed application"
|
||||
ansible.builtin.import_tasks: 02_bootstrap.yml
|
||||
|
||||
- name: "Bootstrap the Pixelfed administrator"
|
||||
ansible.builtin.import_tasks: 03_administrator.yml
|
||||
|
||||
- name: "Bootstrap the Pixelfed administrator OIDC mapping"
|
||||
ansible.builtin.import_tasks: 04_oidc_mapping.yml
|
||||
when: PIXELFED_OIDC_ENABLED | bool
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
- name: "Deploy {{ SHOPWARE_INIT_HOST }}"
|
||||
copy:
|
||||
src: init.sh
|
||||
dest: "{{ SHOPWARE_INIT_HOST }}"
|
||||
mode: "0755"
|
||||
notify:
|
||||
- compose up
|
||||
- compose build
|
||||
|
||||
- name: "Render framework.yaml (trusted proxies/headers/hosts)"
|
||||
copy:
|
||||
src: "framework.yaml"
|
||||
dest: "{{ SHOPWARE_FRAMEWORK_HOST }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose up
|
||||
|
||||
- name: "Flush compose handlers"
|
||||
meta: flush_handlers
|
||||
|
||||
- name: Wait for Shopware HTTP endpoint
|
||||
wait_for:
|
||||
host: "{{ DOCKER_REACH_HOST }}"
|
||||
port: "{{ lookup('config', application_id, 'services.shopware.ports.local.http') }}"
|
||||
delay: 5
|
||||
timeout: 300
|
||||
|
||||
- name: "Ensure admin user exists with correct password"
|
||||
include_tasks: 01_admin.yml
|
||||
@@ -5,39 +5,6 @@
|
||||
vars:
|
||||
compose_handlers_flush: false
|
||||
|
||||
- name: "Deploy {{ SHOPWARE_INIT_HOST }}"
|
||||
copy:
|
||||
src: init.sh
|
||||
dest: "{{ SHOPWARE_INIT_HOST }}"
|
||||
mode: "0755"
|
||||
notify:
|
||||
- compose up
|
||||
- compose build
|
||||
|
||||
- name: "Render framework.yaml (trusted proxies/headers/hosts)"
|
||||
copy:
|
||||
src: "framework.yaml"
|
||||
dest: "{{ SHOPWARE_FRAMEWORK_HOST }}"
|
||||
mode: "0644"
|
||||
notify:
|
||||
- compose up
|
||||
|
||||
- name: "Flush compose handlers"
|
||||
meta: flush_handlers
|
||||
|
||||
- name: Wait for Shopware HTTP endpoint
|
||||
wait_for:
|
||||
host: "{{ DOCKER_REACH_HOST }}"
|
||||
port: "{{ lookup('config', application_id, 'services.shopware.ports.local.http') }}"
|
||||
delay: 5
|
||||
timeout: 300
|
||||
|
||||
- name: "Ensure admin user exists with correct password"
|
||||
include_tasks: 01_admin.yml
|
||||
|
||||
#- name: Execute setup routines (OIDC/LDAP)
|
||||
# include_tasks: 02_setup.yml
|
||||
#
|
||||
#- name: Execute cleanup routines
|
||||
# include_tasks: 03_cleanup.yml
|
||||
# when: MODE_CLEANUP
|
||||
- name: "Shopware manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
- name: "Deploy PHP error configuration (env aware)"
|
||||
template:
|
||||
src: "php.ini.j2"
|
||||
dest: "{{ SUITECRM_PHP_CONFIG_HOST }}"
|
||||
notify:
|
||||
- compose up
|
||||
|
||||
- name: "Render SuiteCRM LDAP mapping (local + swarm peers)"
|
||||
ansible.builtin.include_tasks:
|
||||
file: "{{ [playbook_dir, 'roles/sys-svc-compose/tasks/utils/render_replicated_templates.yml'] | path_join }}"
|
||||
vars:
|
||||
role_templates:
|
||||
- src: "ldap.yaml.j2"
|
||||
dest: "{{ SUITECRM_LDAP_CONFIG_HOST }}"
|
||||
when: "{{ SUITECRM_LDAP_ENABLED | bool }}"
|
||||
|
||||
- name: "Deploy '{{ SUITECRM_ENTRYPOINT_SCRIPT_HOST_ABS }}'"
|
||||
copy:
|
||||
src: "{{ SUITECRM_ENTRYPOINT_SCRIPT_FILE }}"
|
||||
dest: "{{ SUITECRM_ENTRYPOINT_SCRIPT_HOST_ABS }}"
|
||||
notify:
|
||||
- compose up
|
||||
- compose build
|
||||
|
||||
- name: "Docker Compose Up for '{{ application_id }}'"
|
||||
meta: flush_handlers
|
||||
@@ -5,29 +5,6 @@
|
||||
vars:
|
||||
compose_handlers_flush: false
|
||||
|
||||
- name: "Deploy PHP error configuration (env aware)"
|
||||
template:
|
||||
src: "php.ini.j2"
|
||||
dest: "{{ SUITECRM_PHP_CONFIG_HOST }}"
|
||||
notify:
|
||||
- compose up
|
||||
|
||||
- name: "Render SuiteCRM LDAP mapping (local + swarm peers)"
|
||||
ansible.builtin.include_tasks:
|
||||
file: "{{ [playbook_dir, 'roles/sys-svc-compose/tasks/utils/render_replicated_templates.yml'] | path_join }}"
|
||||
vars:
|
||||
role_templates:
|
||||
- src: "ldap.yaml.j2"
|
||||
dest: "{{ SUITECRM_LDAP_CONFIG_HOST }}"
|
||||
when: "{{ SUITECRM_LDAP_ENABLED | bool }}"
|
||||
|
||||
- name: "Deploy '{{ SUITECRM_ENTRYPOINT_SCRIPT_HOST_ABS }}'"
|
||||
copy:
|
||||
src: "{{ SUITECRM_ENTRYPOINT_SCRIPT_FILE }}"
|
||||
dest: "{{ SUITECRM_ENTRYPOINT_SCRIPT_HOST_ABS }}"
|
||||
notify:
|
||||
- compose up
|
||||
- compose build
|
||||
|
||||
- name: "Docker Compose Up for '{{ application_id }}'"
|
||||
meta: flush_handlers
|
||||
- name: "SuiteCRM manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
- name: "copy taiga gateway config with OIDC route"
|
||||
template:
|
||||
src: "taiga-gateway/taiga.conf.j2"
|
||||
dest: "{{ [compose_repository_path, 'taiga-gateway', 'taiga.conf'] | path_join }}"
|
||||
when: TAIGA_OIDC_ENABLED | bool
|
||||
notify: compose up
|
||||
|
||||
- name: "render Dockerfile.taiga-back for custom auth-enabled backend image"
|
||||
template:
|
||||
src: "Dockerfile.backend.j2"
|
||||
dest: "{{ [compose_repository_path, 'Dockerfile.taiga-back'] | path_join }}"
|
||||
when: TAIGA_PLUGINS_ENABLED | bool
|
||||
notify: compose up
|
||||
|
||||
- name: "render Dockerfile.taiga-front for custom OIDC frontend image"
|
||||
template:
|
||||
src: "Dockerfile.frontend.j2"
|
||||
dest: "{{ [compose_repository_path, 'Dockerfile.taiga-front'] | path_join }}"
|
||||
when: TAIGA_OIDC_ENABLED | bool
|
||||
notify: compose up
|
||||
|
||||
- name: "copy taiga config.append.py (OIDC and/or LDAP settings appended to config.py at runtime)"
|
||||
template:
|
||||
src: "settings/config.append.py.j2"
|
||||
dest: "{{ TAIGA_CONFIG_APPEND_PATH }}"
|
||||
when: TAIGA_PLUGINS_ENABLED | bool
|
||||
notify: compose up
|
||||
|
||||
- name: "copy taiga urls.py (OIDC URL routing)"
|
||||
template:
|
||||
src: "settings/urls.py.j2"
|
||||
dest: "{{ TAIGA_URLS_PATH }}"
|
||||
when: TAIGA_OIDC_ENABLED | bool
|
||||
notify: compose up
|
||||
|
||||
- name: "copy taiga frontend conf.json (loginFormType / contribPlugins)"
|
||||
template:
|
||||
src: "settings/conf.json.j2"
|
||||
dest: "{{ TAIGA_FRONTEND_CONF_PATH }}"
|
||||
when: TAIGA_PLUGINS_ENABLED | bool
|
||||
notify: compose up
|
||||
|
||||
- name: "create {{ TAIGA_COMPOSE_INIT_PATH }}"
|
||||
template:
|
||||
src: "compose-inits.yml.j2"
|
||||
dest: "{{ TAIGA_COMPOSE_INIT_PATH }}"
|
||||
notify: compose up
|
||||
|
||||
- name: "Include Taiga administrator routines"
|
||||
include_tasks: 01_administrator.yml
|
||||
|
||||
- name: "Flush Taiga handlers"
|
||||
meta: flush_handlers
|
||||
@@ -8,56 +8,6 @@
|
||||
compose_repository_ref: "{{ TAIGA_REF }}"
|
||||
compose_repository_pull: true
|
||||
|
||||
- name: "copy taiga gateway config with OIDC route"
|
||||
template:
|
||||
src: "taiga-gateway/taiga.conf.j2"
|
||||
dest: "{{ [compose_repository_path, 'taiga-gateway', 'taiga.conf'] | path_join }}"
|
||||
when: TAIGA_OIDC_ENABLED | bool
|
||||
notify: compose up
|
||||
|
||||
- name: "render Dockerfile.taiga-back for custom auth-enabled backend image"
|
||||
template:
|
||||
src: "Dockerfile.backend.j2"
|
||||
dest: "{{ [compose_repository_path, 'Dockerfile.taiga-back'] | path_join }}"
|
||||
when: TAIGA_PLUGINS_ENABLED | bool
|
||||
notify: compose up
|
||||
|
||||
- name: "render Dockerfile.taiga-front for custom OIDC frontend image"
|
||||
template:
|
||||
src: "Dockerfile.frontend.j2"
|
||||
dest: "{{ [compose_repository_path, 'Dockerfile.taiga-front'] | path_join }}"
|
||||
when: TAIGA_OIDC_ENABLED | bool
|
||||
notify: compose up
|
||||
|
||||
- name: "copy taiga config.append.py (OIDC and/or LDAP settings appended to config.py at runtime)"
|
||||
template:
|
||||
src: "settings/config.append.py.j2"
|
||||
dest: "{{ TAIGA_CONFIG_APPEND_PATH }}"
|
||||
when: TAIGA_PLUGINS_ENABLED | bool
|
||||
notify: compose up
|
||||
|
||||
- name: "copy taiga urls.py (OIDC URL routing)"
|
||||
template:
|
||||
src: "settings/urls.py.j2"
|
||||
dest: "{{ TAIGA_URLS_PATH }}"
|
||||
when: TAIGA_OIDC_ENABLED | bool
|
||||
notify: compose up
|
||||
|
||||
- name: "copy taiga frontend conf.json (loginFormType / contribPlugins)"
|
||||
template:
|
||||
src: "settings/conf.json.j2"
|
||||
dest: "{{ TAIGA_FRONTEND_CONF_PATH }}"
|
||||
when: TAIGA_PLUGINS_ENABLED | bool
|
||||
notify: compose up
|
||||
|
||||
- name: "create {{ TAIGA_COMPOSE_INIT_PATH }}"
|
||||
template:
|
||||
src: "compose-inits.yml.j2"
|
||||
dest: "{{ TAIGA_COMPOSE_INIT_PATH }}"
|
||||
notify: compose up
|
||||
|
||||
- name: "Include Taiga administrator routines"
|
||||
include_tasks: 01_administrator.yml
|
||||
|
||||
- name: "Flush Taiga handlers"
|
||||
meta: flush_handlers
|
||||
- name: "Taiga manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -11,77 +11,13 @@
|
||||
|
||||
- name: Deploy Bootstrap Config
|
||||
include_tasks: _flush_config.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
vars:
|
||||
xwiki_oidc_enabled_switch: false
|
||||
xwiki_ldap_enabled_switch: false
|
||||
|
||||
- name: "ASSERT | superadmin can authenticate (needed for installer)"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_XWIKI, 'spaces'] | url_join }}"
|
||||
method: GET
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [200, 401, 403, 500, 503]
|
||||
return_content: true
|
||||
register: _super_check_ext
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- block:
|
||||
- name: "DEBUG | superadmin check + pw hash compare (condensed)"
|
||||
script: >-
|
||||
debug_superadmin.sh
|
||||
"{{ _super_check_ext.status }}"
|
||||
"{{ _super_check_ext.redirected | default('n/a') }}"
|
||||
"{{ _super_check_ext.url | default('n/a') }}"
|
||||
"{{ _super_check_ext.www_authenticate | default(_super_check_ext.headers['www-authenticate'] | default('n/a')) }}"
|
||||
"{{ _super_check_ext.content_type | default(_super_check_ext.headers['content-type'] | default('n/a')) }}"
|
||||
"{{ (_super_check_ext.content | default(''))[:600] | replace('\n','\\n') }}"
|
||||
"{{ XWIKI_CONTAINER }}"
|
||||
"{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
register: _dbg_superadmin
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: "FAIL | superadmin authentication failed (extensions phase)"
|
||||
fail:
|
||||
msg: |
|
||||
superadmin authentication failed (extensions phase)
|
||||
|
||||
superadmin check status={{ _super_check_ext.status }}
|
||||
www_authenticate={{
|
||||
_super_check_ext.www_authenticate
|
||||
| default(_super_check_ext.headers['www-authenticate'] | default('n/a'))
|
||||
}}
|
||||
|
||||
debug:
|
||||
{{ _dbg_superadmin.stdout | default('') }}
|
||||
|
||||
hint: check xwiki.cfg in image / password / Dockerfile build
|
||||
when: _super_check_ext.status != 200
|
||||
|
||||
- name: Load setup procedures for admin
|
||||
include_tasks: 03_administrator.yml
|
||||
when: not (XWIKI_SSO_ENABLED | bool)
|
||||
|
||||
- name: Load setup procedures for extensions
|
||||
include_tasks: 04_extensions.yml
|
||||
|
||||
- name: "XWIKI | Ensure Main.WebHome exists"
|
||||
include_tasks: 05_main_webhome.yml
|
||||
|
||||
- block:
|
||||
- name: "Create Final Docker Compose File"
|
||||
template:
|
||||
src: "compose.yml.j2"
|
||||
dest: "{{ lookup('container', application_id, 'files.compose') }}"
|
||||
notify: compose up
|
||||
|
||||
- name: Deploy Final Config
|
||||
include_tasks: _flush_config.yml
|
||||
vars:
|
||||
xwiki_oidc_enabled_switch: "{{ XWIKI_OIDC_ENABLED | bool }}"
|
||||
xwiki_ldap_enabled_switch: "{{ XWIKI_LDAP_ENABLED | bool }}"
|
||||
- name: "XWiki manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
|
||||
- include_tasks: utils/once/flag.yml
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
- name: "ASSERT | superadmin can authenticate (needed for installer)"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_XWIKI, 'spaces'] | url_join }}"
|
||||
method: GET
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [200, 401, 403, 500, 503]
|
||||
return_content: true
|
||||
register: _super_check_ext
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- block:
|
||||
- name: "DEBUG | superadmin check + pw hash compare (condensed)"
|
||||
script: >-
|
||||
debug_superadmin.sh
|
||||
"{{ _super_check_ext.status }}"
|
||||
"{{ _super_check_ext.redirected | default('n/a') }}"
|
||||
"{{ _super_check_ext.url | default('n/a') }}"
|
||||
"{{ _super_check_ext.www_authenticate | default(_super_check_ext.headers['www-authenticate'] | default('n/a')) }}"
|
||||
"{{ _super_check_ext.content_type | default(_super_check_ext.headers['content-type'] | default('n/a')) }}"
|
||||
"{{ (_super_check_ext.content | default(''))[:600] | replace('\n','\\n') }}"
|
||||
"{{ XWIKI_CONTAINER }}"
|
||||
"{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
register: _dbg_superadmin
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: "FAIL | superadmin authentication failed (extensions phase)"
|
||||
fail:
|
||||
msg: |
|
||||
superadmin authentication failed (extensions phase)
|
||||
|
||||
superadmin check status={{ _super_check_ext.status }}
|
||||
www_authenticate={{
|
||||
_super_check_ext.www_authenticate
|
||||
| default(_super_check_ext.headers['www-authenticate'] | default('n/a'))
|
||||
}}
|
||||
|
||||
debug:
|
||||
{{ _dbg_superadmin.stdout | default('') }}
|
||||
|
||||
hint: check xwiki.cfg in image / password / Dockerfile build
|
||||
when: _super_check_ext.status != 200
|
||||
|
||||
- name: Load setup procedures for admin
|
||||
include_tasks: 03_administrator.yml
|
||||
when: not (XWIKI_SSO_ENABLED | bool)
|
||||
|
||||
- name: Load setup procedures for extensions
|
||||
include_tasks: 04_extensions.yml
|
||||
|
||||
- name: "XWIKI | Ensure Main.WebHome exists"
|
||||
include_tasks: 05_main_webhome.yml
|
||||
|
||||
- block:
|
||||
- name: "Create Final Docker Compose File"
|
||||
template:
|
||||
src: "compose.yml.j2"
|
||||
dest: "{{ lookup('container', application_id, 'files.compose') }}"
|
||||
notify: compose up
|
||||
|
||||
- name: Deploy Final Config
|
||||
include_tasks: _flush_config.yml
|
||||
vars:
|
||||
xwiki_oidc_enabled_switch: "{{ XWIKI_OIDC_ENABLED | bool }}"
|
||||
xwiki_ldap_enabled_switch: "{{ XWIKI_LDAP_ENABLED | bool }}"
|
||||
@@ -7,140 +7,6 @@
|
||||
vars:
|
||||
compose_handlers_flush: false
|
||||
|
||||
- name: "Stage Zammad host-side init files (local + swarm peers)"
|
||||
ansible.builtin.include_tasks:
|
||||
file: "{{ [playbook_dir, 'roles/sys-svc-compose/tasks/utils/render_replicated_templates.yml'] | path_join }}"
|
||||
vars:
|
||||
role_templates:
|
||||
- src: "auto_wizard.json.j2"
|
||||
dest: "{{ ZAMMAD_AUTO_WIZARD_HOST_PATH }}"
|
||||
mode: "0644"
|
||||
- src: "zz_omniauth_allow_get.rb.j2"
|
||||
dest: "{{ ZAMMAD_OMNIAUTH_INIT_HOST_PATH }}"
|
||||
mode: "0644"
|
||||
- src: "telegram_helper_stub.rb.j2"
|
||||
dest: "{{ ZAMMAD_TELEGRAM_HELPER_HOST_PATH }}"
|
||||
mode: "0644"
|
||||
- src: "zz_oidc_group_role_mapping.rb.j2"
|
||||
dest: "{{ ZAMMAD_OIDC_GROUP_ROLE_MAP_HOST_PATH }}"
|
||||
mode: "0644"
|
||||
when: "{{ ZAMMAD_OIDC_ENABLED }}"
|
||||
|
||||
- name: "grant CREATEDB to the Zammad PostgreSQL role (Zammad's init runs rake db:create even when the DB exists)"
|
||||
database_query:
|
||||
mask_values: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
config: >-
|
||||
{{ lookup('database', application_id) | combine({
|
||||
'username': 'postgres',
|
||||
'name': 'postgres',
|
||||
'password': lookup('config', 'svc-db-postgres', 'credentials.POSTGRES_PASSWORD'),
|
||||
}) }}
|
||||
# nocheck: database-query-raw
|
||||
query: "{{ lookup('template', role_path ~ '/templates/sql/grant_createdb.sql.j2') }}"
|
||||
when: lookup('config', application_id, 'services.postgres.shared') | bool
|
||||
|
||||
- name: "flush handlers so the Zammad init container starts with auto_wizard.json bind-mounted"
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "wait for the Zammad Rails server to answer"
|
||||
ansible.builtin.shell: |
|
||||
set -eo pipefail
|
||||
container exec {{ ZAMMAD_RAILS_CONTAINER }} \
|
||||
bundle exec rails r 'Setting.any? || raise'
|
||||
register: zammad_rails_ready
|
||||
changed_when: false
|
||||
retries: 60
|
||||
delay: 5
|
||||
until: zammad_rails_ready.rc == 0
|
||||
|
||||
- name: "process auto_wizard.json if Zammad's setup wizard hasn't completed yet (idempotent)"
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
container exec -i {{ ZAMMAD_RAILS_CONTAINER }} bundle exec rails r '
|
||||
if !Setting.get("system_init_done")
|
||||
raise "AutoWizard data missing (auto_wizard.json not mounted in rails container)" unless AutoWizard.enabled?
|
||||
AutoWizard.setup
|
||||
end
|
||||
'
|
||||
register: zammad_auto_wizard_setup
|
||||
changed_when: false
|
||||
|
||||
- name: "ensure API-bot user in Zammad (Basic-auth REST API; runs in EVERY variant including no-auth)"
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
container exec -i \
|
||||
-e API_BOT_LOGIN -e API_BOT_EMAIL -e API_BOT_FIRSTNAME -e API_BOT_LASTNAME -e API_BOT_PASSWORD \
|
||||
{{ ZAMMAD_RAILS_CONTAINER }} \
|
||||
bundle exec rails r - < {{ role_path }}/files/scripts/apply_api_bot.rb
|
||||
environment:
|
||||
API_BOT_LOGIN: "{{ ZAMMAD_API_BOT_USER.username }}"
|
||||
API_BOT_EMAIL: "{{ ZAMMAD_API_BOT_USER.email }}"
|
||||
API_BOT_FIRSTNAME: "{{ ZAMMAD_API_BOT_USER.firstname }}"
|
||||
API_BOT_LASTNAME: "{{ ZAMMAD_API_BOT_USER.lastname }}"
|
||||
API_BOT_PASSWORD: "{{ ZAMMAD_API_BOT_PASSWORD }}"
|
||||
register: zammad_api_bot_apply
|
||||
changed_when: false
|
||||
|
||||
- name: "apply OIDC settings in Zammad (post-bootstrap; idempotent)"
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
container exec -i \
|
||||
-e ZAMMAD_FQDN -e ZAMMAD_HTTP_TYPE \
|
||||
-e OIDC_BUTTON_TEXT -e OIDC_CLIENT_ID -e OIDC_CLIENT_SECRET -e OIDC_ISSUER_URL \
|
||||
{{ ZAMMAD_RAILS_CONTAINER }} \
|
||||
bundle exec rails r - < {{ role_path }}/files/scripts/apply_oidc_settings.rb
|
||||
environment:
|
||||
ZAMMAD_FQDN: "{{ ZAMMAD_HOSTNAME }}"
|
||||
ZAMMAD_HTTP_TYPE: "{{ ZAMMAD_WEB_PROTOCOL }}"
|
||||
OIDC_BUTTON_TEXT: "{{ OIDC.BUTTON_TEXT }}"
|
||||
OIDC_CLIENT_ID: "{{ OIDC.CLIENT.ID }}"
|
||||
OIDC_CLIENT_SECRET: "{{ OIDC.CLIENT.SECRET }}"
|
||||
OIDC_ISSUER_URL: "{{ OIDC.CLIENT.ISSUER_URL }}"
|
||||
register: zammad_oidc_apply
|
||||
changed_when: false
|
||||
when: ZAMMAD_OIDC_ENABLED
|
||||
|
||||
- name: "configure Zammad's LdapSource against svc-db-openldap (post-bootstrap; idempotent)"
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
container exec -i \
|
||||
-e LDAP_NAME -e LDAP_HOST -e LDAP_PORT -e LDAP_BASE_DN \
|
||||
-e LDAP_BIND_DN -e LDAP_BIND_PW -e LDAP_UID_ATTR \
|
||||
{{ ZAMMAD_RAILS_CONTAINER }} \
|
||||
bundle exec rails r - < {{ role_path }}/files/scripts/apply_ldap_source.rb
|
||||
environment:
|
||||
LDAP_NAME: "{{ ORGANIZATION }} svc-db-openldap"
|
||||
LDAP_HOST: "{{ LDAP.SERVER.DOMAIN }}"
|
||||
LDAP_PORT: "{{ LDAP.SERVER.PORT }}"
|
||||
LDAP_BASE_DN: "{{ LDAP.DN.OU.USERS }}"
|
||||
LDAP_BIND_DN: "{{ LDAP.DN.ADMINISTRATOR.DATA }}"
|
||||
LDAP_BIND_PW: "{{ LDAP.BIND_CREDENTIAL }}"
|
||||
LDAP_UID_ATTR: "{{ LDAP.USER.ATTRIBUTES.ID }}"
|
||||
register: zammad_ldap_apply
|
||||
changed_when: false
|
||||
when: ZAMMAD_LDAP_ENABLED
|
||||
|
||||
- name: "configure Zammad's Email channel (inbound IMAP + outbound SMTP) against Mailu"
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
container exec -i \
|
||||
-e MAIL_GROUP_NAME -e MAIL_FROM_NAME -e MAIL_FROM_ADDRESS \
|
||||
-e IMAP_HOST -e IMAP_PORT -e IMAP_USER -e IMAP_PASS \
|
||||
-e SMTP_HOST -e SMTP_PORT -e SMTP_USER -e SMTP_PASS \
|
||||
{{ ZAMMAD_RAILS_CONTAINER }} \
|
||||
bundle exec rails r - < {{ role_path }}/files/scripts/apply_email_channel.rb
|
||||
environment:
|
||||
MAIL_GROUP_NAME: "Users"
|
||||
MAIL_FROM_NAME: "{{ ORGANIZATION }} Helpdesk"
|
||||
MAIL_FROM_ADDRESS: "{{ ZAMMAD_HELPDESK_EMAIL }}"
|
||||
IMAP_HOST: "{{ ZAMMAD_MAIL.host }}"
|
||||
IMAP_PORT: "143"
|
||||
IMAP_USER: "{{ ZAMMAD_HELPDESK_EMAIL }}"
|
||||
IMAP_PASS: "{{ ZAMMAD_HELPDESK_PASS }}"
|
||||
SMTP_HOST: "{{ ZAMMAD_MAIL.host }}"
|
||||
SMTP_PORT: "{{ ZAMMAD_MAIL.port }}"
|
||||
SMTP_USER: "{{ ZAMMAD_HELPDESK_EMAIL }}"
|
||||
SMTP_PASS: "{{ ZAMMAD_HELPDESK_PASS }}"
|
||||
register: zammad_email_apply
|
||||
changed_when: false
|
||||
when: ZAMMAD_EMAIL_ENABLED and (ZAMMAD_HELPDESK_EMAIL | length > 0)
|
||||
- name: "Zammad manager-side operations (file deploys + container exec)"
|
||||
include_tasks: 02_manager_ops.yml
|
||||
when: IS_STACK_HOST | bool
|
||||
@@ -0,0 +1,138 @@
|
||||
---
|
||||
- name: "Stage Zammad host-side init files (local + swarm peers)"
|
||||
ansible.builtin.include_tasks:
|
||||
file: "{{ [playbook_dir, 'roles/sys-svc-compose/tasks/utils/render_replicated_templates.yml'] | path_join }}"
|
||||
vars:
|
||||
role_templates:
|
||||
- src: "auto_wizard.json.j2"
|
||||
dest: "{{ ZAMMAD_AUTO_WIZARD_HOST_PATH }}"
|
||||
mode: "0644"
|
||||
- src: "zz_omniauth_allow_get.rb.j2"
|
||||
dest: "{{ ZAMMAD_OMNIAUTH_INIT_HOST_PATH }}"
|
||||
mode: "0644"
|
||||
- src: "telegram_helper_stub.rb.j2"
|
||||
dest: "{{ ZAMMAD_TELEGRAM_HELPER_HOST_PATH }}"
|
||||
mode: "0644"
|
||||
- src: "zz_oidc_group_role_mapping.rb.j2"
|
||||
dest: "{{ ZAMMAD_OIDC_GROUP_ROLE_MAP_HOST_PATH }}"
|
||||
mode: "0644"
|
||||
when: "{{ ZAMMAD_OIDC_ENABLED }}"
|
||||
|
||||
- name: "grant CREATEDB to the Zammad PostgreSQL role (Zammad's init runs rake db:create even when the DB exists)"
|
||||
database_query:
|
||||
mask_values: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
config: >-
|
||||
{{ lookup('database', application_id) | combine({
|
||||
'username': 'postgres',
|
||||
'name': 'postgres',
|
||||
'password': lookup('config', 'svc-db-postgres', 'credentials.POSTGRES_PASSWORD'),
|
||||
}) }}
|
||||
# nocheck: database-query-raw
|
||||
query: "{{ lookup('template', role_path ~ '/templates/sql/grant_createdb.sql.j2') }}"
|
||||
when: lookup('config', application_id, 'services.postgres.shared') | bool
|
||||
|
||||
- name: "flush handlers so the Zammad init container starts with auto_wizard.json bind-mounted"
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "wait for the Zammad Rails server to answer"
|
||||
ansible.builtin.shell: |
|
||||
set -eo pipefail
|
||||
container exec {{ ZAMMAD_RAILS_CONTAINER }} \
|
||||
bundle exec rails r 'Setting.any? || raise'
|
||||
register: zammad_rails_ready
|
||||
changed_when: false
|
||||
retries: 60
|
||||
delay: 5
|
||||
until: zammad_rails_ready.rc == 0
|
||||
|
||||
- name: "process auto_wizard.json if Zammad's setup wizard hasn't completed yet (idempotent)"
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
container exec -i {{ ZAMMAD_RAILS_CONTAINER }} bundle exec rails r '
|
||||
if !Setting.get("system_init_done")
|
||||
raise "AutoWizard data missing (auto_wizard.json not mounted in rails container)" unless AutoWizard.enabled?
|
||||
AutoWizard.setup
|
||||
end
|
||||
'
|
||||
register: zammad_auto_wizard_setup
|
||||
changed_when: false
|
||||
|
||||
- name: "ensure API-bot user in Zammad (Basic-auth REST API; runs in EVERY variant including no-auth)"
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
container exec -i \
|
||||
-e API_BOT_LOGIN -e API_BOT_EMAIL -e API_BOT_FIRSTNAME -e API_BOT_LASTNAME -e API_BOT_PASSWORD \
|
||||
{{ ZAMMAD_RAILS_CONTAINER }} \
|
||||
bundle exec rails r - < {{ role_path }}/files/scripts/apply_api_bot.rb
|
||||
environment:
|
||||
API_BOT_LOGIN: "{{ ZAMMAD_API_BOT_USER.username }}"
|
||||
API_BOT_EMAIL: "{{ ZAMMAD_API_BOT_USER.email }}"
|
||||
API_BOT_FIRSTNAME: "{{ ZAMMAD_API_BOT_USER.firstname }}"
|
||||
API_BOT_LASTNAME: "{{ ZAMMAD_API_BOT_USER.lastname }}"
|
||||
API_BOT_PASSWORD: "{{ ZAMMAD_API_BOT_PASSWORD }}"
|
||||
register: zammad_api_bot_apply
|
||||
changed_when: false
|
||||
|
||||
- name: "apply OIDC settings in Zammad (post-bootstrap; idempotent)"
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
container exec -i \
|
||||
-e ZAMMAD_FQDN -e ZAMMAD_HTTP_TYPE \
|
||||
-e OIDC_BUTTON_TEXT -e OIDC_CLIENT_ID -e OIDC_CLIENT_SECRET -e OIDC_ISSUER_URL \
|
||||
{{ ZAMMAD_RAILS_CONTAINER }} \
|
||||
bundle exec rails r - < {{ role_path }}/files/scripts/apply_oidc_settings.rb
|
||||
environment:
|
||||
ZAMMAD_FQDN: "{{ ZAMMAD_HOSTNAME }}"
|
||||
ZAMMAD_HTTP_TYPE: "{{ ZAMMAD_WEB_PROTOCOL }}"
|
||||
OIDC_BUTTON_TEXT: "{{ OIDC.BUTTON_TEXT }}"
|
||||
OIDC_CLIENT_ID: "{{ OIDC.CLIENT.ID }}"
|
||||
OIDC_CLIENT_SECRET: "{{ OIDC.CLIENT.SECRET }}"
|
||||
OIDC_ISSUER_URL: "{{ OIDC.CLIENT.ISSUER_URL }}"
|
||||
register: zammad_oidc_apply
|
||||
changed_when: false
|
||||
when: ZAMMAD_OIDC_ENABLED
|
||||
|
||||
- name: "configure Zammad's LdapSource against svc-db-openldap (post-bootstrap; idempotent)"
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
container exec -i \
|
||||
-e LDAP_NAME -e LDAP_HOST -e LDAP_PORT -e LDAP_BASE_DN \
|
||||
-e LDAP_BIND_DN -e LDAP_BIND_PW -e LDAP_UID_ATTR \
|
||||
{{ ZAMMAD_RAILS_CONTAINER }} \
|
||||
bundle exec rails r - < {{ role_path }}/files/scripts/apply_ldap_source.rb
|
||||
environment:
|
||||
LDAP_NAME: "{{ ORGANIZATION }} svc-db-openldap"
|
||||
LDAP_HOST: "{{ LDAP.SERVER.DOMAIN }}"
|
||||
LDAP_PORT: "{{ LDAP.SERVER.PORT }}"
|
||||
LDAP_BASE_DN: "{{ LDAP.DN.OU.USERS }}"
|
||||
LDAP_BIND_DN: "{{ LDAP.DN.ADMINISTRATOR.DATA }}"
|
||||
LDAP_BIND_PW: "{{ LDAP.BIND_CREDENTIAL }}"
|
||||
LDAP_UID_ATTR: "{{ LDAP.USER.ATTRIBUTES.ID }}"
|
||||
register: zammad_ldap_apply
|
||||
changed_when: false
|
||||
when: ZAMMAD_LDAP_ENABLED
|
||||
|
||||
- name: "configure Zammad's Email channel (inbound IMAP + outbound SMTP) against Mailu"
|
||||
ansible.builtin.shell: |
|
||||
set -euo pipefail
|
||||
container exec -i \
|
||||
-e MAIL_GROUP_NAME -e MAIL_FROM_NAME -e MAIL_FROM_ADDRESS \
|
||||
-e IMAP_HOST -e IMAP_PORT -e IMAP_USER -e IMAP_PASS \
|
||||
-e SMTP_HOST -e SMTP_PORT -e SMTP_USER -e SMTP_PASS \
|
||||
{{ ZAMMAD_RAILS_CONTAINER }} \
|
||||
bundle exec rails r - < {{ role_path }}/files/scripts/apply_email_channel.rb
|
||||
environment:
|
||||
MAIL_GROUP_NAME: "Users"
|
||||
MAIL_FROM_NAME: "{{ ORGANIZATION }} Helpdesk"
|
||||
MAIL_FROM_ADDRESS: "{{ ZAMMAD_HELPDESK_EMAIL }}"
|
||||
IMAP_HOST: "{{ ZAMMAD_MAIL.host }}"
|
||||
IMAP_PORT: "143"
|
||||
IMAP_USER: "{{ ZAMMAD_HELPDESK_EMAIL }}"
|
||||
IMAP_PASS: "{{ ZAMMAD_HELPDESK_PASS }}"
|
||||
SMTP_HOST: "{{ ZAMMAD_MAIL.host }}"
|
||||
SMTP_PORT: "{{ ZAMMAD_MAIL.port }}"
|
||||
SMTP_USER: "{{ ZAMMAD_HELPDESK_EMAIL }}"
|
||||
SMTP_PASS: "{{ ZAMMAD_HELPDESK_PASS }}"
|
||||
register: zammad_email_apply
|
||||
changed_when: false
|
||||
when: ZAMMAD_EMAIL_ENABLED and (ZAMMAD_HELPDESK_EMAIL | length > 0)
|
||||
Reference in new issue
Block a user