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:
@@ -1,65 +0,0 @@
|
||||
- name: Configure Gitea reverse proxy on ru-vps
|
||||
hosts: ru-vps
|
||||
gather_facts: false
|
||||
vars:
|
||||
caddy_dir: /opt/services/ru-vps/caddy
|
||||
caddyfile_path: /opt/services/ru-vps/caddy/Caddyfile
|
||||
gitea_domain: git.ada-dev.ru
|
||||
gitea_upstream: 192.168.1.25:3000
|
||||
|
||||
tasks:
|
||||
- name: Ensure Caddy service directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ caddy_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Configure Gitea Caddy site
|
||||
ansible.builtin.blockinfile:
|
||||
path: "{{ caddyfile_path }}"
|
||||
create: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
marker: "# {mark} ANSIBLE MANAGED GITEA SITE"
|
||||
block: |
|
||||
{{ gitea_domain }} {
|
||||
reverse_proxy {{ gitea_upstream }}
|
||||
}
|
||||
register: gitea_caddy_site
|
||||
|
||||
- name: Remove legacy unmanaged Gitea Caddy site
|
||||
ansible.builtin.replace:
|
||||
path: "{{ caddyfile_path }}"
|
||||
regexp: '(?ms)^git\.ada-dev\.ru \{\n\s*reverse_proxy 10\.122\.62\.51:3002\n\}\n+'
|
||||
replace: ''
|
||||
register: gitea_legacy_site
|
||||
|
||||
- name: Validate host Caddy config
|
||||
ansible.builtin.command: caddy validate --config {{ caddyfile_path }}
|
||||
changed_when: false
|
||||
|
||||
- name: Check Caddy container mounted config
|
||||
ansible.builtin.command: docker exec caddy grep -F 'reverse_proxy {{ gitea_upstream }}' /etc/caddy/Caddyfile
|
||||
register: gitea_container_caddyfile
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Restart Caddy when config changed or bind mount is stale
|
||||
ansible.builtin.command: docker restart caddy
|
||||
when: >-
|
||||
gitea_caddy_site.changed or
|
||||
gitea_legacy_site.changed or
|
||||
gitea_container_caddyfile.rc != 0
|
||||
|
||||
- name: Validate Caddy container config after restart
|
||||
ansible.builtin.command: docker exec caddy caddy validate --config /etc/caddy/Caddyfile
|
||||
changed_when: false
|
||||
|
||||
- name: Check Gitea upstream from ru-vps
|
||||
ansible.builtin.command: curl -fsS -o /dev/null -w '%{http_code}' http://{{ gitea_upstream }}/
|
||||
register: gitea_upstream_http
|
||||
changed_when: false
|
||||
failed_when: gitea_upstream_http.stdout != '200'
|
||||
@@ -1,88 +0,0 @@
|
||||
---
|
||||
- name: Configure Grimmory reverse proxy on ru-vps
|
||||
hosts: ru-vps
|
||||
gather_facts: false
|
||||
vars:
|
||||
caddy_dir: /opt/services/ru-vps/caddy
|
||||
caddyfile_path: /opt/services/ru-vps/caddy/Caddyfile
|
||||
grimmory_domain: books.ada-dev.ru
|
||||
grimmory_upstream: 192.168.1.34:6060
|
||||
tasks:
|
||||
- name: Ensure Caddy service directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ caddy_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Configure Grimmory Caddy site
|
||||
ansible.builtin.blockinfile:
|
||||
path: "{{ caddyfile_path }}"
|
||||
create: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
marker: "# {mark} ANSIBLE MANAGED GRIMMORY SITE"
|
||||
block: |
|
||||
{{ grimmory_domain }} {
|
||||
@grimmory_opds_atom path /api/v1/opds /api/v1/opds/libraries /api/v1/opds/shelves /api/v1/opds/magic-shelves /api/v1/opds/authors /api/v1/opds/series /api/v1/opds/catalog /api/v1/opds/recent /api/v1/opds/surprise
|
||||
handle @grimmory_opds_atom {
|
||||
reverse_proxy {{ grimmory_upstream }} {
|
||||
header_up Accept "application/atom+xml"
|
||||
header_up Accept-Encoding identity
|
||||
transport http {
|
||||
compression off
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@grimmory_opds_search path /api/v1/opds/search.opds
|
||||
handle @grimmory_opds_search {
|
||||
reverse_proxy {{ grimmory_upstream }} {
|
||||
header_up Accept "application/opensearchdescription+xml"
|
||||
header_up Accept-Encoding identity
|
||||
transport http {
|
||||
compression off
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@grimmory_device_api path /api/koreader /api/koreader/* /api/v1/opds /api/v1/opds/*
|
||||
handle @grimmory_device_api {
|
||||
reverse_proxy {{ grimmory_upstream }} {
|
||||
header_up Accept-Encoding identity
|
||||
transport http {
|
||||
compression off
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handle {
|
||||
reverse_proxy {{ grimmory_upstream }}
|
||||
}
|
||||
}
|
||||
register: grimmory_caddy_site
|
||||
|
||||
- name: Validate host Caddy config
|
||||
ansible.builtin.command: caddy validate --config {{ caddyfile_path }}
|
||||
changed_when: false
|
||||
|
||||
- name: Check Caddy container mounted config
|
||||
ansible.builtin.command: docker exec caddy grep -F '@grimmory_device_api path /api/koreader' /etc/caddy/Caddyfile
|
||||
register: grimmory_container_caddyfile
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Restart Caddy when config changed or bind mount is stale
|
||||
ansible.builtin.command: docker restart caddy
|
||||
when: grimmory_caddy_site.changed or grimmory_container_caddyfile.rc != 0
|
||||
|
||||
- name: Validate Caddy container config after restart
|
||||
ansible.builtin.command: docker exec caddy caddy validate --config /etc/caddy/Caddyfile
|
||||
changed_when: false
|
||||
|
||||
- name: Check Grimmory upstream from ru-vps
|
||||
ansible.builtin.uri:
|
||||
url: http://{{ grimmory_upstream }}/api/v1/healthcheck
|
||||
status_code: 200
|
||||
@@ -1,65 +0,0 @@
|
||||
- name: Configure Vaultwarden reverse proxy on ru-vps
|
||||
hosts: ru-vps
|
||||
gather_facts: false
|
||||
vars:
|
||||
caddy_dir: /opt/services/ru-vps/caddy
|
||||
caddyfile_path: /opt/services/ru-vps/caddy/Caddyfile
|
||||
vaultwarden_domain: pass.ada-dev.ru
|
||||
vaultwarden_upstream: 192.168.1.24:80
|
||||
|
||||
tasks:
|
||||
- name: Ensure Caddy service directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ caddy_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Configure Vaultwarden Caddy site
|
||||
ansible.builtin.blockinfile:
|
||||
path: "{{ caddyfile_path }}"
|
||||
create: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
marker: "# {mark} ANSIBLE MANAGED VAULTWARDEN SITE"
|
||||
block: |
|
||||
{{ vaultwarden_domain }} {
|
||||
reverse_proxy {{ vaultwarden_upstream }}
|
||||
}
|
||||
register: vaultwarden_caddy_site
|
||||
|
||||
- name: Remove legacy unmanaged Vaultwarden Caddy site
|
||||
ansible.builtin.replace:
|
||||
path: "{{ caddyfile_path }}"
|
||||
regexp: '(?ms)^pass\.ada-dev\.ru \{\n\s*reverse_proxy 10\.122\.62\.95:10380\n\}\n+'
|
||||
replace: ''
|
||||
register: vaultwarden_legacy_site
|
||||
|
||||
- name: Validate host Caddy config
|
||||
ansible.builtin.command: caddy validate --config {{ caddyfile_path }}
|
||||
changed_when: false
|
||||
|
||||
- name: Check Caddy container mounted config
|
||||
ansible.builtin.command: docker exec caddy grep -F 'reverse_proxy {{ vaultwarden_upstream }}' /etc/caddy/Caddyfile
|
||||
register: vaultwarden_container_caddyfile
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Restart Caddy when config changed or bind mount is stale
|
||||
ansible.builtin.command: docker restart caddy
|
||||
when: >-
|
||||
vaultwarden_caddy_site.changed or
|
||||
vaultwarden_legacy_site.changed or
|
||||
vaultwarden_container_caddyfile.rc != 0
|
||||
|
||||
- name: Validate Caddy container config after restart
|
||||
ansible.builtin.command: docker exec caddy caddy validate --config /etc/caddy/Caddyfile
|
||||
changed_when: false
|
||||
|
||||
- name: Check Vaultwarden upstream from ru-vps
|
||||
ansible.builtin.command: curl -fsS -o /dev/null -w '%{http_code}' http://{{ vaultwarden_upstream }}/
|
||||
register: vaultwarden_upstream_http
|
||||
changed_when: false
|
||||
failed_when: vaultwarden_upstream_http.stdout != '200'
|
||||
@@ -0,0 +1,182 @@
|
||||
---
|
||||
# ============================================================================
|
||||
# Единый плейбук публикации сервисов через Caddy на ru-vps.
|
||||
#
|
||||
# Заменяет reverse-proxy-gitea.yml, reverse-proxy-vaultwarden.yml и
|
||||
# reverse-proxy-grimmory.yml. Источник данных — реестр homelab_services
|
||||
# (ansible/inventory/group_vars/all/services.yml): обрабатываются все сервисы,
|
||||
# у которых определён блок `proxy`.
|
||||
#
|
||||
# Отличие от трёх старых плейбуков: контейнер Caddy перезапускается ОДИН раз
|
||||
# в конце, если изменился хотя бы один сайт (раньше — по разу на плейбук).
|
||||
#
|
||||
# Запуск:
|
||||
# ansible-playbook playbooks/reverse-proxy.yml
|
||||
# ansible-playbook playbooks/reverse-proxy.yml -e 'reverse_proxy_only=[gitea]'
|
||||
# ============================================================================
|
||||
- name: Configure public reverse proxy sites on ru-vps
|
||||
hosts: ru-vps
|
||||
gather_facts: false
|
||||
vars:
|
||||
caddy_dir: "{{ homelab_reverse_proxy_dir | default('/opt/services/ru-vps/caddy') }}"
|
||||
caddyfile_path: "{{ homelab_reverse_proxy_caddyfile | default('/opt/services/ru-vps/caddy/Caddyfile') }}"
|
||||
caddy_container: "{{ homelab_reverse_proxy_container | default('caddy') }}"
|
||||
caddyfile_container_path: /etc/caddy/Caddyfile
|
||||
# Ограничить прогон подмножеством сервисов: -e 'reverse_proxy_only=[gitea]'
|
||||
reverse_proxy_only: []
|
||||
reverse_proxy_services: >-
|
||||
{{ homelab_services | dict2items
|
||||
| selectattr('value.proxy', 'defined')
|
||||
| selectattr('key', 'in', reverse_proxy_only) | list
|
||||
if reverse_proxy_only | length > 0
|
||||
else homelab_services | dict2items
|
||||
| selectattr('value.proxy', 'defined') | list }}
|
||||
|
||||
tasks:
|
||||
- name: Require at least one service with a published domain
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- reverse_proxy_services | length > 0
|
||||
fail_msg: >-
|
||||
В homelab_services нет ни одного сервиса с блоком `proxy`
|
||||
(или reverse_proxy_only отфильтровал всё).
|
||||
|
||||
# Циклы по именам, а не по записям целиком: иначе assert печатает весь
|
||||
# словарь сервиса в вывод при каждом прогоне.
|
||||
- name: Require every proxied service to declare domain and upstream
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- svc.domain | default('') | length > 0
|
||||
- svc.upstream | default('') | length > 0
|
||||
- svc.caddy_marker | default('') is search('{mark}')
|
||||
fail_msg: >-
|
||||
Сервис {{ item }}: в proxy обязаны быть domain, upstream и
|
||||
caddy_marker с плейсхолдером {mark}.
|
||||
quiet: true
|
||||
loop: "{{ reverse_proxy_services | map(attribute='key') | list }}"
|
||||
vars:
|
||||
svc: "{{ homelab_services[item].proxy }}"
|
||||
|
||||
- name: Require a custom Caddy body to point at the declared upstream
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- svc.upstream in svc.caddy_body
|
||||
fail_msg: >-
|
||||
Сервис {{ item }}: caddy_body не содержит апстрим
|
||||
{{ svc.upstream }} — реестр рассинхронизирован.
|
||||
quiet: true
|
||||
loop: >-
|
||||
{{ reverse_proxy_services
|
||||
| selectattr('value.proxy.caddy_body', 'defined')
|
||||
| map(attribute='key') | list }}
|
||||
vars:
|
||||
svc: "{{ homelab_services[item].proxy }}"
|
||||
|
||||
- name: Ensure Caddy service directory exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ caddy_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Configure Caddy sites
|
||||
ansible.builtin.blockinfile:
|
||||
path: "{{ caddyfile_path }}"
|
||||
create: true
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
marker: "{{ item.value.proxy.caddy_marker }}"
|
||||
block: |
|
||||
{{ item.value.proxy.domain }} {
|
||||
{% if item.value.proxy.caddy_body is defined %}
|
||||
{{ item.value.proxy.caddy_body | trim | indent(2, first=True) }}
|
||||
{% else %}
|
||||
reverse_proxy {{ item.value.proxy.upstream }}
|
||||
{% endif %}
|
||||
}
|
||||
loop: "{{ reverse_proxy_services }}"
|
||||
loop_control:
|
||||
label: "{{ item.key }} ({{ item.value.proxy.domain }})"
|
||||
register: caddy_sites
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# МИГРАЦИОННАЯ СЕКЦИЯ. Удаляет неуправляемые секции Caddyfile, оставшиеся
|
||||
# от прежней инсталляции на 10.122.62.0/24. Держится только ради миграции:
|
||||
# после подтверждённого прогона на ru-vps эту задачу и поля
|
||||
# proxy.caddy_legacy_regexp в services.yml можно удалить.
|
||||
# ------------------------------------------------------------------
|
||||
- name: Remove legacy unmanaged Caddy sites
|
||||
ansible.builtin.replace:
|
||||
path: "{{ caddyfile_path }}"
|
||||
regexp: "{{ item.value.proxy.caddy_legacy_regexp }}"
|
||||
replace: ''
|
||||
loop: "{{ reverse_proxy_services | selectattr('value.proxy.caddy_legacy_regexp', 'defined') | list }}"
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
register: caddy_legacy
|
||||
|
||||
- name: Validate host Caddy config
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- caddy
|
||||
- validate
|
||||
- --config
|
||||
- "{{ caddyfile_path }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Check Caddy container mounted config
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- exec
|
||||
- "{{ caddy_container }}"
|
||||
- grep
|
||||
- -F
|
||||
- >-
|
||||
{{ item.value.proxy.caddy_container_check
|
||||
| default('reverse_proxy ' ~ item.value.proxy.upstream) }}
|
||||
- "{{ caddyfile_container_path }}"
|
||||
loop: "{{ reverse_proxy_services }}"
|
||||
loop_control:
|
||||
label: "{{ item.key }}"
|
||||
register: caddy_container_checks
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Restart Caddy once when any site changed or a bind mount is stale
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- restart
|
||||
- "{{ caddy_container }}"
|
||||
when: >-
|
||||
caddy_sites.changed or
|
||||
caddy_legacy.changed or
|
||||
(caddy_container_checks.results
|
||||
| selectattr('rc', 'defined')
|
||||
| rejectattr('rc', 'equalto', 0)
|
||||
| list | length > 0)
|
||||
|
||||
- name: Validate Caddy container config after restart
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- exec
|
||||
- "{{ caddy_container }}"
|
||||
- caddy
|
||||
- validate
|
||||
- --config
|
||||
- "{{ caddyfile_container_path }}"
|
||||
changed_when: false
|
||||
|
||||
- name: Check service upstreams from ru-vps
|
||||
ansible.builtin.uri:
|
||||
url: "http://{{ item.value.proxy.upstream }}{{ item.value.proxy.health.path | default('/') }}"
|
||||
status_code: "{{ item.value.proxy.health.status_code | default([200]) }}"
|
||||
follow_redirects: "{{ item.value.proxy.health.follow_redirects | default('none') }}"
|
||||
return_content: false
|
||||
loop: "{{ reverse_proxy_services }}"
|
||||
loop_control:
|
||||
label: "{{ item.key }} -> {{ item.value.proxy.upstream }}"
|
||||
Reference in New Issue
Block a user