Fix playbook bugs from review, add Telegram alerts to health and service checks
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. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XEizHawueRXUcxQELRZ8kq
This commit is contained in:
+96
-38
@@ -1,62 +1,120 @@
|
||||
---
|
||||
- name: Backup Docker containers and volumes to storage
|
||||
# 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_dest: "192.168.2.115:/mnt/storage/Docker_backups"
|
||||
compose_dir: "/home/harry"
|
||||
volumes_dir: "/var/lib/docker/volumes"
|
||||
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.command:
|
||||
cmd: ssh -o StrictHostKeyChecking=no root@192.168.2.115 "mkdir -p /mnt/storage/Docker_backups/compose /mnt/storage/Docker_backups/volumes"
|
||||
changed_when: false
|
||||
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.shell: |
|
||||
rsync -az --delete \
|
||||
--include='*/' \
|
||||
--include='docker-compose.yml' \
|
||||
--include='compose.yml' \
|
||||
--include='.env' \
|
||||
--include='*.conf' \
|
||||
--include='*.yml' \
|
||||
--include='*.yaml' \
|
||||
--include='*.json' \
|
||||
--exclude='node_modules/' \
|
||||
--exclude='.git/' \
|
||||
--exclude='__pycache__/' \
|
||||
{{ compose_dir }}/ root@{{ backup_dest }}/compose/
|
||||
register: compose_sync
|
||||
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: Get list of running containers
|
||||
ansible.builtin.command: docker ps -q
|
||||
- name: List running containers
|
||||
ansible.builtin.command:
|
||||
argv: [docker, ps, --format, "{{ '{{' }}.Names{{ '}}' }}"]
|
||||
register: running_containers
|
||||
changed_when: false
|
||||
|
||||
- name: Stop all containers
|
||||
ansible.builtin.command: docker stop $(docker ps -q)
|
||||
when: running_containers.stdout | length > 0
|
||||
- name: Work out which containers to stop
|
||||
ansible.builtin.set_fact:
|
||||
containers_to_stop: "{{ running_containers.stdout_lines | difference(backup_skip_containers) }}"
|
||||
|
||||
- name: Rsync Docker volumes to storage
|
||||
ansible.builtin.shell: |
|
||||
rsync -az --delete \
|
||||
{{ volumes_dir }}/ root@{{ backup_dest }}/volumes/
|
||||
register: volume_sync
|
||||
- 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: Start all containers back up
|
||||
ansible.builtin.command: docker start $(docker ps -aq)
|
||||
- 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: docker ps --format "{{ '{{' }}.Names{{ '}}' }} - {{ '{{' }}.Status{{ '}}' }}"
|
||||
ansible.builtin.command:
|
||||
argv: [docker, ps, --format, "{{ '{{' }}.Names{{ '}}' }} - {{ '{{' }}.Status{{ '}}' }}"]
|
||||
register: container_status
|
||||
changed_when: false
|
||||
|
||||
- name: Backup summary
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "Compose sync: {{ 'OK' if compose_sync.rc == 0 else 'FAILED' }}"
|
||||
- "Volume sync: {{ 'OK' if volume_sync.rc == 0 else 'FAILED' }}"
|
||||
- "Containers running: {{ container_status.stdout_lines | length }}"
|
||||
- "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', ' - .*$', '')) }}"
|
||||
|
||||
Reference in New Issue
Block a user