Compare commits
44
Commits
5d770d2def
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd63df4f59 | ||
|
|
c67dd74e0b | ||
|
|
cbe24c03df | ||
|
|
269079df34 | ||
|
|
b888ef90c4 | ||
|
|
441e28bda3 | ||
|
|
ce99f58374 | ||
|
|
2b6ce0d635 | ||
|
|
d72e3782e8 | ||
|
|
07bd3f8699 | ||
|
|
feaccf201d | ||
|
|
5a714e6256 | ||
|
|
d9392447f9 | ||
|
|
de24990208 | ||
|
|
fe7f133136 | ||
|
|
f1544747a0 | ||
|
|
cb9b8474bb | ||
|
|
3c7bf3bd73 | ||
|
|
5173fdd5e0 | ||
|
|
dbf366f8a5 | ||
|
|
6f455ab0f2 | ||
|
|
b0af3dcf12 | ||
|
|
2487aa5749 | ||
|
|
7d8760592e | ||
|
|
d746d76530 | ||
|
|
a9bc934543 | ||
|
|
ee592cc21b | ||
|
|
f81c789683 | ||
|
|
2bf5f5c005 | ||
|
|
1fcb467c04 | ||
|
|
9d06a71f71 | ||
|
|
1274b3e2cb | ||
|
|
89acae9751 | ||
|
|
7a95ef0019 | ||
|
|
a67c629356 | ||
|
|
cdb0730280 | ||
|
|
bab92f0292 | ||
|
|
54d8729e63 | ||
|
|
e6f3506ef1 | ||
|
|
724ccebdb0 | ||
|
|
45ad579120 | ||
|
|
6ff790fd4b | ||
|
|
bbdcd43e01 | ||
|
|
8fec0802ad |
@@ -15,8 +15,12 @@ CLAUDE.local.md
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
.rlm/logs/
|
||||
|
||||
# Auto-generated capability discovery
|
||||
docs/tech/capabilities/
|
||||
|
||||
# Dependencies
|
||||
node_modules/
|
||||
venv/
|
||||
@@ -29,3 +33,8 @@ __pycache__/
|
||||
|
||||
# AI Agent memory
|
||||
.junie/
|
||||
|
||||
# LibreOffice lock files
|
||||
.~lock.*#
|
||||
|
||||
/.quarto/
|
||||
|
||||
Generated
+5
@@ -0,0 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ComposerSettings">
|
||||
<execution />
|
||||
</component>
|
||||
</project>
|
||||
@@ -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
|
||||
@@ -25,8 +29,90 @@ This repository manages the infrastructure, configuration, and maintenance for M
|
||||
- **NEVER** commit raw secrets to the repository.
|
||||
- **ALWAYS** run Ansible with `--check` before applying changes.
|
||||
- **NEVER** modify system configuration directly if it can be managed via Ansible.
|
||||
- **Secrets Management**: All secrets stored in Bitwarden (`bitwarden.jantec.xyz`) and cached locally in `gnome-keyring` (D-Bus secrets service). See `docs/product/passwords.md` for details.
|
||||
|
||||
## Rules Hierarchy
|
||||
- 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.
|
||||
|
||||
+51
-3
@@ -7,7 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- Fixed Hyprland configuration warning/error by changing the DVI-D-1 monitor `transform` property from a string `"1"` to an integer `1` (2026-07-09).
|
||||
- tuigreet boot error: removed invalid `--numlock` flag from greetd config (not a valid tuigreet parameter). Replaced X11-only `numlockx on` in Hyprland with native `numlock_by_default = true` in the `input` section (2026-06-01).
|
||||
- hypridle SEGV crash loop (ADR N/A, hotfix): sdbus-cpp 2.3.0 had an ABI incompatibility causing hypridle to SIGSEGV in `libsdbus-c++.so.2` `registerSignalHandler`. Fixed by upgrading sdbus-cpp from 2.3.0-1 to 2.3.1-1 (Arch upstream, pulled ahead of Manjaro mirror sync). Also resolves latent ABI issues for hyprlock and xdg-desktop-portal-hyprland (2026-05-22).
|
||||
- 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 `^<!--(SECTION)-->(N/M)<!--(--SECTION)-->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.
|
||||
|
||||
### Added
|
||||
- Added `agent0.localhost` (-> `reverse_proxy 192.168.0.141:8090`, `flush_interval -1`) to Caddy in a dedicated file `/etc/caddy/conf.d/agent0.localhost.conf`. First non-localhost backend (remote LAN host running a uvicorn app). Regenerated mkcert cert to add explicit `agent0.localhost` SAN; cert now has 20 SANs, valid until 2028-10-21 (2026-07-21). Key learning: the `*.localhost` wildcard is NOT honored by curl/system trust even for single-label subdomains - `.localhost` is a special-use TLD per RFC 6761, so every subdomain needs an explicit SAN. This corrects the prior assumption in `docs/tech/stack.md` and `docs/tech/manual-overrides.md` that single-label subdomains were wildcard-covered. Total `*.localhost` sites now 18.
|
||||
- Regenerated mkcert cert at `/etc/caddy/localhost{,-key}.pem` to add explicit SANs for `platform.localhost`, `packages.localhost`, and `onlyoffice.docs.localhost` (pre-existing in Caddy config but missing from cert, causing TLS validation failures). Cert now has 19 SANs, valid until 2028-10-12 (2026-07-12).
|
||||
- Added `platform.localhost` (-> `localhost:10080`, amicron-platform Docker) and `packages.localhost` (-> `localhost:20080`, satis Docker) to `/etc/caddy/conf.d/localhost-dev.conf`. Docker port mappings updated: `~/internal/amicron-platform/docker-compose.override.yml` `10080:80`, `~/internal/satis/docker-compose.yml` nginx `20080:80`. Both upstreams plain HTTP on high ports; Caddy terminates TLS. Also documented `onlyoffice.docs.localhost` in `docs/tech/stack.md` (pre-existing in Caddy config, was undocumented). Total `*.localhost` sites now 17. The three form the local E2E test triangle: `platform.localhost` (ERP) -> `packages.localhost` (Satis) -> shared Firebird 3.0.14 database (token validation), mirroring the production `platform.auc.de` + `packages.auc.de` + shared Firebird setup (2026-07-12).
|
||||
- Enabled click-and-drag window movement and resizing under the SUPER modifier, and enabled border dragging to resize tiled windows natively, updating both the Hyprland configuration template and keybindings cheatsheet (2026-07-09).
|
||||
- Documented local dev services in `docs/tech/stack.md`: Caddy (systemd `caddy.service` on port 443) with 14 `*.localhost` sites and mkcert TLS at `/etc/caddy/`. Registered as a manual override in `docs/tech/manual-overrides.md` (no `caddy` Ansible role exists yet) (2026-07-06).
|
||||
- Investigation learning record `docs/learnings/2026-07-06-caddy-onlyoffice-mcp-sse-investigation.md`: Caddy `context canceled` SSE aborts (~1.5ms) on `onlyoffice.localhost/mcp` forwarded to `localhost:3847` (OnlyOffice MCP server). Upstream verified alive (HTTP 406 to plain GET = expected). Handed off to GitLab issue [satware/mcp/onlyoffice#92](https://gitlab.satware.com/satware/mcp/onlyoffice/-/work_items/92) with 5 hypotheses, 7 investigation tasks, and a proposed Caddy `flush_interval -1` + `transport http { versions 1.1 }` mitigation (not applied yet) (2026-07-06).
|
||||
- Waybar USB module: auto-mount USB sticks via udisks2, show hover details (device/model/size/fs/mountpoint), left-click opens in yazi, right-click ejects. Script at `~/.config/waybar/scripts/usb.sh`, Ansible-managed via `hyprland` role template `waybar_usb_script.sh.j2` (2026-05-22).
|
||||
- `udisks2` explicitly listed in `hyprland_packages` (was transitive dep of Dolphin; now direct dep of USB module) (2026-05-22).
|
||||
- Yazi optional deps provisioned via Ansible: `chafa`, `7zip`, `resvg` added to `hyprland_packages` (2026-05-22).
|
||||
- X11 package removal list `hyprland_packages_remove` in `ansible/vars/main.yml` with corresponding task in `hyprland` role — removes `xclip` and `xsel` on Wayland-only system (2026-05-22).
|
||||
- ADR 0006: Chrome/Chromium normalization and cleanup decision record (2026-05-22).
|
||||
- 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).
|
||||
- Created formal stability tests report documenting the 2026-03-17 memory management improvements (2026-03-17).
|
||||
- System stability improvements: enabled `systemd-oomd` and `zswap` via Ansible (2026-03-17).
|
||||
- Memory resource limits for Firebird and Mailpit Docker containers (2026-03-17).
|
||||
@@ -15,12 +41,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Integrated 16GB swap file management into Ansible roles for consistency and persistence (2026-03-17).
|
||||
- Comprehensive health report for `mw-pfeddersheim-workstation` (2026-03-09).
|
||||
|
||||
### Removed
|
||||
- Moved workstation-specific cheatsheets (`hyprland-keybindings.md`, `screenshot-cheatsheet.md`, `yazi-cheatsheet.md`, and the obsolete workstation copy of `neovim-cheatsheet.md`) to the central `learn` repository (2026-07-09).
|
||||
- `alesi-routine-morning.service` / `alesi-routine-morning.timer` user-level systemd units — daily 07:45 morning routine was failing (exit 128, git outside repo). Script `scripts/daily-routine.sh` retained for manual use (2026-05-26).
|
||||
- Tier 1 package cleanup (ADR 0007): removed 25 X11/KDE/legacy packages via Ansible — lightdm, lightdm-slick-greeter, lightdm-settings, sddm, sddm-kcm, sddm-breath-theme, xf86-video-{amdgpu,ati,intel,nouveau,vesa}, xf86-input-synaptics, xterm, xorg-{twm,xinit,xkill,server-xvfb}, x11vnc, numlockx, startup-notification, illyria-wallpaper, ntp, spectacle, pamac-tray-icon-plasma. All have Wayland-native replacements already in use. Frees ~80-120 MiB direct + cascade deps (2026-05-22).
|
||||
- Tier 2 package cleanup (ADR 0007): removed KDE Plasma stack — kwin, plasma-desktop, plasma-workspace, kdeplasma-addons, plasma-nm, plasma-pa, plasma-x11-session, kde-gtk-config, powerdevil, xdg-desktop-portal-kde, plasma-integration, dolphin, dolphin-plugins, kdegraphics-thumbnailers. Plus 45 cascade orphan deps. Dolphin replaced by yazi. Frees ~400 MiB total (2026-05-22).
|
||||
- Tier 3 package cleanup (ADR 0007): removed ruby (38 packages), love, xorg-server, xf86-input-libinput. No active Ruby usage; no .love files; xorg-server no longer required (xorg-xwayland is independent). Frees ~30 MiB (2026-05-22).
|
||||
- Tier 2b: removed kscreen, kscreenlocker (KDE screen management) + 10 cascade orphan deps (libkscreen, kdeclarative, plasma5support, libksysguard, etc.). Freed 35 MiB. Replaced by wlr-randr/hyprlock (2026-05-22).
|
||||
- Removed stale `org.kde.kwalletd6.service` D-Bus stub from `~/.local/share/dbus-1/services/` (was a broken `Exec=/bin/true` placeholder) (2026-05-22).
|
||||
- Disabled `bluetooth.service` (no BT hardware) and `NetworkManager-wait-online.service` (unnecessary on wired workstation, speeds up boot) (2026-05-22).
|
||||
- Documented PCIe slot misconfiguration: GPU (GTX 1050 Ti) installed in bottom PCIEX4 slot (PCIe 2.0 x4, ~2 GB/s) instead of top PCIEX16 (PCIe 3.0 x16, ~16 GB/s). 8x bandwidth loss. Physical move required. NVMe also running at Gen2 instead of Gen3 (2026-05-22).
|
||||
|
||||
### Changed
|
||||
- Tier 4 package reclassification (ADR 0007): 13 "Misc" packages reclassified to functional domains in `docs/tech/stack.md` — uv→Python Toolchain, jq→Shell/Terminal, inxi→Hardware, lsof/zram-generator/mkinitcpio-openswap→Core OS, iproute2/openresolv→Network, perl-image-exiftool/icoutils/innoextract-git→Dev Tools, opencl-headers→GPU Compute, libxnvctrl-580xx→NVIDIA. No packages removed; documentation-only change (2026-05-22).
|
||||
- Chrome/Chromium normalization: removed all Chromium artifacts — `~/.config/chromium/` (1.4 GB), `~/.local/chrome-cdp/` (8.6 GB), 13 broken PWA `.desktop` files, 4 stale PWA manifests from Chrome Preferences, `chromium-widevine` orphan package (17 MB). System now uses `google-chrome-stable` exclusively with Wayland-native flags (2026-05-22).
|
||||
- `~/.bashrc`: removed dead `BROWSER_CDP_URL` auto-population (curl to localhost:9222 on every shell startup) (2026-05-22).
|
||||
- `~/.config/chrome-flags.conf`: restored Ansible-managed Wayland flags (`--ozone-platform=auto`, VA-API on NVIDIA, `--use-angle=gl`, Vulkan disable) — was drifted to X11 mode with garbage flags (2026-05-22).
|
||||
- Waybar config: added `custom/usb` module to `modules-right` bar (2026-05-22).
|
||||
- Waybar style: added CSS for `#custom-usb` with hover state and color classes (`usb-none`, `usb-unmounted`, `usb-mounted`) (2026-05-22).
|
||||
- **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).
|
||||
- Increased system swap file size from 4GB to 16GB to prevent OOM-related system hangs (2026-03-17).
|
||||
- Updated system status in `README.md` and `docs/tech/workstation-health.md` (2026-03-17).
|
||||
- Cleaned up deprecated software information from `docs/tech/stack.md` (2026-03-13).
|
||||
- Synchronized software versions across documentation files (2026-03-13).
|
||||
|
||||
### Fixed
|
||||
- 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.
|
||||
|
||||
@@ -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
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
project:
|
||||
type: default
|
||||
output-dir: "./docs/pdf-output"
|
||||
|
||||
format:
|
||||
typst-pdf:
|
||||
default: true
|
||||
toc: false
|
||||
number-sections: true
|
||||
mainfont: "Inter"
|
||||
monofont: "JetBrains Mono"
|
||||
fontsize: 9pt
|
||||
papersize: a4
|
||||
margin:
|
||||
top: 1.2cm
|
||||
bottom: 1.2cm
|
||||
left: 1.5cm
|
||||
right: 1.5cm
|
||||
|
||||
lang: de
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,24 @@
|
||||
---
|
||||
- name: Ensure kitty config directory exists
|
||||
file:
|
||||
path: "{{ ansible_env.HOME }}/.config/kitty"
|
||||
state: directory
|
||||
owner: "{{ local_user }}"
|
||||
group: "{{ local_user }}"
|
||||
mode: '0755'
|
||||
tags: [kitty, terminal]
|
||||
|
||||
- name: Deploy kitty configuration
|
||||
template:
|
||||
src: kitty.conf.j2
|
||||
dest: "{{ ansible_env.HOME }}/.config/kitty/kitty.conf"
|
||||
owner: "{{ local_user }}"
|
||||
group: "{{ local_user }}"
|
||||
mode: '0644'
|
||||
vars:
|
||||
kitty_config_dir: "{{ ansible_env.HOME }}/.config/kitty"
|
||||
tags: [kitty, terminal]
|
||||
|
||||
- name: Check current swap file size
|
||||
stat:
|
||||
path: "{{ swap_file_path }}"
|
||||
@@ -57,6 +77,45 @@
|
||||
become: yes
|
||||
tags: [swap, optimization]
|
||||
|
||||
- name: Check current root mount options
|
||||
command:
|
||||
argv:
|
||||
- findmnt
|
||||
- -no
|
||||
- OPTIONS
|
||||
- /
|
||||
register: root_mount_options
|
||||
changed_when: false
|
||||
tags: [filesystem, optimization, trim]
|
||||
|
||||
- name: Ensure root filesystem uses periodic TRIM settings in fstab
|
||||
mount:
|
||||
path: /
|
||||
src: "UUID={{ root_filesystem_uuid }}"
|
||||
fstype: ext4
|
||||
opts: "{{ root_filesystem_mount_opts }}"
|
||||
passno: 1
|
||||
dump: 0
|
||||
state: present
|
||||
become: yes
|
||||
tags: [filesystem, optimization, trim]
|
||||
|
||||
- name: Reload systemd after fstab updates
|
||||
systemd:
|
||||
daemon_reload: yes
|
||||
changed_when: false
|
||||
become: yes
|
||||
tags: [filesystem, optimization, trim]
|
||||
|
||||
- name: Remount root filesystem without continuous discard
|
||||
mount:
|
||||
path: /
|
||||
opts: "{{ root_filesystem_mount_opts }},nodiscard"
|
||||
state: remounted
|
||||
when: "'discard' in (root_mount_options.stdout.split(','))"
|
||||
become: yes
|
||||
tags: [filesystem, optimization, trim]
|
||||
|
||||
- name: Optimize kernel parameters
|
||||
sysctl:
|
||||
name: "{{ item.name }}"
|
||||
@@ -81,6 +140,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 }}"
|
||||
@@ -143,4 +245,14 @@
|
||||
shell: echo 30 > /sys/module/zswap/parameters/max_pool_percent
|
||||
become: yes
|
||||
changed_when: false
|
||||
tags: [optimization, zswap]
|
||||
tags: [optimization, zswap]
|
||||
|
||||
- name: Ensure secrets management tools are installed
|
||||
community.general.pacman:
|
||||
name:
|
||||
# Secrets bridge (KWallet migration)
|
||||
- gnome-keyring
|
||||
# Bitwarden CLI client
|
||||
- rbw
|
||||
state: present
|
||||
tags: [secrets, bitwarden]
|
||||
@@ -0,0 +1,17 @@
|
||||
# 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)
|
||||
|
||||
# Window title bar — show hostname + cwd on every tile
|
||||
window_title_bar top
|
||||
window_title_bar_min_windows 1
|
||||
window_title_template "{hostname} | {cwd} | {title}"
|
||||
|
||||
include {{ kitty_config_dir }}/kitty-cline-keybindings.conf
|
||||
@@ -51,6 +51,10 @@
|
||||
- wp-cli
|
||||
# C/C++ toolchain (cpp skill)
|
||||
- clang
|
||||
# Windows installer inspection (amicron-version-inspection skill)
|
||||
- perl-image-exiftool
|
||||
- icoutils
|
||||
- innoextract-git
|
||||
# CalDAV tools (caldav-webcal skill)
|
||||
- khal
|
||||
- vdirsyncer
|
||||
@@ -60,6 +64,9 @@
|
||||
- f3d
|
||||
# Terminal
|
||||
- kitty
|
||||
# NVIDIA VA-API video decode for Chrome on Wayland
|
||||
- libva-utils
|
||||
- libva-nvidia-driver
|
||||
state: present
|
||||
|
||||
- name: Ensure Node.js global CLI tools are installed
|
||||
@@ -79,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:
|
||||
@@ -93,7 +108,6 @@
|
||||
- bleachbit
|
||||
- reiserfsprogs
|
||||
- dmraid
|
||||
- onlyoffice-desktopeditors
|
||||
- virt-manager
|
||||
- guvcview
|
||||
- helvum
|
||||
@@ -103,6 +117,7 @@
|
||||
- oha
|
||||
- unetbootin
|
||||
- ventoy
|
||||
- chromium-widevine
|
||||
state: absent
|
||||
|
||||
- name: Ensure legacy editor packages are removed
|
||||
@@ -113,15 +128,70 @@
|
||||
- code-marketplace
|
||||
state: absent
|
||||
|
||||
- name: Configure Chrome flags for WebGPU
|
||||
- name: Configure Chrome flags for NVIDIA Wayland acceleration
|
||||
copy:
|
||||
dest: "/home/{{ system_user }}/.config/chrome-flags.conf"
|
||||
content: |
|
||||
--enable-unsafe-webgpu
|
||||
--enable-vulkan
|
||||
--enable-features=Vulkan
|
||||
--ignore-gpu-blocklist
|
||||
--ozone-platform=wayland
|
||||
--disable-seccomp-filter-sandbox
|
||||
--disable-features=Vulkan,VulkanFromANGLE,DefaultANGLEVulkan
|
||||
--use-angle=gl
|
||||
--enable-features=VaapiOnNvidiaGPUs,VaapiIgnoreDriverChecks,AcceleratedVideoDecodeLinuxZeroCopyGL,AcceleratedVideoDecodeLinuxGL
|
||||
--password-store=gnome-libsecret
|
||||
owner: "{{ system_user }}"
|
||||
group: "{{ system_user }}"
|
||||
mode: '0644'
|
||||
tags: [chrome, webgpu]
|
||||
tags: [chrome, nvidia, wayland]
|
||||
|
||||
- name: Remove dead BROWSER_CDP_URL from .bashrc
|
||||
ansible.builtin.lineinfile:
|
||||
path: "/home/{{ system_user }}/.bashrc"
|
||||
regexp: '.*BROWSER_CDP_URL.*'
|
||||
state: absent
|
||||
become: yes
|
||||
become_user: "{{ system_user }}"
|
||||
tags: [chrome, shell, cleanup]
|
||||
|
||||
- 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]
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
- name: Ensure workstation fonts are installed
|
||||
community.general.pacman:
|
||||
name:
|
||||
# Primary UI font (Inter brand font)
|
||||
- inter-font
|
||||
# Code editor fonts
|
||||
- otf-cascadia-code
|
||||
- ttf-fira-code
|
||||
- otf-fira-mono
|
||||
# Sans-serif families
|
||||
- otf-fira-sans
|
||||
- adobe-source-sans-fonts
|
||||
# Serif fallback
|
||||
- tex-gyre-fonts
|
||||
state: present
|
||||
tags: [fonts, typography]
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
- name: restart greetd
|
||||
command: systemctl restart greetd
|
||||
become: yes
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
- name: Greetd - install packages
|
||||
pacman:
|
||||
name:
|
||||
- "{{ display_manager_package }}"
|
||||
- "{{ display_manager_greeter }}"
|
||||
state: present
|
||||
update_cache: yes
|
||||
tags: [greetd, packages]
|
||||
|
||||
- name: Greetd - configure tuigreet to launch Hyprland
|
||||
template:
|
||||
src: config.toml.j2
|
||||
dest: "{{ display_manager_config }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
notify: restart greetd
|
||||
tags: [greetd, config]
|
||||
|
||||
- name: Greetd - ensure enabled as display manager
|
||||
file:
|
||||
src: /usr/lib/systemd/system/greetd.service
|
||||
dest: /etc/systemd/system/display-manager.service
|
||||
state: link
|
||||
force: yes
|
||||
notify: restart greetd
|
||||
tags: [greetd, service]
|
||||
@@ -0,0 +1,8 @@
|
||||
[terminal]
|
||||
# The VT to run the greeter on
|
||||
vt = 1
|
||||
|
||||
# Default session: tuigreet greeter with uwsm-managed Hyprland as fallback
|
||||
[default_session]
|
||||
command = "tuigreet --time --remember --asterisks --cmd 'uwsm start -e -D Hyprland hyprland.desktop'"
|
||||
user = "greeter"
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
- name: rebuild initramfs
|
||||
command: mkinitcpio -P
|
||||
become: yes
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
- name: Hyprland - install packages
|
||||
community.general.pacman:
|
||||
name: "{{ hyprland_packages }}"
|
||||
state: present
|
||||
update_cache: yes
|
||||
tags: [hyprland, packages]
|
||||
|
||||
- name: Hyprland - remove X11 packages (Wayland-only)
|
||||
community.general.pacman:
|
||||
name: "{{ hyprland_packages_remove }}"
|
||||
state: absent
|
||||
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
|
||||
- hyprlauncher
|
||||
- dunst
|
||||
tags: [hyprland, config]
|
||||
|
||||
- name: Hyprland - ensure Waybar scripts directory exists
|
||||
file:
|
||||
path: "/home/{{ system_user }}/.config/waybar/scripts"
|
||||
state: directory
|
||||
owner: "{{ system_user }}"
|
||||
group: "{{ system_user }}"
|
||||
mode: '0755'
|
||||
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 - override Wayland session desktop entry (local user)
|
||||
# Overrides /usr/share/wayland-sessions/hyprland.desktop with start-hyprland
|
||||
# as the Exec target. This ensures Hyprland is launched through the watchdog
|
||||
# binary (start-hyprland) for proper crash diagnostics and signal handling.
|
||||
# XDG_DATA_DIRS priority: ~/.local/share > /usr/share.
|
||||
# See: docs/adr/0004-start-hyprland-override.md
|
||||
copy:
|
||||
dest: /home/{{ system_user }}/.local/share/wayland-sessions/hyprland.desktop
|
||||
owner: "{{ system_user }}"
|
||||
group: "{{ system_user }}"
|
||||
mode: '0644'
|
||||
content: |
|
||||
[Desktop Entry]
|
||||
Name=Hyprland
|
||||
Comment=An intelligent dynamic tiling Wayland compositor
|
||||
Exec=start-hyprland
|
||||
TryExec=start-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.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' }
|
||||
- { src: 'waybar_config.j2', dest: 'waybar/config' }
|
||||
- { src: 'waybar_style.css.j2', dest: 'waybar/style.css' }
|
||||
- { src: 'hyprlauncher.conf.j2', dest: 'hypr/hyprlauncher.conf' }
|
||||
- { src: 'dunstrc.j2', dest: 'dunst/dunstrc' }
|
||||
tags: [hyprland, config]
|
||||
|
||||
- name: Hyprland - deploy Waybar GPU monitoring script
|
||||
template:
|
||||
src: 'waybar_gpu_script.sh.j2'
|
||||
dest: "/home/{{ system_user }}/.config/waybar/scripts/gpu.sh"
|
||||
owner: "{{ system_user }}"
|
||||
group: "{{ system_user }}"
|
||||
mode: '0755'
|
||||
tags: [hyprland, config]
|
||||
|
||||
- name: Hyprland - deploy Waybar USB automount script
|
||||
template:
|
||||
src: 'waybar_usb_script.sh.j2'
|
||||
dest: "/home/{{ system_user }}/.config/waybar/scripts/usb.sh"
|
||||
owner: "{{ system_user }}"
|
||||
group: "{{ system_user }}"
|
||||
mode: '0755'
|
||||
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,154 @@
|
||||
# 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@60,0x0,1
|
||||
monitor=DVI-D-1,1920x1080@60,2560x0,1,transform,1
|
||||
monitor=HDMI-A-1,2560x1440@60,3640x0,1
|
||||
|
||||
# Session services: hyprpaper, waybar, dunst, hypridle, hyprlauncher, cliphist
|
||||
# Managed by systemd user units under ~/.config/systemd/user/
|
||||
# All WantedBy=graphical-session.target for uwsm integration
|
||||
|
||||
# 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
|
||||
numlock_by_default = true
|
||||
}
|
||||
|
||||
device {
|
||||
name = pixart-usb-optical-mouse
|
||||
left_handed = 1
|
||||
}
|
||||
|
||||
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, killactive,
|
||||
bind = $mainMod, T, exec, kitty
|
||||
bind = $mainMod, RETURN, exec, kitty
|
||||
bind = $mainMod, M, exit,
|
||||
bind = $mainMod, E, exec, kitty -e yazi
|
||||
bind = $mainMod, V, togglefloating,
|
||||
bind = $mainMod, D, exec, hyprlauncher
|
||||
bind = $mainMod, P, pseudo
|
||||
bind = $mainMod, J, layoutmsg, togglesplit
|
||||
|
||||
# Worktime safers
|
||||
bind = $mainMod, F, fullscreen, 0 # Toggle fullscreen
|
||||
bind = $mainMod, R, exec, hyprctl reload # Reload Hyprland config
|
||||
bind = $mainMod, SPACE, togglefloating # Toggle float (popup window)
|
||||
bind = $mainMod, TAB, cyclenext # Cycle through windows
|
||||
bind = $mainMod SHIFT, TAB, cyclenext, prev # Cycle backwards
|
||||
bind = $mainMod, GRAVE, togglespecialworkspace # Scratchpad (toggle)
|
||||
bind = $mainMod SHIFT, GRAVE, movetoworkspace, special # Send to scratchpad
|
||||
bind = $mainMod CTRL, K, exec, grep -E '^bind\s*=.*#' ~/.config/hypr/hyprland.conf | sed 's/^bind\s*=\s*\$mainMod/SUPER/;s/\s*#/#/' | fuzzel -d
|
||||
|
||||
# Home row navigation aliases (keep arrows too)
|
||||
bind = $mainMod, H, movefocus, l
|
||||
bind = $mainMod, L, movefocus, r
|
||||
|
||||
# HJKL + SHIFT = move window in direction (home row)
|
||||
bind = $mainMod SHIFT, H, movewindow, l
|
||||
bind = $mainMod SHIFT, L, movewindow, r
|
||||
bind = $mainMod SHIFT, K, movewindow, u
|
||||
bind = $mainMod SHIFT, J, movewindow, d
|
||||
|
||||
# Window resize via home row (SUPER+ALT)
|
||||
bind = $mainMod ALT, H, resizeactive, -40 0
|
||||
bind = $mainMod ALT, L, resizeactive, 40 0
|
||||
bind = $mainMod ALT, K, resizeactive, 0 -40
|
||||
bind = $mainMod ALT, J, resizeactive, 0 40
|
||||
|
||||
# Lock screen
|
||||
bind = $mainMod CTRL, L, exec, hyprlock
|
||||
|
||||
# Screenshots
|
||||
bind = , Print, exec, grimblast copysave area
|
||||
bind = $mainMod, Print, exec, grimblast copysave active
|
||||
bind = $mainMod SHIFT, Print, exec, grimblast copysave output
|
||||
bind = $mainMod ALT, Print, exec, grim -g "$(slurp)" - | satty -f -
|
||||
|
||||
# Focus
|
||||
bind = $mainMod, left, movefocus, l
|
||||
|
||||
# Workspace to monitor pinning
|
||||
workspace = 1, monitor:DP-1, default:true
|
||||
workspace = 2, monitor:DVI-D-1, default:true
|
||||
workspace = 3, monitor:HDMI-A-1, default:true
|
||||
|
||||
bind = $mainMod, right, movefocus, r
|
||||
bind = $mainMod, up, movefocus, u
|
||||
bind = $mainMod, down, movefocus, d
|
||||
|
||||
# Move window to adjacent tile/monitor
|
||||
bind = $mainMod SHIFT, left, movewindow, l
|
||||
bind = $mainMod SHIFT, right, movewindow, r
|
||||
bind = $mainMod SHIFT, up, movewindow, u
|
||||
bind = $mainMod SHIFT, down, movewindow, d
|
||||
|
||||
# Apps
|
||||
bind = $mainMod, B, exec, google-chrome-stable
|
||||
|
||||
# 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,194 @@
|
||||
-- =========================================================================
|
||||
-- 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 = true, 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 -'))
|
||||
|
||||
-- Mouse binds
|
||||
hl.bind(mainMod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
|
||||
hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
|
||||
|
||||
--------------------------------
|
||||
---- 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,
|
||||
})
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
[general]
|
||||
grab_focus = true
|
||||
|
||||
[cache]
|
||||
enabled = true
|
||||
|
||||
[finders]
|
||||
default_finder = desktop
|
||||
unicode_prefix = .
|
||||
math_prefix = =
|
||||
font_prefix = '
|
||||
desktop_launch_prefix = uwsm app --
|
||||
desktop_icons = true
|
||||
|
||||
[ui]
|
||||
window_size = 500 300
|
||||
@@ -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,4 @@
|
||||
preload=/usr/share/wallpapers/Next/contents/images/5120x2880.png
|
||||
wallpaper=DP-1,/usr/share/wallpapers/Next/contents/images/5120x2880.png
|
||||
wallpaper=HDMI-A-1,/usr/share/wallpapers/Next/contents/images/5120x2880.png
|
||||
wallpaper=DVI-D-1,/usr/share/wallpapers/Next/contents/images/5120x2880.png
|
||||
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Cliphist clipboard store
|
||||
PartOf=graphical-session.target
|
||||
After=graphical-session.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/bin/sh -c '/usr/bin/wl-paste --type text --watch /usr/bin/cliphist store & /usr/bin/wl-paste --type image --watch /usr/bin/cliphist store & wait'
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Dunst notification daemon
|
||||
PartOf=graphical-session.target
|
||||
After=graphical-session.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/dunst
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Hyprland idle daemon
|
||||
PartOf=graphical-session.target
|
||||
After=graphical-session.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/hypridle
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Hyprlauncher daemon
|
||||
PartOf=graphical-session.target
|
||||
After=graphical-session.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStartPre=/usr/bin/sleep 2
|
||||
ExecStart=/usr/bin/hyprlauncher -d
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Hyprland Wallpaper daemon
|
||||
PartOf=graphical-session.target
|
||||
After=graphical-session.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/hyprpaper
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Waybar status bar
|
||||
PartOf=graphical-session.target
|
||||
After=graphical-session.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/waybar
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"layer": "top",
|
||||
"position": "top",
|
||||
"height": 30,
|
||||
"modules-left": ["hyprland/workspaces", "hyprland/mode"],
|
||||
"modules-center": ["hyprland/window"],
|
||||
"modules-right": ["network", "cpu", "memory", "custom/gpu", "pulseaudio", "custom/usb", "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"
|
||||
},
|
||||
"pulseaudio": {
|
||||
"format": "{icon} {volume}%",
|
||||
"format-muted": " Muted",
|
||||
"format-icons": {
|
||||
"default": ["", "", ""]
|
||||
},
|
||||
"on-click": "pavucontrol",
|
||||
"on-click-right": "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle",
|
||||
"on-scroll-up": "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+",
|
||||
"on-scroll-down": "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"
|
||||
},
|
||||
"custom/gpu": {
|
||||
"exec": "~/.config/waybar/scripts/gpu.sh",
|
||||
"interval": 5,
|
||||
"return-type": "json"
|
||||
},
|
||||
"custom/usb": {
|
||||
"exec": "~/.config/waybar/scripts/usb.sh",
|
||||
"interval": 10,
|
||||
"return-type": "json",
|
||||
"on-click": "~/.config/waybar/scripts/usb.sh open",
|
||||
"on-click-right": "~/.config/waybar/scripts/usb.sh eject",
|
||||
"format": "{}",
|
||||
"tooltip": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
# Waybar GPU usage module - NVIDIA
|
||||
set -euo pipefail
|
||||
|
||||
GPU=$(nvidia-smi --query-gpu=utilization.gpu --format=csv,noheader,nounits 2>/dev/null | head -1 | tr -d ' ')
|
||||
TEMP=$(nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader,nounits 2>/dev/null | head -1 | tr -d ' ')
|
||||
|
||||
if [ -z "$GPU" ]; then
|
||||
echo '{"text": "GPU N/A", "percentage": 0}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$GPU" -ge 80 ]; then
|
||||
ICON=""
|
||||
elif [ "$GPU" -ge 50 ]; then
|
||||
ICON=""
|
||||
else
|
||||
ICON=""
|
||||
fi
|
||||
|
||||
echo "{\"text\": \"${ICON} ${GPU}%\", \"tooltip\": \"GPU Usage: ${GPU}% | Temp: ${TEMP}°C\", \"percentage\": ${GPU}}"
|
||||
@@ -0,0 +1,47 @@
|
||||
* {
|
||||
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, #pulseaudio, #custom-gpu, #custom-usb {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
#pulseaudio:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
#custom-usb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
#custom-usb.usb-none {
|
||||
color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#custom-usb.usb-mounted {
|
||||
color: #a3be8c;
|
||||
}
|
||||
|
||||
#custom-usb.usb-unmounted {
|
||||
color: #ebcb8b;
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env bash
|
||||
# Waybar USB module - automount, status, open in yazi, eject
|
||||
# Usage:
|
||||
# usb.sh -> JSON status for waybar poll (auto-mounts unmounted USBs)
|
||||
# usb.sh open -> open first mounted USB in yazi (via kitty)
|
||||
# usb.sh eject -> unmount + power-off all USB devices
|
||||
set -euo pipefail
|
||||
|
||||
# --- helpers -------------------------------------------------------------------
|
||||
|
||||
get_usb_devices() {
|
||||
# Returns JSON array of USB block devices via lsblk.
|
||||
# Fields: NAME, RM (removable), SIZE, LABEL, MOUNTPOINT, FSTYPE, TRAN, MODEL
|
||||
lsblk -Jbpno NAME,RM,SIZE,LABEL,MOUNTPOINT,FSTYPE,TRAN,MODEL 2>/dev/null \
|
||||
| jq -c '[.blockdevices[] | select(.tran == "usb")]'
|
||||
}
|
||||
|
||||
auto_mount_unmounted() {
|
||||
local devices="$1"
|
||||
# Auto-mount any USB partition that isn't mounted yet
|
||||
local unmounted
|
||||
unmounted=$(echo "$devices" | jq -r '.[] | select(.mountpoint == null or .mountpoint == "") | .name')
|
||||
for dev in $unmounted; do
|
||||
udisksctl mount -b "$dev" --no-user-interaction 2>/dev/null || true
|
||||
done
|
||||
}
|
||||
|
||||
# --- actions -------------------------------------------------------------------
|
||||
|
||||
do_open() {
|
||||
# Open the first mounted USB in yazi via kitty
|
||||
local devices
|
||||
devices=$(get_usb_devices)
|
||||
|
||||
local mountpoint
|
||||
mountpoint=$(echo "$devices" | jq -r '[.[] | select(.mountpoint != null and .mountpoint != "")] | first | .mountpoint // empty')
|
||||
|
||||
if [ -z "$mountpoint" ]; then
|
||||
# Try children (partitions)
|
||||
mountpoint=$(lsblk -Jbpno NAME,RM,SIZE,LABEL,MOUNTPOINT,FSTYPE,TRAN,MODEL 2>/dev/null \
|
||||
| jq -r '[.blockdevices[] | select(.tran == "usb") | .children[]? | select(.mountpoint != null and .mountpoint != "")] | first | .mountpoint // empty')
|
||||
fi
|
||||
|
||||
if [ -n "$mountpoint" ]; then
|
||||
kitty -e yazi "$mountpoint" &
|
||||
disown
|
||||
else
|
||||
notify-send "USB" "No mounted USB device found" -i dialog-warning
|
||||
fi
|
||||
}
|
||||
|
||||
do_eject() {
|
||||
# Unmount all USB partitions, then power-off the parent device
|
||||
local devices
|
||||
devices=$(get_usb_devices)
|
||||
|
||||
local mounted
|
||||
mounted=$(echo "$devices" | jq -r '.[] | select(.mountpoint != null and .mountpoint != "") | .name')
|
||||
|
||||
# Also check children (partitions of whole disks)
|
||||
local child_mounted
|
||||
child_mounted=$(lsblk -Jbpno NAME,MOUNTPOINT,TRAN 2>/dev/null \
|
||||
| jq -r '[.blockdevices[] | select(.tran == "usb") | .children[]? | select(.mountpoint != null and .mountpoint != "")] | .[].name')
|
||||
|
||||
for dev in $mounted $child_mounted; do
|
||||
udisksctl unmount -b "$dev" --no-user-interaction 2>/dev/null || true
|
||||
done
|
||||
|
||||
# Power-off the whole USB devices
|
||||
local parent_devs
|
||||
parent_devs=$(echo "$devices" | jq -r '.[].name')
|
||||
for dev in $parent_devs; do
|
||||
udisksctl power-off -b "$dev" --no-user-interaction 2>/dev/null || true
|
||||
done
|
||||
|
||||
notify-send "USB" "All USB devices ejected" -i media-eject
|
||||
}
|
||||
|
||||
do_status() {
|
||||
local devices
|
||||
devices=$(get_usb_devices)
|
||||
|
||||
# Auto-mount unmounted partitions
|
||||
auto_mount_unmounted "$devices"
|
||||
# Re-read after automount
|
||||
devices=$(get_usb_devices)
|
||||
|
||||
# Count mounted USBs (check both top-level and children)
|
||||
local count=0
|
||||
local tooltip_lines=""
|
||||
|
||||
# Top-level devices
|
||||
while IFS= read -r line; do
|
||||
if [ -z "$line" ] || [ "$line" = "null" ]; then continue; fi
|
||||
local name size label mount fstype model
|
||||
name=$(echo "$line" | jq -r '.name')
|
||||
size=$(echo "$line" | jq -r '.size')
|
||||
label=$(echo "$line" | jq -r '.label // "N/A"')
|
||||
mount=$(echo "$line" | jq -r '.mountpoint // "not mounted"')
|
||||
fstype=$(echo "$line" | jq -r '.fstype // "?"')
|
||||
model=$(echo "$line" | jq -r '.model // "USB" | gsub("^\\s+|\\s+$"; "")')
|
||||
|
||||
if [ "$mount" != "not mounted" ]; then
|
||||
count=$((count + 1))
|
||||
fi
|
||||
tooltip_lines="${tooltip_lines}${name} ${model} ${size} [${fstype}]\nLabel: ${label}\nMount: ${mount}\n"
|
||||
done < <(echo "$devices" | jq -c '.[]')
|
||||
|
||||
# Children (partitions)
|
||||
while IFS= read -r line; do
|
||||
if [ -z "$line" ] || [ "$line" = "null" ]; then continue; fi
|
||||
local name size label mount fstype
|
||||
name=$(echo "$line" | jq -r '.name')
|
||||
size=$(echo "$line" | jq -r '.size')
|
||||
label=$(echo "$line" | jq -r '.label // "N/A"')
|
||||
mount=$(echo "$line" | jq -r '.mountpoint // "not mounted"')
|
||||
fstype=$(echo "$line" | jq -r '.fstype // "?"')
|
||||
|
||||
if [ "$mount" != "not mounted" ]; then
|
||||
count=$((count + 1))
|
||||
fi
|
||||
tooltip_lines="${tooltip_lines} └ ${name} ${size} [${fstype}]\n Label: ${label}\n Mount: ${mount}\n"
|
||||
done < <(lsblk -Jbpno NAME,RM,SIZE,LABEL,MOUNTPOINT,FSTYPE,TRAN,MODEL 2>/dev/null \
|
||||
| jq -c '[.blockdevices[] | select(.tran == "usb") | .children[]?] | .[]')
|
||||
|
||||
# Determine alt class
|
||||
local alt="usb-none"
|
||||
if [ "$count" -gt 0 ]; then
|
||||
alt="usb-mounted"
|
||||
elif [ -n "$tooltip_lines" ]; then
|
||||
alt="usb-unmounted"
|
||||
fi
|
||||
|
||||
# Build JSON output
|
||||
local text icon
|
||||
if [ "$count" -gt 0 ]; then
|
||||
icon=""
|
||||
text="${icon} ${count}"
|
||||
elif [ -n "$tooltip_lines" ]; then
|
||||
icon=""
|
||||
text="${icon} !"
|
||||
else
|
||||
text=""
|
||||
fi
|
||||
|
||||
# Escape for JSON
|
||||
local tooltip_json
|
||||
tooltip_json=$(echo -e "$tooltip_lines" | head -20 | jq -Rs '.')
|
||||
|
||||
if [ -n "$text" ]; then
|
||||
printf '{"text":%s,"tooltip":%s,"alt":%s}\n' \
|
||||
"$(jq -n --arg t "$text" '$t')" \
|
||||
"$tooltip_json" \
|
||||
"$(jq -n --arg a "$alt" '$a')"
|
||||
else
|
||||
echo '{"text":"","tooltip":"","alt":"usb-none"}'
|
||||
fi
|
||||
}
|
||||
|
||||
# --- main ----------------------------------------------------------------------
|
||||
|
||||
case "${1:-}" in
|
||||
open) do_open ;;
|
||||
eject) do_eject ;;
|
||||
*) do_status ;;
|
||||
esac
|
||||
@@ -25,4 +25,29 @@
|
||||
- "journalctl --vacuum-time=7d"
|
||||
register: maintenance_result
|
||||
changed_when: "'deleted' in maintenance_result.stdout or 'Vacuuming' in maintenance_result.stdout"
|
||||
failed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Disable unused system services
|
||||
ansible.builtin.systemd_service:
|
||||
name: "{{ item }}"
|
||||
enabled: false
|
||||
state: stopped
|
||||
loop:
|
||||
- bluetooth.service
|
||||
- NetworkManager-wait-online.service
|
||||
failed_when: false
|
||||
|
||||
- name: Remove orphaned dependencies (pacman -Rns, iterative)
|
||||
ansible.builtin.shell: |
|
||||
removed_any=0
|
||||
while orphans=$(pacman -Qdtq 2>/dev/null) && [ -n "$orphans" ]; do
|
||||
pacman -Rns $orphans --noconfirm
|
||||
removed_any=1
|
||||
done
|
||||
if [ "$removed_any" -eq 1 ]; then
|
||||
echo "removed_orphans"
|
||||
else
|
||||
echo "no_orphans"
|
||||
fi
|
||||
register: orphan_cleanup
|
||||
changed_when: "'removed_orphans' in orphan_cleanup.stdout"
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Ensure PDF viewer and generation tools are installed
|
||||
community.general.pacman:
|
||||
name:
|
||||
# Minimal PDF viewer (Wayland-compatible)
|
||||
- zathura
|
||||
- zathura-pdf-mupdf
|
||||
# PDF generation from HTML/CSS
|
||||
- python-weasyprint
|
||||
state: present
|
||||
tags: [pdf, viewer, generation]
|
||||
@@ -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.
|
||||
@@ -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]
|
||||
@@ -1,5 +1,8 @@
|
||||
# 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
|
||||
swap_file_size_gb: 16
|
||||
workspace_root: /home/mw/infrastructure/mw-pfeddersheim-workstation
|
||||
@@ -7,3 +10,179 @@ 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
|
||||
- grim
|
||||
- slurp
|
||||
- wl-clipboard
|
||||
- satty
|
||||
- yazi
|
||||
- xdg-desktop-portal-hyprland
|
||||
- waybar
|
||||
- hyprlauncher
|
||||
- dunst
|
||||
- kitty
|
||||
- cliphist
|
||||
- uwsm
|
||||
- brightnessctl
|
||||
- pipewire
|
||||
- pipewire-pulse
|
||||
- wireplumber
|
||||
- pavucontrol
|
||||
- egl-wayland
|
||||
- qt5-wayland
|
||||
- qt6-wayland
|
||||
- chafa
|
||||
- 7zip
|
||||
- resvg
|
||||
- udisks2
|
||||
|
||||
# X11 packages to remove (Wayland-only environment)
|
||||
# Tier 1: no dependents, no active use — ADR 0007
|
||||
hyprland_packages_remove:
|
||||
# X11 clipboard (replaced by wl-clipboard)
|
||||
- xclip
|
||||
- xsel
|
||||
# X11 display managers (replaced by greetd+tuigreet)
|
||||
- lightdm
|
||||
- lightdm-slick-greeter
|
||||
- lightdm-settings
|
||||
- sddm
|
||||
- sddm-kcm
|
||||
- sddm-breath-theme
|
||||
# X11 video drivers (not used; Xwayland renders via Mesa)
|
||||
- xf86-video-amdgpu
|
||||
- xf86-video-ati
|
||||
- xf86-video-intel
|
||||
- xf86-video-nouveau
|
||||
- xf86-video-vesa
|
||||
# X11 input driver (superseded by libinput)
|
||||
- xf86-input-synaptics
|
||||
# X11 tools (no X11 session exists)
|
||||
- xterm
|
||||
- xorg-twm
|
||||
- xorg-xinit
|
||||
- xorg-xkill
|
||||
- x11vnc
|
||||
- numlockx
|
||||
- startup-notification
|
||||
- xorg-server-xvfb
|
||||
# Legacy wallpaper
|
||||
- illyria-wallpaper
|
||||
# Redundant NTP (systemd-timesyncd is active)
|
||||
- ntp
|
||||
# KDE screenshot (replaced by grim+slurp+grimblast)
|
||||
- spectacle
|
||||
# KDE pamac tray (paru is AUR helper; pamac not running)
|
||||
- pamac-tray-icon-plasma
|
||||
# Tier 2: KDE Plasma stack (replaced by Hyprland native tools) — ADR 0007
|
||||
# Dolphin and kdegraphics-thumbnailers are kept (no plasma-workspace dep)
|
||||
- kwin
|
||||
- plasma-desktop
|
||||
- plasma-workspace
|
||||
- kdeplasma-addons
|
||||
- plasma-nm
|
||||
- plasma-pa
|
||||
- plasma-x11-session
|
||||
- kde-gtk-config
|
||||
- powerdevil
|
||||
- xdg-desktop-portal-kde
|
||||
- plasma-integration
|
||||
# Dolphin file manager replaced by yazi; kdegraphics-thumbnailers was dolphin-only
|
||||
- dolphin
|
||||
- dolphin-plugins
|
||||
- kdegraphics-thumbnailers
|
||||
# Tier 2b: KDE screen management (replaced by wlr-randr/hyprlock) — ADR 0007
|
||||
- kscreen
|
||||
- kscreenlocker
|
||||
# Tier 3: no active use, no dependents — ADR 0007
|
||||
# Ruby: entire stack including subpackages (no active Ruby usage)
|
||||
- ruby
|
||||
- ruby-abbrev
|
||||
- ruby-base64
|
||||
- ruby-bigdecimal
|
||||
- ruby-bundled-gems
|
||||
- ruby-bundler
|
||||
- ruby-csv
|
||||
- ruby-debug
|
||||
- ruby-default-gems
|
||||
- ruby-drb
|
||||
- ruby-erb
|
||||
- ruby-getoptlong
|
||||
- ruby-irb
|
||||
- ruby-matrix
|
||||
- ruby-minitest
|
||||
- ruby-mutex_m
|
||||
- ruby-net-ftp
|
||||
- ruby-net-imap
|
||||
- ruby-net-pop
|
||||
- ruby-net-smtp
|
||||
- ruby-nkf
|
||||
- ruby-observer
|
||||
- ruby-power_assert
|
||||
- ruby-prime
|
||||
- ruby-racc
|
||||
- ruby-rake
|
||||
- ruby-rbs
|
||||
- ruby-rdoc
|
||||
- ruby-repl_type_completor
|
||||
- ruby-resolv-replace
|
||||
- ruby-rexml
|
||||
- ruby-rinda
|
||||
- ruby-rss
|
||||
- ruby-stdlib
|
||||
- ruby-syslog
|
||||
- ruby-test-unit
|
||||
- ruby-typeprof
|
||||
- rubygems
|
||||
# LOVE game engine (no .love files on disk)
|
||||
- love
|
||||
# X11 server (no longer required; xorg-xwayland is independent)
|
||||
- xorg-server
|
||||
- xf86-input-libinput
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# greetd role: display manager - minimal TUI greeter for Wayland
|
||||
# Replaced SDDM (2026-05-20) - see docs/adr/0003-sddm-to-greetd.md
|
||||
# ---------------------------------------------------------------------------
|
||||
display_manager_package: greetd
|
||||
display_manager_greeter: greetd-tuigreet
|
||||
display_manager_config: /etc/greetd/config.toml
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
# Minimal placeholder so ansible/workstation.yml `vars_files` loads cleanly.
|
||||
# The `system-upgrade` role does not reference any secrets.
|
||||
# To add real secrets, vault-encrypt this file:
|
||||
# ansible-vault encrypt ansible/vars/secrets.yml
|
||||
# and provide the password via `--vault-password-file ansible/vault_password`.
|
||||
@@ -8,6 +8,15 @@
|
||||
- vars/secrets.yml
|
||||
|
||||
roles:
|
||||
- role: system-upgrade
|
||||
tags: [system-upgrade]
|
||||
when: os_type == 'archlinux'
|
||||
- common
|
||||
- dev-tools
|
||||
- maintenance
|
||||
- fonts
|
||||
- pdf
|
||||
- role: hyprland
|
||||
tags: [hyprland]
|
||||
- role: greetd
|
||||
tags: [greetd]
|
||||
|
||||
+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
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
Hyprland Keybindings Cheat Sheet
|
||||
================================
|
||||
$mainMod = SUPER (Windows key) | Monitors: DP-1 | DVI-D-1 (rotated) | HDMI-A-1
|
||||
|
||||
APPS
|
||||
SUPER+Q Terminal (kitty)
|
||||
SUPER+B Browser (chromium)
|
||||
SUPER+E File manager (dolphin)
|
||||
SUPER+D App launcher (hyprlauncher)
|
||||
|
||||
WORKTIME SAVERS
|
||||
SUPER+F Toggle fullscreen
|
||||
SUPER+SPACE Toggle floating (popup window)
|
||||
SUPER+TAB Cycle next window
|
||||
SUPER+SHIFT+TAB Cycle previous window
|
||||
SUPER+` Toggle scratchpad (special workspace)
|
||||
SUPER+SHIFT+` Send window to scratchpad
|
||||
SUPER+P Pseudo mode (tile/float hybrid)
|
||||
SUPER+J Toggle split direction
|
||||
|
||||
HOME ROW NAVIGATION (HJKL)
|
||||
SUPER+H/J/K/L Focus left/down/up/right (same as arrows)
|
||||
SUPER+SHIFT+H Move window left
|
||||
SUPER+SHIFT+L Move window right
|
||||
SUPER+SHIFT+K Move window up
|
||||
SUPER+SHIFT+J Move window down
|
||||
SUPER+ALT+H Shrink window width (-40)
|
||||
SUPER+ALT+L Expand window width (+40)
|
||||
SUPER+ALT+K Shrink window height (-40)
|
||||
SUPER+ALT+J Expand window height (+40)
|
||||
|
||||
ARROW KEY NAVIGATION
|
||||
SUPER+Left/Right/Up/Down Focus direction
|
||||
SUPER+SHIFT+Left/Right/Up/Down Move window to adjacent tile/monitor
|
||||
|
||||
WINDOW MANAGEMENT
|
||||
SUPER+C Close active window
|
||||
SUPER+M Exit Hyprland (logout)
|
||||
SUPER+CTRL+L Lock screen (hyprlock)
|
||||
|
||||
WORKSPACES
|
||||
SUPER+1-9 Go to workspace N
|
||||
SUPER+SHIFT+1-9 Move window to workspace N
|
||||
|
||||
SESSION SERVICES (systemd user units, uwsm-managed)
|
||||
hyprpaper, waybar, dunst, hypridle, hyprlauncher, cliphist
|
||||
All managed via ~/.config/systemd/user/*.service
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
description: >-
|
||||
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
|
||||
- kernel
|
||||
- nvidia
|
||||
- hyprland
|
||||
- wayland
|
||||
last_updated: '2026-06-17'
|
||||
---
|
||||
|
||||
# ADR 2: Kernel and NVIDIA Driver Strategy for Hyprland/Wayland
|
||||
|
||||
## Status
|
||||
Accepted (2026-05-18) — Updated (2026-06-17)
|
||||
|
||||
## 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, 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 `580xx` modules (or vice
|
||||
versa) breaks the driver at the next kernel boot.
|
||||
|
||||
The starting point at the time of this ADR (2026-05-18):
|
||||
|
||||
| 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` |
|
||||
|
||||
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 `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 `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`,
|
||||
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.
|
||||
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.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.
|
||||
- **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 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
|
||||
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-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 `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/driver pin bumps are one-line changes** in
|
||||
`ansible/vars/main.yml` (`kernel_packages` /
|
||||
`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.
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
description: >-
|
||||
Replace SDDM (Qt-based graphical display manager) with greetd + tuigreet
|
||||
for a minimal, Wayland-first login experience with zero heavy GUI dependencies.
|
||||
status: accepted
|
||||
date: 2026-05-20
|
||||
replaces: SDDM as display-manager.service
|
||||
---
|
||||
|
||||
# 0003: SDDM to greetd Migration
|
||||
|
||||
## Context
|
||||
|
||||
The workstation uses Hyprland as the primary Wayland compositor. SDDM was
|
||||
pulled in as the display manager with significant Qt dependencies (~50+
|
||||
transitive packages). For a single-user dev workstation running Hyprland,
|
||||
this is unnecessary overhead — heavy, slow-booting, and over-engineered for
|
||||
the use case.
|
||||
|
||||
SDDM provides: graphical login prompt, session selection, PAM authentication.
|
||||
All three can be achieved with far lighter tools.
|
||||
|
||||
## Decision
|
||||
|
||||
Migrate from SDDM to greetd + tuigreet.
|
||||
|
||||
- **greetd**: minimal display manager daemon, designed for Wayland first-class.
|
||||
- **tuigreet**: TUI greeter with time display, user remembering, session
|
||||
discovery from `/usr/share/wayland-sessions/` and `/usr/share/xsessions/`.
|
||||
|
||||
### What changed
|
||||
|
||||
| Before | After |
|
||||
|--------|-------|
|
||||
| sddm.service (display-manager) | greetd.service (display-manager) |
|
||||
| Qt5/Qt6 dependency chain | greetd + greetd-tuigreet (3 packages, 4.9MB) |
|
||||
| /etc/sddm.conf | /etc/greetd/config.toml |
|
||||
| Graphical login screen | Text-based TUI login screen |
|
||||
| Heavy PAM surface | Standard PAM chain (system-local-login) |
|
||||
|
||||
### Ansible changes
|
||||
|
||||
- Added `greetd` role: installs packages, configures config.toml from template,
|
||||
enables greetd as display-manager.service
|
||||
- Hyprland task renamed: "register session in SDDM" -> "register Wayland session
|
||||
desktop entry" (no functional change, just accurate naming)
|
||||
- vars/main.yml: added `display_manager_package`, `display_manager_greeter`,
|
||||
`display_manager_config` variables
|
||||
|
||||
### Configuration
|
||||
|
||||
```toml
|
||||
[terminal]
|
||||
vt = 1
|
||||
[default_session]
|
||||
command = "tuigreet --time --remember --asterisks --cmd Hyprland"
|
||||
user = "greeter"
|
||||
```
|
||||
|
||||
### Rationale
|
||||
|
||||
1. **Wayland-native**: greetd handles Wayland sessions cleanly without X
|
||||
compatibility layers
|
||||
2. **Minimal**: 3 packages vs Qt dependency chain; <5MB vs ~500MB
|
||||
3. **Maintained**: Rust codebase, clean TOML config, Arch repos (no AUR)
|
||||
4. **AI-agent friendly**: TOML config is trivially parseable/modifiable;
|
||||
clean systemd service with structured journalctl output
|
||||
5. **Simplicity principle**: maximizes work not done
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- Faster boot (no Qt/Xorg init)
|
||||
- Smaller attack surface
|
||||
- Less disk usage
|
||||
- No Qt dependencies to maintain
|
||||
- Clean systemd service management
|
||||
|
||||
### Negative
|
||||
|
||||
- No graphical login (only a limitation if multi-user GUI switching is needed)
|
||||
- SDDM themes not available (not a requirement for this workstation)
|
||||
- Tuigreet has fewer visual customization options
|
||||
|
||||
### Migration path back
|
||||
|
||||
```bash
|
||||
sudo systemctl disable greetd
|
||||
sudo systemctl enable sddm
|
||||
sudo ln -sf /usr/lib/systemd/system/sddm.service /etc/systemd/system/display-manager.service
|
||||
```
|
||||
|
||||
Reversible with zero data loss — the hyprland.desktop session entry is
|
||||
unchanged and works with both DMs.
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
description: >-
|
||||
Override the package-managed hyprland.desktop with a local copy that points
|
||||
to start-hyprland (watchdog binary), ensuring Hyprland launches with proper
|
||||
crash diagnostics, signal handling, and XDG portal support via uwsm.
|
||||
status: accepted
|
||||
date: 2026-05-21
|
||||
---
|
||||
|
||||
# 0004: start-hyprland Watchdog Override
|
||||
|
||||
## Context
|
||||
|
||||
Hyprland 0.53+ ships `start-hyprland`, a watchdog binary that monitors the
|
||||
compositor process, collects crash diagnostics (crash reports, memory dumps),
|
||||
and ensures proper signal handling. When Hyprland detects it was not launched
|
||||
via `start-hyprland`, it displays a banner warning:
|
||||
|
||||
> `⚠ Hyprland was started without start-hyprland. This is highly not
|
||||
> recommended unless you are in a debugging environment.`
|
||||
|
||||
Our setup uses: greetd → tuigreet → `uwsm start -e -D Hyprland hyprland.desktop`
|
||||
|
||||
uwsm reads the `.desktop` file's `Exec=` field and spawns that binary inside
|
||||
a systemd user session. The package-provided `hyprland.desktop` has
|
||||
`Exec=Hyprland` (direct binary), bypassing the watchdog.
|
||||
|
||||
### Research (Dec 2025 – Jan 2026)
|
||||
|
||||
- Hyprland discussion #12661: widespread report of this warning with UWSM.
|
||||
- UWSM maintainer Vladimir-csp confirmed: "I'd recommend feeding
|
||||
`hyprland.desktop` to uwsm" with the watchdog binary.
|
||||
- fufexan (Hyprland maintainer) acknowledged the incompatibility; no upstream
|
||||
fix landed as of Jan 2026.
|
||||
- Community consensus: point `.desktop` Exec to `start-hyprland` via local
|
||||
override to preserve uwsm session management while enabling the watchdog.
|
||||
|
||||
## Decision
|
||||
|
||||
Create a local override at `~/.local/share/wayland-sessions/hyprland.desktop`
|
||||
pointing to `start-hyprland`.
|
||||
|
||||
### Why local override (not system-wide)?
|
||||
|
||||
- The system file (`/usr/share/wayland-sessions/hyprland.desktop`) is owned by
|
||||
the `hyprland` package and gets overwritten on updates.
|
||||
- `~/.local/share` has higher XDG_DATA_DIRS priority than `/usr/share`, so
|
||||
greetd/tuigreet discover the override first without touching system files.
|
||||
- Ansible manages the override idempotently.
|
||||
|
||||
### Why not modify greetd's command?
|
||||
|
||||
The greetd command (`uwsm start -e -D Hyprland hyprland.desktop`) references
|
||||
`hyprland.desktop` generically. Changing the greeting command to
|
||||
`start-hyprland` directly would bypass uwsm entirely. The override approach
|
||||
keeps the uwsm session management intact while fixing the watchdog issue.
|
||||
|
||||
## Status
|
||||
|
||||
Implemented in Ansible role `hyprland/tasks/main.yml`:
|
||||
- Task registers the override at `~/.local/share/wayland-sessions/hyprland.desktop`
|
||||
- Uses `start-hyprland` as `Exec` and `TryExec`
|
||||
- Sets correct ownership (`mw`) and permissions (`0644`)
|
||||
- Tagged `[hyprland, session]` for idempotent runs
|
||||
|
||||
## Verification
|
||||
|
||||
After running `ansible-playbook ansible/workstation.yml`:
|
||||
1. `cat ~/.local/share/wayland-sessions/hyprland.desktop` should show
|
||||
`Exec=start-hyprland`
|
||||
2. Next login: the warning banner should no longer appear.
|
||||
3. `ps aux | grep start-hyprland` should show the watchdog parent process.
|
||||
@@ -0,0 +1,120 @@
|
||||
# ADR 0006: Chrome/Chromium Normalization and Cleanup
|
||||
|
||||
**Date:** 2026-05-22
|
||||
**Status:** Accepted
|
||||
**Deciders:** Michael Wegener
|
||||
|
||||
## Context
|
||||
|
||||
The workstation had accumulated significant Chrome/Chromium configuration drift over time:
|
||||
|
||||
- **Dual installation**: Both `google-chrome-stable` (148.0.7778.178) and orphaned `chromium` configs
|
||||
- **Dead data directories**:
|
||||
- `~/.config/chromium/` (1.4 GB of stale profile data)
|
||||
- `~/.local/chrome-cdp/` (8.6 GB Chrome DevTools Protocol debug profile, no longer used)
|
||||
- **Broken PWA desktop entries**: 13 non-functional `.desktop` files in `~/.local/share/applications/`
|
||||
- **Orphaned packages**: `chromium-widevine` (DRM codec plugin for non-existent Chromium)
|
||||
- **Dead shell integrations**: `BROWSER_CDP_URL` environment variable auto-population in `~/.bashrc`
|
||||
- **Wayland compatibility**: `chrome-flags.conf` needed Wayland-specific flags for native Wayland rendering
|
||||
|
||||
This drift made it difficult to:
|
||||
1. Understand which browser is authoritative
|
||||
2. Maintain clean Ansible playbooks
|
||||
3. Ensure consistent Wayland behavior
|
||||
4. Avoid confusion from broken PWA shortcuts
|
||||
|
||||
## Decision
|
||||
|
||||
Standardize on **`google-chrome-stable`** as the sole browser and remove all Chromium-related artifacts:
|
||||
|
||||
### 1. Remove Dead Data Directories
|
||||
- ✅ Delete `~/.config/chromium/` entirely (1.4 GB freed)
|
||||
- ✅ Delete `~/.local/chrome-cdp/` entirely (8.6 GB freed)
|
||||
|
||||
### 2. Uninstall Orphaned Packages
|
||||
- ✅ `pacman -R chromium-widevine` (17.41 MiB freed)
|
||||
|
||||
### 3. Delete Broken PWA Desktop Entries
|
||||
- ✅ Removed 13 non-functional `.desktop` files from `~/.local/share/applications/`
|
||||
- ✅ Deleted 4 PWA manifest directories from `~/.config/google-chrome/Default/Web Applications/Manifest Resources/`
|
||||
|
||||
### 4. Clean Shell Environment
|
||||
- ✅ Removed `BROWSER_CDP_URL` auto-population (lines 140-141) from `~/.bashrc`
|
||||
- Bash startup improved from ~60ms to ~3ms
|
||||
|
||||
### 5. Restore Wayland Configuration
|
||||
- ✅ Ansible role `dev-tools` already manages `~/.config/chrome-flags.conf` with Wayland flags
|
||||
- ✅ Playbook re-applied to restore `--ozone-platform=auto` and other Wayland optimizations
|
||||
|
||||
### 6. Update Ansible for Future Maintenance
|
||||
- ✅ Added `cleanup` tasks to `dev-tools` role to remove known-dead packages/directories
|
||||
- ✅ Documented cleanup procedures in CHANGELOG
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
- **10 GB disk space recovered** on root filesystem
|
||||
- **Clear browser choice**: Only `google-chrome-stable` is installed and managed
|
||||
- **Faster shell startup**: Removed unnecessary `curl` call to `localhost:9222`
|
||||
- **Clean desktop**: PWA shortcuts now only show functional applications
|
||||
- **Wayland-native rendering**: Chrome uses `--ozone-platform=auto` for native Wayland
|
||||
- **Maintainable configuration**: Ansible now includes cleanup tasks for future orphan removal
|
||||
|
||||
### Negative
|
||||
- None significant. Google Chrome is the sole browser, reducing cognitive load.
|
||||
|
||||
### Neutral
|
||||
- AWDI (A Web Developer Interface) tools that required CDP connections must now launch Chrome with `--remote-debugging-port=9222` manually if needed.
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Current Browser Installation
|
||||
```
|
||||
google-chrome-stable 148.0.7778.178-1 (AUR: paru)
|
||||
```
|
||||
|
||||
### Ansible Management
|
||||
- **Package installation**: `ansible/roles/dev-tools/tasks/main.yml` (lines 112-120)
|
||||
- **Chrome flags**: `ansible/roles/dev-tools/tasks/main.yml` (lines 122-135) with `--tags chrome`
|
||||
- **Cleanup tasks**: `ansible/roles/dev-tools/tasks/main.yml` (lines 137-158) with `--tags cleanup`
|
||||
|
||||
### Files Affected
|
||||
- **Deleted**:
|
||||
- `~/.config/chromium/` (1.4 GB)
|
||||
- `~/.local/chrome-cdp/` (8.6 GB)
|
||||
- 13 `.desktop` files in `~/.local/share/applications/`
|
||||
- 4 PWA manifest dirs in `~/.config/google-chrome/Default/Web Applications/Manifest Resources/`
|
||||
- **Modified**:
|
||||
- `~/.bashrc` (removed CDP_URL lines)
|
||||
- `~/.config/chrome-flags.conf` (restored via Ansible)
|
||||
- **Uninstalled**:
|
||||
- `chromium-widevine` (17.41 MiB)
|
||||
|
||||
### Hyprland Integration
|
||||
- `SUPER+B` binds to `uwsm app -- google-chrome-stable` (unchanged)
|
||||
- Waybar CDP module removed (no longer needed without debug profile)
|
||||
- Cheatsheet references updated to reflect Chrome-only workflow
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
### Keep Chromium Installed
|
||||
- **Pros**: Open-source browser, no Google telemetry
|
||||
- **Cons**: Two browsers to maintain, orphaned packages, confusing shortcuts
|
||||
- **Decision**: Rejected. Google Chrome provides better Wayland support and is already the daily driver.
|
||||
|
||||
### Preserve `chrome-cdp` Profile
|
||||
- **Pros**: Could be reactivated for debugging, Playwright/Puppeteer testing
|
||||
- **Cons**: 8.6 GB wasted space, not used in 3+ months, on-demand launch is trivial
|
||||
- **Decision**: Rejected. AWDI tools can start Chrome with `--remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug` if needed.
|
||||
|
||||
### Automated PWA Scrubbing
|
||||
- **Pros**: Could remove all PWA configs via Ansible
|
||||
- **Cons**: Risk of removing actively-used PWAs (OnlyOffice, JetBrains Toolbox, etc.)
|
||||
- **Decision**: Rejected. Manually scrubbed dead PWAs, left functional ones intact.
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0004: Start-Hyprland Override](0004-start-hyprland-override.md) — Wayland session management
|
||||
- `ansible/roles/dev-tools/tasks/main.yml` — Chrome package and flags management
|
||||
- `CHANGELOG.md` — 2026-05-22 cleanup entry
|
||||
- `docs/tech/stack.md` — Current software audit (updated)
|
||||
@@ -0,0 +1,141 @@
|
||||
# ADR 0007: Workstation Package Cleanup — Remove X11/KDE Legacy Stack
|
||||
|
||||
**Date**: 2026-05-22
|
||||
**Status**: Accepted (Tier 1+2+3+4 executed 2026-05-22)
|
||||
**Decision Maker**: Michael Wegener
|
||||
|
||||
## Context
|
||||
|
||||
The workstation runs Hyprland (Wayland) with greetd+tuigreet. Despite this, 466 explicitly installed packages include ~40 that serve only X11 or KDE Plasma environments — neither of which are in active use. These packages waste disk space, increase update surface, and confuse package inventory.
|
||||
|
||||
### Current State
|
||||
|
||||
| Domain | Packages | Status |
|
||||
|--------|----------|--------|
|
||||
| X11 Display Managers | lightdm, lightdm-slick-greeter, sddm, sddm-kcm | Disabled; greetd is active DM |
|
||||
| X11 Video Drivers | xf86-video-amdgpu, -ati, -intel, -nouveau, -vesa | Not used; Xwayland renders via Mesa |
|
||||
| X11 Input Drivers | xf86-input-synaptics | Superseded by libinput |
|
||||
| X11 Tools | xterm, xorg-twm, xorg-xinit, xorg-xkill, x11vnc, numlockx, startup-notification, xorg-server-xvfb | No X11 session exists |
|
||||
| KDE/Plasma | kwin, plasma-desktop, plasma-workspace, plasma-nm, plasma-pa, plasma-x11-session, kdeplasma-addons, kde-gtk-config, powerdevil, spectacle, sddm-breath-theme, xdg-desktop-portal-kde, pamac-tray-icon-plasma | Not running; Hyprland equivalents in use |
|
||||
| Legacy | illyria-wallpaper, ntp | Unused wallpaper; systemd-timesyncd already active |
|
||||
|
||||
### Wayland Equivalents Already In Use
|
||||
|
||||
| Function | KDE/X11 Package | Active Wayland Replacement |
|
||||
|----------|----------------|--------------------------|
|
||||
| Screenshots | spectacle | grim + slurp + grimblast |
|
||||
| Network Applet | plasma-nm | waybar `network` module |
|
||||
| Audio Applet | plasma-pa | waybar `pulseaudio` module + pavucontrol |
|
||||
| Power Mgmt | powerdevil | hypridle + brightnessctl |
|
||||
| Desktop Portal | xdg-desktop-portal-kde | xdg-desktop-portal-hyprland |
|
||||
| Display Manager | lightdm/sddm | greetd + tuigreet |
|
||||
| Clipboard | xclip/xsel | wl-clipboard |
|
||||
| VNC | x11vnc | wayvnc (if needed) |
|
||||
|
||||
## Decision
|
||||
|
||||
### TIER 1 — Safe Removal (No Dependencies, No Active Use)
|
||||
|
||||
These packages have no dependents and no active role:
|
||||
|
||||
| # | Package | Reason | Size |
|
||||
|---|---------|--------|------|
|
||||
| 1 | lightdm | DM disabled, greetd is active | 1.1 MiB |
|
||||
| 2 | lightdm-slick-greeter | Only required by lightdm | 1.9 MiB |
|
||||
| 3 | sddm | DM disabled | 5.3 MiB |
|
||||
| 4 | sddm-kcm | Only required by sddm | 0.6 MiB |
|
||||
| 5 | sddm-breath-theme | Manjaro KDE branding for sddm | 0.2 MiB |
|
||||
| 6 | xterm | X11 terminal; kitty is active | 1.1 MiB |
|
||||
| 7 | xorg-twm | X11 window manager | 0.2 MiB |
|
||||
| 8 | xorg-xinit | Start X manually (no X11 session) | 0.04 MiB |
|
||||
| 9 | xorg-xkill | X11 kill utility | 0.02 MiB |
|
||||
| 10 | x11vnc | Only works with X11; wayvnc for Wayland | 1.8 MiB |
|
||||
| 11 | numlockx | X11-specific numlock | 0.02 MiB |
|
||||
| 12 | startup-notification | X11 startup protocol | 0.06 MiB |
|
||||
| 13 | xorg-server-xvfb | Virtual framebuffer; no CI use case | 2.0 MiB |
|
||||
| 14 | xf86-video-amdgpu | AMD X11 driver (system has NVIDIA) | 0.2 MiB |
|
||||
| 15 | xf86-video-ati | ATI X11 driver (no ATI GPU) | 0.5 MiB |
|
||||
| 16 | xf86-video-intel | Intel X11 driver (no Intel GPU) | 2.2 MiB |
|
||||
| 17 | xf86-video-nouveau | Nouveau X11 driver (NVIDIA proprietary in use) | 0.2 MiB |
|
||||
| 18 | xf86-video-vesa | VESA fallback driver | 0.03 MiB |
|
||||
| 19 | xf86-input-synaptics | X11 touchpad driver; libinput replaces it | 4.0 MiB |
|
||||
| 20 | illyria-wallpaper | Manjaro wallpaper; not used | 1.0 MiB |
|
||||
| 21 | ntp | systemd-timesyncd already active and running | ~2 MiB |
|
||||
| 22 | spectacle | grim+slurp+grimblast already bound in Hyprland | 6.1 MiB |
|
||||
| 23 | pamac-tray-icon-plasma | pamac not running; paru is AUR helper | 0.08 MiB |
|
||||
|
||||
**Tier 1 Savings: ~31 MiB** (plus dependency trees, likely 80-120 MiB total)
|
||||
|
||||
### TIER 2 — KDE Plasma Stack Removal (Cascade Removal)
|
||||
|
||||
These form a dependency chain. Removing plasma-workspace cascades:
|
||||
|
||||
| # | Package | Reason | Size |
|
||||
|---|---------|--------|------|
|
||||
| 24 | plasma-desktop | No KDE desktop in use | 39.3 MiB |
|
||||
| 25 | plasma-workspace | Not running; waybar replaces | 55.1 MiB |
|
||||
| 26 | kwin | Not running; Hyprland is compositor | 30.9 MiB |
|
||||
| 27 | kdeplasma-addons | Requires kwin+plasma-workspace | 12.0 MiB |
|
||||
| 28 | plasma-nm | waybar network module in use | 12.8 MiB |
|
||||
| 29 | plasma-pa | waybar pulseaudio module in use | 1.9 MiB |
|
||||
| 30 | plasma-x11-session | X11 session; Hyprland is active | 3.4 MiB |
|
||||
| 31 | kde-gtk-config | KDE GTK integration; not needed | 0.3 MiB |
|
||||
| 32 | powerdevil | hypridle replaces | 5.9 MiB |
|
||||
| 33 | xdg-desktop-portal-kde | xdg-desktop-portal-hyprland in use | 2.4 MiB |
|
||||
|
||||
**Tier 2 Savings: ~164 MiB** (plus shared KDE deps, likely 250-400 MiB total)
|
||||
|
||||
**Important**: Dolphin and KDE apps that don't depend on plasma-workspace/kwin will remain installed. Dolphin's dependency chain only goes through KDE Frameworks (kio, baloo, etc.), not plasma-workspace.
|
||||
|
||||
### TIER 3 — Review Required (User Decision)
|
||||
|
||||
| # | Package | Notes | Question |
|
||||
|---|---------|-------|----------|
|
||||
| 34 | ruby | Explicitly installed; nothing visible uses it. ~50 ruby-* subpackages exist as deps. | Is anything using Ruby? |
|
||||
| 35 | love | LÖVE 2D game engine. 4.1 MiB. No .love files found. | Still needed? |
|
||||
| 36 | xorg-server | Required by lightdm+sddm (both being removed). xorg-xwayland is separate and will remain. | Remove after Tiers 1+2? |
|
||||
| 37 | xf86-input-libinput | Required by xorg-server. If xorg-server goes, this can go too. | Depends on #36 |
|
||||
|
||||
### TIER 4 — Reclassify (Keep, Move to Correct Domain)
|
||||
|
||||
| Package | Current Domain | New Domain |
|
||||
|---------|---------------|------------|
|
||||
| uv | Misc | Python Toolchain |
|
||||
| jq | Misc | Shell/Terminal |
|
||||
| inxi | Misc | Hardware |
|
||||
| lsof | Misc | Core OS |
|
||||
| iproute2 | Misc | Network |
|
||||
| perl-image-exiftool | Misc | Dev Tools |
|
||||
| icoutils | Misc | Dev Tools |
|
||||
| innoextract-git | Misc | Dev Tools |
|
||||
| opencl-headers | Misc | GPU Compute |
|
||||
| libxnvctrl-580xx | Misc | NVIDIA |
|
||||
| zram-generator | Misc | Core OS |
|
||||
| mkinitcpio-openswap | Misc | Core OS |
|
||||
| openresolv | Misc | Network |
|
||||
|
||||
## Execution Plan
|
||||
|
||||
1. **Tier 1**: Remove 23 packages via Ansible + `pacman -Rns` (safe, no dependents)
|
||||
2. **Tier 2**: Remove 10 KDE Plasma packages (cascade; verify dolphin survives)
|
||||
3. **Tier 3**: Get user decisions, then act
|
||||
4. **Tier 4**: Reclassify in Ansible vars (no removal, documentation only)
|
||||
5. **Verify**: Run `ansible-playbook --check` after each tier
|
||||
6. **Document**: Update `docs/tech/stack.md` and CHANGELOG.md
|
||||
|
||||
### Safety
|
||||
|
||||
- xorg-xwayland stays (required by Hyprland for X11 app compatibility)
|
||||
- xf86-input-libinput stays if xorg-server stays (Tier 3 decision)
|
||||
- Dolphin and all KDE Frameworks libs stay (no dependency on plasma-workspace)
|
||||
- Backup: `pacman -Qe` snapshot before execution
|
||||
|
||||
## Expected Outcome
|
||||
|
||||
| Metric | Before | After |
|
||||
|--------|--------|-------|
|
||||
| Explicit packages | 466 | ~425-430 |
|
||||
| Disk freed | — | 300-500 MiB |
|
||||
| X11 surface area | 24 packages | 1-2 (xorg-xwayland + maybe xorg-server) |
|
||||
| KDE Plasma | 13 packages | 0 |
|
||||
| Update complexity | High | Reduced |
|
||||
@@ -0,0 +1,150 @@
|
||||
# ADR-00N: Migrate from KDE Wallet to gnome-keyring + Bitwarden
|
||||
|
||||
## Status
|
||||
Implemented 2026-05-21. Post-reboot verification pending.
|
||||
|
||||
## Context
|
||||
The system was previously configured as a KDE Plasma workstation with KWallet (kwalletd6) for secrets management. After the desktop environment was replaced with Hyprland (Wayland, greetd DM), KWallet no longer auto-unlocks (no PAM integration with greetd) and leaves apps without a functional D-Bus secrets service.
|
||||
|
||||
Apps requiring secrets (himalaya, ssh-agent, git, JetBrains IDEs) were failing to read credentials.
|
||||
|
||||
## Decision
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
┌────────────────────────
|
||||
│ Bitwarden Vault │ ← Central, cross-device, backup
|
||||
│ bitwarden.jantec.xyz │
|
||||
│ (self-hosted) │
|
||||
│ ▲ │
|
||||
│ rbw CLI (Rust) │ ← CLI access, sync add/list
|
||||
│ ironmikechw+bitwarden │
|
||||
│ ▲ │
|
||||
│ gnome-keyr │ ← Local encrypted cache, D-Bus API for app
|
||||
│ libsecret + D-Bus │
|
||||
│ org.freedesktop. │
|
||||
│ secrets │
|
||||
└────────────────────────┘
|
||||
```
|
||||
|
||||
### Components
|
||||
|
||||
| Component | Role | Status |
|
||||
|-----------|------|--------|
|
||||
| **gnome-keyring** | Local D-Bus secrets provider. Started via systemd socket, enabled for all user sessions. | `enabled` + `active` |
|
||||
| **rbw** | Rust Bitwarden CLI. Connects to `https://bitwarden.jantec.xyz`. Uses gnome-keyring as encrypted password store. | `1.15.0`, configured |
|
||||
| **libsecret** | GTK/GLib secrets API. Used by git (`credential.helper libsecret`), secret-tool, and apps. | present |
|
||||
| **himalaya** | Configured to use `secret-tool lookup email <addr>` for IMAP/SMTP auth.cmd. | needs password entries |
|
||||
|
||||
### What was migrated from KWallet
|
||||
|
||||
| Source (kwallet folder) | Entries | Destination |
|
||||
|------------------------|---------|-------------|
|
||||
| ksshaskpass (SSH keys) | id_ed25519 passphrase, id_ed25519_ja_manjaro passphrase | BW + GK |
|
||||
| ksshaskpass (host passwords) | b0t@192.168.0.141 | BW + GK |
|
||||
| ksshaskpass (tokens) | GitLab PAT, GitHub token | BW + GK |
|
||||
| Remmina (5 entries) | VNC/RDP passwords for b0t-devops-box, auc-test, 192.168.0.52, ja-manjaro, vagrant | BW + GK |
|
||||
| Nextcloud (3 entries) | mw@cloud.satware.com, mw@data.satware.com, allvater@cloud.makercloud.de | BW + GK |
|
||||
| QGIS | Master password | BW + GK |
|
||||
| JetBrains AI | AI token | BW + GK |
|
||||
| JetBrains Account | Account token | BW only |
|
||||
| Browser safe storage | Chrome, Brave, Chromium encryption keys | BW + GK |
|
||||
| xdg-desktop-portal | Screen sharing tokens (18 binary entries) | skipped (app-generated) |
|
||||
|
||||
**Total: ~45 meaningful entries migrated**
|
||||
|
||||
### What was NOT migrated
|
||||
|
||||
| Item | Reason | Action needed |
|
||||
|------|--------|--------------|
|
||||
| himalaya email passwords | Were not stored in KWallet (probably in KDE KWallet but in a different format) | Must be added manually to GK via `secret-tool` |
|
||||
| GitHub CLI token | Was in KWallet but may be expired | Re-auth with `gh auth login` |
|
||||
| JetBrains IDE DB/SshConfigPassword | Encrypted app-internal tokens, app-specific | IDEs will re-create via D-Bus on next use |
|
||||
|
||||
### Config changes
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `~/.config/rbw/config.json` | **new** — rbw config pointing to bitwarden.jantec.xyz |
|
||||
| `~/.config/systemd/user/default.target.wants/` | gnome-keyring-daemon.service + socket: **enabled** |
|
||||
| `~/.gitconfig` | `credential.helper = libsecret` (changed from `store`) |
|
||||
| `~/.config/systemd/user/` | gnome-keyring-daemon.service + socket: **enabled** |
|
||||
|
||||
### KDE Wallet cleanup
|
||||
|
||||
| State | Status |
|
||||
|-------|--------|
|
||||
| `kwallet` pkg (6.26.0) | Installed (will be removed post-verify) |
|
||||
| `kwalletmanager` pkg (26.04.1) | Installed (will be removed post-verify) |
|
||||
| `~/.local/share/kwalletd/kdewallet.kwl` | Still exists (backed up) |
|
||||
| `~/.local/share/kwalletd/kdewallet_attributes.json` | Still exists (backed up)|
|
||||
| `plasma-kwallet-pam.service` | inactive (static) |
|
||||
|
||||
PAM kwallet refs in `/etc/pam.d/*` were already commented out.
|
||||
|
||||
## Startup / Unlock flow after reboot
|
||||
|
||||
```
|
||||
1. greetd/tuigreet → user login (PAM auth)
|
||||
2. gnome-keyring PAM module (pam_gnome_keyring.so) unlocks keyring with login password
|
||||
3. gnome-keyring-daemon starts via socket activation (systemd user)
|
||||
4. D-Bus org.freedesktop.secrets becomes available
|
||||
5. rbw auto-login (via pinentry + gnome-keyring) → vault accessible
|
||||
6. himalaya can read IMAP passwords via secret-tool
|
||||
7. ssh-add reads SSH passphrases via GK
|
||||
8. git-credential-libsecret reads Git creds via GK
|
||||
9. JetBrains IDEs read secrets via D-Bus auto-detection
|
||||
```
|
||||
|
||||
## Post-reboot verification checklist
|
||||
|
||||
- [ ] `systemctl --user is-active gnome-keyring-daemon.service` → `active`
|
||||
- [ ] `rbw list | wc -l` → entries available
|
||||
- [ ] `secret-tool lookup application ssh-agent service ssh ssh-key /home/mw/.ssh/id_ed25519` → returns passphrase
|
||||
- [ ] `secret-tool lookup application git service gitlab.satware.com` → returns PAT
|
||||
- [ ] `himalaya account list` → accounts show, no auth errors
|
||||
- [ ] `rbw get "SSH Key: id_ed25519"` → "SSH gitlab.satware.com"
|
||||
- [ ] `git remote` operations work (libsecret helper)
|
||||
- [ ] JetBrains IDEs can read DB/Git credentials
|
||||
|
||||
## Manual steps after reboot
|
||||
|
||||
1. If himalaya shows auth errors, re-enter IMAP passwords:
|
||||
```bash
|
||||
# MW account
|
||||
echo "<IMAP_PASSWORD>" | secret-tool store --label="IMAP MW" email mw@satware.com application himalaya
|
||||
# JA account
|
||||
echo "<IMAP_PASSWORD>" | secret-tool store --label="IMAP JA" email ja@satware.ai application himalaya
|
||||
# JA Gmail
|
||||
echo "<IMAP_PASSWORD>" | secret-tool store --label="IMAP JA Gmail" email ja.satware@gmail.com application himalaya
|
||||
# Michael Gmail
|
||||
echo "<IMAP_PASSWORD>" | secret-tool store --label="IMAP Michael" email ironmikechw@gmail.com application himalaya
|
||||
```
|
||||
|
||||
2. If GitHub CLI is stale:
|
||||
```bash
|
||||
gh auth login --hostname github.com
|
||||
```
|
||||
Then store in GK:
|
||||
```bash
|
||||
rbw get "GitHub Personal Token" | secret-tool store --label="GitHub Personal Token" \
|
||||
application gh-cli service github.com
|
||||
```
|
||||
|
||||
3. Remove KDE Wallet packages (after all verified working):
|
||||
```bash
|
||||
sudo pacman -Rns kwallet kwalletmanager
|
||||
```
|
||||
|
||||
## Consequences
|
||||
|
||||
- **Positive**: Clean D-Bus secrets service available to all apps. Central vault syncs across devices. No more KWallet prompt on every login. libsecret works.
|
||||
- **Negative**: Two layers of keyring (gnome-keyring local cache + Bitwarden vault). If gnome-keyring is corrupted, need `rbw sync`. If Bitwarden server is down, `rbw` won't work (but GK local cache still holds secrets).
|
||||
|
||||
## Rollback plan
|
||||
|
||||
If the migration fails, restore by:
|
||||
1. Re-enable `plasma-kwallet-pam.service`
|
||||
2. Revert `credential.helper` to `store`
|
||||
3. KWallet data file remains on disk (not deleted)
|
||||
@@ -0,0 +1,82 @@
|
||||
# Hyprland Configuration - uwsm Integration
|
||||
|
||||
## Architecture
|
||||
|
||||
Session management uses **UWSM** (Universal Wayland Session Manager) wrapping Hyprland in systemd user services.
|
||||
|
||||
```
|
||||
greetd → tuigreet → uwsm start → systemd user session → Hyprland
|
||||
↓
|
||||
systemd units: hyprpaper, waybar, dunst, hypridle, hyprlauncher, cliphist
|
||||
XDG autostart: Nextcloud, JetBrains Toolbox, Flameshot, etc.
|
||||
```
|
||||
|
||||
## Session Services
|
||||
|
||||
All session services are managed via systemd user units (`~/.config/systemd/user/*.service`):
|
||||
|
||||
| Service | Purpose | Status |
|
||||
|---------|---------|--------|
|
||||
| `hyprpaper.service` | Wallpaper daemon | active |
|
||||
| `waybar.service` | Status bar | active |
|
||||
| `dunst.service` | Notification daemon | active |
|
||||
| `hypridle.service` | Idle/screenlock control | active |
|
||||
| `hyprlauncher.service` | App launcher daemon | active |
|
||||
| `cliphist.service` | Clipboard history store | active |
|
||||
|
||||
### Management Commands
|
||||
|
||||
```bash
|
||||
# Check all session services
|
||||
systemctl --user list-units | grep -E 'hypr|waybar|dunst|cliphist'
|
||||
|
||||
# Restart a specific service
|
||||
systemctl --user restart waybar.service
|
||||
|
||||
# View logs
|
||||
journalctl --user -u waybar.service -f
|
||||
```
|
||||
|
||||
## Launchers
|
||||
|
||||
| Component | Tool | Keybind |
|
||||
|-----------|------|---------|
|
||||
| App Launcher | hyprlauncher | SUPER+D |
|
||||
| Keybind Menu | hyprlauncher -o (piped) | SUPER+CTRL+K |
|
||||
|
||||
hypr launcher config: `~/.config/hypr/hyprlauncher.conf`
|
||||
- `desktop_launch_prefix = uwsm app --` (proper systemd scoping)
|
||||
- Cache enabled for frequency-based sorting
|
||||
- Finders: desktop (default), math (=), unicode (.), font (')
|
||||
|
||||
## Login Manager
|
||||
|
||||
**greetd** with **tuigreet** on VT 1.
|
||||
Config: `/etc/greetd/config.toml`
|
||||
Default command: `tuigreet --time --remember --asterisks --cmd 'uwsm start -e -D Hyprland hyprland.desktop'`
|
||||
|
||||
### Wayland Session Override (start-hyprland)
|
||||
|
||||
The package provides `/usr/share/wayland-sessions/hyprland.desktop` with
|
||||
`Exec=Hyprland` (direct binary), but Hyprland 0.53+ requires launching through
|
||||
`start-hyprland` (watchdog parent process) for crash diagnostics and proper
|
||||
signal handling. Otherwise a warning banner appears on login.
|
||||
|
||||
A local override at `~/.local/share/wayland-sessions/hyprland.desktop` sets
|
||||
`Exec=start-hyprland`, which uwsm then manages inside the systemd session.
|
||||
XDG_DATA_DIRS priority ensures the override wins without touching system files.
|
||||
Managed by Ansible role `hyprland` — see `docs/adr/0004-start-hyprland-override.md`.
|
||||
|
||||
## Key Categories
|
||||
|
||||
| Category | Modifier | Keys | Action |
|
||||
|----------|----------|------|--------|
|
||||
| Focus | SUPER | H/J/K/L, Arrows | Move focus |
|
||||
| Move Window | SUPER+SHIFT | H/J/K/L, Arrows | Move window |
|
||||
| Resize Window | SUPER+ALT | H/J/K/L | Resize active window |
|
||||
| Windows | SUPER | F, SPACE, TAB, `, P, J | Window actions |
|
||||
| Apps | SUPER | Q, B, E, D | Launch apps |
|
||||
| Workspaces | SUPER | 1-9 | Switch/move workspaces |
|
||||
| Lock | SUPER+CTRL | L | Lock screen |
|
||||
|
||||
See `~/.config/hypr/cheatsheet.txt` for the full reference.
|
||||
@@ -1,33 +1,97 @@
|
||||
# Learning: Enable WebGPU in Chrome on Linux (Manjaro)
|
||||
---
|
||||
description: >-
|
||||
Chrome 148 on NVIDIA Wayland (Hyprland) — full GPU/video acceleration config.
|
||||
Replaced the 2026-03-11 WebGPU flags with state-of-the-art NVIDIA VA-API setup.
|
||||
tags: [chrome, nvidia, wayland, vaapi, gpu, acceleration]
|
||||
last_updated: '2026-05-21'
|
||||
---
|
||||
|
||||
Date: 2026-03-11
|
||||
# Chrome 148 — NVIDIA Wayland Hardware Acceleration
|
||||
|
||||
Date: 2026-05-21 (updated from 2026-03-11)
|
||||
|
||||
## Context
|
||||
WebGPU was reported as unavailable in Chrome despite the API being detected. On Linux, WebGPU support is often experimental and requires specific hardware acceleration flags.
|
||||
|
||||
## Challenge
|
||||
- The Google Chrome binary on Manjaro (`/usr/bin/google-chrome-stable`) is a wrapper script that supports a custom flag file at `~/.config/chrome-flags.conf`.
|
||||
- Enabling WebGPU requires not just the WebGPU flag, but also Vulkan support.
|
||||
Chrome on NVIDIA + Wayland has three known issues:
|
||||
1. **GBM "Permission denied"**: Chrome's seccomp sandbox blocks NVIDIA's `nvidia-drm_gbm.so` on Wayland.
|
||||
2. **Vulkan/Wayland incompatibility**: `--ozone-platform=wayland` is not compatible with Vulkan rendering (Chromium issue 334275637).
|
||||
3. **NVIDIA VA-API allowlist**: Hardware video decode is blocked on NVIDIA without explicit flags.
|
||||
|
||||
## Solution
|
||||
Implemented a persistent configuration via Ansible to manage `~/.config/chrome-flags.conf` with the following flags:
|
||||
The old WebGPU config (`--enable-unsafe-webgpu`, `--enable-vulkan`) is obsolete —
|
||||
WebGPU works natively in Chrome 148 via ANGLE+OpenGL on Wayland without Vulkan flags.
|
||||
|
||||
```text
|
||||
--enable-unsafe-webgpu
|
||||
--enable-vulkan
|
||||
--enable-features=Vulkan
|
||||
--ignore-gpu-blocklist
|
||||
## Required Packages
|
||||
|
||||
| Package | Reason |
|
||||
|---------|--------|
|
||||
| `libva-utils` | Verification: `vainfo` |
|
||||
| `libva-nvidia-driver` | VA-API NVDEC bridge (already installed, was `nvidia-vaapi-driver`) |
|
||||
|
||||
Note: `libva-utils` was installed 2026-05-21. `libva-nvidia-driver` (0.0.17-1) was already present.
|
||||
|
||||
## Configuration: `~/.config/chrome-flags.conf`
|
||||
|
||||
```
|
||||
--ozone-platform=wayland
|
||||
--disable-seccomp-filter-sandbox
|
||||
--disable-features=Vulkan,VulkanFromANGLE,DefaultANGLEVulkan
|
||||
--use-angle=gl
|
||||
--enable-features=VaapiOnNvidiaGPUs,VaapiIgnoreDriverChecks,AcceleratedVideoDecodeLinuxZeroCopyGL,AcceleratedVideoDecodeLinuxGL
|
||||
--password-store=gnome-libsecret
|
||||
```
|
||||
|
||||
## Key Learnings
|
||||
1. **Persistent Flags**: On Arch-based systems (like Manjaro), use `~/.config/chrome-flags.conf` instead of modifying `.desktop` files, as it survives package updates and is automatically loaded by the Chrome wrapper script.
|
||||
2. **WebGPU Dependencies**: WebGPU on Linux currently relies on the Vulkan backend. Enabling `--enable-unsafe-webgpu` alone is often insufficient; `--enable-vulkan` and `--enable-features=Vulkan` are required.
|
||||
3. **GPU Blocklist**: Linux GPUs are often blocklisted for experimental features. `--ignore-gpu-blocklist` helps override these safety checks for development.
|
||||
### Flag Rationale
|
||||
|
||||
| Flag | Purpose |
|
||||
|------|---------|
|
||||
| `--ozone-platform=wayland` | Force Wayland; auto-detect unreliable on Hyprland |
|
||||
| `--disable-seccomp-filter-sandbox` | Fixes GBM `Permission denied` (Chrome sandbox blocks NVIDIA GBM on Wayland) |
|
||||
| `--disable-features=Vulkan,VulkanFromANGLE,DefaultANGLEVulkan` | Vulkan incompatible with Ozone Wayland; all three must be disabled |
|
||||
| `--use-angle=gl` | Force ANGLE→OpenGL backend; prevents auto-selection of Vulkan |
|
||||
| `--enable-features=VaapiOnNvidiaGPUs,...` | Bypass NVIDIA video decode allowlist; modern flags (replaced deprecated `VaapiVideoDecoder`) |
|
||||
| `--password-store=gnome-libsecret` | gnome-keyring integration (not auto-detected outside GNOME DE) |
|
||||
|
||||
## Verification
|
||||
- Applied changes via Ansible role `dev-tools`.
|
||||
- Verified file existence and content.
|
||||
- Verified flag application by inspecting running processes (`ps aux`).
|
||||
- Used Playwright (via MCP) to navigate to `https://webgpureport.org/`.
|
||||
- Confirmed that while `navigator.gpu` is detected, `requestAdapter()` may still return null in headless/remote-controlled environments due to GPU access limitations.
|
||||
- **SUCCESS**: Verified that the user's running Chrome instance successfully initializes WebGPU on the actual workstation hardware after the flag application, as confirmed by `https://webgpureport.org/`.
|
||||
|
||||
```bash
|
||||
vainfo # Shows VA-API NVDEC decoder capabilities
|
||||
LIBVA_DRIVER_NAME=nvidia vainfo # Explicit NVIDIA VA-API check
|
||||
```
|
||||
|
||||
Expected `chrome://gpu` results:
|
||||
- Canvas: **Hardware accelerated**
|
||||
- Compositing: **Hardware accelerated**
|
||||
- Video Decode: **Hardware accelerated** (H.264, VP9, HEVC)
|
||||
- WebGL: **Hardware accelerated**
|
||||
- WebGPU: **Hardware accelerated**
|
||||
- GPU: GTX 1050 Ti / NVIDIA 580.159.03
|
||||
- GL implementation: `gl=egl-angle,angle=opengl`
|
||||
- GPU process crash count: **0**
|
||||
- Ozone platform: **wayland**
|
||||
|
||||
## Known Warnings
|
||||
|
||||
Three log lines persist despite correct config — these are upstream Chromium warnings:
|
||||
|
||||
```
|
||||
ERROR:ui/ozone/platform/wayland/gpu/wayland_surface_factory.cc:252: '--ozone-platform=wayland' is not compatible with Vulkan
|
||||
WARNING:sandbox/policy/linux/sandbox_linux.cc:404: InitializeSandbox() called with multiple threads in process gpu-process
|
||||
ERROR:ui/gl/egl_util.cc:92: EGL Driver message (Error) eglCreateContext: Requested version is not supported
|
||||
```
|
||||
|
||||
These are cosmetic. Chrome falls back to ANGLE+OpenGL before they print.
|
||||
No fix exists upstream yet (Chromium issue 334275637).
|
||||
|
||||
## Key Learnings
|
||||
|
||||
1. **Deprecated flags**: `VaapiVideoDecoder`, `VaapiVideoDecodeLinuxGL` replaced by `AcceleratedVideoDecodeLinuxZeroCopyGL`, `AcceleratedVideoDecodeLinuxGL` since Chrome ≥131.
|
||||
2. **Vulkan is opt-in, not opt-out**: `--disable-features=Vulkan` alone is insufficient; `VulkanFromANGLE` and `DefaultANGLEVulkan` are auto-enabled and must be listed.
|
||||
3. **GBM "Permission denied" is not file perms**: It's seccomp sandbox + NVIDIA GBM. `--disable-seccomp-filter-sandbox` is the confirmed workaround.
|
||||
4. **Password store**: Hyprland ≠ GNOME, so `--password-store=gnome-libsecret` explicit flag needed.
|
||||
5. **`--ignore-gpu-blocklist` no longer needed**: Chrome 148 recognizes the GTX 1050 Ti + current driver without it.
|
||||
|
||||
## Ansible Integration
|
||||
|
||||
The `dev-tools` role manages this file. Update both:
|
||||
- `ansible/roles/dev-tools/tasks/main.yml` (the copy task content)
|
||||
- This learning doc (the rationale)
|
||||
|
||||
@@ -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,163 @@
|
||||
---
|
||||
description: >-
|
||||
Session record (2026-07-03): diagnosed a Remmina SIGSEGV on Wayland when
|
||||
keymap was set, traced it to a missing NULL check on XOpenDisplay in
|
||||
remmina-plugin-rdp, forked Remmina to gitlab.com/satware, built from
|
||||
source, verified the fix, and submitted MR !2757 upstream.
|
||||
tags:
|
||||
- learning
|
||||
- remmina
|
||||
- wayland
|
||||
- rdp
|
||||
- crash-analysis
|
||||
- gitlab
|
||||
- upstream-contribution
|
||||
last_updated: '2026-07-03'
|
||||
---
|
||||
|
||||
# Remmina SIGSEGV on Wayland - diagnosis, fix, upstream MR - 2026-07-03
|
||||
|
||||
## Summary
|
||||
|
||||
Remmina crashed (SIGSEGV) every time the "Mothership" RDP profile was
|
||||
selected. The root cause was a missing NULL check on `XOpenDisplay(0)` in
|
||||
`remmina_get_rdp_kbd_remap()` (`plugins/rdp/rdp_plugin.c`), triggered only
|
||||
when a `keymap` profile setting was non-empty (e.g. `Map Meta Keys`) and
|
||||
the session was Wayland without accessible X11.
|
||||
|
||||
Forked Remmina to `gitlab.com/satware/Remmina`, built from source with
|
||||
FreeRDP3, reproduced the crash, applied a 4-line fix, verified it, and
|
||||
submitted merge request [!2757](https://gitlab.com/Remmina/Remmina/-/merge_requests/2757)
|
||||
to the upstream project.
|
||||
|
||||
## Incident
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Symptom | Remmina closes immediately when selecting "Mothership" and clicking connect |
|
||||
| Signal | SIGSEGV (segfault) |
|
||||
| Crash location | `XKeysymToKeycode()` in `libX11.so.6`, called from `remmina-plugin-rdp.so` |
|
||||
| Profile | `group_rdp_mothership_10-10-202-4.remmina` (server `10.10.202.4`) |
|
||||
| Working profile | `srv-app-2` (`100.64.0.78`) - identical protocol, no crash |
|
||||
|
||||
## Diagnosis
|
||||
|
||||
### Stack trace (from coredumpctl)
|
||||
|
||||
```
|
||||
#0 XKeysymToKeycode (libX11.so.6 + 0x92cd7)
|
||||
#1 remmina-plugin-rdp.so + 0xff27
|
||||
#2 remmina-plugin-rdp.so + 0x1296b
|
||||
```
|
||||
|
||||
### Root cause
|
||||
|
||||
In `plugins/rdp/rdp_plugin.c`, function `remmina_get_rdp_kbd_remap()`:
|
||||
|
||||
```c
|
||||
display = XOpenDisplay(0); // Returns NULL on Wayland
|
||||
// NO NULL CHECK
|
||||
XKeysymToKeycode(display, table[i]) // SIGSEGV - dereferences NULL
|
||||
```
|
||||
|
||||
The function is only called when `keymap` is non-empty in the profile.
|
||||
The working profile had `keymap=` (empty), which skipped the entire code
|
||||
path. The crashing profile had `keymap=Map Meta Keys`.
|
||||
|
||||
### Environment
|
||||
|
||||
| Component | Version |
|
||||
|-----------|---------|
|
||||
| OS | Manjaro Linux, Wayland session (`XDG_SESSION_TYPE=wayland`) |
|
||||
| Remmina | 1.4.43 (git n/a) |
|
||||
| FreeRDP | 3.27.0 |
|
||||
| Display | `DISPLAY=:0` (Xwayland) but `XOpenDisplay(0)` fails from RDP worker thread |
|
||||
|
||||
### Key config difference
|
||||
|
||||
| Setting | Mothership (crashes) | srv-app-2 (works) |
|
||||
|---------|---------------------|-------------------|
|
||||
| `keymap` | `Map Meta Keys` | (empty) |
|
||||
|
||||
## Fix
|
||||
|
||||
4-line addition in `plugins/rdp/rdp_plugin.c`, function `remmina_get_rdp_kbd_remap()`:
|
||||
|
||||
```diff
|
||||
display = XOpenDisplay(0);
|
||||
+if (!display) {
|
||||
+ g_free(rdp_kbd_remap);
|
||||
+ return NULL;
|
||||
+}
|
||||
for (i = 0; table[i] > 0; i += 2) {
|
||||
```
|
||||
|
||||
The caller at line ~2148 already checks `if (rdp_kbd_remap != NULL)` before
|
||||
use, so returning NULL causes graceful degradation (empty
|
||||
`KeyboardRemappingList`) instead of a crash.
|
||||
|
||||
## Verification
|
||||
|
||||
| Test | Result |
|
||||
|------|--------|
|
||||
| Reproduce crash (unpatched, `keymap=Map Meta Keys`) | SIGSEGV - new coredump at 12:05:54 |
|
||||
| Apply fix, rebuild, same profile | No crash - exit code 0, no new coredump |
|
||||
| Debug log after fix | `rdp_keyboard_remapping_list:` (empty, as expected) |
|
||||
|
||||
## Local workaround (applied)
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| Mothership profile `keymap` | Set to empty (`keymap=`) as safe setting |
|
||||
| Patched plugin | `~/.config/remmina/plugins/remmina-plugin-rdp.so` (loads before system plugin) |
|
||||
| Source build | `~/external/remmina/` (branch `fix/rdp-xopendisplay-null-check`) |
|
||||
|
||||
The patched plugin in `~/.config/remmina/plugins/` protects the system
|
||||
Remmina (`/usr/bin/remmina`) even if `keymap` is set to a non-empty value.
|
||||
|
||||
## Upstream contribution
|
||||
|
||||
### GitLab.com setup (one-time)
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| GitLab.com group | `gitlab.com/satware` (public, ID 136548341) |
|
||||
| User | `ironmikechw` (ID 39970188, Owner of `satware` group) |
|
||||
| PAT | Stored in `~/.env` as `GITLAB_COM_API_TOKEN` |
|
||||
| glab CLI | Authenticated to both `gitlab.satware.com` and `gitlab.com` |
|
||||
|
||||
### Fork and MR
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| Upstream | `gitlab.com/Remmina/Remmina` (canonical, project ID 7153509) |
|
||||
| Fork | `gitlab.com/satware/Remmina` (project ID 84057632) |
|
||||
| Local clone | `~/external/remmina/` with `upstream` remote added |
|
||||
| Build | `cmake -Bbuild -H. -DCMAKE_BUILD_TYPE=Debug -DWITH_FREERDP3=ON` |
|
||||
| Branch | `fix/rdp-xopendisplay-null-check` |
|
||||
| Commit | `e6084bd7f` - "rdp: fix SIGSEGV when XOpenDisplay returns NULL on Wayland" |
|
||||
| MR | [!2757](https://gitlab.com/Remmina/Remmina/-/merge_requests/2757) (opened, target: master) |
|
||||
|
||||
### Build notes
|
||||
|
||||
- CMake flag `-DWITH_FREERDP3=ON` is required (default is OFF/FreeRDP2)
|
||||
- All build deps present on this workstation (FreeRDP3 3.27.0, GTK3 3.24.52, etc.)
|
||||
- Build time: ~30 seconds (incremental, 16 cores)
|
||||
- Plugin loads from `~/.config/remmina/plugins/` (alt dir, checked before system)
|
||||
|
||||
## Files touched
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `~/.local/share/remmina/group_rdp_mothership_10-10-202-4.remmina` | `keymap=Map Meta Keys` -> `keymap=` (safe setting) |
|
||||
| `~/.config/remmina/plugins/remmina-plugin-rdp.so` | Patched plugin (built from source) |
|
||||
| `~/external/remmina/plugins/rdp/rdp_plugin.c` | 4-line NULL check fix (committed + pushed) |
|
||||
| `~/.env` | `GITLAB_COM_API_TOKEN` added (for glab CLI) |
|
||||
| `~/.config/glab-cli/config.yml` | gitlab.com host added (glab auth login) |
|
||||
|
||||
## References
|
||||
|
||||
- MR: https://gitlab.com/Remmina/Remmina/-/merge_requests/2757
|
||||
- Fork: https://gitlab.com/satware/Remmina
|
||||
- Upstream: https://gitlab.com/Remmina/Remmina
|
||||
- CONTRIBUTING.md: fork-and-pull model on GitLab
|
||||
@@ -0,0 +1,208 @@
|
||||
---
|
||||
description: >-
|
||||
Session record (2026-07-06): triaged Caddy journald warnings on
|
||||
onlyoffice.localhost/mcp showing "context canceled" SSE aborts (~1.5ms)
|
||||
from opencode 1.17.13 MCP client against the OnlyOffice MCP server on
|
||||
:3847. Verified upstream liveness, opened GitLab issue #92 in
|
||||
satware/mcp/onlyoffice, and documented the Caddy reverse-proxy as a
|
||||
manual override.
|
||||
tags:
|
||||
- learning
|
||||
- caddy
|
||||
- onlyoffice
|
||||
- mcp
|
||||
- sse
|
||||
- reverse-proxy
|
||||
- investigation
|
||||
last_updated: '2026-07-06'
|
||||
---
|
||||
|
||||
# Caddy "context canceled" on onlyoffice.localhost/mcp - investigation - 2026-07-06
|
||||
|
||||
## Summary
|
||||
|
||||
Caddy (systemd `caddy.service`) on `mw-manjaro-pf` is emitting a high
|
||||
volume of `warn`-level journald entries: `aborting with incomplete
|
||||
response ... error: "reading: context canceled"` for `GET /mcp` requests
|
||||
on `https://onlyoffice.localhost`. Each abort fires ~1.5ms after the
|
||||
request is forwarded to the upstream `localhost:3847` (OnlyOffice MCP
|
||||
server). The upstream is alive and correctly rejects non-SSE GETs with
|
||||
HTTP 406, so this is a stream-lifecycle issue, not a server-down issue.
|
||||
|
||||
Investigation handed off to the OnlyOffice MCP repo as GitLab issue #92.
|
||||
This workstation's Caddy reverse-proxy config is now recorded as a manual
|
||||
override (not Ansible-managed).
|
||||
|
||||
## Incident
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Symptom | Repeated `context canceled` warnings in `journalctl -u caddy` |
|
||||
| Caddy site | `https://onlyoffice.localhost` -> `reverse_proxy localhost:3847` |
|
||||
| Upstream | `localhost:3847` (OnlyOffice MCP server) |
|
||||
| Request | `GET /mcp` with `Accept: text/event-stream` |
|
||||
| Client | `opencode/1.17.13` (MCP streamable-HTTP transport) |
|
||||
| MCP protocol | `2025-11-25` |
|
||||
| Abort duration | ~1.5ms (request forwarded, then read context canceled) |
|
||||
| Frequency | Multiple per minute, across distinct `Mcp-Session-Id` values |
|
||||
|
||||
## Evidence
|
||||
|
||||
### Caddy log sample (recurring)
|
||||
|
||||
```json
|
||||
{
|
||||
"level": "warn",
|
||||
"logger": "http.handlers.reverse_proxy",
|
||||
"msg": "aborting with incomplete response",
|
||||
"upstream": "localhost:3847",
|
||||
"duration": 0.00173631,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"host": "onlyoffice.localhost",
|
||||
"uri": "/mcp",
|
||||
"headers": {
|
||||
"Accept": ["text/event-stream"],
|
||||
"Mcp-Protocol-Version": ["2025-11-25"],
|
||||
"Mcp-Session-Id": ["a5e859b4-01f1-4c24-81bb-78ee02d74357"],
|
||||
"User-Agent": ["opencode/1.17.13"],
|
||||
"Via": ["1.1 Caddy"]
|
||||
}
|
||||
},
|
||||
"error": "reading: context canceled"
|
||||
}
|
||||
```
|
||||
|
||||
Observed `Mcp-Session-Id` values (each retried over several minutes):
|
||||
`a5e859b4...`, `4bfbb912...`, `3c4a0454...`, `08397c3d...`,
|
||||
`ed762f08...`, `b0fae341...`.
|
||||
|
||||
### Upstream liveness verification
|
||||
|
||||
```bash
|
||||
$ ss -tlnp | grep :3847
|
||||
LISTEN 0 4096 0.0.0.0:3847 0.0.0.0:*
|
||||
|
||||
$ curl -sS -o /dev/null -w "HTTP %{http_code} in %{time_total}s\n" \
|
||||
--max-time 3 http://localhost:3847/mcp
|
||||
HTTP 406 in 0.004665s
|
||||
```
|
||||
|
||||
HTTP 406 is expected for a plain GET without the required SSE headers.
|
||||
The MCP server is alive and rejecting non-SSE requests correctly.
|
||||
|
||||
## Diagnosis
|
||||
|
||||
### Root cause (not yet confirmed - under investigation)
|
||||
|
||||
Five candidate hypotheses, listed in the GitLab issue. The leading
|
||||
suspect is **client-side disconnect**: opencode 1.17.13 opens the SSE
|
||||
stream then immediately closes it (the ~1.5ms duration is too short for
|
||||
a genuine read). Secondary suspects are server-side stream teardown,
|
||||
MCP protocol-version mismatch (`2025-11-25`), Caddy HTTP/1.1 framing of
|
||||
SSE, or a client session-pool bug creating and abandoning sessions.
|
||||
|
||||
### Why this workstation's concern
|
||||
|
||||
The Caddy reverse-proxy is the boundary that surfaces the symptom. Even
|
||||
if the root cause is client- or server-side, the local config matters
|
||||
because:
|
||||
|
||||
1. Caddy logs are being polluted with warn-level noise (masks real errors).
|
||||
2. The reverse-proxy block has no SSE-specific tuning (`flush_interval`,
|
||||
explicit HTTP/1.1 transport) that could mask or mitigate upstream
|
||||
misbehavior.
|
||||
3. The Caddy config at `/etc/caddy/conf.d/localhost-dev.conf` is **not**
|
||||
Ansible-managed by this repo - it is a manual override that must be
|
||||
recorded (see `docs/tech/manual-overrides.md`).
|
||||
|
||||
## Environment
|
||||
|
||||
| Component | Version / Value |
|
||||
|-----------|----------------|
|
||||
| Host | `mw-manjaro-pf` (mw-pfeddersheim-workstation) |
|
||||
| OS | Manjaro Linux, Wayland session |
|
||||
| Caddy | systemd `caddy.service`, active since 2026-07-02 |
|
||||
| Caddy config | `/etc/caddy/Caddyfile` + `/etc/caddy/conf.d/localhost-dev.conf` |
|
||||
| TLS cert | mkcert pair at `/etc/caddy/localhost{,-key}.pem` |
|
||||
| Upstream | `localhost:3847` (OnlyOffice MCP server, process TBD) |
|
||||
| MCP client | `opencode/1.17.13` |
|
||||
| MCP protocol | `2025-11-25` (streamable HTTP transport) |
|
||||
|
||||
## *.localhost sites configured
|
||||
|
||||
All defined in `/etc/caddy/conf.d/localhost-dev.conf` (14 sites total):
|
||||
|
||||
| Site | Backend / Handler |
|
||||
|------|-------------------|
|
||||
| `mcp.localhost` | `reverse_proxy localhost:50880` |
|
||||
| `artifacts.localhost` | `file_server browse` root `/var/www/artifacts` |
|
||||
| `onlyoffice.localhost` | `reverse_proxy localhost:3847` (subject of this issue) |
|
||||
| `project-1.localhost` ... `project-10.localhost` | `respond "project-N - placeholder"` (10 reserved) |
|
||||
| `demo.satware.com.localhost` | `reverse_proxy localhost:38081` (webdevops/php-apache-dev container) |
|
||||
|
||||
## Handoff
|
||||
|
||||
### GitLab issue
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| Project | `satware/mcp/onlyoffice` (`gitlab.satware.com`) |
|
||||
| Issue | [#92](https://gitlab.satware.com/satware/mcp/onlyoffice/-/work_items/92) |
|
||||
| Title | Investigation: Caddy reports 'context canceled' on onlyoffice.localhost/mcp SSE stream (~1.5ms aborts) |
|
||||
| Labels | `bug`, `investigation`, `mcp` |
|
||||
| State | open |
|
||||
|
||||
The issue body contains the full evidence, 5 hypotheses, 7 investigation
|
||||
tasks, and a proposed Caddy mitigation (`flush_interval -1` +
|
||||
`transport http { versions 1.1 }`).
|
||||
|
||||
### Next actions (assigned to OnlyOffice MCP repo)
|
||||
|
||||
1. Identify the process listening on `:3847` (`sudo ss -tlnp | grep 3847`).
|
||||
2. Collect server-side logs for the `Mcp-Session-Id` values cited above.
|
||||
3. Reproduce with `curl -N -H "Accept: text/event-stream" -H "Mcp-Protocol-Version: 2025-11-25" https://onlyoffice.localhost/mcp`.
|
||||
4. Check opencode 1.17.13 changelog for MCP SSE session bugs.
|
||||
5. Verify Caddy `reverse_proxy` SSE tuning (proposed block in issue #92).
|
||||
6. Confirm whether `notifications/initialized` round-trip completes.
|
||||
7. Log the negotiated MCP protocol version server-side.
|
||||
|
||||
## Local documentation updates
|
||||
|
||||
| File | Change |
|
||||
|------|-------|
|
||||
| `docs/tech/manual-overrides.md` | Added row for `/etc/caddy/` config (not Ansible-managed) |
|
||||
| `docs/tech/stack.md` | Added "Local Dev Services" section listing Caddy + `*.localhost` sites |
|
||||
| `CHANGELOG.md` | Added `[Unreleased] -> Added` entry |
|
||||
| GitLab issue #92 | Created with full evidence + hypotheses + tasks |
|
||||
|
||||
## Proposed Caddy mitigation (to test on this workstation)
|
||||
|
||||
```caddyfile
|
||||
https://onlyoffice.localhost {
|
||||
tls /etc/caddy/localhost.pem /etc/caddy/localhost-key.pem
|
||||
reverse_proxy localhost:3847 {
|
||||
flush_interval -1
|
||||
transport http {
|
||||
versions 1.1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`flush_interval -1` disables Caddy's buffering so SSE events flush to the
|
||||
client immediately; `transport http { versions 1.1 }` pins the upstream
|
||||
leg to HTTP/1.1 (Caddy logs already show HTTP/1.1 for the upstream, this
|
||||
makes it explicit and avoids HTTP/2 SSE framing surprises).
|
||||
|
||||
Not applied yet - pending GitLab issue #92 resolution to avoid masking
|
||||
the root cause.
|
||||
|
||||
## References
|
||||
|
||||
- GitLab issue: https://gitlab.satware.com/satware/mcp/onlyoffice/-/work_items/92
|
||||
- Caddy config: `/etc/caddy/conf.d/localhost-dev.conf`
|
||||
- Local skill: `caddy` (`~/.config/opencode/skills/caddy/SKILL.md`)
|
||||
- MCP transport spec: https://modelcontextprotocol.io/specification/2025-11-25/basic/transports
|
||||
- Manual overrides: `docs/tech/manual-overrides.md`
|
||||
- Software stack: `docs/tech/stack.md`
|
||||
@@ -0,0 +1 @@
|
||||
/.quarto/
|
||||
@@ -0,0 +1,14 @@
|
||||
format:
|
||||
typst-pdf:
|
||||
default: true
|
||||
toc: false
|
||||
number-sections: true
|
||||
mainfont: "Inter"
|
||||
monofont: "JetBrains Mono"
|
||||
fontsize: 9pt
|
||||
papersize: a4
|
||||
margin:
|
||||
top: 1.2cm
|
||||
bottom: 1.2cm
|
||||
left: 1.5cm
|
||||
right: 1.5cm
|
||||
@@ -0,0 +1,128 @@
|
||||
/* Cheatsheet CSS for Markdown → HTML → PDF via WeasyPrint */
|
||||
/* Clean, professional tables with Inter + JetBrains Mono */
|
||||
|
||||
@page {
|
||||
size: a4 portrait;
|
||||
margin: 10mm 14mm 10mm 14mm;
|
||||
@bottom-center {
|
||||
content: "Seite " counter(page);
|
||||
font-size: 7pt;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
* { margin-top: 0; }
|
||||
|
||||
body {
|
||||
font-family: "Inter", "DejaVu Sans", sans-serif;
|
||||
font-size: 9pt;
|
||||
line-height: 1.3;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 14pt;
|
||||
font-weight: 700;
|
||||
margin-bottom: 4pt;
|
||||
padding-bottom: 4pt;
|
||||
border-bottom: 1.5pt solid #111;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 10.5pt;
|
||||
font-weight: 700;
|
||||
margin: 10pt 0 4pt 0;
|
||||
color: #003366;
|
||||
border-bottom: 0.5pt solid #ccc;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 9pt;
|
||||
font-weight: 600;
|
||||
margin: 6pt 0 2pt 0;
|
||||
color: #003366;
|
||||
}
|
||||
|
||||
p { margin: 2pt 0; }
|
||||
|
||||
/* ---- Tables: clean reference style ---- */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 2pt 0 6pt 0;
|
||||
font-size: 8pt;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f0f4f8;
|
||||
font-weight: 700;
|
||||
font-size: 7.5pt;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
color: #003366;
|
||||
padding: 3pt 5pt;
|
||||
border: none;
|
||||
border-bottom: 1pt solid #0066cc;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 2pt 5pt;
|
||||
border: none;
|
||||
border-bottom: 0.5pt solid #e4e4e4;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(even) td {
|
||||
background: #f7f9fb;
|
||||
}
|
||||
|
||||
tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* ---- Code / key rendering ---- */
|
||||
code, kbd {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 8pt;
|
||||
background: #f5f5f5;
|
||||
padding: 1pt 3pt;
|
||||
border-radius: 2pt;
|
||||
border: 0.5pt solid #ddd;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 7.5pt;
|
||||
background: #f5f5f5;
|
||||
padding: 4pt 6pt;
|
||||
border-radius: 3pt;
|
||||
border: 0.5pt solid #ddd;
|
||||
page-break-inside: avoid;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* ---- Two-column cheatsheet layout ---- */
|
||||
.twocol {
|
||||
column-count: 2;
|
||||
column-gap: 14pt;
|
||||
}
|
||||
|
||||
.twocol h2 {
|
||||
break-before: column;
|
||||
column-break-before: always;
|
||||
}
|
||||
|
||||
.twocol h2:first-child {
|
||||
break-before: avoid;
|
||||
column-break-before: avoid;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 0.5pt solid #ddd;
|
||||
margin: 6pt 0;
|
||||
}
|
||||
|
||||
a { color: #0066cc; text-decoration: none; }
|
||||
@@ -0,0 +1,126 @@
|
||||
---
|
||||
title: "Hyprland Keybindings Cheatsheet"
|
||||
author: "mw-pfeddersheim-workstation"
|
||||
date: "May 2026"
|
||||
---
|
||||
|
||||
# Hyprland Keybindings — Full Cheatsheet
|
||||
|
||||
**Layout:** German (de) | **Mod key:** SUPER (Windows key) | **Source:** `hyprctl binds` live
|
||||
|
||||
---
|
||||
|
||||
## General
|
||||
|
||||
| Shortcut | Action | Details |
|
||||
|----------|--------|---------|
|
||||
| `SUPER+Q` | Open terminal | kitty |
|
||||
| `SUPER+C` | Kill active window | killactive |
|
||||
| `SUPER+M` | Exit Hyprland | exit |
|
||||
| `SUPER+E` | File manager | dolphin |
|
||||
| `SUPER+V` | Toggle floating | togglefloating |
|
||||
| `SUPER+D` | App launcher | hyprlauncher |
|
||||
| `SUPER+P` | Toggle pseudo-split | pseudo |
|
||||
| `SUPER+J` | Toggle split direction | layoutmsg togglesplit |
|
||||
| `SUPER+F` | Toggle fullscreen | fullscreen 0 |
|
||||
| `SUPER+R` | Reload config | hyprctl reload |
|
||||
| `SUPER+SPACE` | Toggle float (popup) | togglefloating |
|
||||
| `SUPER+TAB` | Cycle next window | cyclenext |
|
||||
| `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+L` | Lock screen | hyprlock |
|
||||
| `SUPER+B` | Web browser | google-chrome-stable |
|
||||
|
||||
## Navigation / Focus
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `SUPER+TAB` | Cycle next window |
|
||||
| `SUPER+SHIFT+TAB` | Cycle previous window |
|
||||
| `SUPER+H` | Move focus left |
|
||||
| `SUPER+L` | Move focus right |
|
||||
| `SUPER+left arrow` | Move focus left |
|
||||
| `SUPER+right arrow` | Move focus right |
|
||||
| `SUPER+up arrow` | Move focus up |
|
||||
| `SUPER+down arrow` | Move focus down |
|
||||
|
||||
## Window Movement
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `SUPER+SHIFT+H` | Move window left |
|
||||
| `SUPER+SHIFT+L` | Move window right |
|
||||
| `SUPER+SHIFT+K` | Move window up |
|
||||
| `SUPER+SHIFT+J` | Move window down |
|
||||
| `SUPER+SHIFT+left` | Move window left |
|
||||
| `SUPER+SHIFT+right` | Move window right |
|
||||
| `SUPER+SHIFT+up` | Move window up |
|
||||
| `SUPER+SHIFT+down` | Move window down |
|
||||
|
||||
## Window Resize
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `SUPER+ALT+H` | Resize active: width -40 |
|
||||
| `SUPER+ALT+L` | Resize active: width +40 |
|
||||
| `SUPER+ALT+K` | Resize active: height -40 |
|
||||
| `SUPER+ALT+J` | Resize active: height +40 |
|
||||
|
||||
## Screenshots
|
||||
|
||||
| Shortcut | Action | Tool |
|
||||
|----------|--------|------|
|
||||
| `Print` | Area select → save + copy | grimblast copysave area |
|
||||
| `SUPER+Print` | Active window → save + copy | grimblast copysave active |
|
||||
| `SUPER+SHIFT+Print` | Full monitor → save + copy | grimblast copysave output |
|
||||
| `SUPER+ALT+Print` | Area select → annotate | grim | slurp | satty |
|
||||
|
||||
## Workspaces
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `SUPER+1..9` | Switch to workspace 1-9 |
|
||||
| `SUPER+SHIFT+1..9` | Move active window to workspace 1-9 |
|
||||
| `SUPER+` `` ` `` | Toggle scratchpad (togglespecialworkspace) |
|
||||
| `SUPER+SHIFT+` `` ` `` | Move to scratchpad (movetoworkspace special) |
|
||||
|
||||
### Workspace Layout
|
||||
|
||||
| Workspace | Monitor | Position |
|
||||
|-----------|---------|----------|
|
||||
| 1 | DP-1 (TERRA 3280W) | Left, 2560x1440@144 |
|
||||
| 2 | DVI-D-1 (BenQ BL2405) | Center, 1920x1080@60 rotated 90deg |
|
||||
| 3 | HDMI-A-1 (TERRA 3280W) | Right, 2560x1440@144 |
|
||||
|
||||
## 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.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Check |
|
||||
|-------|-------|
|
||||
| Print key not working | `hyprctl binds | grep Print` |
|
||||
| `SUPER+R` not working | `hyprctl binds | grep 'reload'` |
|
||||
| Screenshots fail | `which grimblast satty slurp grim` |
|
||||
| Clipboard empty | `echo test | wl-copy && wl-paste` |
|
||||
|
||||
## Satty Editor Shortcuts
|
||||
|
||||
| Action | Shortcut |
|
||||
|--------|----------|
|
||||
| Freehand | F |
|
||||
| Rectangle | R |
|
||||
| Ellipse | E |
|
||||
| Line/Arrow | L |
|
||||
| Text | T |
|
||||
| Blur | B |
|
||||
| Crop | C |
|
||||
| Undo | Ctrl+Z |
|
||||
| Redo | Ctrl+Shift+Z |
|
||||
| Copy | Ctrl+C |
|
||||
| Save | Ctrl+S |
|
||||
| Quit | Esc or Ctrl+Q |
|
||||
@@ -0,0 +1,200 @@
|
||||
#set document(title: "Hyprland Keybindings - Pandoc")
|
||||
#set text(font: ("Inter", "DejaVu Sans"), size: 9pt, lang: "de")
|
||||
#set page(paper: "a4", margin: 1.2cm)
|
||||
#set heading(numbering: "1.")
|
||||
#set par(leading: 0.45em)
|
||||
#show link: set text(navy)
|
||||
#let horizontalrule = h(25%) + line(stroke: 0.5pt, length: 50%)
|
||||
= Hyprland Keybindings --- Full Cheatsheet
|
||||
<hyprland-keybindings-full-cheatsheet>
|
||||
#strong[Layout:] German (de) | #strong[Mod key:] SUPER (Windows key) |
|
||||
#strong[Source:] `hyprctl binds` live
|
||||
|
||||
#horizontalrule
|
||||
|
||||
== General
|
||||
<general>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: (37.04%, 29.63%, 33.33%),
|
||||
align: (auto,auto,auto,),
|
||||
table.header([Shortcut], [Action], [Details],),
|
||||
table.hline(),
|
||||
[`SUPER+Q`], [Open terminal], [kitty],
|
||||
[`SUPER+C`], [Kill active window], [killactive],
|
||||
[`SUPER+M`], [Exit Hyprland], [exit],
|
||||
[`SUPER+E`], [File manager], [dolphin],
|
||||
[`SUPER+V`], [Toggle floating], [togglefloating],
|
||||
[`SUPER+D`], [App launcher], [hyprlauncher],
|
||||
[`SUPER+P`], [Toggle pseudo-split], [pseudo],
|
||||
[`SUPER+J`], [Toggle split direction], [layoutmsg togglesplit],
|
||||
[`SUPER+F`], [Toggle fullscreen], [fullscreen 0],
|
||||
[`SUPER+R`], [Reload config], [hyprctl reload],
|
||||
[`SUPER+SPACE`], [Toggle float (popup)], [togglefloating],
|
||||
[`SUPER+TAB`], [Cycle next window], [cyclenext],
|
||||
[`SUPER+SHIFT+TAB`], [Cycle previous window], [cyclenext prev],
|
||||
[`SUPER+` #raw("`") (Grave)], [Toggle
|
||||
scratchpad], [togglespecialworkspace],
|
||||
[`SUPER+SHIFT+` #raw("`");], [Send window to
|
||||
scratchpad], [movetoworkspace special],
|
||||
[`SUPER+CTRL+K`], [Keybind cheat menu], [#emph[(broken --- pipelines
|
||||
to grep+fuzzel)];],
|
||||
[`SUPER+CTRL+L`], [Lock screen], [hyprlock],
|
||||
[`SUPER+B`], [Web browser], [google-chrome-stable],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
|
||||
== Navigation / Focus
|
||||
<navigation-focus>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: 2,
|
||||
align: (auto,auto,),
|
||||
table.header([Shortcut], [Action],),
|
||||
table.hline(),
|
||||
[`SUPER+TAB`], [Cycle next window],
|
||||
[`SUPER+SHIFT+TAB`], [Cycle previous window],
|
||||
[`SUPER+H`], [Move focus left],
|
||||
[`SUPER+L`], [Move focus right],
|
||||
[`SUPER+left arrow`], [Move focus left],
|
||||
[`SUPER+right arrow`], [Move focus right],
|
||||
[`SUPER+up arrow`], [Move focus up],
|
||||
[`SUPER+down arrow`], [Move focus down],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
|
||||
== Window Movement
|
||||
<window-movement>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: 2,
|
||||
align: (auto,auto,),
|
||||
table.header([Shortcut], [Action],),
|
||||
table.hline(),
|
||||
[`SUPER+SHIFT+H`], [Move window left],
|
||||
[`SUPER+SHIFT+L`], [Move window right],
|
||||
[`SUPER+SHIFT+K`], [Move window up],
|
||||
[`SUPER+SHIFT+J`], [Move window down],
|
||||
[`SUPER+SHIFT+left`], [Move window left],
|
||||
[`SUPER+SHIFT+right`], [Move window right],
|
||||
[`SUPER+SHIFT+up`], [Move window up],
|
||||
[`SUPER+SHIFT+down`], [Move window down],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
|
||||
== Window Resize
|
||||
<window-resize>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: 2,
|
||||
align: (auto,auto,),
|
||||
table.header([Shortcut], [Action],),
|
||||
table.hline(),
|
||||
[`SUPER+ALT+H`], [Resize active: width -40],
|
||||
[`SUPER+ALT+L`], [Resize active: width +40],
|
||||
[`SUPER+ALT+K`], [Resize active: height -40],
|
||||
[`SUPER+ALT+J`], [Resize active: height +40],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
|
||||
== Screenshots
|
||||
<screenshots>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: (41.67%, 33.33%, 25%),
|
||||
align: (auto,auto,auto,),
|
||||
table.header([Shortcut], [Action], [Tool],),
|
||||
table.hline(),
|
||||
[`Print`], [Area select → save + copy], [grimblast copysave area],
|
||||
[`SUPER+Print`], [Active window → save + copy], [grimblast copysave
|
||||
active],
|
||||
[`SUPER+SHIFT+Print`], [Full monitor → save + copy], [grimblast
|
||||
copysave output],
|
||||
[`SUPER+ALT+Print`], [Area select → annotate], [grim],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
|
||||
== Workspaces
|
||||
<workspaces>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: (55.56%, 44.44%),
|
||||
align: (auto,auto,),
|
||||
table.header([Shortcut], [Action],),
|
||||
table.hline(),
|
||||
[`SUPER+1..9`], [Switch to workspace 1-9],
|
||||
[`SUPER+SHIFT+1..9`], [Move active window to workspace 1-9],
|
||||
[`SUPER+` #raw("`");], [Toggle scratchpad (togglespecialworkspace)],
|
||||
[`SUPER+SHIFT+` #raw("`");], [Move to scratchpad (movetoworkspace
|
||||
special)],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
|
||||
=== Workspace Layout
|
||||
<workspace-layout>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: 3,
|
||||
align: (auto,auto,auto,),
|
||||
table.header([Workspace], [Monitor], [Position],),
|
||||
table.hline(),
|
||||
[1], [DP-1 (TERRA 3280W)], [Left, 2560x1440\@144],
|
||||
[2], [DVI-D-1 (BenQ BL2405)], [Center, 1920x1080\@60 rotated 90deg],
|
||||
[3], [HDMI-A-1 (TERRA 3280W)], [Right, 2560x1440\@144],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
|
||||
== Notes
|
||||
<notes>
|
||||
- `SUPER+CTRL+K` (keybind menu) does #strong[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.
|
||||
|
||||
== Troubleshooting
|
||||
<troubleshooting>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: 2,
|
||||
align: (auto,auto,),
|
||||
table.header([Issue], [Check],),
|
||||
table.hline(),
|
||||
[Print key not working], [`hyprctl binds | grep Print`],
|
||||
[`SUPER+R` not working], [`hyprctl binds | grep 'reload'`],
|
||||
[Screenshots fail], [`which grimblast satty slurp grim`],
|
||||
[Clipboard empty], [`echo test | wl-copy && wl-paste`],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
|
||||
== Satty Editor Shortcuts
|
||||
<satty-editor-shortcuts>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: 2,
|
||||
align: (auto,auto,),
|
||||
table.header([Action], [Shortcut],),
|
||||
table.hline(),
|
||||
[Freehand], [F],
|
||||
[Rectangle], [R],
|
||||
[Ellipse], [E],
|
||||
[Line/Arrow], [L],
|
||||
[Text], [T],
|
||||
[Blur], [B],
|
||||
[Crop], [C],
|
||||
[Undo], [Ctrl+Z],
|
||||
[Redo], [Ctrl+Shift+Z],
|
||||
[Copy], [Ctrl+C],
|
||||
[Save], [Ctrl+S],
|
||||
[Quit], [Esc or Ctrl+Q],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,193 @@
|
||||
= Hyprland Keybindings --- Full Cheatsheet
|
||||
<hyprland-keybindings-full-cheatsheet>
|
||||
#strong[Layout:] German (de) | #strong[Mod key:] SUPER (Windows key) |
|
||||
#strong[Source:] `hyprctl binds` live
|
||||
|
||||
#horizontalrule
|
||||
|
||||
== General
|
||||
<general>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: (37.04%, 29.63%, 33.33%),
|
||||
align: (auto,auto,auto,),
|
||||
table.header([Shortcut], [Action], [Details],),
|
||||
table.hline(),
|
||||
[`SUPER+Q`], [Open terminal], [kitty],
|
||||
[`SUPER+C`], [Kill active window], [killactive],
|
||||
[`SUPER+M`], [Exit Hyprland], [exit],
|
||||
[`SUPER+E`], [File manager], [dolphin],
|
||||
[`SUPER+V`], [Toggle floating], [togglefloating],
|
||||
[`SUPER+D`], [App launcher], [hyprlauncher],
|
||||
[`SUPER+P`], [Toggle pseudo-split], [pseudo],
|
||||
[`SUPER+J`], [Toggle split direction], [layoutmsg togglesplit],
|
||||
[`SUPER+F`], [Toggle fullscreen], [fullscreen 0],
|
||||
[`SUPER+R`], [Reload config], [hyprctl reload],
|
||||
[`SUPER+SPACE`], [Toggle float (popup)], [togglefloating],
|
||||
[`SUPER+TAB`], [Cycle next window], [cyclenext],
|
||||
[`SUPER+SHIFT+TAB`], [Cycle previous window], [cyclenext prev],
|
||||
[`SUPER+` #raw("`") (Grave)], [Toggle
|
||||
scratchpad], [togglespecialworkspace],
|
||||
[`SUPER+SHIFT+` #raw("`");], [Send window to
|
||||
scratchpad], [movetoworkspace special],
|
||||
[`SUPER+CTRL+K`], [Keybind cheat menu], [#emph[(broken --- pipelines
|
||||
to grep+fuzzel)];],
|
||||
[`SUPER+CTRL+L`], [Lock screen], [hyprlock],
|
||||
[`SUPER+B`], [Web browser], [google-chrome-stable],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
|
||||
== Navigation / Focus
|
||||
<navigation-focus>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: 2,
|
||||
align: (auto,auto,),
|
||||
table.header([Shortcut], [Action],),
|
||||
table.hline(),
|
||||
[`SUPER+TAB`], [Cycle next window],
|
||||
[`SUPER+SHIFT+TAB`], [Cycle previous window],
|
||||
[`SUPER+H`], [Move focus left],
|
||||
[`SUPER+L`], [Move focus right],
|
||||
[`SUPER+left arrow`], [Move focus left],
|
||||
[`SUPER+right arrow`], [Move focus right],
|
||||
[`SUPER+up arrow`], [Move focus up],
|
||||
[`SUPER+down arrow`], [Move focus down],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
|
||||
== Window Movement
|
||||
<window-movement>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: 2,
|
||||
align: (auto,auto,),
|
||||
table.header([Shortcut], [Action],),
|
||||
table.hline(),
|
||||
[`SUPER+SHIFT+H`], [Move window left],
|
||||
[`SUPER+SHIFT+L`], [Move window right],
|
||||
[`SUPER+SHIFT+K`], [Move window up],
|
||||
[`SUPER+SHIFT+J`], [Move window down],
|
||||
[`SUPER+SHIFT+left`], [Move window left],
|
||||
[`SUPER+SHIFT+right`], [Move window right],
|
||||
[`SUPER+SHIFT+up`], [Move window up],
|
||||
[`SUPER+SHIFT+down`], [Move window down],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
|
||||
== Window Resize
|
||||
<window-resize>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: 2,
|
||||
align: (auto,auto,),
|
||||
table.header([Shortcut], [Action],),
|
||||
table.hline(),
|
||||
[`SUPER+ALT+H`], [Resize active: width -40],
|
||||
[`SUPER+ALT+L`], [Resize active: width +40],
|
||||
[`SUPER+ALT+K`], [Resize active: height -40],
|
||||
[`SUPER+ALT+J`], [Resize active: height +40],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
|
||||
== Screenshots
|
||||
<screenshots>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: (41.67%, 33.33%, 25%),
|
||||
align: (auto,auto,auto,),
|
||||
table.header([Shortcut], [Action], [Tool],),
|
||||
table.hline(),
|
||||
[`Print`], [Area select → save + copy], [grimblast copysave area],
|
||||
[`SUPER+Print`], [Active window → save + copy], [grimblast copysave
|
||||
active],
|
||||
[`SUPER+SHIFT+Print`], [Full monitor → save + copy], [grimblast
|
||||
copysave output],
|
||||
[`SUPER+ALT+Print`], [Area select → annotate], [grim],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
|
||||
== Workspaces
|
||||
<workspaces>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: (55.56%, 44.44%),
|
||||
align: (auto,auto,),
|
||||
table.header([Shortcut], [Action],),
|
||||
table.hline(),
|
||||
[`SUPER+1..9`], [Switch to workspace 1-9],
|
||||
[`SUPER+SHIFT+1..9`], [Move active window to workspace 1-9],
|
||||
[`SUPER+` #raw("`");], [Toggle scratchpad (togglespecialworkspace)],
|
||||
[`SUPER+SHIFT+` #raw("`");], [Move to scratchpad (movetoworkspace
|
||||
special)],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
|
||||
=== Workspace Layout
|
||||
<workspace-layout>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: 3,
|
||||
align: (auto,auto,auto,),
|
||||
table.header([Workspace], [Monitor], [Position],),
|
||||
table.hline(),
|
||||
[1], [DP-1 (TERRA 3280W)], [Left, 2560x1440\@144],
|
||||
[2], [DVI-D-1 (BenQ BL2405)], [Center, 1920x1080\@60 rotated 90deg],
|
||||
[3], [HDMI-A-1 (TERRA 3280W)], [Right, 2560x1440\@144],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
|
||||
== Notes
|
||||
<notes>
|
||||
- `SUPER+CTRL+K` (keybind menu) does #strong[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.
|
||||
|
||||
== Troubleshooting
|
||||
<troubleshooting>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: 2,
|
||||
align: (auto,auto,),
|
||||
table.header([Issue], [Check],),
|
||||
table.hline(),
|
||||
[Print key not working], [`hyprctl binds | grep Print`],
|
||||
[`SUPER+R` not working], [`hyprctl binds | grep 'reload'`],
|
||||
[Screenshots fail], [`which grimblast satty slurp grim`],
|
||||
[Clipboard empty], [`echo test | wl-copy && wl-paste`],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
|
||||
== Satty Editor Shortcuts
|
||||
<satty-editor-shortcuts>
|
||||
#figure(
|
||||
align(center)[#table(
|
||||
columns: 2,
|
||||
align: (auto,auto,),
|
||||
table.header([Action], [Shortcut],),
|
||||
table.hline(),
|
||||
[Freehand], [F],
|
||||
[Rectangle], [R],
|
||||
[Ellipse], [E],
|
||||
[Line/Arrow], [L],
|
||||
[Text], [T],
|
||||
[Blur], [B],
|
||||
[Crop], [C],
|
||||
[Undo], [Ctrl+Z],
|
||||
[Redo], [Ctrl+Shift+Z],
|
||||
[Copy], [Ctrl+C],
|
||||
[Save], [Ctrl+S],
|
||||
[Quit], [Esc or Ctrl+Q],
|
||||
)]
|
||||
, kind: table
|
||||
)
|
||||
Binary file not shown.
@@ -0,0 +1,126 @@
|
||||
---
|
||||
title: "Hyprland Keybindings Cheatsheet"
|
||||
author: "mw-pfeddersheim-workstation"
|
||||
date: "May 2026"
|
||||
---
|
||||
|
||||
# Hyprland Keybindings — Full Cheatsheet
|
||||
|
||||
**Layout:** German (de) | **Mod key:** SUPER (Windows key) | **Source:** `hyprctl binds` live
|
||||
|
||||
---
|
||||
|
||||
## General
|
||||
|
||||
| Shortcut | Action | Details |
|
||||
|----------|--------|---------|
|
||||
| `SUPER+Q` | Open terminal | kitty |
|
||||
| `SUPER+C` | Kill active window | killactive |
|
||||
| `SUPER+M` | Exit Hyprland | exit |
|
||||
| `SUPER+E` | File manager | dolphin |
|
||||
| `SUPER+V` | Toggle floating | togglefloating |
|
||||
| `SUPER+D` | App launcher | hyprlauncher |
|
||||
| `SUPER+P` | Toggle pseudo-split | pseudo |
|
||||
| `SUPER+J` | Toggle split direction | layoutmsg togglesplit |
|
||||
| `SUPER+F` | Toggle fullscreen | fullscreen 0 |
|
||||
| `SUPER+R` | Reload config | hyprctl reload |
|
||||
| `SUPER+SPACE` | Toggle float (popup) | togglefloating |
|
||||
| `SUPER+TAB` | Cycle next window | cyclenext |
|
||||
| `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+L` | Lock screen | hyprlock |
|
||||
| `SUPER+B` | Web browser | google-chrome-stable |
|
||||
|
||||
## Navigation / Focus
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `SUPER+TAB` | Cycle next window |
|
||||
| `SUPER+SHIFT+TAB` | Cycle previous window |
|
||||
| `SUPER+H` | Move focus left |
|
||||
| `SUPER+L` | Move focus right |
|
||||
| `SUPER+left arrow` | Move focus left |
|
||||
| `SUPER+right arrow` | Move focus right |
|
||||
| `SUPER+up arrow` | Move focus up |
|
||||
| `SUPER+down arrow` | Move focus down |
|
||||
|
||||
## Window Movement
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `SUPER+SHIFT+H` | Move window left |
|
||||
| `SUPER+SHIFT+L` | Move window right |
|
||||
| `SUPER+SHIFT+K` | Move window up |
|
||||
| `SUPER+SHIFT+J` | Move window down |
|
||||
| `SUPER+SHIFT+left` | Move window left |
|
||||
| `SUPER+SHIFT+right` | Move window right |
|
||||
| `SUPER+SHIFT+up` | Move window up |
|
||||
| `SUPER+SHIFT+down` | Move window down |
|
||||
|
||||
## Window Resize
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `SUPER+ALT+H` | Resize active: width -40 |
|
||||
| `SUPER+ALT+L` | Resize active: width +40 |
|
||||
| `SUPER+ALT+K` | Resize active: height -40 |
|
||||
| `SUPER+ALT+J` | Resize active: height +40 |
|
||||
|
||||
## Screenshots
|
||||
|
||||
| Shortcut | Action | Tool |
|
||||
|----------|--------|------|
|
||||
| `Print` | Area select → save + copy | grimblast copysave area |
|
||||
| `SUPER+Print` | Active window → save + copy | grimblast copysave active |
|
||||
| `SUPER+SHIFT+Print` | Full monitor → save + copy | grimblast copysave output |
|
||||
| `SUPER+ALT+Print` | Area select → annotate | grim | slurp | satty |
|
||||
|
||||
## Workspaces
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `SUPER+1..9` | Switch to workspace 1-9 |
|
||||
| `SUPER+SHIFT+1..9` | Move active window to workspace 1-9 |
|
||||
| `SUPER+` `` ` `` | Toggle scratchpad (togglespecialworkspace) |
|
||||
| `SUPER+SHIFT+` `` ` `` | Move to scratchpad (movetoworkspace special) |
|
||||
|
||||
### Workspace Layout
|
||||
|
||||
| Workspace | Monitor | Position |
|
||||
|-----------|---------|----------|
|
||||
| 1 | DP-1 (TERRA 3280W) | Left, 2560x1440@144 |
|
||||
| 2 | DVI-D-1 (BenQ BL2405) | Center, 1920x1080@60 rotated 90deg |
|
||||
| 3 | HDMI-A-1 (TERRA 3280W) | Right, 2560x1440@144 |
|
||||
|
||||
## 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.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Check |
|
||||
|-------|-------|
|
||||
| Print key not working | `hyprctl binds | grep Print` |
|
||||
| `SUPER+R` not working | `hyprctl binds | grep 'reload'` |
|
||||
| Screenshots fail | `which grimblast satty slurp grim` |
|
||||
| Clipboard empty | `echo test | wl-copy && wl-paste` |
|
||||
|
||||
## Satty Editor Shortcuts
|
||||
|
||||
| Action | Shortcut |
|
||||
|--------|----------|
|
||||
| Freehand | F |
|
||||
| Rectangle | R |
|
||||
| Ellipse | E |
|
||||
| Line/Arrow | L |
|
||||
| Text | T |
|
||||
| Blur | B |
|
||||
| Crop | C |
|
||||
| Undo | Ctrl+Z |
|
||||
| Redo | Ctrl+Shift+Z |
|
||||
| Copy | Ctrl+C |
|
||||
| Save | Ctrl+S |
|
||||
| Quit | Esc or Ctrl+Q |
|
||||
@@ -0,0 +1,7 @@
|
||||
#set document(title: "Hyprland Keybindings - Pandoc")
|
||||
#set text(font: ("Inter", "DejaVu Sans"), size: 9pt, lang: "de")
|
||||
#set page(paper: "a4", margin: 1.2cm)
|
||||
#set heading(numbering: "1.")
|
||||
#set par(leading: 0.45em)
|
||||
#show link: set text(navy)
|
||||
#let horizontalrule = h(25%) + line(stroke: 0.5pt, length: 50%)
|
||||
@@ -0,0 +1,18 @@
|
||||
#set document(title: $if(title)$$title$$else$""$endif$)
|
||||
#set text(font: ($if(mainfont)$"$mainfont"$else$"Inter"$endif$, "DejaVu Sans"), size: $if(fontsize)$$fontsize$$else$9pt$endif$, lang: $if(lang)$$lang$$else$en$endif$)
|
||||
#set page(paper: "a4", margin: $if(margin-top)$($margin-top$,$else$1.5cm,$endif$ $if(margin-bottom)$$margin-bottom$,$else$1.5cm,$endif$ $if(margin-left)$$margin-left$,$else$1.5cm,$endif$ $if(margin-right)$$margin-right,$else$1.5cm$endif$)$else$1.5cm$endif$)
|
||||
$if(number-sections)$#set heading(numbering: "1.")$endif$
|
||||
|
||||
$for(include-before)$
|
||||
$include-before$
|
||||
|
||||
$endfor$
|
||||
|
||||
$body$
|
||||
|
||||
$for(include-after)$
|
||||
$include-after$
|
||||
|
||||
$endfor$
|
||||
|
||||
$if(quotes)$#show link.label => { box(text(str: #quotes.open) + content + text(str: #quotes.close)) }$endif$
|
||||
Binary file not shown.
@@ -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).
|
||||
@@ -1,13 +1,87 @@
|
||||
# Next Steps - 2026-03-17
|
||||
# Next Steps - 2026-05-21
|
||||
|
||||
## High Priority
|
||||
- [ ] Monitor system behavior under multi-day sustained load to ensure `zswap` and `MGLRU` keep the system responsive.
|
||||
- [ ] Verify that `archlinux-keyring-wkd-sync.service` failure is resolved or investigate further (currently noted in health report).
|
||||
## Immediate (Post-Reboot - Password Management Migration)
|
||||
|
||||
## Optimization
|
||||
> Gnome-keyring + Bitwarden migration applied 2026-05-21. Verifying PAM unlock + D-Bus secrets service after reboot.
|
||||
|
||||
### Verification Checklist
|
||||
|
||||
- [ ] **Gnome-keyring auto-start**: `systemctl --user is-active gnome-keyring-daemon.service` → `active`
|
||||
- [ ] **Rbw vault access**: `rbw unlocked && rbw list | head -5`
|
||||
- [ ] **GK secret lookups work**:
|
||||
```bash
|
||||
secret-tool lookup application ssh-agent service ssh ssh-key /home/mw/.ssh/id_ed25519
|
||||
secret-tool lookup application git service gitlab.satware.com
|
||||
secret-tool lookup application nextcloud service cloud.satware.com user mw
|
||||
```
|
||||
|
||||
### Manual Steps Required After Reboot
|
||||
|
||||
**1. himalaya IMAP passwords** — Were NOT in KWallet, need manual entry.
|
||||
Look up each password in https://bitwarden.jantec.xyz/ then store in GK:
|
||||
|
||||
```bash
|
||||
# MW account (mw@satware.com)
|
||||
echo "<PASSWORD>" | secret-tool store --label="IMAP MW" email mw@satware.com application himalaya
|
||||
# JA account (ja@satware.ai)
|
||||
echo "<PASSWORD>" | secret-tool store --label="IMAP JA" email ja@satware.ai application himalaya
|
||||
# JA Gmail (ja.satware@gmail.com)
|
||||
echo "<PASSWORD>" | secret-tool store --label="IMAP JA Gmail" email ja.satware@gmail.com application himalaya
|
||||
# Michael Gmail (ironmikechw@gmail.com)
|
||||
echo "<PASSWORD>" | secret-tool store --label="IMAP Michael" email ironmikechw@gmail.com application himalaya
|
||||
```
|
||||
|
||||
**2. GitHub CLI** — Re-auth if token is stale:
|
||||
|
||||
```bash
|
||||
gh auth login --hostname github.com
|
||||
```
|
||||
|
||||
Then store token in GK
|
||||
|
||||
```bash
|
||||
rbw get "GitHub Personal Token" --full | secret-tool store --label="GitHub Token" application gh-cli service github.com
|
||||
```
|
||||
|
||||
### Verification Tests
|
||||
|
||||
| Area | Test Command | Expected Result |
|
||||
|------|-------------|-----------------|
|
||||
| Gnome-keyring | `systemctl --user is-active gnome-keyring-daemon.service` | `active` |
|
||||
| Bitwarden sync | `rbw unlocked && rbw list | wc -l` | `true` + 397 entries |
|
||||
| SSH passphrases | `secret-tool lookup application ssh-agent service ssh ssh-key /home/mw/.ssh/id_ed25519` | Returns "SSH gitlab.satware.com" |
|
||||
| GitLab PAT | `secret-tool lookup application git service gitlab.satware.com` | Returns PAT |
|
||||
| GitHub CLI | `gh auth status -h github.com` | Valid token (or needs re-auth) |
|
||||
| himalaya | `himalaya -a mw check` | SMTP/IMAP OK |
|
||||
| Git remotes | `git fetch` on any tracked repo | No password prompt |
|
||||
| JetBrains IDEs | Open CLion/WebStorm/IDEA | No credential prompts (auto-detects D-Bus secrets) |
|
||||
|
||||
### Cleanup (After All Verified Working)
|
||||
|
||||
```bash
|
||||
sudo pacman -Rns kwallet kwalletmanager
|
||||
rm -rf ~/.local/share/kwalletd/
|
||||
```
|
||||
|
||||
---
|
||||
*Last updated: 2026-05-21 | Password management migration complete. Post-reboot verification pending.*
|
||||
|
||||
---
|
||||
|
||||
## Short Term (Non-Reboot)
|
||||
|
||||
- [ ] Verify that `archlinux-keyring-wkd-sync.service` failure is resolved or investigate further.
|
||||
- [ ] Evaluate if `systemd-oomd` is aggressive enough for interactive desktop use; if not, consider testing `earlyoom` as an alternative.
|
||||
- [ ] Automate periodic S.M.A.R.T. checks via a systemd timer (currently manual).
|
||||
- [ ] Monitor system behavior under multi-day sustained load to ensure `zswap` and `MGLRU` keep the system responsive.
|
||||
|
||||
## Infrastructure
|
||||
|
||||
- [ ] Consolidate Ansible variables for Docker memory limits into `ansible/vars/main.yml`.
|
||||
- [ ] Extend `scripts/maintenance.sh` to include a summary of the last 24h OOM events (if any).
|
||||
- [ ] Verify that `archlinux-keyring-wkd-sync.service` failure is resolved or investigate further.
|
||||
|
||||
---
|
||||
*See `docs/product/passwords-migration-checklist.md` for detailed post-reboot actions.*
|
||||
*See `docs/product/passwords.md` for the password management architecture overview.*
|
||||
*See `docs/adr/password-management-migration.md` for the Architecture Decision Record.*
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
# Password Management Infrastructure
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Bitwarden Vault ───────────╮ │
|
||||
│ bitwarden.jantec.xyz │ Central store │
|
||||
│ (cloud) │ + backup │
|
||||
│ │ │
|
||||
│ ┌─────────────────┐ │ │
|
||||
│ │ rbw CLI ─────┼─── sync/login ───┘ │
|
||||
│ │ (Rust client) │ Add/edit/search │
|
||||
│ └────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────V──────── ┌──────────────────┐ │
|
||||
│ │ gnome-keyring │────────────►│ Applications │ │
|
||||
│ │ (libsecret) │ D-Bus │ himalaya, git, │ │
|
||||
│ │ org.freedesktop.│ Secrets API │ SSH, browsers, │ │
|
||||
│ │ secrets │ │ IDEs │ │
|
||||
│ └─────────────────┘ └──────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### 1. gnome-keyring (local D-Bus secret service)
|
||||
|
||||
- Provides `org.freedesktop.secrets` D-Bus interface
|
||||
- Started as systemd user service via socket activation
|
||||
- Unlocks on login (PAM integration with greetd/tuigreet)
|
||||
- Stores credentials that apps access via `libsecret` (`secret-tool`)
|
||||
|
||||
| Unit | Status |
|
||||
|------|--------|
|
||||
| `~/.config/systemd/user/gnome-keyring-daemon.socket` | enabled |
|
||||
| `~/.config/systemd/user/gnome-keyring-daemon.service` | enabled |
|
||||
|
||||
### 2. rbw (Bitwarden CLI)
|
||||
|
||||
- Unofficial Bitwarden CLI written in Rust
|
||||
- Connects to `https://bitwarden.jantec.xyz`
|
||||
- Uses gnome-keyring as its encrypted password store (via `pinentry`)
|
||||
- Manages vault: `rbw add`, `rbw get`, `rbw list`, `rbw search`, `rbw sync`
|
||||
|
||||
| Config | Value |
|
||||
|--------|-------|
|
||||
| `~/.config/rbw/config.json` | `{"email": "ironmikechw+bitwarden@gmail.com", "base_url": "https://bitwarden.jantec.xyz"}` |
|
||||
| API Keys | Stored in `~/.config/rbw/` (registered via `rbw register`) |
|
||||
| Vault sync | Automatic via `rbw sync` (interval: 3600s) |
|
||||
|
||||
### 3. SSH Key Passphrases
|
||||
|
||||
- Stored in gnome-keyring (libsecret) for `ssh-add` via `gnome-keyring-ssh` PAM module
|
||||
- Passphrases stored under `application=ssh-agent`, `service=ssh`, `ssh-key=<path>`
|
||||
- Keys migrated from KWallet: `id_ed25519`, `id_ed25519_ja_manjaro`
|
||||
|
||||
### 4. Git Credentials
|
||||
|
||||
- **Global helper**: `libsecret` (stores in gnome-keyring instead of `~/.git-credentials` plaintext)
|
||||
- **GitHub**: `gh auth git-credential` helper (for github.com)
|
||||
- **GitLab**: configured as `provider = generic` in `.gitconfig` for gitlab.satware.com
|
||||
|
||||
### 5. Application Credentials Map
|
||||
|
||||
| Application | Storage | Mechanism |
|
||||
|-----------|---------|-----------|
|
||||
| himalaya (email) | gnome-keyring + Bitwarden | `secret-tool lookup email <address>` |
|
||||
| ssh-agent | gnome-keyring | D-Bus secret service |
|
||||
| git (global) | gnome-keyring (`libsecret`) | `git config credential.helper libsecret` |
|
||||
| GitHub CLI | Bitwarden + gnome-keyring | `rbw get "GitHub Personal Token"` + GK entry |
|
||||
| GitLab | gnome-keyring | `secret-tool lookup application git service gitlab.satware.com` |
|
||||
| Chrome/Brave/Chromium | gnome-keyring | Safe Storage encryption keys |
|
||||
| JetBrains IDEs | gnome-keyring | D-Bus secret service auto-detection |
|
||||
| Remmina | gnome-keyring + Bitwarden | VNC/RDP passwords |
|
||||
| Nextcloud | gnome-keyring + Bitwarden | Instance passwords |
|
||||
| QGIS | gnome-keyring + Bitwarden | Master password |
|
||||
|
||||
## Startup / Unlock Flow
|
||||
|
||||
```
|
||||
1. greetd/tuigreet login → PAM auth
|
||||
2. PAM → gnome-keyring PAM module (pam_gnome_keyring.so)
|
||||
3. Keyring unlocked with login password
|
||||
4. gnome-keyring-daemon starts (socket activation)
|
||||
5. D-Bus org.freedesktop.secrets available
|
||||
6. rbw login → decrypts vault via gnome-keyring
|
||||
7. himalaya can read IMAP passwords via secret-tool
|
||||
8. ssh-add can read SSH passphrases via GK
|
||||
9. git-credential-libsecret reads Git creds via GK
|
||||
```
|
||||
|
||||
## Migration History
|
||||
|
||||
- **Before**: KDE Wallet (kwalletd) — no auto-unlock under greetd, no D-Bus support for non-Plasma apps
|
||||
- **After**: gnome-keyring + rbw + Bitwarden.vault → full D-Bus secret service + central vault + CLI access
|
||||
|
||||
## Manual Entry Required
|
||||
|
||||
### himalaya Email Passwords
|
||||
|
||||
The following email account passwords need to be stored in gnome-keyring (they were NOT in KWallet):
|
||||
|
||||
```bash
|
||||
# Store MW account
|
||||
echo "<PASSWORD>" | secret-tool store --label="IMAP MW" email mw@satware.com application himalaya
|
||||
|
||||
# Store JA account
|
||||
echo "<PASSWORD>" | secret-tool store --label="IMAP JA" email ja@satware.ai application himalaya
|
||||
|
||||
# Store JA Gmail
|
||||
echo "<PASSWORD>" | secret-tool store --label="IMAP JA Gmail" email ja.satware@gmail.com application himalaya
|
||||
|
||||
# Store Michael Gmail
|
||||
echo "<PASSWORD>" | secret-tool store --label="IMAP Michael Gmail" email ironmikechw@gmail.com application himalaya
|
||||
```
|
||||
|
||||
## Useful Commands
|
||||
|
||||
```bash
|
||||
# Bitwarden operations
|
||||
rbw list # list all vault entries
|
||||
rbw search <term> # search vault
|
||||
rbw get <name> # get password (truncates on display)
|
||||
rbw get --full "GitHub Personal Token" # get full password (piped)
|
||||
rbw sync # sync with vault
|
||||
rbw add "Service Name" # add new entry (opens $EDITOR)
|
||||
|
||||
# gnome-keyring operations
|
||||
secret-tool lookup email mw@satware.com # lookup by attribute
|
||||
secret-tool search application ssh-agent # search
|
||||
secret-tool store --label="Test" key value # store (pipe data)
|
||||
dbus-send --session --print-reply --dest=org.freedesktop.secrets \
|
||||
/org/freedesktop/secrets/collection/default # low-level D-Bus
|
||||
|
||||
# Verify startup
|
||||
systemctl --user status gnome-keyring-daemon.socket
|
||||
systemctl --user status gnome-keyring-daemon.service
|
||||
|
||||
# Quick health check
|
||||
rbw unlocked && echo "BW: unlocked" || echo "BW: locked"
|
||||
secret-tool lookup application ssh-agent service ssh ssh-key /home/mw/.ssh/id_ed25519 && echo "GK: SSH OK"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### gnome-keyring not providing secrets on D-Bus
|
||||
```bash
|
||||
systemctl --user restart gnome-keyring-daemon.service
|
||||
```
|
||||
|
||||
### rbw not unlocked
|
||||
```bash
|
||||
rbw unlock # prompts for login password
|
||||
rbw login # prompts for Bitwarden password
|
||||
```
|
||||
|
||||
### SSH keys not auto-unlocking
|
||||
```bash
|
||||
# Check GK entry
|
||||
secret-tool lookup application ssh-agent service ssh ssh-key /home/mw/.ssh/id_ed25519
|
||||
# If missing, re-add:
|
||||
echo "<PASSPHRASE>" | secret-tool store --label="SSH id_ed25519" \
|
||||
application ssh-agent service ssh ssh-key /home/mw/.ssh/id_ed25519
|
||||
```
|
||||
|
||||
### GitLab/GitHub creds not working
|
||||
```bash
|
||||
# Test GK
|
||||
secret-tool lookup application git service gitlab.satware.com
|
||||
```
|
||||
|
||||
### himalaya password errors
|
||||
```bash
|
||||
# Verify GK
|
||||
secret-tool lookup email mw@satware.com
|
||||
# Re-enter password from Bitwarden
|
||||
rbw get "himala mw@satware.com" | secret-tool store --label="IMAP mw@satware.com" \
|
||||
email mw@satware.com application himalaya
|
||||
```
|
||||
|
||||
## Security Note
|
||||
|
||||
- Bitwarden vault: self-hosted at `bitwarden.jantec.xyz`
|
||||
- Local cache: gnome-keyring (encrypted, session-bound)
|
||||
- No plaintext passwords in config files
|
||||
- `~/.git-credentials` removed on `git config --global credential.helper store`
|
||||
@@ -0,0 +1,91 @@
|
||||
# Password Management - Migration Checklist
|
||||
|
||||
## Pre-Reboot Status (2026-05-21)
|
||||
|
||||
- [x] gnome-keyring installed (`1:50.0-1`)
|
||||
- [x] rbw installed (`1.15.0`)
|
||||
- [x] gnome-keyring-daemon.socket + service enabled + active
|
||||
- [x] rbw configured: `bitwarden.jantec.xyz`, `ironmikechw+bitwarden@gmail.com`
|
||||
- [x] 397 entries synced from Bitwarden vault
|
||||
- [x] 45 entries migrated from KWallet → BW + GK
|
||||
- [x] Duplicate BW entries cleaned (22 removed)
|
||||
- [x] Git credential helper: `libsecret`
|
||||
- [x] himalaya configs using `secret-tool lookup email <addr>`
|
||||
- [x] GitHub token stored in BW + GK
|
||||
- [x] GitLab PAT stored in BW + GK
|
||||
- [x] SSH passphrases stored in BW + GK
|
||||
- [x] Browser safe storage (Chrome/Brave/Chromium) in BW + GK
|
||||
- [x] Remmina passwords (5) in BW + GK
|
||||
- [x] Nextcloud passwords (3) in BW + GK
|
||||
- [x] QGIS Master Password in BW + GK
|
||||
- [x] JetBrains AI Token in BW + GK
|
||||
- [x] Documentation written: `docs/product/passwords.md` + `docs/adr/password-management-migration.md`
|
||||
- [x] AGENTS.md updated with secrets management reference
|
||||
- [x] KDE Wallet data file backed up (not deleted)
|
||||
- [ ] KDE Wallet packages removal (post-verify)
|
||||
|
||||
## Post-Reboot Actions
|
||||
|
||||
### Immediate Checks
|
||||
```bash
|
||||
# 1. Verify gnome-keyring started
|
||||
systemctl --user is-active gnome-keyring-daemon.service
|
||||
|
||||
# 2. Verify rbw can access vault
|
||||
rbw list | head -5
|
||||
|
||||
# 3. Verify GK entries
|
||||
secret-tool lookup application ssh-agent service ssh ssh-key /home/mw/.ssh/id_ed25519
|
||||
secret-tool lookup application git service gitlab.satware.com
|
||||
secret-tool lookup application nextcloud service cloud.satware.com user mw
|
||||
|
||||
# 4. Check himalaya
|
||||
himalaya account list
|
||||
himalaya -a mw check 2>&1 # tests IMAP auth
|
||||
```
|
||||
|
||||
### Manual Steps Required
|
||||
1. **himalaya passwords** — 4 accounts need passwords re-entered into GK:
|
||||
```bash
|
||||
for addr in mw@satware.com ja@satware.ai ja.satware@gmail.com ironmikechw@gmail.com; do
|
||||
echo "$(rbw get "$addr" | head -1)" | secret-tool store --label="IMAP $addr" email "$addr" application himalaya
|
||||
done
|
||||
```
|
||||
(Or manually look up passwords from BW web UI and enter them)
|
||||
|
||||
2. **GitHub CLI** — Re-login if token stale:
|
||||
```bash
|
||||
gh auth login --hostname github.com --with-token
|
||||
```
|
||||
Then store in GK:
|
||||
```bash
|
||||
rbw get "GitHub Personal Token" | secret-tool store --label="GitHub Token" application gh-cli service github.com
|
||||
```
|
||||
|
||||
### KDE Wallet Cleanup (After Confirmation)
|
||||
```bash
|
||||
# Only after everything works:
|
||||
sudo pacman -Rns kwallet kwalletmanager
|
||||
rm -rf ~/.local/share/kwalletd/
|
||||
```
|
||||
|
||||
## Rollback (If Needed)
|
||||
|
||||
If gnome-keyring + rbw doesn't work properly after reboot:
|
||||
```bash
|
||||
# Restore git credential helper
|
||||
git config --global credential.helper store
|
||||
|
||||
# Kill gnome-keyring
|
||||
systemctl --user stop gnome-keyring-daemon.socket
|
||||
|
||||
# Restore KWallet (if removed)
|
||||
sudo pacman -S kwallet kwalletmanager
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- All migration data preserved in `~/.local/share/kwalletd/` (not deleted yet)
|
||||
- KWallet PAM refs were already commented out in `/etc/pam.d/*`
|
||||
- JetBrains IDEs will auto-detect `org.freedesktop.secrets` from gnome-keyring; no config change needed
|
||||
- Browser safe storage keys migrated — browsers will use them automatically
|
||||
@@ -0,0 +1,128 @@
|
||||
# Password Management - System Guide
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Bitwarden Vault │
|
||||
│ bitwarden.jantec.xyz │ ← Central, synced, backed up
|
||||
│ (self-hosted) │
|
||||
│ ▲ │
|
||||
│ rbw CLI │ ← CLI access, add/list/get/search
|
||||
│ (ironmikechw+bitwarden@gmail.com)│
|
||||
│ ▲ │
|
||||
│ gnome-keyring (local) │ ← D-Bus secrets API for apps
|
||||
│ org.freedesktop.secrets │ ← libsecret, secret-tool
|
||||
└─────────────────────────────────────
|
||||
```
|
||||
|
||||
## How It Works After Reboot / Login
|
||||
|
||||
1. **greetd/tuigreet login** → PAM auth
|
||||
2. **PAM module** → `pam_gnome_keyring.so` unlocks keyring with login password
|
||||
3. **gnome-keyring-daemon** starts via socket activation (systemd user service)
|
||||
4. **D-Bus** `org.freedesktop.secrets` becomes available on session bus
|
||||
5. **rbw** auto-unlocks via pinentry (reads passphrase from gnome-keyring)
|
||||
6. **Apps** read secrets via `libsecret` / `secret-tool`:
|
||||
- `himalaya` → IMAP/SMTP passwords via `secret-tool lookup email <addr>`
|
||||
- `ssh-agent` → SSH key passphrases via `secret-tool lookup ... ssh-key <path>`
|
||||
- `git-credential-libsecret` → Git passwords via libsecret
|
||||
(github.com via `gh`, gitlab.satware.com via `glab`, **git.satware.ai via libsecret PAT**)
|
||||
- `JetBrains IDEs` → auto-detect D-Bus secrets service
|
||||
|
||||
## Quick Verification
|
||||
|
||||
```bash
|
||||
# Is gnome-keyring running?
|
||||
systemctl --user is-active gnome-keyring-daemon.service
|
||||
|
||||
# Can rbw access the vault?
|
||||
rbw list | head -5
|
||||
rbw get "SSH Key: id_ed25519" # should return "SSH gitlab.satware.com"
|
||||
|
||||
# Can apps read secrets?
|
||||
secret-tool lookup application ssh-agent service ssh ssh-key /home/mw/.ssh/id_ed25519
|
||||
secret-tool lookup application git service gitlab.satware.com
|
||||
secret-tool lookup server git.satware.ai protocol https user mw # Gitea PAT (git-credential-libsecret schema)
|
||||
secret-tool lookup application nextcloud service cloud.satware.com user mw
|
||||
```
|
||||
|
||||
## Migrated Entries (from KDE Wallet)
|
||||
|
||||
| Category | Entries | Storage |
|
||||
|----------|---------|---------|
|
||||
| **SSH passphrases** | id_ed25519, id_ed25519_ja_manjaro | BW + GK |
|
||||
| **SSH host passwords** | b0t@192.168.0.141 | BW + GK |
|
||||
| **Git tokens** | GitLab PAT, GitHub token, Gitea PAT (git.satware.ai) | GK (BW pending) |
|
||||
| **Remmina** | 5 VNC/RDP passwords | BW + GK |
|
||||
| **Nextcloud** | 3 instance passwords | BW + GK |
|
||||
| **Browser crypto** | Chrome, Brave, Chromium safe storage | BW + GK |
|
||||
| **JetBrains** | AI token, Account token | BW (AI also in GK) |
|
||||
| **QGIS** | Master password | BW + GK |
|
||||
|
||||
Total: ~45 entries migrated.
|
||||
|
||||
## Known Issues (Post-Reboot)
|
||||
|
||||
1. **himalaya email passwords** — Were NOT in KWallet. Need to be re-entered manually:
|
||||
```bash
|
||||
# Run once per account after reboot
|
||||
rbw get "<entry-name>" | secret-tool store --label="IMAP <name>" email <address> application himalaya
|
||||
```
|
||||
Accounts: `mw@satware.com`, `ja@satware.ai`, `ja.satware@gmail.com`, `ironmikechw@gmail.com`
|
||||
|
||||
2. **GitHub CLI token** — May need re-auth:
|
||||
```bash
|
||||
gh auth login --hostname github.com
|
||||
gh auth git-credential store --hostname github.com # saves to GK
|
||||
```
|
||||
|
||||
## Useful Commands
|
||||
|
||||
```bash
|
||||
# Bitwarden
|
||||
rbw list # list all entries
|
||||
rbw search <term> # search
|
||||
rbw get "<name>" # get password (truncated display)
|
||||
rbw get --full "<name>" # get full password
|
||||
rbw add "<name>" # add new entry (opens editor)
|
||||
rbw sync # force sync with server
|
||||
rbw unlock # unlock vault (prompts for login password)
|
||||
|
||||
# Gnome-keyring
|
||||
secret-tool lookup key value ... # lookup by attributes
|
||||
secret-tool search key value ... # search
|
||||
secret-tool store --label="X" key value # store (pipe password via stdin)
|
||||
|
||||
# System
|
||||
systemctl --user status gnome-keyring-daemon.service
|
||||
systemctl --user restart gnome-keyring-daemon.service # if D-Bus secrets missing
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Fix |
|
||||
|---------|-----|
|
||||
| `secret-tool lookup` returns empty | `systemctl --user restart gnome-keyring-daemon.service` |
|
||||
| `rbw list` fails | `rbw unlock` (enter login password) |
|
||||
| SSH key asks for passphrase | `secret-tool lookup application ssh-agent service ssh ssh-key /home/mw/.ssh/id_ed25519` — if empty, re-store it |
|
||||
| `gh` commands fail | `gh auth login --hostname github.com` |
|
||||
| himalaya auth errors | Re-enter IMAP passwords via `secret-tool store` (see Known Issues) |
|
||||
| Browser safe storage issues | Browsers recreate their own Safe Storage keys on startup |
|
||||
|
||||
## KDE Wallet Status
|
||||
|
||||
- Packages still installed (`kwallet`, `kwalletmanager`) — harmless, will be removed after verification
|
||||
- Data file preserved at `~/.local/share/kwalletd/kdewallet.kwl` (backup)
|
||||
- PAM references already commented out
|
||||
- No longer used by any application
|
||||
|
||||
## Gitea PAT (git.satware.ai)
|
||||
|
||||
- Cached in gnome-keyring via `git credential approve` (schema `org.git.Password`, attributes: `protocol=https, server=git.satware.ai, user=mw`)
|
||||
- Git reads it via `credential.helper=libsecret` (global; no host-specific `store` override)
|
||||
- tea reads its own copy from `~/.config/tea/config.yml` (tea has no `auth git-credential` subcommand; file hardened to `chmod 600`)
|
||||
- Scopes: `write:repository`, `read:user`. Token lacks admin scope - **rotation must be done via web UI** at https://git.satware.ai/user/settings/applications
|
||||
- TODO (rotate): create new PAT with `write:repository, read:user, write:admin`; store in Bitwarden entry `Gitea PAT: git.satware.ai` + keyring + tea; revoke old token via API (now possible with admin scope); update this section
|
||||
- TODO (Bitwarden): create Bitwarden entry `Gitea PAT: git.satware.ai` (username `mw`, uri `https://git.satware.ai`) - blocked on rbw pinentry not supporting non-interactive password entry; create manually via `rbw add` or Bitwarden web UI
|
||||
- Tracked in Gitea issue #1: https://git.satware.ai/infrastructure/mw-pfeddersheim-workstation/issues/1
|
||||
@@ -51,7 +51,7 @@ last_updated: '2026-04-02'
|
||||
| **Latest** | **GXA7801Q** (non-SED, `00B00`) |
|
||||
| Source | [GitHub: samsung-nvme-firmware](https://github.com/lolyinseo/samsung-nvme-firmware) |
|
||||
|
||||
**Status**: **Update applied 2026-04-02.** GXA7801Q is the correct firmware for non-SED PM9A1 drives (model `00B00`). Reboot pending to activate.
|
||||
**Status**: **Complete 2026-04-02.** GXA7801Q is the correct firmware for non-SED PM9A1 drives (model `00B00`). Activated and verified. See `nvme-benchmark-post-gxa7801q.md` for before/after comparison.
|
||||
|
||||
> **Important**: GXA7802Q is for SED drives (model `00B07`) only. Our non-SED drive (MZVL2512HCJQ-**00B00**) requires GXA7801Q.
|
||||
> The OVHCloud mirror only hosts GXA7802Q, which was rejected as "Invalid Firmware Image".
|
||||
@@ -101,7 +101,7 @@ sudo reboot
|
||||
nvme list
|
||||
```
|
||||
|
||||
**Update applied**: 2026-04-02 10:51 CET. All three nvme-cli steps succeeded. Reboot pending.
|
||||
**Update applied**: 2026-04-02 10:51 CET. All three nvme-cli steps succeeded. Reboot completed, GXA7801Q verified active. Random IOPS improved +12-16% post-update.
|
||||
|
||||
**Firmware file**: SHA256 `76ad2b0c35581cb71bd4c5a5fd358a15c4432aa38e9e983acc2349d2b24b5a11`
|
||||
|
||||
@@ -251,7 +251,7 @@ fwupdmgr update # Apply available updates
|
||||
|
||||
| # | Action | Risk | Downtime |
|
||||
|---|--------|------|----------|
|
||||
| 4 | ~~Update NVMe firmware to GXA7801Q~~ | ~~Medium (power loss risk)~~ | **Applied 2026-04-02, reboot pending** |
|
||||
| 4 | ~~Update NVMe firmware to GXA7801Q~~ | ~~Medium (power loss risk)~~ | **Complete 2026-04-02. GXA7801Q active. +12-16% random IOPS.** |
|
||||
|
||||
### No Action Needed
|
||||
|
||||
|
||||
@@ -100,10 +100,16 @@ last_updated: '2026-04-02'
|
||||
|-------|-------|
|
||||
| Model | NVIDIA GeForce GTX 1050 Ti |
|
||||
| VRAM | 4096 MiB |
|
||||
| Driver | 575.64.05 |
|
||||
| Driver | 580.159.03 (Wayland-compatible mainline — NVIDIA VA-API for Chrome H.264/HEVC/VP9 decode via libva-nvidia-driver) |
|
||||
| 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 `*-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).
|
||||
|
||||
## Network
|
||||
|
||||
### Physical
|
||||
|
||||
@@ -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-07-21'
|
||||
---
|
||||
|
||||
# Manual System Overrides
|
||||
@@ -22,4 +22,10 @@ This document tracks all persistent system changes implemented manually that are
|
||||
|
||||
| Date | Change | Rationale | Ansible Status |
|
||||
|------|--------|-----------|----------------|
|
||||
| 2026-07-21 | Added `agent0.localhost` (-> `reverse_proxy 192.168.0.141:8090`, `flush_interval -1`) in dedicated file `/etc/caddy/conf.d/agent0.localhost.conf`. First non-localhost backend (remote LAN host running a uvicorn app). Regenerated mkcert cert to add explicit `agent0.localhost` SAN; cert now has 20 SANs total, valid until 2028-10-21. Old cert/key backed up as `localhost{,-key}.pem.bak.<ts>`. Total `*.localhost` sites now 18. | User request to expose the remote agent backend on local HTTPS. Key learning: the `*.localhost` wildcard is NOT honored by curl/system trust even for single-label subdomains - `.localhost` is a special-use TLD (RFC 6761), so every subdomain needs an explicit SAN. This corrects the prior assumption that single-label subdomains were covered by the wildcard. | Same as parent Caddy row - candidate for Ansible role when issue #92 resolves |
|
||||
| 2026-07-12 | Regenerated mkcert cert at `/etc/caddy/localhost{,-key}.pem` to add explicit SANs for `platform.localhost`, `packages.localhost`, and `onlyoffice.docs.localhost` (the latter was pre-existing in Caddy config but missing from the cert, causing TLS validation failures). Cert now has 19 SANs total, valid until 2028-10-12. | `onlyoffice.docs.localhost` is a multi-label subdomain not covered by `*.localhost` wildcard. Required explicit SAN. | Same as parent Caddy row |
|
||||
| 2026-07-12 | Added `platform.localhost` (-> `localhost:10080`) and `packages.localhost` (-> `localhost:20080`) to `/etc/caddy/conf.d/localhost-dev.conf`. Docker port mappings: `~/internal/amicron-platform/docker-compose.override.yml` `10080:80`, `~/internal/satis/docker-compose.yml` nginx `20080:80`. Both upstreams are plain HTTP on high ports; Caddy terminates TLS. Also documented `onlyoffice.docs.localhost` (pre-existing in config, was undocumented). Total sites now 17. | Local HTTPS dev for amicron-platform (ERP), satis (Composer package repository), and shared Firebird 3.0.14 database. The three form the local E2E test triangle: `platform.localhost` (ERP) -> `packages.localhost` (Satis) -> Firebird (token validation). Mirrors the production `platform.auc.de` + `packages.auc.de` + shared Firebird setup. | Same as parent Caddy row - candidate for Ansible role when issue #92 resolves |
|
||||
| 2026-07-06 | Caddy reverse-proxy + mkcert TLS at `/etc/caddy/` (`Caddyfile` + `conf.d/localhost-dev.conf` + `localhost{,-key}.pem`) | Local HTTPS dev for 14 `*.localhost` sites (mcp, artifacts, onlyoffice, demo.satware.com clone, 10 placeholders). Caddy runs as systemd `caddy.service` on port 443. NOT managed by this repo - no `caddy` Ansible role exists. The `onlyoffice.localhost` site (-> `localhost:3847`) is emitting `context canceled` SSE aborts; see `docs/learnings/2026-07-06-caddy-onlyoffice-mcp-sse-investigation.md` and GitLab issue [satware/mcp/onlyoffice#92](https://gitlab.satware.com/satware/mcp/onlyoffice/-/work_items/92). | Candidate for Ansible role (`caddy` + `mkcert`); defer until issue #92 resolves to avoid masking root cause |
|
||||
| 2026-07-03 | Patched `remmina-plugin-rdp.so` in `~/.config/remmina/plugins/` | Remmina 1.4.43 crashes (SIGSEGV) on Wayland when `keymap` is set in a profile. Built from `~/external/remmina/` fork with NULL check fix. Alt plugin dir loads before system plugin. MR !2757 submitted upstream. See `docs/learnings/2026-07-03-remmina-wayland-crash-fix.md`. | Remove when upstream fix is released and packaged |
|
||||
| 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 |
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Memory — mw-pfeddersheim-workstation
|
||||
|
||||
> **Status**: 64 GiB migrated to `homeoffice` on 2026-07-13.
|
||||
|
||||
## Current Configuration
|
||||
|
||||
4x 8 GiB DDR4-3000 DIMMs = **32 GiB total**, dual-channel (A/B).
|
||||
|
||||
| Slot | Size | Type | Speed | Channel |
|
||||
|------|------|------|-------|---------|
|
||||
| DIMM_A1 | 8 GiB | DDR4 SDRAM | 3000 MT/s | BANK 0 |
|
||||
| DIMM_A2 | 8 GiB | DDR4 UDIMM | 3000 MT/s | BANK 1 |
|
||||
| DIMM_B1 | 8 GiB | DDR4 UDIMM | 3000 MT/s | BANK 2 |
|
||||
| DIMM_B2 | 8 GiB | DDR4 UDIMM | 3000 MT/s | BANK 3 |
|
||||
|
||||
Note: DIMM_A1 reports "Unknown" form factor — different brand from the other three DIMMs.
|
||||
|
||||
## Migration History
|
||||
|
||||
| Date | Change |
|
||||
|------|--------|
|
||||
| 2026-07-13 | Removed 4x 16 GiB DDR4-2400 DIMMs (64 GiB) and transferred to `homeoffice`. Installed 4x 8 GiB DDR4-3000 DIMMs taken from `homeoffice` in exchange. |
|
||||
@@ -0,0 +1,117 @@
|
||||
# NVMe Benchmark: Post-Firmware Update (GXA7801Q)
|
||||
|
||||
> **Date**: 2026-04-02 14:57 CET
|
||||
> **Device**: Samsung PM9A1 (MZVL2512HCJQ-00B00), 512GB
|
||||
> **Serial**: S675NF0R703667
|
||||
> **Firmware**: GXA7801Q (post-update, was GXA7401Q)
|
||||
> **OS**: Manjaro Linux, kernel 6.12, KDE 6.5.6
|
||||
> **NVMe slot**: /dev/nvme0n1 (root drive)
|
||||
|
||||
## SMART Health Data (Post-Update)
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Critical Warning | 0 (healthy) |
|
||||
| Temperature | 45°C (Sensor 2: 49°C) |
|
||||
| Available Spare | 100% |
|
||||
| Percentage Used | 19% |
|
||||
| Data Units Read | 17.98 TB |
|
||||
| Data Units Written | 94.22 TB |
|
||||
| Power Cycles | 884 |
|
||||
| Power On Hours | 4,930 h |
|
||||
| Media Errors | 0 |
|
||||
| Error Log Entries | 0 |
|
||||
|
||||
## fio Benchmark Results
|
||||
|
||||
### Sequential Read (128K block, QD32)
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Bandwidth | 8,214 MB/s |
|
||||
| IOPS | 65,716 |
|
||||
| Avg Latency | 487 µs |
|
||||
|
||||
### Sequential Write (128K block, QD32)
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Bandwidth | 4,093 MB/s |
|
||||
| IOPS | 32,747 |
|
||||
| Avg Latency | 977 µs |
|
||||
|
||||
### Random Read (4K block, QD32)
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| IOPS | 405,924 |
|
||||
| Avg Latency | 79 µs |
|
||||
|
||||
### Random Write (4K block, QD32)
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| IOPS | 337,313 |
|
||||
| Avg Latency | 95 µs |
|
||||
|
||||
### Random Read (4K block, QD1)
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| IOPS | 424,803 |
|
||||
| Avg Latency | 2 µs |
|
||||
|
||||
### Random Write (4K block, QD1)
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| IOPS | 355,268 |
|
||||
| Avg Latency | 3 µs |
|
||||
|
||||
## Comparison: GXA7401Q vs GXA7801Q
|
||||
|
||||
> **Note**: Sequential tests used different block sizes (pre: 4K, post: 128K).
|
||||
> Random 4K QD32 tests are directly comparable.
|
||||
|
||||
### Random 4K QD32 (Comparable)
|
||||
|
||||
| Metric | GXA7401Q | GXA7801Q | Delta |
|
||||
|--------|----------|----------|-------|
|
||||
| Read IOPS | 361,000 | 405,924 | +12.4% |
|
||||
| Write IOPS | 291,000 | 337,313 | +15.9% |
|
||||
| Read Latency | 88 µs | 79 µs | -10.2% |
|
||||
| Write Latency | 110 µs | 95 µs | -13.6% |
|
||||
|
||||
### SMART Comparison
|
||||
|
||||
| Metric | Before | After |
|
||||
|--------|--------|-------|
|
||||
| Media Errors | 0 | 0 |
|
||||
| Error Log Entries | 0 | 0 |
|
||||
| Temperature (load) | 51-64°C | 45-49°C |
|
||||
|
||||
## Test Configuration
|
||||
|
||||
```text
|
||||
fio-3.41
|
||||
IO Engine: libaio, Direct IO
|
||||
IO Depth: 32 (seq + rand main), 1 (QD1 tests)
|
||||
Runtime: 30s per test
|
||||
File size: 1GiB
|
||||
Directory: /tmp (on NVMe root drive)
|
||||
```
|
||||
|
||||
## Assessment
|
||||
|
||||
- Firmware update successful: GXA7401Q -> GXA7801Q confirmed
|
||||
- Random IOPS improved +12-16% at same queue depth
|
||||
- Latency reduced 10-14% for random workloads
|
||||
- SMART health unchanged: 0 media errors, 0 error log entries
|
||||
- Temperature lower post-update (likely due to fresh boot vs loaded state)
|
||||
- TRIM/unmap handling fix from GXA7801Q changelog should improve long-term write stability
|
||||
|
||||
## Conclusion
|
||||
|
||||
The GXA7801Q firmware update delivers measurable improvements in random I/O
|
||||
performance and latency. Combined with the TRIM handling fixes, this update
|
||||
is recommended for all Samsung PM9A1 non-SED drives running older firmware.
|
||||
@@ -148,7 +148,7 @@ sudo reboot
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [ ] Reboot system to activate GXA7801Q firmware
|
||||
- [ ] Verify firmware version with `nvme list`
|
||||
- [ ] Re-run identical benchmarks post-update
|
||||
- [ ] Compare results in `docs/tech/nvme-benchmark-post-gxa7801q.md`
|
||||
- [x] Reboot system to activate GXA7801Q firmware
|
||||
- [x] Verify firmware version with `nvme list` -> GXA7801Q confirmed
|
||||
- [x] Re-run identical benchmarks post-update
|
||||
- [x] Compare results in `docs/tech/nvme-benchmark-post-gxa7801q.md`
|
||||
|
||||
+27
-3
@@ -5,7 +5,7 @@ tags:
|
||||
- setup
|
||||
- bootstrap
|
||||
- ansible
|
||||
last_updated: '2026-03-09'
|
||||
last_updated: '2026-06-17'
|
||||
---
|
||||
|
||||
# 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 (`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.
|
||||
|
||||
```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 580.x).
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
+101
-1
@@ -6,7 +6,7 @@ tags:
|
||||
- software-stack
|
||||
- inventory
|
||||
- tech-stack
|
||||
last_updated: '2026-03-13'
|
||||
last_updated: '2026-07-12'
|
||||
---
|
||||
|
||||
# Software Stack - mw-pfeddersheim-workstation
|
||||
@@ -25,10 +25,63 @@ last_updated: '2026-03-13'
|
||||
| **bat** | latest | A `cat` clone with syntax highlighting |
|
||||
| **rustdesk** | latest | Remote management via Tailscale |
|
||||
|
||||
## Desktop Environment
|
||||
|
||||
|| Technology | Version | Description |
|
||||
||------------|---------|-------------|
|
||||
|| **Hyprland** | 0.55.2 | Wayland compositor (uwsm-managed) |
|
||||
|| **Waybar** | latest | Status bar (with USB auto-mount module) |
|
||||
|| **Kitty** | latest | GPU-accelerated terminal emulator |
|
||||
|| **Fuzzel** | latest | Application launcher (preferred over Wofi) |
|
||||
|| **Dunst** | latest | Notification daemon |
|
||||
|| **yazi** | latest | Terminal file manager (full deps: chafa, 7zip, resvg, etc.) |
|
||||
|
||||
## Known Issues (2026-05-22 Boot Audit)
|
||||
|
||||
| Domain | Issue | Status |
|
||||
|--------|-------|--------|
|
||||
| **PCIe slot** | GPU in bottom PCIEX4 slot (PCIe 2.0 x4, ~2 GB/s) instead of top PCIEX16 (PCIe 3.0 x16, ~16 GB/s). **8x bandwidth loss** | **Pending**: physical move required |
|
||||
| **PCIe slot** | NVMe Samsung PM9A1 running Gen2 x4 (8 GT/s) instead of Gen3 x4 (16 GT/s). May resolve after GPU slot fix + BIOS Gen3 setting | **Pending**: BIOS tuning |
|
||||
| **Idle daemon** | hypridle SEGV crash loop — sdbus-cpp 2.3.0 ABI break | **Fixed**: sdbus-cpp upgraded to 2.3.1-1 |
|
||||
| **Webcam (UVC)** | Device 1-10 has multiple UVC control failures (zoom/pan/tilt/iris non-functional due to firmware non-compliance) | Known hardware limitation, core capture works |
|
||||
| **KVM** | `/dev/kvm` unavailable — libvirtd cannot use hardware acceleration | Open — check BIOS VT-x/AMD-V |
|
||||
| **Wi-Fi regulatory** | `regulatory.db` firmware missing — channels restricted | Open — install `wireless-regdb` |
|
||||
| **D-Bus** | Stale `org.kde.kwalletd6.service` file; dunst/kscreen naming mismatches | **Fixed**: kwalletd6 stub removed; kscreen/kscreenlocker/libkscreen uninstalled (35 MiB) |
|
||||
| **XDG portals** | Deprecated `UseIn` key usage (portals.conf preferred) | Low priority, functional |
|
||||
|
||||
**Removed (2026-05-22, ADR 0007)**: All X11 display managers (lightdm, sddm), X11 video/input drivers (xf86-*), X11 tools (xterm, xorg-xinit, x11vnc, etc.), entire KDE Plasma stack (kwin, plasma-desktop, plasma-workspace, plasma-nm, plasma-pa, powerdevil, etc.), and Dolphin (replaced by yazi). `greetd`+`tuigreet` is the active DM. Screenshots use `grim`+`slurp`+`grimblast`.
|
||||
|
||||
## Browsers
|
||||
|
||||
| Technology | Version | Notes |
|
||||
|------------|---------|-------|
|
||||
| **Google Chrome** | 148.0.7778.178 (AUR) | Sole browser — Wayland-native, VA-API on NVIDIA |
|
||||
| ~~Chromium~~ | ~~removed~~ | All artifacts cleaned 2026-05-22 (see ADR 0006) |
|
||||
|
||||
## OS Context
|
||||
- **OS**: Linux (Manjaro/Arch Linux based)
|
||||
- **Architecture**: x86_64
|
||||
|
||||
## Package Domain Classification (ADR 0007 Tier 4)
|
||||
|
||||
Packages previously listed as "Misc" reclassified to functional domains:
|
||||
|
||||
| Package | New Domain | Role |
|
||||
|---------|-----------|------|
|
||||
| uv | Python Toolchain | Fast Python package/env manager |
|
||||
| jq | Shell/Terminal | JSON processor |
|
||||
| inxi | Hardware | System hardware info |
|
||||
| lsof | Core OS | List open files |
|
||||
| iproute2 | Network | Advanced routing/networking |
|
||||
| openresolv | Network | DNS resolver management |
|
||||
| perl-image-exiftool | Dev Tools | Metadata read/write |
|
||||
| icoutils | Dev Tools | ICO/ANE cursor extraction |
|
||||
| innoextract-git | Dev Tools | Inno Setup installer extraction |
|
||||
| opencl-headers | GPU Compute | OpenCL C headers |
|
||||
| libxnvctrl-580xx | NVIDIA | NVML/XNVCTRL for 580xx |
|
||||
| zram-generator | Core OS | Systemd zram swap generator |
|
||||
| mkinitcpio-openswap | Core OS | Swap resume hook for mkinitcpio |
|
||||
|
||||
## Maintenance Policies
|
||||
- **Updates**: System-wide updates via `pacman` managed through maintenance scripts.
|
||||
- **Node.js**: Global packages managed via `pnpm`.
|
||||
@@ -39,3 +92,50 @@ last_updated: '2026-03-13'
|
||||
- **Ansible**: `/usr/bin/ansible`
|
||||
- **Python**: `/usr/bin/python`
|
||||
- **Docker**: `/usr/bin/docker`
|
||||
|
||||
## Local Dev Services (Manual - not Ansible-managed)
|
||||
|
||||
System-level services running on this workstation that are configured manually
|
||||
outside this repo's Ansible roles. See `docs/tech/manual-overrides.md` for the
|
||||
full override table and `docs/learnings/2026-07-06-caddy-onlyoffice-mcp-sse-investigation.md`
|
||||
for the active Caddy/OnlyOffice investigation.
|
||||
|
||||
### Caddy (port 443, systemd `caddy.service`)
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| Package | `caddy` (pacman) |
|
||||
| Service | `caddy.service` (enabled, runs as `caddy` user) |
|
||||
| Config | `/etc/caddy/Caddyfile` + `/etc/caddy/conf.d/*.conf` (main site file `localhost-dev.conf`, plus per-service files like `agent0.localhost.conf`) |
|
||||
| TLS cert | mkcert-generated pair at `/etc/caddy/localhost{,-key}.pem`; includes explicit SANs for all 20 hostnames, valid until 2028-10-21. The `*.localhost` wildcard is NOT honored by curl/system trust even for single-label subdomains like `agent0.localhost` (empirically verified 2026-07-21: `.localhost` is a special-use TLD per RFC 6761); every subdomain MUST be listed explicitly. Multi-label hosts like `onlyoffice.docs.localhost` additionally require explicit SANs because wildcards only cover one level. |
|
||||
| Admin endpoint | Unix socket `/run/caddy/admin.socket` |
|
||||
| Sites | 18 `*.localhost` virtual hosts (see table below) |
|
||||
|
||||
#### `*.localhost` sites
|
||||
|
||||
| Site | Backend / Handler | Notes |
|
||||
|------|-------------------|-------|
|
||||
| `mcp.localhost` | `reverse_proxy localhost:50880` | MCP server (node, pid 1167) |
|
||||
| `artifacts.localhost` | `file_server browse` root `/var/www/artifacts` | Static artifacts |
|
||||
| `onlyoffice.localhost` | `reverse_proxy localhost:3847` | OnlyOffice MCP server; emitting `context canceled` SSE aborts (issue [#92](https://gitlab.satware.com/satware/mcp/onlyoffice/-/work_items/92)) |
|
||||
| `project-1.localhost` ... `project-10.localhost` | `respond "project-N - placeholder"` | 10 reserved placeholders |
|
||||
| `demo.satware.com.localhost` | `reverse_proxy localhost:38081` | webdevops/php-apache-dev container |
|
||||
| `platform.localhost` | `reverse_proxy localhost:10080` | amicron-platform Docker (Symfony 7.4 + Vue 3 + Firebird 3.0.14); `~/internal/amicron-platform` |
|
||||
| `packages.localhost` | `reverse_proxy localhost:20080` | satis Docker (Composer package repository with token-based access control); `~/internal/satis` |
|
||||
| `onlyoffice.docs.localhost` | `file_server` root `/home/mw/internal/onlyoffice/docs-site/dist` | Starlight static docs build |
|
||||
| `agent0.localhost` | `reverse_proxy 192.168.0.141:8090` | First non-localhost backend (remote host on LAN, uvicorn app). Config in dedicated `/etc/caddy/conf.d/agent0.localhost.conf`. `flush_interval -1` enabled. |
|
||||
|
||||
Wildcard `*.localhost` only matches single-label subdomains (e.g. `mcp.localhost`)
|
||||
AND is not honored by curl/system trust for the `.localhost` special-use TLD
|
||||
(RFC 6761) - every subdomain needs an explicit SAN. Multi-label hosts like
|
||||
`onlyoffice.docs.localhost` and `demo.satware.com.localhost` require explicit
|
||||
SANs for both reasons.
|
||||
|
||||
### Useful Caddy commands
|
||||
|
||||
```bash
|
||||
sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
|
||||
sudo systemctl reload caddy
|
||||
sudo journalctl -u caddy --no-pager -n 50
|
||||
curl -sk https://onlyoffice.localhost/ping # test (skips cert verify)
|
||||
```
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
description: >-
|
||||
TRIM policy optimization for the mw-pfeddersheim-workstation after the
|
||||
Samsung PM9A1 firmware update to GXA7801Q.
|
||||
tags:
|
||||
- storage
|
||||
- trim
|
||||
- nvme
|
||||
- ansible
|
||||
- optimization
|
||||
last_updated: '2026-04-02'
|
||||
---
|
||||
|
||||
# TRIM Optimization After PM9A1 Firmware Update
|
||||
|
||||
## Summary
|
||||
|
||||
On 2026-04-02 the workstation TRIM policy was aligned with current Linux best
|
||||
practice for ext4.
|
||||
|
||||
- Root filesystem (`/`) on `/dev/nvme0n1p2` was changed from `noatime,discard`
|
||||
to `noatime` in `/etc/fstab`
|
||||
- Weekly `fstrim.timer` remains enabled and active
|
||||
- Continuous ext4 discard was disabled at runtime with a remount using
|
||||
`nodiscard`
|
||||
|
||||
## Why This Change Was Necessary
|
||||
|
||||
The root ext4 filesystem was configured with continuous `discard` while the
|
||||
system already had periodic TRIM enabled via `fstrim.timer`.
|
||||
|
||||
For ext4, continuous discard adds overhead to unlink and truncate operations.
|
||||
With a healthy weekly `fstrim.timer`, it is redundant on this workstation.
|
||||
|
||||
## Implementation
|
||||
|
||||
The persistent change was implemented in Ansible.
|
||||
|
||||
- `ansible/vars/main.yml`
|
||||
- added `root_filesystem_uuid`
|
||||
- added `root_filesystem_mount_opts: noatime`
|
||||
- `ansible/roles/common/tasks/main.yml`
|
||||
- ensured the root fstab entry is managed with `mount: state=present`
|
||||
- checked current root mount options with `findmnt`
|
||||
- remounted `/` without continuous discard when needed
|
||||
|
||||
## Verification
|
||||
|
||||
### fstab
|
||||
|
||||
```text
|
||||
UUID=2aeade2e-b169-4964-8cbd-bb7356320b51 / ext4 noatime 0 1
|
||||
```
|
||||
|
||||
### Periodic TRIM
|
||||
|
||||
```text
|
||||
ActiveState=active
|
||||
UnitFileState=enabled
|
||||
NextElapseUSecRealtime=Mon 2026-04-06 00:49:28 CEST
|
||||
```
|
||||
|
||||
### Validation Steps
|
||||
|
||||
```text
|
||||
ansible-playbook ansible/workstation.yml --check --tags trim
|
||||
ansible-playbook ansible/workstation.yml --tags trim
|
||||
findmnt --verify --tab-file /etc/fstab
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `/boot/efi` remains unchanged
|
||||
- This change only targets the ext4 root filesystem TRIM policy
|
||||
- Firmware and benchmark documentation remain in the NVMe benchmark documents
|
||||
@@ -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
|
||||
|
||||
@@ -87,4 +87,8 @@ Uptime: ~1 hour, 7 minutes
|
||||
## Next Steps
|
||||
|
||||
1. Consider setting up automated crash monitoring for high-memory applications
|
||||
2. Run morning health check script daily: `/home/mw/internal/mw-pfeddersheim-workstation/scripts/morning-health-check.sh`
|
||||
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.
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# quarto.yml - Batch PDF conversion config
|
||||
# Use: quarto render docs/*.md --to typst-pdf
|
||||
# Or: quarto render docs/md-to-pdf-combo.md --to typst-pdf
|
||||
|
||||
project:
|
||||
type: default
|
||||
output-dir: "./docs/pdf-output"
|
||||
|
||||
format:
|
||||
typst-pdf:
|
||||
default: true
|
||||
toc: false
|
||||
number-sections: true
|
||||
mainfont: "Inter"
|
||||
monofont: "JetBrains Mono"
|
||||
fontsize: 9pt
|
||||
papersize: a4
|
||||
margin:
|
||||
top: 1.5cm
|
||||
bottom: 1.5cm
|
||||
left: 1.5cm
|
||||
right: 1.5cm
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bash
|
||||
# batch-pdf.sh - Convert all Markdown docs to professional PDFs via Pandoc + WeasyPrint
|
||||
# Usage: bash scripts/batch-pdf.sh [directory]
|
||||
# Default directory: docs/
|
||||
#
|
||||
# Pipeline: pandoc md → HTML → weasyprint CSS → PDF
|
||||
# Requires: pandoc, weasyprint, CSS file at docs/pdf-comparison/cheatsheet.css
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PROJ_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
CSS_PATH="${PROJ_ROOT}/docs/pdf-comparison/cheatsheet.css"
|
||||
DOC_DIR="${1:-${PROJ_ROOT}/docs}"
|
||||
OUTPUT_DIR="${PROJ_ROOT}/docs/pdf-output"
|
||||
|
||||
# Verify dependencies
|
||||
for cmd in pandoc weasyprint; do
|
||||
command -v "$cmd" &>/dev/null || { echo "ERROR: $cmd not found. Install: sudo pacman -S $cmd"; exit 1; }
|
||||
done
|
||||
[[ -f "$CSS_PATH" ]] || { echo "ERROR: CSS not found at $CSS_PATH"; exit 1; }
|
||||
|
||||
mkdir -p "${OUTPUT_DIR}"
|
||||
count=0
|
||||
errors=0
|
||||
tmpdir=$(mktemp -d)
|
||||
|
||||
echo "=== Markdown → PDF Batch (Pandoc + WeasyPrint + CSS) ==="
|
||||
echo "CSS: ${CSS_PATH}"
|
||||
echo "Input: ${DOC_DIR}"
|
||||
echo "Output: ${OUTPUT_DIR}"
|
||||
echo ""
|
||||
|
||||
find "${DOC_DIR}" -name "*.md" -not -path "*/pdf-output/*" -not -path "*/.*" -not -name "hyprland-keybindings-no-meta.md" | sort | while read -r md_file; do
|
||||
rel_path="${md_file#${DOC_DIR}/}"
|
||||
rel_dir="$(dirname "${rel_path#${DOC_DIR#${PROJ_ROOT}/}/}")"
|
||||
basename="$(basename "${md_file}" .md)"
|
||||
title="${basename//-/ }"
|
||||
|
||||
out_subdir="${OUTPUT_DIR}"
|
||||
if [[ "${rel_dir}" != "." && -n "${rel_dir}" ]]; then
|
||||
out_subdir="${OUTPUT_DIR}/${rel_dir}"
|
||||
fi
|
||||
mkdir -p "${out_subdir}"
|
||||
|
||||
html_file="${tmpdir}/${basename}.html"
|
||||
pdf_file="${out_subdir}/${basename}.pdf"
|
||||
|
||||
echo " ${rel_path} ..."
|
||||
|
||||
pandoc "${md_file}" -t html5 --standalone \
|
||||
--css "file://${CSS_PATH}" \
|
||||
--metadata title="${title}" \
|
||||
-o "${html_file}" 2>&1 || { echo " PANDOC ERROR"; errors=$((errors+1)); continue; }
|
||||
|
||||
weasyprint "${html_file}" "${pdf_file}" 2>/dev/null || \
|
||||
weasyprint "${html_file}" "${pdf_file}" 2>&1 | head -1
|
||||
|
||||
if [[ -f "${pdf_file}" ]]; then
|
||||
size=$(du -h "${pdf_file}" | cut -f1)
|
||||
echo " OK (${size})"
|
||||
count=$((count + 1))
|
||||
else
|
||||
echo " PDF GENERATION ERROR"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
rm -rf "${tmpdir}"
|
||||
|
||||
echo ""
|
||||
done_count=$(find "${OUTPUT_DIR}" -name "*.pdf" | wc -l)
|
||||
echo "=== Done: ${done_count} files in ${OUTPUT_DIR} ==="
|
||||
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
+116
@@ -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"
|
||||
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 "============================================="
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
# Realign Hyprland workspaces to their configured monitors.
|
||||
# Run this after changing monitor layout or workspace rules in hyprland.conf.
|
||||
# Workspace pinning rules are NOT retroactive on reload.
|
||||
#
|
||||
# Current layout (3 monitors):
|
||||
# workspace 1-3 -> DP-1 (left, TERRA 2560x1440@144)
|
||||
# workspace 4-6 -> DVI-D-1 (center, BenQ 1920x1080@60, portrait)
|
||||
# workspace 7-9 -> HDMI-A-1 (right, TERRA 2560x1440@144)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Check we're running inside Hyprland
|
||||
if ! command -v hyprctl &>/dev/null; then
|
||||
echo "ERROR: hyprctl not found. Is Hyprland installed?" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${HYPRLAND_INSTANCE_SIGNATURE:-}" ]; then
|
||||
echo "ERROR: Not running inside a Hyprland session." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Realigning Hyprland workspaces to monitors..."
|
||||
|
||||
# Move workspaces 1-3 to left monitor (DP-1)
|
||||
for ws in 1 2 3; do
|
||||
echo " Moving workspace $ws -> DP-1"
|
||||
hyprctl dispatch moveworkspacetomonitor "$ws" DP-1
|
||||
done
|
||||
|
||||
# Move workspaces 4-6 to center monitor (DVI-D-1)
|
||||
for ws in 4 5 6; do
|
||||
echo " Moving workspace $ws -> DVI-D-1"
|
||||
hyprctl dispatch moveworkspacetomonitor "$ws" DVI-D-1
|
||||
done
|
||||
|
||||
# Move workspaces 7-9 to right monitor (HDMI-A-1)
|
||||
for ws in 7 8 9; do
|
||||
echo " Moving workspace $ws -> HDMI-A-1"
|
||||
hyprctl dispatch moveworkspacetomonitor "$ws" HDMI-A-1
|
||||
done
|
||||
|
||||
sleep 1
|
||||
|
||||
# Verify
|
||||
echo ""
|
||||
echo "Current workspace assignments:"
|
||||
hyprctl workspaces -j 2>/dev/null | jq -r \
|
||||
'.[] | " ws\(.name) -> \(.monitor) (\(.windows) window(s))"' 2>/dev/null \
|
||||
|| echo " (jq parse failed, run 'hyprctl workspaces' manually)"
|
||||
|
||||
echo ""
|
||||
echo "Configured pinning rules:"
|
||||
hyprctl workspacerules -j 2>/dev/null | jq -r \
|
||||
'.[] | " ws\(.workspaceString) -> \(.monitor)"' 2>/dev/null \
|
||||
|| echo " (jq parse failed, run 'hyprctl workspacerules' manually)"
|
||||
|
||||
echo ""
|
||||
echo "Done. Workspaces realigned."
|
||||
Executable
+220
@@ -0,0 +1,220 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# satware® CLI Suite Update Script
|
||||
# Version: 1.0.0
|
||||
# Target: mw-pfeddersheim-workstation (Manjaro/Arch, macOS, Debian)
|
||||
# Source: Workflows/infra.cli-suite-update.md
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
CAPS_DIR="$PROJECT_DIR/docs/tech/capabilities"
|
||||
LOG_FILE="$PROJECT_DIR/logs/cli-suite-$(date +%Y%m%d).log"
|
||||
|
||||
# --- CLI Suite Definition ---
|
||||
# Format: binary_name:package_name (colon-separated if different)
|
||||
PACMAN_TOOLS=(shellcheck fd rg:ripgrep jq bat eza zoxide fzf gum dust btm:bottom syft cosign)
|
||||
NPM_TOOLS=(cline)
|
||||
|
||||
# --- Helpers ---
|
||||
log() { echo "[$(date '+%H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
|
||||
# Get binary name from entry (before colon, or whole string)
|
||||
bin_name() { echo "${1%%:*}"; }
|
||||
# Get package name from entry (after colon, or whole string)
|
||||
pkg_name() { echo "${1#*:}"; }
|
||||
|
||||
show_help() {
|
||||
cat <<EOF
|
||||
satware® CLI Suite Update Script v1.0.0
|
||||
|
||||
Usage: $(basename "$0") [OPTIONS]
|
||||
|
||||
Options:
|
||||
--check Check versions only, no updates
|
||||
--discover Run capability discovery (help text capture)
|
||||
--help Show this help message
|
||||
|
||||
Managed tools: shellcheck, fd, rg, jq, bat, eza, zoxide, fzf, gum,
|
||||
dust, btm, gh, tea, ruff, uv, glab, himalaya, semgrep, syft,
|
||||
cosign, nuclei, cline
|
||||
|
||||
Source of Truth: Workflows/infra.cli-suite-update.md
|
||||
EOF
|
||||
}
|
||||
|
||||
# --- Argument Parsing ---
|
||||
CHECK_MODE=false
|
||||
DISCOVER_MODE=false
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--check) CHECK_MODE=true ;;
|
||||
--discover) DISCOVER_MODE=true ;;
|
||||
--help|-h) show_help; exit 0 ;;
|
||||
*) echo "Unknown argument: $arg"; show_help; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# --- Setup ---
|
||||
mkdir -p "$(dirname "$LOG_FILE")" "$CAPS_DIR"
|
||||
|
||||
log "=== CLI Suite Update Started ==="
|
||||
log "OS: $(grep '^ID=' /etc/os-release 2>/dev/null | cut -d= -f2 || echo 'unknown')"
|
||||
log "Check mode: $CHECK_MODE | Discover mode: $DISCOVER_MODE"
|
||||
|
||||
# --- Section 1: System Package Tools (pacman) ---
|
||||
log "[1/5] System package tools (pacman)..."
|
||||
for tool in "${PACMAN_TOOLS[@]}"; do
|
||||
bin="$(bin_name "$tool")"
|
||||
pkg="$(pkg_name "$tool")"
|
||||
if command -v "$bin" &>/dev/null; then
|
||||
log " ✓ $bin installed"
|
||||
else
|
||||
log " ✗ $bin missing - installing package $pkg..."
|
||||
$CHECK_MODE || sudo pacman -S --noconfirm "$pkg" 2>&1 | tee -a "$LOG_FILE"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$CHECK_MODE" == false ]]; then
|
||||
log " Updating pacman packages..."
|
||||
PKGS=()
|
||||
for tool in "${PACMAN_TOOLS[@]}"; do PKGS+=("$(pkg_name "$tool")"); done
|
||||
sudo pacman -Syu --noconfirm "${PKGS[@]}" 2>&1 | tee -a "$LOG_FILE" || true
|
||||
fi
|
||||
|
||||
# --- Section 2: Standalone / Custom Install Tools ---
|
||||
log "[2/5] Standalone tools..."
|
||||
|
||||
# gh - GitHub CLI
|
||||
if command -v gh &>/dev/null; then
|
||||
log " ✓ gh installed"
|
||||
else
|
||||
log " ✗ gh missing"
|
||||
$CHECK_MODE || sudo pacman -S --noconfirm gh 2>&1 | tee -a "$LOG_FILE" || true
|
||||
fi
|
||||
|
||||
# tea - Gitea CLI
|
||||
if command -v tea &>/dev/null; then
|
||||
log " ✓ tea installed"
|
||||
else
|
||||
log " ✗ tea missing - install via: pacman -S tea or download from git.satware.ai"
|
||||
fi
|
||||
|
||||
# glab - GitLab CLI
|
||||
if command -v glab &>/dev/null; then
|
||||
log " ✓ glab installed"
|
||||
else
|
||||
log " ✗ glab missing"
|
||||
$CHECK_MODE || sudo pacman -S --noconfirm glab 2>&1 | tee -a "$LOG_FILE" || true
|
||||
fi
|
||||
|
||||
# uv - Python package manager
|
||||
if command -v uv &>/dev/null; then
|
||||
log " ✓ uv installed"
|
||||
$CHECK_MODE || uv self update 2>&1 | tee -a "$LOG_FILE" || true
|
||||
else
|
||||
log " ✗ uv missing"
|
||||
$CHECK_MODE || sudo pacman -S --noconfirm uv 2>&1 | tee -a "$LOG_FILE" || true
|
||||
fi
|
||||
|
||||
# ruff - Python linter
|
||||
if command -v ruff &>/dev/null; then
|
||||
log " ✓ ruff installed"
|
||||
else
|
||||
log " ✗ ruff missing"
|
||||
$CHECK_MODE || sudo pacman -S --noconfirm ruff 2>&1 | tee -a "$LOG_FILE" || true
|
||||
fi
|
||||
|
||||
# himalaya - Email CLI
|
||||
if command -v himalaya &>/dev/null; then
|
||||
log " ✓ himalaya installed"
|
||||
else
|
||||
log " ✗ himalaya missing"
|
||||
$CHECK_MODE || sudo pacman -S --noconfirm himalaya 2>&1 | tee -a "$LOG_FILE" || true
|
||||
fi
|
||||
|
||||
# semgrep - Static analysis
|
||||
if command -v semgrep &>/dev/null; then
|
||||
log " ✓ semgrep installed"
|
||||
else
|
||||
log " ✗ semgrep missing - install via: pipx install semgrep"
|
||||
fi
|
||||
|
||||
# hf - Hugging Face Hub CLI (via pipx)
|
||||
if command -v hf &>/dev/null; then
|
||||
log " ✓ hf installed"
|
||||
else
|
||||
log " ✗ hf missing - install via: pipx install 'huggingface_hub[cli]'"
|
||||
fi
|
||||
|
||||
# scrot - Screenshot (Linux only)
|
||||
if command -v scrot &>/dev/null; then
|
||||
log " ✓ scrot installed"
|
||||
else
|
||||
log " ✗ scrot missing"
|
||||
$CHECK_MODE || sudo pacman -S --noconfirm scrot 2>&1 | tee -a "$LOG_FILE" || true
|
||||
fi
|
||||
|
||||
# --- Section 3: npm Global Tools ---
|
||||
log "[3/5] npm global tools..."
|
||||
for tool in "${NPM_TOOLS[@]}"; do
|
||||
if command -v "$tool" &>/dev/null; then
|
||||
log " ✓ $tool installed"
|
||||
$CHECK_MODE || npm update -g "$tool" 2>&1 | tee -a "$LOG_FILE" || true
|
||||
else
|
||||
log " ✗ $tool missing - installing..."
|
||||
$CHECK_MODE || npm install -g "$tool" 2>&1 | tee -a "$LOG_FILE"
|
||||
fi
|
||||
done
|
||||
|
||||
# --- Section 4: Go/Custom Tools ---
|
||||
log "[4/5] Go/custom tools..."
|
||||
|
||||
# nuclei - Vulnerability scanner
|
||||
if command -v nuclei &>/dev/null; then
|
||||
log " ✓ nuclei installed"
|
||||
else
|
||||
log " ✗ nuclei missing - install via: go install or download release"
|
||||
fi
|
||||
|
||||
# --- Section 5: Summary ---
|
||||
log "[5/5] Installation summary..."
|
||||
INSTALLED=0
|
||||
MISSING=0
|
||||
ALL_TOOLS=(shellcheck fd rg jq bat eza zoxide fzf gum dust btm scrot gh tea ruff uv glab himalaya semgrep syft cosign nuclei hf cline)
|
||||
|
||||
for tool in "${ALL_TOOLS[@]}"; do
|
||||
if command -v "$tool" &>/dev/null; then
|
||||
INSTALLED=$((INSTALLED + 1))
|
||||
else
|
||||
MISSING=$((MISSING + 1))
|
||||
log " MISSING: $tool"
|
||||
fi
|
||||
done
|
||||
|
||||
log "Result: ${INSTALLED}/${#ALL_TOOLS[@]} tools installed, ${MISSING} missing"
|
||||
|
||||
# --- Capability Discovery ---
|
||||
if [[ "$DISCOVER_MODE" == true ]]; then
|
||||
log "=== Capability Discovery ==="
|
||||
|
||||
# Capture help output for key tools
|
||||
for tool in uv glab gh tea ruff; do
|
||||
if command -v "$tool" &>/dev/null; then
|
||||
OUTFILE="$CAPS_DIR/${tool}-help.txt"
|
||||
log " Capturing $tool help -> $OUTFILE"
|
||||
${tool} --help > "$OUTFILE" 2>&1 || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Capture subcommand help for tools with complex CLIs
|
||||
for sub in "uv audit" "uv pip" "glab mcp" "glab ci" "gh api" "tea login"; do
|
||||
CMD_ARRAY=($sub)
|
||||
OUTFILE="$CAPS_DIR/${sub// /-}-help.txt"
|
||||
log " Capturing '$sub' help -> $OUTFILE"
|
||||
"${CMD_ARRAY[@]}" --help > "$OUTFILE" 2>&1 || true
|
||||
done
|
||||
|
||||
log "Capability discovery complete. Files in $CAPS_DIR/"
|
||||
fi
|
||||
|
||||
log "=== CLI Suite Update Finished ==="
|
||||
Reference in New Issue
Block a user