mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
In swarm the role gated init only through "wait for Zammad init (seeds + pk-sequence reset)", which checks database state. On an existing installation that state is already there, so a swarm deploy that restarts init while Elasticsearch refuses shards passed: the init task kept failing on the allocation precheck and nothing reported it. A swarm-only task now runs sys-svc-compose's job_state.sh against the init service after the seeds wait. It passes once the newest init task is Complete. If the newest task stays Failed, it fails the deploy after 60 retries 5 s apart. An init the stack deploy did not restart is already Complete and passes at once. Compose needs no extra wait, because railsserver depends on init with service_completed_successfully. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
220 lines
8.6 KiB
YAML
220 lines
8.6 KiB
YAML
---
|
|
- 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: "🎬 Stage Zammad host-side init files (local + swarm peers)"
|
|
ansible.builtin.include_tasks:
|
|
file: "{{ lookup('path_absolute', 'roles/sys-svc-compose/tasks/utils/render_replicated_templates.yml') }}"
|
|
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"
|
|
condition: "{{ ZAMMAD_OIDC_ENABLED }}"
|
|
- src: "zz/oidc/http_discovery.rb.j2"
|
|
dest: "{{ ZAMMAD_OIDC_HTTP_DISCOVERY_HOST_PATH }}"
|
|
mode: "0644"
|
|
condition: "{{ ZAMMAD_OIDC_ENABLED and ZAMMAD_HTTP_ONLY_ENABLED }}"
|
|
|
|
- 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: DEPLOYMENT_MODE == 'swarm'
|
|
|
|
- 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: "✅ 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 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: "🔐 Verify the gateway contract for {{ application_id }}"
|
|
ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'roles/svc-ai-litellm/tasks/utils/consumer_contract.yml') }}"
|
|
when: ZAMMAD_LITELLM_ENABLED | bool
|
|
|
|
- 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 }}"
|
|
when: ZAMMAD_LITELLM_ENABLED | 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)
|