---
- name: "β³ Transferring search_allocation_precheck.sh to {{ ZAMMAD_SEARCH_PRECHECK_ABS }}"
stack_host_copy:
src: shell/search_allocation_precheck.sh
dest: "{{ ZAMMAD_SEARCH_PRECHECK_ABS }}"
mode: "0555"
notify: compose-up
- name: "π grant CREATEDB to Zammad PostgreSQL role (init runs rake db:create)"
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', 'secrets.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 Zammad init starts with auto_wizard.json bind-mounted"
meta: flush_handlers
- name: "β³ wait for Zammad init (seeds + pk-sequence reset) to finish"
ansible.builtin.shell: |
set -eo pipefail
container exec {{ ZAMMAD_RAILS_CONTAINER_ADDRESS }} \
bundle exec rails r '
raise unless Setting.any?
raise unless ActiveRecord::Base.connection.select_value("SELECT is_called FROM users_id_seq")
'
register: zammad_rails_ready
changed_when: false
retries: 60
delay: 5
until: zammad_rails_ready.rc == 0
- name: "β³ wait for the current Zammad init task to complete"
ansible.builtin.script: "{{ lookup('path_absolute', 'roles/sys-svc-compose/files/shell/swarm/job_state.sh') }}"
environment:
JOB: "{{ lookup('container_service', application_id, ZAMMAD_INIT_SERVICE) }}"
register: zammad_init_state
changed_when: false
retries: 60
delay: 5
until: zammad_init_state.rc != 1
failed_when: zammad_init_state.rc != 0
when: IS_SWARM_MODE
- name: "βΆοΈ process auto_wizard.json if Zammad setup wizard incomplete (idempotent)"
ansible.builtin.shell: |
set -euo pipefail
container exec -i {{ ZAMMAD_RAILS_CONTAINER_ADDRESS }} 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: "ποΈ point Zammad's attachment store at the S3 bucket"
ansible.builtin.shell:
cmd: |
set -euo pipefail
container exec -i \
{{ ZAMMAD_RAILS_CONTAINER_ADDRESS }} \
bundle exec rails r -
stdin: "{{ lookup('file', 'ruby/apply/storage_provider.rb') }}"
register: zammad_storage_provider_apply
changed_when: false
when: ZAMMAD_OBJSTORE_ENABLED | bool
- name: "β
ensure API-bot user in Zammad (Basic-auth REST API; all variants, no-auth too)"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
ansible.builtin.shell:
cmd: |
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_ADDRESS }} \
bundle exec rails r -
stdin: "{{ lookup('file', 'ruby/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: "π ensure the platform administrator in Zammad while sso is off"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
ansible.builtin.shell:
cmd: |
set -euo pipefail
container exec -i \
-e ADMIN_LOGIN -e ADMIN_EMAIL -e ADMIN_FIRSTNAME -e ADMIN_LASTNAME -e ADMIN_PASSWORD \
{{ ZAMMAD_RAILS_CONTAINER_ADDRESS }} \
bundle exec rails r -
stdin: "{{ lookup('file', 'ruby/apply/administrator.rb') }}"
environment:
ADMIN_LOGIN: "{{ ZAMMAD_ADMINISTRATOR.username }}"
ADMIN_EMAIL: "{{ ZAMMAD_ADMINISTRATOR.email }}"
ADMIN_FIRSTNAME: "{{ ZAMMAD_ADMINISTRATOR.firstname }}"
ADMIN_LASTNAME: "{{ ZAMMAD_ADMINISTRATOR.lastname }}"
ADMIN_PASSWORD: "{{ ZAMMAD_ADMINISTRATOR.password }}"
register: zammad_administrator_apply
changed_when: "'administrator: changed' in zammad_administrator_apply.stdout"
when: not (ZAMMAD_OIDC_ENABLED | bool)
- name: "π¨ Converge the corporate design of {{ application_id }}"
ansible.builtin.include_tasks: 02_design.yml
- name: "π ensure the read-only MCP agent and its API token in Zammad"
ansible.builtin.shell:
cmd: |
set -euo pipefail
container exec -i \
-e MCP_BOT_LOGIN -e MCP_BOT_EMAIL -e MCP_BOT_FIRSTNAME -e MCP_BOT_LASTNAME \
-e MCP_BOT_PASSWORD -e MCP_API_TOKEN -e MCP_TOKEN_LABEL \
{{ ZAMMAD_RAILS_CONTAINER_ADDRESS }} \
bundle exec rails r -
stdin: "{{ lookup('file', 'ruby/apply/mcp_bot.rb') }}"
environment:
MCP_BOT_LOGIN: "{{ ZAMMAD_MCP_UPSTREAM_USER }}"
MCP_BOT_EMAIL: "{{ ZAMMAD_MCP_UPSTREAM_EMAIL }}"
MCP_BOT_FIRSTNAME: "MCP"
MCP_BOT_LASTNAME: "Adapter"
MCP_BOT_PASSWORD: "{{ ZAMMAD_MCP_UPSTREAM_PASSWORD }}"
MCP_API_TOKEN: "{{ ZAMMAD_MCP_UPSTREAM_KEY }}"
MCP_TOKEN_LABEL: "{{ ZAMMAD_MCP_UPSTREAM_TOKEN_LABEL }}"
register: zammad_mcp_bot_apply
changed_when: false
when: ZAMMAD_MCP_ENABLED | bool
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "π apply OIDC settings in Zammad (post-bootstrap; idempotent)"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
ansible.builtin.shell:
cmd: |
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_ADDRESS }} \
bundle exec rails r -
stdin: "{{ lookup('file', 'ruby/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 LdapSource vs svc-db-openldap (post-bootstrap; idempotent)"
ansible.builtin.shell:
cmd: |
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_ADDRESS }} \
bundle exec rails r -
stdin: "{{ lookup('file', 'ruby/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: "π€ Wire Zammad Smart Assist to the platform LLM gateway"
when: ZAMMAD_LITELLM_ENABLED | bool
block:
- name: "π Verify the gateway contract for {{ application_id }}"
ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'roles/svc-ai-litellm/tasks/utils/consumer_contract.yml') }}"
- name: "π€ Point Zammad Smart Assist at the platform gateway"
ansible.builtin.shell:
cmd: |
set -euo pipefail
container exec -i \
-e AI_URL -e AI_MODEL -e AI_TOKEN \
{{ ZAMMAD_RAILS_CONTAINER_ADDRESS }} \
bundle exec rails r -
stdin: "{{ lookup('file', 'ruby/apply/ai_provider.rb') }}"
environment:
AI_URL: "{{ ZAMMAD_LITELLM_BASE_URL }}"
AI_MODEL: "{{ ZAMMAD_LITELLM_MODEL }}"
AI_TOKEN: "{{ ZAMMAD_LITELLM_API_KEY }}"
register: zammad_ai_apply
changed_when: false
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: "π§ configure Zammad's Email channel (inbound IMAP + outbound SMTP) against Mailu"
ansible.builtin.shell:
cmd: |
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_ADDRESS }} \
bundle exec rails r -
stdin: "{{ lookup('file', 'ruby/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)