feat: add system-upgrade role for kernel/nvidia migration

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
ja
2026-05-20 10:49:07 +02:00
co-authored by Junie
parent 724ccebdb0
commit e6f3506ef1
3 changed files with 301 additions and 0 deletions
@@ -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).
@@ -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.
+289
View File
@@ -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]