diff --git a/ansible/roles/system-upgrade/defaults/main.yml b/ansible/roles/system-upgrade/defaults/main.yml new file mode 100644 index 0000000..2b5655d --- /dev/null +++ b/ansible/roles/system-upgrade/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# Role-local defaults for system-upgrade. +# Project-wide kernel/NVIDIA package lists live in ansible/vars/main.yml so +# they can be reused by future roles (e.g. hyprland) without re-declaration. +# Override here only if you need a different value when running this role +# stand-alone (ansible-playbook ... -e @custom-vars.yml). diff --git a/ansible/roles/system-upgrade/handlers/main.yml b/ansible/roles/system-upgrade/handlers/main.yml new file mode 100644 index 0000000..229caeb --- /dev/null +++ b/ansible/roles/system-upgrade/handlers/main.yml @@ -0,0 +1,6 @@ +--- +# Handlers for system-upgrade role. +# Currently the role uses explicit gated tasks (when: ... .changed) rather +# than notify/handler chains, because every kernel/driver change must trigger +# both mkinitcpio AND grub-mkconfig in a strict order. If you later split +# tasks across files, prefer the standard notify pattern here. diff --git a/ansible/roles/system-upgrade/tasks/main.yml b/ansible/roles/system-upgrade/tasks/main.yml new file mode 100644 index 0000000..26fc2fa --- /dev/null +++ b/ansible/roles/system-upgrade/tasks/main.yml @@ -0,0 +1,289 @@ +--- +# system-upgrade role — full system upgrade + kernel/NVIDIA preparation +# for the upcoming Hyprland/Wayland migration. Idempotent: a clean re-run +# on an already-upgraded box performs no destructive action. +# +# Sections: +# 1. Pre-flight checks (OS guard, free space, summary) +# 2. Full system upgrade (pacman -Syu) — added in Step 2 +# 3. NVIDIA branch migration + kernel install — added in Step 3 +# 4. Initramfs + GRUB refresh + final reboot notice — added in Step 4 +# +# Run as: +# ansible-playbook ansible/workstation.yml --check --tags system-upgrade +# ansible-playbook ansible/workstation.yml --tags system-upgrade + +# --------------------------------------------------------------------------- +# 1. Pre-flight checks +# --------------------------------------------------------------------------- + +- name: Pre-flight - announce role start + debug: + msg: + - "system-upgrade: preparing baseline for Hyprland/Wayland migration" + - "kernels to ensure installed: {{ kernel_packages | join(', ') }}" + - "nvidia modules to ensure installed: {{ nvidia_kernel_modules | join(', ') }}" + - "nvidia legacy packages to remove: {{ nvidia_packages_to_remove | join(', ') }}" + tags: [system-upgrade, preflight] + +- name: Pre-flight - capture free space on / (GiB) + shell: | + set -o pipefail + df --output=avail -BG / | tail -1 | tr -dc '0-9' + args: + executable: /bin/bash + register: root_free_gb + changed_when: false + check_mode: false + tags: [system-upgrade, preflight] + +- name: Pre-flight - fail if free space on / < {{ min_free_root_gb }} GiB + fail: + msg: >- + Only {{ root_free_gb.stdout }} GiB free on /, need at least + {{ min_free_root_gb }} GiB before running pacman -Syu and installing + a second kernel. Run `paccache -rk2` and clear ~/.cache before retrying. + when: (root_free_gb.stdout | int) < (min_free_root_gb | int) + tags: [system-upgrade, preflight] + +- name: Pre-flight - record currently running kernel + command: uname -r + register: running_kernel + changed_when: false + check_mode: false + tags: [system-upgrade, preflight] + +- name: Pre-flight - summary + debug: + msg: + - "running kernel: {{ running_kernel.stdout }}" + - "free space on /: {{ root_free_gb.stdout }} GiB (min required {{ min_free_root_gb }})" + - "OS guard: os_type == '{{ os_type }}' (must be 'archlinux')" + tags: [system-upgrade, preflight] + +# --------------------------------------------------------------------------- +# 2. Full system upgrade (pacman -Syu) +# --------------------------------------------------------------------------- +# Bring the box to current Manjaro Stable before touching kernels/drivers. +# `community.general.pacman` runs the equivalent of `pacman -Syu`: +# - update_cache: yes -> pacman -Sy +# - upgrade: yes -> pacman -Su +# It reports changed=True iff packages were actually upgraded, which we use +# below to gate the kernel/driver migration so an already-current box is a +# true no-op. + +- name: Upgrade - full system upgrade (pacman -Syu) + community.general.pacman: + update_cache: yes + upgrade: yes + register: pacman_syu + tags: [system-upgrade, upgrade, pacman] + +- name: Upgrade - summary + debug: + msg: + - "pacman -Syu changed: {{ pacman_syu.changed | default(false) }}" + - "packages touched: {{ (pacman_syu.packages | default([])) | length }}" + - "first 10 packages: {{ (pacman_syu.packages | default([]))[:10] | join(', ') }}" + tags: [system-upgrade, upgrade] + +# --------------------------------------------------------------------------- +# 3. NVIDIA branch migration + kernel install +# --------------------------------------------------------------------------- +# Goal: end up with BOTH linux618 (LTS/Fallback) and linux70 (stable) installed, each +# with matching *-nvidia kernel modules on the 580xx branch. +# in the same transaction so the GPU never has a "no driver" window of more +# than ~1 second on a local pacman cache. +# +# Safety guarantees: +# - linux618 (currently running kernel) is in `kernel_packages`, never in +# `nvidia_packages_to_remove` — asserted below. +# - The remove step uses the list of 575xx packages that are *actually* +# installed, so re-running the role after a successful migration is a +# true no-op (LEGACY is empty -> pacman -Rdd is skipped). +# - The install step uses --needed so already-current packages are not +# touched a second time. + +- name: Migration - safety assertion (Fallback kernel must NOT be removed) + assert: + that: + - "'linux618' in kernel_packages" + - "'linux618' not in nvidia_packages_to_remove" + - "kernel_packages | length > 0" + - "nvidia_kernel_modules | length > 0" + fail_msg: >- + Refusing to migrate: configuration would remove the running fallback kernel. + Check ansible/vars/main.yml: 'linux618' must stay in kernel_packages. + tags: [system-upgrade, migration] + +- name: Migration - detect which legacy 575xx packages are currently installed + shell: | + set -o pipefail + pacman -Qq {{ nvidia_packages_to_remove | join(' ') }} 2>/dev/null || true + args: + executable: /bin/bash + register: nvidia_legacy_installed + changed_when: false + check_mode: false + tags: [system-upgrade, migration] + +- name: Migration - check-mode dry-run (pacman -S --print) + shell: | + set -o pipefail + pacman -S --needed --print {{ (kernel_packages + nvidia_kernel_modules + nvidia_userspace_packages) | join(' ') }} 2>&1 | head -40 || true + args: + executable: /bin/bash + register: nvidia_dryrun + changed_when: false + check_mode: false + when: ansible_check_mode + tags: [system-upgrade, migration] + +- name: Migration - check-mode preview output + debug: + msg: + - "legacy 575xx packages currently installed:" + - "{{ nvidia_legacy_installed.stdout_lines | default([]) }}" + - "pacman would install (first 40 lines of --print):" + - "{{ nvidia_dryrun.stdout_lines | default([]) }}" + when: ansible_check_mode + tags: [system-upgrade, migration] + +- name: Migration - swap NVIDIA branch (575xx -> 580xx) and install kernels + shell: | + set -eo pipefail + LEGACY="{{ nvidia_legacy_installed.stdout_lines | default([]) | join(' ') }}" + if [ -n "$LEGACY" ]; then + echo ">>> dropping legacy 575xx packages: $LEGACY" + pacman -Rdd --noconfirm $LEGACY + else + echo ">>> no legacy 575xx packages present — skipping drop step" + fi + echo ">>> ensuring target packages are present (kernels + nvidia modules + userspace)" + pacman -S --needed --noconfirm {{ (kernel_packages + nvidia_kernel_modules + nvidia_userspace_packages) | join(' ') }} + args: + executable: /bin/bash + register: nvidia_migration + become: yes + when: not ansible_check_mode + # Detect REAL pacman activity (not our own echo lines): pacman prints + # progress lines like "(3/4) installing linux70" / "(1/4) upgrading foo" / + # "(2/4) removing nvidia-575xx-utils". Match exactly that format so re-runs + # on an already-current box report changed=false. + changed_when: >- + (nvidia_migration.stdout | default('')) is search('\([0-9]+/[0-9]+\)\s+(installing|upgrading|removing)\s') + tags: [system-upgrade, migration] + +- name: Migration - verify all target packages are present after install + command: pacman -Q {{ item }} + loop: "{{ kernel_packages + nvidia_kernel_modules + nvidia_userspace_packages }}" + register: pkg_verify + changed_when: false + check_mode: false + failed_when: (pkg_verify.rc | default(0)) != 0 and not ansible_check_mode + tags: [system-upgrade, migration] + +- name: Migration - recovery notice + debug: + msg: + - "Recovery if linux70 fails to boot:" + - " 1. Reboot, hold SHIFT (or ESC) at POST to open the GRUB menu." + - " 2. Pick 'Advanced options for Manjaro Linux' -> 'linux618-x86_64'." + - " 3. linux618 + linux618-nvidia (580xx) modules are kept intact" + - " so the fallback path is always bootable." + tags: [system-upgrade, migration] + +# --------------------------------------------------------------------------- +# 4. Initramfs refresh + GRUB regenerate + final reboot notice +# --------------------------------------------------------------------------- +# Manjaro's pacman post-install hooks normally invoke mkinitcpio and update-grub +# automatically. We re-run them explicitly here as a belt-and-suspenders safety +# net: if a hook was missed (e.g. interrupted transaction), the system still +# ends up bootable. Both tasks are gated on "did pacman actually change +# anything?" so an idempotent re-run is a true no-op. + +- name: Boot - rebuild initramfs for all installed kernels (mkinitcpio -P) + command: mkinitcpio -P + become: yes + register: mkinitcpio_result + when: + - not ansible_check_mode + - (pacman_syu.changed | default(false)) or (nvidia_migration.changed | default(false)) + tags: [system-upgrade, boot, initramfs] + +- name: Boot - regenerate GRUB config (-> /boot/grub/grub.cfg) + command: grub-mkconfig -o /boot/grub/grub.cfg + become: yes + register: grub_mkconfig_result + when: + - not ansible_check_mode + - (pacman_syu.changed | default(false)) or (nvidia_migration.changed | default(false)) + tags: [system-upgrade, boot, grub] + +- name: Boot - stat both initramfs images + stat: + path: "{{ item }}" + loop: + - /boot/initramfs-6.18-x86_64.img + - /boot/initramfs-7.0-x86_64.img + register: initramfs_stat + changed_when: false + check_mode: false + tags: [system-upgrade, boot, initramfs] + +- name: Boot - assert both initramfs images are present after install + assert: + that: + - item.stat.exists + fail_msg: >- + Missing initramfs image: {{ item.item }} — investigate the mkinitcpio + output above before rebooting. linux618 path may still be usable + if only the linux70 image is missing. + loop: "{{ initramfs_stat.results }}" + loop_control: + label: "{{ item.item }}" + when: not ansible_check_mode + tags: [system-upgrade, boot, initramfs] + +- name: Boot - assert GRUB menu lists both kernels (linux618 + linux70) + shell: | + # Manjaro grub.cfg uses 'Manjaro Linux (Kernel: X.Y.Z-MANJARO x64)' format, + # NOT 'linux618'/'linux70' package-name tokens. Count menuentries for each + # kernel family separately so we fail if one is missing (a simple total + # count would mask a missing kernel if the other family has 2+ entries). + K618=$(grep -cE "menuentry .*Kernel: 6\.18\." /boot/grub/grub.cfg) + K70=$(grep -cE "menuentry .*Kernel: 7\.0\." /boot/grub/grub.cfg) + echo "linux618 (Kernel: 6.18.x) menuentries: ${K618}" + echo "linux70 (Kernel: 7.0.x) menuentries: ${K70}" + if [ "${K618}" -lt 1 ] || [ "${K70}" -lt 1 ]; then + echo "FAIL: missing menuentry for one or both kernels" >&2 + exit 1 + fi + echo "OK: both kernels are listed in /boot/grub/grub.cfg" + args: + executable: /bin/bash + register: grub_menu_check + changed_when: false + check_mode: false + failed_when: + - grub_menu_check.rc != 0 + - not ansible_check_mode + tags: [system-upgrade, boot, grub] + +- name: Boot - final reboot notice + debug: + msg: + - "================================================================" + - "system-upgrade role finished successfully." + - "" + - "NEXT STEP: reboot and select 'linux70' from the GRUB menu." + - " 1. sudo reboot" + - " 2. At the GRUB menu, choose 'Advanced options for Manjaro Linux'" + - " -> 'Manjaro Linux on /dev/... linux70-x86_64'." + - " 3. After login, verify with:" + - " uname -r # expect 7.0.x" + - " nvidia-smi # expect driver 580.159.03 on GTX 1050 Ti" + - "" + - "Only AFTER a successful linux70 boot, run the upcoming Hyprland role." + - "================================================================" + tags: [system-upgrade, notice]