--- - name: Apply Security Patches and Updates hosts: all become: true gather_facts: true vars: patch_window_start: "02:00" patch_window_end: "04:00" reboot_required: false security_only: true pre_tasks: - name: Check patch window assert: that: ansible_date_time.hour|int >= patch_window_start.split(':')[0]|int and ansible_date_time.hour|int < patch_window_end.split(':')[0]|int fail_msg: "Current time {{ ansible_date_time.hour }}:{{ ansible_date_time.minute }} is outside patch window {{ patch_window_start }}-{{ patch_window_end }}" when: enforce_patch_window | default(true) | bool - name: Create patch backup file: path: "/var/backups/pre-patch-{{ ansible_date_time.iso8601 }}" state: directory - name: Backup package list command: dpkg --get-selections register: package_backup changed_when: false - name: Save package backup copy: content: "{{ package_backup.stdout }}" dest: "/var/backups/pre-patch-{{ ansible_date_time.iso8601 }}/packages.list" tasks: - name: Update package cache apt: update_cache: yes cache_valid_time: 300 when: ansible_os_family == "Debian" - name: Check for available updates command: apt list --upgradable 2>/dev/null | grep -v "Listing..." | wc -l register: updates_available changed_when: false when: ansible_os_family == "Debian" - name: Apply security updates only apt: upgrade: dist update_cache: yes when: security_only and ansible_os_family == "Debian" - name: Apply all updates apt: upgrade: dist update_cache: yes when: not security_only and ansible_os_family == "Debian" - name: Check if reboot required stat: path: /var/run/reboot-required register: reboot_required_file when: ansible_os_family == "Debian" - name: Set reboot flag set_fact: reboot_required: "{{ reboot_required_file.stat.exists | default(false) }}" - name: Restart services after patching service: name: "{{ item }}" state: restarted loop: - sshd - fail2ban - unattended-upgrades ignore_errors: true - name: Update monitoring agent include_role: name: monitoring_agent_update when: "'monitoring' in group_names" - name: Verify critical services service: name: "{{ item }}" state: started loop: - systemd-journald - systemd-logind - cron ignore_errors: true - name: Run post-patch health checks uri: url: http://localhost/health method: GET status_code: 200 register: health_result ignore_errors: true when: "'webservers' in group_names" post_tasks: - name: Generate patch report template: src: templates/patch_report.j2 dest: "/var/log/patch_report_{{ ansible_date_time.iso8601 }}.log" vars: patch_status: "{{ 'SUCCESS' if health_result.status == 200 else 'WARNING' }}" updates_applied: "{{ updates_available.stdout | default('0') }}" reboot_needed: "{{ reboot_required }}" - name: Send patch notification mail: to: "{{ patch_notification_email | default('infra-team@company.com') }}" subject: "Patch Report - {{ inventory_hostname }}" body: | Patch completed for {{ inventory_hostname }} Updates applied: {{ updates_applied }} Reboot required: {{ reboot_required }} Health check: {{ 'PASSED' if health_result.status == 200 else 'FAILED' }} See /var/log/patch_report_{{ ansible_date_time.iso8601 }}.log for details when: patch_notification_email is defined ignore_errors: true - name: Schedule reboot if required command: shutdown -r +5 "Rebooting for security patches" when: reboot_required and auto_reboot | default(false) | bool async: 600 poll: 0 handlers: - name: restart monitoring service: name: "{{ monitoring_service | default('prometheus-node-exporter') }}" state: restarted when: "'monitoring' in group_names"