diff --git a/roles/authentik/defaults/main.yml b/roles/authentik/defaults/main.yml index 880734e..3ff71be 100644 --- a/roles/authentik/defaults/main.yml +++ b/roles/authentik/defaults/main.yml @@ -17,15 +17,6 @@ authentik_docker_volume_dir: "{{ docker_volume_base_dir }}/{{ authentik_service_ # server-to-server traffic so backend calls don't hairpin via DMZ. authentik_domains: - "authentik.local.test" - -# Hostnames that should reach authentik but make it generate URLs (OIDC -# issuer, password reset links, etc.) as if requested from the canonical -# `authentik_domains[0]` instead. Used for split-horizon setups where an -# internal FQDN (e.g. `auth.int.example.com`) keeps server-to-server -# traffic in the LAN but the iss claim must still match the public -# hostname that browsers see. Traefik handles each entry via a separate -# router that rewrites the Host header before forwarding to authentik. -authentik_host_rewrite_domains: [] 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-proxy-app.yaml.j2 b/roles/authentik/templates/blueprints/blueprint-proxy-app.yaml.j2 index acfc7a9..5e29756 100644 --- a/roles/authentik/templates/blueprints/blueprint-proxy-app.yaml.j2 +++ b/roles/authentik/templates/blueprints/blueprint-proxy-app.yaml.j2 @@ -20,16 +20,6 @@ entries: internal_host: "{{ item.internal_host }}" external_host: "{{ item.external_host }}" -{# Provider mode controls how authentik treats the proxy app: - - proxy : the outpost itself proxies traffic to internal_host - - forward_single : a single app behind an external reverse proxy - (traefik forwardauth talks to authentik per-domain) - - forward_domain : wildcard mode — one provider guards every host on a - cookie domain; configure forward_auth_mode=domain on - the outpost in that case. Default to forward_single - since that's the common ForwardAuth-with-traefik - pattern. #} - mode: {{ item.mode | default('forward_single') }} {% if item.skip_path_regex is defined and item.skip_path_regex|length > 0 %} skip_path_regex: | @@ -44,20 +34,3 @@ entries: name: "{{ item.name | default(item.slug) }}" slug: {{ item.slug }} provider: !KeyOf proxy-provider-{{ item.slug }} - -{% if item.allowed_groups is defined and item.allowed_groups | length > 0 %} -{# Restrict access to listed groups: one PolicyBinding per group, all bound - to the application. Authentik treats multiple bindings on the same target - as OR (a user matching any binding passes), and a request from a user in - none of the bound groups is denied. #} -{% for group_name in item.allowed_groups %} - - model: authentik_policies.policybinding - identifiers: - target: !KeyOf app-{{ item.slug }} - order: {{ loop.index0 }} - group: !Find [authentik_core.group, [name, "{{ group_name }}"]] - attrs: - enabled: true - negate: false -{% endfor %} -{% endif %} diff --git a/roles/authentik/templates/docker-compose.yml.j2 b/roles/authentik/templates/docker-compose.yml.j2 index e5b8a11..f0193ec 100644 --- a/roles/authentik/templates/docker-compose.yml.j2 +++ b/roles/authentik/templates/docker-compose.yml.j2 @@ -43,19 +43,12 @@ services: postgres: condition: service_healthy networks: - {{ authentik_backend_network }}: {} - # Network alias so traefik (which shares this network) can resolve - # the canonical FQDN to this container directly. The URL-based - # service below uses that to send upstream traffic with a fixed - # Host header equal to the canonical hostname. - {{ authentik_traefik_network }}: - aliases: - - {{ authentik_domains[0] }} + - {{ authentik_backend_network }} + - {{ authentik_traefik_network }} labels: - traefik.enable=true - traefik.docker.network={{ authentik_traefik_network }} - traefik.http.routers.{{ authentik_service_name }}.rule={% for d in authentik_domains %}Host(`{{ d }}`){% if not loop.last %} || {% endif %}{% endfor +%} - - traefik.http.routers.{{ authentik_service_name }}.service={{ authentik_service_name }} {% if authentik_use_ssl %} - traefik.http.routers.{{ authentik_service_name }}.entrypoints=websecure - traefik.http.routers.{{ authentik_service_name }}.tls=true @@ -66,34 +59,6 @@ services: - traefik.http.routers.{{ authentik_service_name }}.entrypoints=web {% endif %} - traefik.http.services.{{ authentik_service_name }}.loadbalancer.server.port={{ authentik_port }} -{% if authentik_host_rewrite_domains | length > 0 %} - # Server-to-server entry: a separate service points at this very - # container by the canonical FQDN (resolved via the network alias - # above) and disables passHostHeader so the upstream Host header - # becomes `{{ authentik_domains[0] }}`. Authentik builds OIDC issuer - # URLs from X-Forwarded-Host (not Host), so we also pin that header - # via middleware. Together this keeps the iss claim aligned with - # the public hostname browsers see during login, even when the - # request itself arrived on an internal *.int.* FQDN. - - traefik.http.services.{{ authentik_service_name }}-rewrite.loadbalancer.server.url=http://{{ authentik_domains[0] }}:{{ authentik_port }} - - traefik.http.services.{{ authentik_service_name }}-rewrite.loadbalancer.passhostheader=false - - traefik.http.middlewares.{{ authentik_service_name }}-xfh-rewrite.headers.customrequestheaders.X-Forwarded-Host={{ authentik_domains[0] }} -{% for d in authentik_host_rewrite_domains %} - - traefik.http.routers.{{ authentik_service_name }}-rewrite-{{ loop.index0 }}.rule=Host(`{{ d }}`) - - traefik.http.routers.{{ authentik_service_name }}-rewrite-{{ loop.index0 }}.priority=100 - - traefik.http.routers.{{ authentik_service_name }}-rewrite-{{ loop.index0 }}.service={{ authentik_service_name }}-rewrite - - traefik.http.routers.{{ authentik_service_name }}-rewrite-{{ loop.index0 }}.middlewares={{ authentik_service_name }}-xfh-rewrite -{% if authentik_use_ssl %} - - traefik.http.routers.{{ authentik_service_name }}-rewrite-{{ loop.index0 }}.entrypoints=websecure - - traefik.http.routers.{{ authentik_service_name }}-rewrite-{{ loop.index0 }}.tls=true -{% if traefik_cert_mode | default('selfsigned') == 'acme' %} - - traefik.http.routers.{{ authentik_service_name }}-rewrite-{{ loop.index0 }}.tls.certresolver={{ traefik_ssl_cert_resolver | default('dns') }} -{% endif %} -{% else %} - - traefik.http.routers.{{ authentik_service_name }}-rewrite-{{ loop.index0 }}.entrypoints=web -{% endif %} -{% endfor %} -{% endif %} worker: image: {{ authentik_image }} diff --git a/roles/drawio/defaults/main.yml b/roles/drawio/defaults/main.yml index 2b2b758..7b67976 100644 --- a/roles/drawio/defaults/main.yml +++ b/roles/drawio/defaults/main.yml @@ -17,11 +17,4 @@ drawio_extra_hosts: [] # Traefik configuration drawio_traefik_network: "proxy" -drawio_use_ssl: true - -# Optional Authentik ForwardAuth (set to true and provide the URL to gate -# drawio behind an authentik proxy provider). Expects the authentik -# embedded outpost to expose the /outpost.goauthentik.io/auth/traefik -# endpoint on the configured URL (typically the public auth.* FQDN). -drawio_authentik_forward_auth: false -drawio_authentik_forward_auth_url: "" \ No newline at end of file +drawio_use_ssl: true \ No newline at end of file diff --git a/roles/drawio/templates/docker-compose.yml.j2 b/roles/drawio/templates/docker-compose.yml.j2 index c9b0c9a..b6b9ef5 100644 --- a/roles/drawio/templates/docker-compose.yml.j2 +++ b/roles/drawio/templates/docker-compose.yml.j2 @@ -22,15 +22,6 @@ services: {% else %} - traefik.http.routers.{{ drawio_service_name }}.entrypoints=web {% endif %} -{% if drawio_authentik_forward_auth | default(false) %} - # ForwardAuth via the authentik embedded outpost. Unauthenticated - # requests get redirected to authentik to log in; authentik then - # sets X-Authentik-* headers traefik forwards downstream. - - traefik.http.middlewares.{{ drawio_service_name }}-authentik.forwardauth.address={{ drawio_authentik_forward_auth_url }} - - traefik.http.middlewares.{{ drawio_service_name }}-authentik.forwardauth.trustForwardHeader=true - - traefik.http.middlewares.{{ drawio_service_name }}-authentik.forwardauth.authResponseHeaders=X-authentik-username,X-authentik-groups,X-authentik-entitlements,X-authentik-email,X-authentik-name,X-authentik-uid,X-authentik-jwt,X-authentik-meta-jwks,X-authentik-meta-outpost,X-authentik-meta-provider,X-authentik-meta-app,X-authentik-meta-version - - traefik.http.routers.{{ drawio_service_name }}.middlewares={{ drawio_service_name }}-authentik -{% endif %} networks: {{ drawio_traefik_network }}: diff --git a/roles/garage/defaults/main.yml b/roles/garage/defaults/main.yml index 5a207eb..091e318 100644 --- a/roles/garage/defaults/main.yml +++ b/roles/garage/defaults/main.yml @@ -25,20 +25,10 @@ garage_webui_domain: "console.storage.local.test" garage_webui_enabled: true garage_webui_image: "khairul169/garage-webui:latest" garage_webui_port: 3909 -# WebUI basic auth credentials (plaintext, will be hashed automatically). -# Ignored when garage_webui_authentik_forward_auth is true — in that case -# authentik handles authentication via the ForwardAuth middleware below. +# WebUI basic auth credentials (plaintext, will be hashed automatically) garage_webui_username: "admin" garage_webui_password: "admin" -# Optional Authentik ForwardAuth in front of the WebUI. When true: -# - the AUTH_USER_PASS env-var is dropped from the container so htpasswd -# isn't enforced; authentik is the only gate. -# - traefik attaches a ForwardAuth middleware pointing at the URL below. -# Leave false to keep classic htpasswd protection. -garage_webui_authentik_forward_auth: false -garage_webui_authentik_forward_auth_url: "" - # Garage ports garage_s3_api_port: 3900 garage_s3_web_port: 3902 diff --git a/roles/garage/tasks/bootstrap.yml b/roles/garage/tasks/bootstrap.yml index 5dc7e6e..6cab2cf 100644 --- a/roles/garage/tasks/bootstrap.yml +++ b/roles/garage/tasks/bootstrap.yml @@ -7,27 +7,21 @@ container: "{{ garage_service_name }}" command: /garage node id -q register: _garage_node_id - changed_when: false - name: Extract short node ID ansible.builtin.set_fact: _garage_node_id_short: "{{ _garage_node_id.stdout.split('@')[0] }}" -- name: Extract truncated node ID (first 16 chars, matches `garage layout show` output) - ansible.builtin.set_fact: - _garage_node_id_truncated: "{{ _garage_node_id_short[:16] }}" - - name: Check if node layout is configured community.docker.docker_container_exec: container: "{{ garage_service_name }}" command: /garage layout show register: _garage_layout_show failed_when: false - changed_when: false - name: Check if node is in layout ansible.builtin.set_fact: - _node_in_layout: "{{ (_garage_node_id_truncated in _garage_layout_show.stdout) or (_garage_node_id_short in _garage_layout_show.stdout) }}" + _node_in_layout: "{{ _garage_node_id_short in _garage_layout_show.stdout }}" - name: Configure garage node layout community.docker.docker_container_exec: diff --git a/roles/garage/tasks/main.yml b/roles/garage/tasks/main.yml index 4478f51..4aebbeb 100644 --- a/roles/garage/tasks/main.yml +++ b/roles/garage/tasks/main.yml @@ -26,77 +26,12 @@ dest: "{{ garage_docker_compose_dir }}/garage.toml" mode: '0644' -- name: Set webui htpasswd activation fact - ansible.builtin.set_fact: - # htpasswd only runs when the WebUI is enabled AND authentik ForwardAuth - # is not handling authentication. When authentik is in front, the - # compose template drops AUTH_USER_PASS so no hash is needed. - _garage_webui_htpasswd_active: >- - {{ - garage_webui_enabled - and not (garage_webui_authentik_forward_auth | default(false)) - }} - -- name: Read cached webui htpasswd hash - ansible.builtin.slurp: - src: "{{ garage_docker_compose_dir }}/webui.htpasswd" - register: _garage_webui_htpasswd_cached - failed_when: false - changed_when: false - when: _garage_webui_htpasswd_active - -- name: Verify cached webui htpasswd hash still matches password - ansible.builtin.command: - argv: - - htpasswd - - -vbB - - "{{ garage_docker_compose_dir }}/webui.htpasswd" - - "{{ garage_webui_username }}" - - "{{ garage_webui_password }}" - register: _garage_webui_htpasswd_verify - failed_when: false - changed_when: false - no_log: true - when: - - _garage_webui_htpasswd_active - - _garage_webui_htpasswd_cached.content is defined - - name: Generate bcrypt hash for webui password using htpasswd - ansible.builtin.command: - argv: - - htpasswd - - -nbBC - - "10" - - "{{ garage_webui_username }}" - - "{{ garage_webui_password }}" - register: _garage_webui_password_hash_new - changed_when: true - when: - - _garage_webui_htpasswd_active - - (_garage_webui_htpasswd_cached.content is not defined) - or (_garage_webui_htpasswd_verify.rc | default(1) != 0) - -- name: Persist webui htpasswd hash on disk - ansible.builtin.copy: - content: "{{ _garage_webui_password_hash_new.stdout }}\n" - dest: "{{ garage_docker_compose_dir }}/webui.htpasswd" - mode: '0600' - when: - - _garage_webui_htpasswd_active - - _garage_webui_password_hash_new is changed - -- name: Load current webui htpasswd hash - ansible.builtin.slurp: - src: "{{ garage_docker_compose_dir }}/webui.htpasswd" - register: _garage_webui_htpasswd_current + ansible.builtin.shell: | + htpasswd -nbBC 10 "{{ garage_webui_username }}" "{{ garage_webui_password }}" + register: _garage_webui_password_hash changed_when: false - when: _garage_webui_htpasswd_active - -- name: Expose current webui htpasswd hash to template - ansible.builtin.set_fact: - _garage_webui_password_hash: - stdout: "{{ (_garage_webui_htpasswd_current.content | b64decode).strip() }}" - when: _garage_webui_htpasswd_active + when: garage_webui_enabled - name: Create docker-compose file for garage template: diff --git a/roles/garage/tasks/provision.yml b/roles/garage/tasks/provision.yml index dacf2c0..1c2628e 100644 --- a/roles/garage/tasks/provision.yml +++ b/roles/garage/tasks/provision.yml @@ -4,17 +4,11 @@ container: "{{ garage_service_name }}" command: /garage key list register: _existing_keys_output - changed_when: false when: garage_s3_keys | length > 0 - name: Parse existing key names ansible.builtin.set_fact: - # `garage key list` columns: ID Created Name Expiration. - # Data rows begin with a GK key ID; header is "ID Created ..." - # and INFO log lines may interleave on stderr (kept separate by - # docker_container_exec). Strip ANSI escapes defensively, filter to - # GK-prefixed rows, then take the 3rd whitespace-separated field. - _existing_keys: "{{ _existing_keys_output.stdout_lines | map('regex_replace', '\\x1b\\[[0-9;]*m', '') | select('match', '^GK[0-9a-fA-F]+') | map('regex_replace', '^\\S+\\s+\\S+\\s+(\\S+).*$', '\\1') | list }}" + _existing_keys: "{{ _existing_keys_output.stdout_lines[1:] | select('match', '^GK') | map('regex_replace', '^\\S+\\s+\\S+\\s+(\\S+)\\s+.*$', '\\1') | list }}" when: garage_s3_keys | length > 0 - name: Create S3 keys @@ -33,7 +27,6 @@ command: /garage key info {{ item.name }} loop: "{{ garage_s3_keys }}" register: _key_info_results - changed_when: false when: garage_s3_keys | length > 0 - name: Extract key IDs from info @@ -49,21 +42,11 @@ container: "{{ garage_service_name }}" command: /garage bucket list register: _existing_buckets_output - changed_when: false when: garage_s3_keys | length > 0 - name: Parse existing bucket names ansible.builtin.set_fact: - # `garage bucket list` columns: ID Created Global aliases Local aliases - # Data rows start with a hex bucket ID; filter to those and take the - # third whitespace-separated field (the global alias = bucket name). - _existing_buckets: >- - {{ - _existing_buckets_output.stdout_lines - | select('match', '^[0-9a-f]{16}\\s') - | map('regex_replace', '^\\S+\\s+\\S+\\s+(\\S+).*$', '\\1') - | list - }} + _existing_buckets: "{{ _existing_buckets_output.stdout_lines[2:] | map('split') | map('first') | list }}" when: garage_s3_keys | length > 0 - name: Get unique bucket names @@ -81,37 +64,12 @@ - item not in _existing_buckets failed_when: false -- name: Get current bucket permissions - community.docker.docker_container_exec: - container: "{{ garage_service_name }}" - command: /garage bucket info {{ item.1.name }} - loop: "{{ garage_s3_keys | subelements('buckets', skip_missing=True) }}" - loop_control: - label: "{{ item.1.name }}" - register: _bucket_info_results - changed_when: false - when: garage_s3_keys | length > 0 - - name: Set bucket permissions using key IDs community.docker.docker_container_exec: container: "{{ garage_service_name }}" - command: /garage bucket allow {{ item.item.1.name }} {% for perm in item.item.1.permissions %}--{{ perm }} {% endfor %}--key {{ _key_id_map[item.item.0.name] }} - loop: "{{ _bucket_info_results.results }}" - loop_control: - label: "{{ item.item.1.name }} -> {{ item.item.0.name }}" - when: - - garage_s3_keys | length > 0 - - >- - (item.stdout | regex_search( - '(?m)^\s*' ~ _wanted_flags ~ '\s+' ~ _key_id_map[item.item.0.name] - )) is none - vars: - _wanted_flags: >- - {{ - ('R' if 'read' in item.item.1.permissions else '-') - ~ ('W' if 'write' in item.item.1.permissions else '-') - ~ ('O' if 'owner' in item.item.1.permissions else '-') - }} + command: /garage bucket allow {{ item.1.name }} {% for perm in item.1.permissions %}--{{ perm }} {% endfor %}--key {{ _key_id_map[item.0.name] }} + loop: "{{ garage_s3_keys | subelements('buckets', skip_missing=True) }}" + when: garage_s3_keys | length > 0 # Export key credentials for use by other roles - name: Get detailed key information for all keys @@ -120,7 +78,6 @@ command: /garage key info {{ item.name }} --show-secret loop: "{{ garage_s3_keys }}" register: _key_details_results - changed_when: false when: garage_s3_keys | length > 0 - name: Build garage S3 credentials map diff --git a/roles/garage/templates/docker-compose.yml.j2 b/roles/garage/templates/docker-compose.yml.j2 index 7b1c017..d344e5f 100644 --- a/roles/garage/templates/docker-compose.yml.j2 +++ b/roles/garage/templates/docker-compose.yml.j2 @@ -38,9 +38,7 @@ services: environment: API_BASE_URL: "http://{{ garage_service_name }}:{{ garage_admin_port }}" S3_ENDPOINT_URL: "http://{{ garage_service_name }}:{{ garage_s3_api_port }}" -{% if not (garage_webui_authentik_forward_auth | default(false)) %} AUTH_USER_PASS: '{{ _garage_webui_password_hash.stdout | replace("$", "$$") }}' -{% endif %} volumes: - {{ garage_docker_compose_dir }}/garage.toml:/etc/garage.toml:ro networks: @@ -62,16 +60,6 @@ services: - traefik.http.routers.{{ garage_service_name }}-console.service={{ garage_service_name }}-console - traefik.http.routers.{{ garage_service_name }}-console.priority=10 - traefik.http.services.{{ garage_service_name }}-console.loadbalancer.server.port={{ garage_webui_port }} -{% if garage_webui_authentik_forward_auth | default(false) %} - # ForwardAuth via the authentik embedded outpost. Unauthenticated - # requests are redirected to authentik; authentik then forwards - # X-Authentik-* identity headers downstream. htpasswd is disabled - # in the env block above so authentik is the only gate. - - traefik.http.middlewares.{{ garage_service_name }}-console-authentik.forwardauth.address={{ garage_webui_authentik_forward_auth_url }} - - traefik.http.middlewares.{{ garage_service_name }}-console-authentik.forwardauth.trustForwardHeader=true - - traefik.http.middlewares.{{ garage_service_name }}-console-authentik.forwardauth.authResponseHeaders=X-authentik-username,X-authentik-groups,X-authentik-entitlements,X-authentik-email,X-authentik-name,X-authentik-uid,X-authentik-jwt,X-authentik-meta-jwks,X-authentik-meta-outpost,X-authentik-meta-provider,X-authentik-meta-app,X-authentik-meta-version - - traefik.http.routers.{{ garage_service_name }}-console.middlewares={{ garage_service_name }}-console-authentik -{% endif %} {% endif %} networks: diff --git a/roles/nextcloud/tasks/collabora.yml b/roles/nextcloud/tasks/collabora.yml index d9d4f62..2a7bd82 100644 --- a/roles/nextcloud/tasks/collabora.yml +++ b/roles/nextcloud/tasks/collabora.yml @@ -1,55 +1,28 @@ #SPDX-License-Identifier: MIT-0 --- # tasks file for configuring Collabora in Nextcloud -- name: Read current richdocuments config values - community.docker.docker_container_exec: - container: "{{ nextcloud_docker_compose_dir | basename }}-nextcloud-1" - command: php /var/www/html/occ config:app:get richdocuments {{ item }} - loop: - - wopi_url - - public_wopi_url - - disable_certificate_verification - - wopi_allowlist - register: _richdocuments_current - changed_when: false - failed_when: false - -- name: Build map of current richdocuments config - ansible.builtin.set_fact: - _richdocuments_cfg: "{{ _richdocuments_cfg | default({}) | combine({item.item: (item.stdout | default('')).strip()}) }}" - loop: "{{ _richdocuments_current.results }}" - loop_control: - label: "{{ item.item }}" - - 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 }} - when: _richdocuments_cfg.wopi_url != ('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 - - nextcloud_collabora_public_domain != nextcloud_collabora_domain - - _richdocuments_cfg.public_wopi_url != ('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" command: php /var/www/html/occ config:app:set richdocuments disable_certificate_verification --value={{ nextcloud_collabora_disable_cert_verification | ternary('yes', 'no') }} - when: _richdocuments_cfg.disable_certificate_verification != (nextcloud_collabora_disable_cert_verification | ternary('yes', 'no')) - name: Set Collabora WOPI allowlist community.docker.docker_container_exec: container: "{{ nextcloud_docker_compose_dir | basename }}-nextcloud-1" command: php /var/www/html/occ config:app:set richdocuments wopi_allowlist --value='' - when: _richdocuments_cfg.wopi_allowlist | default('') != '' - name: Activate richdocuments configuration (fetch discovery from Collabora) community.docker.docker_container_exec: container: "{{ nextcloud_docker_compose_dir | basename }}-nextcloud-1" - command: php /var/www/html/occ richdocuments:activate-config - changed_when: false \ No newline at end of file + command: php /var/www/html/occ richdocuments:activate-config \ No newline at end of file diff --git a/roles/nextcloud/tasks/drawio.yml b/roles/nextcloud/tasks/drawio.yml index e693862..bd2e17e 100644 --- a/roles/nextcloud/tasks/drawio.yml +++ b/roles/nextcloud/tasks/drawio.yml @@ -2,41 +2,18 @@ --- # tasks file for configuring draw.io in Nextcloud -- name: Read current drawio config values - community.docker.docker_container_exec: - container: "{{ nextcloud_docker_compose_dir | basename }}-nextcloud-1" - command: php /var/www/html/occ config:app:get drawio {{ item }} - loop: - - DrawioUrl - - DrawioTheme - - DrawioOffline - register: _drawio_current - changed_when: false - failed_when: false - -- name: Build map of current drawio config - ansible.builtin.set_fact: - _drawio_cfg: "{{ _drawio_cfg | default({}) | combine({item.item: (item.stdout | default('')).strip()}) }}" - loop: "{{ _drawio_current.results }}" - loop_control: - label: "{{ item.item }}" - - name: Configure draw.io URL community.docker.docker_container_exec: container: "{{ nextcloud_docker_compose_dir | basename }}-nextcloud-1" command: php /var/www/html/occ config:app:set drawio DrawioUrl --value={{ nextcloud_drawio_url }} - when: - - nextcloud_drawio_url | length > 0 - - _drawio_cfg.DrawioUrl != nextcloud_drawio_url + when: nextcloud_drawio_url | length > 0 - name: Configure draw.io theme community.docker.docker_container_exec: container: "{{ nextcloud_docker_compose_dir | basename }}-nextcloud-1" command: php /var/www/html/occ config:app:set drawio DrawioTheme --value={{ nextcloud_drawio_theme }} - when: _drawio_cfg.DrawioTheme != (nextcloud_drawio_theme | string) - name: Configure draw.io offline mode community.docker.docker_container_exec: container: "{{ nextcloud_docker_compose_dir | basename }}-nextcloud-1" - command: php /var/www/html/occ config:app:set drawio DrawioOffline --value={{ nextcloud_drawio_offline }} - when: _drawio_cfg.DrawioOffline != (nextcloud_drawio_offline | string) \ No newline at end of file + command: php /var/www/html/occ config:app:set drawio DrawioOffline --value={{ nextcloud_drawio_offline }} \ No newline at end of file diff --git a/roles/nextcloud/tasks/ldap.yml b/roles/nextcloud/tasks/ldap.yml index 89618d5..dcb2392 100644 --- a/roles/nextcloud/tasks/ldap.yml +++ b/roles/nextcloud/tasks/ldap.yml @@ -15,24 +15,6 @@ command: php /var/www/html/occ ldap:create-empty-config when: "'s01' not in ldap_show_config.stdout" -- name: Read current LDAP config for s01 - community.docker.docker_container_exec: - container: "{{ nextcloud_service_name }}-nextcloud-1" - command: php /var/www/html/occ ldap:show-config s01 --output=json - register: _ldap_show_s01 - changed_when: false - failed_when: false - -- name: Parse current LDAP config - ansible.builtin.set_fact: - _ldap_current: >- - {{ - (_ldap_show_s01.stdout | from_json) if ( - (_ldap_show_s01.stdout | default('') | trim) is match('^[\\[{]') - ) else {} - }} - when: _ldap_show_s01.rc | default(1) == 0 - - name: Configure LDAP settings community.docker.docker_container_exec: container: "{{ nextcloud_service_name }}-nextcloud-1" @@ -47,7 +29,6 @@ loop_control: label: "{{ item.key }}" no_log: true - when: ((_ldap_current | default({})).get(item.key) | default(none) | string) != (item.value | string) - name: Test LDAP configuration community.docker.docker_container_exec: diff --git a/roles/nextcloud/tasks/main.yml b/roles/nextcloud/tasks/main.yml index a5c8dc9..8d2a5cd 100644 --- a/roles/nextcloud/tasks/main.yml +++ b/roles/nextcloud/tasks/main.yml @@ -49,42 +49,6 @@ project_src: "{{ nextcloud_docker_compose_dir }}" state: present -# nextcloud/server#59629: UserConfig::getValueBool() passes a non-string from -# getTypedValue() into strtolower() under PHP 8.x + OPcache, throwing a -# TypeError on every authenticated request once user_ldap is involved. Fix -# is in master (PR #59646) but no stable33 backport landed before 33.0.4. -# Apply the (string) cast in-container; idempotent via grep guard. Remove -# this block once nextcloud_image >= 33.0.4. -- name: Discover nextcloud php containers needing the UserConfig patch - ansible.builtin.shell: - cmd: >- - docker ps --filter "label=com.docker.compose.project={{ nextcloud_docker_compose_dir | basename }}" - --filter "label=com.docker.compose.service=nextcloud" - --format '{% raw %}{{.Names}}{% endraw %}' - register: _nextcloud_php_containers - changed_when: false - -- name: Check UserConfig.php patch status per container - ansible.builtin.shell: - cmd: >- - docker exec {{ item }} grep -q "strtolower((string)" /var/www/html/lib/private/Config/UserConfig.php - loop: "{{ _nextcloud_php_containers.stdout_lines }}" - register: _nextcloud_userconfig_check - changed_when: false - failed_when: false - -- name: Apply UserConfig::getValueBool string-cast workaround - ansible.builtin.shell: - cmd: >- - docker exec {{ item.item }} - sed -i 's|$b = strtolower($this->getTypedValue|$b = strtolower((string)$this->getTypedValue|' - /var/www/html/lib/private/Config/UserConfig.php - loop: "{{ _nextcloud_userconfig_check.results }}" - loop_control: - label: "{{ item.item }}" - when: - - item.rc | default(1) != 0 - - name: Wait for Nextcloud to be ready ansible.builtin.shell: cmd: docker compose exec -T nextcloud php /var/www/html/occ status --output=json diff --git a/roles/nextcloud/tasks/notify_push.yml b/roles/nextcloud/tasks/notify_push.yml index 2fba4d9..1497c68 100644 --- a/roles/nextcloud/tasks/notify_push.yml +++ b/roles/nextcloud/tasks/notify_push.yml @@ -2,16 +2,7 @@ --- # tasks file for configuring notify_push in Nextcloud -- name: Read current notify_push base endpoint - community.docker.docker_container_exec: - container: "{{ nextcloud_docker_compose_dir | basename }}-nextcloud-1" - command: php /var/www/html/occ config:app:get notify_push base_endpoint - register: _notify_push_current - changed_when: false - failed_when: false - - 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_notify_push_domain | default(nextcloud_domains[0]) }}/push - when: (_notify_push_current.stdout | default('') | trim) != ('https://' ~ (nextcloud_notify_push_domain | default(nextcloud_domains[0])) ~ '/push') \ No newline at end of file + command: php /var/www/html/occ notify_push:setup https://{{ nextcloud_notify_push_domain | default(nextcloud_domains[0]) }}/push \ No newline at end of file diff --git a/roles/nextcloud/tasks/plugins.yml b/roles/nextcloud/tasks/plugins.yml index a93e37c..2a6d8a5 100644 --- a/roles/nextcloud/tasks/plugins.yml +++ b/roles/nextcloud/tasks/plugins.yml @@ -8,9 +8,7 @@ chdir: "{{ nextcloud_docker_compose_dir }}" loop: "{{ nextcloud_apps_to_install }}" register: app_install_result - changed_when: - - "'already installed' not in app_install_result.stdout" - - "'installed' in app_install_result.stdout" + changed_when: "'installed' in app_install_result.stdout" failed_when: - app_install_result.rc != 0 - "'already installed' not in app_install_result.stdout" @@ -21,9 +19,7 @@ chdir: "{{ nextcloud_docker_compose_dir }}" loop: "{{ nextcloud_apps_to_install }}" register: app_enable_result - changed_when: - - "'already enabled' not in app_enable_result.stdout" - - "'enabled' in app_enable_result.stdout" + changed_when: "'enabled' in app_enable_result.stdout" failed_when: - app_enable_result.rc != 0 - "'already enabled' not in app_enable_result.stdout" diff --git a/roles/traefik/defaults/main.yml b/roles/traefik/defaults/main.yml index ffc237e..eea7391 100644 --- a/roles/traefik/defaults/main.yml +++ b/roles/traefik/defaults/main.yml @@ -11,13 +11,6 @@ service_name: traefik docker_compose_dir: "{{ docker_compose_base_dir }}/{{ service_name }}" docker_volume_dir: "{{ docker_volume_base_dir }}/{{ service_name }}" -# Optional /etc/hosts entries injected into the traefik container. Useful -# when downstream middlewares (e.g. ForwardAuth to an authentik instance -# running on a sibling LAN) need a public FQDN to resolve to an internal -# IP because the DMZ doesn't hairpin the public address back inside. -# Example: ["auth.example.com:172.16.19.101"] -traefik_extra_hosts: [] - # Deployment mode: 'dmz' or 'backend' # - dmz: Public-facing reverse proxy that routes to backend servers using file provider # - backend: Application server with docker provider for local container discovery diff --git a/roles/traefik/templates/docker-compose.yml.j2 b/roles/traefik/templates/docker-compose.yml.j2 index 9463e58..6dbb9ec 100644 --- a/roles/traefik/templates/docker-compose.yml.j2 +++ b/roles/traefik/templates/docker-compose.yml.j2 @@ -33,12 +33,6 @@ services: {% endif %} networks: - {{ traefik_network }} -{% if traefik_extra_hosts | default([]) | length > 0 %} - extra_hosts: -{% for h in traefik_extra_hosts %} - - "{{ h }}" -{% endfor %} -{% endif %} networks: {{ traefik_network }}: