Compare commits

...
8 Commits
Author SHA1 Message Date
ja 7a95ef0019 fix(ansible/hyprland): standardize keybindings (Super+Q open, Super+C close)
Change default Hyprland keybindings to match convention:
- Super+Q now opens kitty (was Return)
- Super+C now kills active window (was Q)
2026-05-20 12:02:34 +02:00
ja a67c629356 fix(ansible/hyprland): update template for Hyprland 0.55+ syntax
- Remove pseudotile (removed in 0.55)
- Change togglesplit to layoutmsg, togglesplit
2026-05-20 11:34:53 +02:00
jaandJunie cdb0730280 docs: update hardware/setup docs and add install script
Co-authored-by: Junie <junie@jetbrains.com>
2026-05-20 10:49:07 +02:00
jaandJunie bab92f0292 feat: add hyprland environment templates
Co-authored-by: Junie <junie@jetbrains.com>
2026-05-20 10:49:07 +02:00
jaandJunie 54d8729e63 feat: implement hyprland role and package configuration
Co-authored-by: Junie <junie@jetbrains.com>
2026-05-20 10:49:07 +02:00
jaandJunie e6f3506ef1 feat: add system-upgrade role for kernel/nvidia migration
Co-authored-by: Junie <junie@jetbrains.com>
2026-05-20 10:49:07 +02:00
jaandJunie 724ccebdb0 docs: add ADR 0002 for kernel and nvidia strategy
Co-authored-by: Junie <junie@jetbrains.com>
2026-05-20 10:49:07 +02:00
jaandJunie 45ad579120 feat: configure systemd power management and fix kitty TERM
Co-authored-by: Junie <junie@jetbrains.com>
2026-05-20 10:49:07 +02:00
21 changed files with 1069 additions and 4 deletions
+11
View File
@@ -8,10 +8,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- New Ansible role `ansible/roles/system-upgrade/` that runs as the **first** role in `workstation.yml`. Performs a full `pacman -Syu`, dual-installs `linux612` (LTS, fallback) and `linux70` (stable, new default) kernels with matching headers, and verifies both initramfs images and GRUB menu entries post-install. Invoke stand-alone with `ansible-playbook ansible/workstation.yml --tags system-upgrade`; idempotent on re-runs (2026-05-18).
- `docs/adr/0002-kernel-and-nvidia-strategy.md` — ADR documenting the LTS+stable kernel choice and the `575xx → 595.71` NVIDIA migration for Hyprland/Wayland preparation (2026-05-18).
- Pre-flight safety gate in the new role: aborts if free space on `/` drops below `min_free_root_gb` (5 GiB by default) (2026-05-18).
- Comprehensive NVMe benchmark for Samsung PM9A1 before firmware update: SMART health, hdparm, fio seq/rand benchmarks (2026-04-02). See `docs/tech/nvme-benchmark-pre-gxa7802q.md`.
- CLI suite update script `scripts/update-cli-suite.sh` for Manjaro/Arch tool maintenance (2026-04-02).
### Changed
- **Executed `system-upgrade` playbook on the live workstation** (2026-05-18). `pacman -Syu` upgraded **1103 packages**; kernel set now `linux612 6.12.85-1` (LTS, was 6.12.77-1) + `linux70 7.0.3-1` (NEW); NVIDIA migrated to mainline `595.71.05` for both `linux612-nvidia` and `linux70-nvidia`; legacy `*-nvidia-575xx` packages removed. Both `/boot/initramfs-6.12-x86_64.img` (54 MB) and `/boot/initramfs-7.0-x86_64.img` (122 MB) generated; GRUB regenerated with menuentries for both kernels. **Reboot required**: select `linux70` from the GRUB "Advanced options for Manjaro Linux" submenu to activate the new kernel and NVIDIA 595.71.05 modules. Logs: `logs/system-upgrade-20260518-101253-live.log`.
- NVIDIA driver migrated from the pinned `*-nvidia-575xx` (575.64.05) branch to the mainline `*-nvidia` (595.71.05) branch via the new `system-upgrade` role. `linux612-nvidia-575xx`, `nvidia-575xx-utils`, `lib32-nvidia-575xx-utils`, `nvidia-575xx-settings` are removed; `linux612-nvidia`, `linux70-nvidia`, `nvidia-utils`, `lib32-nvidia-utils`, `opencl-nvidia`, `nvidia-settings` are installed in one atomic pacman transaction (2026-05-18).
- `ansible/workstation.yml`: `system-upgrade` registered as the first role, guarded by `when: os_type == 'archlinux'`, with tag `system-upgrade` (2026-05-18).
- `ansible/vars/main.yml`: added `kernel_packages`, `nvidia_kernel_modules`, `nvidia_userspace_packages`, `nvidia_packages_to_remove`, `min_free_root_gb` so future kernel/driver pins are a one-line change (2026-05-18).
- `docs/tech/setup.md`: new bootstrap step "Run system upgrade & kernel install" before the main playbook, with `--tags system-upgrade` examples and a reboot-into-`linux70` callout (2026-05-18).
- Samsung PM9A1 NVMe firmware update applied: GXA7401Q → GXA7801Q (non-SED variant, reboot pending to activate). All nvme-cli steps succeeded (2026-04-02).
- Corrected firmware research: GXA7802Q is SED-only (`00B07`); non-SED drives (`00B00`) require GXA7801Q. Updated `docs/tech/firmware-update-research.md` with naming convention and correct source.
- Updated `.gitignore`: added `logs/` and `docs/tech/capabilities/` exclusions (2026-04-02).
@@ -31,5 +39,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Synchronized software versions across documentation files (2026-03-13).
### Fixed
- Resolved kitty cursor keys not working by disabling the `TERM=xterm-256color` override in `.bashrc`, allowing Kitty to correctly set `TERM=xterm-kitty`. Added Ansible management for `kitty.conf` to ensure proper terminal behavior and shell integration (2026-05-19).
- `system-upgrade` role: GRUB menu assertion now matches Manjaro's real `menuentry 'Manjaro Linux (Kernel: X.Y.Z-MANJARO x64)'` format (counted per-kernel-family) instead of the wrong `menuentry .*linux(612|70)` regex that always returned 0 hits on Manjaro and broke the live run with a false-negative failure (2026-05-18).
- `system-upgrade` role: migration task `changed_when` now matches pacman's progress lines `^\(N/M\)\s+(installing|upgrading|removing)\s` instead of plain substring matching, so the role's own echo lines no longer trigger spurious `changed=true` on idempotent re-runs. Verified: second live re-run reports `ok=16 changed=0 failed=0 skipped=5` (2026-05-18).
- Resolved system unresponsiveness issue by addressing critical memory/swap pressure with increased headroom and proactive OOM killing (2026-03-17).
- Identified failed `archlinux-keyring-wkd-sync.service` in system health check.
+7
View File
@@ -11,6 +11,13 @@
become: yes
tags: [systemd, oomd]
- name: Restart systemd-logind
systemd:
name: systemd-logind
state: restarted
become: yes
tags: [systemd]
- name: Reload systemd
systemd:
daemon_reload: yes
+43
View File
@@ -120,6 +120,49 @@
become: yes
tags: [systemd, optimization]
- name: Ensure systemd drop-in directories exist
file:
path: "{{ item }}"
state: directory
owner: root
group: root
mode: '0755'
with_items:
- /etc/systemd/logind.conf.d
- /etc/systemd/sleep.conf.d
become: yes
tags: [systemd, power]
- name: Configure systemd-logind to prevent sleep on keys/lid
copy:
dest: /etc/systemd/logind.conf.d/prevent-sleep.conf
content: |
[Login]
HandleSuspendKey=ignore
HandleHibernateKey=ignore
HandleLidSwitch=ignore
owner: root
group: root
mode: '0644'
become: yes
notify: Restart systemd-logind
tags: [systemd, power]
- name: Configure systemd-sleep to disable sleep/hibernation
copy:
dest: /etc/systemd/sleep.conf.d/prevent-sleep.conf
content: |
[Sleep]
AllowSuspend=no
AllowHibernation=no
AllowSuspendThenHibernate=no
AllowHybridSleep=no
owner: root
group: root
mode: '0644'
become: yes
tags: [systemd, power]
- name: Ensure systemd directories exist
file:
path: "{{ item }}"
+44
View File
@@ -125,3 +125,47 @@
group: "{{ system_user }}"
mode: '0644'
tags: [chrome, webgpu]
- name: Fix kitty cursor keys by removing TERM override in .bashrc
ansible.builtin.lineinfile:
path: "/home/{{ system_user }}/.bashrc"
regexp: '^export TERM=xterm-256color'
line: '# export TERM=xterm-256color # Fix: allow kitty to set its own TERM for cursor keys'
state: present
become: yes
become_user: "{{ system_user }}"
tags: [kitty, shell]
- name: Ensure kitty configuration directory exists
ansible.builtin.file:
path: "/home/{{ system_user }}/.config/kitty"
state: directory
owner: "{{ system_user }}"
group: "{{ system_user }}"
mode: '0755'
become: yes
become_user: "{{ system_user }}"
tags: [kitty]
- name: Configure kitty for proper terminal behavior
ansible.builtin.copy:
dest: "/home/{{ system_user }}/.config/kitty/kitty.conf"
content: |
# Managed by Ansible - saTway DevOps
shell bash
editor vim
# Shell integration is enabled by default in kitty 0.24.0+
# but we can explicitly enable it for clarity.
shell_integration enabled
# Ensure cursor keys work correctly by not overriding TERM in shell
# (Handled via .bashrc fix)
include /home/mw/.config/kitty/kitty-cline-keybindings.conf
owner: "{{ system_user }}"
group: "{{ system_user }}"
mode: '0644'
become: yes
become_user: "{{ system_user }}"
tags: [kitty]
+4
View File
@@ -0,0 +1,4 @@
---
- name: rebuild initramfs
command: mkinitcpio -P
become: yes
+73
View File
@@ -0,0 +1,73 @@
---
- name: Hyprland - install packages
community.general.pacman:
name: "{{ hyprland_packages }}"
state: present
update_cache: yes
tags: [hyprland, packages]
- name: Hyprland - ensure configuration directories exist
file:
path: "/home/{{ system_user }}/.config/{{ item }}"
state: directory
owner: "{{ system_user }}"
group: "{{ system_user }}"
mode: '0755'
loop:
- hypr
- waybar
- wofi
- dunst
tags: [hyprland, config]
- name: Hyprland - configure NVIDIA DRM KMS (modeset=1)
lineinfile:
path: /etc/modprobe.d/nvidia.conf
line: "options nvidia-drm modeset=1"
create: yes
notify: rebuild initramfs
tags: [hyprland, nvidia]
- name: Hyprland - check if NVIDIA modules are already in mkinitcpio
command: grep -q "nvidia nvidia_modeset nvidia_uvm nvidia_drm" /etc/mkinitcpio.conf
register: mkinitcpio_check
failed_when: false
changed_when: false
tags: [hyprland, nvidia]
- name: Hyprland - configure early module loading in mkinitcpio
replace:
path: /etc/mkinitcpio.conf
regexp: '^MODULES=\((.*)\)'
replace: 'MODULES=(nvidia nvidia_modeset nvidia_uvm nvidia_drm \1)'
when: mkinitcpio_check.rc != 0
notify: rebuild initramfs
tags: [hyprland, nvidia]
- name: Hyprland - register session in SDDM
copy:
dest: /usr/share/wayland-sessions/hyprland.desktop
content: |
[Desktop Entry]
Name=Hyprland
Comment=An intelligent dynamic tiling Wayland compositor
Exec=Hyprland
Type=Application
DesktopNames=Hyprland
tags: [hyprland, session]
- name: Hyprland - template core configuration
template:
src: "{{ item.src }}"
dest: "/home/{{ system_user }}/.config/{{ item.dest }}"
owner: "{{ system_user }}"
group: "{{ system_user }}"
mode: '0644'
loop:
- { src: 'hyprland.conf.j2', dest: 'hypr/hyprland.conf' }
- { src: 'hyprlock.conf.j2', dest: 'hypr/hyprlock.conf' }
- { src: 'hypridle.conf.j2', dest: 'hypr/hypridle.conf' }
- { src: 'waybar_config.j2', dest: 'waybar/config' }
- { src: 'waybar_style.css.j2', dest: 'waybar/style.css' }
- { src: 'dunstrc.j2', dest: 'dunst/dunstrc' }
tags: [hyprland, config]
@@ -0,0 +1,65 @@
[global]
monitor = 0
follow = mouse
width = 300
height = 300
origin = top-right
offset = 10x10
scale = 0
notification_limit = 0
progress_bar = true
progress_bar_height = 10
progress_bar_frame_width = 1
progress_bar_min_width = 150
progress_bar_max_width = 300
indicate_hidden = yes
transparency = 0
separator_height = 2
padding = 8
horizontal_padding = 8
text_icon_padding = 0
frame_width = 3
frame_color = "#aaaaaa"
separator_color = frame
sort = yes
idle_threshold = 120
font = Monospace 12
line_height = 0
markup = full
format = "<b>%s</b>\n%b"
alignment = left
vertical_alignment = center
show_age_threshold = 60
ellipsize = middle
ignore_newline = no
stack_duplicates = true
hide_duplicate_count = false
show_indicators = yes
icon_position = left
min_icon_size = 0
max_icon_size = 32
sticky_history = yes
history_length = 20
dmenu = /usr/bin/wofi -dmenu -p dunst:
browser = /usr/bin/xdg-open
always_run_script = true
title = Dunst
class = Dunst
corner_radius = 10
ignore_dbusclose = false
[urgency_low]
background = "#222222"
foreground = "#888888"
timeout = 10
[urgency_normal]
background = "#285577"
foreground = "#ffffff"
timeout = 20
[urgency_critical]
background = "#900000"
foreground = "#ffffff"
frame_color = "#ff0000"
timeout = 0
@@ -0,0 +1,16 @@
general {
lock_cmd = pidof hyprlock || hyprlock
before_sleep_cmd = loginctl lock-session
after_sleep_cmd = hyprctl dispatch dpms on
}
listener {
timeout = 300
on-timeout = loginctl lock-session
}
listener {
timeout = 330
on-timeout = hyprctl dispatch dpms off
on-resume = hyprctl dispatch dpms on
}
@@ -0,0 +1,102 @@
# hyprland.conf - managed by Ansible
# Monitor layout (left to right):
# 1. DP-1 TERRA 3280W 2560x1440@144 (0x0)
# 2. DVI-D-1 BenQ BL2405 1920x1080@60 (rotated 90° CW, effective width 1080)
# 3. HDMI-A-1 TERRA 3280W 2560x1440@144 (3640x0)
monitor=DP-1,2560x1440@144,0x0,1
monitor=DVI-D-1,1920x1080@60,2560x0,1,transform,1
monitor=HDMI-A-1,2560x1440@144,3640x0,1
# Execute on start
exec-once = waybar
exec-once = dunst
exec-once = hypridle
exec-once = wl-paste --type text --watch cliphist store
exec-once = wl-paste --type image --watch cliphist store
# Environment variables for NVIDIA
env = LIBVA_DRIVER_NAME,nvidia
env = XDG_SESSION_TYPE,wayland
env = GBM_BACKEND,nvidia-drm
env = __GLX_VENDOR_LIBRARY_NAME,nvidia
env = WLR_NO_HARDWARE_CURSORS,1
env = NVD_BACKEND,direct
# Input
input {
kb_layout = de
follow_mouse = 1
sensitivity = 0
}
# General
general {
gaps_in = 5
gaps_out = 10
border_size = 2
col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg
col.inactive_border = rgba(595959aa)
layout = dwindle
}
# Decoration
decoration {
rounding = 10
blur {
enabled = true
size = 3
passes = 1
}
}
# Animations
animations {
enabled = yes
}
# Dwindle layout
dwindle {
preserve_split = yes
}
# Keybindings
$mainMod = SUPER
bind = $mainMod, Q, exec, kitty
bind = $mainMod, C, killactive,
bind = $mainMod, M, exit,
bind = $mainMod, E, exec, dolphin
bind = $mainMod, V, togglefloating,
bind = $mainMod, D, exec, wofi --show drun
bind = $mainMod, P, pseudo
bind = $mainMod, J, layoutmsg, togglesplit
bind = $mainMod, L, exec, hyprlock
# Focus
bind = $mainMod, left, movefocus, l
bind = $mainMod, right, movefocus, r
bind = $mainMod, up, movefocus, u
bind = $mainMod, down, movefocus, d
# Workspaces
bind = $mainMod, 1, workspace, 1
bind = $mainMod, 2, workspace, 2
bind = $mainMod, 3, workspace, 3
bind = $mainMod, 4, workspace, 4
bind = $mainMod, 5, workspace, 5
bind = $mainMod, 6, workspace, 6
bind = $mainMod, 7, workspace, 7
bind = $mainMod, 8, workspace, 8
bind = $mainMod, 9, workspace, 9
# Move to workspace
bind = $mainMod SHIFT, 1, movetoworkspace, 1
bind = $mainMod SHIFT, 2, movetoworkspace, 2
bind = $mainMod SHIFT, 3, movetoworkspace, 3
bind = $mainMod SHIFT, 4, movetoworkspace, 4
bind = $mainMod SHIFT, 5, movetoworkspace, 5
bind = $mainMod SHIFT, 6, movetoworkspace, 6
bind = $mainMod SHIFT, 7, movetoworkspace, 7
bind = $mainMod SHIFT, 8, movetoworkspace, 8
bind = $mainMod SHIFT, 9, movetoworkspace, 9
@@ -0,0 +1,29 @@
general {
no_fade_in = false
grace = 0
disable_loading = true
}
background {
monitor =
color = rgba(25, 20, 20, 1.0)
blur_passes = 2
}
input-field {
monitor =
size = 200, 50
outline_thickness = 3
dots_size = 0.33
dots_spacing = 0.15
dots_center = true
outer_color = rgb(151515)
inner_color = rgb(200, 200, 200)
font_color = rgb(10, 10, 10)
fade_on_empty = true
placeholder_text = <i>Input Password...</i>
hide_input = false
position = 0, -20
halign = center
valign = center
}
@@ -0,0 +1,29 @@
{
"layer": "top",
"position": "top",
"height": 30,
"modules-left": ["hyprland/workspaces", "hyprland/mode"],
"modules-center": ["hyprland/window"],
"modules-right": ["network", "cpu", "memory", "clock", "tray"],
"hyprland/workspaces": {
"disable-scroll": true,
"all-outputs": true,
"format": "{name}"
},
"clock": {
"format": "{:%Y-%m-%d %H:%M}",
"tooltip-format": "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>"
},
"cpu": {
"format": "CPU {usage}%",
"tooltip": false
},
"memory": {
"format": "RAM {}%"
},
"network": {
"format-wifi": " {essid} ({signalStrength}%)",
"format-ethernet": "󰈀 {ifname}",
"format-disconnected": "⚠ Disconnected"
}
}
@@ -0,0 +1,26 @@
* {
border: none;
border-radius: 0;
font-family: monospace;
font-size: 14px;
}
window#waybar {
background: rgba(43, 48, 59, 0.5);
color: #ffffff;
}
#workspaces button {
padding: 0 5px;
background: transparent;
color: #ffffff;
}
#workspaces button.active {
background: #64727D;
border-bottom: 3px solid #ffffff;
}
#clock, #cpu, #memory, #network, #tray {
padding: 0 10px;
}
@@ -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]
+56
View File
@@ -9,3 +9,59 @@ os_type: archlinux
pnpm_version: "25.6.1"
python_version: "3.14.3"
php_version: "8.5.3"
# ---------------------------------------------------------------------------
# system-upgrade role: kernel + NVIDIA preparation for Hyprland/Wayland
# ---------------------------------------------------------------------------
# Strategy: keep linux618 as fallback, add linux70 (7.0.x stable)
# as the new daily-driver kernel. Migrate NVIDIA from the pinned 575xx branch
# to the 580xx branch (NFB) for Pascal compatibility.
# See: docs/adr/0002-kernel-and-nvidia-strategy.md
kernel_packages:
- linux618
- linux618-headers
- linux70
- linux70-headers
nvidia_kernel_modules:
- linux618-nvidia-580xx
- linux70-nvidia-580xx
nvidia_userspace_packages:
- nvidia-580xx-utils
- lib32-nvidia-580xx-utils
- opencl-nvidia-580xx
- nvidia-580xx-settings
nvidia_packages_to_remove:
- linux612-nvidia-575xx
- nvidia-575xx-utils
- lib32-nvidia-575xx-utils
- nvidia-575xx-settings
- opencl-nvidia
# Pre-flight: refuse to proceed if free space on / drops below this many GiB.
# Current usage is ~87% so we keep this conservative.
min_free_root_gb: 5
# ---------------------------------------------------------------------------
# hyprland role: packages and configuration
# ---------------------------------------------------------------------------
hyprland_packages:
- hyprland
- hyprlock
- hypridle
- hyprshot
- hyprcursor
- xdg-desktop-portal-hyprland
- waybar
- wofi
- dunst
- kitty
- cliphist
- wl-clipboard
- brightnessctl
- pipewire
- pipewire-pulse
- wireplumber
- pavucontrol
- dolphin
- egl-wayland
- qt5-wayland
- qt6-wayland
+5
View File
@@ -8,6 +8,11 @@
- vars/secrets.yml
roles:
- role: system-upgrade
tags: [system-upgrade]
when: os_type == 'archlinux'
- common
- dev-tools
- maintenance
- role: hyprland
tags: [hyprland]
+108
View File
@@ -0,0 +1,108 @@
---
description: >-
Decision to dual-install linux612 (LTS) + linux70 (stable) kernels and
migrate NVIDIA from the pinned 575xx branch to the mainline 595.71 branch
to prepare the workstation for Hyprland/Wayland.
tags:
- adr
- kernel
- nvidia
- hyprland
- wayland
last_updated: '2026-05-18'
---
# ADR 2: Kernel and NVIDIA Driver Strategy for Hyprland/Wayland
## Status
Accepted (2026-05-18)
## Context
The workstation runs Manjaro Linux on AMD Ryzen 7 2700X with an NVIDIA
GeForce GTX 1050 Ti (Pascal, GP107) driving three monitors. The next
infrastructure plan switches the desktop from KDE/X11 to Hyprland on
Wayland. Two preconditions must hold before that switch is safe:
1. **DRM/atomic and NVIDIA explicit-sync support** — Hyprland on NVIDIA
relies on driver-level explicit sync (introduced in 555.x and matured
through 575.x and 595.x). Without it, flickering and missed vblanks
are very common on Pascal+multi-monitor.
2. **Matching kernel modules** — Manjaro ships pre-built `*-nvidia`
modules pinned to a single `nvidia-utils` version. Mixing `575xx`
userspace with a kernel that only has `595.71` modules (or vice
versa) breaks the driver at the next kernel boot.
The starting point at the time of this ADR:
| Item | Version |
|---------------------------|------------------------|
| Running kernel | `linux612 6.12.77-1` |
| NVIDIA driver | `575.64.05-3` (575xx branch) |
| NVIDIA kernel module pkg | `linux612-nvidia-575xx 575.64.05-31` |
| `linux70` available in repo | `7.0.3-1` |
| `linux70-nvidia` available | `595.71.05-0.1` |
| `linux612-nvidia` (mainline) | `595.71.05-2` |
## Decision
1. **Run two kernels side by side.**
- Keep `linux612` (LTS 6.12.x) installed as the *fallback* boot entry.
- Install `linux70` (stable 7.0.x) and select it as the daily-driver
kernel via GRUB at the next reboot.
2. **Migrate NVIDIA from the `575xx` branch to mainline `595.71`** in
one atomic pacman transaction so both kernels have matching modules:
- Remove: `linux612-nvidia-575xx`, `nvidia-575xx-utils`,
`lib32-nvidia-575xx-utils`, `nvidia-575xx-settings`.
- Install: `linux612-nvidia`, `linux70-nvidia`, `nvidia-utils`,
`lib32-nvidia-utils`, `opencl-nvidia`, `nvidia-settings`.
3. **Do not change `GRUB_DEFAULT` programmatically.** The user picks
`linux70` from the GRUB menu on first boot. If `linux70` misbehaves,
the next reboot is one menu selection away from the proven LTS path.
4. **Encapsulate everything in an idempotent Ansible role**
(`ansible/roles/system-upgrade/`) that runs **before** `common`,
`dev-tools` and `maintenance`. It is invoked stand-alone with
`--tags system-upgrade` and is safe on every subsequent run.
## Rationale
- **LTS as a parachute.** 6.12 is a Linux LTS series with a multi-year
support window. Keeping it installed makes the migration reversible by
one keystroke in GRUB.
- **7.0 as the daily driver.** The 7.0 series brings the maturest
DRM/atomic-modeset and NVIDIA explicit-sync paths that Hyprland on
Pascal needs to avoid flicker on multi-monitor.
- **Mainline 595.71 over pinned 575.64.05.** Manjaro builds
`linux70-nvidia` only against `nvidia-utils=595.71.05`, so the 575xx
branch is not an option for the new kernel. Pascal (GP107) is fully
supported by 595.x, and 595.x ships the latest explicit-sync /
modesetting fixes upstream.
- **Single atomic transaction.** Removing the old branch and installing
the new one in one pacman call (with the removal list narrowed to
packages that are *actually* installed) keeps the GPU-driver window
to roughly one second of cache I/O on a local SSD.
- **Idempotent role over shell script.** Matches the existing
`common`/`dev-tools`/`maintenance` pattern, supports `--check`, and
re-running after success is a true no-op (validated by gating every
mutating task on `pacman_syu.changed or nvidia_migration.changed`).
## Consequences
- **Two kernels on `/` partition.** Roughly +120 MB for `linux70` plus
~50 MB for `linux70-nvidia`. The role pre-flight refuses to start if
free space on `/` is below 5 GiB.
- **`mhwd` profile referencing `575xx` stays as-is.** Only the
kernel-bound `linux612-nvidia-575xx` and matching userspace are
swapped; `mhwd-nvidia-575xx` stays installed so MHWD's profile
listing remains consistent. A future cleanup can switch the MHWD
profile to `video-nvidia` once `linux70` is verified.
- **Manual reboot required.** The role never reboots the machine; it
ends with a clear notice instructing the user to boot into `linux70`
from GRUB before applying the next (Hyprland) playbook.
- **Future kernel pin bumps are one-line changes** in
`ansible/vars/main.yml` (`kernel_packages` /
`nvidia_kernel_modules` lists).
## References
- Hyprland NVIDIA notes (wiki.hypr.land/Nvidia): explicit sync
available from driver ≥555 and required for tear-free Wayland.
- `docs/tech/hardware-inventory.md` (NVIDIA section).
- `ansible/roles/system-upgrade/tasks/main.yml`.
- ADR 1: `docs/adr/0001-use-ansible-for-configuration.md` — anchors the
"Ansible-first" guardrail this decision follows.
+7 -1
View File
@@ -100,10 +100,16 @@ last_updated: '2026-04-02'
|-------|-------|
| Model | NVIDIA GeForce GTX 1050 Ti |
| VRAM | 4096 MiB |
| Driver | 575.64.05 |
| Driver | 575.64.05 (migrating to 595.71.05 — see [ADR 0002](../adr/0002-kernel-and-nvidia-strategy.md)) |
| GPU Temp (idle) | 47°C |
| PCI Address | 08:00.0 |
> **Driver strategy**: This Pascal-generation GPU is being migrated from the
> pinned Manjaro `*-nvidia-575xx` branch to the mainline `*-nvidia` 595.71
> branch as part of the Hyprland/Wayland preparation. The migration is
> automated by the `system-upgrade` Ansible role and documented in
> [ADR 0002 — Kernel and NVIDIA Driver Strategy for Hyprland/Wayland](../adr/0002-kernel-and-nvidia-strategy.md).
## Network
### Physical
+27 -3
View File
@@ -5,7 +5,7 @@ tags:
- setup
- bootstrap
- ansible
last_updated: '2026-03-09'
last_updated: '2026-05-18'
---
# Workstation Setup Guide
@@ -41,14 +41,38 @@ This guide describes how to bootstrap the **mw-pfeddersheim-workstation** infras
ansible-vault create ansible/vars/secrets.yml
```
4. **Run the Playbook**
4. **Run system upgrade & kernel install** (preparation for Hyprland/Wayland)
This step runs the dedicated `system-upgrade` role: full `pacman -Syu`,
dual-kernel install (`linux612` LTS + `linux70` stable), and NVIDIA
driver migration from the pinned `575xx` branch to mainline `595.71`.
Always preview first with `--check`. See
[ADR 0002](../adr/0002-kernel-and-nvidia-strategy.md) for the rationale.
```bash
# Preview — never run the live playbook before --check is clean
ansible-playbook -i localhost, -c local \
--vault-password-file ansible/vault_password \
ansible/workstation.yml --check --tags system-upgrade
# Apply (will install/remove packages and regenerate GRUB)
ansible-playbook -i localhost, -c local \
--vault-password-file ansible/vault_password \
ansible/workstation.yml --tags system-upgrade
```
After it finishes, **reboot and select `linux70` from the GRUB menu**
before continuing to step 5. Verify with `uname -r` (expect 7.0.x)
and `nvidia-smi` (expect driver 595.71.05).
5. **Run the Playbook** (remaining roles: common, dev-tools, maintenance)
```bash
ansible-playbook -i localhost, -c local --vault-password-file ansible/vault_password ansible/workstation.yml --check
# If check passes:
ansible-playbook -i localhost, -c local --vault-password-file ansible/vault_password ansible/workstation.yml
```
5. **Initialize Maintenance**
6. **Initialize Maintenance**
```bash
./scripts/maintenance.sh
```
+116
View File
@@ -0,0 +1,116 @@
#!/bin/bash
# install-hyprland.sh - Install Hyprland with Wayland on Manjaro (NVIDIA)
# Run: bash scripts/install-hyprland.sh
set -euo pipefail
echo "=== Hyprland Installation for mw-manjaro-pf ==="
echo "GPU: NVIDIA GTX 1050 Ti (595.71.05)"
echo "Monitors: 3x (DP-1 + HDMI-A-1 + DVI-D-1)"
echo ""
# Step 1: Install core packages
echo "[1/5] Installing Hyprland and core Wayland packages..."
sudo pacman -S --needed --noconfirm \
hyprland \
hyprlock \
hypridle \
hyprshot \
hyprcursor \
xdg-desktop-portal-hyprland \
waybar \
wofi \
dunst \
kitty \
cliphist \
wl-clipboard \
brightnessctl \
pipewire \
pipewire-pulse \
wireplumber \
pavucontrol
echo "[1/5] Done."
# Step 2: NVIDIA-specific package verification
echo "[2/5] Verifying NVIDIA packages..."
pacman -Q nvidia-utils >/dev/null 2>&1 && echo " nvidia-utils: OK" || echo " nvidia-utils: MISSING"
pacman -Q egl-wayland >/dev/null 2>&1 && echo " egl-wayland: OK" || echo " egl-wayland: MISSING"
echo "[2/5] Done."
# Step 3: Config directory (already created)
echo "[3/5] Verifying configuration..."
if [ -f ~/.config/hypr/hyprland.conf ]; then
echo " hyprland.conf: OK ($(wc -l < ~/.config/hypr/hyprland.conf) lines)"
else
echo " hyprland.conf: MISSING - create it first!"
exit 1
fi
echo "[3/5] Done."
# Step 4: Enable DRM KMS for NVIDIA (critical for Wayland)
echo "[4/5] Configuring NVIDIA DRM KMS..."
if grep -q "nvidia-drm.modeset=1" /etc/modprobe.d/*.conf 2>/dev/null; then
echo " nvidia-drm.modeset=1: Already configured"
else
echo " Adding nvidia-drm.modeset=1 to /etc/modprobe.d/nvidia.conf..."
echo "options nvidia-drm modeset=1" | sudo tee -a /etc/modprobe.d/nvidia.conf
echo " IMPORTANT: Reboot required after installation."
fi
# Early loading of NVIDIA modules
if grep -q "nvidia" /etc/mkinitcpio.conf 2>/dev/null; then
echo " mkinitcpio modules: Already configured"
else
echo " Adding NVIDIA modules to mkinitcpio..."
sudo sed -i 's/^MODULES=(/MODULES=(nvidia nvidia_modeset nvidia_uvm nvidia_drm /' /etc/mkinitcpio.conf
sudo mkinitcpio -P
fi
echo "[4/5] Done."
# Step 5: Register Hyprland session
echo "[5/5] Verifying session registration..."
if [ -f /usr/share/wayland-sessions/hyprland.desktop ]; then
echo " Session file: OK"
else
echo " Session file: Creating..."
sudo tee /usr/share/wayland-sessions/hyprland.desktop > /dev/null << 'DESKTOP'
[Desktop Entry]
Name=Hyprland
Comment=An intelligent dynamic tiling Wayland compositor
Exec=Hyprland
Type=Application
DesktopNames=Hyprland
DESKTOP
fi
echo "[5/5] Done."
echo ""
echo "=== Installation Complete ==="
echo ""
echo "NEXT STEPS:"
echo " 1. REBOOT to apply NVIDIA DRM KMS changes"
echo " 2. Select 'Hyprland' from the login manager (SDDM)"
echo " 3. If monitors are wrong, check names with: hyprctl monitors"
echo " and adjust in ~/.config/hypr/hyprland.conf"
echo ""
echo "MONITOR NAMES may differ from X11:"
echo " X11 name -> Wayland name (expected)"
echo " DP-0 -> DP-1"
echo " HDMI-0 -> HDMI-A-1"
echo " DVI-D-0 -> DVI-D-1"
echo ""
echo "KEY BINDINGS (SUPER = Windows key):"
echo " SUPER+Return = Terminal (kitty)"
echo " SUPER+D = Launcher (wofi)"
echo " SUPER+E = File manager (dolphin)"
echo " SUPER+L = Lock screen"
echo " SUPER+Q = Close window"
echo " SUPER+F = Fullscreen"
echo " SUPER+1-9 = Switch workspace"
echo " SUPER+Arrows = Focus monitor"
echo ""
echo "TROUBLESHOOTING:"
echo " - Cursor issues: WLR_NO_HARDWARE_CURSORS=1 is set"
echo " - Blank screen: Check 'nvidia-drm.modeset=1' in kernel params"
echo " - Flickering: Try disabling blur in hyprland.conf"
echo " - Monitor names: Run 'hyprctl monitors' after first launch"