Compare commits
6
Commits
f1544747a0
...
07bd3f8699
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07bd3f8699 | ||
|
|
feaccf201d | ||
|
|
5a714e6256 | ||
|
|
d9392447f9 | ||
|
|
de24990208 | ||
|
|
fe7f133136 |
@@ -14,6 +14,10 @@ This repository manages the infrastructure, configuration, and maintenance for M
|
||||
|------|---------|
|
||||
| Apply Configuration | `ansible-playbook ansible/workstation.yml` |
|
||||
| System Maintenance | `bash scripts/maintenance.sh` |
|
||||
| Package Cleanup (dry-run) | `bash scripts/cleanup.sh --check` |
|
||||
| Package Cleanup (apply) | `bash scripts/cleanup.sh --apply` |
|
||||
| NVIDIA Recovery (broken GUI) | `bash scripts/nvidia-recovery.sh --apply` |
|
||||
| **Post-recovery** | **Reboot required to restore GUI login** |
|
||||
| Verify Ansible | `ansible-playbook ansible/workstation.yml --check` |
|
||||
|
||||
## Standards
|
||||
@@ -31,3 +35,84 @@ This repository manages the infrastructure, configuration, and maintenance for M
|
||||
- Global rules: `Rules/`
|
||||
- Project rules: `.clinerules/`
|
||||
- Agent instructions: `AGENTS.md` (this file)
|
||||
|
||||
# DOX Framework
|
||||
|
||||
## Core Contract
|
||||
|
||||
- AGENTS.md files are binding work contracts for their subtrees
|
||||
- Work products, source materials, instructions, records, assets, and durable docs must stay understandable from the nearest applicable AGENTS.md plus every parent AGENTS.md above it
|
||||
|
||||
## Read Before Editing
|
||||
|
||||
1. Read the root AGENTS.md
|
||||
2. Identify every file or folder you expect to touch
|
||||
3. Walk from the repository root to each target path
|
||||
4. Read every AGENTS.md found along each route
|
||||
5. If a parent AGENTS.md lists a child AGENTS.md whose scope contains the path, read that child and continue from there
|
||||
6. Use the nearest AGENTS.md as the local contract and parent docs for repo-wide rules
|
||||
7. If docs conflict, the closer doc controls local work details, but no child doc may weaken DOX
|
||||
|
||||
Do not rely on memory. Re-read the applicable DOX chain in the current session before editing.
|
||||
|
||||
## Update After Editing
|
||||
|
||||
Every meaningful change requires a DOX pass before the task is done.
|
||||
|
||||
Update the closest owning AGENTS.md when a change affects:
|
||||
|
||||
- purpose, scope, ownership, or responsibilities
|
||||
- durable structure, contracts, workflows, or operating rules
|
||||
- required inputs, outputs, permissions, constraints, side effects, or artifacts
|
||||
- user preferences about behavior, communication, process, organization, or quality
|
||||
- AGENTS.md creation, deletion, move, rename, or index contents
|
||||
|
||||
Update parent docs when parent-level structure, ownership, workflow, or child index changes. Update child docs when parent changes alter local rules. Remove stale or contradictory text immediately. Small edits that do not change behavior or contracts may leave docs unchanged, but the DOX pass still must happen.
|
||||
|
||||
## Hierarchy
|
||||
|
||||
- Root AGENTS.md is the DOX rail: project-wide instructions, global preferences, durable workflow rules, and the top-level Child DOX Index
|
||||
- Child AGENTS.md files own domain-specific instructions and their own Child DOX Index
|
||||
- Each parent explains what its direct children cover and what stays owned by the parent
|
||||
- The closer a doc is to the work, the more specific and practical it must be
|
||||
|
||||
## Child Doc Shape
|
||||
|
||||
- Create a child AGENTS.md when a folder becomes a durable boundary with its own purpose, rules, responsibilities, workflow, materials, or quality standards
|
||||
- Work Guidance must reflect the current standards of the project or user instructions; if there are no specific standards or instructions yet, leave it empty
|
||||
- Verification must reflect an existing check; if no verification framework exists yet, leave it empty and update it when one exists
|
||||
|
||||
Default section order:
|
||||
- Purpose
|
||||
- Ownership
|
||||
- Local Contracts
|
||||
- Work Guidance
|
||||
- Verification
|
||||
- Child DOX Index
|
||||
|
||||
## Style
|
||||
|
||||
- Keep docs concise, current, and operational
|
||||
- Document stable contracts, not diary entries
|
||||
- Put broad rules in parent docs and concrete details in child docs
|
||||
- Prefer direct bullets with explicit names
|
||||
- Do not duplicate rules across many files unless each scope needs a local version
|
||||
- Delete stale notes instead of explaining history
|
||||
- Trim obvious statements, repeated rules, misplaced detail, and warnings for risks that no longer exist
|
||||
|
||||
## Closeout
|
||||
|
||||
1. Re-check changed paths against the DOX chain
|
||||
2. Update nearest owning docs and any affected parents or children
|
||||
3. Refresh every affected Child DOX Index
|
||||
4. Remove stale or contradictory text
|
||||
5. Run existing verification when relevant
|
||||
6. Report any docs intentionally left unchanged and why
|
||||
|
||||
## User Preferences
|
||||
|
||||
When the user requests a durable behavior change, record it here or in the relevant child AGENTS.md
|
||||
|
||||
## Child DOX Index
|
||||
|
||||
No child AGENTS.md files found. The root AGENTS.md covers the entire project scope.
|
||||
|
||||
@@ -8,7 +8,7 @@ tags:
|
||||
- infrastructure-as-code
|
||||
- linux
|
||||
- automation
|
||||
last_updated: '2026-03-17'
|
||||
last_updated: '2026-06-17'
|
||||
---
|
||||
|
||||
# saTway® Infrastructure - mw-pfeddersheim-workstation
|
||||
@@ -34,25 +34,22 @@ bash scripts/daily-routine.sh morning-full
|
||||
bash scripts/daily-routine.sh eod
|
||||
```
|
||||
|
||||
## 📊 Current Status (2026-03-17)
|
||||
|
||||
- **System Health**: ✅ Healthy (Load: 0.40, Disk: 84% used, Memory: 19% used, Swap: 16GB)
|
||||
- **Active Containers**: 6 running (with memory limits)
|
||||
- **Network**: Tailscale active
|
||||
|
||||
## 📂 Project Structure
|
||||
|
||||
| Directory | Purpose |
|
||||
|-----------|---------|
|
||||
| `ansible/` | Configuration-as-Code (Roles: `common`, `dev-tools`, `maintenance`) |
|
||||
| `ansible/` | Configuration-as-Code (Roles: `system-upgrade`, `common`, `dev-tools`, `maintenance`, `fonts`, `pdf`, `hyprland`, `greetd`) |
|
||||
| `config/` | System configuration templates (`.j2`) |
|
||||
| `docs/` | PARA-structured documentation (`product`, `adr`, `learnings`, `tech`) |
|
||||
| `docs/` | PARA-structured documentation (`product`, `adr`, `learnings`, `tech`, `workflows`, `plans`) |
|
||||
| `scripts/` | Maintenance and automation scripts |
|
||||
|
||||
## 🛠 Software Stack
|
||||
|
||||
- **Languages**: Node.js 22 LTS, Python 3.12+, PHP 8.4
|
||||
- **Languages**: Node.js 25.6.1, Python 3.14.3, PHP 8.5.3 (CLI)
|
||||
- **Automation**: Ansible, Docker
|
||||
- **Desktop**: Hyprland (Wayland), greetd + tuigreet, Waybar, Kitty
|
||||
- **Kernel**: linux618 (LTS fallback) + linux70 (daily driver)
|
||||
- **NVIDIA**: 580xx branch (Pascal/GT 1050 Ti)
|
||||
- **Environment**: Manjaro/Arch Linux, Bash
|
||||
|
||||
## 📜 Core Principles
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
file:
|
||||
path: "{{ ansible_env.HOME }}/.config/kitty"
|
||||
state: directory
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
owner: "{{ local_user }}"
|
||||
group: "{{ local_user }}"
|
||||
mode: '0755'
|
||||
tags: [kitty, terminal]
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
template:
|
||||
src: kitty.conf.j2
|
||||
dest: "{{ ansible_env.HOME }}/.config/kitty/kitty.conf"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: "{{ ansible_user }}"
|
||||
owner: "{{ local_user }}"
|
||||
group: "{{ local_user }}"
|
||||
mode: '0644'
|
||||
vars:
|
||||
kitty_config_dir: "{{ ansible_env.HOME }}/.config/kitty"
|
||||
|
||||
@@ -86,6 +86,14 @@
|
||||
changed_when: false
|
||||
tags: [python, pipx]
|
||||
|
||||
- name: Ensure JetBrains Toolbox and IntelliJ IDEA Ultimate are installed from AUR
|
||||
become: true
|
||||
become_user: "{{ system_user }}"
|
||||
command: paru -S --noconfirm --needed jetbrains-toolbox intellij-idea-ultimate-edition
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
tags: [jetbrains, aur]
|
||||
|
||||
- name: Ensure redundant and non-core software is removed
|
||||
community.general.pacman:
|
||||
name:
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
group: "{{ system_user }}"
|
||||
mode: '0644'
|
||||
loop:
|
||||
- { src: 'hyprland.conf.j2', dest: 'hypr/hyprland.conf' }
|
||||
- { src: 'hyprland.lua.j2', dest: 'hypr/hyprland.lua' }
|
||||
- { src: 'hyprlock.conf.j2', dest: 'hypr/hyprlock.conf' }
|
||||
- { src: 'hypridle.conf.j2', dest: 'hypr/hypridle.conf' }
|
||||
- { src: 'hyprpaper.conf.j2', dest: 'hypr/hyprpaper.conf' }
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
-- =========================================================================
|
||||
-- hyprland.lua - managed by Ansible
|
||||
-- Hyprland 0.55+ Lua config for mw-pfeddersheim-workstation
|
||||
-- SUPER = Windows key
|
||||
-- =========================================================================
|
||||
|
||||
local terminal = "kitty"
|
||||
local fileManager = "kitty -e yazi"
|
||||
local menu = "hyprlauncher"
|
||||
local browser = "google-chrome-stable"
|
||||
local mainMod = "SUPER"
|
||||
|
||||
-----------------
|
||||
---- MONITORS ----
|
||||
-----------------
|
||||
|
||||
-- 1. DP-1 TERRA 3280W 2560x1440@144 (0x0)
|
||||
-- 2. DVI-D-1 BenQ BL2405 1920x1080@60 (rotated 90 CW)
|
||||
-- 3. HDMI-A-1 TERRA 3280W 2560x1440@144 (3640x0)
|
||||
hl.monitor({ output = "DP-1", mode = "2560x1440@60", position = "0x0", scale = "1" })
|
||||
hl.monitor({ output = "DVI-D-1", mode = "1920x1080@60", position = "2560x0", scale = "1", transform = "1" })
|
||||
hl.monitor({ output = "HDMI-A-1", mode = "2560x1440@60", position = "3640x0", scale = "1" })
|
||||
|
||||
hl.workspace_rule({ workspace = 1, monitor = "DP-1", default = true })
|
||||
hl.workspace_rule({ workspace = 2, monitor = "DVI-D-1", default = true })
|
||||
hl.workspace_rule({ workspace = 3, monitor = "HDMI-A-1", default = true })
|
||||
|
||||
-------------------------------
|
||||
---- ENVIRONMENT VARIABLES ----
|
||||
-------------------------------
|
||||
|
||||
hl.env("LIBVA_DRIVER_NAME", "nvidia")
|
||||
hl.env("XDG_SESSION_TYPE", "wayland")
|
||||
hl.env("GBM_BACKEND", "nvidia-drm")
|
||||
hl.env("__GLX_VENDOR_LIBRARY_NAME", "nvidia")
|
||||
hl.env("WLR_NO_HARDWARE_CURSORS", "1")
|
||||
hl.env("NVD_BACKEND", "direct")
|
||||
|
||||
---------------
|
||||
---- INPUT ----
|
||||
---------------
|
||||
|
||||
hl.config({
|
||||
input = {
|
||||
kb_layout = "de",
|
||||
follow_mouse = 1,
|
||||
sensitivity = 0,
|
||||
numlock_by_default = true,
|
||||
},
|
||||
})
|
||||
|
||||
hl.device({ name = "pixart-usb-optical-mouse", left_handed = 1 })
|
||||
|
||||
-----------------------
|
||||
---- LOOK AND FEEL ----
|
||||
-----------------------
|
||||
|
||||
hl.config({
|
||||
general = {
|
||||
gaps_in = 5, gaps_out = 10, border_size = 2,
|
||||
col = {
|
||||
active_border = { colors = {"rgba(33ccffee)", "rgba(00ff99ee)"}, angle = 45 },
|
||||
inactive_border = "rgba(595959aa)",
|
||||
},
|
||||
resize_on_border = false, allow_tearing = false, layout = "dwindle",
|
||||
},
|
||||
decoration = {
|
||||
rounding = 10, rounding_power = 2,
|
||||
active_opacity = 1.0, inactive_opacity = 1.0,
|
||||
shadow = { enabled = true, range = 4, render_power = 3, color = 0xee1a1a1a },
|
||||
blur = { enabled = true, size = 3, passes = 1, vibrancy = 0.1696 },
|
||||
},
|
||||
animations = { enabled = true },
|
||||
})
|
||||
|
||||
hl.curve("easeOutQuint", { type = "bezier", points = { {0.23,1},{0.32,1} } })
|
||||
hl.curve("easeInOutCubic", { type = "bezier", points = { {0.65,0.05},{0.36,1} } })
|
||||
hl.curve("linear", { type = "bezier", points = { {0,0},{1,1} } })
|
||||
hl.curve("almostLinear", { type = "bezier", points = { {0.5,0.5},{0.75,1} } })
|
||||
hl.curve("quick", { type = "bezier", points = { {0.15,0},{0.1,1} } })
|
||||
hl.curve("easy", { type = "spring", mass = 1, stiffness = 71.2633, dampening = 15.8273644 })
|
||||
|
||||
hl.animation({ leaf = "global", enabled = true, speed = 10, bezier = "default" })
|
||||
hl.animation({ leaf = "border", enabled = true, speed = 5.39, bezier = "easeOutQuint" })
|
||||
hl.animation({ leaf = "windows", enabled = true, speed = 4.79, spring = "easy" })
|
||||
hl.animation({ leaf = "windowsIn", enabled = true, speed = 4.1, spring = "easy", style = "popin 87%" })
|
||||
hl.animation({ leaf = "windowsOut", enabled = true, speed = 1.49, bezier = "linear", style = "popin 87%" })
|
||||
hl.animation({ leaf = "fadeIn", enabled = true, speed = 1.73, bezier = "almostLinear" })
|
||||
hl.animation({ leaf = "fadeOut", enabled = true, speed = 1.46, bezier = "almostLinear" })
|
||||
hl.animation({ leaf = "fade", enabled = true, speed = 3.03, bezier = "quick" })
|
||||
hl.animation({ leaf = "layers", enabled = true, speed = 3.81, bezier = "easeOutQuint" })
|
||||
hl.animation({ leaf = "layersIn", enabled = true, speed = 4, bezier = "easeOutQuint", style = "fade" })
|
||||
hl.animation({ leaf = "layersOut", enabled = true, speed = 1.5, bezier = "linear", style = "fade" })
|
||||
hl.animation({ leaf = "fadeLayersIn", enabled = true, speed = 1.79, bezier = "almostLinear" })
|
||||
hl.animation({ leaf = "fadeLayersOut", enabled = true, speed = 1.39, bezier = "almostLinear" })
|
||||
hl.animation({ leaf = "workspaces", enabled = true, speed = 1.94, bezier = "almostLinear", style = "fade" })
|
||||
hl.animation({ leaf = "workspacesIn", enabled = true, speed = 1.21, bezier = "almostLinear", style = "fade" })
|
||||
hl.animation({ leaf = "workspacesOut", enabled = true, speed = 1.94, bezier = "almostLinear", style = "fade" })
|
||||
hl.animation({ leaf = "zoomFactor", enabled = true, speed = 7, bezier = "quick" })
|
||||
|
||||
hl.config({ dwindle = { preserve_split = true } })
|
||||
hl.config({ master = { new_status = "master" } })
|
||||
hl.config({ misc = { force_default_wallpaper = 0, disable_hyprland_logo = true } })
|
||||
|
||||
---------------------
|
||||
---- KEYBINDINGS ----
|
||||
---------------------
|
||||
|
||||
-- Core binds
|
||||
hl.bind(mainMod .. " + Q", hl.dsp.window.close())
|
||||
hl.bind(mainMod .. " + T", hl.dsp.exec_cmd(terminal))
|
||||
hl.bind(mainMod .. " + RETURN", hl.dsp.exec_cmd(terminal))
|
||||
hl.bind(mainMod .. " + M", hl.dsp.exec_cmd("command -v hyprshutdown >/dev/null 2>&1 && hyprshutdown || hyprctl dispatch 'hl.dsp.exit()'"))
|
||||
hl.bind(mainMod .. " + E", hl.dsp.exec_cmd(fileManager))
|
||||
hl.bind(mainMod .. " + V", hl.dsp.window.float({ action = "toggle" }))
|
||||
hl.bind(mainMod .. " + D", hl.dsp.exec_cmd(menu))
|
||||
hl.bind(mainMod .. " + P", hl.dsp.window.pseudo())
|
||||
hl.bind(mainMod .. " + J", hl.dsp.layout("togglesplit"))
|
||||
|
||||
-- Worktime safers
|
||||
hl.bind(mainMod .. " + F", hl.dsp.window.fullscreen({ mode = 0 }))
|
||||
hl.bind(mainMod .. " + R", hl.dsp.exec_cmd("hyprctl reload"))
|
||||
hl.bind(mainMod .. " + SPACE", hl.dsp.window.float({ action = "toggle" }))
|
||||
hl.bind(mainMod .. " + TAB", hl.dsp.exec_cmd("hyprctl dispatch cyclenext"))
|
||||
hl.bind(mainMod .. " + SHIFT + TAB", hl.dsp.exec_cmd("hyprctl dispatch cyclenext prev"))
|
||||
|
||||
-- Scratchpad
|
||||
hl.bind(mainMod .. " + GRAVE", hl.dsp.workspace.toggle_special("magic"))
|
||||
hl.bind(mainMod .. " + SHIFT + GRAVE", hl.dsp.window.move({ workspace = "special:magic" }))
|
||||
|
||||
-- Home row focus
|
||||
hl.bind(mainMod .. " + H", hl.dsp.focus({ direction = "left" }))
|
||||
hl.bind(mainMod .. " + L", hl.dsp.focus({ direction = "right" }))
|
||||
|
||||
-- Home row move window
|
||||
hl.bind(mainMod .. " + SHIFT + H", hl.dsp.window.move({ direction = "left" }))
|
||||
hl.bind(mainMod .. " + SHIFT + L", hl.dsp.window.move({ direction = "right" }))
|
||||
hl.bind(mainMod .. " + SHIFT + K", hl.dsp.window.move({ direction = "up" }))
|
||||
hl.bind(mainMod .. " + SHIFT + J", hl.dsp.window.move({ direction = "down" }))
|
||||
|
||||
-- Home row resize
|
||||
hl.bind(mainMod .. " + ALT + H", hl.dsp.exec_cmd("hyprctl dispatch resizeactive -40 0"))
|
||||
hl.bind(mainMod .. " + ALT + L", hl.dsp.exec_cmd("hyprctl dispatch resizeactive 40 0"))
|
||||
hl.bind(mainMod .. " + ALT + K", hl.dsp.exec_cmd("hyprctl dispatch resizeactive 0 -40"))
|
||||
hl.bind(mainMod .. " + ALT + J", hl.dsp.exec_cmd("hyprctl dispatch resizeactive 0 40"))
|
||||
|
||||
-- Arrow focus
|
||||
hl.bind(mainMod .. " + left", hl.dsp.focus({ direction = "left" }))
|
||||
hl.bind(mainMod .. " + right", hl.dsp.focus({ direction = "right" }))
|
||||
hl.bind(mainMod .. " + up", hl.dsp.focus({ direction = "up" }))
|
||||
hl.bind(mainMod .. " + down", hl.dsp.focus({ direction = "down" }))
|
||||
|
||||
-- Arrow move window
|
||||
hl.bind(mainMod .. " + SHIFT + left", hl.dsp.window.move({ direction = "left" }))
|
||||
hl.bind(mainMod .. " + SHIFT + right", hl.dsp.window.move({ direction = "right" }))
|
||||
hl.bind(mainMod .. " + SHIFT + up", hl.dsp.window.move({ direction = "up" }))
|
||||
hl.bind(mainMod .. " + SHIFT + down", hl.dsp.window.move({ direction = "down" }))
|
||||
|
||||
-- Lock, browser
|
||||
hl.bind(mainMod .. " + CTRL + L", hl.dsp.exec_cmd("hyprlock"))
|
||||
hl.bind(mainMod .. " + B", hl.dsp.exec_cmd(browser))
|
||||
|
||||
-- Workspaces 1-9
|
||||
for i = 1, 9 do
|
||||
hl.bind(mainMod .. " + " .. i, hl.dsp.focus({ workspace = i }))
|
||||
hl.bind(mainMod .. " + SHIFT + " .. i, hl.dsp.window.move({ workspace = i }))
|
||||
end
|
||||
|
||||
-- Screenshots
|
||||
hl.bind("Print", hl.dsp.exec_cmd("grimblast copysave area"))
|
||||
hl.bind(mainMod .. " + Print", hl.dsp.exec_cmd("grimblast copysave active"))
|
||||
hl.bind(mainMod .. " + SHIFT + Print", hl.dsp.exec_cmd("grimblast copysave output"))
|
||||
hl.bind(mainMod .. " + ALT + Print", hl.dsp.exec_cmd('grim -g "$(slurp)" - | satty -f -'))
|
||||
|
||||
--------------------------------
|
||||
---- WINDOW RULES ----
|
||||
--------------------------------
|
||||
|
||||
hl.window_rule({
|
||||
name = "suppress-maximize-events",
|
||||
match = { class = ".*" },
|
||||
suppress_event = "maximize",
|
||||
})
|
||||
|
||||
hl.window_rule({
|
||||
name = "fix-xwayland-drags",
|
||||
match = { class = "^$", title = "^$", xwayland = true, float = true, fullscreen = false, pin = false },
|
||||
no_focus = true,
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# mw-pfeddersheim-workstation variables
|
||||
system_user: mw
|
||||
local_user: "{{ system_user }}"
|
||||
root_filesystem_uuid: 2aeade2e-b169-4964-8cbd-bb7356320b51
|
||||
root_filesystem_mount_opts: noatime
|
||||
swap_file_path: /swapfile
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ alias k='kubectl'
|
||||
alias main='bash {{ workspace_root }}/scripts/maintenance.sh'
|
||||
|
||||
# Environment
|
||||
export EDITOR='vim'
|
||||
export EDITOR='nvim'
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
||||
# saTway indicators
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
description: >-
|
||||
Decision to dual-install linux612 (LTS) + linux70 (stable) kernels and
|
||||
migrate NVIDIA from the pinned 575xx branch to the mainline 595.71 branch
|
||||
Decision to dual-install linux618 (LTS) + linux70 (stable) kernels and
|
||||
migrate NVIDIA from the pinned 575xx branch to the 580xx branch (NFB)
|
||||
to prepare the workstation for Hyprland/Wayland.
|
||||
tags:
|
||||
- adr
|
||||
@@ -9,13 +9,13 @@ tags:
|
||||
- nvidia
|
||||
- hyprland
|
||||
- wayland
|
||||
last_updated: '2026-05-18'
|
||||
last_updated: '2026-06-17'
|
||||
---
|
||||
|
||||
# ADR 2: Kernel and NVIDIA Driver Strategy for Hyprland/Wayland
|
||||
|
||||
## Status
|
||||
Accepted (2026-05-18)
|
||||
Accepted (2026-05-18) — Updated (2026-06-17)
|
||||
|
||||
## Context
|
||||
The workstation runs Manjaro Linux on AMD Ryzen 7 2700X with an NVIDIA
|
||||
@@ -25,14 +25,14 @@ 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
|
||||
through 575.x, 580.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
|
||||
userspace with a kernel that only has `580xx` modules (or vice
|
||||
versa) breaks the driver at the next kernel boot.
|
||||
|
||||
The starting point at the time of this ADR:
|
||||
The starting point at the time of this ADR (2026-05-18):
|
||||
|
||||
| Item | Version |
|
||||
|---------------------------|------------------------|
|
||||
@@ -43,17 +43,29 @@ The starting point at the time of this ADR:
|
||||
| `linux70-nvidia` available | `595.71.05-0.1` |
|
||||
| `linux612-nvidia` (mainline) | `595.71.05-2` |
|
||||
|
||||
Current state (2026-06-17):
|
||||
|
||||
| Item | Version |
|
||||
|---------------------------|------------------------|
|
||||
| Kernel packages | `linux618`, `linux70` |
|
||||
| NVIDIA driver branch | `580xx` (NVIDIA Fast Branch) |
|
||||
| NVIDIA kernel module pkg | `linux618-nvidia-580xx`, `linux70-nvidia-580xx` |
|
||||
| NVIDIA userspace packages | `nvidia-580xx-utils`, `lib32-nvidia-580xx-utils`, `opencl-nvidia-580xx`, `nvidia-580xx-settings` |
|
||||
| Legacy packages removed | `*-nvidia-575xx` family (see `nvidia_packages_to_remove`) |
|
||||
|
||||
## Decision
|
||||
1. **Run two kernels side by side.**
|
||||
- Keep `linux612` (LTS 6.12.x) installed as the *fallback* boot entry.
|
||||
- Keep `linux618` (LTS 6.18.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
|
||||
2. **Migrate NVIDIA from the `575xx` branch to `580xx` (NVIDIA Fast Branch)** 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`.
|
||||
`lib32-nvidia-575xx-utils`, `nvidia-575xx-settings`,
|
||||
and the entire `*-nvidia-575xx` family.
|
||||
- Install: `linux618-nvidia-580xx`, `linux70-nvidia-580xx`,
|
||||
`nvidia-580xx-utils`, `lib32-nvidia-580xx-utils`,
|
||||
`opencl-nvidia-580xx`, `nvidia-580xx-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.
|
||||
@@ -63,17 +75,18 @@ The starting point at the time of this ADR:
|
||||
`--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
|
||||
- **LTS as a parachute.** 6.18 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
|
||||
- **580xx over 595.71.** The NVIDIA Fast Branch (580xx) provides newer
|
||||
driver improvements while maintaining backward compatibility. Manjaro
|
||||
builds `linux70-nvidia` against `nvidia-580xx-utils`, 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.
|
||||
supported by 580.x, which ships 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
|
||||
@@ -85,24 +98,23 @@ The starting point at the time of this ADR:
|
||||
|
||||
## Consequences
|
||||
- **Two kernels on `/` partition.** Roughly +120 MB for `linux70` plus
|
||||
~50 MB for `linux70-nvidia`. The role pre-flight refuses to start if
|
||||
~50 MB for `linux70-nvidia-580xx`. 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.
|
||||
kernel-bound `linux618-nvidia-580xx` and matching userspace are
|
||||
installed; legacy `*-nvidia-575xx` packages are removed.
|
||||
- **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
|
||||
- **Future kernel/driver pin bumps are one-line changes** in
|
||||
`ansible/vars/main.yml` (`kernel_packages` /
|
||||
`nvidia_kernel_modules` lists).
|
||||
`nvidia_kernel_modules` / `nvidia_userspace_packages` 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`.
|
||||
- `ansible/vars/main.yml` (current kernel and NVIDIA package pins).
|
||||
- ADR 1: `docs/adr/0001-use-ansible-for-configuration.md` — anchors the
|
||||
"Ansible-first" guardrail this decision follows.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Hyprland Keybindings — Full Cheatsheet
|
||||
# Hyprland Keybindings - Full Cheatsheet
|
||||
|
||||
**Layout:** German (de) | **Mod key:** SUPER (Windows key) | **Source:** `hyprctl binds` live
|
||||
**Layout:** German (de) | **Mod key:** SUPER (Windows key) | **Config:** `hyprland.lua` (Hyprland 0.55+)
|
||||
|
||||
---
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
| `SUPER+SHIFT+TAB` | Cycle previous window | cyclenext prev |
|
||||
| `SUPER+` `` ` `` (Grave) | Toggle scratchpad | togglespecialworkspace |
|
||||
| `SUPER+SHIFT+` `` ` `` | Send window to scratchpad | movetoworkspace special |
|
||||
| `SUPER+CTRL+K` | Keybind cheat menu | *(broken — pipelines to grep+fuzzel)* |
|
||||
| `SUPER+CTRL+K` | Keybind cheat menu | *(broken - pipelines to grep+fuzzel)* |
|
||||
| `SUPER+CTRL+L` | Lock screen | hyprlock |
|
||||
| `SUPER+B` | Web browser | google-chrome-stable |
|
||||
|
||||
@@ -103,8 +103,10 @@ Every kitty terminal tile shows a title bar with:
|
||||
|
||||
## Notes
|
||||
|
||||
- `SUPER+CTRL+K` (keybind menu) does **not work** — the `sed` pipeline breaks. Needs fix.
|
||||
- The `sed` pipeline `sed 's/^bind\s*=\s*\$mainMod/SUPER/;s/\s*#/#/'` fails silently because the unescaped `$` in the `sed` command gets interpreted as a variable.
|
||||
- `SUPER+CTRL+K` (keybind menu) does **not work** - the `sed` pipeline breaks.
|
||||
The Lua config format makes this harder to grep; consider a different approach.
|
||||
- Config migrated from `.conf` to `.lua` format (Hyprland 0.55+).
|
||||
See `docs/tech/hyprland-lua-migration.md` for the migration mapping.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
description: >-
|
||||
Session record (2026-06-24) of detecting the YubiKey, planning GPG commit
|
||||
signing, building a reusable SPECCOL inventory/provisioning stick, and
|
||||
hardening + provisioning a Dell Latitude E7250 as a dedicated air-gapped
|
||||
keygen/forensic host.
|
||||
tags:
|
||||
- learning
|
||||
- yubikey
|
||||
- gpg
|
||||
- security
|
||||
- air-gap
|
||||
- latitude-e7250
|
||||
last_updated: '2026-06-24'
|
||||
---
|
||||
|
||||
# YubiKey + Dell E7250 air-gapped keygen host - 2026-06-24
|
||||
|
||||
## Summary
|
||||
|
||||
Detected and inventoried the attached YubiKey; researched 2026 best practice
|
||||
for hardware-backed Git commit signing; wrote a baby-step plan; then procured,
|
||||
hardened, and provisioned a dedicated Dell Latitude E7250 as the permanent
|
||||
air-gapped keygen/forensic host. Built a reusable `SPECCOL` USB toolkit
|
||||
(inventory + provisioning scripts, verified BIOS, checklist) along the way.
|
||||
|
||||
**Outcome:** E7250 is fully hardened + provisioned and ready to go offline for
|
||||
PGP key generation. The YubiKey remains the daily-carry signer; the Type-C USB
|
||||
will hold the LUKS-encrypted key recovery backup (pending keygen).
|
||||
|
||||
## Decisions (with rationale)
|
||||
|
||||
| Decision | Choice | Why |
|
||||
|----------|--------|-----|
|
||||
| Signing approach | **GPG/OpenPGP on YubiKey** (not SSH `ed25519-sk`) | Secret keys are exportable -> a true LUKS recovery backup on USB is possible (requirement #3). |
|
||||
| Keygen environment | **Dedicated air-gapped host** (Dell E7250) instead of a re-booted live USB | Reproducible; Certify key never touches a networked machine; reusable for forensic work. |
|
||||
| Key identity | `Michael Wegener <mw@satware.com>`; subkeys ed25519(S)/cv25519(E)/ed25519(A); 2y expiry; touch required | 2026 modern; avoids RSA-deprecation for SSH auth. |
|
||||
| Push transport | HTTPS + GPG-sign (unchanged) | Lowest risk; signing is transport-independent. |
|
||||
|
||||
## Components & final state
|
||||
|
||||
### 1. YubiKey (serial 30836069, FW 5.7.1, USB-A)
|
||||
- OTP: slot 1 programmed, slot 2 empty.
|
||||
- FIDO2: PIN set (8 tries left), 4 credentials - `google.com` (Jane Alesi),
|
||||
`auth.satware.ai`, `git.g-b-c.de`, `github.com` (satwareAG-ironMike).
|
||||
- OATH: empty. PIV: factory-defaults/empty. OpenPGP: **empty, sign-PIN was blocked**.
|
||||
- Note: existing GPG keyring has 3 **expired** ed25519 keys tied to a *different*
|
||||
card serial (24530904) - stale references, not on this key.
|
||||
|
||||
### 2. Plan (committed)
|
||||
- `docs/plans/2026-06-24-yubikey-gpg-github-signing.md` (commit `5a714e6`).
|
||||
- 9 phases (0-9): remediate -> air-gapped keygen -> LUKS backup -> transfer to
|
||||
YubiKey -> configure workstation -> register with GitHub -> Ansible -> recovery
|
||||
-> 2nd YubiKey.
|
||||
|
||||
### 3. Boot stick (Type-C USB, 29 GB)
|
||||
- Debian Live **13.5.0 Trixie XFCE** ISO, verified (SHA512 + Dell... Debian CD
|
||||
OpenPGP signature), `dd`'d clean at ~40 MB/s.
|
||||
|
||||
### 4. SPECCOL toolkit stick (FAT32, label `SPECCOL`, ~1.9 GB) - reusable
|
||||
```
|
||||
toolkit/collect-specs.sh dock-aware inventory (TPM version, radios,
|
||||
TB dock nvm/serial, fwupd, robust tooling check)
|
||||
devices/latitude-e7250/provision.sh apt update + full-upgrade + tooling + autoremove
|
||||
+ logging + re-inventory (idempotent)
|
||||
devices/latitude-e7250/checklist.md hardening + provisioning checklist
|
||||
devices/latitude-e7250/bios/ E7250A24.exe (verified) + flash instructions
|
||||
devices/latitude-e7250/logs/ one provision log per run
|
||||
inventory/<hostname>/ collected hw-specs baselines
|
||||
README.txt incl. FAT32 "+x" rule (run via `bash`)
|
||||
```
|
||||
|
||||
### 5. Dell Latitude E7250 (Service Tag JQN3P32) - the air-gapped host
|
||||
- i7-5600U (vPro), 16 GB RAM, SK hynix SC210 mSATA 256 GB SSD (TRIM), Intel HD 5500.
|
||||
- Debian 13 Trixie, kernel **6.12.94** (dist-upgraded), LUKS on root+swap.
|
||||
- **Hardened:** BIOS **A24**; Secure Boot **enabled** (+ kernel lockdown); AMT/vPro
|
||||
**disabled** (MEBx); Wi-Fi **off** (gone from bus); Bluetooth **hard-blocked**;
|
||||
WWAN (Dell Gobi 5809e 4G LTE) **off**; TPM **cleared -> 1.2-only** (hardware
|
||||
limit - no 2.0 option in BIOS A24; non-blocking).
|
||||
- **Provisioned:** full dist-upgrade applied; 20/20 Phase-2 tools installed
|
||||
(scdaemon, pcscd, pcsc-tools, yubikey-manager/ykman, yubikey-personalization,
|
||||
cryptsetup, parted, fdisk, dosfstools, e2fsprogs, paperkey, tpm2-tools, fwupd,
|
||||
hdparm, smartmontools, nvme-cli, usbutils, pciutils, rfkill). 0 packages pending.
|
||||
- **Dock:** Dell E-Port Replicator (legacy bottom-connector; SMSC USB hubs only;
|
||||
no Thunderbolt, no serial, no updatable firmware).
|
||||
|
||||
## Architecture (refined)
|
||||
- **E7250** (offline, LUKS) = keygen + recovery host; holds the Certify key.
|
||||
- **YubiKey** = daily-carry commit/tag signer on the workstation.
|
||||
- **Type-C USB** = offline LUKS-encrypted recovery backup of the keys (pending Phase 3).
|
||||
|
||||
## Key learnings / gotchas
|
||||
- **LVFS:** the E7250 is **NOT** on the LVFS - no `fwupd`/`.cab` BIOS path; Dell
|
||||
dock also not updatable. BIOS update only via the Dell `.exe` (F12 BIOS Flash
|
||||
Update or FreeDOS).
|
||||
- **Dell package naming:** the YubiKey Manager CLI is Debian package
|
||||
**`yubikey-manager`** (provides the `ykman` binary), **not** `ykman`. One invalid
|
||||
name aborts the entire `apt-get install`. Fixed in `provision.sh`.
|
||||
- **FAT32 +x:** the SPECCOL stick can't store the executable bit - scripts must be
|
||||
invoked as `bash <script>` (the toolkit scripts chain each other via `bash`).
|
||||
- **TXT vs TPM:** Intel TXT must be **disabled before clearing the TPM** ("TXT must
|
||||
be disabled before clearing"). TXT is not needed for this host - left disabled.
|
||||
- **TPM version:** `/sys/class/tpm/tpm0/tpm_version_major` (kernel) is the reliable
|
||||
TPM 1.2-vs-2.0 indicator; `/dev/tpmrm0` presence also signals TPM 2.0.
|
||||
- **OpenPGP sign-PIN block** was pre-existing; a `ykman openpgp reset` will clear it
|
||||
before key transfer (Phase 4).
|
||||
- **gh auth** token for satwareAG-ironMike is invalid on the workstation; git
|
||||
protocol is HTTPS. (Re-auth when starting workstation-side setup.)
|
||||
- **Plaintext secrets exposure:** a `yubikey-provision-…` folder with unencrypted
|
||||
GnuPG + SSH private keys was on the Type-C USB ("safety net"). It gets destroyed
|
||||
when that stick is repartitioned for the LUKS backup (Phase 3) - same keys exist
|
||||
on the workstation `~/.gnupg`/`~/.ssh`, so nothing is lost.
|
||||
|
||||
## Verification baselines captured (on the SPECCOL stick)
|
||||
`inventory/michael-latitudee7250/hw-specs-*.txt` across: pre-flash (A19) ->
|
||||
post-flash (A24) -> post-provision -> post-TPM-clear. Tooling-detection bugs
|
||||
(scdaemon/pcsc-tools/yubikey-personalization binary-name mismatches; ambiguous
|
||||
lsusb messages; ANSI codes from fwupd) were fixed in `collect-specs.sh`.
|
||||
|
||||
## Open items / next steps
|
||||
1. On the E7250: (optional) set a BIOS/Setup admin password; **unplug Ethernet
|
||||
permanently**.
|
||||
2. **Phase 2 - keygen** on the E7250 (tmpfs GNUPGHOME): Certify key + S/E/A
|
||||
subkeys. Then Phase 3 (LUKS backup to Type-C USB `sdc3`) and Phase 4 (transfer
|
||||
to YubiKey; reset OpenPGP app first to clear the blocked sign-PIN).
|
||||
3. Workstation side: `gh auth login`, import public key, configure `git` signing
|
||||
(Phases 5-6), fold non-secret config into `ansible/` (Phase 7).
|
||||
4. Procure a 2nd YubiKey for redundancy (Phase 9).
|
||||
|
||||
## Artifacts produced today
|
||||
- `docs/plans/2026-06-24-yubikey-gpg-github-signing.md` (commit `5a714e6`)
|
||||
- `docs/learnings/2026-06-24-yubikey-e7250-airgap-setup.md` (this file)
|
||||
- SPECCOL toolkit stick (toolkit + devices/latitude-e7250/* + verified BIOS A24)
|
||||
- Type-C USB = current Debian-Live boot stick (29 GB; to be repurposed as the
|
||||
LUKS recovery backup in Phase 3)
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
title: "Neovim v0.12.2 Cheatsheet"
|
||||
author: "mw-pfeddersheim-workstation"
|
||||
date: "June 2026"
|
||||
---
|
||||
|
||||
# Neovim Cheatsheet
|
||||
|
||||
**Version:** v0.12.2 (LuaJIT 2.1) **Config:** `~/.config/nvim/init.lua`
|
||||
|
||||
## Search
|
||||
|
||||
### Buffer Search (current file)
|
||||
|
||||
| Command | Action |
|
||||
|---------|--------|
|
||||
| `/pattern` | Search forward |
|
||||
| `?pattern` | Search backward |
|
||||
| `n` | Next match (forward) |
|
||||
| `N` | Previous match (backward) |
|
||||
|
||||
### File Search (across files)
|
||||
|
||||
| Command | Action |
|
||||
|---------|--------|
|
||||
| `:grep pattern` | Search current directory with ripgrep |
|
||||
| `:grep pattern /path/to/dir` | Search specific directory |
|
||||
| `:cfirst` | Jump to first match |
|
||||
| `:cnext` / `:cprev` | Next / previous match |
|
||||
| `:copen` | Open quickfix window with all results |
|
||||
| `:cclose` | Close quickfix window |
|
||||
|
||||
## Editing
|
||||
|
||||
| Command | Action |
|
||||
|---------|--------|
|
||||
| `D` | Delete from cursor to end of line |
|
||||
| `d$` | Delete to end of line (same as `D`) |
|
||||
| `dd` | Delete entire line |
|
||||
| `C` | Change from cursor to end of line (delete + insert mode) |
|
||||
|
||||
## Text Objects
|
||||
|
||||
Delete or operate on structured text by type:
|
||||
|
||||
| Command | Action |
|
||||
|---------|--------|
|
||||
| `di{` | Delete **inside** `{}` (keeps braces) |
|
||||
| `da{` | Delete **a** `{}` (removes braces too) |
|
||||
| `di"` | Delete inside double quotes |
|
||||
| `da"` | Delete a double-quoted section |
|
||||
| `di(` | Delete inside parentheses |
|
||||
| `da(` | Delete a parenthesized section |
|
||||
| `di[` | Delete inside brackets |
|
||||
| `da[` | Delete a bracketed section |
|
||||
| `di<` | Delete inside angle brackets |
|
||||
| `da<` | Delete an angle-bracketed section |
|
||||
|
||||
**Pattern:** `d` (delete) + `i` (inside) / `a` (around) + `{type}`
|
||||
|
||||
**Also works with:**
|
||||
- `v` + text object — select (e.g. `vi{` selects inside braces)
|
||||
- `y` + text object — yank/copy (e.g. `ya{` copies including braces)
|
||||
- `c` + text object — change (e.g. `ci{` deletes + enters insert mode)
|
||||
@@ -0,0 +1,279 @@
|
||||
---
|
||||
description: >-
|
||||
Baby-step plan to sign GitHub/Git commits and tags with a hardware-backed
|
||||
OpenPGP key on YubiKey 5 NFC #30836069, generated in a dedicated air-gapped
|
||||
Debian-Live environment, with LUKS-encrypted recovery backups on the Type-C
|
||||
USB stick. Non-secret config folded into the ansible/ role; secrets via Bitwarden.
|
||||
tags:
|
||||
- plan
|
||||
- security
|
||||
- yubikey
|
||||
- gpg
|
||||
- github
|
||||
last_updated: '2026-06-24'
|
||||
---
|
||||
|
||||
# YubiKey + GPG signing for GitHub commits - 2026-06-24
|
||||
|
||||
Sign all Git commits/tags with a hardware-backed OpenPGP key whose secret
|
||||
material lives on the YubiKey, and whose full recovery backup is stored
|
||||
LUKS-encrypted on the Type-C USB stick.
|
||||
|
||||
## Goal
|
||||
|
||||
1. Hardware-backed commit/tag signing for GitHub (and GitLab, PGP email) using
|
||||
the YubiKey OpenPGP smartcard.
|
||||
2. A complete, offline recovery path: secret keys on a LUKS-encrypted USB stick.
|
||||
3. Non-secret configuration managed through the repository's `ansible/` role.
|
||||
|
||||
## Decisions (confirmed 2026-06-24)
|
||||
|
||||
| Decision | Choice | Rationale |
|
||||
|----------|--------|-----------|
|
||||
| Approach | **A - GPG/OpenPGP** (not SSH `ed25519-sk`) | Secret keys are exportable, so a true recovery backup on USB is possible. Matches the "pgp" requirement. |
|
||||
| Keygen env | **Dedicated air-gapped** Debian-Live (internal disk disconnected) | drduh gold standard; the Certify key never touches a networked machine. |
|
||||
| Key identity | `Michael Wegener <mw@satware.com>` | Matches `git config user.email/name`. |
|
||||
| Algorithms | Certify `ed25519`; Subkeys `ed25519` (S), `cv25519` (E), `ed25519` (A) | 2026 modern; avoids RSA-deprecation for the SSH auth subkey. RSA-4096 is the fallback. |
|
||||
| Touch policy | **Required** for sign + auth | Defeats malware key use; YubiKey blinks for a physical tap. |
|
||||
| Subkey expiry | **2 years** (Certify never expires) | drduh lifecycle; renewable offline. |
|
||||
| Push transport | **HTTPS + GPG-sign** (unchanged) | Lowest risk; signing is transport-independent. SSH-via-auth-subkey deferred. |
|
||||
| Config mgmt | Fold non-secret config into `ansible/` | Repo convention; reproducible. |
|
||||
| Backup target | Type-C USB `/dev/sdc` (wipe confirmed acceptable) + a 2nd USB later | Requirement #3. |
|
||||
| 2nd YubiKey | Scheduled as Phase 9 follow-up (after this key works) | Redundancy + travel spare. |
|
||||
|
||||
## Architecture - two environments
|
||||
|
||||
- **Air-gapped (Debian-Live, no internal disk):** generate Certify + Subkeys,
|
||||
move Subkeys onto the YubiKey, LUKS-encrypt secrets to the USB, export the
|
||||
public key. The Certify key never leaves this environment.
|
||||
- **This workstation (Manjaro `mw-manjaro-pf`):** import the **public key
|
||||
only**, configure `git`/`gpg-agent`, sign. Private material stays on the
|
||||
YubiKey.
|
||||
|
||||
## Pre-flight facts (current state, verified 2026-06-24)
|
||||
|
||||
- YubiKey 5 NFC, serial `30836069`, FW `5.7.1`, OpenPGP app **empty**,
|
||||
**sign-PIN blocked (`3 0 3`)**, PIV factory-defaults/empty, OATH empty.
|
||||
- FIDO2 has 4 credentials (incl. `github.com` -> `satwareAG-ironMike`).
|
||||
- `gh auth` token **invalid** -> re-login required.
|
||||
- USB `/dev/sdc1` = dirty FAT32 (`errors=remount-ro`) -> `fsck`, then repartition.
|
||||
- Tools present: `ykman`, `ykinfo`, `gpg`, `pcscd`; `gpg-agent.conf` already has
|
||||
`enable-ssh-support`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 - Prep on this workstation (read/config only; no key material yet)
|
||||
|
||||
1. Fix GitHub auth + confirm tooling:
|
||||
```bash
|
||||
gh auth login -h github.com # re-auth (browser/token)
|
||||
gh auth status
|
||||
git --version # >=2.34 fine; GPG works on any
|
||||
pacman -Q gnupg pcscd yubikey-manager # all present (verified earlier)
|
||||
```
|
||||
2. Check the USB stick holds nothing needed, then dry-run a repair:
|
||||
```bash
|
||||
ls "/run/media/mw/Type-C USB"
|
||||
sudo fsck.vfat -n /dev/sdc1 # review output; do NOT -a yet
|
||||
```
|
||||
3. Identify which **spare** USB will carry the Debian-Live image (Phase 1) and
|
||||
which 2nd USB will later hold the mirrored backup.
|
||||
|
||||
## Phase 1 - Build the air-gapped keygen USB (drduh method)
|
||||
|
||||
1. Download Debian-Live + signature, **verify before trusting**:
|
||||
```bash
|
||||
IMG=https://cdimage.debian.org/debian-cd/current-live/amd64/iso-hybrid
|
||||
curl -fLO "$IMG/SHA512SUMS" -O "$IMG/SHA512SUMS.sign"
|
||||
curl -fLO "$IMG/$(awk '/xfce.iso$/ {print $2}' SHA512SUMS)"
|
||||
gpg --keyserver hkps://keyserver.ubuntu.com:443 \
|
||||
--recv DF9B9C49EAA9298432589D76DA87E80D6294BE9B
|
||||
gpg --verify SHA512SUMS.sign SHA512SUMS # MUST say "Good signature"
|
||||
grep "$(sha512sum debian-live-*-amd64-xfce.iso)" SHA512SUMS
|
||||
```
|
||||
2. Write to a **spare** USB (not the backup stick):
|
||||
```bash
|
||||
sudo dd if=debian-live-*-amd64-xfce.iso of=/dev/sdX bs=4M status=progress; sync
|
||||
```
|
||||
|
||||
## Phase 2 - Boot air-gapped, generate keys
|
||||
|
||||
1. Power off, **disconnect the internal NVMe**, boot the Debian-Live USB.
|
||||
Network can stay off.
|
||||
2. Install tools + hardened config in a **tmpfs GNUPGHOME**:
|
||||
```bash
|
||||
sudo apt update && sudo apt -y install gnupg2 scdaemon pcscd yubikey-manager
|
||||
export GNUPGHOME=$(mktemp -d -t $(date +%Y.%m.%d)-XXXX)
|
||||
cd "$GNUPGHOME"
|
||||
wget https://raw.githubusercontent.com/drduh/YubiKey-Guide/master/config/gpg.conf
|
||||
```
|
||||
3. Create a strong **Certify passphrase** (write it down; store it apart from
|
||||
the USB; record in Bitwarden):
|
||||
```bash
|
||||
CERTIFY_PASS=$(LC_ALL=C tr -dc "A-Z2-9" </dev/urandom | tr -d "IOUS5" \
|
||||
| fold -w4 | paste -sd- - | head -c29); printf "\n$CERTIFY_PASS\n\n"
|
||||
```
|
||||
4. **Certify key** (never expires):
|
||||
```bash
|
||||
IDENTITY="Michael Wegener <mw@satware.com>"
|
||||
echo "$CERTIFY_PASS" | gpg --batch --passphrase-fd 0 \
|
||||
--quick-generate-key "$IDENTITY" ed25519 cert never
|
||||
KEYFP=$(gpg -k --with-colons "$IDENTITY" | awk -F: '/^fpr:/{print $10; exit}')
|
||||
```
|
||||
5. **Subkeys** (2-year expiry): sign `ed25519`, encrypt `cv25519`, auth `ed25519`:
|
||||
```bash
|
||||
echo "$CERTIFY_PASS" | gpg --batch --pinentry-mode=loopback --passphrase-fd 0 --quick-add-key "$KEYFP" ed25519 sign 2y
|
||||
echo "$CERTIFY_PASS" | gpg --batch --pinentry-mode=loopback --passphrase-fd 0 --quick-add-key "$KEYFP" cv25519 encrypt 2y
|
||||
echo "$CERTIFY_PASS" | gpg --batch --pinentry-mode=loopback --passphrase-fd 0 --quick-add-key "$KEYFP" ed25519 auth 2y
|
||||
gpg -K # expect [C] + ssb [S] [E] [A]
|
||||
```
|
||||
|
||||
## Phase 3 - Backup to the USB stick (requirement #3) - LUKS
|
||||
|
||||
Use the **Type-C USB stick** (`/dev/sdc`), repartitioned: LUKS partition for
|
||||
secrets + small plaintext partition for the public key.
|
||||
|
||||
1. Export everything:
|
||||
```bash
|
||||
KEYID=${KEYFP: -16}
|
||||
echo "$CERTIFY_PASS" | gpg -o "$GNUPGHOME/$KEYID-Certify.key" --batch --pinentry-mode=loopback --passphrase-fd 0 --armor --export-secret-keys "$KEYID"
|
||||
echo "$CERTIFY_PASS" | gpg -o "$GNUPGHOME/$KEYID-Subkeys.key" --batch --pinentry-mode=loopback --passphrase-fd 0 --armor --export-secret-subkeys "$KEYID"
|
||||
gpg -o "$GNUPGHOME/$KEYID-$(date +%F).asc" --armor --export "$KEYID"
|
||||
```
|
||||
2. Partition + LUKS-encrypt (confirm `/dev/sdc` is the right device - destructive):
|
||||
```bash
|
||||
LUKS_PASS=$(LC_ALL=C tr -dc "A-Z2-9" </dev/urandom | tr -d "IOUS5" | fold -w4 | paste -sd- - | head -c29)
|
||||
sudo dd if=/dev/zero of=/dev/sdc bs=4M count=1
|
||||
sudo fdisk /dev/sdc <<EOF
|
||||
g
|
||||
n
|
||||
1
|
||||
|
||||
+50M
|
||||
n
|
||||
2
|
||||
|
||||
|
||||
w
|
||||
EOF
|
||||
echo "$LUKS_PASS" | sudo cryptsetup -q luksFormat /dev/sdc1
|
||||
echo "$LUKS_PASS" | sudo cryptsetup luksOpen /dev/sdc1 gnupg-secrets
|
||||
sudo mkfs.ext2 /dev/mapper/gnupg-secrets
|
||||
sudo mkfs.vfat /dev/sdc2
|
||||
sudo mkdir -p /mnt/sec /mnt/pub
|
||||
sudo mount /dev/mapper/gnupg-secrets /mnt/sec
|
||||
sudo mount /dev/sdc2 /mnt/pub
|
||||
sudo cp -av "$GNUPGHOME" /mnt/sec/ # encrypted secrets
|
||||
gpg --armor --export "$KEYID" | sudo tee "/mnt/pub/$KEYID-$(date +%F).asc" >/dev/null
|
||||
sudo umount /mnt/sec /mnt/pub
|
||||
sudo cryptsetup luksClose gnupg-secrets
|
||||
```
|
||||
3. Record `LUKS_PASS` in Bitwarden (separate from the Certify passphrase).
|
||||
4. **Repeat Phase 3 on a 2nd USB** and store both offline in separate locations.
|
||||
|
||||
## Phase 4 - Transfer Subkeys to the YubiKey
|
||||
|
||||
1. Reset the OpenPGP app (sign-PIN is blocked; app is empty, so nothing is lost):
|
||||
```bash
|
||||
ykman openpgp reset -f # confirm when ready
|
||||
gpg --card-status # retry counters should read 3 3 3
|
||||
```
|
||||
2. Change PINs from defaults and set cardholder identity. Generate your own
|
||||
`ADMIN_PIN` (8 digits) and `USER_PIN` (6 digits); store in Bitwarden:
|
||||
```bash
|
||||
gpg --change-pin # menu: 3 = admin PIN (12345678 -> $ADMIN_PIN)
|
||||
gpg --change-pin # menu: 1 = user PIN (123456 -> $USER_PIN)
|
||||
gpg --edit-card # admin -> login -> "Michael Wegener <mw@satware.com>"
|
||||
```
|
||||
3. Move subkeys onto the card, then require touch:
|
||||
```bash
|
||||
gpg --edit-key "$KEYID" # key 1 -> keytocard (sig=1)
|
||||
# key 2 -> keytocard (enc=2)
|
||||
# key 3 -> keytocard (auth=3)
|
||||
# save
|
||||
ykman openpgp keys set-touch sig on -f -a "$ADMIN_PIN"
|
||||
ykman openpgp keys set-touch dec on -f -a "$ADMIN_PIN"
|
||||
ykman openpgp keys set-touch aut on -f -a "$ADMIN_PIN"
|
||||
gpg -K # ssb> markers confirm subkeys are on the card
|
||||
```
|
||||
|
||||
## Phase 5 - Configure this workstation (public key only)
|
||||
|
||||
1. Copy the public `.asc` from the USB plaintext partition, import, trust:
|
||||
```bash
|
||||
sudo mkdir -p /mnt/pub && sudo mount /dev/sdc2 /mnt/pub
|
||||
gpg --import /mnt/pub/*.asc
|
||||
gpg --edit-key "$KEYID" # trust -> 5 (ultimate) -> y -> save
|
||||
```
|
||||
2. Git signing (global; uses the **sign subkey**):
|
||||
```bash
|
||||
SIGNID=$(gpg -k --with-colons "$KEYID" | awk -F: '/^sub:/ && /s/ {split($0,a,":"); print a[5]}')
|
||||
git config --global gpg.format openpgp
|
||||
git config --global user.signingkey "$SIGNID!"
|
||||
git config --global commit.gpgsign true
|
||||
git config --global tag.gpgsign true
|
||||
```
|
||||
3. Agent/scdaemon (the `disable-ccid` line prevents the scdaemon-vs-pcscd lock
|
||||
observed during detection):
|
||||
```bash
|
||||
echo "disable-ccid" >> ~/.gnupg/scdaemon.conf
|
||||
# ~/.gnupg/gpg-agent.conf already has: enable-ssh-support, cache-ttls
|
||||
gpgconf --kill gpg-agent
|
||||
```
|
||||
|
||||
## Phase 6 - Register the public key with GitHub
|
||||
|
||||
```bash
|
||||
gpg --armor --export "$KEYID" | gh gpg-key add - # after `gh auth login`
|
||||
# or paste at github.com/settings/keys
|
||||
```
|
||||
|
||||
Verify end-to-end: create a test commit, `git push`, the commit shows
|
||||
**Verified** on GitHub.
|
||||
|
||||
## Phase 7 - Fold non-secret config into Ansible (repo convention)
|
||||
|
||||
- New role `ansible/roles/gpg_signing/` templating:
|
||||
- `~/.gnupg/gpg.conf`, `~/.gnupg/gpg-agent.conf`, `~/.gnupg/scdaemon.conf`
|
||||
- the four `git config --global` signing lines from Phase 5.2
|
||||
- Commit the **public key** to `ansible/files/mw-satware-<KEYID>.asc` (public,
|
||||
safe to track).
|
||||
- **PINs, Certify and LUKS passphrases -> Bitwarden only**, never the repo
|
||||
(zero-trust rule). Use Ansible Vault vars if a task must consume them.
|
||||
- Add the role to `ansible/workstation.yml`; verify with
|
||||
`ansible-playbook ansible/workstation.yml --check`.
|
||||
|
||||
## Phase 8 - Recovery & verification
|
||||
|
||||
- **Recovery:** plug a backup USB -> `cryptsetup luksOpen /dev/sdc1 gnupg-secrets`
|
||||
-> restore `$KEYID-Subkeys.key` onto a *new* YubiKey via `keytocard`. The
|
||||
Certify key is needed only to rotate/renew subkeys.
|
||||
- Final verification checklist:
|
||||
- `gpg --card-status` shows the three subkeys with correct fingerprints.
|
||||
- `git commit -S --allow-empty -m "test"` prompts for a YubiKey tap and
|
||||
succeeds.
|
||||
- `git push`; GitHub shows the commit as **Verified**.
|
||||
- `git tag -s v-test && git verify-tag v-test` succeeds.
|
||||
|
||||
## Phase 9 - Second YubiKey (follow-up, after this key works)
|
||||
|
||||
- Mirror the same Subkeys onto a backup YubiKey from the LUKS backup
|
||||
(`$KEYID-Subkeys.key` -> `keytocard`), set identical PINs and touch policy.
|
||||
- Register it on GitHub as an **additional** GPG signing key.
|
||||
- Re-register its FIDO2/WebAuthn handles for the relying parties already on the
|
||||
primary key (`github.com`, `auth.satware.ai`, `git.g-b-c.de`, `google.com`).
|
||||
- Create a **2nd backup USB** mirroring Phase 3 and store it off-site.
|
||||
|
||||
## Secrets handling (per repo zero-trust rule)
|
||||
|
||||
| Item | Stored where |
|
||||
|------|--------------|
|
||||
| Public key | `ansible/files/*.asc` (committed) + USB plaintext partition |
|
||||
| Certify key + Subkeys (encrypted) | LUKS partition on USB (offline, 2 copies) |
|
||||
| Certify passphrase, LUKS passphrase, User PIN, Admin PIN | Bitwarden only (`bitwarden.jantec.xyz`), cached locally in `gnome-keyring` |
|
||||
|
||||
## References
|
||||
|
||||
- GitHub: About commit signature verification (GPG/SSH/S-MIME).
|
||||
- GitHub: Generating a new SSH key (incl. `ed25519-sk` security-key variant).
|
||||
- drduh/YubiKey-Guide (canonical GPG-on-YubiKey walkthrough; LUKS backup model).
|
||||
@@ -105,8 +105,8 @@ last_updated: '2026-04-02'
|
||||
| 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
|
||||
> pinned Manjaro `*-nvidia-575xx` branch to `*-nvidia-580xx` (NVIDIA Fast 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).
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
# Hyprland Lua Config Migration (0.55+)
|
||||
|
||||
## Summary
|
||||
|
||||
Hyprland 0.55 deprecates the legacy `hyprlang` (`.conf`) format in favor of a native Lua
|
||||
DSL (`hyprland.lua`). Both formats coexist during transition, but `.lua` is the future.
|
||||
|
||||
## Configuration File Priority
|
||||
|
||||
| File | Format | Priority |
|
||||
|------|--------|----------|
|
||||
| `~/.config/hypr/hyprland.lua` | Lua DSL (`hl.*` API) | **Preferred** (0.55+) |
|
||||
| `~/.config/hypr/hyprland.conf` | Legacy hyprlang | Deprecated |
|
||||
|
||||
When both exist, Hyprland loads `.lua` first. If `.lua` is present, `.conf` is ignored.
|
||||
|
||||
## Lua API Quick Reference
|
||||
|
||||
```lua
|
||||
-- Variables
|
||||
local terminal = "kitty"
|
||||
local mainMod = "SUPER"
|
||||
|
||||
-- Config blocks
|
||||
hl.config({ general = { gaps_in = 5 } })
|
||||
hl.env("XCURSOR_SIZE", "24")
|
||||
hl.monitor({ output = "", mode = "preferred", position = "auto", scale = "auto" })
|
||||
|
||||
-- Keybindings (single combined key string)
|
||||
hl.bind(mainMod .. " + Q", hl.dsp.window.close())
|
||||
hl.bind(mainMod .. " + T", hl.dsp.exec_cmd(terminal))
|
||||
|
||||
-- Window/workspace rules
|
||||
hl.window_rule({ name = "my-rule", match = { class = ".*" }, suppress_event = "maximize" })
|
||||
hl.workspace_rule({ workspace = 1, monitor = "DP-1", default = true })
|
||||
|
||||
-- Autostart
|
||||
hl.on("hyprland.start", function() hl.exec_cmd("waybar &") end)
|
||||
|
||||
-- Gestures, animations
|
||||
hl.gesture({ fingers = 3, direction = "horizontal", action = "workspace" })
|
||||
hl.curve("easeOutQuint", { type = "bezier", points = { {0.23, 1}, {0.32, 1} } })
|
||||
hl.animation({ leaf = "windows", enabled = true, speed = 4.79, spring = "easy" })
|
||||
```
|
||||
|
||||
## Dispatcher Functions (`hl.dsp.*`)
|
||||
|
||||
| Lua API | Legacy .conf equivalent |
|
||||
|---------|------------------------|
|
||||
| `hl.dsp.exec_cmd("cmd")` | `exec, cmd` |
|
||||
| `hl.dsp.window.close()` | `killactive` |
|
||||
| `hl.dsp.window.float({ action = "toggle" })` | `togglefloating` |
|
||||
| `hl.dsp.window.pseudo()` | `pseudo` |
|
||||
| `hl.dsp.window.fullscreen({ mode = 0 })` | `fullscreen, 0` |
|
||||
| `hl.dsp.window.move({ direction = "left" })` | `movewindow, l` |
|
||||
| `hl.dsp.window.move({ workspace = 2 })` | `movetoworkspace, 2` |
|
||||
| `hl.dsp.window.drag()` / `.resize()` | mouse bind |
|
||||
| `hl.dsp.focus({ direction = "left" })` | `movefocus, l` |
|
||||
| `hl.dsp.focus({ workspace = 2 })` | `workspace, 2` |
|
||||
| `hl.dsp.focus({ workspace = "e+1" })` | `workspace, e+1` |
|
||||
| `hl.dsp.workspace.toggle_special("magic")` | `togglespecialworkspace` |
|
||||
| `hl.dsp.layout("togglesplit")` | `layoutmsg, togglesplit` |
|
||||
|
||||
## Pitfalls Learned
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| `hl.dsp.focus({ direction = "next" })` | **Invalid.** Only `left/right/up/down` accepted. Use `hl.dsp.exec_cmd("hyprctl dispatch cyclenext")` instead. |
|
||||
| `tap-to-click = true` | **Invalid Lua.** Hyphens not allowed in identifiers. Use `tap_to_click` (Hyprland accepts underscores). |
|
||||
| `hl.bind("", "Print", ...)` | **Invalid.** Lua API uses single key string: `hl.bind("Print", ...)`. |
|
||||
| `hl.bind(mainMod, "Q", ...)` | **Invalid.** Combine into one string: `hl.bind(mainMod .. " + Q", ...)`. |
|
||||
| `resizeactive` not in `hl.dsp.*` | Use `hl.dsp.exec_cmd("hyprctl dispatch resizeactive -40 0")` as wrapper. |
|
||||
| `luac -p` false positives | Standard `luac` can't validate Hyprland's custom `hl.*` API. Use `hyprctl configerrors` at runtime instead. |
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
# Runtime validation (requires running Hyprland session)
|
||||
hyprctl configerrors
|
||||
|
||||
# Over SSH (find instance signature first)
|
||||
HYPRLAND_INSTANCE_SIGNATURE=$(ls /run/user/$(id -u)/hypr/) hyprctl configerrors
|
||||
|
||||
# Syntax-only check (limited - won't catch hl.* API issues)
|
||||
luac -p ~/.config/hypr/hyprland.lua
|
||||
```
|
||||
|
||||
## conf-to-lua Migration Mapping
|
||||
|
||||
| Legacy `.conf` | Lua `.lua` |
|
||||
|----------------|------------|
|
||||
| `$mainMod = SUPER` | `local mainMod = "SUPER"` |
|
||||
| `monitor=DP-1,...` | `hl.monitor({ output = "DP-1", ... })` |
|
||||
| `env = KEY,VALUE` | `hl.env("KEY", "VALUE")` |
|
||||
| `input { ... }` | `hl.config({ input = { ... } })` |
|
||||
| `general { ... }` | `hl.config({ general = { ... } })` |
|
||||
| `bind = MOD, KEY, exec, cmd` | `hl.bind(mod .. " + KEY", hl.dsp.exec_cmd("cmd"))` |
|
||||
| `bind = MOD, KEY, killactive` | `hl.bind(mod .. " + KEY", hl.dsp.window.close())` |
|
||||
| `bind = MOD, KEY, togglefloating` | `hl.bind(mod .. " + KEY", hl.dsp.window.float({ action = "toggle" }))` |
|
||||
| `bind = MOD, KEY, fullscreen, 0` | `hl.bind(mod .. " + KEY", hl.dsp.window.fullscreen({ mode = 0 }))` |
|
||||
| `bind = MOD, KEY, movefocus, l` | `hl.bind(mod .. " + KEY", hl.dsp.focus({ direction = "left" }))` |
|
||||
| `bind = MOD, KEY, movewindow, l` | `hl.bind(mod .. " + KEY", hl.dsp.window.move({ direction = "left" }))` |
|
||||
| `bind = MOD, KEY, workspace, N` | `hl.bind(mod .. " + KEY", hl.dsp.focus({ workspace = N }))` |
|
||||
| `bind = MOD, KEY, cyclenext` | `hl.bind(mod .. " + KEY", hl.dsp.exec_cmd("hyprctl dispatch cyclenext"))` |
|
||||
| `bind = MOD, KEY, pseudo` | `hl.bind(mod .. " + KEY", hl.dsp.window.pseudo())` |
|
||||
| `bind = MOD, KEY, layoutmsg, togglesplit` | `hl.bind(mod .. " + KEY", hl.dsp.layout("togglesplit"))` |
|
||||
| `bind = , Print, exec, cmd` | `hl.bind("Print", hl.dsp.exec_cmd("cmd"))` |
|
||||
| `bind = MOD, KEY, exit` | `hl.bind(mod .. " + KEY", hl.dsp.exec_cmd("hyprctl dispatch exit"))` |
|
||||
| `device { name = "..."; ... }` | `hl.device({ name = "...", ... })` |
|
||||
| `workspace = 1, monitor:DP-1, default:true` | `hl.workspace_rule({ workspace = 1, monitor = "DP-1", default = true })` |
|
||||
| `exec-once = cmd` | `hl.on("hyprland.start", function() hl.exec_cmd("cmd") end)` |
|
||||
| `col.active_border = rgba(...) 45deg` | `col = { active_border = { colors = {"rgba(...)"}, angle = 45 } }` |
|
||||
|
||||
## Multi-Machine Setup
|
||||
|
||||
This project manages configs for two machines with identical keybindings but different hardware:
|
||||
|
||||
| Aspect | Workstation (pfeddersheim) | MacBook Pro 16,1 |
|
||||
|--------|---------------------------|-------------------|
|
||||
| GPU | NVIDIA (6 env vars) | Intel UHD 630 + AMD RX 5500M (no env vars) |
|
||||
| Monitors | 3 fixed (DP-1, DVI-D-1, HDMI-A-1) | Built-in + HDMI dock (auto-detect) |
|
||||
| Input | de layout, left-handed mouse | de + mac_nodeadkeys + caps:swapescape, touchpad |
|
||||
| Touchpad | N/A | natural_scroll, gestures, disable_while_typing |
|
||||
| Media keys | N/A | Volume, brightness, playerctl |
|
||||
| Window rules | None | Suppress maximize, XWayland fix |
|
||||
| Config format | `.lua` (Ansible-managed) | `.lua` (manual) |
|
||||
@@ -6,7 +6,7 @@ tags:
|
||||
- manual-overrides
|
||||
- infrastructure
|
||||
- configuration
|
||||
last_updated: '2026-03-09'
|
||||
last_updated: '2026-06-17'
|
||||
---
|
||||
|
||||
# Manual System Overrides
|
||||
@@ -22,4 +22,5 @@ This document tracks all persistent system changes implemented manually that are
|
||||
|
||||
| Date | Change | Rationale | Ansible Status |
|
||||
|------|--------|-----------|----------------|
|
||||
| 2026-06-17 | Discovered and deleted `br_vm_internal` bridge (10.10.2.0/24) | Externally-created bridge, no NM config file, no VMs attached. Origin unknown — likely ad-hoc `nmcli con add` or manual `brctl` session (Jun 10). Traffic-shaping qdisc (`htb`) suggests prior VM network isolation attempt. Deleted via `nmcli con delete` + `ip link delete`. | N/A (removed) |
|
||||
| 2026-04-02 | Moved LLM models to `/home/mw/models/` (SATA SSD) with symlinks | Free root NVMe space (93%→87%, +25G freed) | Not yet |
|
||||
|
||||
+4
-4
@@ -5,7 +5,7 @@ tags:
|
||||
- setup
|
||||
- bootstrap
|
||||
- ansible
|
||||
last_updated: '2026-05-20'
|
||||
last_updated: '2026-06-17'
|
||||
---
|
||||
|
||||
# Workstation Setup Guide
|
||||
@@ -44,8 +44,8 @@ This guide describes how to bootstrap the **mw-pfeddersheim-workstation** infras
|
||||
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`.
|
||||
dual-kernel install (`linux618` LTS + `linux70` stable), and NVIDIA
|
||||
driver migration from the pinned `575xx` branch to `580xx` (NVIDIA Fast Branch).
|
||||
Always preview first with `--check`. See
|
||||
[ADR 0002](../adr/0002-kernel-and-nvidia-strategy.md) for the rationale.
|
||||
|
||||
@@ -63,7 +63,7 @@ This guide describes how to bootstrap the **mw-pfeddersheim-workstation** infras
|
||||
|
||||
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).
|
||||
and `nvidia-smi` (expect driver 580.x).
|
||||
|
||||
5. **Run the Playbook** (remaining roles: common, dev-tools, maintenance)
|
||||
```bash
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ tags:
|
||||
- software-stack
|
||||
- inventory
|
||||
- tech-stack
|
||||
last_updated: '2026-05-22'
|
||||
last_updated: '2026-06-17'
|
||||
---
|
||||
|
||||
# Software Stack - mw-pfeddersheim-workstation
|
||||
|
||||
@@ -7,7 +7,7 @@ tags:
|
||||
- health-check
|
||||
- workstation
|
||||
- monitoring
|
||||
last_updated: '2026-03-17'
|
||||
last_updated: '2026-06-17'
|
||||
---
|
||||
# Workstation Health Report: mw-manjaro-pf
|
||||
|
||||
@@ -88,3 +88,7 @@ Uptime: ~1 hour, 7 minutes
|
||||
|
||||
1. Consider setting up automated crash monitoring for high-memory applications
|
||||
2. Run health check script manually as needed: `/home/mw/internal/mw-pfeddersheim-workstation/scripts/morning-health-check.sh` (automated systemd timer removed 2026-05-26)
|
||||
|
||||
## Recent Cleanup (2026-06-17)
|
||||
|
||||
- **Externally-created bridge `br_vm_internal` (10.10.2.0/24)** discovered and removed. No NM config file, no libvirt network, no attached VMs. Origin traced to Jun 10 2026 (first activation) — likely ad-hoc CLI creation. Bridge had `htb` qdisc suggesting prior VM network isolation attempt. Both the NM connection and kernel interface fully deleted.
|
||||
Executable
+372
@@ -0,0 +1,372 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Workstation Package Cleanup Script
|
||||
# Version: 1.0.0
|
||||
# Target: mw-pfeddersheim-workstation
|
||||
# Purpose: Remove identified cleanup candidates and log for verification
|
||||
|
||||
MODE="apply"
|
||||
if [[ "${1:-}" == "--check" ]]; then
|
||||
MODE="check"
|
||||
echo "=== Running in CHECK mode (dry-run, no packages removed) ==="
|
||||
elif [[ "${1:-}" == "--help" ]]; then
|
||||
echo "Usage: $0 [--check | --apply | --help]"
|
||||
echo " --check Dry-run: show what would be removed (default)"
|
||||
echo " --apply Actually remove packages"
|
||||
echo " --help Show this help"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Ensure log directory exists
|
||||
mkdir -p "$HOME/logs" 2>/dev/null || true
|
||||
LOCAL_LOG="$HOME/logs/cleanup-$(date +%Y%m%d-%H%M%S).log"
|
||||
BOLD=$(tput bold 2>/dev/null || true)
|
||||
RED=$(tput setaf 1 2>/dev/null || true)
|
||||
GREEN=$(tput setaf 2 2>/dev/null || true)
|
||||
RESET=$(tput sgr0 2>/dev/null || true)
|
||||
|
||||
log() { echo -e "$*" | tee -a "$LOCAL_LOG"; }
|
||||
section() { log ""; log "${BOLD}--- $1 ---${RESET}"; }
|
||||
|
||||
echo "" | tee -a "$LOCAL_LOG"
|
||||
log "============================================="
|
||||
log " Workstation Package Cleanup"
|
||||
log " Mode: $MODE | Date: $(date)"
|
||||
log " Log: $LOCAL_LOG"
|
||||
log "============================================="
|
||||
|
||||
# Track totals
|
||||
TOTAL_CANDIDATES=0
|
||||
TOTAL_REMOVED=0
|
||||
TOTAL_ERRORS=0
|
||||
SKIPPED=0
|
||||
|
||||
# Helper: add package to candidate list file
|
||||
add_candidates() {
|
||||
local category="$1"
|
||||
shift
|
||||
echo "$@" >> "$HOME/logs/.cleanup-candidates-${category}.txt"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 1. PRE-FLIGHT CHECKS
|
||||
# ============================================================
|
||||
section "Pre-flight Checks"
|
||||
|
||||
# Verify pacman is available
|
||||
if ! command -v pacman &> /dev/null; then
|
||||
log "${RED}ERROR: pacman not found. This script must be run on Arch/Manjaro.${RESET}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check disk space (need room for transaction)
|
||||
DISK_PCT=$(df / | awk 'NR==2 {gsub(/%/,"",$5); print $5}')
|
||||
if [[ "$DISK_PCT" -gt 95 ]]; then
|
||||
log "${RED}WARNING: Disk usage at ${DISK_PCT}%. Consider freeing space first.${RESET}"
|
||||
fi
|
||||
DISK_AVAIL=$(df -h / | awk 'NR==2 {print $4}')
|
||||
log "Disk available: $DISK_AVAIL"
|
||||
|
||||
# Check for existing packages (only list ones that are installed)
|
||||
log "Scanning for installed cleanup candidates..."
|
||||
|
||||
# ============================================================
|
||||
# 2. OLD MULTIMEDIA PACKAGES
|
||||
# ============================================================
|
||||
section "[1/7] Old Multimedia Packages"
|
||||
|
||||
CANDIDATES=()
|
||||
# Old ffmpeg version
|
||||
if pacman -Qe ffmpeg4.4 &>/dev/null || pacman -Q ffmpeg4.4 &>/dev/null; then
|
||||
CANDIDATES+=(ffmpeg4.4)
|
||||
fi
|
||||
|
||||
# VLC plugins (keep base vlc)
|
||||
while IFS= read -r pkg; do
|
||||
[[ "$pkg" == "vlc" ]] && continue # skip base vlc
|
||||
CANDIDATES+=("$pkg")
|
||||
done < <(pacman -Qq 2>/dev/null | grep -E '^vlc-' || true)
|
||||
|
||||
TOTAL_CANDIDATES=$((TOTAL_CANDIDATES + ${#CANDIDATES[@]}))
|
||||
log "Found ${#CANDIDATES[@]} multimedia candidates: ${CANDIDATES[*]:0:5}..."
|
||||
|
||||
if [[ ${#CANDIDATES[@]} -gt 0 ]]; then
|
||||
log "Candidates: ${CANDIDATES[*]}"
|
||||
if [[ "$MODE" == "check" ]]; then
|
||||
log "DRY-RUN: Would run: sudo pacman -Rns --noconfirm ${CANDIDATES[*]}"
|
||||
else
|
||||
sudo pacman -Rns --noconfirm "${CANDIDATES[@]}" 2>&1 | tee -a "$LOCAL_LOG" || true
|
||||
TOTAL_REMOVED=$((TOTAL_REMOVED + ${#CANDIDATES[@]}))
|
||||
fi
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# 3. INACTIVE NVIDIA/MHWHD DRIVERS
|
||||
# ============================================================
|
||||
section "[2/7] Inactive NVIDIA Drivers (mhwd-nvidia-*)"
|
||||
|
||||
CANDIDATES=()
|
||||
while IFS= read -r pkg; do
|
||||
[[ -n "$pkg" ]] && CANDIDATES+=("$pkg")
|
||||
done < <(pacman -Qq 2>/dev/null | grep -E '^mhwd-nvidia-' || true)
|
||||
|
||||
TOTAL_CANDIDATES=$((TOTAL_CANDIDATES + ${#CANDIDATES[@]}))
|
||||
if [[ ${#CANDIDATES[@]} -gt 0 ]]; then
|
||||
log "Found ${#CANDIDATES[@]} mhwd-nvidia candidates: ${CANDIDATES[*]}"
|
||||
if [[ "$MODE" == "check" ]]; then
|
||||
log "DRY-RUN: Would run: sudo pacman -Rns --noconfirm ${CANDIDATES[*]}"
|
||||
else
|
||||
sudo pacman -Rns --noconfirm "${CANDIDATES[@]}" 2>&1 | tee -a "$LOCAL_LOG" || true
|
||||
TOTAL_REMOVED=$((TOTAL_REMOVED + ${#CANDIDATES[@]}))
|
||||
fi
|
||||
else
|
||||
log "No mhwd-nvidia packages found."
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# 4. OLD KERNELS
|
||||
# ============================================================
|
||||
section "[3/7] Old Kernel Packages"
|
||||
|
||||
CANDIDATES=()
|
||||
# linux618 and linux70 (keep current running kernel and its modules)
|
||||
CURRENT_KERNEL=$(uname -r)
|
||||
KMVR="${CURRENT_KERNEL%-MANJARO}" # 6.18.33-1
|
||||
KMJ="${KMVR%%.*}" # 6
|
||||
KMREST="${KMVR#*.}" # 18.33-1
|
||||
KMN="${KMREST%%.*}" # 18
|
||||
CURRENT_KERNEL_PKG="linux${KMJ}${KMN}" # linux618
|
||||
log "Current running kernel: $CURRENT_KERNEL (package: $CURRENT_KERNEL_PKG)"
|
||||
|
||||
for kernel in linux618 linux70; do
|
||||
# Skip if this is the running kernel package
|
||||
if [[ "$kernel" == "linux${CURRENT_KERNEL_PKG}" ]]; then
|
||||
log "Skipping $kernel — this is the running kernel."
|
||||
continue
|
||||
fi
|
||||
if pacman -Q "$kernel" &>/dev/null; then
|
||||
CANDIDATES+=("$kernel")
|
||||
# Also remove its modules/headers/etc (but not nvidia/r8168 for running kernel — already skipped above)
|
||||
while IFS= read -r pkg; do
|
||||
[[ -n "$pkg" ]] && CANDIDATES+=("$pkg")
|
||||
done < <(pacman -Qq 2>/dev/null | grep -E "^${kernel}-" || true)
|
||||
fi
|
||||
done
|
||||
|
||||
TOTAL_CANDIDATES=$((TOTAL_CANDIDATES + ${#CANDIDATES[@]}))
|
||||
if [[ ${#CANDIDATES[@]} -gt 0 ]]; then
|
||||
log "Found ${#CANDIDATES[@]} kernel candidates: ${CANDIDATES[*]}"
|
||||
if [[ "$MODE" == "check" ]]; then
|
||||
log "DRY-RUN: Would run: sudo pacman -Rns --noconfirm ${CANDIDATES[*]}"
|
||||
else
|
||||
sudo pacman -Rns --noconfirm "${CANDIDATES[@]}" 2>&1 | tee -a "$LOCAL_LOG" || true
|
||||
TOTAL_REMOVED=$((TOTAL_REMOVED + ${#CANDIDATES[@]}))
|
||||
fi
|
||||
else
|
||||
log "No old kernel packages found."
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# 5. lib32 PACKAGES
|
||||
# ============================================================
|
||||
section "[4/7] lib32 Packages"
|
||||
|
||||
CANDIDATES=()
|
||||
while IFS= read -r pkg; do
|
||||
[[ -n "$pkg" ]] && CANDIDATES+=("$pkg")
|
||||
done < <(pacman -Qq 2>/dev/null | grep -E '^lib32-' || true)
|
||||
|
||||
TOTAL_CANDIDATES=$((TOTAL_CANDIDATES + ${#CANDIDATES[@]}))
|
||||
log "Found ${#CANDIDATES[@]} lib32 packages"
|
||||
|
||||
if [[ ${#CANDIDATES[@]} -gt 0 ]]; then
|
||||
# List first 10, then total
|
||||
log "First 10 candidates: ${CANDIDATES[*]:0:10}..."
|
||||
log "Total: ${#CANDIDATES[@]} lib32 packages"
|
||||
|
||||
# Interactive: ask user if they want to remove lib32 packages
|
||||
if [[ "$MODE" == "check" ]]; then
|
||||
log "DRY-RUN: Would run: sudo pacman -Rns --noconfirm ${CANDIDATES[*]}"
|
||||
else
|
||||
log "${BOLD}This will remove ALL ${#CANDIDATES[@]} lib32 packages.${RESET}"
|
||||
log "Only remove if you don't run 32-bit applications (games, wine, etc.)"
|
||||
read -rp "Remove all lib32 packages? [y/N] " confirm
|
||||
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
||||
sudo pacman -Rns --noconfirm "${CANDIDATES[@]}" 2>&1 | tee -a "$LOCAL_LOG" || true
|
||||
TOTAL_REMOVED=$((TOTAL_REMOVED + ${#CANDIDATES[@]}))
|
||||
else
|
||||
log "SKIPPED lib32 removal."
|
||||
SKIPPED=$((SKIPPED + ${#CANDIDATES[@]}))
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# 6. REDUNDANT FONTS (Nerd Font variants)
|
||||
# ============================================================
|
||||
section "[5/7] Redundant Font Packages"
|
||||
|
||||
CANDIDATES=()
|
||||
# Nerd Font packages (keep the one in active use)
|
||||
while IFS= read -r pkg; do
|
||||
[[ -n "$pkg" ]] && CANDIDATES+=("$pkg")
|
||||
done < <(pacman -Qq 2>/dev/null | grep -iE 'nerd-font' || true)
|
||||
|
||||
# Also check for duplicate font collections
|
||||
while IFS= read -r pkg; do
|
||||
[[ -n "$pkg" ]] && CANDIDATES+=("$pkg")
|
||||
done < <(pacman -Qq 2>/dev/null | grep -iE 'ttf-(jetbrains|iosevka|fira|hack|noto)' || true)
|
||||
|
||||
TOTAL_CANDIDATES=$((TOTAL_CANDIDATES + ${#CANDIDATES[@]}))
|
||||
if [[ ${#CANDIDATES[@]} -gt 0 ]]; then
|
||||
log "Found ${#CANDIDATES[@]} font candidates:"
|
||||
for pkg in "${CANDIDATES[@]}"; do
|
||||
log " - $pkg"
|
||||
done
|
||||
|
||||
if [[ "$MODE" == "check" ]]; then
|
||||
log "DRY-RUN: Would show above packages for manual review"
|
||||
else
|
||||
log "${RED}CRITICAL: Review font packages carefully - removing wrong fonts can break UI.${RESET}"
|
||||
read -rp "Remove these ${#CANDIDATES[@]} font packages? [y/N] " confirm
|
||||
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
||||
sudo pacman -Rns --noconfirm "${CANDIDATES[@]}" 2>&1 | tee -a "$LOCAL_LOG" || true
|
||||
TOTAL_REMOVED=$((TOTAL_REMOVED + ${#CANDIDATES[@]}))
|
||||
else
|
||||
log "SKIPPED font removal."
|
||||
SKIPPED=$((SKIPPED + ${#CANDIDATES[@]}))
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log "No redundant font packages found."
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# 7. BROWSERS (keep current active, remove extras)
|
||||
# ============================================================
|
||||
section "[6/7] Browser Packages"
|
||||
|
||||
CANDIDATES=()
|
||||
# Check which browsers are installed
|
||||
BROWSERS=()
|
||||
for browser in firefox chromium google-chrome firefox-developer-edition brave-bin microsoft-edge; do
|
||||
if pacman -Q "$browser" &>/dev/null; then
|
||||
BROWSERS+=("$browser")
|
||||
fi
|
||||
done
|
||||
|
||||
log "Installed browsers: ${BROWSERS[*]}"
|
||||
log "${BOLD}Recommended: Keep firefox-developer-edition + one other${RESET}"
|
||||
|
||||
if [[ ${#BROWSERS[@]} -gt 2 ]]; then
|
||||
# Suggest removal of non-essential browsers
|
||||
SUGGESTED_REMOVAL=()
|
||||
for b in "${BROWSERS[@]}"; do
|
||||
# Keep firefox-developer-edition and chromium/google-chrome
|
||||
if [[ "$b" != "firefox-developer-edition" && "$b" != "chromium" && "$b" != "google-chrome" ]]; then
|
||||
SUGGESTED_REMOVAL+=("$b")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#SUGGESTED_REMOVAL[@]} -gt 0 ]]; then
|
||||
CANDIDATES=("${SUGGESTED_REMOVAL[@]}")
|
||||
TOTAL_CANDIDATES=$((TOTAL_CANDIDATES + ${#CANDIDATES[@]}))
|
||||
log "Suggested removal: ${CANDIDATES[*]}"
|
||||
if [[ "$MODE" == "check" ]]; then
|
||||
log "DRY-RUN: Would run: sudo pacman -Rns --noconfirm ${CANDIDATES[*]}"
|
||||
else
|
||||
sudo pacman -Rns --noconfirm "${CANDIDATES[@]}" 2>&1 | tee -a "$LOCAL_LOG" || true
|
||||
TOTAL_REMOVED=$((TOTAL_REMOVED + ${#CANDIDATES[@]}))
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log "Browser count is manageable (${#BROWSERS[@]}). No action needed."
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# 8. UNUSED PYTHON PACKAGES
|
||||
# ============================================================
|
||||
section "[7/7] Unused Python GUI Packages"
|
||||
|
||||
CANDIDATES=()
|
||||
PYTHON_CANDIDATES=(
|
||||
wxpython
|
||||
wxpython-gtk3
|
||||
pyqt5
|
||||
pyqt5-sip
|
||||
pyqt6
|
||||
pyqt6-sip
|
||||
fluidity
|
||||
tk
|
||||
tkinter
|
||||
python-tk
|
||||
)
|
||||
|
||||
for pkg in "${PYTHON_CANDIDATES[@]}"; do
|
||||
if pacman -Q "$pkg" &>/dev/null; then
|
||||
CANDIDATES+=("$pkg")
|
||||
fi
|
||||
done
|
||||
|
||||
TOTAL_CANDIDATES=$((TOTAL_CANDIDATES + ${#CANDIDATES[@]}))
|
||||
if [[ ${#CANDIDATES[@]} -gt 0 ]]; then
|
||||
log "Found ${#CANDIDATES[@]} Python GUI candidates: ${CANDIDATES[*]}"
|
||||
if [[ "$MODE" == "check" ]]; then
|
||||
log "DRY-RUN: Would run: sudo pacman -Rns --noconfirm ${CANDIDATES[*]}"
|
||||
else
|
||||
sudo pacman -Rns --noconfirm "${CANDIDATES[@]}" 2>&1 | tee -a "$LOCAL_LOG" || true
|
||||
TOTAL_REMOVED=$((TOTAL_REMOVED + ${#CANDIDATES[@]}))
|
||||
fi
|
||||
else
|
||||
log "No unused Python GUI packages found."
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# CLEANUP ORPHANS & CACHE
|
||||
# ============================================================
|
||||
section "Post-Cleanup: Orphans & Cache"
|
||||
|
||||
log "Finding orphaned dependencies..."
|
||||
ORPHANS=$(pacman -Qtdq 2>/dev/null || true)
|
||||
if [[ -n "$ORPHANS" ]]; then
|
||||
ORPHAN_COUNT=$(echo "$ORPHANS" | wc -l)
|
||||
log "Found $ORPHAN_COUNT orphaned packages"
|
||||
if [[ "$MODE" == "check" ]]; then
|
||||
log "DRY-RUN: Would run: sudo pacman -Rns --noconfirm $(echo $ORPHANS)"
|
||||
else
|
||||
sudo pacman -Rns --noconfirm $ORPHANS 2>&1 | tee -a "$LOCAL_LOG" || true
|
||||
TOTAL_REMOVED=$((TOTAL_REMOVED + ORPHAN_COUNT))
|
||||
fi
|
||||
else
|
||||
log "No orphaned packages found."
|
||||
fi
|
||||
|
||||
log "Cleaning pacman cache (keep last 2 versions)..."
|
||||
if [[ "$MODE" == "check" ]]; then
|
||||
CACHE_SIZE=$(du -sh /var/cache/pacman/pkg/ 2>/dev/null | cut -f1)
|
||||
log "Cache size: $CACHE_SIZE"
|
||||
log "DRY-RUN: Would run: sudo paccache -rk2"
|
||||
else
|
||||
sudo paccache -rk2 2>&1 | tee -a "$LOCAL_LOG" || true
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# SUMMARY
|
||||
# ============================================================
|
||||
section "Cleanup Summary"
|
||||
|
||||
FINAL_DISK=$(df -h / | awk 'NR==2 {print $4}')
|
||||
log "Disk space available after cleanup: $FINAL_DISK"
|
||||
log "Total candidates identified: $TOTAL_CANDIDATES"
|
||||
log "Packages removed: $TOTAL_REMOVED"
|
||||
log "Packages skipped: $SKIPPED"
|
||||
log "Log saved to: $LOCAL_LOG"
|
||||
|
||||
log ""
|
||||
log "============================================="
|
||||
log " Cleanup Complete"
|
||||
log "============================================="
|
||||
|
||||
# Cleanup temp files
|
||||
rm -f "$HOME/logs/.cleanup-candidates-"*.txt 2>/dev/null || true
|
||||
Executable
+230
@@ -0,0 +1,230 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# NVIDIA Kernel Module Recovery Script
|
||||
# Purpose: Reinstall nvidia-580xx-dkms and rebuild kernel module after package cleanup removed it
|
||||
# Target: mw-pfeddersheim-workstation (Manjaro, NVIDIA GPU)
|
||||
|
||||
MODE="apply"
|
||||
if [[ "${1:-}" == "--check" ]]; then
|
||||
MODE="check"
|
||||
echo "=== Running in CHECK mode (diagnostic only, no changes) ==="
|
||||
elif [[ "${1:-}" == "--help" ]]; then
|
||||
echo "Usage: $0 [--check | --apply | --help]"
|
||||
echo " --check Diagnose and show what would be done"
|
||||
echo " --apply Reinstall nvidia-580xx-dkms and rebuild module (requires sudo)"
|
||||
echo " --help Show this help"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$HOME/logs" 2>/dev/null || true
|
||||
LOCAL_LOG="$HOME/logs/nvidia-recovery-$(date +%Y%m%d-%H%M%S).log"
|
||||
BOLD=$(tput bold 2>/dev/null || true)
|
||||
RED=$(tput setaf 1 2>/dev/null || true)
|
||||
GREEN=$(tput setaf 2 2>/dev/null || true)
|
||||
YELLOW=$(tput setaf 3 2>/dev/null || true)
|
||||
RESET=$(tput sgr0 2>/dev/null || true)
|
||||
|
||||
log() { echo -e "$*" | tee -a "$LOCAL_LOG"; }
|
||||
section() { log ""; log "${BOLD}--- $1 ---${RESET}"; }
|
||||
|
||||
echo "" | tee -a "$LOCAL_LOG"
|
||||
log "============================================="
|
||||
log " NVIDIA Kernel Module Recovery"
|
||||
log " Date: $(date)"
|
||||
log " Log: $LOCAL_LOG"
|
||||
log "============================================="
|
||||
|
||||
# ============================================================
|
||||
# 1. DIAGNOSTICS
|
||||
# ============================================================
|
||||
section "Diagnostic"
|
||||
|
||||
# Get current kernel
|
||||
RUNNING_KERNEL=$(uname -r)
|
||||
log "Running kernel: $RED$RUNNING_KERNEL$RESET"
|
||||
|
||||
KERNEL_VERSION="${RUNNING_KERNEL%-MANJARO}" # 6.18.33-1
|
||||
KERNEL_MAJOR="${KERNEL_VERSION%%.*}" # 6
|
||||
KERNEL_MINOR="${KERNEL_VERSION#*.}" # 18.33-1
|
||||
KERNEL_MINOR="${KERNEL_MINOR%%.*}" # 18
|
||||
KERNEL_PKG="linux${KERNEL_MAJOR}${KERNEL_MINOR}" # linux618
|
||||
log "Kernel package: $KERNEL_PKG"
|
||||
|
||||
# Check if nvidia module exists
|
||||
MODULE_PATH="/usr/lib/modules/$RUNNING_KERNEL/extra/modules/nvidia.ko.zst"
|
||||
MODULE_PATH2="/usr/lib/modules/$RUNNING_KERNEL/extra/nvidia.ko.zst"
|
||||
MODULE_PATH3="/usr/lib/modules/$RUNNING_KERNEL/updates/dkms/nvidia.ko.zst"
|
||||
MODULE_PATH4="/usr/lib/modules/$RUNNING_KERNEL/updates/nvidia.ko.zst"
|
||||
MODULE_PATH5="/usr/lib/modules/$RUNNING_KERNEL/kernel/drivers/gpu/drm/nvidia/nvidia.ko.zst"
|
||||
|
||||
MODULE_FOUND=false
|
||||
if [[ -f "$MODULE_PATH" ]] || [[ -f "$MODULE_PATH2" ]] || [[ -f "$MODULE_PATH3" ]] || [[ -f "$MODULE_PATH4" ]] || [[ -f "$MODULE_PATH5" ]]; then
|
||||
MODULE_FOUND=true
|
||||
log "${GREEN}NVIDIA module found at $MODULE_PATH3$RESET"
|
||||
else
|
||||
log "${RED}NVIDIA module MISSING for kernel $RUNNING_KERNEL$RESET"
|
||||
fi
|
||||
|
||||
# Check nvidia-utils
|
||||
if command -v nvidia-smi &>/dev/null; then
|
||||
log "${GREEN}nvidia-smi: available$RESET"
|
||||
else
|
||||
log "${RED}nvidia-smi: not found$RESET"
|
||||
fi
|
||||
|
||||
# Check DKMS
|
||||
if command -v dkms &>/dev/null; then
|
||||
log "${GREEN}dkms: available$RESET"
|
||||
else
|
||||
log "${RED}dkms: not found$RESET"
|
||||
fi
|
||||
|
||||
# Check if nvidia-580xx-dkms is installed
|
||||
if pacman -Q nvidia-580xx-dkms &>/dev/null; then
|
||||
DKMS_INSTALLED=true
|
||||
log "${GREEN}nvidia-580xx-dkms: installed$(pacman -Qi nvidia-580xx-dkms 2>/dev/null | grep Version)"
|
||||
else
|
||||
DKMS_INSTALLED=false
|
||||
log "${RED}nvidia-580xx-dkms: NOT installed$RESET"
|
||||
fi
|
||||
|
||||
# Check if nvidia-utils is installed
|
||||
if pacman -Q nvidia-utils &>/dev/null; then
|
||||
log "${GREEN}nvidia-utils: installed"
|
||||
else
|
||||
log "${RED}nvidia-utils: NOT installed$RESET"
|
||||
fi
|
||||
|
||||
# Check modules-load config
|
||||
if [[ -f /etc/modules-load.d/mhwd-gpu.conf ]]; then
|
||||
log "modules-load config: /etc/modules-load.d/mhwd-gpu.conf exists"
|
||||
log " Entries: $(grep -v '^#\|^$' /etc/modules-load.d/mhwd-gpu.conf | tr '\n' ' ')"
|
||||
else
|
||||
log "${RED}No mhwd-gpu.conf found$RESET"
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
# 2. CHECK DEPENDENCIES
|
||||
# ============================================================
|
||||
section "Dependency Check"
|
||||
|
||||
MISSING=()
|
||||
if ! command -v dkms &>/dev/null; then
|
||||
MISSING+=(dkms)
|
||||
fi
|
||||
if ! command -v mkinitcpio &>/dev/null; then
|
||||
MISSING+=(mkinitcpio)
|
||||
fi
|
||||
|
||||
if [[ ${#MISSING[@]} -gt 0 ]]; then
|
||||
log "${RED}Missing base tools: ${MISSING[*]}$RESET"
|
||||
log "These will be installed as part of the fix."
|
||||
fi
|
||||
|
||||
# Check available disk space
|
||||
DISK_AVAIL=$(df -h / | awk 'NR==2 {print $4}')
|
||||
log "Disk available: $DISK_AVAIL"
|
||||
|
||||
# ============================================================
|
||||
# 3. APPLY FIX
|
||||
# ============================================================
|
||||
section "Applying Fix"
|
||||
|
||||
if [[ "$MODE" == "check" ]]; then
|
||||
log "${BOLD}DRY-RUN MODE${RESET}"
|
||||
log "Would execute:"
|
||||
log " sudo pacman -Rns --noconfirm nvidia-utils # remove conflicting non-580xx driver"
|
||||
log " sudo pacman -S --needed nvidia-580xx-dkms nvidia-580xx-utils"
|
||||
log " sudo pacman -S --needed linux618-headers # kernel headers for DKMS"
|
||||
log " sudo dkms autoinstall"
|
||||
log " sudo mkinitcpio -P"
|
||||
log " sudo reboot"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Confirm before destructive action
|
||||
log "${BOLD}This will reinstall the NVIDIA kernel module driver.$RESET"
|
||||
log "The system will reboot automatically."
|
||||
read -rp "Continue? [y/N] " confirm
|
||||
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
||||
log "ABORTED by user."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Step 1: Resolve driver conflict, install 580xx suite
|
||||
log "[1/5] Resolving NVIDIA driver conflict and installing 580xx suite..."
|
||||
# Check if newer nvidia-utils conflicts with 580xx-dkms
|
||||
if pacman -Q nvidia-utils &>/dev/null && ! pacman -Q nvidia-580xx-utils &>/dev/null; then
|
||||
log "Conflict: nvidia-utils (non-580xx) installed alongside nvidia-580xx-dkms."
|
||||
log "Removing conflicting nvidia-utils, installing nvidia-580xx-utils..."
|
||||
sudo pacman -Rns --noconfirm nvidia-utils 2>&1 | tee -a "$LOCAL_LOG"
|
||||
sudo pacman -S --needed --noconfirm nvidia-580xx-dkms nvidia-580xx-utils 2>&1 | tee -a "$LOCAL_LOG"
|
||||
else
|
||||
sudo pacman -S --needed --noconfirm nvidia-580xx-dkms nvidia-580xx-utils 2>&1 | tee -a "$LOCAL_LOG"
|
||||
fi
|
||||
|
||||
# Step 2: Ensure kernel headers are available for DKMS
|
||||
log "[1b/4] Checking kernel headers for DKMS..."
|
||||
KERNEL_HEADERS="${KERNEL_PKG}-headers"
|
||||
if pacman -Q "$KERNEL_HEADERS" &>/dev/null; then
|
||||
log "${GREEN}Kernel headers ($KERNEL_HEADERS) already installed.$RESET"
|
||||
else
|
||||
log "Installing $KERNEL_HEADERS for DKMS..."
|
||||
sudo pacman -S --needed --noconfirm "$KERNEL_HEADERS" 2>&1 | tee -a "$LOCAL_LOG"
|
||||
fi
|
||||
|
||||
# Step 3: Rebuild DKMS module for current kernel
|
||||
log "[2/5] Rebuilding DKMS module for kernel $RUNNING_KERNEL..."
|
||||
sudo dkms autoinstall 2>&1 | tee -a "$LOCAL_LOG"
|
||||
|
||||
# Step 3: Verify module was created
|
||||
log "[3/5] Verifying module..."
|
||||
if [[ -f "$MODULE_PATH" ]] || [[ -f "$MODULE_PATH2" ]] || [[ -f "$MODULE_PATH3" ]] || [[ -f "$MODULE_PATH4" ]] || [[ -f "$MODULE_PATH5" ]]; then
|
||||
log "${GREEN}Module verified!$RESET"
|
||||
# Show module details
|
||||
modinfo_output=$(modinfo nvidia 2>/dev/null || echo "modinfo failed")
|
||||
log " $modinfo_output"
|
||||
else
|
||||
log "${RED}WARNING: Module file not found after DKMS build.$RESET"
|
||||
# Detect version for manual build
|
||||
DKMS_VERSION=$(pacman -Qi nvidia-580xx-dkms 2>/dev/null | grep Version | awk '{print $2}' | cut -d- -f1)
|
||||
[[ -z "$DKMS_VERSION" ]] && DKMS_VERSION="580.159.04"
|
||||
log "Attempting manual build (version: $DKMS_VERSION)..."
|
||||
sudo dkms build -m nvidia -v "$DKMS_VERSION" -k "$RUNNING_KERNEL" 2>&1 | tee -a "$LOCAL_LOG"
|
||||
sudo dkms install -m nvidia -v "$DKMS_VERSION" -k "$RUNNING_KERNEL" 2>&1 | tee -a "$LOCAL_LOG"
|
||||
fi
|
||||
|
||||
# Step 4: Rebuild initramfs
|
||||
log "[4/5] Rebuilding initramfs..."
|
||||
sudo mkinitcpio -P 2>&1 | tee -a "$LOCAL_LOG"
|
||||
|
||||
# ============================================================
|
||||
# 4. VERIFICATION
|
||||
# ============================================================
|
||||
section "Post-Recovery Verification"
|
||||
|
||||
log "Checking module loading..."
|
||||
if sudo modprobe nvidia 2>&1; then
|
||||
log "${GREEN}nvidia module loaded successfully$RESET"
|
||||
else
|
||||
log "${YELLOW}modprobe nvidia failed - module will be loaded at next boot$RESET"
|
||||
fi
|
||||
|
||||
# Detect 580xx version for manual DKMS fallback
|
||||
DKMS_VERSION=$(pacman -Qi nvidia-580xx-dkms 2>/dev/null | grep Version | awk '{print $2}' | cut -d- -f1 || echo "580.159.04")
|
||||
|
||||
# Manual DKMS fallback uses DKMS_VERSION variable
|
||||
|
||||
log ""
|
||||
log "${BOLD}Recovery complete!${RESET}"
|
||||
log ""
|
||||
log "Before rebooting, verify:"
|
||||
log " 1. Module exists: ls /usr/lib/modules/$(uname -r)/extra/nvidia*"
|
||||
log " 2. nvidia-smi works: sudo nvidia-smi"
|
||||
log ""
|
||||
log "Then reboot:"
|
||||
log " sudo reboot"
|
||||
log ""
|
||||
log "Log saved to: $LOCAL_LOG"
|
||||
log "============================================="
|
||||
Reference in New Issue
Block a user