nginx-proxy (new role): - Shared nginx + certbot under /opt/nginx, replacing the copies that lived inside the Paperclip compose project - Apps publish via tasks_from: add_vhost (cert marker -> issue cert -> write vhost -> nginx -t -> reload); a vhost that fails nginx -t is rolled back - Certs issued one per domain via Cloudflare DNS challenge; domains registered as marker files in cert-domains/; optional LE staging - Stock nginx.conf plus conf.d (directory mounts, not single-file mounts); catch-all server drops unknown Host/SNI - Containers renamed to nginx and certbot hermes_docker (new role): - Official Hermes image, host networking, gateway and dashboard bound to loopback and published through nginx-proxy - Dashboard login via basic-auth provider, secrets from the vault - Main model set from hermes_model (fantasmic endpoint, key from vault); config.yaml overrides are merged from a 0600 staged file that is removed afterwards, so the key never appears in a command line - Data dir owner-only (HERMES_HOME_MODE=0700) paperclip-docker: - Remove nginx, certbot and docker-compose download tasks; deploy with community.docker.docker_compose_v2 (idempotent, remove_orphans) - Stop passing the Cloudflare token and certbot email to the Paperclip container via env_file - Bind Postgres to 127.0.0.1 (host networking exposed it on all interfaces); stop chowning pgdata, which the postgres image owns - Handler recreates the paperclip service so .env changes apply - Domain m-v.wdw-labs.cloud -> cranium-command.wdw-labs.cloud Playbooks: - Add day1_deploy_nginx_proxy.yml and day1_deploy_hermes_docker.yml - deploy_paperclip_stack.yml: run nginx-proxy before paperclip-docker, CLI-based container checks (no Docker SDK dependency) - Remove deploy-hermes.yml Host vars: add nginx_proxy_certbot_email and nginx_proxy_certbot_staging (staging stays on until the switch to real certs).
109 lines
3.2 KiB
YAML
109 lines
3.2 KiB
YAML
---
|
|
- name: Preflight | Required vault variables are defined
|
|
ansible.builtin.assert:
|
|
that: "{{ item }} is defined and ({{ item }} | length) > 0"
|
|
fail_msg: "{{ item }} is not defined. Add it to your ansible vault."
|
|
quiet: true
|
|
loop: "{{ hermes_required_vault_vars }}"
|
|
tags: [hermes, preflight]
|
|
|
|
- name: Preflight | Confirm docker CLI is available
|
|
ansible.builtin.command: docker --version
|
|
changed_when: false
|
|
tags: [hermes, preflight]
|
|
|
|
- name: Preflight | Get existing container state
|
|
community.docker.docker_container_info:
|
|
name: "{{ hermes_container_name }}"
|
|
register: hermes_existing
|
|
tags: [hermes, preflight]
|
|
|
|
# Only enforce free ports when our container is not already running,
|
|
# so redeploys stay idempotent.
|
|
- name: Preflight | Ensure required ports are free
|
|
ansible.builtin.wait_for:
|
|
host: 127.0.0.1
|
|
port: "{{ item }}"
|
|
state: stopped
|
|
timeout: 3
|
|
loop: "{{ hermes_preflight_ports }}"
|
|
when: not (hermes_existing.container.State.Running | default(false))
|
|
tags: [hermes, preflight]
|
|
|
|
- name: Create project and data directories
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
state: directory
|
|
mode: "0755"
|
|
loop:
|
|
- "{{ hermes_project_dir }}"
|
|
- "{{ hermes_data_dir }}"
|
|
tags: [hermes]
|
|
|
|
# Compose reads this as root on the host; the container never sees the file.
|
|
- name: Template secrets env file
|
|
ansible.builtin.template:
|
|
src: hermes-secrets.env.j2
|
|
dest: "{{ hermes_secrets_env_file }}"
|
|
owner: root
|
|
group: root
|
|
mode: "0600"
|
|
no_log: true
|
|
tags: [hermes]
|
|
|
|
- name: Template docker compose file
|
|
ansible.builtin.template:
|
|
src: docker-compose.yml.j2
|
|
dest: "{{ hermes_project_dir }}/docker-compose.yml"
|
|
mode: "0644"
|
|
tags: [hermes]
|
|
|
|
- name: Deploy hermes
|
|
community.docker.docker_compose_v2:
|
|
project_src: "{{ hermes_project_dir }}"
|
|
pull: "{{ hermes_pull_policy }}"
|
|
state: present
|
|
tags: [hermes]
|
|
|
|
- name: Verify gateway health
|
|
ansible.builtin.uri:
|
|
url: "http://127.0.0.1:{{ hermes_gateway_port }}/health"
|
|
status_code: 200
|
|
register: hermes_health
|
|
until: hermes_health.status == 200
|
|
retries: "{{ (hermes_wait_timeout | int) // 5 }}"
|
|
delay: 5
|
|
when: hermes_api_server_enabled | bool
|
|
tags: [hermes, verify]
|
|
|
|
- name: Wait for dashboard port
|
|
ansible.builtin.wait_for:
|
|
host: "{{ hermes_dashboard_host }}"
|
|
port: "{{ hermes_dashboard_port }}"
|
|
timeout: "{{ hermes_wait_timeout }}"
|
|
when: hermes_dashboard_enabled | bool
|
|
tags: [hermes, verify]
|
|
|
|
# Runs inside the container with the image's own venv (PyYAML is available
|
|
# there) as the hermes user, so config.yaml stays owned by UID 10000.
|
|
- name: Merge config.yaml overrides
|
|
ansible.builtin.command:
|
|
argv:
|
|
- docker
|
|
- exec
|
|
- -i
|
|
- --user
|
|
- hermes
|
|
- -e
|
|
- "HERMES_CONFIG_OVERRIDES={{ hermes_config_overrides | to_json }}"
|
|
- "{{ hermes_container_name }}"
|
|
- /opt/hermes/.venv/bin/python
|
|
- "-"
|
|
stdin: "{{ lookup('ansible.builtin.file', 'merge_config.py') }}"
|
|
register: hermes_config_merge
|
|
changed_when: "'CHANGED' in hermes_config_merge.stdout and 'UNCHANGED' not in hermes_config_merge.stdout"
|
|
no_log: true
|
|
when: hermes_config_overrides | length > 0
|
|
notify: Restart hermes
|
|
tags: [hermes, config]
|