feat: add gated Compose deploy and make inventory the host source of truth
Keep sanitized Compose sources in-repo with a confirmation-gated Ansible playbook, add repo-wide validation, tighten runbook ownership/STOP/review metadata, and archive stale research docs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,3 +11,8 @@ host_key_checking = True
|
||||
become = True
|
||||
become_method = sudo
|
||||
become_ask_pass = False
|
||||
|
||||
[ssh_connection]
|
||||
# Keep SSH control sockets inside the repo (gitignored .ansible/) so playbook
|
||||
# runs work in sandboxed/CI environments without touching ~/.ansible.
|
||||
ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s -o ControlPath=.ansible/ssh-control/%h-%p-%r
|
||||
|
||||
@@ -15,6 +15,7 @@ all:
|
||||
mx2:
|
||||
ansible_host: mx2.windy.me
|
||||
ansible_host_ipv4: 194.163.160.244
|
||||
display_name: mx2.windy.me
|
||||
service_role: mailcow
|
||||
compose_project_dir: /opt/mail
|
||||
healthcheck_profiles: [mailcow]
|
||||
@@ -24,8 +25,11 @@ all:
|
||||
us2:
|
||||
ansible_host: us2.wsvc.info
|
||||
ansible_host_ipv4: 193.9.44.165
|
||||
display_name: us2.wsvc.info
|
||||
service_role: vaultwarden
|
||||
compose_project_dir: /opt/vaultwarden
|
||||
compose_repo_project: vaultwarden
|
||||
compose_remote_file: docker-compose.yml
|
||||
healthcheck_profiles: [vaultwarden]
|
||||
restic_backup_profile: vaultwarden
|
||||
service_reconcile_services:
|
||||
@@ -35,8 +39,11 @@ all:
|
||||
hk2:
|
||||
ansible_host: hk2.chans.xyz
|
||||
ansible_host_ipv4: 154.36.174.161
|
||||
display_name: hk2.chans.xyz
|
||||
service_role: powerdns
|
||||
compose_project_dir: /opt/pdns
|
||||
compose_repo_project: pdns
|
||||
compose_remote_file: compose.yml
|
||||
healthcheck_profiles: [pdns, rustdesk, hk2aux]
|
||||
restic_backup_profile: pdns
|
||||
service_reconcile_services:
|
||||
@@ -54,6 +61,7 @@ all:
|
||||
us4:
|
||||
ansible_host: us4.wsvc.info
|
||||
ansible_host_ipv4: 185.201.226.122
|
||||
display_name: us4.wsvc.info
|
||||
service_role: wireguard
|
||||
compose_project_dir: /opt/wireguard
|
||||
healthcheck_profiles: [wireguard]
|
||||
@@ -65,6 +73,7 @@ all:
|
||||
dns_windy_lan:
|
||||
ansible_host: 192.168.66.36
|
||||
ansible_host_ipv4: 192.168.66.36
|
||||
display_name: dns.windy.lan
|
||||
service_role: adguardhome
|
||||
compose_project_dir: /opt/adguardhome
|
||||
healthcheck_profiles: [adguardhome]
|
||||
@@ -94,6 +103,7 @@ all:
|
||||
ubnt:
|
||||
ansible_host: 192.168.66.46
|
||||
ansible_host_ipv4: 192.168.66.46
|
||||
display_name: ubnt
|
||||
vars:
|
||||
service_role: unifi
|
||||
compose_project_dir: /home/windy/unifi-9
|
||||
@@ -114,6 +124,7 @@ all:
|
||||
matrix_vps:
|
||||
ansible_host: 169.58.86.13
|
||||
ansible_host_ipv4: 169.58.86.13
|
||||
display_name: synapse.chans.xyz
|
||||
service_role: matrix_k3s
|
||||
matrix_server_name: chans.xyz
|
||||
matrix_synapse_host: synapse.chans.xyz
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
# Deploy repo-owned Compose declarations (compose/<project>/compose.yml) to
|
||||
# inventory hosts. Non-secret source; server-local .env provides the values.
|
||||
# Gated: apply requires compose_deploy_confirm=true; --check is a read-only
|
||||
# diff + validation. See runbooks/ansible-operations.md.
|
||||
- name: Deploy repo-owned Compose declarations
|
||||
hosts: docker_hosts
|
||||
become: true
|
||||
gather_facts: false
|
||||
serial: 1
|
||||
roles:
|
||||
- role: compose_deploy
|
||||
tags: [compose, deploy, mutating]
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
# Allowlist of compose/ projects this playbook may deploy. A host may only
|
||||
# reference a project listed here (see tasks: "Require a repo compose project").
|
||||
compose_repo_projects:
|
||||
- vaultwarden
|
||||
- pdns
|
||||
- adguardhome
|
||||
- unifi
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
# Deploy the repo-owned, sanitized Compose declaration to the host.
|
||||
#
|
||||
# Safety model:
|
||||
# - Only hosts with an inventory `compose_repo_project` (allowlisted) are valid.
|
||||
# - The repo file is staged to `<file>.dsh-new` and validated with
|
||||
# `docker compose config --quiet` against the server-local .env BEFORE it
|
||||
# replaces anything. A failed validation never touches the live file.
|
||||
# - The current file is kept as `*.bak-<timestamp>` before promotion.
|
||||
# - Apply mode requires `compose_deploy_confirm=true`; `--check` gives a
|
||||
# read-only diff + validation without writes.
|
||||
# - The playbook never writes, reads, or transfers the server .env.
|
||||
|
||||
- name: Require an allowlisted repo compose project for this host
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- compose_repo_project is defined
|
||||
- compose_repo_project in compose_repo_projects
|
||||
fail_msg: >-
|
||||
No allowlisted compose_repo_project for {{ inventory_hostname }}.
|
||||
Supported: {{ compose_repo_projects | join(', ') }}.
|
||||
|
||||
- name: Require explicit confirmation for apply mode
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_check_mode or (compose_deploy_confirm | bool)
|
||||
fail_msg: >-
|
||||
This playbook replaces the server compose file and may recreate
|
||||
containers. Run with --check for a read-only diff, or supply
|
||||
compose_deploy_confirm=true to apply.
|
||||
|
||||
- name: Stage the repo compose file next to the live one
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../../compose/{{ compose_repo_project }}/compose.yml"
|
||||
dest: "{{ compose_project_dir }}/{{ compose_remote_file | default('compose.yml') }}.dsh-new"
|
||||
mode: "0644"
|
||||
diff: true
|
||||
register: compose_stage
|
||||
|
||||
- name: Validate staged compose against the server .env (read-only)
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- compose
|
||||
- -f
|
||||
- "{{ compose_project_dir }}/{{ compose_remote_file | default('compose.yml') }}.dsh-new"
|
||||
- --project-directory
|
||||
- "{{ compose_project_dir }}"
|
||||
- config
|
||||
- --quiet
|
||||
register: compose_validate
|
||||
changed_when: false
|
||||
failed_when: compose_validate.rc != 0
|
||||
|
||||
- name: Show staged-vs-live difference
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ compose_stage.diff | default('(no change)') }}"
|
||||
when: ansible_check_mode
|
||||
|
||||
- name: Back up the current compose file (apply mode)
|
||||
ansible.builtin.shell:
|
||||
cmd: >-
|
||||
cp -a '{{ compose_project_dir }}/{{ compose_remote_file | default('compose.yml') }}'
|
||||
'{{ compose_project_dir }}/{{ compose_remote_file | default('compose.yml') }}.bak-$(date +%Y%m%d-%H%M%S)'
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Promote the validated compose file (apply mode)
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- mv
|
||||
- "{{ compose_project_dir }}/{{ compose_remote_file | default('compose.yml') }}.dsh-new"
|
||||
- "{{ compose_project_dir }}/{{ compose_remote_file | default('compose.yml') }}"
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Apply the compose declaration (apply mode)
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- compose
|
||||
- --project-directory
|
||||
- "{{ compose_project_dir }}"
|
||||
- up
|
||||
- -d
|
||||
when: not ansible_check_mode
|
||||
Reference in New Issue
Block a user