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,61 @@
|
||||
# multica_docker
|
||||
|
||||
Deploys [Multica](https://multica.ai/docs/self-host-quickstart) (Docker Compose self-host) and a containerised
|
||||
[Caddy](https://caddyserver.com) reverse proxy with automatic HTTPS.
|
||||
|
||||
Mirrors the official quickstart: clones the repo, creates `.env` from `.env.example` (first run only, like
|
||||
`make selfhost`), manages specific keys in it, runs `docker-compose.selfhost.yml`, waits for `/readyz`, then
|
||||
fronts it with Caddy using the documented single-origin or two-domain config.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Docker Engine + Compose v2 on the target (the role checks; it does not install Docker)
|
||||
- `ansible-galaxy collection install -r requirements.yml` (needs `community.docker >= 3.10`)
|
||||
- DNS for your hostname(s) pointing at the target
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
cp group_vars/multica/vault.yml.example group_vars/multica/vault.yml # fill in, then:
|
||||
ansible-vault encrypt group_vars/multica/vault.yml
|
||||
# edit inventory.ini + group_vars/multica/vars.yml
|
||||
ansible-playbook deploy_multica.yml --ask-vault-pass
|
||||
```
|
||||
|
||||
## Key variables
|
||||
|
||||
| Variable | Default | Notes |
|
||||
|---|---|---|
|
||||
| `multica_layout` | `single` | `single` = one hostname; `split` = app + api hostnames |
|
||||
| `multica_domain` | – | Hostname for `single` |
|
||||
| `multica_app_domain` / `multica_api_domain` | – | Hostnames for `split` |
|
||||
| `caddy_tls_mode` | `acme_http` | `acme_http`, `cloudflare_dns` (builds custom Caddy image), `internal` (Caddy CA, LAN only) |
|
||||
| `caddy_http_port` / `caddy_https_port` | `80` / `443` | Change if the host already runs nginx etc. |
|
||||
| `multica_version` / `multica_image_tag` | `main` / `latest` | Pin both to the same release tag for reproducible deploys |
|
||||
| `multica_pull_latest` | `false` | `true` pulls images every run (needed to move `latest`) |
|
||||
| `multica_backup_enabled` | `false` | `pg_dump` before changes to a running stack |
|
||||
| `multica_disable_telemetry` | `false` | Sets `DO_NOT_TRACK=true` |
|
||||
| `multica_extra_env` | `{}` | Extra `.env` keys, e.g. `ALLOW_SIGNUP: "false"` |
|
||||
|
||||
Secrets (vault): `vault_multica_jwt_secret`, `vault_multica_postgres_password`, `vault_multica_vcs_secret_key`,
|
||||
`vault_cloudflare_api_token` (cloudflare_dns only), optional `vault_multica_resend_api_key`.
|
||||
|
||||
## Behaviour worth knowing
|
||||
|
||||
- **Idempotent**: `.env` is created once; only managed keys are rewritten. Compose recreates containers only when config changes.
|
||||
- **Ports 3000/8080 stay bound to 127.0.0.1** (as upstream intends). Caddy uses `network_mode: host` to reach them.
|
||||
- **Existing install?** The role overwrites `JWT_SECRET` (logs everyone out) and `POSTGRES_PASSWORD`. Postgres only reads its
|
||||
password at first init, so for an existing database copy the *current* values from `.env` into the vault.
|
||||
- **Upgrades**: `-e multica_backup_enabled=true -e multica_pull_latest=true` (or bump `multica_version`/`multica_image_tag`).
|
||||
Migrations run automatically on backend start; the role waits for `/readyz`.
|
||||
- **Tags**: `multica_preflight`, `multica`, `caddy`, `multica_verify`.
|
||||
|
||||
## After the play: connect a computer
|
||||
|
||||
The daemon runs on the machine with your AI coding tools, not the server:
|
||||
|
||||
```bash
|
||||
multica setup self-host --server-url https://<domain> --app-url https://<domain> # single layout
|
||||
```
|
||||
|
||||
First login code (no email configured): `docker compose -f docker-compose.selfhost.yml logs backend | grep "Verification code"`
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
# ───────────────────────── Multica ─────────────────────────
|
||||
multica_install_dir: /opt/multica
|
||||
multica_repo_url: https://github.com/multica-ai/multica.git
|
||||
# Git ref for the compose file / .env.example. Pin to a release tag for reproducible deploys.
|
||||
multica_version: main
|
||||
multica_git_update: true
|
||||
# Image tag for backend + frontend (written to MULTICA_IMAGE_TAG). Keep in step with multica_version when pinning.
|
||||
multica_image_tag: latest
|
||||
multica_compose_project: multica
|
||||
# true = `docker compose pull` every run (needed to move a floating `latest` tag forward)
|
||||
multica_pull_latest: false
|
||||
# Sets DO_NOT_TRACK=true on the backend (disables first-party anonymous telemetry)
|
||||
multica_disable_telemetry: false
|
||||
|
||||
# Secrets (supply via ansible-vault). Role fails fast if empty.
|
||||
multica_jwt_secret: "{{ vault_multica_jwt_secret | default('') }}"
|
||||
multica_postgres_password: "{{ vault_multica_postgres_password | default('') }}"
|
||||
multica_vcs_secret_key: "{{ vault_multica_vcs_secret_key | default('') }}"
|
||||
# Optional: enables emailed verification codes instead of reading them from backend logs
|
||||
multica_resend_api_key: "{{ vault_multica_resend_api_key | default('') }}"
|
||||
|
||||
# Any extra .env keys, e.g. { ALLOW_SIGNUP: "false", GOOGLE_CLIENT_ID: "..." }
|
||||
multica_extra_env: {}
|
||||
|
||||
# Postgres identity used by the optional backup task (match your .env if changed)
|
||||
multica_postgres_user: multica
|
||||
multica_postgres_db: multica
|
||||
# Take a pg_dump before changing a running stack. Recommended before upgrades: -e multica_backup_enabled=true
|
||||
multica_backup_enabled: false
|
||||
multica_backup_dir: /var/backups/multica
|
||||
|
||||
# Hide secret-bearing task output
|
||||
multica_no_log: true
|
||||
|
||||
# ───────────────────────── Routing ─────────────────────────
|
||||
# single: one hostname (multica_domain)
|
||||
# split : two hostnames (multica_app_domain + multica_api_domain)
|
||||
multica_layout: single
|
||||
multica_domain: ""
|
||||
multica_app_domain: ""
|
||||
multica_api_domain: ""
|
||||
|
||||
# ───────────────────────── Caddy ─────────────────────────
|
||||
caddy_install_dir: /opt/caddy
|
||||
caddy_container_name: caddy
|
||||
# Caddy image tag. "2" tracks latest 2.x
|
||||
caddy_version: "2"
|
||||
|
||||
# acme_http – automatic HTTPS via HTTP-01/TLS-ALPN-01 (needs public 80/443)
|
||||
# cloudflare_dns – DNS-01 via Cloudflare; builds a custom Caddy image with the caddy-dns/cloudflare module
|
||||
# internal – Caddy's internal CA (LAN only; clients must trust Caddy's root cert)
|
||||
caddy_tls_mode: acme_http
|
||||
caddy_acme_email: ""
|
||||
caddy_cloudflare_api_token: "{{ vault_cloudflare_api_token | default('') }}"
|
||||
# Rebuild the custom image on every run (cloudflare_dns only)
|
||||
caddy_force_rebuild: false
|
||||
|
||||
# Caddy runs with host networking so it can reach Multica's 127.0.0.1-bound ports.
|
||||
# Change these if 80/443 are already taken on the host (e.g. nginx).
|
||||
caddy_http_port: 80
|
||||
caddy_https_port: 443
|
||||
|
||||
# Post-deploy checks through the public hostname(s). Needs DNS to resolve from the target host.
|
||||
multica_verify_public: true
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
- name: Validate Caddyfile
|
||||
community.docker.docker_container_exec:
|
||||
container: "{{ caddy_container_name }}"
|
||||
command: caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
|
||||
changed_when: false
|
||||
listen: Reload caddy
|
||||
|
||||
- name: Reload Caddy
|
||||
community.docker.docker_container_exec:
|
||||
container: "{{ caddy_container_name }}"
|
||||
command: caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile
|
||||
listen: Reload caddy
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
galaxy_info:
|
||||
role_name: multica_docker
|
||||
author: Ryan
|
||||
company: WDW Labs
|
||||
license: MIT
|
||||
description: Deploy Multica (Docker Compose self-host) behind a containerised Caddy reverse proxy
|
||||
min_ansible_version: "2.15"
|
||||
platforms:
|
||||
- name: Ubuntu
|
||||
versions: [jammy, noble]
|
||||
- name: Debian
|
||||
versions: [bookworm]
|
||||
dependencies: []
|
||||
@@ -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 }}"
|
||||
@@ -0,0 +1,73 @@
|
||||
# Managed by Ansible (multica_docker) - local changes will be overwritten
|
||||
{
|
||||
{% if caddy_acme_email | length > 0 %}
|
||||
email {{ caddy_acme_email }}
|
||||
{% endif %}
|
||||
{% if caddy_http_port | int != 80 %}
|
||||
http_port {{ caddy_http_port }}
|
||||
{% endif %}
|
||||
{% 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 <server-url>/health and needs a 200
|
||||
handle /health {
|
||||
reverse_proxy 127.0.0.1:{{ _multica_backend_port }}
|
||||
}
|
||||
|
||||
# Browser realtime WebSocket (the web image cannot proxy WS upgrades)
|
||||
handle /ws {
|
||||
reverse_proxy 127.0.0.1:{{ _multica_backend_port }} {
|
||||
flush_interval -1
|
||||
}
|
||||
}
|
||||
|
||||
# Daemon long-connection WebSocket: daemons dial {server-url}/api/daemon/ws
|
||||
handle /api/daemon/ws {
|
||||
reverse_proxy 127.0.0.1:{{ _multica_backend_port }} {
|
||||
flush_interval -1
|
||||
}
|
||||
}
|
||||
|
||||
# Everything else goes to the frontend, which forwards API and login requests
|
||||
handle {
|
||||
reverse_proxy 127.0.0.1:{{ _multica_frontend_port }}
|
||||
}
|
||||
}
|
||||
{% else %}
|
||||
{{ multica_app_domain }} {
|
||||
{% if caddy_tls_mode == 'internal' %}
|
||||
tls internal
|
||||
{% endif %}
|
||||
# Hand browser WebSockets straight to the backend
|
||||
@ws path /ws /ws/*
|
||||
handle @ws {
|
||||
reverse_proxy 127.0.0.1:{{ _multica_backend_port }} {
|
||||
flush_interval -1
|
||||
}
|
||||
}
|
||||
|
||||
# Everything else goes to the frontend, which forwards API and login requests
|
||||
handle {
|
||||
reverse_proxy 127.0.0.1:{{ _multica_frontend_port }}
|
||||
}
|
||||
}
|
||||
|
||||
{{ multica_api_domain }} {
|
||||
{% if caddy_tls_mode == 'internal' %}
|
||||
tls internal
|
||||
{% endif %}
|
||||
reverse_proxy 127.0.0.1:{{ _multica_backend_port }} {
|
||||
flush_interval -1
|
||||
}
|
||||
}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,6 @@
|
||||
# Managed by Ansible (multica_docker)
|
||||
FROM caddy:{{ caddy_version }}-builder AS builder
|
||||
RUN xcaddy build --with github.com/caddy-dns/cloudflare
|
||||
|
||||
FROM caddy:{{ caddy_version }}
|
||||
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
|
||||
@@ -0,0 +1 @@
|
||||
CLOUDFLARE_API_TOKEN={{ caddy_cloudflare_api_token }}
|
||||
@@ -0,0 +1,26 @@
|
||||
# Managed by Ansible (multica_docker)
|
||||
name: caddy
|
||||
services:
|
||||
caddy:
|
||||
container_name: {{ caddy_container_name }}
|
||||
{% if caddy_tls_mode == 'cloudflare_dns' %}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image: local/caddy-cloudflare:{{ caddy_version }}
|
||||
env_file:
|
||||
- caddy.env
|
||||
{% else %}
|
||||
image: caddy:{{ caddy_version }}
|
||||
{% endif %}
|
||||
# Host networking: Multica's ports are bound to 127.0.0.1 only
|
||||
network_mode: host
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- {{ caddy_install_dir }}/conf:/etc/caddy:ro
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
# Fixed by Multica's docker-compose.selfhost.yml (bound to 127.0.0.1)
|
||||
_multica_frontend_port: 3000
|
||||
_multica_backend_port: 8080
|
||||
_multica_compose_file: docker-compose.selfhost.yml
|
||||
|
||||
_multica_app_host: "{{ multica_domain if multica_layout == 'single' else multica_app_domain }}"
|
||||
_multica_api_host: "{{ multica_domain if multica_layout == 'single' else multica_api_domain }}"
|
||||
_multica_port_suffix: "{{ '' if caddy_https_port | int == 443 else ':' ~ caddy_https_port }}"
|
||||
_multica_app_url: "https://{{ _multica_app_host }}{{ _multica_port_suffix }}"
|
||||
_multica_api_url: "https://{{ _multica_api_host }}{{ _multica_port_suffix }}"
|
||||
|
||||
_multica_env_core:
|
||||
JWT_SECRET: "{{ multica_jwt_secret }}"
|
||||
POSTGRES_PASSWORD: "{{ multica_postgres_password }}"
|
||||
MULTICA_VCS_SECRET_KEY: "{{ multica_vcs_secret_key }}"
|
||||
MULTICA_IMAGE_TAG: "{{ multica_image_tag }}"
|
||||
FRONTEND_ORIGIN: "{{ _multica_app_url }}"
|
||||
MULTICA_APP_URL: "{{ _multica_app_url }}"
|
||||
|
||||
# Split layout only: daemons/CLI talk to the API hostname
|
||||
_multica_env_split: "{{ {'MULTICA_PUBLIC_URL': _multica_api_url} if multica_layout == 'split' else {} }}"
|
||||
_multica_env_telemetry: "{{ {'DO_NOT_TRACK': 'true'} if multica_disable_telemetry | bool else {} }}"
|
||||
_multica_env_resend: "{{ {'RESEND_API_KEY': multica_resend_api_key} if (multica_resend_api_key | length) > 0 else {} }}"
|
||||
|
||||
_multica_env: >-
|
||||
{{ _multica_env_core
|
||||
| combine(_multica_env_split, _multica_env_telemetry, _multica_env_resend, multica_extra_env) }}
|
||||
|
||||
# Health URL used by post-deploy verification
|
||||
_multica_public_health_url: >-
|
||||
{{ _multica_app_url ~ '/health' if multica_layout == 'single' else _multica_api_url ~ '/readyz' }}
|
||||
Reference in new issue
Block a user