Initial Ansible automation for hobbs.farm infrastructure
Complete IaC covering: - 14 roles: common, docker, traefik, vaultwarden, monitoring, media, matrix, home_assistant, frigate, adguard, gitea, semaphore, openclaw, security, backup - Inventory for all hosts (Peggy, Bart, Yoda, NAS, Firewalla) - Stack playbooks: core-infrastructure, monitoring, media, communication, automation, smarthome, security-hardening - Maintenance playbooks: update-containers, backup, health-check - Production Docker Compose templates with Traefik labels - Security hardening (sysctl, auditd, UFW, fail2ban) - Automated backups with retention policy
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
---
|
||||
- name: Run backups
|
||||
hosts: docker_hosts
|
||||
become: true
|
||||
tasks:
|
||||
- name: Ensure backup destination exists
|
||||
ansible.builtin.file:
|
||||
path: "{{ backup_destination }}"
|
||||
state: directory
|
||||
mode: "0750"
|
||||
tags: [backup]
|
||||
|
||||
- name: Run backup script
|
||||
ansible.builtin.command:
|
||||
cmd: /usr/local/bin/homelab-backup.sh
|
||||
register: backup_result
|
||||
changed_when: backup_result.rc == 0
|
||||
tags: [backup]
|
||||
|
||||
- name: Display backup result
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ backup_result.stdout_lines }}"
|
||||
tags: [backup]
|
||||
|
||||
- name: Clean up backups older than 30 days
|
||||
ansible.builtin.find:
|
||||
paths: "{{ backup_destination }}"
|
||||
age: 30d
|
||||
recurse: false
|
||||
file_type: file
|
||||
patterns: "*.tar.gz"
|
||||
register: old_backups
|
||||
tags: [backup, cleanup]
|
||||
|
||||
- name: Remove old backups
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.path }}"
|
||||
state: absent
|
||||
loop: "{{ old_backups.files }}"
|
||||
loop_control:
|
||||
label: "{{ item.path | basename }}"
|
||||
tags: [backup, cleanup]
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
- name: Health check all services
|
||||
hosts: docker_hosts
|
||||
become: true
|
||||
tasks:
|
||||
- name: Check Docker daemon is running
|
||||
ansible.builtin.systemd:
|
||||
name: docker
|
||||
state: started
|
||||
check_mode: true
|
||||
register: docker_status
|
||||
tags: [health]
|
||||
|
||||
- name: Get running containers
|
||||
community.docker.docker_host_info:
|
||||
containers: true
|
||||
register: docker_info
|
||||
tags: [health]
|
||||
|
||||
- name: Display container count
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ inventory_hostname }}: {{ docker_info.containers | length }} containers running"
|
||||
tags: [health]
|
||||
|
||||
- name: Check for unhealthy containers
|
||||
ansible.builtin.shell: |
|
||||
docker ps --filter health=unhealthy --format '{{ '{{' }}.Names{{ '}}' }}'
|
||||
register: unhealthy
|
||||
changed_when: false
|
||||
tags: [health]
|
||||
|
||||
- name: Report unhealthy containers
|
||||
ansible.builtin.debug:
|
||||
msg: "WARNING: Unhealthy containers on {{ inventory_hostname }}: {{ unhealthy.stdout_lines }}"
|
||||
when: unhealthy.stdout | length > 0
|
||||
tags: [health]
|
||||
|
||||
- name: Check disk usage
|
||||
ansible.builtin.shell: |
|
||||
df -h / /data | tail -n +2 | awk '{print $1, $5, $6}'
|
||||
register: disk_usage
|
||||
changed_when: false
|
||||
tags: [health]
|
||||
|
||||
- name: Display disk usage
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ disk_usage.stdout_lines }}"
|
||||
tags: [health]
|
||||
|
||||
- name: Check memory usage
|
||||
ansible.builtin.shell: |
|
||||
free -h | grep Mem | awk '{print "Total:", $2, "Used:", $3, "Free:", $4}'
|
||||
register: mem_usage
|
||||
changed_when: false
|
||||
tags: [health]
|
||||
|
||||
- name: Display memory usage
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ mem_usage.stdout }}"
|
||||
tags: [health]
|
||||
|
||||
- name: Check key services via HTTP
|
||||
ansible.builtin.uri:
|
||||
url: "https://{{ item }}.{{ domain }}"
|
||||
validate_certs: false
|
||||
status_code: [200, 301, 302, 303, 401, 403]
|
||||
timeout: 10
|
||||
loop:
|
||||
- traefik
|
||||
- vault
|
||||
- grafana
|
||||
- jellyfin
|
||||
- gitea
|
||||
- ha
|
||||
register: http_checks
|
||||
ignore_errors: true
|
||||
tags: [health]
|
||||
when: inventory_hostname == 'peggy'
|
||||
|
||||
- name: Report HTTP check results
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ item.item }}.{{ domain }}: {{ item.status | default('FAILED') }}"
|
||||
loop: "{{ http_checks.results | default([]) }}"
|
||||
loop_control:
|
||||
label: "{{ item.item | default('unknown') }}"
|
||||
when: inventory_hostname == 'peggy'
|
||||
tags: [health]
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
- name: Update Docker containers
|
||||
hosts: docker_hosts
|
||||
become: true
|
||||
vars:
|
||||
update_stack: all
|
||||
tasks:
|
||||
- name: Get list of compose directories
|
||||
ansible.builtin.find:
|
||||
paths: "{{ docker_compose_base }}"
|
||||
file_type: directory
|
||||
recurse: false
|
||||
register: compose_dirs
|
||||
tags: [update]
|
||||
|
||||
- name: Pull latest images for each stack
|
||||
community.docker.docker_compose_v2_pull:
|
||||
project_src: "{{ item.path }}"
|
||||
loop: "{{ compose_dirs.files }}"
|
||||
loop_control:
|
||||
label: "{{ item.path | basename }}"
|
||||
when: update_stack == 'all' or (item.path | basename) == update_stack
|
||||
tags: [update]
|
||||
|
||||
- name: Recreate containers with new images
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ item.path }}"
|
||||
state: present
|
||||
recreate: auto
|
||||
loop: "{{ compose_dirs.files }}"
|
||||
loop_control:
|
||||
label: "{{ item.path | basename }}"
|
||||
when: update_stack == 'all' or (item.path | basename) == update_stack
|
||||
tags: [update]
|
||||
|
||||
- name: Prune unused Docker images
|
||||
community.docker.docker_prune:
|
||||
images: true
|
||||
images_filters:
|
||||
dangling: false
|
||||
tags: [cleanup]
|
||||
|
||||
- name: Prune unused Docker volumes
|
||||
community.docker.docker_prune:
|
||||
volumes: true
|
||||
tags: [cleanup]
|
||||
when: prune_volumes | default(false) | bool
|
||||
Reference in New Issue
Block a user