Add shared nginx-proxy role; deploy Hermes in Docker; rename Paperclip
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).
This commit is contained in:
1 parent
5b48536c32
commit
881e2e24a8
37 files changed
+1683
-755
No files matched your search
@@ -1,27 +1,6 @@
|
||||
---
|
||||
- name: Install Docker SDK for Python
|
||||
apt:
|
||||
name: python3-docker
|
||||
state: present
|
||||
become: true
|
||||
|
||||
- name: Download latest docker-compose from GitHub
|
||||
shell: |
|
||||
curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
chmod +x /usr/local/bin/docker-compose
|
||||
become: true
|
||||
|
||||
- name: Verify docker-compose installation
|
||||
shell: /usr/local/bin/docker-compose --version
|
||||
register: compose_version
|
||||
become: true
|
||||
|
||||
- name: Show docker-compose version
|
||||
debug:
|
||||
msg: "{{ compose_version.stdout }}"
|
||||
|
||||
- name: Create paperclip directories
|
||||
file:
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
@@ -29,97 +8,77 @@
|
||||
group: root
|
||||
loop:
|
||||
- "{{ paperclip_base_path }}"
|
||||
- "{{ paperclip_data_dir }}"
|
||||
- "{{ paperclip_certs_dir }}"
|
||||
# The postgres image chowns its own data dir on start; do not chown it here.
|
||||
- "{{ paperclip_base_path }}/pgdata"
|
||||
become: true
|
||||
tags: [paperclip, files]
|
||||
|
||||
- name: Create OpenCode config directory
|
||||
file:
|
||||
path: "{{ paperclip_base_path }}/.config/opencode"
|
||||
# Container runs as UID 1000. Deliberately NOT recursive: recurse + mode would
|
||||
# chmod every file the app writes under these paths on each run. A fresh
|
||||
# deploy has no root-owned content to repair; if you ever need to, run a
|
||||
# one-off `chown -R 1000:1000 <dir>` by hand.
|
||||
- name: Create container-owned directories (UID 1000)
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
owner: 1000
|
||||
group: 1000
|
||||
loop:
|
||||
- "{{ paperclip_data_dir }}"
|
||||
- "{{ paperclip_base_path }}/.config/opencode"
|
||||
- "{{ paperclip_base_path }}/.local/share/opencode"
|
||||
become: true
|
||||
tags: [paperclip, files]
|
||||
|
||||
- name: Create OpenCode .local/share directory
|
||||
file:
|
||||
path: "{{ paperclip_base_path }}/.local/share/opencode"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
owner: 1000
|
||||
group: 1000
|
||||
become: yes
|
||||
|
||||
- name: Deploy OpenCode auth.json with vLLM credentials
|
||||
template:
|
||||
src: opencode-auth.json.j2
|
||||
dest: "{{ paperclip_base_path }}/.local/share/opencode/auth.json"
|
||||
mode: '0600'
|
||||
owner: 1000
|
||||
group: 1000
|
||||
become: true
|
||||
no_log: true
|
||||
|
||||
- name: Fix directory ownership for container user (UID 1000)
|
||||
shell: |
|
||||
chown -R 1000:1000 {{ paperclip_data_dir }}
|
||||
chown -R 1000:1000 {{ paperclip_base_path }}/pgdata
|
||||
chown -R 1000:1000 {{ paperclip_base_path }}/.config
|
||||
become: true
|
||||
# - name: Deploy OpenCode auth.json with vLLM credentials
|
||||
# template:
|
||||
# src: opencode-auth.json.j2
|
||||
# dest: "{{ paperclip_base_path }}/.local/share/opencode/auth.json"
|
||||
# mode: '0600'
|
||||
# owner: 1000
|
||||
# group: 1000
|
||||
# become: true
|
||||
# no_log: true
|
||||
|
||||
# This file is both the compose interpolation source and the container's
|
||||
# env_file, so everything here is visible inside the Paperclip container.
|
||||
# Infrastructure secrets (Cloudflare token, certbot email) live in
|
||||
# /opt/nginx/.env instead.
|
||||
- name: Write secrets and configuration to .env
|
||||
copy:
|
||||
ansible.builtin.copy:
|
||||
content: |
|
||||
# Authentication secrets (from vault)
|
||||
BETTER_AUTH_SECRET={{ vault_better_auth_secret }}
|
||||
PAPERCLIP_AGENT_JWT_SECRET={{ vault_paperclip_agent_jwt_secret }}
|
||||
|
||||
# Infrastructure configuration
|
||||
CLOUDFLARE_API_TOKEN={{ vault_cloudflare_api_token }}
|
||||
CERTBOT_EMAIL={{ certbot_email }}
|
||||
PAPERCLIP_DOMAIN={{ paperclip_domain }}
|
||||
VLLM_ENDPOINT={{ vllm_endpoint }}
|
||||
VLLM_API_KEY={{ vault_vllm_api_key }}
|
||||
|
||||
# Inference endpoints
|
||||
ILLUMINATIONS_ENDPOINT={{ illuminations_endpoint }}
|
||||
ILLUMINATIONS_API_KEY={{ vault_illuminations_api_key }}
|
||||
FANTASMIC_ENDPOINT={{ fantasmic_endpoint }}
|
||||
FANTASMIC_API_KEY={{ vault_fantasmic_api_key }}
|
||||
dest: "{{ paperclip_base_path }}/.env"
|
||||
mode: '0600'
|
||||
owner: root
|
||||
group: root
|
||||
become: true
|
||||
no_log: true
|
||||
notify: Restart paperclip stack
|
||||
tags: [paperclip, files]
|
||||
|
||||
# No notify: the deploy task below recreates whatever the compose file changed.
|
||||
- name: Template docker-compose.yml
|
||||
template:
|
||||
ansible.builtin.template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ paperclip_base_path }}/docker-compose.yml"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
become: true
|
||||
notify: Restart paperclip stack
|
||||
|
||||
- name: Template nginx config
|
||||
template:
|
||||
src: nginx.conf.j2
|
||||
dest: "{{ paperclip_base_path }}/nginx.conf"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
become: true
|
||||
notify: Reload nginx
|
||||
|
||||
- name: Template certbot init script
|
||||
template:
|
||||
src: certbot-init.sh.j2
|
||||
dest: "{{ paperclip_base_path }}/certbot-init.sh"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
become: true
|
||||
tags: [paperclip, files]
|
||||
|
||||
- name: Template OpenCode config
|
||||
template:
|
||||
ansible.builtin.template:
|
||||
src: opencode.jsonc.j2
|
||||
dest: "{{ paperclip_base_path }}/.config/opencode/opencode.jsonc"
|
||||
mode: '0644'
|
||||
@@ -127,43 +86,71 @@
|
||||
group: 1000
|
||||
become: true
|
||||
notify: Restart paperclip stack
|
||||
tags: [paperclip, files]
|
||||
|
||||
- name: Stop and remove old containers
|
||||
shell: |
|
||||
cd {{ paperclip_base_path }}
|
||||
/usr/local/bin/docker-compose down --remove-orphans 2>/dev/null || true
|
||||
# Idempotent: only changed services are recreated. remove_orphans also cleans
|
||||
# up the old paperclip-nginx / paperclip-certbot containers if they still exist.
|
||||
- name: Deploy paperclip stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ paperclip_base_path }}"
|
||||
pull: missing
|
||||
remove_orphans: true
|
||||
state: present
|
||||
register: paperclip_deploy
|
||||
become: true
|
||||
ignore_errors: true
|
||||
|
||||
- name: Deploy docker compose stack
|
||||
shell: |
|
||||
cd {{ paperclip_base_path }}
|
||||
/usr/local/bin/docker-compose up -d
|
||||
become: true
|
||||
register: compose_result
|
||||
tags: [paperclip, deploy]
|
||||
|
||||
- name: Wait for Paperclip to be ready
|
||||
uri:
|
||||
ansible.builtin.uri:
|
||||
url: "http://localhost:{{ paperclip_port }}/health"
|
||||
status_code: 200
|
||||
register: result
|
||||
until: result.status == 200
|
||||
register: paperclip_health
|
||||
until: paperclip_health.status == 200
|
||||
retries: 30
|
||||
delay: 5
|
||||
ignore_errors: true
|
||||
tags: [paperclip, deploy, verify]
|
||||
|
||||
- name: Wait for nginx to be ready
|
||||
uri:
|
||||
url: "https://{{ paperclip_domain }}/"
|
||||
validate_certs: no
|
||||
status_code: [200, 301, 302]
|
||||
register: result
|
||||
until: result.status in [200, 301, 302]
|
||||
retries: 30
|
||||
- name: Warn if Paperclip never became healthy
|
||||
ansible.builtin.debug:
|
||||
msg: "WARNING: Paperclip did not report healthy on port {{ paperclip_port }}. Check: docker logs paperclip"
|
||||
when: paperclip_health.status | default(0) != 200
|
||||
tags: [paperclip, verify]
|
||||
|
||||
# Requires day1_deploy_nginx_proxy.yml to have run first.
|
||||
- name: Publish Paperclip through nginx-proxy
|
||||
ansible.builtin.include_role:
|
||||
name: nginx-proxy
|
||||
tasks_from: add_vhost
|
||||
apply:
|
||||
become: true
|
||||
tags: [paperclip, nginx]
|
||||
vars:
|
||||
vhost_name: paperclip
|
||||
vhost_domain: "{{ paperclip_domain }}"
|
||||
vhost_upstream_port: "{{ paperclip_port }}"
|
||||
vhost_health_path: /health
|
||||
tags: [paperclip, nginx]
|
||||
|
||||
# --resolve keeps this independent of DNS: it tests nginx, the cert and the
|
||||
# upstream. DNS is a separate (manual) prerequisite.
|
||||
- name: Verify HTTPS through nginx
|
||||
ansible.builtin.command: >-
|
||||
curl -sk -o /dev/null -w '%{http_code}'
|
||||
--resolve {{ paperclip_domain }}:443:127.0.0.1
|
||||
https://{{ paperclip_domain }}/
|
||||
register: paperclip_https
|
||||
until: paperclip_https.stdout in ['200', '301', '302']
|
||||
retries: 6
|
||||
delay: 5
|
||||
ignore_errors: true
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
tags: [paperclip, verify]
|
||||
|
||||
- name: Verify Paperclip is accessible via https
|
||||
debug:
|
||||
msg: "Paperclip is accessible at https://{{ paperclip_domain }}"
|
||||
when: result.status is defined and result.status in [200, 301, 302]
|
||||
- name: Report HTTPS result
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ 'Paperclip is serving at https://' ~ paperclip_domain
|
||||
if paperclip_https.stdout in ['200', '301', '302']
|
||||
else 'WARNING: https://' ~ paperclip_domain ~ ' returned ' ~ (paperclip_https.stdout | default('no response')) ~ ' (502 usually means Paperclip is not up yet)' }}
|
||||
tags: [paperclip, verify]
|
||||
Reference in new issue
Block a user