5aaa7020ac
Review fixes: - Availability plays: "ping_result is succeeded" is true for unreachable hosts, so offline hosts were added to reachable_hosts and failed the next play. Test reachability explicitly (7 playbooks). - bootstrap_lxc: vim-enhanced and mtr-tiny package names, enable CRB next to EPEL, add the root key without replacing authorized_keys, validate sshd_config edits and restart sshd only on change. - backup_docker: the command module does not expand $(...); fix rsync filter order and add --exclude=*; restart only the containers that were stopped, even when rsync fails; read the storage address from the inventory; keep semaphore and semaphore-db running. - new_lxc: community.general.proxmox no longer exists, use community.proxmox; node is mini-pc; valid hostname; password or token auth; let the module pick the VMID; longer timeout. - proxmox-backup: surface the vzdump exit code, let vzdump prune with keep-last, drop the rename/prune steps that globbed *.zstd. - time-region: chrony regex no longer comments out its own server line; /etc/localtime as a symlink; reuse facts across plays. - update_pihole_blocklists: FQCN, become: true, changed_when. - ansible.cfg gathering = smart; update_system only pings ubuntu/rocky. Telegram alerts: - health_check: filesystems above disk_warn_pct (default 80), excluding efivarfs, and offline hosts. - service_check: services not running. - One message per run from localhost, credentials from Semaphore (TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID), no_log on the send task, prints the alert text when Telegram is not configured. Claude-Session: https://claude.ai/code/session_01XEizHawueRXUcxQELRZ8kq
121 lines
4.4 KiB
YAML
121 lines
4.4 KiB
YAML
---
|
|
# Backs up the docker host to the storage container: compose files and
|
|
# configs from compose_dir while everything runs, then the Docker volumes
|
|
# with the containers stopped so the copy is consistent.
|
|
#
|
|
# Containers in backup_skip_containers are never stopped. Semaphore runs on
|
|
# this host, so it and its database stay up (their volumes are copied live).
|
|
# root on the docker host needs an SSH key that is authorized on backup_host.
|
|
#
|
|
# Extra vars (optional):
|
|
# backup_host - inventory name of the destination (default storage)
|
|
# backup_root - directory on backup_host (default /mnt/storage/Docker_backups)
|
|
# backup_skip_containers - default [semaphore, semaphore-db]
|
|
|
|
- name: Backup Docker compose files and volumes to storage
|
|
hosts: docker
|
|
become: true
|
|
gather_facts: false
|
|
|
|
vars:
|
|
backup_host: storage
|
|
backup_root: /mnt/storage/Docker_backups
|
|
backup_addr: "{{ hostvars[backup_host].ansible_host }}"
|
|
backup_skip_containers:
|
|
- semaphore
|
|
- semaphore-db
|
|
compose_dir: /home/harry
|
|
volumes_dir: /var/lib/docker/volumes
|
|
rsync_ssh: ssh -o StrictHostKeyChecking=accept-new
|
|
|
|
tasks:
|
|
- name: Ensure backup destination directories exist
|
|
ansible.builtin.file:
|
|
path: "{{ backup_root }}/{{ item }}"
|
|
state: directory
|
|
mode: '0755'
|
|
loop: [compose, volumes]
|
|
delegate_to: "{{ backup_host }}"
|
|
|
|
# rsync applies the first matching rule: excludes first, then the rule
|
|
# that lets it descend into directories, then the wanted files, then
|
|
# everything else is dropped.
|
|
- name: Rsync compose files and configs (safe while running)
|
|
ansible.builtin.command:
|
|
argv:
|
|
- rsync
|
|
- -az
|
|
- --delete
|
|
- --prune-empty-dirs
|
|
- -e
|
|
- "{{ rsync_ssh }}"
|
|
- --exclude=node_modules/
|
|
- --exclude=.git/
|
|
- --exclude=__pycache__/
|
|
- --include=*/
|
|
- --include=docker-compose.yml
|
|
- --include=compose.yml
|
|
- --include=.env
|
|
- --include=*.conf
|
|
- --include=*.yml
|
|
- --include=*.yaml
|
|
- --include=*.json
|
|
- --exclude=*
|
|
- "{{ compose_dir }}/"
|
|
- "root@{{ backup_addr }}:{{ backup_root }}/compose/"
|
|
changed_when: true
|
|
|
|
- name: List running containers
|
|
ansible.builtin.command:
|
|
argv: [docker, ps, --format, "{{ '{{' }}.Names{{ '}}' }}"]
|
|
register: running_containers
|
|
changed_when: false
|
|
|
|
- name: Work out which containers to stop
|
|
ansible.builtin.set_fact:
|
|
containers_to_stop: "{{ running_containers.stdout_lines | difference(backup_skip_containers) }}"
|
|
|
|
- name: Copy the volumes with the containers stopped
|
|
block:
|
|
- name: Stop containers
|
|
ansible.builtin.command:
|
|
argv: "{{ ['docker', 'stop'] + containers_to_stop }}"
|
|
when: containers_to_stop | length > 0
|
|
|
|
- name: Rsync Docker volumes to storage
|
|
ansible.builtin.command:
|
|
argv:
|
|
- rsync
|
|
- -az
|
|
- --delete
|
|
- -e
|
|
- "{{ rsync_ssh }}"
|
|
- "{{ volumes_dir }}/"
|
|
- "root@{{ backup_addr }}:{{ backup_root }}/volumes/"
|
|
changed_when: true
|
|
|
|
always:
|
|
- name: Start the containers that were running before
|
|
ansible.builtin.command:
|
|
argv: "{{ ['docker', 'start'] + containers_to_stop }}"
|
|
when: containers_to_stop | length > 0
|
|
|
|
- name: Verify containers are running
|
|
ansible.builtin.command:
|
|
argv: [docker, ps, --format, "{{ '{{' }}.Names{{ '}}' }} - {{ '{{' }}.Status{{ '}}' }}"]
|
|
register: container_status
|
|
changed_when: false
|
|
|
|
- name: Backup summary
|
|
ansible.builtin.debug:
|
|
msg:
|
|
- "Destination: {{ backup_host }} ({{ backup_addr }}):{{ backup_root }}"
|
|
- "Stopped for the volume copy: {{ containers_to_stop | join(', ') if containers_to_stop else 'none' }}"
|
|
- "Kept running: {{ backup_skip_containers | join(', ') }}"
|
|
- "Containers running: {{ container_status.stdout_lines | length }} now, {{ running_containers.stdout_lines | length }} before"
|
|
|
|
- name: Warn when fewer containers are running than before
|
|
ansible.builtin.debug:
|
|
msg: "WARNING: {{ item }} did not come back up on {{ inventory_hostname }}"
|
|
loop: "{{ running_containers.stdout_lines | difference(container_status.stdout_lines | map('regex_replace', ' - .*$', '')) }}"
|