Files
infra/ansible/roles/compose_service/tasks/main.yml
T
DmitryandClaude Opus 5 9725d3ea7c Add service registry, shared roles and unified reverse proxy
Collect the facts about all 14 services -- VMID, node, address, ports,
domain, pinned images, resources, backup and monitoring participation --
into group_vars/all/services.yml. Values are taken from the existing
playbooks; gaps are marked null rather than invented.

Replace reverse-proxy-{gitea,vaultwarden,grimmory}.yml with a single
playbook iterating over registry entries that declare a domain. It keeps
every check the three had, preserves grimmory's richer Caddy block
byte-for-byte, and restarts Caddy once when any site changed instead of
up to three times. Verified with --check --diff against ru-vps: ok=6
changed=0, so it reproduces the current Caddyfile exactly.

Add two roles factoring out the skeleton duplicated across the pve-*
playbooks: lxc_docker_host (packages, /dev/fuse assertion, fuse-overlayfs
storage driver, UFW baseline) and compose_service (compose file, systemd
unit, config validation, health check). They are not wired into any
playbook yet -- migrating a live service is a separate, per-service step;
compose_service/README.md shows the Gitea example and spells out what
actually changes on the host.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTocXkGUUazHdKKd3r9k71
2026-08-26 22:10:16 +03:00

174 lines
6.8 KiB
YAML

---
- name: Validate compose_service parameters
ansible.builtin.assert:
that:
- compose_service_name | length > 0
- (compose_service_compose_content | length > 0)
!= (compose_service_compose_template | length > 0)
fail_msg: >-
Задайте compose_service_name и ровно один из
compose_service_compose_content / compose_service_compose_template.
- name: Ensure the service root directory exists
ansible.builtin.file:
path: "{{ compose_service_root }}"
state: directory
owner: "{{ compose_service_owner }}"
group: "{{ compose_service_group }}"
mode: "{{ compose_service_root_mode }}"
- name: Ensure the service data directories exist
ansible.builtin.file:
path: "{{ item.path }}"
state: directory
owner: "{{ item.owner | default(compose_service_owner) }}"
group: "{{ item.group | default(compose_service_group) }}"
mode: "{{ item.mode | default('0750') }}"
loop: "{{ compose_service_directories }}"
loop_control:
label: "{{ item.path }}"
# --- Файл окружения --------------------------------------------------------
# Создаётся один раз под umask 077. Секреты никогда не попадают в вывод:
# задача целиком под no_log.
- name: Generate the environment file once
ansible.builtin.shell: |
set -eu
umask 077
if [ -e {{ compose_service_env_path | quote }} ]; then
exit 0
fi
: > {{ compose_service_env_path | quote }}
{% for key, value in compose_service_env_static.items() %}
printf '%s\n' {{ (key ~ '=' ~ value) | quote }} >> {{ compose_service_env_path | quote }}
{% endfor %}
{% for key in compose_service_env_generated %}
printf '%s=%s\n' {{ key | quote }} "$(openssl rand -hex {{ compose_service_env_secret_bytes }})" >> {{ compose_service_env_path | quote }}
{% endfor %}
printf created
args:
executable: /bin/sh
register: compose_service_env_result
changed_when: compose_service_env_result.stdout == 'created'
no_log: true
when: >-
compose_service_env_static | length > 0 or
compose_service_env_generated | length > 0
- name: Enforce the environment file permissions
ansible.builtin.file:
path: "{{ compose_service_env_path }}"
owner: "{{ compose_service_owner }}"
group: "{{ compose_service_group }}"
mode: "{{ compose_service_env_mode }}"
when: >-
compose_service_env_static | length > 0 or
compose_service_env_generated | length > 0
- name: Install the environment example
ansible.builtin.copy:
dest: "{{ compose_service_env_path }}.example"
owner: "{{ compose_service_owner }}"
group: "{{ compose_service_group }}"
mode: "{{ compose_service_env_example_mode }}"
content: "{{ compose_service_env_example }}"
when: compose_service_env_example | length > 0
# --- compose.yml -----------------------------------------------------------
- name: Install the Compose configuration from inline content
ansible.builtin.copy:
dest: "{{ compose_service_root }}/{{ compose_service_compose_file }}"
owner: "{{ compose_service_owner }}"
group: "{{ compose_service_group }}"
mode: "{{ compose_service_compose_mode }}"
content: "{{ compose_service_compose_content }}"
when: compose_service_compose_content | length > 0
register: compose_service_compose_inline
- name: Install the Compose configuration from a template
ansible.builtin.template:
src: "{{ compose_service_compose_template }}"
dest: "{{ compose_service_root }}/{{ compose_service_compose_file }}"
owner: "{{ compose_service_owner }}"
group: "{{ compose_service_group }}"
mode: "{{ compose_service_compose_mode }}"
when: compose_service_compose_template | length > 0
register: compose_service_compose_templated
- name: Record whether the Compose configuration changed
ansible.builtin.set_fact:
compose_service_compose_changed: >-
{{ (compose_service_compose_inline.changed | default(false)) or
(compose_service_compose_templated.changed | default(false)) }}
# --- systemd ---------------------------------------------------------------
- name: Install the systemd unit
ansible.builtin.copy:
dest: "{{ compose_service_unit_path }}"
owner: root
group: root
mode: "0644"
content: |
[Unit]
Description={{ compose_service_description }}
Wants=network-online.target
After=network-online.target docker.service{{ (' ' ~ compose_service_after | join(' ')) if compose_service_after else '' }}
Requires=docker.service{{ (' ' ~ compose_service_requires | join(' ')) if compose_service_requires else '' }}
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory={{ compose_service_root }}
ExecStart={{ compose_service_docker_binary }} compose -f {{ compose_service_root }}/{{ compose_service_compose_file }} up -d --remove-orphans
ExecStop={{ compose_service_docker_binary }} compose -f {{ compose_service_root }}/{{ compose_service_compose_file }} down
[Install]
WantedBy=multi-user.target
register: compose_service_unit
notify: compose_service_daemon_reload
- name: Apply the pending systemd daemon reload
ansible.builtin.meta: flush_handlers
# --- Валидация -------------------------------------------------------------
# no_log: сообщение об ошибке `docker compose config` подставляет значения
# переменных из .env.
- name: Validate the Compose configuration
ansible.builtin.command:
argv:
- "{{ compose_service_docker_binary }}"
- compose
- -f
- "{{ compose_service_root }}/{{ compose_service_compose_file }}"
- config
- --quiet
args:
chdir: "{{ compose_service_root }}"
changed_when: false
no_log: true
when: compose_service_validate
# --- Запуск ----------------------------------------------------------------
- name: Enable and start the service
ansible.builtin.systemd:
name: "{{ compose_service_name }}"
enabled: "{{ compose_service_enabled }}"
state: "{{ 'restarted' if compose_service_needs_restart else 'started' }}"
vars:
compose_service_needs_restart: >-
{{ (compose_service_compose_changed | bool) or
(compose_service_unit.changed | default(false)) or
(compose_service_restart_triggers | select | list | length > 0) }}
- name: Wait for the service health endpoint
ansible.builtin.uri:
url: "{{ compose_service_health_url }}"
status_code: "{{ compose_service_health_status }}"
follow_redirects: "{{ compose_service_health_follow_redirects }}"
return_content: false
register: compose_service_health
retries: "{{ compose_service_health_retries }}"
delay: "{{ compose_service_health_delay }}"
until: compose_service_health.status in (compose_service_health_status | map('int') | list)
when: compose_service_health_url | length > 0