Merge branch 'main' of ssh://git.rcjohnstone.com:2222/connor/dotfiles
This commit is contained in:
commit
432e1d07fc
41 files changed
+992
-38
No files matched your search
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/sh
|
#!/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
|
#!/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
|
#!/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
|
#!/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
|
## The dotfiles repo and the fleet
|
||||||
|
|
||||||
`~/.dotfiles` is **one branch linked onto four machines** by dotbot. Shared
|
`~/.dotfiles` is **one branch linked onto four machines** by dotbot.
|
||||||
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`.
|
|
||||||
There are deliberately no per-host branches: the layout this replaced was three
|
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
|
machines with two histories that had never met, and host layers cost a few small
|
||||||
files and remove the merge entirely.
|
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
|
`~/.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`.
|
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
|
## General
|
||||||
|
|
||||||
- `~/config/` and `~/docs/` are deliberately untracked. Back up edits there by
|
- `~/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,
|
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
|
no sentence whose only job is to make a point land. Concrete examples that
|
||||||
explain something are welcome; salesmanship is not.
|
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
|
- [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
|
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:
|
metadata:
|
||||||
type: project
|
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
|
`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;
|
greenfield Python rewrite, started 2026-09-08. Keep the old one running until cutover;
|
||||||
Connor chose **no data import** into the new schema.
|
Connor chose **no data import** into the new schema.
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ metadata:
|
|||||||
type: project
|
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:
|
each of which costs a debugging cycle to rediscover:
|
||||||
|
|
||||||
- **SIGHUP does not reload sources.** `watch_sighup` swaps the config, and per-cycle reads
|
- **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.
|
renaming it is invisible until Caddy restarts — the mount holds the inode.
|
||||||
Mount the parent of the directory being replaced.
|
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]].
|
See [[runway-v2-build-plan]] and [[verify-before-claiming]].
|
||||||
@@ -8,7 +8,7 @@ metadata:
|
|||||||
modified: 2026-08-26T17:49:34.494Z
|
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
|
as Connor Johnstone. **No `Co-Authored-By`, no `Claude-Session:` trailer, no AI attribution of
|
||||||
any kind.**
|
any kind.**
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ metadata:
|
|||||||
modified: 2026-08-27T00:52:30.624Z
|
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
|
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."
|
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
|
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`
|
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
|
— 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).
|
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
|
- [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
|
- [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
|
- [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
|
- [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
|
- [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.
|
it polls its own config every 2s and reloads itself.
|
||||||
|
|
||||||
Related: [[zen-tos-hold]]
|
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": {
|
"enabledPlugins": {
|
||||||
"rust-analyzer-lsp@claude-plugins-official": true
|
"rust-analyzer-lsp@claude-plugins-official": true
|
||||||
},
|
},
|
||||||
|
"permissions": {
|
||||||
|
"defaultMode": "auto"
|
||||||
|
},
|
||||||
"tui": "fullscreen",
|
"tui": "fullscreen",
|
||||||
"theme": "dark",
|
"theme": "dark",
|
||||||
"autoMode": {
|
"autoMode": {
|
||||||
"soft_deny": [
|
"soft_deny": [
|
||||||
"$defaults",
|
"$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(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(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"
|
"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)",
|
"**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",
|
"### User-specific",
|
||||||
"**Primary use of Claude Code**: software development (Rust/Leptos/Axum CalDAV client)",
|
"**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",
|
"**Org-specific CLIs**: None configured",
|
||||||
"routine podman/systemctl/journalctl operations under this project's local/self-hosted infra",
|
"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",
|
"**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
|
-- Default is 'warn', which hides request failures. If completions stop
|
||||||
-- appearing, set this to 'debug' and the reason shows up in `:messages`.
|
-- appearing, set this to 'debug' and the reason shows up in `:messages`.
|
||||||
notify = "warn",
|
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)
|
-- Ghost text: there is no completion engine here (no nvim-cmp, no blink)
|
||||||
-- and virtual text is the copilot-shaped one.
|
-- and virtual text is the copilot-shaped one.
|
||||||
virtualtext = {
|
virtualtext = {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ aero https://filespace.aero.org/human.aspx?r=4292318470
|
|||||||
whatsapp https://web.whatsapp.com/
|
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_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
|
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::
|
e https://192.168.1.101:8006/#v1:0:=qemu%2F101:4:::::8::
|
||||||
element https://element.rcjohnstone.com/
|
element https://element.rcjohnstone.com/
|
||||||
aws access portal https://start.us-gov-east-1.us-gov-home.awsapps.com/directory/d-c267218df8#/?tab=accounts
|
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}"
|
IDLE_MINUTES="${STREAM_IDLE_MINUTES:-15}"
|
||||||
STAMP="${XDG_RUNTIME_DIR:-/tmp}/stream.lastseen"
|
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() {
|
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() {
|
vram() {
|
||||||
|
|||||||
@@ -20,11 +20,43 @@
|
|||||||
{
|
{
|
||||||
"do": "sh -c \"swaymsg output HEADLESS-1 mode ${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}@${SUNSHINE_CLIENT_FPS}Hz\"",
|
"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"
|
"undo": "swaymsg output HEADLESS-1 mode 1920x1080@60Hz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"do": "swaymsg workspace steam",
|
||||||
|
"undo": "swaymsg workspace number 1"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"detached": [
|
"detached": [
|
||||||
"setsid steam -gamepadui"
|
"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
|
# Input arrives from the Moonlight client, so keybinds are worth having even
|
||||||
# though nobody is sitting at this machine. Without them the Desktop app
|
# though nobody is sitting at this machine. Without them the Desktop app
|
||||||
# streams an empty screen with no way to open anything.
|
# 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
|
# Tiling, sway's default. This used to float every window, which left a
|
||||||
# workstation: the stream shows one thing at a time, usually Steam fullscreen.
|
# single-app stream at whatever size the app opened at instead of filling the
|
||||||
# Tiling only ever meant a dialog box stealing half the screen. Both criteria
|
# output. Each Sunshine app switches to its own workspace first (apps.json), so
|
||||||
# are needed - app_id matches Wayland clients, class matches Xwayland ones,
|
# its window is alone there and fills the screen. Dialogs sway recognizes as
|
||||||
# and Steam is an X client.
|
# dialogs still float.
|
||||||
for_window [app_id=".*"] floating enable
|
|
||||||
for_window [class=".*"] floating enable
|
|
||||||
|
|
||||||
# Title bars are the handle you grab with the mouse, so floating windows keep
|
# Title bars are the handle you grab with the mouse, so floating windows keep
|
||||||
# a real border rather than the thin default.
|
# a real border rather than the thin default.
|
||||||
default_floating_border normal 2
|
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.
|
# only way to move a window is the title bar, which a fullscreen app has none of.
|
||||||
floating_modifier $mod normal
|
floating_modifier $mod normal
|
||||||
|
|
||||||
# The stream forwards a mouse, so pointing and clicking is the primary way
|
# Hyprland takes $browser from hosts/<host>/hypr/local-env.conf; mainframe links
|
||||||
# around; these are for when the pointer is not enough.
|
# 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+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+Tab focus next
|
||||||
bindsym Alt+Shift+Tab focus prev
|
bindsym Alt+Shift+Tab focus prev
|
||||||
bindsym $mod+f fullscreen
|
|
||||||
|
|
||||||
bindsym $mod+Return exec kitty
|
# Workspaces, and $mod+scroll to cycle them, as on the laptops. Sway has no
|
||||||
bindsym $mod+d exec wofi --show drun
|
# master layout, so Hyprland's $mainMod+Space, Y, U, O and I have no
|
||||||
bindsym $mod+Shift+q kill
|
# counterpart; nor do its
|
||||||
bindsym $mod+Shift+e exec swaymsg exit
|
# 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
|
# Something visible on login, so a working capture is obviously working rather
|
||||||
# than indistinguishable from a broken one.
|
# than indistinguishable from a broken one.
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
~/.config/opencode/local.json: common/opencode/local.json
|
~/.config/opencode/local.json: common/opencode/local.json
|
||||||
~/.claude/CLAUDE.md: common/claude/CLAUDE.md
|
~/.claude/CLAUDE.md: common/claude/CLAUDE.md
|
||||||
~/.claude/settings.json: common/claude/settings.json
|
~/.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/sunshine.conf: hosts/mainframe/sunshine/sunshine.conf
|
||||||
~/.config/sunshine/apps.json: hosts/mainframe/sunshine/apps.json
|
~/.config/sunshine/apps.json: hosts/mainframe/sunshine/apps.json
|
||||||
~/.config/systemd/user/inbox-tidy.service: hosts/mainframe/systemd/inbox-tidy.service
|
~/.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/work-strict.json: common/opencode/work-strict.json
|
||||||
~/.config/opencode/local.json: common/opencode/local.json
|
~/.config/opencode/local.json: common/opencode/local.json
|
||||||
~/.claude/CLAUDE.md: common/claude/CLAUDE.md
|
~/.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/hyprland.conf: common/hypr/hyprland.conf
|
||||||
~/.config/hypr/hyprlock.conf: common/hypr/hyprlock.conf
|
~/.config/hypr/hyprlock.conf: common/hypr/hyprlock.conf
|
||||||
~/.config/hypr/toggleFloating.sh: common/hypr/toggleFloating.sh
|
~/.config/hypr/toggleFloating.sh: common/hypr/toggleFloating.sh
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ claude-code
|
|||||||
llama.cpp-cuda
|
llama.cpp-cuda
|
||||||
nvidia-patch-git
|
nvidia-patch-git
|
||||||
oh-my-zsh-git
|
oh-my-zsh-git
|
||||||
|
orca-slicer-bin
|
||||||
overskride-bin
|
overskride-bin
|
||||||
paru
|
paru
|
||||||
paru-debug
|
paru-debug
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ cronie
|
|||||||
cuda
|
cuda
|
||||||
decibels
|
decibels
|
||||||
dhcpcd
|
dhcpcd
|
||||||
|
difftastic
|
||||||
dmidecode
|
dmidecode
|
||||||
docker
|
docker
|
||||||
|
dosfstools
|
||||||
dust
|
dust
|
||||||
dysk
|
dysk
|
||||||
efibootmgr
|
efibootmgr
|
||||||
@@ -21,10 +23,12 @@ ethtool
|
|||||||
eza
|
eza
|
||||||
fd
|
fd
|
||||||
fio
|
fio
|
||||||
|
freecad
|
||||||
fzf
|
fzf
|
||||||
gcc
|
gcc
|
||||||
gdm
|
gdm
|
||||||
git
|
git
|
||||||
|
git-delta
|
||||||
glow
|
glow
|
||||||
gnome-backgrounds
|
gnome-backgrounds
|
||||||
gnome-calculator
|
gnome-calculator
|
||||||
@@ -54,9 +58,11 @@ gnome-tour
|
|||||||
gnome-user-docs
|
gnome-user-docs
|
||||||
gnome-user-share
|
gnome-user-share
|
||||||
gnome-weather
|
gnome-weather
|
||||||
|
go
|
||||||
go-yq
|
go-yq
|
||||||
gpicview
|
gpicview
|
||||||
grilo-plugins
|
grilo-plugins
|
||||||
|
grim
|
||||||
grub
|
grub
|
||||||
gvfs
|
gvfs
|
||||||
gvfs-afc
|
gvfs-afc
|
||||||
@@ -72,6 +78,7 @@ htop
|
|||||||
hyprland
|
hyprland
|
||||||
i3-wm
|
i3-wm
|
||||||
intel-ucode
|
intel-ucode
|
||||||
|
jdk21-openjdk
|
||||||
jq
|
jq
|
||||||
kitty
|
kitty
|
||||||
lazygit
|
lazygit
|
||||||
@@ -123,6 +130,7 @@ pacman-contrib
|
|||||||
pandoc-cli
|
pandoc-cli
|
||||||
papers
|
papers
|
||||||
pcmanfm
|
pcmanfm
|
||||||
|
perf
|
||||||
pkgconf
|
pkgconf
|
||||||
podman
|
podman
|
||||||
podman-compose
|
podman-compose
|
||||||
@@ -130,6 +138,7 @@ python
|
|||||||
python-pip
|
python-pip
|
||||||
python-requests
|
python-requests
|
||||||
python-rich
|
python-rich
|
||||||
|
python-weasyprint
|
||||||
qutebrowser
|
qutebrowser
|
||||||
rbw
|
rbw
|
||||||
restic
|
restic
|
||||||
|
|||||||
Reference in new issue
Block a user