# compose_service Раскладывает Docker Compose стек и systemd-юнит, который им управляет. Обобщение того, что делает `playbooks/pve-grimmory.yml`; такой же по форме код продублирован в `roles/monitoring_server` и `roles/uptime_kuma` (`grep -rn "up -d --remove-orphans"` — четыре копии одного юнита). ## Что делает 1. Создаёт корневой каталог сервиса и, при необходимости, каталоги данных с нужными owner/group/mode. 2. Генерирует `.env` **один раз** под `umask 077`: статические пары из `compose_service_env_static` и случайные секреты (`openssl rand -hex`) для имён из `compose_service_env_generated`. Задача целиком под `no_log: true`; права форсируются в `0600`. Повторный прогон существующий `.env` не перетирает — иначе поменялся бы пароль работающей БД. 3. Кладёт `compose.yml` из inline-строки или из Jinja-шаблона. 4. Ставит systemd-юнит `Type=oneshot`, `RemainAfterExit=yes`, `ExecStart=docker compose up -d --remove-orphans`, `ExecStop=docker compose down`. 5. `daemon-reload` через handler + немедленный `meta: flush_handlers` (перечитать юнит надо ДО `systemctl start`, а не в конце play). 6. Валидирует конфигурацию: `docker compose config --quiet`, `no_log: true` — при ошибке вывод подставляет значения из `.env`. 7. Стартует сервис, перезапуская его только если изменился `compose.yml`, юнит или сработал внешний триггер (`compose_service_restart_triggers`). 8. Ждёт health-endpoint через `uri` с `retries`/`until`. ## Переменные Полный список — в `defaults/main.yml`. Обязательные: `compose_service_name` и ровно один из `compose_service_compose_content` / `compose_service_compose_template` (проверяется `assert` в начале роли). | Переменная | По умолчанию | Назначение | |---|---|---| | `compose_service_name` | — | имя сервиса и systemd-юнита | | `compose_service_root` | `/opt/` | корень стека | | `compose_service_compose_content` | `""` | inline `compose.yml` | | `compose_service_compose_template` | `""` | путь к Jinja-шаблону | | `compose_service_directories` | `[]` | каталоги данных | | `compose_service_env_static` | `{}` | пары для `.env` | | `compose_service_env_generated` | `[]` | имена случайных секретов | | `compose_service_after` / `_requires` | `[]` | доп. юниты в `After=`/`Requires=` | | `compose_service_restart_triggers` | `[]` | внешние причины рестарта | | `compose_service_health_url` | `""` | URL health-check (пусто — пропустить) | ## Пример использования — как выглядел бы Gitea на новых ролях `playbooks/pve-gitea.yml` сейчас 231 строка. На ролях `pve_lxc` + `lxc_docker_host` + `compose_service` содержательная часть сводится примерно к такому (сам стек Gitea пока НЕ мигрирован — это следующий этап): ```yaml --- - name: Create the Gitea LXC on cloud-pc hosts: localhost connection: local gather_facts: false vars: gitea: "{{ homelab_services.gitea }}" roles: - role: pve_lxc pve_lxc_vmid: "{{ gitea.vmid }}" pve_lxc_node: "{{ gitea.node }}" pve_lxc_hostname: "{{ gitea.hostname }}" pve_lxc_ip: "{{ gitea.ip }}/24" pve_lxc_disk: "{{ gitea.lxc.disk }}" pve_lxc_cores: "{{ gitea.lxc.cores }}" pve_lxc_memory: "{{ gitea.lxc.memory }}" pve_lxc_swap: "{{ gitea.lxc.swap }}" pve_lxc_startup: "{{ gitea.lxc.startup }}" pve_lxc_ostemplate: "{{ gitea.lxc.ostemplate }}" - name: Configure the Gitea service hosts: gitea gather_facts: true vars: gitea: "{{ homelab_services.gitea }}" roles: - role: lxc_docker_host lxc_docker_host_extra_packages: [sqlite3, rsync] lxc_docker_host_ufw_service_rules: - port: "3000" sources: ["{{ homelab_lan_cidr }}", "{{ openvpn_network_cidr }}"] - port: "2222" sources: ["{{ homelab_lan_cidr }}", "{{ openvpn_network_cidr }}"] - role: compose_service compose_service_name: gitea compose_service_root: /opt/gitea compose_service_description: Gitea Compose stack compose_service_directories: - {path: /opt/gitea/data, owner: "1000", group: "1000", mode: "0750"} compose_service_env_static: USER_UID: "1000" USER_GID: "1000" compose_service_compose_content: | services: gitea: image: {{ gitea.images[0] }} container_name: gitea environment: USER_UID: "${USER_UID}" USER_GID: "${USER_GID}" ports: - "3000:3000" - "2222:22" volumes: - /opt/gitea/data:/data restart: unless-stopped compose_service_health_url: http://127.0.0.1:3000/api/healthz compose_service_health_retries: 24 compose_service_health_delay: 5 ``` Около 30 строк `vars` вместо 231 строки процедурного кода, и все факты (vmid, узел, адрес, ресурсы, digest образа) берутся из реестра `homelab_services`, а не дублируются в плейбуке. ### Что при такой миграции меняется на живом хосте Это не чистый рефакторинг, поэтому мигрировать нужно осознанно: * `docker run` в `ExecStart=` заменяется на compose-стек — контейнер пересоздаётся, юнит `gitea.service` меняет тип на `oneshot`. * появляется `/opt/gitea/.env`, которого раньше не было; * `--pull never` и явный `docker pull` по digest заменяются на `image:` в compose — политику закрепления образов надо перенести отдельно. Поэтому перевод существующих сервисов вынесен в отдельный этап и делается по одному сервису, с бэкапом и `--check --diff` перед реальным прогоном.