multica deployment
This commit is contained in:
1 parent
63c5f7eed7
commit
39e2b87941
26 files changed
+2746
-394
No files matched your search
@@ -0,0 +1,51 @@
|
||||
---
|
||||
- name: Create Caddy directories
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ caddy_install_dir }}"
|
||||
- "{{ caddy_install_dir }}/conf"
|
||||
|
||||
# Mount the conf *directory* (not the file): atomic template writes swap the inode,
|
||||
# which a single-file bind mount would not see.
|
||||
- name: Render Caddyfile
|
||||
ansible.builtin.template:
|
||||
src: Caddyfile.j2
|
||||
dest: "{{ caddy_install_dir }}/conf/Caddyfile"
|
||||
mode: "0644"
|
||||
notify: Reload caddy
|
||||
|
||||
- name: Render Dockerfile for Cloudflare DNS module
|
||||
ansible.builtin.template:
|
||||
src: Dockerfile.caddy.j2
|
||||
dest: "{{ caddy_install_dir }}/Dockerfile"
|
||||
mode: "0644"
|
||||
when: caddy_tls_mode == 'cloudflare_dns'
|
||||
|
||||
- name: Render Caddy environment file (Cloudflare token)
|
||||
ansible.builtin.template:
|
||||
src: caddy.env.j2
|
||||
dest: "{{ caddy_install_dir }}/caddy.env"
|
||||
mode: "0600"
|
||||
when: caddy_tls_mode == 'cloudflare_dns'
|
||||
no_log: "{{ multica_no_log }}"
|
||||
|
||||
- name: Render Caddy compose file
|
||||
ansible.builtin.template:
|
||||
src: docker-compose.caddy.yml.j2
|
||||
dest: "{{ caddy_install_dir }}/docker-compose.yml"
|
||||
mode: "0644"
|
||||
|
||||
- name: Start / update Caddy
|
||||
community.docker.docker_compose_v2:
|
||||
project_name: caddy
|
||||
project_src: "{{ caddy_install_dir }}"
|
||||
state: present
|
||||
build: "{{ 'always' if caddy_force_rebuild | bool else 'policy' }}"
|
||||
recreate: auto
|
||||
register: _caddy_up
|
||||
|
||||
- name: Apply pending Caddy reloads now
|
||||
ansible.builtin.meta: flush_handlers
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
- name: Pre-flight checks
|
||||
ansible.builtin.import_tasks: preflight.yml
|
||||
tags: [multica_preflight]
|
||||
|
||||
- name: Deploy Multica
|
||||
ansible.builtin.import_tasks: multica.yml
|
||||
tags: [multica]
|
||||
|
||||
- name: Deploy Caddy
|
||||
ansible.builtin.import_tasks: caddy.yml
|
||||
tags: [caddy]
|
||||
|
||||
- name: Verify deployment
|
||||
ansible.builtin.import_tasks: verify.yml
|
||||
tags: [multica_verify]
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
- name: Create Multica install directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ multica_install_dir }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Clone / update Multica repository
|
||||
ansible.builtin.git:
|
||||
repo: "{{ multica_repo_url }}"
|
||||
dest: "{{ multica_install_dir }}"
|
||||
version: "{{ multica_version }}"
|
||||
update: "{{ multica_git_update }}"
|
||||
force: false
|
||||
register: _multica_git
|
||||
|
||||
# Optional: dump Postgres before touching a stack that is already running
|
||||
- name: Back up Postgres before changes
|
||||
when:
|
||||
- multica_backup_enabled | bool
|
||||
- _multica_compose_stat.stat.exists
|
||||
- (_multica_ps.stdout | default('') | length) > 0
|
||||
block:
|
||||
- name: Create backup directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ multica_backup_dir }}"
|
||||
state: directory
|
||||
mode: "0700"
|
||||
|
||||
- name: Gather date facts
|
||||
ansible.builtin.setup:
|
||||
gather_subset: [date_time]
|
||||
|
||||
- name: Dump database (redirect to file first so pg_dump's exit status counts)
|
||||
ansible.builtin.shell: >-
|
||||
docker compose -p {{ multica_compose_project }} -f {{ _multica_compose_file }}
|
||||
exec -T postgres pg_dump -U {{ multica_postgres_user }} {{ multica_postgres_db }}
|
||||
> {{ multica_backup_dir }}/multica-{{ ansible_date_time.iso8601_basic_short }}.sql
|
||||
&& gzip {{ multica_backup_dir }}/multica-{{ ansible_date_time.iso8601_basic_short }}.sql
|
||||
args:
|
||||
chdir: "{{ multica_install_dir }}"
|
||||
changed_when: true
|
||||
|
||||
# `make selfhost` only creates .env when missing; we do the same, then manage specific keys.
|
||||
- name: Create .env from .env.example (first run only)
|
||||
ansible.builtin.copy:
|
||||
src: "{{ multica_install_dir }}/.env.example"
|
||||
dest: "{{ multica_install_dir }}/.env"
|
||||
remote_src: true
|
||||
force: false
|
||||
mode: "0600"
|
||||
|
||||
- name: Set managed keys in .env
|
||||
ansible.builtin.lineinfile:
|
||||
path: "{{ multica_install_dir }}/.env"
|
||||
regexp: "^{{ item.key }}="
|
||||
line: "{{ item.key }}={{ item.value }}"
|
||||
mode: "0600"
|
||||
loop: "{{ _multica_env | dict2items }}"
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
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:
|
||||
project_name: "{{ multica_compose_project }}"
|
||||
project_src: "{{ multica_install_dir }}"
|
||||
files:
|
||||
- "{{ _multica_compose_file }}"
|
||||
state: present
|
||||
pull: "{{ 'always' if multica_pull_latest | bool else 'missing' }}"
|
||||
recreate: auto
|
||||
register: _multica_up
|
||||
|
||||
- name: Wait for backend /readyz (db + migrations ok)
|
||||
ansible.builtin.uri:
|
||||
url: "http://127.0.0.1:{{ _multica_backend_port }}/readyz"
|
||||
return_content: true
|
||||
status_code: 200
|
||||
register: _multica_ready
|
||||
until: >-
|
||||
_multica_ready.status == 200 and
|
||||
(_multica_ready.json.checks.db | default('')) == 'ok' and
|
||||
(_multica_ready.json.checks.migrations | default('')) == 'ok'
|
||||
retries: 30
|
||||
delay: 5
|
||||
changed_when: false
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
- name: Assert layout and hostnames
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- multica_layout in ['single', 'split']
|
||||
- caddy_tls_mode in ['acme_http', 'cloudflare_dns', 'internal']
|
||||
- multica_layout == 'split' or (multica_domain | length) > 0
|
||||
- multica_layout == 'single' or ((multica_app_domain | length) > 0 and (multica_api_domain | length) > 0)
|
||||
fail_msg: >-
|
||||
Set multica_domain (layout=single) or multica_app_domain + multica_api_domain (layout=split),
|
||||
and use a valid caddy_tls_mode.
|
||||
|
||||
- name: Assert required secrets are provided
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (multica_jwt_secret | length) >= 32
|
||||
- (multica_postgres_password | length) >= 16
|
||||
- (multica_vcs_secret_key | length) >= 16
|
||||
- caddy_tls_mode != 'cloudflare_dns' or (caddy_cloudflare_api_token | length) > 0
|
||||
fail_msg: >-
|
||||
Missing secrets. Define vault_multica_jwt_secret (>=32 chars), vault_multica_postgres_password (>=16),
|
||||
vault_multica_vcs_secret_key (>=16) and, for cloudflare_dns, vault_cloudflare_api_token.
|
||||
Generate with: openssl rand -hex 32
|
||||
quiet: true
|
||||
|
||||
- name: Check Docker Compose v2 is available
|
||||
ansible.builtin.command: docker compose version
|
||||
register: _multica_compose_ver
|
||||
changed_when: false
|
||||
|
||||
- name: Ensure git is installed
|
||||
ansible.builtin.package:
|
||||
name: git
|
||||
state: present
|
||||
|
||||
# ── Port checks (skipped when our own stack already holds the ports) ──
|
||||
- name: Look for an existing Multica deployment
|
||||
ansible.builtin.stat:
|
||||
path: "{{ multica_install_dir }}/{{ _multica_compose_file }}"
|
||||
register: _multica_compose_stat
|
||||
|
||||
- name: List running Multica containers
|
||||
ansible.builtin.command:
|
||||
cmd: "docker compose -p {{ multica_compose_project }} -f {{ _multica_compose_file }} ps -q"
|
||||
chdir: "{{ multica_install_dir }}"
|
||||
register: _multica_ps
|
||||
changed_when: false
|
||||
when: _multica_compose_stat.stat.exists
|
||||
|
||||
- name: Check for a running Caddy container
|
||||
ansible.builtin.command: "docker ps -q --filter name=^{{ caddy_container_name }}$"
|
||||
register: _caddy_ps
|
||||
changed_when: false
|
||||
|
||||
- name: Ensure Multica ports are free on first deploy
|
||||
when: not (_multica_compose_stat.stat.exists and ((_multica_ps.stdout | default('')) | length) > 0)
|
||||
block:
|
||||
- name: Wait for 3000/8080 to be unused
|
||||
ansible.builtin.wait_for:
|
||||
host: 127.0.0.1
|
||||
port: "{{ item }}"
|
||||
state: stopped
|
||||
timeout: 3
|
||||
loop:
|
||||
- "{{ _multica_frontend_port }}"
|
||||
- "{{ _multica_backend_port }}"
|
||||
rescue:
|
||||
- name: Fail with hint
|
||||
ansible.builtin.fail:
|
||||
msg: "Port 3000 or 8080 is already in use on the target host and Multica is not running. Free it before deploying."
|
||||
|
||||
- name: Ensure Caddy ports are free on first deploy
|
||||
when: (_caddy_ps.stdout | length) == 0
|
||||
block:
|
||||
- name: Wait for Caddy HTTP/HTTPS ports to be unused
|
||||
ansible.builtin.wait_for:
|
||||
host: 127.0.0.1
|
||||
port: "{{ item }}"
|
||||
state: stopped
|
||||
timeout: 3
|
||||
loop: "{{ [caddy_http_port, caddy_https_port] | map('int') | list }}"
|
||||
rescue:
|
||||
- name: Fail with hint
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
Port {{ caddy_http_port }} or {{ caddy_https_port }} is already in use on the target host
|
||||
(another reverse proxy such as nginx?). Set caddy_http_port / caddy_https_port to free ports,
|
||||
or deploy Caddy on a different host.
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
- name: Verify through the public hostname(s)
|
||||
when: multica_verify_public | bool
|
||||
block:
|
||||
- name: Check health endpoint via Caddy
|
||||
ansible.builtin.uri:
|
||||
url: "{{ _multica_public_health_url }}"
|
||||
status_code: 200
|
||||
validate_certs: "{{ caddy_tls_mode != 'internal' }}"
|
||||
register: _multica_pub_health
|
||||
until: _multica_pub_health.status == 200
|
||||
retries: 18
|
||||
delay: 10
|
||||
changed_when: false
|
||||
|
||||
- name: Fetch /api/config via the app hostname
|
||||
ansible.builtin.uri:
|
||||
url: "{{ _multica_app_url }}/api/config"
|
||||
return_content: true
|
||||
validate_certs: "{{ caddy_tls_mode != 'internal' }}"
|
||||
register: _multica_cfg
|
||||
changed_when: false
|
||||
|
||||
- name: Assert daemons will be told the public URL, not localhost
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- _multica_cfg.json.daemon_server_url is defined
|
||||
- "'localhost' not in _multica_cfg.json.daemon_server_url"
|
||||
fail_msg: >-
|
||||
daemon_server_url is missing or points at localhost. Check FRONTEND_ORIGIN / MULTICA_APP_URL in
|
||||
{{ multica_install_dir }}/.env and recreate the stack.
|
||||
success_msg: "daemon_server_url = {{ _multica_cfg.json.daemon_server_url }}"
|
||||
Reference in new issue
Block a user