--- # 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]