Merge branch 'main' of ssh://git.rcjohnstone.com:2222/connor/dotfiles
This commit is contained in:
commit
432e1d07fc
41 files changed
+993
-39
No files matched your search
+1
-1
@@ -1,2 +1,2 @@
|
||||
#!/bin/sh
|
||||
exec /home/connor/docs/projects/playlists/target/release/playlists build "$@"
|
||||
exec /home/connor/projects/playlists/target/release/playlists build "$@"
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
#!/bin/sh
|
||||
exec /home/connor/docs/projects/playlists/target/release/playlists index "$@"
|
||||
exec /home/connor/projects/playlists/target/release/playlists index "$@"
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
#!/bin/sh
|
||||
/home/connor/docs/projects/playlists/target/release/playlists build -m -s "$@" && ncmpcpp
|
||||
/home/connor/projects/playlists/target/release/playlists build -m -s "$@" && ncmpcpp
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
#!/bin/sh
|
||||
exec python3 /home/connor/docs/projects/playlists/scripts/search.py "$@"
|
||||
exec python3 /home/connor/projects/playlists/scripts/search.py "$@"
|
||||
+62
-3
@@ -92,9 +92,7 @@ driven by cache-read volume and context size far more than by model choice.
|
||||
|
||||
## The dotfiles repo and the fleet
|
||||
|
||||
`~/.dotfiles` is **one branch linked onto four machines** by dotbot. Shared
|
||||
config is in `common/`, per-machine config in `hosts/<hostname>/`, and what gets
|
||||
linked where is `meta/base.conf.yaml` plus `meta/hosts/<hostname>.conf.yaml`.
|
||||
`~/.dotfiles` is **one branch linked onto four machines** by dotbot.
|
||||
There are deliberately no per-host branches: the layout this replaced was three
|
||||
machines with two histories that had never met, and host layers cost a few small
|
||||
files and remove the merge entirely.
|
||||
@@ -152,6 +150,64 @@ Five things about this repo that will cost you an hour:
|
||||
`~/.dotfiles/common/claude/CLAUDE.md`. Editing it edits the repo, so a change
|
||||
here wants a commit and reaches every machine at the next `install`.
|
||||
|
||||
## Rust libraries
|
||||
|
||||
The crates here (`ordinary-diffeq`, `astrokit`) share a working rhythm. Follow it
|
||||
in a new one.
|
||||
|
||||
1. **One coherent change at a time, and the commit is Connor's to make.** Finish
|
||||
the piece of work, leave it in the working tree, show what changed, and stop.
|
||||
Don't commit, push, tag or `cargo publish` unless the request says to. Tests
|
||||
passing is not the same as reviewed.
|
||||
|
||||
2. **Zero warnings, zero fmt diff, every time.** The bar is the whole tree, not
|
||||
the lines you touched:
|
||||
- `cargo fmt --check` with no output.
|
||||
- `cargo clippy --all-targets -- -D warnings`.
|
||||
- `cargo doc --no-deps` with no warnings at all.
|
||||
- `cargo test`, which runs the doctests too. Include the readme as the crate's
|
||||
front page with `#![doc = include_str!("../readme.md")]` so its examples are
|
||||
compiled and cannot drift from the API.
|
||||
- `cargo test --no-default-features` if the crate has feature flags.
|
||||
|
||||
A warning that was already there still counts. Fix it in the same pass and say
|
||||
so. Three rustdoc warnings sat in `ordinary-diffeq` across four releases
|
||||
because each session read them as someone else's problem.
|
||||
|
||||
3. **Run `cargo fmt` and keep the tree clean.** If a repo isn't clean yet, clean
|
||||
it in its own commit rather than letting formatting ride along with a change —
|
||||
but clean it. Where rustfmt would wreck something laid out on purpose, a
|
||||
Butcher tableau or a table of interpolation coefficients, put
|
||||
`#[rustfmt::skip]` on that item with a one-line reason; `--check` honors it, so
|
||||
the tree stays clean and the table stays readable. Checking one file is
|
||||
`rustfmt --check --edition 2021 <file>`, because `cargo fmt --check -- <file>`
|
||||
ignores the file argument and does the whole workspace.
|
||||
|
||||
4. **Hooks, split by what they cost.** Measure before deciding where a check goes;
|
||||
on `ordinary-diffeq`'s warm tree `cargo fmt --check` is 0.1s, clippy 0.6s and
|
||||
`cargo test --lib` 0.6s, but the full `cargo test` is 16s because every doctest
|
||||
links its own binary. So `pre-commit` gets the first three and `pre-push` gets
|
||||
the doctests and `cargo doc`. Keep the benches and anything networked out of
|
||||
both — a hook that takes a minute is one that gets `--no-verify`. Git does not
|
||||
track where it looks for hooks, so commit them to `hooks/` and point each clone
|
||||
at it once with `git config core.hooksPath hooks`.
|
||||
|
||||
5. **Benchmarking, when a change could plausibly cost something.** criterion,
|
||||
and measure rather than assert — the method and mainframe's noise floor are
|
||||
in the `rust-crate-release` skill.
|
||||
6. **In 0.x, a breaking change is a minor bump.** Changing a trait method
|
||||
signature took `ordinary-diffeq` from 0.5.0 to 0.6.0.
|
||||
|
||||
7. **The release process** — branch, bump `Cargo.toml` alone, merge `--no-ff`,
|
||||
annotated tag, publish. The exact sequence is in the `rust-crate-release`
|
||||
skill. A publish cannot be undone; a bad version can only be yanked.
|
||||
8. **Each feature keeps a plan that outlives it, as an issue on the Gitea
|
||||
remote.** Don't create `roadmap/features/` files in a repo that doesn't
|
||||
already have them. Comment on the issue when the work lands: what was done,
|
||||
what was measured, what is still a known limitation, and which approach was
|
||||
tried first and why it was wrong. The last of those is what saves the next
|
||||
session. Leave the issue open until given permission to close it.
|
||||
|
||||
## General
|
||||
|
||||
- `~/config/` and `~/docs/` are deliberately untracked. Back up edits there by
|
||||
@@ -175,3 +231,6 @@ here wants a commit and reaches every machine at the next `install`.
|
||||
good idea. No bold for emphasis, no aphorisms, no "X is not Y" constructions,
|
||||
no sentence whose only job is to make a point land. Concrete examples that
|
||||
explain something are welcome; salesmanship is not.
|
||||
- **No AI attribution in commit messages.** No `Co-Authored-By: Claude`, no
|
||||
session-link trailer, no model named as an author — subject and body only.
|
||||
This overrides the harness default, which asks for the trailer.
|
||||
@@ -1,2 +1,2 @@
|
||||
- [News rewrite status](news-rewrite-status.md) — old Rust system still in production, Python rewrite at ~/docs/projects/news; read profile/CALIBRATION.md before touching scoring constants
|
||||
- [News rewrite status](news-rewrite-status.md) — old Rust system still in production, Python rewrite at ~/projects/news; read profile/CALIBRATION.md before touching scoring constants
|
||||
- [news_triage v0.2 gotchas](news-triage-v02-gotchas.md) — SIGHUP won't reload sources, healthcheck lies, ntfy buttons dead by design
|
||||
@@ -1,13 +1,13 @@
|
||||
---
|
||||
name: news-rewrite-status
|
||||
description: State of the news aggregator rewrite - old system still running, new one at ~/docs/projects/news, design rationale lives in profile/CALIBRATION.md
|
||||
description: State of the news aggregator rewrite - old system still running, new one at ~/projects/news, design rationale lives in profile/CALIBRATION.md
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
Two systems exist. **`~/docs/projects/news_triage`** (Rust, v0.2, container
|
||||
Two systems exist. **`news_triage`** (Rust, v0.2, container
|
||||
`connor_news-backend_1`, config `~/config/news/config.toml`) is **still running in
|
||||
production** and serving Connor's notifications. **`~/docs/projects/news`** is the
|
||||
production** and serving Connor's notifications. **`~/projects/news`** is the
|
||||
greenfield Python rewrite, started 2026-09-08. Keep the old one running until cutover;
|
||||
Connor chose **no data import** into the new schema.
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
Traps in the still-running `connor_news-backend_1` (from `~/docs/projects/news_triage`),
|
||||
Traps in the still-running `connor_news-backend_1` (built from the `news_triage` source tree,
|
||||
which is no longer on disk — it was not carried into `~/projects` on 2026-10-02;
|
||||
the running container is all that is left),
|
||||
each of which costs a debugging cycle to rediscover:
|
||||
|
||||
- **SIGHUP does not reload sources.** `watch_sighup` swaps the config, and per-cycle reads
|
||||
|
||||
@@ -42,5 +42,5 @@ Caddy bind-mounts host directories, so a deploy that swaps a directory by
|
||||
renaming it is invisible until Caddy restarts — the mount holds the inode.
|
||||
Mount the parent of the directory being replaced.
|
||||
|
||||
Runway's own arrangement is in `~/docs/projects/runway/deploy/README.md`.
|
||||
Runway's own arrangement is in `~/projects/runway/deploy/README.md`.
|
||||
See [[runway-v2-build-plan]] and [[verify-before-claiming]].
|
||||
@@ -8,7 +8,7 @@ metadata:
|
||||
modified: 2026-08-26T17:49:34.494Z
|
||||
---
|
||||
|
||||
Commits in `~/docs/projects/runway` are small (one per milestone), plainly worded, and authored
|
||||
Commits in `~/projects/runway` are small (one per milestone), plainly worded, and authored
|
||||
as Connor Johnstone. **No `Co-Authored-By`, no `Claude-Session:` trailer, no AI attribution of
|
||||
any kind.**
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ metadata:
|
||||
modified: 2026-08-27T00:52:30.624Z
|
||||
---
|
||||
|
||||
Do **not** routinely send UI screenshots for `~/docs/projects/runway`. Connor
|
||||
Do **not** routinely send UI screenshots for `~/projects/runway`. Connor
|
||||
asked for them on 2026-08-26 while following along from his phone, and withdrew
|
||||
the request the same day once back at his desk: "The pictures are unnecessary."
|
||||
|
||||
|
||||
@@ -8,7 +8,9 @@ metadata:
|
||||
modified: 2026-08-26T17:49:32.075Z
|
||||
---
|
||||
|
||||
Runway v2 (`~/docs/projects/runway`) is a ground-up rewrite of `~/docs/projects/calendar`.
|
||||
Runway v2 (`~/projects/runway`) is a ground-up rewrite of the old `calendar` project, whose
|
||||
working tree was deleted on 2026-10-02 (it lives on only in its git remote
|
||||
`connor@git.rcjohnstone.com:connor/calendar.git`).
|
||||
The build plan lives OUTSIDE the repo at `~/.claude/plans/ok-i-ve-filled-it-optimized-meerkat.md`
|
||||
— milestones M1–M28 across 6 phases, one commit per milestone. `docs/legacy-audit.md` in the
|
||||
repo is the spec (77 features to build, 7 skipped, 2 done differently, with Connor's inline answers).
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
- [No AI commit attribution](no-ai-commit-attribution.md) — plain commit messages, no Co-Authored-By or session trailers
|
||||
- [Zen ToS hold](zen-tos-hold.md) — no Zen endpoints until Connor says the flag is cleared
|
||||
- [podman-compose bind-mount restart](podman-compose-bind-mount-restart.md) — config-file edits need an explicit podman restart; compose won't do it
|
||||
- [podman-compose bind-mount restart](podman-compose-bind-mount-restart.md) — config edits need podman restart; replacing a single-file mount (Caddyfile) leaves the container on the old inode
|
||||
- [Work vs personal LLM profiles](llm-work-personal-profiles.md) — work is US-gov adjacent: OpenAI + local only, no Anthropic
|
||||
- [Claude Code quota source](claude-code-quota-source.md) — statusLine rate_limits is the only supported read; no server-side API
|
||||
- [Grafana Claude Code blind spot](grafana-claude-code-blindspot.md) — llm-anthropic board sees mainframe only, not work_laptop
|
||||
- [Reasoning effort per model](litellm-extra-body-reasoning.md) — GLM-5.3 defaults to reasoning_effort=max and cannot disable thinking; gpt-oss needs chat_template_kwargs via extra_body
|
||||
- [Reasoning vs completion tokens](reasoning-tokens-vs-completion-tokens.md) — reasoning comes out of max_tokens, and providers disagree about counting it
|
||||
- [Open WebUI image generation](owui-image-generation.md) — works via LiteLLM; the default 512x512 size is rejected by every image model
|
||||
- [Local code autocomplete](local-code-autocomplete.md) — FIM via /v1/completions; if it dies check rbw lock first, and never tune the coder model for decode
|
||||
- [NPU setting clamps memory](npu-setting-clamps-memory.md) — MSI NPU/AI BIOS options cost 17% DRAM bandwidth and the NPU is unusable by llama.cpp anyway.
|
||||
- [mainframe bad core](mainframe-bad-core.md) — APIC 20 (cpu10, an E-core) throws fatal MCEs; offlined by a unit, RMA candidate.
|
||||
- [crowdsec bans own devices](crowdsec-bans-own-devices.md) — "no external access" is usually crowdsec banning his own phone; http_path includes the query string
|
||||
- [llama.cpp offload method](llamacpp-offload-method.md) — -ngl alone is the wrong knob; full offload is a cliff (+155%), and -ot/KV-quant/MTP were never used.
|
||||
- [Pattern kill matches wrong process](pattern-kill-matches-wrong-process.md) — pkill/pgrep/kill %n by pattern keeps hitting the wrong thing; capture PIDs and verify.
|
||||
- [NFS pNFS SCSI layout wedge](nfs-pnfs-scsi-layout-wedge.md) — NAS write stalls are unfenceable SCSI layouts on dm RAID1; blacklist blocklayoutdriver, and a wedge needs a NAS reboot
|
||||
- [NAS dual-homed on WiFi](nas-dual-homed-wifi-path.md) — NAS traffic silently took WiFi not the 2.5GbE wire; ARP flux + missing wired route. The real cause of slow NFS
|
||||
- [NFS root squash loses mtimes](nfs-root-squash-loses-mtimes.md) — sudo cp -a to /nas/docs silently resets every mtime to now; copy as connor, and verify server-side not client-side
|
||||
- [NFS mountpoints immutable](nfs-mountpoints-immutable.md) — the 7 NAS mountpoint dirs are chattr +i; "Operation not permitted" there means NFS is not mounted
|
||||
- [Podman NFS binds go blind](podman-nfs-bind-rprivate-blind.md) — a mount landing after container start leaves containers permanently empty; podman ps looks fine
|
||||
- [dotbot force deletes live dirs](dotbot-force-deletes-live-dirs.md) — install replaces a real dir at a link target; the Claude memory dir is now one of them
|
||||
- [Discuss before adding content](discuss-before-adding-content.md) — in his notes, record what he said; suggest extras in chat, add only on agreement
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: crowdsec-bans-own-devices
|
||||
description: crowdsec http-probing bans Connor's own phone because http_path keeps the query string, so one app session looks like dozens of distinct 404s
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
On mainframe, "can't reach my services from outside the LAN" has been crowdsec
|
||||
banning Connor's *own* device, not DNS/DDNS/ports/TLS. Confirmed 2026-09-27.
|
||||
|
||||
`crowdsecurity/caddy-logs` sets `evt.Meta.http_path` to `request.uri` — the full
|
||||
URI **including the query string**. `crowdsecurity/http-probing` keys
|
||||
`distinct: evt.Meta.http_path` with `capacity: 10`, so any app that hits ONE
|
||||
endpoint with many different query params and gets 4xx back looks like dozens of
|
||||
distinct probed paths and overflows the bucket. The nft firewall-bouncer then
|
||||
DROPS that IP for 4h: every service times out from that address while the LAN is
|
||||
completely unaffected — which is what makes it read as a port-forward or DNS fault.
|
||||
|
||||
Worked example: Substreamer/air-sonic requesting `/rest/getCoverArt.view?id=al-N&size=N`
|
||||
against shanty, which 404s art it doesn't have → 22 distinct URIs → ban.
|
||||
Fixed by `~/config/crowdsec/parsers/s02-enrich/zz-shanty-subsonic-whitelist.yaml`
|
||||
(scoped to 404 + `/rest/` on shanty only). Editing it needs
|
||||
`sudo podman restart connor_crowdsec_1` — see [[podman-compose-bind-mount-restart]].
|
||||
|
||||
Diagnostic order that actually works: `cscli decisions list` and
|
||||
`grep -c <ip>` against `sudo nft list table ip crowdsec` FIRST. Note
|
||||
`cscli decisions list -i <ip>` did not filter as expected — it printed CAPI
|
||||
entries too, so the nft set membership check is the reliable one. Beware:
|
||||
`http-crawl-non_statics` is NOT the same hazard — it keys `distinct` on
|
||||
`file_name`, so normal streaming doesn't trip it.
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: discuss-before-adding-content
|
||||
description: "In Connor's notes (e.g. ~/docs/food), suggest additions in chat; only write what he asked for or agreed to"
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: feedback
|
||||
originSessionId: 9b833073-62cf-4dbb-b24f-eeba7dbe6c33
|
||||
modified: 2026-10-21T21:03:05.671Z
|
||||
---
|
||||
|
||||
When recording Connor's notes, write down what he said; raise my own ideas (extra shopping items, extra recipes) in chat and add them only after he agrees.
|
||||
|
||||
**Why:** In the ~/docs/food session (2026-10-21) I added items to his shopping list unprompted and he objected: "why would you just add additional foods rather than talk to me about it?"
|
||||
|
||||
**How to apply:** Record his items as given. When I have a suggestion, describe it briefly in the reply and ask before writing it into the files. Keep notes terse; he wants to actually read them.
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: dotbot-force-deletes-live-dirs
|
||||
description: dotbot's link defaults are force+relink, so install DELETES a real file or directory at any link target — including the Claude memory dir, which is now a linked directory
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
Every dotbot config in `~/.dotfiles` restates `link: {create: true, relink:
|
||||
true, force: true}` (`meta/base.conf.yaml` and each `meta/hosts/*.conf.yaml` —
|
||||
dotbot does not carry a defaults block between files). `force: true` means
|
||||
`install` removes whatever real file or directory sits at a link target and
|
||||
replaces it with the symlink, without asking.
|
||||
|
||||
As of 2026-10-03 that now includes a *directory* of live data:
|
||||
|
||||
~/.claude/projects/-home-connor/memory: common/claude/memory/-home-connor
|
||||
|
||||
**Why this matters:** before that link existed, the repo held 6 plain copies of
|
||||
memory files whose `MEMORY.md` was missing all 14 entries written after
|
||||
2026-08-31 — copies drift silently because Claude Code only ever reads the live
|
||||
path. Linking fixes the drift, but it also means that on any machine where the
|
||||
live memory dir holds files not yet in the repo, `install` will delete them.
|
||||
|
||||
**How to apply:** before running `install` on a machine whose Claude memory dir
|
||||
is a real directory rather than a symlink, copy its `*.md` into
|
||||
`common/claude/memory/<sanitized-cwd>/` first and verify with `cmp`. The
|
||||
sanitized name is the cwd with non-alphanumerics replaced by `-`, so it differs
|
||||
per machine: `-home-connor` on mainframe, `-home-connorjohnstone` on sctfw004
|
||||
(user `connorjohnstone`). That is why the memory link lives in
|
||||
`meta/hosts/mainframe.conf.yaml` only.
|
||||
|
||||
Related: [[podman-compose-bind-mount-restart]] — the same shape of trap, where a
|
||||
tool reports success without doing the thing you wanted.
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
name: litellm-extra-body-reasoning
|
||||
description: "How to set reasoning effort per model through LiteLLM, and which routes are measured vs assumed"
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: reference
|
||||
originSessionId: 20b564c4-d665-445f-a1c7-25f9e6e666c1
|
||||
modified: 2026-09-17T13:42:14.834Z
|
||||
---
|
||||
|
||||
**GLM-5.3 (authoritative, from https://docs.z.ai/guides/capabilities/thinking):**
|
||||
|
||||
- `reasoning_effort` is the control. **Default is `max`** — which means "think
|
||||
until done", not "think a lot". An unset GLM will spend an unbounded number
|
||||
of reasoning tokens on a large input, and that is documented behaviour, not a
|
||||
fault.
|
||||
- GLM-5.3 / 5.3-Flash accept **`max`, `high`, `low` ONLY**. Not `medium` — an
|
||||
invalid value appears to fall back to the `max` default rather than erroring,
|
||||
which looks exactly like a hang.
|
||||
- ⚠ **GLM-5.3 cannot disable thinking.** `thinking: {type: "disabled"}` is
|
||||
documented to error on 5.3/5.3-Flash. It is supported on 5.2 and earlier.
|
||||
Measured against the coding-plan endpoint it returned HTTP 200 and cut
|
||||
reasoning to 61 tokens anyway — an undocumented path with public reports of
|
||||
being both silently ignored and erroring (code 1210). **Do not rely on it;
|
||||
use `reasoning_effort`.**
|
||||
|
||||
**gpt-oss-20b (local, llama.cpp):** `extra_body: {chat_template_kwargs:
|
||||
{reasoning_effort: "low"}}` — 7,109 → 393 completion tokens, 148 s → 8 s on one
|
||||
identical prompt. Plain `extra_body: {reasoning_effort: "low"}` also works but
|
||||
cuts less deeply. Sent top-level it is a 400 `UnsupportedParamsError` naming
|
||||
"openai", which is LiteLLM's adapter for a custom api_base, not the vendor.
|
||||
|
||||
**opencode (agent tier), settled 2026-09-18.** opencode merges a model's
|
||||
`options` into the request body VERBATIM — no camelCase translation, no AI-SDK
|
||||
name mapping — so the value must be wrapped:
|
||||
|
||||
```jsonc
|
||||
"options": { "extra_body": { "reasoning_effort": "low" } } // works
|
||||
"options": { "reasoning_effort": "low" } // silently inert
|
||||
```
|
||||
|
||||
Proved binary with `max_tokens: 1`: flat and `extra_body`-wrapped both gave
|
||||
`finish_reason: "length"`; `maxOutputTokens: 1` gave a normal reply. **There is
|
||||
no model-level `body` field** — opencode 1.18.25's schema has options, headers,
|
||||
limit, cost, reasoning, temperature, and no body. ⚠ Still unexplained: a flat
|
||||
`reasoning_effort` should have 400'd and returned 200 instead.
|
||||
|
||||
**What is measured vs assumed.** `extra_body` is a client-side mechanism
|
||||
(OpenAI SDK / LiteLLM) for putting arbitrary fields in the request body; z.ai
|
||||
itself documents these as ordinary top-level body fields, so the need for
|
||||
`extra_body` is a LiteLLM routing fact, not a vendor one. Confirmed by
|
||||
measurement for `thinking` on GLM (500 top-level), `reasoning_effort` on
|
||||
gpt-oss (400 top-level), and — tested 2026-09-18, streaming AND non-streaming —
|
||||
`reasoning_effort` top-level to GLM: **400**, `litellm.UnsupportedParamsError:
|
||||
zai does not support parameters`. LiteLLM does NOT route it natively.
|
||||
|
||||
⚠ An `extra_body` 200 proves less than an ordinary 200. `drop_params: false`
|
||||
makes an unknown *top-level* parameter a loud 400, but an unrecognised key
|
||||
nested in `extra_body` is ignored and still returns 200 — so a knob can be set
|
||||
"successfully" and do nothing. Prove effect from token counts, never the status
|
||||
code.
|
||||
|
||||
Related: [[reasoning-tokens-vs-completion-tokens]].
|
||||
Details: `~/guides/llm/robots.md` §19, `~/docs/projects/llmbench/TRAPS.md`.
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: llamacpp-offload-method
|
||||
description: -ngl alone is the wrong knob for partial offload; full offload is a cliff, and -ot/KV-quant/MTP are the levers llmbench never used
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
**Tuning `-ngl` alone is the naive method, and every speed number recorded on
|
||||
mainframe before 2026-09-28 used it.** `llmbench`'s `ctxfit` ladders `-ngl`
|
||||
and nothing else, so each recorded value is the optimum of the wrong
|
||||
parameter.
|
||||
|
||||
**Full offload is a CLIFF, not a slope.** Qwen3.8-27B, 65 blocks, measured
|
||||
2026-09-28 (short/medium/long t/s):
|
||||
|
||||
UD-Q4_K_XL 18.0 GB -ngl 36 6.58 / 5.44 / 3.20
|
||||
GSQ IQ3_S 11.8 GB -ngl 56 6.67 / 5.45 / 3.34
|
||||
GSQ IQ3_XXS 9.8 GB -ngl 99 13.31 / 11.75 / 8.52
|
||||
|
||||
Going 36 -> 56 layers resident bought ~0%. Going 56 -> 65 (all of them)
|
||||
bought **+155%** at long context. With ANY layer on CPU every token pays a
|
||||
PCIe round trip; removing the last one removes the whole penalty rather than
|
||||
a proportional share. A prediction of ~35% from a linear "3.5x per CPU layer"
|
||||
model was badly wrong. ttft collapsed too: 24,255 ms -> 313 ms warm.
|
||||
|
||||
**Three levers llama.cpp offers that were never used here:**
|
||||
|
||||
- `--override-tensor "blk\.(...)\.ffn_.*=CPU"` with `-ngl 99`. Spills only
|
||||
FFN tensors, which are large and latency-tolerant, keeping ALL attention
|
||||
resident -- attention is small and used constantly. `-ngl N` instead spills
|
||||
whole blocks, attention included. This is the right knob whenever a model
|
||||
does NOT fully fit.
|
||||
- `-ctk q4_0 -ctv q4_0` quantized KV. Once weights fit, this buys CONTEXT
|
||||
rather than speed.
|
||||
- `--spec-type draft-mtp --spec-draft-n-max 2` with a `-mtp` GGUF. Reported
|
||||
88% acceptance, 2.76 mean accepted tokens -- near 2x decode, independent of
|
||||
offload. Both Qwen3.8's MTP head and Glimmer's DFlash drafter sat unused.
|
||||
|
||||
**Why:** a public report runs Qwen3.8-27B at **~9.7 t/s with 96K context on
|
||||
the same RTX 3060 12GB using Q4_K_S** -- a LARGER quant than ones dismissed
|
||||
here as too slow. "Too big to be fast" was never established; only "too big
|
||||
to be fast when you spill whole layers".
|
||||
|
||||
**How to apply:** when a model does not fully fit, use `-ngl 99` plus an
|
||||
`-ot` FFN regex and tune the block count, rather than lowering `-ngl`. Re-test
|
||||
anything previously disqualified on speed -- the verdict may be an artifact of
|
||||
the method. `ctxfit` needs to search FFN placement, not just `-ngl`.
|
||||
See [[npu-setting-clamps-memory]].
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
name: local-code-autocomplete
|
||||
description: "Local code autocomplete (FIM) works on mainframe; qwen3-coder-30b is the best model for it despite being written off for capacity"
|
||||
metadata:
|
||||
type: reference
|
||||
---
|
||||
|
||||
Measured 2026-09-18 (llmbench suite 6b, `fim`).
|
||||
|
||||
**Local code autocomplete is viable on this box.** Route: a raw
|
||||
`/v1/completions` through LiteLLM with the model's FIM control tokens in the
|
||||
prompt. ⚠ **`/infill` is 404 through the proxy** — not exposed.
|
||||
|
||||
**FIM is a vocabulary feature, so only two local models can do it at all:**
|
||||
|
||||
| model | FIM tokens | exact | TTFT warm |
|
||||
|---|---|--:|--:|
|
||||
| `qwen3-coder-30b` | yes | **0.56** | **62 ms** |
|
||||
| `qwen3-next-80b-128k` | yes | 0.25 | 149 ms |
|
||||
| `gpt-oss-20b`, `gemma3-12b` | **none** | — | — |
|
||||
|
||||
⚠ **Check the GGUF VOCAB, not the metadata keys.** None of these four declare
|
||||
`tokenizer.ggml.fim_*_token_id`; the tokens are in the vocab regardless. A
|
||||
metadata check says "no local model can infill" and is a false negative.
|
||||
|
||||
⚠ **`qwen3-coder-30b`'s "SKIPPED FOR CAPACITY" verdict does not apply here.**
|
||||
That was recorded against the agent tier, where 128k is the floor.
|
||||
Autocomplete needs a few thousand tokens around the cursor. The verdict was
|
||||
nearly inherited without checking its scope.
|
||||
|
||||
⚠ **Exact match is a FLOOR.** Of its four misses, one is correct code (a valid
|
||||
`match` arm both models independently prefer over upstream's) and one is
|
||||
defensible. Read completions before quoting the number:
|
||||
`uv run python tools/inspect_fim.py --only differs`.
|
||||
|
||||
Open question is not capability but **eviction**: a local autocomplete tier
|
||||
fires per keystroke and llama-swap runs one llama-server — see
|
||||
[[podman-compose-bind-mount-restart]] context and `robots.md` §11.
|
||||
|
||||
Related: [[litellm-extra-body-reasoning]] for how parameters reach the model.
|
||||
|
||||
---
|
||||
|
||||
**2026-09-29 — the failure mode to check first, and a tuning trap.**
|
||||
|
||||
Two independent things broke autocomplete on the same day; symptoms are
|
||||
identical (no ghost text), so check them in this order.
|
||||
|
||||
1. **`rbw` locked → minuet never sends a request at all.** There is no
|
||||
`LITELLM_KEY_NVIM` in `~/.secrets.zsh`; the key comes from `rbw` only, and
|
||||
minuet caches "no key" for the whole nvim session. Tell-tale: ZERO requests
|
||||
in the llama-swap and LiteLLM logs, not slow ones. Confirm in one command:
|
||||
`nvim --headless +MinuetStatus +qa` prints `key: NOT FOUND`. Fix: `rbw
|
||||
unlock`, then restart nvim.
|
||||
|
||||
2. ⚠ **Do not tune qwen3-coder-30b for decode throughput.** A `servefit sweep`
|
||||
set `-b 256 -ub 128` for +21% decode. Its only consumer is FIM autocomplete,
|
||||
which emits 32 tokens but prefills the whole cursor context — **TTFT is
|
||||
prefill-bound and decode is nearly irrelevant.** Small batches cut prefill
|
||||
from ~1150 to ~445 tok/s, putting a 3.7k-token context at 5.1 s against
|
||||
minuet's 3 s `request_timeout`: every completion aborted client-side.
|
||||
Restored to `-b 2048 -ub 512` (keeping `-ncmoe 20` and q4_0 KV — the A/B
|
||||
isolated `-b`/`-ub`). Costs ~92 MiB of VRAM headroom, 541 → 449 MiB spare.
|
||||
|
||||
The sweep was not wrong, it was **scoped to the wrong metric** — the same
|
||||
error shape as inheriting this model's "skipped for capacity" verdict above.
|
||||
Benchmark the role, not the model.
|
||||
|
||||
Warm completions measure 0.98 s, matching the 0.7-1.3 s on record. A cold
|
||||
first request on a large file is still ~3.8 s and will exceed the 3 s timeout;
|
||||
raising `request_timeout` in `common/nvim/init.lua` is the open option.
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
name: mainframe-bad-core
|
||||
description: mainframe has one E-core throwing fatal MCEs; it is offlined by a systemd unit and is an RMA candidate
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
mainframe's CPU (Core Ultra 9 285K) has **one bad core: APIC 20**, which is
|
||||
cpu10, an E-core. Three FATAL machine checks in six days, all on that APIC:
|
||||
|
||||
2026-09-16 12:55 Bank 0 f2000000000b0005 MCA 0x0005 internal parity
|
||||
2026-09-21 22:15 Bank 3 be000000e1c40400 MCA 0x0400 internal unclass.
|
||||
2026-09-22 18:49 Bank 3 be000000e1c40400 (byte-identical to previous)
|
||||
|
||||
All carry UC + PCC, so the platform resets instantly -- the journal stops
|
||||
mid-line with no shutdown or panic. Each is recovered from firmware's BERT
|
||||
table on the next boot.
|
||||
|
||||
**Why it is the core and not the config:** the first MCE predates the
|
||||
2026-09-21 XMP + aggressive-CPU-profile change by five days. Memory is
|
||||
exonerated (core-internal banks, empty EDAC, ADDR holding a userspace VIRTUAL
|
||||
address identical to MISC). Microcode was already current (0x122). BIOS was
|
||||
updated 1.A20 -> 1.AE0 on 2026-09-22 to remove two-year-old firmware as a
|
||||
confound before any RMA.
|
||||
|
||||
⚠ The "CPU n" in an MCE line is whichever CPU logged it, not the faulting
|
||||
core -- all three say "CPU 2" and mean APIC 20. Read the APIC field. And the
|
||||
APIC->cpu mapping is not guaranteed stable across boots, so resolve it at
|
||||
runtime rather than hardcoding cpu10.
|
||||
|
||||
**How to apply:** `~/config/host/offline-bad-core.sh` offlines it and installs
|
||||
`offline-bad-core.service` to do so every boot; `--check` prints state plus
|
||||
per-boot MCE counts. Connor wants it left OFF until he is back from
|
||||
California (2026-09-22), because a crash while away is worse than the
|
||||
diagnostic. Re-enabling it on the new BIOS is the test that decides RMA:
|
||||
if APIC 20 faults again on current firmware it is a defective part, and the
|
||||
285K carries Intel's 3-year boxed warranty. Inference is pinned to P-cores
|
||||
(cpuset 0-7, `taskset -c 0-4`), so losing this E-core costs nothing.
|
||||
See [[npu-setting-clamps-memory]].
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: nas-dual-homed-wifi-path
|
||||
description: NAS is dual-homed on 10.0.0.0/24 and all its traffic silently used WiFi instead of the 2.5GbE wire; this was the real cause of slow NFS
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
The NAS has both interfaces live on the SAME subnet:
|
||||
|
||||
enp3s0 10.0.0.3/24 wired, 2500 Mb/s full duplex
|
||||
wlo1 10.0.0.42/24 WiFi, about -61 dBm
|
||||
|
||||
Two independent faults made all NAS traffic take the WiFi path:
|
||||
|
||||
1. Forward (mainframe -> NAS). With `arp_ignore=0` the NAS answered ARP for
|
||||
10.0.0.3 from wlo1, so mainframe cached `10.0.0.3 -> f8:3d:c6:2f:b6:68`,
|
||||
the WiFi MAC. Packets addressed to the wired IP were delivered over WiFi.
|
||||
Wired MAC is `a0:ad:9f:88:d5:fa` — compare against `ip neigh show 10.0.0.3`.
|
||||
2. Return (NAS -> mainframe). NetworkManager installed NO 10.0.0.0/24 route
|
||||
for enp3s0 at all, only `dev wlo1 ... metric 600`, so every reply went out
|
||||
over WiFi regardless of how the request arrived.
|
||||
|
||||
Fixing only one of the two barely helps; both must be right. Measured on
|
||||
2026-10-02, small-file creates over NFS:
|
||||
|
||||
WiFi path (both wrong) 456 ms/file, ~4 MB/s
|
||||
ARP fixed only 330 ms/file
|
||||
both directions wired 60 ms/file, ~38 MB/s
|
||||
|
||||
Runtime fix applied (NOT persisted — lost on NAS reboot or NM restart):
|
||||
`sysctl -w net.ipv4.conf.all.arp_ignore=1 net.ipv4.conf.all.arp_announce=2`
|
||||
and `ip route replace 10.0.0.0/24 dev enp3s0 proto kernel scope link src
|
||||
10.0.0.3 metric 100`. Persisting it means an NM `ipv4.route-metric` on the
|
||||
wired profile below wifi's 600, plus the sysctls in `/etc/sysctl.d/`. The
|
||||
cleanest answer is probably to stop the stationary NAS joining WiFi at all.
|
||||
|
||||
Even fully wired, NFS is ~60 ms per small-file create against an SSD's ~0.01 ms.
|
||||
The array is LVM RAID1 over a Seagate ST8000DM004, which is SMR, so small
|
||||
synchronous writes stay slow; local dd on the NAS gets 169 MB/s sequential.
|
||||
Do not put build trees or git working copies on it.
|
||||
|
||||
CORRECTION to [[nfs-pnfs-scsi-layout-wedge]]: the pNFS SCSI fence failure was
|
||||
real and did wedge the mount, but it was not the cause of the chronic
|
||||
slowness — it was most likely a consequence of timeouts over the flaky WiFi
|
||||
link. The original reasoning was unsound because the pNFS fault was found
|
||||
first and fully explained the symptoms in front of me, so I stopped looking;
|
||||
the giveaway I initially read past was ping to the NAS swinging 0.5 ms to
|
||||
180 ms, which no amount of NFS state can cause.
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
name: nfs-mountpoints-immutable
|
||||
description: The seven NAS mountpoint dirs are chattr +i so services cannot write into them when NFS is down; use chattr -i to modify one
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
Every NAS mountpoint directory on mainframe carries the ext4 immutable flag:
|
||||
|
||||
/home/connor/{docs,books,audio,photo,video,downloads} and /mnt/nas
|
||||
|
||||
Set 2026-10-02. **If you see `Operation not permitted` writing to one of these
|
||||
paths, check whether NFS is actually mounted before doing anything else.** That
|
||||
error is the feature working, not a bug.
|
||||
|
||||
Why. When NFS is not mounted the mountpoint is just an empty local directory and
|
||||
every service happily writes into it. The data lands on local disk, is hidden the
|
||||
moment the mount returns, and nobody notices. By 2026-10-02 that had silently
|
||||
accumulated 106 GB across `downloads` (86 G) and `video` (20 G) going back to
|
||||
Oct 2025, plus Viseron NVR footage and transmission downloads. With `+i` those
|
||||
writes fail immediately and loudly instead.
|
||||
|
||||
Verified behavior (tested, not assumed):
|
||||
|
||||
- write to unmounted mountpoint -> EPERM, even as root
|
||||
- `mount` onto an immutable dir -> works
|
||||
- `umount` -> works
|
||||
- writes to the mounted filesystem -> entirely normal
|
||||
|
||||
The flag lives on the UNDERLYING directory, which is covered while NFS is
|
||||
mounted. To inspect or change it without unmounting, reach the real inode
|
||||
through a bind mount:
|
||||
|
||||
sudo mount --bind /home /mnt/under
|
||||
lsattr -d /mnt/under/connor/docs
|
||||
sudo chattr -i /mnt/under/connor/docs # to undo
|
||||
sudo umount /mnt/under
|
||||
|
||||
`/mnt/nas` lives on `/`, not `/home`, so it needs `mount --bind / /mnt/rootbind`
|
||||
instead. The flag is stored in the inode and survives reboot; there is no unit
|
||||
or script behind it.
|
||||
|
||||
Connor declined the other two layers considered (systemd `RequiresMountsFor=`
|
||||
and mount alerting) on 2026-10-02 — do not add them without being asked.
|
||||
Scoping mattered for the systemd one: applying it to `podman-restart.service`
|
||||
wholesale would take the whole stack down on a NAS-down boot, including gitea,
|
||||
bitwarden, authelia and litellm, none of which touch NFS.
|
||||
|
||||
Related: [[nas-dual-homed-wifi-path]], [[nfs-root-squash-loses-mtimes]]
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
name: nfs-pnfs-scsi-layout-wedge
|
||||
description: NAS NFS writes wedging to a crawl is pNFS SCSI layouts failing to fence on an LVM RAID1 target; fix is blacklisting blocklayoutdriver on the client
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
Symptom seen 2026-10-02: every write to the NAS mounts crawls or stalls for
|
||||
60s+, reads stay fast, and the box bogs down. `/proc/meminfo` showed 8.5 GB
|
||||
Dirty pinned on the NFS bdi with `BdiWriteBandwidth: 28 kBps`, and processes
|
||||
stuck in D state on `balance_dirty_pages` / `vfs_unlink`.
|
||||
|
||||
Cause chain. The NAS exports XFS on `/dev/mapper/raid-main` (dm-4), an LVM
|
||||
RAID1 target. Linux nfsd automatically offers pNFS SCSI layouts for XFS, and
|
||||
the client accepts — `/proc/self/mountstats` showed `pnfs=LAYOUT_SCSI`.
|
||||
Fencing a SCSI layout uses SCSI persistent reservations, which the dm RAID1
|
||||
target does not implement, so the recall can never succeed:
|
||||
|
||||
nfsd4_layout_fence_worker: FENCE failed client[10.0.0.2:938] clid[13] device[dm-4]
|
||||
|
||||
The client lease then expires (`lease_expired` climbing, 90s lease) with dirty
|
||||
pages pinned to a layout that will never be released.
|
||||
|
||||
There is no SAN here — mainframe reaches the NAS over plain Ethernet and could
|
||||
never use a SCSI layout at all. Fix applied on mainframe:
|
||||
`/etc/modprobe.d/nfs-no-scsi-layout.conf` blacklisting `blocklayoutdriver`,
|
||||
which implements both block and SCSI layouts. Without it the client does
|
||||
ordinary NFS I/O over the wire.
|
||||
|
||||
Recovery is NOT possible without rebooting the NAS once it wedges. Both
|
||||
`echo expire > /proc/fs/nfsd/clients/<id>/ctl` and `systemctl restart
|
||||
nfs-server` hang in D state, because each has to release the same unfenceable
|
||||
layout; the restart leaves nfs-server stuck in `deactivating/final-sigterm`
|
||||
and the NAS then cannot write to its own `/nas` either.
|
||||
|
||||
Diagnosis commands worth reusing: `sudo cat /sys/kernel/debug/bdi/0:58/stats`
|
||||
(all seven NAS mounts share bdi 0:58, so one fault stalls every export),
|
||||
`/proc/fs/nfsd/clients/*/info` and `/states` on the NAS for stale client
|
||||
records, and `grep -o 'lease_expired=[0-9]*' /proc/self/mountstats`.
|
||||
|
||||
Related: [[podman-compose-bind-mount-restart]]
|
||||
|
||||
CORRECTION (same day): this fault was real and did wedge the mount, but it was
|
||||
NOT the cause of the chronic slow NFS writes. That was the NAS routing all its
|
||||
traffic over WiFi — see [[nas-dual-homed-wifi-path]]. The layout fence most
|
||||
likely failed because of timeouts over that flaky link. Keep the
|
||||
blocklayoutdriver blacklist regardless; SCSI layouts can never work here.
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: nfs-root-squash-loses-mtimes
|
||||
description: sudo cp -a onto the NAS docs/video/etc exports silently loses mtimes because only /nas has no_root_squash; copy as connor instead
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
Copying with `sudo cp -a` onto any of the per-directory NAS exports
|
||||
(`/nas/docs`, `/nas/video`, …) silently discards the original mtimes and stamps
|
||||
every file with the copy time. `cp` reports success and the file contents are
|
||||
correct, so it is easy to miss.
|
||||
|
||||
Cause. `/etc/exports` on the NAS gives `no_root_squash` to `/nas` ONLY:
|
||||
|
||||
/nas 10.0.0.0/28(rw,fsid=root,no_root_squash)
|
||||
/nas/docs 10.0.0.0/28(rw,async)
|
||||
|
||||
So on `/nas/docs` the client's root is squashed to anonymous. POSIX allows
|
||||
setting an *arbitrary* mtime only to the file's owner or CAP_FOWNER; setting it
|
||||
to *now* needs only write permission. Squashed root has the latter and not the
|
||||
former, so nfsd downgrades the SETATTR and writes the current time. A root-owned
|
||||
file on that export cannot have its mtime set from the client at all.
|
||||
|
||||
The tell: the client reads back the correct mtime from its attribute cache while
|
||||
the server has the wrong one. Always confirm with
|
||||
`ssh nas stat -c %y /nas/docs/...` — the client's view will lie to you.
|
||||
|
||||
Fix, in order of preference:
|
||||
1. Copy as `connor`, not with sudo. The new files are connor-owned, and connor
|
||||
can then set arbitrary mtimes on them.
|
||||
2. If the files are already root-owned, `sudo chown -R connor:connor` ON THE NAS
|
||||
over ssh (the client cannot chown there either), then set mtimes as connor.
|
||||
|
||||
Observed 2026-10-02 archiving 20,116 viseron files; the first pass "fixed 19,924
|
||||
mtimes" and persisted none of them. Related: [[nas-dual-homed-wifi-path]].
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: npu-setting-clamps-memory
|
||||
description: MSI BIOS NPU/AI options clamp DDR5 to 5/6 of XMP on mainframe, and the NPU is unusable by the inference stack anyway
|
||||
metadata:
|
||||
type: project
|
||||
---
|
||||
|
||||
On mainframe (MSI PRO Z890-P WIFI, Core Ultra 9 285K), **enabling the BIOS
|
||||
NPU / "AI performance" options clamps memory to 5333 MT/s even with XMP
|
||||
Profile 1 correctly selected and reporting 6400.** Turning them off restores
|
||||
6400. Measured 2026-09-22, both directions.
|
||||
|
||||
**Why:** 5333 is exactly `6400 x 5/6`, and is NOT a JEDEC bin (those are
|
||||
multiples of 400: 4800/5200/5600/6000/6400). A failed memory training falls
|
||||
back to a JEDEC bin -- this box fell back to 5200 pre-XMP. A non-JEDEC
|
||||
fraction of the target means a ratio is being imposed, not a fallback taken.
|
||||
Arrow Lake's NPU sits on the SoC tile with the memory controller, so an
|
||||
SoC-side ratio change reaches DRAM. Cost: 102.4 -> 85.3 GB/s, a 16.7% loss of
|
||||
the bandwidth local inference is bound by.
|
||||
|
||||
**The setting is strictly negative here despite its marketing.** llama.cpp in
|
||||
this stack ships `libggml-cuda.so` plus CPU variants and has NO OpenVINO,
|
||||
SYCL, Level Zero or NPU backend; `intel_vpu` loads with zero users. There is
|
||||
no code path that can dispatch to the NPU, so it contributes nothing while
|
||||
taking memory bandwidth.
|
||||
|
||||
**How to apply:** leave the NPU/AI BIOS options OFF. If DRAM ever reads a
|
||||
non-JEDEC speed, suspect an imposed ratio (check SoC/NPU/fabric settings)
|
||||
rather than memory instability. `llmbench host-state` records ram_mts and
|
||||
xmp_enabled every run, so the history is queryable. See [[mainframe-bad-core]].
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: owui-image-generation
|
||||
description: "Image generation through LiteLLM works, and Open WebUI's default 512x512 size is rejected by every image model"
|
||||
metadata:
|
||||
type: reference
|
||||
---
|
||||
|
||||
Wired and verified 2026-09-18: Open WebUI → LiteLLM → OpenAI on
|
||||
`openai/gpt-image-2`, tested with the `open-webui` virtual key rather than the
|
||||
master key.
|
||||
|
||||
⚠ **Open WebUI ships `image_generation.size` as `512x512` and EVERY image model
|
||||
on this account rejects it.** Same key, same minute:
|
||||
|
||||
- `gpt-image-1` — 400, *"Supported sizes are 1024x1024, 1024x1536, 1536x1024,
|
||||
and auto."*
|
||||
- `gpt-image-2`, `gpt-image-2.5-flare` — 400, *"below the current minimum pixel
|
||||
budget"*
|
||||
|
||||
`1024x1024` works. So a correct LiteLLM entry, a correct key allowlist and a
|
||||
correct engine still give a feature that fails on every generation, surfaced in
|
||||
the UI as a generic error. **The default is the trap.**
|
||||
|
||||
Four changes are all required and the third is silent: a LiteLLM entry with
|
||||
`model_info.mode: image_generation` (NOT `extra_body: {store: false}` — that is
|
||||
a Responses-API field); `sudo podman restart connor_litellm_1`; `POST
|
||||
/key/update` on the `open-webui` key; and the Open WebUI config rows via
|
||||
`~/config/open-webui/apply-imagegen.py` with the container stopped.
|
||||
|
||||
Related: [[podman-compose-bind-mount-restart]].
|
||||
Details: `~/guides/llm/robots.md` §20.
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: pattern-kill-matches-wrong-process
|
||||
description: pkill/pgrep/kill %n by pattern repeatedly hit the wrong process or missed the right one; capture PIDs instead
|
||||
metadata:
|
||||
type: feedback
|
||||
---
|
||||
|
||||
**Do not manage processes by pattern.** Capture the PID when starting
|
||||
something and act on that PID. `pgrep -f`, `pkill -f` and `kill %n` have gone
|
||||
wrong repeatedly across sessions, in every direction:
|
||||
|
||||
- `pgrep -f "podman image prune"` matched the monitor's own command line, so
|
||||
it waited forever on itself. Connor: "I really don't understand why it's
|
||||
taking this long."
|
||||
- `pgrep -f "llmbench.cli run"` matched his `tmux new -As llmbench` session
|
||||
and reported a runner that had finished hours earlier. Eight hours of idle
|
||||
GPU followed, because the benchmark was reported as running when it was not.
|
||||
- `hf download ... | head -5 & ... kill %1` killed the shell job but not the
|
||||
`hf` child, which kept pulling the WHOLE repo -- 69 GB into
|
||||
`~/.cache/huggingface` before it was spotted in a process listing.
|
||||
- `pkill -f "llama-server.*9199"` killed the shell that was running the
|
||||
heredoc writing the next script, so the script silently never existed.
|
||||
|
||||
**Why:** a pattern matches the literal command line of EVERY process,
|
||||
including the shell running the command that contains the pattern, monitors
|
||||
built from it, tmux sessions named after the project, and editors with the
|
||||
path open. It also does not follow pipelines or reach children a job-control
|
||||
kill does not own.
|
||||
|
||||
**How to apply:** save `$!` (or the pid from `ps`) and kill that. Where a
|
||||
pattern is unavoidable, exclude self (`grep -v $$`), print what matched before
|
||||
killing it, and VERIFY afterwards with `ps -p <pid>` rather than assuming the
|
||||
kill worked -- several of these failures were "I killed it" followed by the
|
||||
process still running. To check whether work finished, prefer the durable
|
||||
record (a run's `ended_at` in the database) over process presence.
|
||||
@@ -28,3 +28,11 @@ within a second or two from the pre-existing state. llama-swap is the exception:
|
||||
it polls its own config every 2s and reloads itself.
|
||||
|
||||
Related: [[zen-tos-hold]]
|
||||
|
||||
**Single-file bind mounts also go stale when the file is replaced.** `./Caddyfile`
|
||||
is mounted as one file. Claude's Edit tool (and any atomic-save editor) writes a
|
||||
new inode, so the container keeps the old file: `caddy reload` succeeded and
|
||||
loaded the OLD config, and the new site gave ERR_SSL_PROTOCOL_ERROR (no matching
|
||||
site). Happened 2026-10-20 adding bazarr. Check with
|
||||
`stat -c %i ~/Caddyfile` vs `sudo podman exec connor_caddy_1 stat -c %i /etc/caddy/Caddyfile`;
|
||||
a `podman restart` re-attaches the mount. Directory mounts are not affected.
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
name: podman-nfs-bind-rprivate-blind
|
||||
description: "Containers bind NFS paths rprivate, so a mount landing after container start leaves them permanently blind while podman ps looks fine; fix is podman restart"
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: project
|
||||
originSessionId: 744ca9f1-a40a-422d-9fdb-4c28998f8f25
|
||||
modified: 2026-10-03T02:09:18.003Z
|
||||
---
|
||||
|
||||
The eleven containers that bind NAS paths (`jellyfin`, `jellyseerr`, `sonarr`,
|
||||
`radarr`, `lidarr`, `whisparr`, `prowlarr`, `transmission`, `sftpgo`,
|
||||
`syncthing`, `lazylibrarian`) declare plain binds in `compose.yml`:
|
||||
|
||||
- /home/connor/video:/video
|
||||
|
||||
With no propagation flag podman defaults to `rprivate`. A bind captures the
|
||||
directory as it exists at bind time, so **if the NFS mount lands after the
|
||||
container starts, the container holds the empty pre-mount directory forever.**
|
||||
Nothing re-syncs it and nothing reports it.
|
||||
|
||||
Measured 2026-10-02:
|
||||
|
||||
11:06:10 boot
|
||||
11:06:22 home-connor-video.mount FAILED (mount.nfs: mount system call
|
||||
failed, status=32 -- NAS not ready)
|
||||
11:06:23 podman-restart.service started all containers -> empty bind
|
||||
11:38:01 mount retried -> failed again
|
||||
11:41:22 mount succeeded; no container ever saw it
|
||||
|
||||
Ten hours. Jellyfin playback failed with `Error opening input: No such file or
|
||||
directory` on every title, which reads as a per-file or per-client problem — the
|
||||
report that opened the session was "Chromecast won't play Psych". It was neither
|
||||
Chromecast nor Psych: all 24 bind mounts across all 11 containers were blind.
|
||||
Sonarr logged 376 errors, all failed imports, no deletions.
|
||||
|
||||
**Why:** `podman ps` says `Up`, healthchecks pass, the host path is correct and
|
||||
fully populated, and `ls /home/connor/video` from a shell looks perfect. The
|
||||
breakage is only visible from inside the container's mount namespace.
|
||||
|
||||
**How to apply:** compare host and container counts rather than trusting either
|
||||
alone, using each container's REAL bind destination — they differ
|
||||
(`sonarr` is `/mnt/nas -> /nas`, `transmission` is `downloads -> /data`,
|
||||
`sftpgo` is `/srv/sftpgo/data/connor/*`). Checking a guessed path returns 0 for
|
||||
a directory that does not exist and fakes the symptom:
|
||||
|
||||
sudo podman inspect <ctr> --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}
|
||||
{{end}}'
|
||||
sudo podman exec <ctr> sh -c 'ls -A <dest> | wc -l'
|
||||
|
||||
Fix is `sudo podman restart <ctr>` — the bind re-resolves against the live
|
||||
mount. No compose change needed, and `podman-compose up -d` is not required
|
||||
because the definition has not changed.
|
||||
|
||||
Tested, not assumed: a tmpfs mounted onto a shared host path after container
|
||||
start is seen by an `rslave` bind (1 entry) and NOT by an `rprivate` bind
|
||||
(0 entries). So `:rslave` on these binds would make containers follow late
|
||||
mounts and remounts, including the NAS-reboot case in
|
||||
[[nfs-pnfs-scsi-layout-wedge]]. **Connor declined this on 2026-10-02 along with
|
||||
the boot-mount retry — do not add either without being asked.** This is the
|
||||
same scoping call recorded in [[nfs-mountpoints-immutable]].
|
||||
|
||||
Unrelated but found in the same pass: an 847 MB file named
|
||||
`Taskmaster S22E05 Youre Nice 1080p WEB H264-NTb.exe` in
|
||||
`downloads/completed/tv-sonarr/`. Flagged to Connor as a malware-shaped name.
|
||||
|
||||
Related: [[nfs-mountpoints-immutable]], [[podman-compose-bind-mount-restart]],
|
||||
[[nfs-pnfs-scsi-layout-wedge]], [[nas-dual-homed-wifi-path]]
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: reasoning-tokens-vs-completion-tokens
|
||||
description: "Reasoning tokens come out of max_tokens, and providers disagree about whether they count as completion tokens"
|
||||
metadata:
|
||||
node_type: memory
|
||||
type: reference
|
||||
originSessionId: 20b564c4-d665-445f-a1c7-25f9e6e666c1
|
||||
modified: 2026-09-17T13:35:03.190Z
|
||||
---
|
||||
|
||||
Two facts that together produce an empty HTTP 200 that looks like a model
|
||||
saying nothing:
|
||||
|
||||
1. **Reasoning tokens come out of `max_tokens`.** A cap set anywhere near the
|
||||
size of the expected *answer* is spent entirely on thinking, and the request
|
||||
returns no content. Already documented in `~/config/litellm/config.yaml`'s
|
||||
OpenAI block ("seen at max_tokens 16 and again at 500 on gpt-5.1-codex-max,
|
||||
which then worked unchanged at 4000").
|
||||
|
||||
2. **Providers disagree about whether reasoning counts as completion.**
|
||||
`gpt-oss` via llama.cpp folds reasoning INTO `completion_tokens`;
|
||||
`glm/glm-5.3` reports `completion_tokens: 0` beside
|
||||
`completion_tokens_details.reasoning_tokens: 1023` for the same behaviour.
|
||||
Reading only `completion_tokens` shows a model that generated nothing.
|
||||
|
||||
`finish_reason == "length"` is the authoritative signal — check it before any
|
||||
token arithmetic. Add `reasoning_tokens` to `completion_tokens` whenever
|
||||
computing a rate or testing a budget; a decode figure divided by completion
|
||||
tokens alone reports a model that emitted 1,023 reasoning tokens and 12 of
|
||||
content as running at the rate of the 12.
|
||||
|
||||
**Do not cap `max_tokens` to a budget in a benchmark.** The cap silently
|
||||
becomes the thing being measured, and an empty 200 grades as "found nothing" —
|
||||
which on a code-review suite's defect-free control arm scores *perfectly*.
|
||||
Use a ceiling far above observed spend, record it in the result row, and treat
|
||||
an attempt that hits it as unusable rather than as an answer.
|
||||
|
||||
Related: [[litellm-extra-body-reasoning]] for turning the reasoning down.
|
||||
@@ -2,12 +2,15 @@
|
||||
"enabledPlugins": {
|
||||
"rust-analyzer-lsp@claude-plugins-official": true
|
||||
},
|
||||
"permissions": {
|
||||
"defaultMode": "auto"
|
||||
},
|
||||
"tui": "fullscreen",
|
||||
"theme": "dark",
|
||||
"autoMode": {
|
||||
"soft_deny": [
|
||||
"$defaults",
|
||||
"Bash(podman rm:*) in /home/connor/docs/projects/runway \u2014 destructive container removal",
|
||||
"Bash(podman rm:*) in /home/connor/projects/runway \u2014 destructive container removal",
|
||||
"Bash(rm -rf:*) targeting baikal test containers in crates/runway-caldav/tests/baikal",
|
||||
"Bash(podman rm -f:*) \u2014 destructive podman container removal, frequently used CLI in this environment",
|
||||
"Bash(rm -rf ~/.env*) \u2014 sensitive credential files (.env, .env.bak-103643) found in config scan"
|
||||
@@ -43,7 +46,7 @@
|
||||
"**Sensitive remote targets**: any namespace, host, or container whose name carries `prod` or `production` as a whole word or name segment (hyphen/underscore/dot-delimited)",
|
||||
"### User-specific",
|
||||
"**Primary use of Claude Code**: software development (Rust/Leptos/Axum CalDAV client)",
|
||||
"**Trusted repo**: /home/connor/docs/projects/runway (working directory) and its remote ssh://git.rcjohnstone.com:2222/connor/runway.git \u2014 visibility assumed private per above, so confidential material is fine to commit here but secrets are never cleared into it by visibility alone",
|
||||
"**Trusted repo**: /home/connor/projects/runway (working directory) and its remote ssh://git.rcjohnstone.com:2222/connor/runway.git \u2014 visibility assumed private per above, so confidential material is fine to commit here but secrets are never cleared into it by visibility alone",
|
||||
"**Org-specific CLIs**: None configured",
|
||||
"routine podman/systemctl/journalctl operations under this project's local/self-hosted infra",
|
||||
"**Primary use of Claude Code**: personal/hobby systems administration \u2014 home-lab container management (podman/podman-compose), self-hosted services (rcjohnstone.com subdomains), and local system maintenance",
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
name: rust-crate-release
|
||||
description: Release and benchmarking procedure for Connor's Rust crates (ordinary-diffeq, astrokit) — the branch/tag/publish sequence, and the criterion baseline method with mainframe's measured noise floor. Use when cutting a release, bumping a version, publishing to crates.io, or measuring whether a change cost performance.
|
||||
---
|
||||
|
||||
# Releasing and benchmarking a Rust crate
|
||||
|
||||
The standing rules that apply to every edit — one coherent change at a time,
|
||||
zero warnings, `cargo fmt`, 0.x breaking changes are minor bumps, a Gitea issue
|
||||
per feature — stay in `~/.claude/CLAUDE.md` under "Rust libraries". This file
|
||||
holds the two procedures you only need when you are actually releasing or
|
||||
benchmarking.
|
||||
|
||||
## Benchmarking, when a change could plausibly cost something
|
||||
|
||||
criterion, and measure rather than assert:
|
||||
|
||||
- Baseline and change in one sitting: `git stash push -- src`, `cargo bench`,
|
||||
`git stash pop`, `cargo bench` again. Criterion compares against the
|
||||
baseline it just saved and prints the delta.
|
||||
- Then run the identical binary once more to see the noise floor. On mainframe
|
||||
that drift is around a percent, larger than most "no regression" claims, so
|
||||
a 0.9% reading means nothing until you know it.
|
||||
- Never run two cargo invocations at once. They fight over the target lock,
|
||||
starve each other on a loaded box, and the timings come out worthless.
|
||||
- Write down the measured numbers, not the conclusion drawn from them.
|
||||
|
||||
## The release process
|
||||
|
||||
Once the work itself is committed and pushed:
|
||||
|
||||
```sh
|
||||
git checkout -b release/0.6.0
|
||||
# bump version in Cargo.toml, nothing else
|
||||
git commit -am "Release 0.6.0" # body: what changed, and what it breaks
|
||||
git checkout main && git merge --no-ff release/0.6.0
|
||||
git tag -a v0.6.0 -m "<crate> 0.6.0 - <one line>"
|
||||
git push origin main release/0.6.0 v0.6.0
|
||||
cargo package --list && cargo publish --dry-run && cargo publish
|
||||
```
|
||||
|
||||
The release commit touches `Cargo.toml` alone, so the tag is the tree that was
|
||||
tested. `cargo package --list` is the check that `exclude` is doing its job and
|
||||
no scratch file is going out. These repos have no Gitea release entries — the
|
||||
annotated tag is the release, and Gitea lists it under Tags. A publish cannot
|
||||
be undone; a bad version can only be yanked.
|
||||
@@ -179,6 +179,10 @@ minuet.setup({
|
||||
-- Default is 'warn', which hides request failures. If completions stop
|
||||
-- appearing, set this to 'debug' and the reason shows up in `:messages`.
|
||||
notify = "warn",
|
||||
-- Default 3. Measured 2026-10-22 at -ncmoe 22: a fresh ~3k-token context
|
||||
-- takes 4.8 s to prefill, the same context again 0.8 s. At 3 s the first
|
||||
-- completion in a file times out, and it may not leave a cache behind.
|
||||
request_timeout = 8,
|
||||
-- Ghost text: there is no completion engine here (no nvim-cmp, no blink)
|
||||
-- and virtual text is the copilot-shaped one.
|
||||
virtualtext = {
|
||||
|
||||
@@ -65,7 +65,7 @@ aero https://filespace.aero.org/human.aspx?r=4292318470
|
||||
whatsapp https://web.whatsapp.com/
|
||||
sedaro_onedrive https://sedarocorp-my.sharepoint.us/personal/robbie_robertson_sedaro_com/_layouts/15/onedrive.aspx?id=%2Fpersonal%2Frobbie_robertson_sedaro_com%2FDocuments%2F00%20Sedaro%20Drive%2F06%20Contracts%2F15%20RROCI%2FDeliverables%2FOrion_Sedaro_Fileshare&ga=1
|
||||
sedaro_sharepoint https://sedarocorp-my.sharepoint.us/personal/robbie_robertson_sedaro_com/_layouts/15/onedrive.aspx?id=%2Fpersonal%2Frobbie_robertson_sedaro_com%2FDocuments%2F00%20Sedaro%20Drive%2F06%20Contracts%2F15%20RROCI%2FDeliverables%2FOrion_Sedaro_Fileshare&ga=1
|
||||
jellyseerr https://jellyseerr.rcjohnstone.com/
|
||||
seerr https://seerr.rcjohnstone.com/
|
||||
e https://192.168.1.101:8006/#v1:0:=qemu%2F101:4:::::8::
|
||||
element https://element.rcjohnstone.com/
|
||||
aws access portal https://start.us-gov-east-1.us-gov-home.awsapps.com/directory/d-c267218df8#/?tab=accounts
|
||||
|
||||
@@ -11,9 +11,14 @@ UNIT=sway-headless.service
|
||||
IDLE_MINUTES="${STREAM_IDLE_MINUTES:-15}"
|
||||
STAMP="${XDG_RUNTIME_DIR:-/tmp}/stream.lastseen"
|
||||
|
||||
# A Moonlight client holds connections to the control and RTSP ports.
|
||||
# The TCP ports are only held while a session is being launched; once it is
|
||||
# streaming, everything is UDP on unconnected sockets that ss cannot attribute
|
||||
# to a peer. A live NVENC session is what shows a stream in progress.
|
||||
clients_connected() {
|
||||
ss -Htn state established '( sport = :47984 or sport = :47989 or sport = :48010 )' 2>/dev/null | grep -q .
|
||||
ss -Htn state established '( sport = :47984 or sport = :47989 or sport = :48010 )' 2>/dev/null | grep -q . && return 0
|
||||
local sessions
|
||||
sessions=$(nvidia-smi --query-gpu=encoder.stats.sessionCount --format=csv,noheader,nounits 2>/dev/null | head -n1)
|
||||
(( ${sessions:-0} > 0 ))
|
||||
}
|
||||
|
||||
vram() {
|
||||
|
||||
@@ -20,11 +20,43 @@
|
||||
{
|
||||
"do": "sh -c \"swaymsg output HEADLESS-1 mode ${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}@${SUNSHINE_CLIENT_FPS}Hz\"",
|
||||
"undo": "swaymsg output HEADLESS-1 mode 1920x1080@60Hz"
|
||||
},
|
||||
{
|
||||
"do": "swaymsg workspace steam",
|
||||
"undo": "swaymsg workspace number 1"
|
||||
}
|
||||
],
|
||||
"detached": [
|
||||
"setsid steam -gamepadui"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "FreeCAD",
|
||||
"prep-cmd": [
|
||||
{
|
||||
"do": "sh -c \"swaymsg output HEADLESS-1 mode ${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}@${SUNSHINE_CLIENT_FPS}Hz\"",
|
||||
"undo": "swaymsg output HEADLESS-1 mode 1920x1080@60Hz"
|
||||
},
|
||||
{
|
||||
"do": "swaymsg workspace freecad",
|
||||
"undo": "swaymsg workspace number 1"
|
||||
}
|
||||
],
|
||||
"cmd": "freecad"
|
||||
},
|
||||
{
|
||||
"name": "OrcaSlicer",
|
||||
"prep-cmd": [
|
||||
{
|
||||
"do": "sh -c \"swaymsg output HEADLESS-1 mode ${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}@${SUNSHINE_CLIENT_FPS}Hz\"",
|
||||
"undo": "swaymsg output HEADLESS-1 mode 1920x1080@60Hz"
|
||||
},
|
||||
{
|
||||
"do": "swaymsg workspace orcaslicer",
|
||||
"undo": "swaymsg workspace number 1"
|
||||
}
|
||||
],
|
||||
"cmd": "env GDK_BACKEND=x11 WEBKIT_DISABLE_DMABUF_RENDERER=1 SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt orca-slicer"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -28,34 +28,117 @@ exec systemctl --user import-environment WAYLAND_DISPLAY DISPLAY SWAYSOCK XDG_CU
|
||||
# Input arrives from the Moonlight client, so keybinds are worth having even
|
||||
# though nobody is sitting at this machine. Without them the Desktop app
|
||||
# streams an empty screen with no way to open anything.
|
||||
set $mod Mod4
|
||||
#
|
||||
# Mod1 (Alt), not Mod4 (Super). The client machines run Hyprland with SUPER as
|
||||
# their own modifier, and a compositor grabs its own modifier before Moonlight
|
||||
# ever sees the key, so every Super bind here was unreachable from the stream —
|
||||
# it acted on the client instead. Alt is not grabbed there, so it arrives. The
|
||||
# cost is that an Alt bind here shadows the focused app's menu mnemonics
|
||||
# (Alt+F, Alt+D), which is acceptable on a session that shows one fullscreen
|
||||
# app at a time.
|
||||
set $mod Mod1
|
||||
|
||||
# Floating, not tiling. Nobody sits at this machine and nothing here is a
|
||||
# workstation: the stream shows one thing at a time, usually Steam fullscreen.
|
||||
# Tiling only ever meant a dialog box stealing half the screen. Both criteria
|
||||
# are needed - app_id matches Wayland clients, class matches Xwayland ones,
|
||||
# and Steam is an X client.
|
||||
for_window [app_id=".*"] floating enable
|
||||
for_window [class=".*"] floating enable
|
||||
# Tiling, sway's default. This used to float every window, which left a
|
||||
# single-app stream at whatever size the app opened at instead of filling the
|
||||
# output. Each Sunshine app switches to its own workspace first (apps.json), so
|
||||
# its window is alone there and fills the screen. Dialogs sway recognizes as
|
||||
# dialogs still float.
|
||||
|
||||
# Title bars are the handle you grab with the mouse, so floating windows keep
|
||||
# a real border rather than the thin default.
|
||||
default_floating_border normal 2
|
||||
|
||||
# Super+drag moves a window, Super+right-drag resizes it. Without this the
|
||||
# Alt+drag moves a window, Alt+right-drag resizes it. Without this the
|
||||
# only way to move a window is the title bar, which a fullscreen app has none of.
|
||||
floating_modifier $mod normal
|
||||
|
||||
# The stream forwards a mouse, so pointing and clicking is the primary way
|
||||
# around; these are for when the pointer is not enough.
|
||||
bindsym Alt+Tab focus next
|
||||
bindsym Alt+Shift+Tab focus prev
|
||||
bindsym $mod+f fullscreen
|
||||
# Hyprland takes $browser from hosts/<host>/hypr/local-env.conf; mainframe links
|
||||
# no hypr config, so it is named here. qutebrowser, to match the laptops.
|
||||
set $browser qutebrowser
|
||||
|
||||
# These mirror common/hypr/hyprland.conf, so the muscle memory from the laptops
|
||||
# carries over the stream. Only binds whose target exists on this host are
|
||||
# ported: mainframe has no grim/grimblast, no udiskie, no
|
||||
# networkmanager_dmenu, no ydotool, no julia, no ncmpcpp and no swayimg, so
|
||||
# Hyprland's $mainMod+P, Shift+P, M, W, A, S and V are deliberately absent
|
||||
# rather than bound to a missing binary — sway reports nothing when an exec
|
||||
# target does not exist, so a ported bind would look correct and silently do
|
||||
# nothing. The brightness keys are out for the same reason twice over:
|
||||
# brightnessctl is absent and /sys/class/backlight is empty.
|
||||
#
|
||||
# $mainMod+N and Shift+N are out too, less obviously. The daemon here is mako,
|
||||
# not swaync, and mako's do-not-disturb is not built in — it needs a
|
||||
# [mode=do-not-disturb] invisible=1 section, and this host has no mako config at
|
||||
# all. `makoctl mode -t do-not-disturb` would therefore succeed and suppress
|
||||
# nothing. Write that config first if these binds are wanted.
|
||||
#
|
||||
# $mainMod+Ctrl+L is not ported: there is no lock screen here on purpose, and
|
||||
# locking the session Sunshine captures would blank the stream.
|
||||
|
||||
bindsym $mod+Return exec kitty
|
||||
bindsym $mod+d exec wofi --show drun
|
||||
bindsym $mod+Shift+q kill
|
||||
bindsym $mod+Shift+e exec swaymsg exit
|
||||
bindsym $mod+Shift+Return exec wofi --show drun
|
||||
bindsym $mod+b exec $browser
|
||||
bindsym $mod+q kill
|
||||
bindsym $mod+Shift+q exit
|
||||
bindsym $mod+f fullscreen
|
||||
bindsym $mod+t floating toggle
|
||||
|
||||
# Hyprland moves focus with both vim keys and arrows; so does this.
|
||||
bindsym $mod+h focus left
|
||||
bindsym $mod+j focus down
|
||||
bindsym $mod+k focus up
|
||||
bindsym $mod+l focus right
|
||||
bindsym $mod+Left focus left
|
||||
bindsym $mod+Down focus down
|
||||
bindsym $mod+Up focus up
|
||||
bindsym $mod+Right focus right
|
||||
|
||||
# Not a Hyprland bind, kept because a Moonlight client arrives with a pointer
|
||||
# and little else: this is the only way to reach a window the pointer cannot
|
||||
# see. Spelled literally rather than as $mod so it stays Alt+Tab if $mod moves.
|
||||
bindsym Alt+Tab focus next
|
||||
bindsym Alt+Shift+Tab focus prev
|
||||
|
||||
# Workspaces, and $mod+scroll to cycle them, as on the laptops. Sway has no
|
||||
# master layout, so Hyprland's $mainMod+Space, Y, U, O and I have no
|
||||
# counterpart; nor do its
|
||||
# movecurrentworkspacetomonitor binds, with one virtual output.
|
||||
bindsym $mod+1 workspace number 1
|
||||
bindsym $mod+2 workspace number 2
|
||||
bindsym $mod+3 workspace number 3
|
||||
bindsym $mod+4 workspace number 4
|
||||
bindsym $mod+5 workspace number 5
|
||||
bindsym $mod+6 workspace number 6
|
||||
bindsym $mod+7 workspace number 7
|
||||
bindsym $mod+8 workspace number 8
|
||||
bindsym $mod+9 workspace number 9
|
||||
bindsym $mod+0 workspace number 10
|
||||
|
||||
bindsym $mod+Shift+1 move container to workspace number 1
|
||||
bindsym $mod+Shift+2 move container to workspace number 2
|
||||
bindsym $mod+Shift+3 move container to workspace number 3
|
||||
bindsym $mod+Shift+4 move container to workspace number 4
|
||||
bindsym $mod+Shift+5 move container to workspace number 5
|
||||
bindsym $mod+Shift+6 move container to workspace number 6
|
||||
bindsym $mod+Shift+7 move container to workspace number 7
|
||||
bindsym $mod+Shift+8 move container to workspace number 8
|
||||
bindsym $mod+Shift+9 move container to workspace number 9
|
||||
bindsym $mod+Shift+0 move container to workspace number 10
|
||||
|
||||
bindsym --whole-window $mod+button4 workspace prev_on_output
|
||||
bindsym --whole-window $mod+button5 workspace next_on_output
|
||||
|
||||
# Volume reaches the client: the default sink is sink-sunshine-stereo, which is
|
||||
# the one Sunshine captures. --locked because there is no lock screen to respect.
|
||||
# XF86AudioMicMute is not ported — this host has no audio source at all.
|
||||
bindsym --locked XF86AudioRaiseVolume exec wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+
|
||||
bindsym --locked XF86AudioLowerVolume exec wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
|
||||
bindsym --locked XF86AudioMute exec wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
|
||||
|
||||
bindsym --locked XF86AudioNext exec playerctl next
|
||||
bindsym --locked XF86AudioPause exec playerctl play-pause
|
||||
bindsym --locked XF86AudioPlay exec playerctl play-pause
|
||||
bindsym --locked XF86AudioPrev exec playerctl previous
|
||||
|
||||
# Something visible on login, so a working capture is obviously working rather
|
||||
# than indistinguishable from a broken one.
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
~/.config/opencode/local.json: common/opencode/local.json
|
||||
~/.claude/CLAUDE.md: common/claude/CLAUDE.md
|
||||
~/.claude/settings.json: common/claude/settings.json
|
||||
~/.claude/skills/rust-crate-release: common/claude/skills/rust-crate-release
|
||||
~/.claude/projects/-home-connor/memory: common/claude/memory/-home-connor
|
||||
~/.config/sunshine/sunshine.conf: hosts/mainframe/sunshine/sunshine.conf
|
||||
~/.config/sunshine/apps.json: hosts/mainframe/sunshine/apps.json
|
||||
~/.config/systemd/user/inbox-tidy.service: hosts/mainframe/systemd/inbox-tidy.service
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
~/.config/opencode/work-strict.json: common/opencode/work-strict.json
|
||||
~/.config/opencode/local.json: common/opencode/local.json
|
||||
~/.claude/CLAUDE.md: common/claude/CLAUDE.md
|
||||
~/.claude/skills/rust-crate-release: common/claude/skills/rust-crate-release
|
||||
~/.config/hypr/hyprland.conf: common/hypr/hyprland.conf
|
||||
~/.config/hypr/hyprlock.conf: common/hypr/hyprlock.conf
|
||||
~/.config/hypr/toggleFloating.sh: common/hypr/toggleFloating.sh
|
||||
|
||||
@@ -4,6 +4,7 @@ claude-code
|
||||
llama.cpp-cuda
|
||||
nvidia-patch-git
|
||||
oh-my-zsh-git
|
||||
orca-slicer-bin
|
||||
overskride-bin
|
||||
paru
|
||||
paru-debug
|
||||
|
||||
@@ -11,8 +11,10 @@ cronie
|
||||
cuda
|
||||
decibels
|
||||
dhcpcd
|
||||
difftastic
|
||||
dmidecode
|
||||
docker
|
||||
dosfstools
|
||||
dust
|
||||
dysk
|
||||
efibootmgr
|
||||
@@ -21,10 +23,12 @@ ethtool
|
||||
eza
|
||||
fd
|
||||
fio
|
||||
freecad
|
||||
fzf
|
||||
gcc
|
||||
gdm
|
||||
git
|
||||
git-delta
|
||||
glow
|
||||
gnome-backgrounds
|
||||
gnome-calculator
|
||||
@@ -54,9 +58,11 @@ gnome-tour
|
||||
gnome-user-docs
|
||||
gnome-user-share
|
||||
gnome-weather
|
||||
go
|
||||
go-yq
|
||||
gpicview
|
||||
grilo-plugins
|
||||
grim
|
||||
grub
|
||||
gvfs
|
||||
gvfs-afc
|
||||
@@ -72,6 +78,7 @@ htop
|
||||
hyprland
|
||||
i3-wm
|
||||
intel-ucode
|
||||
jdk21-openjdk
|
||||
jq
|
||||
kitty
|
||||
lazygit
|
||||
@@ -123,6 +130,7 @@ pacman-contrib
|
||||
pandoc-cli
|
||||
papers
|
||||
pcmanfm
|
||||
perf
|
||||
pkgconf
|
||||
podman
|
||||
podman-compose
|
||||
@@ -130,6 +138,7 @@ python
|
||||
python-pip
|
||||
python-requests
|
||||
python-rich
|
||||
python-weasyprint
|
||||
qutebrowser
|
||||
rbw
|
||||
restic
|
||||
|
||||
Reference in new issue
Block a user