From b1ce7612913faf380a6d28d4ac5a54c218b55d6d Mon Sep 17 00:00:00 2001 From: Ryan Blundon Date: Tue, 20 Oct 2026 14:23:27 -0500 Subject: [PATCH] updated multica deployment --- ansible/group_vars/multica/vars.yml | 2 +- .../roles/multica_docker/defaults/main.yml | 5 ++ .../roles/multica_docker/tasks/multica.yml | 59 +++++++++++++++++++ .../multica_docker/templates/Caddyfile.j2 | 30 +++++----- 4 files changed, 80 insertions(+), 16 deletions(-) diff --git a/ansible/group_vars/multica/vars.yml b/ansible/group_vars/multica/vars.yml index 8bd3216..a064233 100644 --- a/ansible/group_vars/multica/vars.yml +++ b/ansible/group_vars/multica/vars.yml @@ -1,7 +1,7 @@ --- # Single-origin layout: one hostname, Caddy routes /health, /ws, /api/daemon/ws to the backend multica_layout: single -multica_domain: muppet-vision.wdw-labs.cloud +multica_domain: multica.wdw-labs.cloud # TLS: acme_http | cloudflare_dns | internal caddy_tls_mode: cloudflare_dns diff --git a/ansible/roles/multica_docker/defaults/main.yml b/ansible/roles/multica_docker/defaults/main.yml index 06fe5b3..ea4cf40 100644 --- a/ansible/roles/multica_docker/defaults/main.yml +++ b/ansible/roles/multica_docker/defaults/main.yml @@ -53,6 +53,11 @@ caddy_version: "2" caddy_tls_mode: acme_http caddy_acme_email: "" caddy_cloudflare_api_token: "{{ vault_cloudflare_api_token | default('') }}" +# cloudflare_dns only: resolvers Caddy uses for the DNS-01 propagation check. Needed with split-horizon/local DNS, +# where the host resolver returns internal nameserver names that can't be resolved. [] = use the host resolver. +caddy_dns_resolvers: + - 1.1.1.1 + - 1.0.0.1 # Rebuild the custom image on every run (cloudflare_dns only) caddy_force_rebuild: false diff --git a/ansible/roles/multica_docker/tasks/multica.yml b/ansible/roles/multica_docker/tasks/multica.yml index f04cf1e..be09c02 100644 --- a/ansible/roles/multica_docker/tasks/multica.yml +++ b/ansible/roles/multica_docker/tasks/multica.yml @@ -61,6 +61,16 @@ label: "{{ item.key }}" no_log: "{{ multica_no_log }}" +# .env.example ships a DATABASE_URL with the default password embedded. POSTGRES_PASSWORD alone does not +# change it, so keep the URL's password in step (no-op if the line is absent or already matches). +- name: Sync password inside DATABASE_URL + ansible.builtin.replace: + path: "{{ multica_install_dir }}/.env" + regexp: '^(DATABASE_URL=[a-z]+://[^:/@]+:)[^@]*(@.*)$' + replace: '\g<1>{{ multica_postgres_password }}\g<2>' + mode: "0600" + no_log: "{{ multica_no_log }}" + # Compose re-reads .env and recreates containers whose config changed (equivalent of `up -d`). - name: Start / update Multica stack community.docker.docker_compose_v2: @@ -73,6 +83,55 @@ recreate: auto register: _multica_up +# Postgres only reads POSTGRES_PASSWORD when the data volume is first initialised. If the volume pre-dates this +# run (or the vault value changed), the DB user keeps the old password and the backend can't log in. +# Test over TCP (the local socket is trust-auth and proves nothing) and repair via the socket if needed. +- name: Wait for Postgres to accept connections + ansible.builtin.command: + argv: ["docker", "compose", "-p", "{{ multica_compose_project }}", "-f", "{{ _multica_compose_file }}", + "exec", "-T", "postgres", "pg_isready", "-U", "{{ multica_postgres_user }}", "-d", "{{ multica_postgres_db }}"] + chdir: "{{ multica_install_dir }}" + register: _multica_pg_ready + until: _multica_pg_ready.rc == 0 + retries: 24 + delay: 5 + changed_when: false + +- name: Test database login with the configured password (over TCP) + ansible.builtin.command: + argv: ["docker", "compose", "-p", "{{ multica_compose_project }}", "-f", "{{ _multica_compose_file }}", + "exec", "-T", "-e", "PGPASSWORD", "postgres", "sh", "-c", + "psql -h \"$(hostname -i | cut -d' ' -f1)\" -U {{ multica_postgres_user }} -d {{ multica_postgres_db }} -tAc 'select 1'"] + chdir: "{{ multica_install_dir }}" + environment: + PGPASSWORD: "{{ multica_postgres_password }}" + register: _multica_pg_auth + changed_when: false + failed_when: + - _multica_pg_auth.rc != 0 + - "'password authentication failed' not in _multica_pg_auth.stderr" + no_log: "{{ multica_no_log }}" + +- name: Reset database user password to match .env + when: _multica_pg_auth.rc != 0 + block: + - name: ALTER USER via local socket (password passed on stdin) + ansible.builtin.command: + argv: ["docker", "compose", "-p", "{{ multica_compose_project }}", "-f", "{{ _multica_compose_file }}", + "exec", "-T", "postgres", "psql", "-v", "ON_ERROR_STOP=1", + "-U", "{{ multica_postgres_user }}", "-d", "{{ multica_postgres_db }}"] + chdir: "{{ multica_install_dir }}" + stdin: "ALTER USER {{ multica_postgres_user }} PASSWORD '{{ multica_postgres_password }}';" + changed_when: true + no_log: "{{ multica_no_log }}" + + - name: Restart backend so it reconnects + ansible.builtin.command: + argv: ["docker", "compose", "-p", "{{ multica_compose_project }}", "-f", "{{ _multica_compose_file }}", + "restart", "backend"] + chdir: "{{ multica_install_dir }}" + changed_when: true + - name: Wait for backend /readyz (db + migrations ok) ansible.builtin.uri: url: "http://127.0.0.1:{{ _multica_backend_port }}/readyz" diff --git a/ansible/roles/multica_docker/templates/Caddyfile.j2 b/ansible/roles/multica_docker/templates/Caddyfile.j2 index 4734b45..dfc0039 100644 --- a/ansible/roles/multica_docker/templates/Caddyfile.j2 +++ b/ansible/roles/multica_docker/templates/Caddyfile.j2 @@ -1,4 +1,16 @@ # Managed by Ansible (multica_docker) - local changes will be overwritten +{% macro tls_block() %} +{% if caddy_tls_mode == 'internal' %} + tls internal +{% elif caddy_tls_mode == 'cloudflare_dns' %} + tls { + dns cloudflare {env.CLOUDFLARE_API_TOKEN} +{% if caddy_dns_resolvers | length > 0 %} + resolvers {{ caddy_dns_resolvers | join(' ') }} +{% endif %} + } +{% endif %} +{% endmacro %} { {% if caddy_acme_email | length > 0 %} email {{ caddy_acme_email }} @@ -9,17 +21,11 @@ {% if caddy_https_port | int != 443 %} https_port {{ caddy_https_port }} {% endif %} -{% if caddy_tls_mode == 'cloudflare_dns' %} - acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN} -{% endif %} } {% if multica_layout == 'single' %} {{ multica_domain }} { -{% if caddy_tls_mode == 'internal' %} - tls internal -{% endif %} - # CLI reachability probe: `multica setup` GETs /health and needs a 200 +{{ tls_block() }} # CLI reachability probe: `multica setup` GETs /health and needs a 200 handle /health { reverse_proxy 127.0.0.1:{{ _multica_backend_port }} } @@ -45,10 +51,7 @@ } {% else %} {{ multica_app_domain }} { -{% if caddy_tls_mode == 'internal' %} - tls internal -{% endif %} - # Hand browser WebSockets straight to the backend +{{ tls_block() }} # Hand browser WebSockets straight to the backend @ws path /ws /ws/* handle @ws { reverse_proxy 127.0.0.1:{{ _multica_backend_port }} { @@ -63,10 +66,7 @@ } {{ multica_api_domain }} { -{% if caddy_tls_mode == 'internal' %} - tls internal -{% endif %} - reverse_proxy 127.0.0.1:{{ _multica_backend_port }} { +{{ tls_block() }} reverse_proxy 127.0.0.1:{{ _multica_backend_port }} { flush_interval -1 } }