|
|
|
@@ -0,0 +1,142 @@
|
|
|
|
|
---
|
|
|
|
|
- name: Fail on unsupported distribution
|
|
|
|
|
ansible.builtin.fail:
|
|
|
|
|
msg: >-
|
|
|
|
|
os-update supports Debian and Archlinux only;
|
|
|
|
|
this host reports os_family={{ ansible_facts['os_family'] }}
|
|
|
|
|
when: ansible_facts['os_family'] not in ['Debian', 'Archlinux']
|
|
|
|
|
|
|
|
|
|
# --- Baseline: what is healthy before the upgrade must be healthy after ---
|
|
|
|
|
- name: Record failed units before upgrade
|
|
|
|
|
ansible.builtin.command: systemctl list-units --state=failed --plain --no-legend --no-pager
|
|
|
|
|
register: os_update_failed_before
|
|
|
|
|
changed_when: false
|
|
|
|
|
check_mode: false
|
|
|
|
|
|
|
|
|
|
- name: Check for docker
|
|
|
|
|
ansible.builtin.stat:
|
|
|
|
|
path: /usr/bin/docker
|
|
|
|
|
register: os_update_docker
|
|
|
|
|
|
|
|
|
|
- name: Record running containers before upgrade
|
|
|
|
|
ansible.builtin.command: docker ps --format "{{ '{{' }}.Names{{ '}}' }}"
|
|
|
|
|
register: os_update_containers_before
|
|
|
|
|
changed_when: false
|
|
|
|
|
check_mode: false
|
|
|
|
|
when: os_update_docker.stat.exists
|
|
|
|
|
|
|
|
|
|
- name: Assert at least 1 GiB free on /
|
|
|
|
|
ansible.builtin.assert:
|
|
|
|
|
that: >-
|
|
|
|
|
(ansible_facts['mounts'] | selectattr('mount', 'equalto', '/') | first)['size_available']
|
|
|
|
|
>= 1073741824
|
|
|
|
|
fail_msg: "Less than 1 GiB free on / -- free space before upgrading"
|
|
|
|
|
|
|
|
|
|
# --- Arch Linux ---
|
|
|
|
|
- name: Upgrade keyring first (Arch)
|
|
|
|
|
community.general.pacman:
|
|
|
|
|
name: archlinux-keyring
|
|
|
|
|
state: latest
|
|
|
|
|
update_cache: true
|
|
|
|
|
when: ansible_facts['os_family'] == 'Archlinux'
|
|
|
|
|
|
|
|
|
|
- name: Upgrade all packages (Arch)
|
|
|
|
|
community.general.pacman:
|
|
|
|
|
upgrade: true
|
|
|
|
|
register: os_update_pacman
|
|
|
|
|
when: ansible_facts['os_family'] == 'Archlinux'
|
|
|
|
|
|
|
|
|
|
# --- Debian/Ubuntu ---
|
|
|
|
|
- name: Upgrade all packages (Debian)
|
|
|
|
|
ansible.builtin.apt:
|
|
|
|
|
update_cache: true
|
|
|
|
|
upgrade: full
|
|
|
|
|
autoremove: true
|
|
|
|
|
environment:
|
|
|
|
|
DEBIAN_FRONTEND: noninteractive
|
|
|
|
|
when: ansible_facts['os_family'] == 'Debian'
|
|
|
|
|
|
|
|
|
|
- name: Check reboot-required flag (Debian)
|
|
|
|
|
ansible.builtin.stat:
|
|
|
|
|
path: /var/run/reboot-required
|
|
|
|
|
register: os_update_reboot_flag
|
|
|
|
|
when: ansible_facts['os_family'] == 'Debian'
|
|
|
|
|
|
|
|
|
|
# --- Reboot ---
|
|
|
|
|
- name: Read running kernel before reboot (Arch)
|
|
|
|
|
ansible.builtin.command: uname -r
|
|
|
|
|
register: os_update_kernel_before
|
|
|
|
|
changed_when: false
|
|
|
|
|
check_mode: false
|
|
|
|
|
when: ansible_facts['os_family'] == 'Archlinux'
|
|
|
|
|
|
|
|
|
|
# Missing modules dir: a newer kernel is installed but not booted, including
|
|
|
|
|
# from an earlier interrupted run where pacman now reports no change.
|
|
|
|
|
- name: Check running kernel is still installed (Arch)
|
|
|
|
|
ansible.builtin.stat:
|
|
|
|
|
path: "/usr/lib/modules/{{ os_update_kernel_before.stdout }}/vmlinuz"
|
|
|
|
|
register: os_update_kernel_present
|
|
|
|
|
when: ansible_facts['os_family'] == 'Archlinux'
|
|
|
|
|
|
|
|
|
|
- name: Reboot
|
|
|
|
|
ansible.builtin.reboot:
|
|
|
|
|
msg: "Rebooting after package upgrade"
|
|
|
|
|
reboot_timeout: 900
|
|
|
|
|
post_reboot_delay: 30
|
|
|
|
|
when: >-
|
|
|
|
|
(ansible_facts['os_family'] == 'Archlinux' and
|
|
|
|
|
(os_update_pacman is changed or not os_update_kernel_present.stat.exists)) or
|
|
|
|
|
(ansible_facts['os_family'] == 'Debian' and os_update_reboot_flag.stat.exists)
|
|
|
|
|
|
|
|
|
|
# --- Verify ---
|
|
|
|
|
- name: Read running kernel
|
|
|
|
|
ansible.builtin.command: uname -r
|
|
|
|
|
register: os_update_kernel
|
|
|
|
|
changed_when: false
|
|
|
|
|
check_mode: false
|
|
|
|
|
|
|
|
|
|
# Arch keeps only the installed kernel's modules; the directory existing means
|
|
|
|
|
# the host booted the kernel pacman installed.
|
|
|
|
|
- name: Assert running kernel is the installed one (Arch)
|
|
|
|
|
ansible.builtin.stat:
|
|
|
|
|
path: "/usr/lib/modules/{{ os_update_kernel.stdout }}/vmlinuz"
|
|
|
|
|
register: os_update_kernel_installed
|
|
|
|
|
failed_when: not os_update_kernel_installed.stat.exists
|
|
|
|
|
when: ansible_facts['os_family'] == 'Archlinux'
|
|
|
|
|
|
|
|
|
|
- name: Assert no reboot pending (Debian)
|
|
|
|
|
ansible.builtin.stat:
|
|
|
|
|
path: /var/run/reboot-required
|
|
|
|
|
register: os_update_reboot_flag_after
|
|
|
|
|
failed_when: os_update_reboot_flag_after.stat.exists
|
|
|
|
|
when: ansible_facts['os_family'] == 'Debian'
|
|
|
|
|
|
|
|
|
|
- name: Wait for previously running containers
|
|
|
|
|
ansible.builtin.command: docker ps --format "{{ '{{' }}.Names{{ '}}' }}"
|
|
|
|
|
register: os_update_containers_after
|
|
|
|
|
changed_when: false
|
|
|
|
|
check_mode: false
|
|
|
|
|
until: >-
|
|
|
|
|
os_update_containers_before.stdout_lines
|
|
|
|
|
| difference(os_update_containers_after.stdout_lines)
|
|
|
|
|
| length == 0
|
|
|
|
|
retries: 20
|
|
|
|
|
delay: 15
|
|
|
|
|
when: os_update_docker.stat.exists
|
|
|
|
|
|
|
|
|
|
- name: Wait for no new failed units
|
|
|
|
|
ansible.builtin.command: systemctl list-units --state=failed --plain --no-legend --no-pager
|
|
|
|
|
register: os_update_failed_after
|
|
|
|
|
changed_when: false
|
|
|
|
|
check_mode: false
|
|
|
|
|
until: >-
|
|
|
|
|
os_update_failed_after.stdout_lines
|
|
|
|
|
| map('split') | map('first')
|
|
|
|
|
| difference(os_update_failed_before.stdout_lines | map('split') | map('first'))
|
|
|
|
|
| length == 0
|
|
|
|
|
retries: 10
|
|
|
|
|
delay: 15
|
|
|
|
|
|
|
|
|
|
- name: Report kernel
|
|
|
|
|
ansible.builtin.debug:
|
|
|
|
|
msg: "{{ inventory_hostname }} runs {{ os_update_kernel.stdout }}"
|