All ~173 managed skills read in full and merged, one polished reference page per skill-cluster: - LLM cross-cutting (17 pages): GGUF Compatibility, Llama Cpp Serving Sizing, Llama Cpp Multi-Device Splits, PCIe Link and Atomics, RDNA4 Container Serving, RDNA4 MoE Expert Offload VRAM Budget, vLLM Boot Forensics, vLLM Porting and Compile Triage, LLM Bench Windows, LLM Performance Diagnosis, Model Swap and Rollback, Tcclaviger Fork Ops, Heterogeneous Split Triage, FreeToken Builds + deltas folded into the retired runbooks (GLM DFlash2 port laws, Lucebox dual-R9700, R9V qualification pipeline, FLM pack registration, ik hybrid envelope, frank-medium split rebalance, llama.cpp GGUF preflight, batch-ubatch split section, bench-window D-state/stale-read notes, second-endpoint bring-up). - Architecture (3): Stale Signal Audit, Quadlet Lifecycle, Deploy State Triage. - Operations (6 + index): Remote Job Runner, Payload and Disk Reclaim, Container Host Reachability, Fleet Ops Playbook, Fleet Audit and Posture, SSH and Remote Access. - Infrastructure: Observability Provisioning + Homepage Status Probes; updates to Authentik, Anubis, LAN-DNS (per-client DoH bypass, Porkbun zone ops), Swap Configuration, OpenWebUI, Hermes (pre-existing page preserved and extended). - FrankBook-Pro: ARR-Stack filled (API split, schema-first probing, auth-posture, auth-config trap); qBittorrent E2E verification. - Desktop/Agent/Research: Noctalia and Niri, OMP Harness, Agent Docs and Edits, Command Center Dotfiles, PICO-8 Asset Standards. - Indexes: LLM Cross-Cutting References section, Operations/ and Agent/ index pages, root-index Skill Distillation section, AGENTS.md pointer. - log.md carries the wave plan. check-vault-links.py 0 unresolved (185 pages); ./tests/run.sh 366/366.
16 KiB
type, title, description, tags, timestamp
| type | title | description | tags | timestamp | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| reference | Noctalia and Niri | The noctalia v5 desktop shell on niri (workstation + blankpad) — config layers, remote IPC over ssh, custom-palette recolor, offline template rendering, and output rotation/pinning. |
|
2026-10-05T00:00:00-07:00 |
Noctalia and Niri
Noctalia (/usr/bin/noctalia) is the desktop shell on niri, running on both
the workstation and blankpad. Since the 2026-09-10 unification both run the
bare noctalia v5 binary (niri spawn-at-startup "noctalia"); the old
Quickshell-hosted v4 architecture (qs -c noctalia-shell, JSON config) is
retired on both hosts.
Config layers
- Hand-edit declarative layer:
~/.config/noctalia/<name>.toml(e.g.config.toml). Main config:~/.config/noctalia/config.toml; the niri config includesnoctalia.kdlfor display settings. - Runtime state file
~/.local/state/noctalia/settings.tomlloads LAST and overrides declarative values for the same keys — never hand-edit it; UI/IPC writes land there. - Main reference:
noctalia-referencefacts — IPCnoctalia msg <command>replaced theqs -c noctalia-shell ipc callwrapper; app templates live at/usr/share/noctalia/assets/templates/(gtk, ghostty, alacritty, hyprland, sway, wezterm, helix, btop, starship, qt, kde, cava, scroll, mango, umbriel, foot, emacs, kitty); palette generationnoctalia theme [image].
Remote IPC over ssh (the D-Bus trap)
noctalia msg <command> resolves the running instance via the D-Bus session
bus. sshd-spawned non-login shells lack DBUS_SESSION_BUS_ADDRESS, so plain
ssh exec prints error: noctalia is not running even when the shell IS
running — do not conclude "shell dead" from this error. Working remote
procedure (runs in the systemd user-manager context where the bus address
exists):
systemd-run --user --pipe --wait noctalia msg <command> [args]
Verified: msg status returns live JSON state; unknown panel ids return an
error listing all valid panel ids (clipboard, control-center, launcher,
polkit, session, setup-wizard, test, tray-drawer, wallpaper).
Verified v5 msg verb grammar (keybinds-safe): panel-toggle <id>,
session lock|suspend|lock-and-suspend|logout|reboot|shutdown,
volume-up [step], volume-down, volume-mute, mic-mute,
media next|previous|playPause (graceful no-op, rc 0, with no active
player), brightness-up [target] [step], brightness-down,
settings-open [context], settings-toggle [context], config-reload,
wallpaper-get [connector], wallpaper-set <path>, wallpaper-next,
color-scheme-get|set, theme-mode-get, templates-apply,
screenshot-fullscreen.
Wallpaper directory semantics: empty/unset [wallpaper] directory
resolves to XDG Pictures via user-dirs.dirs; an explicit directory key
overrides. Fleet default pattern: point directory at the repo-tracked
wallpapers dir so git-tracked wallpapers serve the shell without copying
files. After writing config.toml over ssh the file watcher hot-reloads
automatically; config-reload via systemd-run forces it immediately. Note:
the current wallpaper stays the built-in default until the next rotation
or an explicit wallpaper-next; wallpaper-get then reporting a path
inside the configured directory is the confirmation the config took effect.
Lock chain (unified): swayidle-lock.service ExecStart on both hosts:
/usr/bin/swayidle -w timeout 300 '/usr/bin/noctalia msg session lock' before-sleep '…' after-resume '…'. msg resolves via the session bus, so
no WAYLAND_DISPLAY pinning is needed in unit context.
Custom-palette recolor (without restarting the session)
Why custom, never the community cache:
~/.local/state/noctalia/community-palettes/*.json is a sync cache —
.catalog/palettes.json carries an md5 per palette and noctalia re-fetches
from https://api.noctalia.dev/palettes, so edits there get reverted.
Custom palettes live at ~/.config/noctalia/palettes/<Name>.json
(NOCTALIA_CONFIG_HOME then XDG_CONFIG_HOME honored) and are never
synced. Missing/invalid file → silent fallback to the builtin palette.
Palette JSON shape = the community file's shape: dark/light maps of
mPrimary, mOnPrimary, mSecondary, mTertiary, mError, mSurface,
mSurfaceVariant, mOutline, mShadow, mHover plus a terminal block
(foreground, background, normal.{black..white}, bright.{…},
cursor, cursorText, selectionFg, selectionBg). NOTE: noctalia theme -o emits M3 snake_case tokens (on_primary_container, …) — that is the
generator format, not this one. Copy an existing community JSON and mutate
with jq.
Procedure:
-
Read the live scheme first — never assume:
noctalia msg color-scheme-get(e.g.community Gruber Darker);noctalia msg theme-mode-get. -
Back up settings:
cp -a ~/.local/state/noctalia/settings.toml /tmp/noctalia-settings-backup-$(date +%s).toml. -
Build the palette from the current one (jq keeps structure):
mkdir -p ~/.config/noctalia/palettes jq '.dark.mPrimary = "#f43841" | .dark.terminal.normal.yellow = "#f43841" | .dark.terminal.bright.yellow = "#ff4f58" | .dark.terminal.foreground = "#f43841"' \ "$HOME/.local/state/noctalia/community-palettes/Gruber%20Darker.json" \ > ~/.config/noctalia/palettes/"Gruber Red.json"Prefer a hex already present in the palette (its own
mErrorred) so the theme stays internally consistent. Set BOTHnormalandbrightvariants, and bothdarkandlightif the user toggles modes. -
Switch (persists
theme.source = custom+theme.custom_palette):noctalia msg color-scheme-set custom "Gruber Red"→ok; confirm withcolor-scheme-get. -
Re-render every configured template:
noctalia msg templates-apply. Regenerates~/.config/ghostty/themes/noctalia,~/.config/gtk-{3,4}.0/noctalia.css, kcolorscheme, foot, wezterm, starship, etc. Symlinked-into-dotfiles paths show up as modified files worth committing. -
Reload surfaces: compositor —
niri msg action load-config-file(reload-configis NOT a valid niri subcommand); ghostty — see the trap below.
Verification: ghostty +show-config | grep -E '^(foreground|palette)'
(proves the theme file resolves — note it prints only the delta from
defaults; cross-check with ghostty +show-config --default before
concluding a key is unset); noctalia config validate → ✓ Config is valid; visual via noctalia msg screenshot-fullscreen (saves to
~/Pictures/screenshot_<ts>.png and ALSO overwrites the clipboard with the
PNG — warn the user); grep -c <old-hex> ~/.config/noctalia/palettes/*.json
→ 0 for no leftovers.
Track it in the dotfiles repo (~/.omp, the command-center —
scripts/deploy.sh is a stow-equivalent over dotfiles/<pkg>/ trees):
mirror the shape for the new palette so git pull + deploy.sh is the
whole propagation story — move the JSON into
~/.omp/dotfiles/noctalia/.config/noctalia/palettes/ and symlink it back
into ~/.config/noctalia/palettes/; re-set the custom palette (re-reads
through the symlink). Do the link by hand for one file instead of running
deploy.sh — sync mode quarantines and replaces every divergent live file.
Traps:
- NEVER
kill -USR1ghostty to reload. It does not reload config on this fleet — it kills/respawns the terminal (took out Frank's window 2026-09-13). Correct: the default keybindctrl+shift+,=reload_config(perghostty +show-config --default). Existing windows keep old colors until reloaded; new windows inherit immediately. - Palette hex values are NOT a proxy for what the user sees: in
Gruber Darkerthe accent (mPrimary) is yellow#ffdd33, so "change the yellow" means the accent too, not just terminal index 3. - Saturating terminal text makes ANSI 1/9 indistinguishable from default text (grep highlights flatten) — flag it and offer a lighter red.
- Contrast sanity:
#f43841on#181818≈ 4.6:1 (dark-on-color beats white-on-color here, ~3.9:1 — keepmOnPrimarydark). - Revert =
noctalia msg color-scheme-set community "<original name>"; the community cache is untouched by the whole flow. - Generated theme files symlinked into the dotfiles repo (
gtk-3.0/gtk-4.0noctalia.css, ghostty theme) show up as tracked modifications aftertemplates-apply; commit them together with the new palette JSON so every machine gets the same recolor.
Offline template render (when templates-apply never ran)
Failure signature: niri failed to read included config from ~/.config/niri/noctalia.kdl: No such file or directory — the desktop runs
but with DEFAULT config (niri 26.04 falls back, does not exit): no custom
keybinds/rules, no noctalia autostart, default output placement. ghostty:
theme "noctalia" not found, tried ~/.config/ghostty/themes/noctalia.
alacritty: import of ~/.config/alacritty/themes/noctalia.toml missing.
Root cause pattern: upstream dotfiles commits theme switches (niri include,
ghostty theme = noctalia, alacritty import) that depend on noctalia
TEMPLATE-GENERATED files. Those files are per-host outputs (rendered from
the local palette) and are intentionally NOT in the repo; on any host where
templates-apply never ran, they don't exist.
Fix (offline, works while a session runs) — built-in templates live at
/usr/share/noctalia/assets/templates/ (declared in builtin.toml); render
each needed one with the live palette:
# niri include (plain host-local file; do NOT symlink into repo — regenerated per host)
noctalia theme --theme-json ~/.config/noctalia/colors.json \
--render "/usr/share/noctalia/assets/templates/niri/niri.kdl:$HOME/.config/niri/noctalia.kdl"
mkdir -p ~/.config/ghostty/themes && \
noctalia theme --theme-json ~/.config/noctalia/colors.json \
--render "/usr/share/noctalia/assets/templates/ghostty/ghostty:$HOME/.config/ghostty/themes/noctalia"
mkdir -p ~/.config/alacritty/themes && \
noctalia theme --theme-json ~/.config/noctalia/colors.json \
--render "/usr/share/noctalia/assets/templates/alacritty/alacritty.toml:$HOME/.config/alacritty/themes/noctalia.toml"
All template tokens (primary, surface, error, primary_container, …) resolve from the flat 16-key colors.json; container tokens are derived, never blank.
Verify: niri validate → rc 0; ghostty +validate-config → rc 0; next
login journal shows loaded config from config.kdl with NO parse WARN,
custom output placement (niri msg outputs vs display.kdl), noctalia
autostarted (pgrep noctalia).
Traps: noctalia msg templates-apply on 5.0.1 returns "ok" but applies
NOTHING (this build's opt-in config schema does not act on it) — do not
trust its exit code, use the explicit --render calls above. Niri config
errors at boot are NOT fatal — the compositor keeps running on defaults,
which reads as "some settings silently stopped working", not a crash.
Regenerate after any palette change; the rendered files are static
snapshots.
Niri output rotation and pinning
Verified on blankpad, niri 26.04 (two PM1601 externals + eDP-1 laptop panel).
Ground truth first: niri msg outputs (connector names, current mode,
scale, transform, logical pos/size) and niri --version. Output names are
connector names (eDP-1, DP-2, HDMI-A-1), not EDID model strings —
the EDID strings printed before them are decoration. On blankpad the live
config splits via include "./cfg/*.kdl" from ~/.config/niri/config.kdl,
and cfg/display.kdl is a symlink into the dotfiles repo — edit the
repo path (that is what gets committed); check with ls -ld.
Transform direction is counter-clockwise (niri msg output <NAME> transform --help): 90 = content rotated 90° CCW ⇒ the panel's top
bezel sits on the LEFT; 270 = top bezel RIGHT; 180 = upside down;
flipped-90/flipped-270 = mirrored variants. 90 vs 270 is a
physical-stand fact no tool can read (portable monitors expose no
orientation in EDID/MCCS) — pick one, apply, and hand the user the one-liner
to flip; don't stall on the question.
Two traps when editing the config:
- An
output "X" { … }block resets every property you don't restate to its default. Pinning position oneDP-1without repeatingscale 1.25silently drops the laptop panel to scale 1.0. Restatescale(andmode) in every block you touch. - Niri re-runs auto-layout for outputs with no pinned position. Pinning
only the externals shoved
eDP-1from0,0to3936,0. Pin every output's position to keep the left-to-right order. Positions are logical pixels at each output's own scale; x-offsets chain (eDP-1 logical width 1536 → DP-2 at 1536 → +1200 portrait width → HDMI-A-1 at 2736):
output "DP-2" { scale 1 transform "90" position x=1536 y=0 }
output "HDMI-A-1" { scale 1 transform "90" position x=2736 y=0 }
output "eDP-1" { scale 1.25 position x=0 y=0 } // MUST restate scale
Apply and verify:
niri validate -c ~/.config/niri/config.kdl # exit 0
niri msg action load-config-file # live reload; NOT reload-config
sleep 1; niri msg outputs | grep -E '^Output|Transform|Logical|Scale'
Success looks like Logical size: 1200x1920 (swapped) plus
Transform: 90° counter-clockwise on each rotated panel, positions matching
the intended chain. One-off rotation without touching config (temporary):
niri msg output DP-2 transform 90. Commit via the dotfiles repo
(~/.omp, main; push to the local Gitea permitted): git pull --ff-only
→ add the display.kdl → commit → push. Pitfalls: reload-config is not a
niri subcommand; never SIGUSR1 a terminal to "reload" (niri config reloads
do not touch ghostty themes); no man page for niri on this host — niri <sub> --help and /usr/share/doc/niri/default-config.kdl are the docs.
GPU RGB over I2C (EVGA iCX3)
Turn RGB on/off (or read it back) on an EVGA FTW3 / iCX3-controller GPU (the 3090 on dankclaw) using the GPU's own nvidia i2c bus. No display engine, no X, no daemon — the mode is EEPROM-saved and survives reboot.
- Find the controller: the nvidia i2c driver exposes the card's lighting
ports as
/dev/i2c-N; on the 3090 FTW3 that is ports 1,5,6,7,8 → i2c-1..i2c-5, and only i2c-1 carries a live slave (NVIDIA port 1). Probe each bus. The iCX3 MCU answers at address 0x2D./dev/i2c-*areroot:i2c 660— run withsudo(dank has passwordless sudo). - Protocol (raw I2C_RDWR, NOT the SMBus ioctls) — write the register
byte first, then the payload:
- enable
0xB2write04 C6 EB EA 15(required once before mode writes) - mode
0xC0write09 m0 m1 m2 m3 .. .. .. .. SYNC; m0..m3 are the four zone modes,MODE_OFF = 0x00; OFF =09 00 00 00 00 FF FF FF FF 00 - save
0x90write04 9E EB 00 90(commit to EEPROM — the reboot-persistence step) - fw
0xB1block read (firmware version) - Read the mode back: write reg
0xC0, then read 16 bytes; bytes 1..4 are the zone modes (0x00 = OFF), bytes 6..9 the per-zone LED counts.
- enable
- Verify persistence: after a reboot, read the mode — all four zone
modes
0x00means the EEPROM save held (confirmed 2026-09-19 post-reboot). - Tool: a ~90-line I2C_RDWR reader/writer lived at
/tmp/evga_raw.c(evga_raw <bus> mode|off|save|enable|fw) —/tmpis wiped by reboot, so recreate it if needed; OpenRGB'sEVGAGPUv3Controller.cppcarries the same protocol plus the full colour API. - Gotchas: raw I2C_RDWR only (the iCX3 answers the raw form, not the SMBus
ioctls); the
enablewrite must precede the first mode write in a session; without thesavewrite the mode is RAM-only and resets on power cycle.
Related
- Command Center Dotfiles — the stow-equivalent deploy this page links into
- Hermes — the agent that drove the CDP daemon alongside this desktop
- GGUF Compatibility — unrelated but shares the "read the file, not the name" lesson