From c3cf779532c0a52d3c5df46e3b195b6e2786fb0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20B=C3=A4rlocher?= Date: Wed, 20 May 2026 22:13:34 +0200 Subject: [PATCH] feat: domain list refactor + demo-gymburgdorf fixes - Refactor: collapse `*_domain` + `*_extra_domains` into a single `*_domains` list across authentik, collabora, garage and nextcloud roles. First entry is the canonical FQDN (used for OVERWRITEHOST, BASE_URL, notify_push setup and garage root_domain). - Authentik blueprint: guard the OAuth sources block so an empty `authentik_login_sources` no longer renders an invalid YAML key. - Nextcloud: introduce `nextcloud_collabora_public_domain` and set Collabora's `public_wopi_url` separately from the server-to-server `wopi_url` so browsers can reach Collabora via the public name while Nextcloud still talks to it on the internal one. - Nextcloud: URL-encode the postgres user/password in DATABASE_URL. --- roles/authentik/defaults/main.yml | 6 ++++- .../blueprint-login-sources.yaml.j2 | 2 ++ .../authentik/templates/docker-compose.yml.j2 | 5 ++++- roles/collabora/defaults/main.yml | 6 ++++- .../collabora/templates/docker-compose.yml.j2 | 5 ++++- roles/garage/defaults/main.yml | 6 ++++- roles/garage/templates/docker-compose.yml.j2 | 8 ++++++- roles/garage/templates/garage.toml.j2 | 2 +- roles/nextcloud/defaults/main.yml | 7 +++++- roles/nextcloud/tasks/collabora.yml | 8 ++++++- roles/nextcloud/tasks/notify_push.yml | 2 +- .../nextcloud/templates/docker-compose.yml.j2 | 22 ++++++++++++++----- 12 files changed, 64 insertions(+), 15 deletions(-) diff --git a/roles/authentik/defaults/main.yml b/roles/authentik/defaults/main.yml index 9b2ca9a..3ff71be 100644 --- a/roles/authentik/defaults/main.yml +++ b/roles/authentik/defaults/main.yml @@ -12,7 +12,11 @@ authentik_docker_compose_dir: "{{ docker_compose_base_dir }}/{{ authentik_servic authentik_docker_volume_dir: "{{ docker_volume_base_dir }}/{{ authentik_service_name }}" # Authentik service configuration -authentik_domain: "authentik.local.test" +# FQDNs the authentik router accepts. The first entry is the canonical +# domain; further entries cover internal *.int.* names used for +# server-to-server traffic so backend calls don't hairpin via DMZ. +authentik_domains: + - "authentik.local.test" authentik_image: "ghcr.io/goauthentik/server:2026.2.2" authentik_port: 9000 authentik_secret_key: "changeme-generate-a-random-string" diff --git a/roles/authentik/templates/blueprints/blueprint-login-sources.yaml.j2 b/roles/authentik/templates/blueprints/blueprint-login-sources.yaml.j2 index acbb635..8bddb41 100644 --- a/roles/authentik/templates/blueprints/blueprint-login-sources.yaml.j2 +++ b/roles/authentik/templates/blueprints/blueprint-login-sources.yaml.j2 @@ -16,8 +16,10 @@ entries: {% for field in authentik_login_user_fields %} - {{ field }} {% endfor %} +{% if authentik_login_sources %} # OAuth/social login sources (use !Find to reference sources from other blueprints) sources: {% for src in authentik_login_sources %} - !Find [authentik_sources_oauth.oauthsource, [slug, {{ src.slug }}]] {% endfor %} +{% endif %} diff --git a/roles/authentik/templates/docker-compose.yml.j2 b/roles/authentik/templates/docker-compose.yml.j2 index c9796a2..f4a1f95 100644 --- a/roles/authentik/templates/docker-compose.yml.j2 +++ b/roles/authentik/templates/docker-compose.yml.j2 @@ -48,10 +48,13 @@ services: labels: - traefik.enable=true - traefik.docker.network={{ authentik_traefik_network }} - - traefik.http.routers.{{ authentik_service_name }}.rule=Host(`{{ authentik_domain }}`) + - traefik.http.routers.{{ authentik_service_name }}.rule=Host({% for d in authentik_domains %}`{{ d }}`{% if not loop.last %}, {% endif %}{% endfor %}) {% if authentik_use_ssl %} - traefik.http.routers.{{ authentik_service_name }}.entrypoints=websecure - traefik.http.routers.{{ authentik_service_name }}.tls=true +{% if traefik_cert_mode | default('selfsigned') == 'acme' %} + - traefik.http.routers.{{ authentik_service_name }}.tls.certresolver={{ traefik_ssl_cert_resolver | default('dns') }} +{% endif %} {% else %} - traefik.http.routers.{{ authentik_service_name }}.entrypoints=web {% endif %} diff --git a/roles/collabora/defaults/main.yml b/roles/collabora/defaults/main.yml index 3cfb559..11aa468 100644 --- a/roles/collabora/defaults/main.yml +++ b/roles/collabora/defaults/main.yml @@ -12,7 +12,11 @@ collabora_docker_compose_dir: "{{ docker_compose_base_dir }}/{{ collabora_servic collabora_docker_volume_dir: "{{ docker_volume_base_dir }}/{{ collabora_service_name }}" # Service configuration -collabora_domain: "office.local.test" +# FQDNs the collabora router accepts. The first entry is the canonical +# domain; further entries cover internal *.int.* names used for +# server-to-server WOPI discovery. +collabora_domains: + - "office.local.test" collabora_image: "collabora/code:latest" collabora_port: 9980 collabora_extra_hosts: [] diff --git a/roles/collabora/templates/docker-compose.yml.j2 b/roles/collabora/templates/docker-compose.yml.j2 index c0f589e..af6ecfc 100644 --- a/roles/collabora/templates/docker-compose.yml.j2 +++ b/roles/collabora/templates/docker-compose.yml.j2 @@ -20,11 +20,14 @@ services: labels: - traefik.enable=true - traefik.docker.network={{ collabora_traefik_network }} - - traefik.http.routers.{{ collabora_service_name }}.rule=Host(`{{ collabora_domain }}`) + - traefik.http.routers.{{ collabora_service_name }}.rule=Host({% for d in collabora_domains %}`{{ d }}`{% if not loop.last %}, {% endif %}{% endfor %}) - traefik.http.services.{{ collabora_service_name }}.loadbalancer.server.port={{ collabora_port }} {% if collabora_use_ssl %} - traefik.http.routers.{{ collabora_service_name }}.entrypoints=websecure - traefik.http.routers.{{ collabora_service_name }}.tls=true +{% if traefik_cert_mode | default('selfsigned') == 'acme' %} + - traefik.http.routers.{{ collabora_service_name }}.tls.certresolver={{ traefik_ssl_cert_resolver | default('dns') }} +{% endif %} {% else %} - traefik.http.routers.{{ collabora_service_name }}.entrypoints=web {% endif %} diff --git a/roles/garage/defaults/main.yml b/roles/garage/defaults/main.yml index 495317e..091e318 100644 --- a/roles/garage/defaults/main.yml +++ b/roles/garage/defaults/main.yml @@ -13,7 +13,11 @@ garage_docker_volume_dir: "{{ docker_volume_base_dir }}/{{ garage_service_name } # Garage service configuration garage_image: "dxflrs/garage:v2.1.0" -garage_s3_domain: "storage.local.test" +# FQDNs the garage S3 router accepts. The first entry is the canonical +# domain and is also used as the virtual-hosted-style root_domain in +# garage.toml; further entries cover internal *.int.* names. +garage_s3_domains: + - "storage.local.test" garage_web_domain: "web.storage.local.test" garage_webui_domain: "console.storage.local.test" diff --git a/roles/garage/templates/docker-compose.yml.j2 b/roles/garage/templates/docker-compose.yml.j2 index 9e3e862..0427fb2 100644 --- a/roles/garage/templates/docker-compose.yml.j2 +++ b/roles/garage/templates/docker-compose.yml.j2 @@ -14,10 +14,13 @@ services: - traefik.enable=true - traefik.docker.network={{ garage_traefik_network }} # S3 API endpoint - - traefik.http.routers.{{ garage_service_name }}.rule=Host(`{{ garage_s3_domain }}`) + - traefik.http.routers.{{ garage_service_name }}.rule=Host({% for d in garage_s3_domains %}`{{ d }}`{% if not loop.last %}, {% endif %}{% endfor %}) {% if garage_use_ssl %} - traefik.http.routers.{{ garage_service_name }}.entrypoints=websecure - traefik.http.routers.{{ garage_service_name }}.tls=true +{% if traefik_cert_mode | default('selfsigned') == 'acme' %} + - traefik.http.routers.{{ garage_service_name }}.tls.certresolver={{ traefik_ssl_cert_resolver | default('dns') }} +{% endif %} {% else %} - traefik.http.routers.{{ garage_service_name }}.entrypoints=web {% endif %} @@ -48,6 +51,9 @@ services: {% if garage_use_ssl %} - traefik.http.routers.{{ garage_service_name }}-console.entrypoints=websecure - traefik.http.routers.{{ garage_service_name }}-console.tls=true +{% if traefik_cert_mode | default('selfsigned') == 'acme' %} + - traefik.http.routers.{{ garage_service_name }}-console.tls.certresolver={{ traefik_ssl_cert_resolver | default('dns') }} +{% endif %} {% else %} - traefik.http.routers.{{ garage_service_name }}-console.entrypoints=web {% endif %} diff --git a/roles/garage/templates/garage.toml.j2 b/roles/garage/templates/garage.toml.j2 index 897ebcd..06b1164 100644 --- a/roles/garage/templates/garage.toml.j2 +++ b/roles/garage/templates/garage.toml.j2 @@ -14,7 +14,7 @@ rpc_secret = "{{ garage_rpc_secret }}" [s3_api] s3_region = "{{ garage_s3_region }}" api_bind_addr = "[::]:{{ garage_s3_api_port }}" -root_domain = ".s3.{{ garage_s3_domain }}" +root_domain = ".s3.{{ garage_s3_domains[0] }}" [s3_web] bind_addr = "[::]:{{ garage_s3_web_port }}" diff --git a/roles/nextcloud/defaults/main.yml b/roles/nextcloud/defaults/main.yml index 0c96046..c289d96 100644 --- a/roles/nextcloud/defaults/main.yml +++ b/roles/nextcloud/defaults/main.yml @@ -9,7 +9,12 @@ nextcloud_service_name: nextcloud nextcloud_docker_compose_dir: "{{ docker_compose_base_dir }}/{{ nextcloud_service_name }}" nextcloud_docker_volume_dir: "{{ docker_volume_base_dir }}/{{ nextcloud_service_name }}" -nextcloud_domain: "nextcloud.local.test" +# FQDNs the nextcloud router accepts. The first entry is the canonical +# domain (used for OVERWRITEHOST and the notify_push setup); further +# entries cover internal *.int.* names so collabora's WOPI callback +# hits us on a name with a valid cert. +nextcloud_domains: + - "nextcloud.local.test" nextcloud_image: "nextcloud:fpm" nextcloud_redis_image: "redis:latest" nextcloud_port: 80 diff --git a/roles/nextcloud/tasks/collabora.yml b/roles/nextcloud/tasks/collabora.yml index 05c56e4..2a7bd82 100644 --- a/roles/nextcloud/tasks/collabora.yml +++ b/roles/nextcloud/tasks/collabora.yml @@ -1,11 +1,17 @@ #SPDX-License-Identifier: MIT-0 --- # tasks file for configuring Collabora in Nextcloud -- name: Configure Collabora WOPI URL +- name: Configure Collabora WOPI URL (server-to-server) community.docker.docker_container_exec: container: "{{ nextcloud_docker_compose_dir | basename }}-nextcloud-1" command: php /var/www/html/occ config:app:set richdocuments wopi_url --value=https://{{ nextcloud_collabora_domain }} +- name: Configure Collabora public WOPI URL (browser-facing) + community.docker.docker_container_exec: + container: "{{ nextcloud_docker_compose_dir | basename }}-nextcloud-1" + command: php /var/www/html/occ config:app:set richdocuments public_wopi_url --value=https://{{ nextcloud_collabora_public_domain }} + when: nextcloud_collabora_public_domain is defined and nextcloud_collabora_public_domain != nextcloud_collabora_domain + - name: Configure certificate verification for Collabora community.docker.docker_container_exec: container: "{{ nextcloud_docker_compose_dir | basename }}-nextcloud-1" diff --git a/roles/nextcloud/tasks/notify_push.yml b/roles/nextcloud/tasks/notify_push.yml index 18dbb8b..ccf2b72 100644 --- a/roles/nextcloud/tasks/notify_push.yml +++ b/roles/nextcloud/tasks/notify_push.yml @@ -5,4 +5,4 @@ - name: Configure notify_push base endpoint community.docker.docker_container_exec: container: "{{ nextcloud_docker_compose_dir | basename }}-nextcloud-1" - command: php /var/www/html/occ notify_push:setup https://{{ nextcloud_domain }}/push \ No newline at end of file + command: php /var/www/html/occ notify_push:setup https://{{ nextcloud_domains[0] }}/push \ No newline at end of file diff --git a/roles/nextcloud/templates/docker-compose.yml.j2 b/roles/nextcloud/templates/docker-compose.yml.j2 index 9f15760..0e05090 100644 --- a/roles/nextcloud/templates/docker-compose.yml.j2 +++ b/roles/nextcloud/templates/docker-compose.yml.j2 @@ -35,10 +35,13 @@ services: labels: - traefik.enable=true - traefik.docker.network={{ nextcloud_traefik_network }} - - traefik.http.routers.{{ nextcloud_service_name }}.rule=Host(`{{ nextcloud_domain }}`) + - traefik.http.routers.{{ nextcloud_service_name }}.rule=Host({% for d in nextcloud_domains %}`{{ d }}`{% if not loop.last %}, {% endif %}{% endfor %}) {% if nextcloud_use_ssl %} - traefik.http.routers.{{ nextcloud_service_name }}.entrypoints=websecure - traefik.http.routers.{{ nextcloud_service_name }}.tls=true +{% if traefik_cert_mode | default('selfsigned') == 'acme' %} + - traefik.http.routers.{{ nextcloud_service_name }}.tls.certresolver={{ traefik_ssl_cert_resolver | default('dns') }} +{% endif %} {% else %} - traefik.http.routers.{{ nextcloud_service_name }}.entrypoints=web {% endif %} @@ -60,7 +63,7 @@ services: PHP_MEMORY_LIMIT: {{ nextcloud_memory_limit_mb }}M PHP_UPLOAD_LIMIT: {{ nextcloud_upload_limit_mb }}M OVERWRITEPROTOCOL: https - OVERWRITEHOST: {{ nextcloud_domain }} + OVERWRITEHOST: {{ nextcloud_domains[0] }} TRUSTED_PROXIES: "{{ nextcloud_trusted_proxies }}" volumes: - {{ nextcloud_docker_volume_dir }}/nextcloud/:/var/www/html @@ -69,6 +72,12 @@ services: {% for net in nextcloud_extra_networks %} - {{ net }} {% endfor %} +{% if nextcloud_extra_hosts is defined and nextcloud_extra_hosts | length > 0 %} + extra_hosts: +{% for host in nextcloud_extra_hosts %} + - "{{ host }}" +{% endfor %} +{% endif %} nextcloud: image: {{ nextcloud_image }} @@ -88,7 +97,7 @@ services: PHP_MEMORY_LIMIT: {{ nextcloud_memory_limit_mb }}M PHP_UPLOAD_LIMIT: {{ nextcloud_upload_limit_mb }}M OVERWRITEPROTOCOL: https - OVERWRITEHOST: {{ nextcloud_domain }} + OVERWRITEHOST: {{ nextcloud_domains[0] }} TRUSTED_PROXIES: "{{ nextcloud_trusted_proxies }}" {% if nextcloud_use_s3_storage %} OBJECTSTORE_S3_KEY: {{ nextcloud_s3_key }} @@ -127,7 +136,7 @@ services: environment: PORT: "7867" REDIS_URL: "redis://redis:6379" - DATABASE_URL: "postgres://{{ nextcloud_postgres_user }}:{{ nextcloud_postgres_password }}@db:5432/{{ nextcloud_postgres_db }}" + DATABASE_URL: "postgres://{{ nextcloud_postgres_user | urlencode }}:{{ nextcloud_postgres_password | urlencode }}@db:5432/{{ nextcloud_postgres_db }}" DATABASE_PREFIX: "oc_" NEXTCLOUD_URL: "http://nginx" networks: @@ -136,11 +145,14 @@ services: labels: - traefik.enable=true - traefik.docker.network={{ nextcloud_traefik_network }} - - traefik.http.routers.{{ nextcloud_service_name }}-push.rule=Host(`{{ nextcloud_domain }}`) && PathPrefix(`/push`) + - traefik.http.routers.{{ nextcloud_service_name }}-push.rule=Host(`{{ nextcloud_domains[0] }}`) && PathPrefix(`/push`) - traefik.http.services.{{ nextcloud_service_name }}-push.loadbalancer.server.port=7867 {% if nextcloud_use_ssl %} - traefik.http.routers.{{ nextcloud_service_name }}-push.entrypoints=websecure - traefik.http.routers.{{ nextcloud_service_name }}-push.tls=true +{% if traefik_cert_mode | default('selfsigned') == 'acme' %} + - traefik.http.routers.{{ nextcloud_service_name }}-push.tls.certresolver={{ traefik_ssl_cert_resolver | default('dns') }} +{% endif %} {% else %} - traefik.http.routers.{{ nextcloud_service_name }}-push.entrypoints=web {% endif %}