216 lines
7.8 KiB
YAML
216 lines
7.8 KiB
YAML
---
|
|
# playbooks/onboarding/watchtower_baseline.yml
|
|
# Idempotent baseline enforcement for the Ansible control node (Watchtower).
|
|
#
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# PURPOSE: Ongoing drift enforcement — safe to run any time, safe to schedule.
|
|
# Does NOT upgrade packages. Does NOT reboot.
|
|
# For OS updates: use playbooks/onboarding/watchtower_update.yml
|
|
# For control node audit: use playbooks/onboarding/watchtower_audit.yml
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
#
|
|
# What this enforces (all idempotent):
|
|
# 0. Packages: Required system packages present (git, curl, python3, python3-venv, etc.)
|
|
# 1. Storage: Swap disabled (swapoff -a + fstab commented)
|
|
# 2. Sysctl: vm.swappiness=0, bridge netfilter, ip_forward (Docker on control node)
|
|
# 3. Docker: /etc/docker/daemon.json with log rotation
|
|
# 4. Toolchain: Python venv exists, Ansible Galaxy collections installed
|
|
# 5. SSH: Ansible SSH key present on control node
|
|
#
|
|
# NOTE: Watchtower connects to itself via ansible_connection=local.
|
|
# Tasks that would SSH to a remote host are not needed here.
|
|
#
|
|
# Usage:
|
|
# ansible-playbook -i inventory/hosts.ini playbooks/onboarding/watchtower_baseline.yml
|
|
#
|
|
# # Dry-run:
|
|
# ansible-playbook -i inventory/hosts.ini playbooks/onboarding/watchtower_baseline.yml --check --diff
|
|
#
|
|
# # Target a specific section only:
|
|
# ansible-playbook -i inventory/hosts.ini playbooks/onboarding/watchtower_baseline.yml --tags toolchain
|
|
# ansible-playbook -i inventory/hosts.ini playbooks/onboarding/watchtower_baseline.yml --tags docker
|
|
|
|
- name: Watchtower control node baseline enforcement
|
|
hosts: watchtower
|
|
become: true
|
|
|
|
vars:
|
|
lab_user: "{{ lab_ansible_user | default('chester') }}"
|
|
homelab_root: "/home/{{ lab_user }}/homelab"
|
|
venv_path: "{{ homelab_root }}/.venv"
|
|
ssh_key_path: "/home/{{ lab_user }}/.ssh/id_ed25519"
|
|
galaxy_requirements: "{{ homelab_root }}/ansible/requirements.yml"
|
|
|
|
handlers:
|
|
- name: Restart Docker
|
|
ansible.builtin.service:
|
|
name: docker
|
|
state: restarted
|
|
|
|
tasks:
|
|
- name: "0. Packages: ensure required system packages are present"
|
|
tags: [packages, baseline]
|
|
ansible.builtin.apt:
|
|
name:
|
|
- git
|
|
- curl
|
|
- htop
|
|
- python3
|
|
- python3-pip
|
|
- python3-venv
|
|
- python3-apt
|
|
- nfs-common
|
|
- ca-certificates
|
|
- docker-ce
|
|
- docker-ce-cli
|
|
- containerd.io
|
|
state: present
|
|
update_cache: true
|
|
|
|
- name: "1. Storage: disable swap"
|
|
tags: [storage, baseline]
|
|
block:
|
|
- name: Disable swap immediately (covers traditional + zram)
|
|
ansible.builtin.command: swapoff -a
|
|
when: ansible_swaptotal_mb > 0
|
|
changed_when: ansible_swaptotal_mb > 0
|
|
|
|
- name: Comment out swap entries in /etc/fstab
|
|
ansible.builtin.replace:
|
|
path: /etc/fstab
|
|
regexp: '^([^#].*\s+swap\s+.*)$'
|
|
replace: '# \1'
|
|
|
|
- name: Remove zram-generator config to prevent zram swap at boot
|
|
ansible.builtin.copy:
|
|
dest: /etc/systemd/zram-generator.conf
|
|
owner: root
|
|
group: root
|
|
mode: '0644'
|
|
content: |
|
|
# Managed by Ansible — watchtower_baseline.yml
|
|
# Empty config disables zram swap on Ubuntu 24.04.
|
|
|
|
- name: Stop and mask systemd-zram-generator service if present
|
|
ansible.builtin.systemd:
|
|
name: systemd-zram-generator
|
|
state: stopped
|
|
enabled: false
|
|
masked: true
|
|
failed_when: false
|
|
|
|
- name: Swapoff zram devices explicitly
|
|
ansible.builtin.shell: |
|
|
for dev in $(ls /dev/zram* 2>/dev/null); do
|
|
swapoff "$dev" 2>/dev/null || true
|
|
done
|
|
changed_when: false
|
|
|
|
- name: "2. Sysctl: Docker networking parameters"
|
|
tags: [sysctl, baseline]
|
|
block:
|
|
- name: Ensure br_netfilter module is loaded
|
|
community.general.modprobe:
|
|
name: br_netfilter
|
|
state: present
|
|
|
|
- name: Persist br_netfilter module load at boot
|
|
ansible.builtin.copy:
|
|
dest: /etc/modules-load.d/br_netfilter.conf
|
|
content: "br_netfilter\n"
|
|
owner: root
|
|
group: root
|
|
mode: '0644'
|
|
|
|
- name: Apply and persist sysctl parameters
|
|
ansible.posix.sysctl:
|
|
name: "{{ item.key }}"
|
|
value: "{{ item.value }}"
|
|
sysctl_file: /etc/sysctl.d/90-watchtower.conf
|
|
state: present
|
|
reload: true
|
|
loop:
|
|
- { key: vm.swappiness, value: "0" }
|
|
- { key: net.bridge.bridge-nf-call-iptables, value: "1" }
|
|
- { key: net.bridge.bridge-nf-call-ip6tables, value: "1" }
|
|
- { key: net.ipv4.ip_forward, value: "1" }
|
|
|
|
- name: "3. Docker: daemon configuration and log rotation"
|
|
tags: [docker, baseline]
|
|
block:
|
|
- name: Ensure /etc/docker directory exists
|
|
ansible.builtin.file:
|
|
path: /etc/docker
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: '0755'
|
|
|
|
- name: Deploy Docker daemon.json with log rotation
|
|
ansible.builtin.copy:
|
|
dest: /etc/docker/daemon.json
|
|
owner: root
|
|
group: root
|
|
mode: '0644'
|
|
content: |
|
|
{
|
|
"log-driver": "json-file",
|
|
"log-opts": {
|
|
"max-size": "10m",
|
|
"max-file": "3"
|
|
}
|
|
}
|
|
notify: Restart Docker
|
|
|
|
- name: Ensure '{{ lab_user }}' is in the docker group
|
|
ansible.builtin.user:
|
|
name: "{{ lab_user }}"
|
|
groups: docker
|
|
append: true
|
|
|
|
- name: "4. Toolchain: Python venv and Ansible Galaxy collections"
|
|
tags: [toolchain, baseline]
|
|
become: false
|
|
block:
|
|
- name: Ensure Python venv exists at {{ venv_path }}
|
|
ansible.builtin.command: "python3 -m venv {{ venv_path }}"
|
|
args:
|
|
creates: "{{ venv_path }}/bin/activate"
|
|
|
|
- name: Ensure pip is up to date in venv
|
|
ansible.builtin.pip:
|
|
name: pip
|
|
state: latest
|
|
virtualenv: "{{ venv_path }}"
|
|
|
|
- name: Ensure Ansible is installed in venv
|
|
ansible.builtin.pip:
|
|
name: ansible
|
|
state: present
|
|
virtualenv: "{{ venv_path }}"
|
|
|
|
- name: Install Ansible Galaxy collections from requirements.yml
|
|
ansible.builtin.command: >
|
|
{{ venv_path }}/bin/ansible-galaxy collection install
|
|
-r {{ galaxy_requirements }}
|
|
--upgrade
|
|
register: galaxy_install
|
|
changed_when: "'Installing' in galaxy_install.stdout"
|
|
|
|
- name: "5. SSH: verify Ansible SSH key is present"
|
|
tags: [ssh, baseline]
|
|
become: false
|
|
block:
|
|
- name: Check SSH private key exists
|
|
ansible.builtin.stat:
|
|
path: "{{ ssh_key_path }}"
|
|
register: ssh_key_stat
|
|
|
|
- name: Fail if SSH private key is missing
|
|
ansible.builtin.fail:
|
|
msg: >-
|
|
SSH private key not found at {{ ssh_key_path }}.
|
|
Generate one with: ssh-keygen -t ed25519 -f {{ ssh_key_path }}
|
|
then run: ssh-copy-id -i {{ ssh_key_path }}.pub chester@<node>
|
|
when: not ssh_key_stat.stat.exists
|