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
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
---
|
||||
- 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
|
||||
Reference in New Issue
Block a user