Files
computer-playbook/roles/web-app-nextcloud/tasks/addons/integration_zammad_provision.yml
kevinveenbirkenbachandClaude Opus 5 f7bb608927 refactor(nextcloud): sort files by language, and let Doorkeeper accept onion
files/ now mirrors the role's own structure: php, ruby and shell, each split
into addons for the per-integration scripts that tasks/addons/ drives and utils
for what more than one caller shares. set_encrypted_appconfig.php serves gitlab,
mattermost and zammad; doorkeeper_allow_onion_redirect.rb serves zammad and
openproject; init-gate.sh belongs to the role itself. The partner directories
survive because two scripts are both named provision_oauth_app.rb.

NEXTCLOUD_INIT_GATE_FILE had to split. It was the source path under files/, the
name Dockerfile.j2 COPYs into /usr/local/bin, and the ENTRYPOINT, all at once;
moving the script would have produced /usr/local/bin/shell/utils/init-gate.sh.
The basename keeps that name and NEXTCLOUD_INIT_GATE_SRC carries the path.

The same commit closes two of the three faces of one defect. resolve_enabled
reports TLS off for an .onion domain, correctly -- the vhost is plaintext and
Tor carries the transport crypto -- so every redirect URI built from
lookup('tls', ...) is http, and Doorkeeper rejects it. Zammad raises out of
app.save! and fails the task at rc 1; OpenProject discards result.errors, keeps
the unsaved model whose plaintext_secret already exists, reports
oauth_application_created:true, and only dies three tasks later on a null
client_id. The prelude relaxes force_ssl_in_redirect_uri for .onion hosts and
delegates every other host to the previous setting, so clearnet is unchanged at
both partners; the OpenProject script now raises with the real validation
errors.

The third face is PeerTube, whose Keycloak callback is registered http and
requested https, and it stays open: fixing it means changing either PeerTube's
scheme, which is its federation identity, or the registration side.

Evidence: run 31658270172, jobs 94319317307 ("Redirect URI must be an HTTPS/SSL
URI.") and 94319317278 (changed:true, then length on NoneType).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 02:02:27 +02:00

85 lines
3.9 KiB
YAML

---
- name: "🔍 Resolve Zammad railsserver hosting node + container id"
ansible.builtin.include_tasks: "{{ lookup('path_absolute', 'roles/sys-svc-compose/tasks/utils/swarm/resolve_host_cid.yml') }}"
vars:
resolve_service_key: railsserver
resolve_service_name: "{{ lookup('container_service', 'web-app-zammad', 'railsserver') }}"
resolve_output_prefix: NEXTCLOUD_ZAMMAD
resolve_compose_cid: "{{ NEXTCLOUD_ZAMMAD_CONTAINER_ADDRESS }}"
resolve_converge_retries: 120
resolve_converge_delay: 5
- name: ⏳ Wait for the Zammad web endpoint
wait_for:
host: "{{ DOCKER_REACH_HOST }}"
port: "{{ lookup('config', 'web-app-zammad', 'services.zammad.ports.local.http') }}"
delay: 5
timeout: "{{ lookup('timeout', 600) | int }}"
- name: 🔧 Provision the Zammad OAuth application for Nextcloud and capture credentials
ansible.builtin.shell:
cmd: >-
container exec -i
-e 'NC_BASE_URL={{ nextcloud_zammad_nc_base_url }}'
-e 'NC_REDIRECT_PATH=/apps/integration_zammad/oauth-redirect'
-e 'NC_HAS_SECRET={{ nextcloud_zammad_client_secret_current.stdout | trim }}'
{{ NEXTCLOUD_ZAMMAD_LOCAL_CID | quote }}
bundle exec rails r -
stdin: "{{ lookup('file', 'ruby/utils/doorkeeper_allow_onion_redirect.rb') ~ '\n' ~ lookup('file', 'ruby/addons/zammad/provision_oauth_app.rb') }}"
delegate_to: "{{ NEXTCLOUD_ZAMMAD_HOST_NODE }}"
register: nextcloud_zammad_provision
retries: 12
delay: 10
until: nextcloud_zammad_provision.rc == 0
changed_when: "'CLIENT_ID=' in nextcloud_zammad_provision.stdout"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: 🔎 Parse the Zammad OAuth client id
set_fact:
nextcloud_zammad_client_id: "{{ nextcloud_zammad_provision.stdout | regex_search('CLIENT_ID=(.+)', '\\1') | first }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: 🔍 Resolve the Zammad OAuth client secret (freshly emitted or persisted)
set_fact:
nextcloud_zammad_fresh_secret: "{{ (nextcloud_zammad_provision.stdout | regex_search('CLIENT_SECRET=(.+)', '\\1') | default([], true) | first | default('', true))
| trim }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: 🎯 Pick the effective Zammad OAuth client secret
set_fact:
nextcloud_zammad_client_secret: "{{ nextcloud_zammad_fresh_secret if (nextcloud_zammad_fresh_secret | length > 0) else (nextcloud_zammad_client_secret_current.stdout
| trim) }}"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
- name: 🛑 Fail when no usable Zammad OAuth client secret is available
assert:
that:
- (nextcloud_zammad_client_id | default('') | trim) | length > 0
- (nextcloud_zammad_client_secret | default('') | trim) | length > 0
fail_msg: >-
Zammad did not return a usable OAuth application. The plaintext client secret is only
retrievable when the app is first created or its secret is renewed; reset the
integration_zammad client_id/client_secret app config to re-provision.
- name: 🔑 Point integration_zammad at the partner instance (admin OAuth instance URL)
shell: >
{{ NEXTCLOUD_DOCKER_EXEC_OCC }}
config:app:set integration_zammad oauth_instance_url
--value '{{ nextcloud_zammad_base_url }}'
register: nextcloud_zammad_oauth_url_set
changed_when: "' set to ' in nextcloud_zammad_oauth_url_set.stdout"
- name: 📝 Store integration_zammad OAuth creds Nextcloud-encrypted (sensitive, lazy)
ansible.builtin.shell:
cmd: >-
container exec -i -u {{ NEXTCLOUD_DOCKER_USER }}
-e 'NC_APP_ID=integration_zammad'
-e 'NC_ENC_MODE=sensitive'
-e 'NC_ENC_B64={{ {'client_id': nextcloud_zammad_client_id, 'client_secret': nextcloud_zammad_client_secret} | to_json | b64encode }}'
{{ NEXTCLOUD_LOCAL_CID | quote }}
php
stdin: "{{ lookup('file', 'php/utils/set_encrypted_appconfig.php') }}"
register: nextcloud_zammad_enc_set
changed_when: "'OK' in nextcloud_zammad_enc_set.stdout"
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"