--- - 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 ansible.builtin.command: "docker inspect -f '{% raw %}{{.State.Running}}{% endraw %}' {{ hermes_container_name }}" register: hermes_existing failed_when: false changed_when: false 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: hermes_existing.stdout | default('') | trim != 'true' tags: [hermes, preflight] - name: Create project directory ansible.builtin.file: path: "{{ hermes_project_dir }}" state: directory mode: "0755" tags: [hermes] # No owner here: the image's start-up hook chowns the volume to the hermes user. - name: Create data directory (owner-only) ansible.builtin.file: path: "{{ hermes_data_dir }}" state: directory mode: "{{ hermes_data_dir_mode }}" 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] # Overrides can contain secrets (the model api_key), so they are staged in a # 0600 file owned by the container user instead of travelling in a command # line (visible in `ps`), and removed again afterwards. The merge script runs # inside the container with the image's own venv (PyYAML is available there), # as the hermes user, so config.yaml stays owned by it. - name: Apply config.yaml overrides when: hermes_effective_overrides | length > 0 tags: [hermes, config] block: - name: Stage overrides file ansible.builtin.copy: content: "{{ hermes_effective_overrides | to_json }}" dest: "{{ hermes_data_dir }}/.ansible-overrides.json" owner: "{{ hermes_uid }}" group: "{{ hermes_uid }}" mode: "0600" changed_when: false # transient file; only the merge result should count as a change no_log: true - name: Merge into config.yaml ansible.builtin.command: argv: - docker - exec - -i - --user - hermes - "{{ hermes_container_name }}" - /opt/hermes/.venv/bin/python - "-" - /opt/data/.ansible-overrides.json 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" notify: Restart hermes always: - name: Remove staged overrides file ansible.builtin.file: path: "{{ hermes_data_dir }}/.ansible-overrides.json" state: absent changed_when: false - name: Publish dashboard through nginx-proxy ansible.builtin.include_role: name: nginx-proxy tasks_from: add_vhost vars: vhost_name: hermes vhost_domain: "{{ hermes_domain }}" vhost_upstream_port: "{{ hermes_dashboard_port }}" vhost_proxy_timeout: 3600s # long-lived dashboard WebSockets when: - hermes_publish_vhost | bool - hermes_dashboard_enabled | bool tags: [hermes, nginx]