11_oidc_restart.ymlΒΆ

---
# Restart only while the issuer answers: Stalwart resolves the OIDC issuer at
# (re)start, and a restart with the issuer down sticks on internal auth.

- name: "⏳ Wait for the OIDC issuer discovery endpoint"
  ansible.builtin.shell: >-
    container exec -i {{ STALWART_CONTAINER_ADDRESS }}
    {{ 30 | curl }} -ksS -o /dev/null -w "%{http_code}"
    --proxy {{ STALWART_OIDC_SOCKS_PROXY | quote }} {{ OIDC.CLIENT.DISCOVERY_DOCUMENT }}
  register: stalwart_oidc_issuer_probe
  changed_when: false
  retries: 30
  delay: 5
  until: stalwart_oidc_issuer_probe.stdout == '200'

- name: "πŸ”„ Restart Stalwart so OIDC directory changes take effect (compose)"
  ansible.builtin.command: "container restart {{ STALWART_CONTAINER }}"
  changed_when: true
  when: IS_COMPOSE_MODE

- name: "πŸ”„ Restart Stalwart so OIDC directory changes take effect (swarm)"
  ansible.builtin.command: "container service update --force {{ lookup('container_service', application_id, 'stalwart') }}"
  changed_when: true
  timeout: "{{ lookup('timeout', 600) | int }}"
  when: IS_SWARM_MODE

- name: "⏳ Wait for Stalwart API after the OIDC restart"
  include_tasks: 01_wait_for_api.yml

- name: "πŸ“– Read the authentication singleton back"
  include_tasks: jmap_call.yml
  vars:
    stalwart_jmap_body: "{{ lookup('template', 'jmap/authentication/get.json.j2') }}"

- name: "πŸ”„ Restart Stalwart until the OIDC directory survives the build"
  ansible.builtin.shell:
    cmd: >-
      {{ ('container restart ' ~ STALWART_CONTAINER) if IS_COMPOSE_MODE
      else ('container service update --force '
      ~ lookup('container_service', application_id, 'stalwart')) }}
      </dev/null && container exec -i {{ STALWART_CONTAINER_ADDRESS }}
      {{ 30 | curl }} -sS -f -K -
      -H "Content-Type:application/json"
      {{ STALWART_JMAP_URL }}
    stdin: |-
      user = {{ (STALWART_ADMIN_USER ~ ':' ~ STALWART_ADMIN_PASSWORD) | to_json(ensure_ascii=False) }}
      data-binary = {{ lookup('template', 'jmap/authentication/get.json.j2') | to_json(ensure_ascii=False) }}
  register: stalwart_oidc_bind_retry
  when: (stalwart_jmap.methodResponses[0][1].list | first).directoryId != stalwart_oidc_dir_id
  changed_when: true
  retries: 3
  delay: 10
  until:
    - stalwart_oidc_bind_retry.rc == 0
    - (stalwart_oidc_bind_retry.stdout | from_json).methodResponses[0][1].list[0].directoryId == stalwart_oidc_dir_id
  timeout: "{{ lookup('timeout', 600) | int }}"
  no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"

- name: "πŸ“– Read the authentication singleton after the retries"
  include_tasks: jmap_call.yml
  vars:
    stalwart_jmap_body: "{{ lookup('template', 'jmap/authentication/get.json.j2') }}"

- name: "βœ… Ensure Stalwart authenticates against the OIDC directory"
  ansible.builtin.assert:
    that:
      - (stalwart_jmap.methodResponses[0][1].list | first).directoryId == stalwart_oidc_dir_id
    fail_msg: >-
      Stalwart dropped the Keycloak OIDC directory '{{ stalwart_oidc_dir_id }}'
      and is serving internal auth, so WebAdmin SSO and IMAP OAUTHBEARER refuse
      every Keycloak token. Its log names the reason
      ({{ STALWART_LOG_DIR }}/stalwart.log*, event registry.build-error).
    quiet: true

- name: "🧹 Clear the deferred OIDC restart flag"
  ansible.builtin.set_fact:
    stalwart_oidc_restart_pending: false