diff --git a/agent/managed-skills/authentik-proxy-provider-app-link/SKILL.md b/agent/managed-skills/authentik-proxy-provider-app-link/SKILL.md new file mode 100644 index 0000000..f15b757 --- /dev/null +++ b/agent/managed-skills/authentik-proxy-provider-app-link/SKILL.md @@ -0,0 +1,52 @@ +--- +name: authentik-proxy-provider-app-link +description: "Diagnose and fix an authentik embedded-outpost proxy provider that never authenticates (login wall / raw flow served on the app host): the provider must be linked to an Application, have a non-null invalidation_flow, and an exact external_host match." +--- + +# authentik proxy provider not authenticating (login wall) + +Symptom: a service behind the authentik embedded outpost (Caddy -> Anubis -> outpost -> app) never lets an authenticated user through. The browser is parked on a login page served ON THE APP'S OWN HOST, e.g. `https://app.example.com/if/flow/default-authentication-flow/?next=/` or `https://app.example.com/flows/-/default/authentication/`. The IdP session cookie lives on `auth.example.com` and is never sent to `app.example.com`, so login is demanded forever. + +## Root causes (check in this order) + +1. **Provider not linked to an Application (most common).** The embedded outpost's config API filters `ProxyProvider.objects.filter(application__isnull=False)`. A provider created with `ProxyProvider.objects.create(...)` and no Application is never returned to the outpost, so the outpost never loads it and the request falls through to raw flow serving. + - FIX: create an Application bound to the provider: + ```python + app = Application.objects.create(name="Marreta", slug="marreta", launch_url="https://marreta.franksdank.cloud") + app.provider = proxy_provider; app.save() + proxy_provider.application = app; proxy_provider.save() + ``` +2. **Null `invalidation_flow`.** The Rust outpost serde requires a formatted UUID; a null value aborts `get_outpost` with `invalid type: null, expected a formatted UUID string at line 1 column N` and takes down EVERY provider on that outpost. + - FIX: `proxy_provider.invalidation_flow = InvalidationFlow.objects.get(name="default-provider-invalidation-flow")`. +3. **`external_host` mismatch.** Must equal the Host header the outpost sees. Trailing-slash mismatch (`https://x` vs `https://x/`) = no match. Match the fleet convention (no trailing slash). +4. **Missing `authentication_flow` / `authorization_flow`.** Set to the fleet defaults (`default-authentication-flow`, `default-provider-authorization-implicit-consent`). + +## Diagnose + +```bash +# 1. Compare broken vs working provider field set (ak shell, as the authentik user): +sudo -u authentik AUTHENTIK_POSTGRESQL_HOST=127.0.0.1 AUTHENTIK_POSTGRESQL_PORT=5432 \ + AUTHENTIK_POSTGRESQL_NAME=authentik AUTHENTIK_POSTGRESQL_USER=authentik \ + AUTHENTIK_POSTGRESQL_PASSWORD='' ak shell +``` +```python +for p in ProxyProvider.objects.all(): + print(p.name, p.external_host, bool(p.application), p.authentication_flow_id, p.authorization_flow_id, p.invalidation_flow_id) +``` +```bash +# 2. The serde error (this is the smoking gun for cause #2): +journalctl --user -u authentik-outpost.service --since '-10 min' | grep -iE 'error|invalid|panic' +# 3. Confirm the outpost loaded the provider after the fix: +journalctl --user -u authentik-outpost.service --since '-2 min' | grep -i 'loaded app' +``` + +## Apply + restart + +After any provider/application change: `systemctl --user restart authentik-server.service authentik-outpost.service` (rootless, user dank on Dankclaw). Then verify the OAuth dance: +```bash +curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' https://app.example.com/ +# expect 302 -> /outpost.goauthentik.io/start?rd=... (NOT a raw /flows/ URL) +``` + +## Provenance note +On the Dank fleet these providers are NOT repo-managed (no blueprint). Fixes applied via `ak shell` are live-only. Capture any new provider+application as a repo blueprint or setup script to avoid drift. diff --git a/agent/managed-skills/dankclaw-guard-chain-layer-diagnosis/SKILL.md b/agent/managed-skills/dankclaw-guard-chain-layer-diagnosis/SKILL.md new file mode 100644 index 0000000..6d7c63a --- /dev/null +++ b/agent/managed-skills/dankclaw-guard-chain-layer-diagnosis/SKILL.md @@ -0,0 +1,33 @@ +--- +name: dankclaw-guard-chain-layer-diagnosis +description: "When a user reports a guarded dankclaw page (homepage/vhost) broken but the server may be healthy: verify each layer of the Anubis→Authentik→Caddy chain server-side and isolate client-side vs server-side." +--- + +# Dankclaw Guard-Chain Layer Diagnosis + +Use when a user reports a guarded page (e.g. `www.franksdank.cloud` homepage) "broken / links gone / not loading" and you must determine whether the fault is server-side or client-side/session. + +## Chain topology +`Browser → Caddy TLS edge → Anubis :8923 → Caddy :8016 → Authentik outpost → Caddy :8017 (@home / @data handlers) → app/static` +- Public vhosts `reverse_proxy anubis:8923` with `header_up X-Real-Ip {remote_host}`. +- Post-auth `http://:8017` listener routes by `@ host `. +- Homepage static: `/srv/homepage/` (index.html, app.js, style.css, services.json, status.json, deployed.js). + +## Layer-by-layer checks (run from the host via ssh) +1. **Caddy unit + guard containers**: `systemctl --user is-active caddy.service`; `podman ps --format '{{.Names}}\t{{.Status}}' | grep -iE 'anubis|authentik|caddy'` — expect `Up ... (healthy)`. +2. **Anubis challenge page validity**: `curl -sk https://www.franksdank.cloud/ --resolve www.franksdank.cloud:443:192.168.0.143` — a healthy challenge contains `"anubis_challenge"` JSON with `difficulty` and the main JS. If present, the guard serves fine. +3. **Authentik outpost errors**: `podman logs --tail 40 authentik-outpost 2>&1 | grep -iE 'error|exception|traceback|500|provider'` — routine dramatiq task logs are normal. +4. **@home / @data from INSIDE the caddy container** (the only way to reach 8016/8017): + `podman exec caddy sh -c 'wget -q -S -T5 --header "Host: www.franksdank.cloud" -O- http://127.0.0.1:8017/'` + and the same for `/services.json`, `/status.json`. Expect 200 + body. +5. **Files on disk**: `ls -la /srv/homepage/` and validate `status.json` parses (`python3 -c "import json;json.load(open('/srv/homepage/status.json'))"`). + +## Gotchas (cost me calls) +- **BusyBox wget in the caddy container**: NO `-H` flag. Use `--header "Host: ..."`. +- **8016/8017 live in the caddy container's netns**: `curl http://127.0.0.1:8017` from the HOST returns `000` (connection refused) — EXPECTED, not a fault. Test from inside the container. +- **anubis container is distroless**: `podman exec anubis sh` fails (`sh: executable not found`). Inspect via its logs / the served challenge page instead. +- **x-real-ip masking**: Anubis logs `x-real-ip: 10.89.1.22` (Caddy container podman IP) for ALL clients — rootless-podman published-port SNAT. Pre-existing; clients indistinguishable by IP; clearance is cookie-based. Do NOT mistake this for the bug. +- **Homepage render**: `app.js` does `Promise.all([fetch services.json, fetch status.json])`; on ANY fetch failure the catch sets `#summary` to `status unavailable: ` and the grid stays EMPTY — which a user reads as "links gone / not loading." A 30-service status.json with a 9-service services.json renders fine (stateByName lookup). + +## Decision +If every layer above is healthy (challenge valid, outpost clean, @home/@data 200, files render), the fault is client-side/session. STOP thrashing the live system (repeated failed browser challenges share the masked 10.89.1.22 identity). Get the authenticated view: ask for the exact symptom (challenge spinning? Authentik login loop? empty grid? "status unavailable" text?) or the Authentik login to drive the page in a real browser. diff --git a/agent/managed-skills/e2e-arr-rawip-reachability-triage/SKILL.md b/agent/managed-skills/e2e-arr-rawip-reachability-triage/SKILL.md new file mode 100644 index 0000000..4bcbe37 --- /dev/null +++ b/agent/managed-skills/e2e-arr-rawip-reachability-triage/SKILL.md @@ -0,0 +1,72 @@ +--- +name: e2e-arr-rawip-reachability-triage +description: "Triage an e2e admin-SSO failure on a *arr raw IP (192.168.0.112:8989) by probing from the box the browser runs on, instead of assuming the Mac is asleep and burning retries waking it." +--- + +# e2e raw-IP *arr failures — probe from the browser's box, not a peer + +`tests/e2e/specs/11-admin.spec.ts` navigates the real browser to +`http://192.168.0.112:8989/` (Sonarr's forwarded IP on FrankBook-Pro). When it +fails with `ERR_CONNECTION_TIMED_OUT` the instinct is "the Mac is asleep". +Sometimes true. Not always — and waking it is not the only recovery. + +## The decisive question + +**Who cannot reach the port?** Playwright runs on the dev box (blankpad), not on +a fleet host. A healthy probe from dankclaw proves nothing about the browser's +path. + +```bash +# 1. From the box that RUNS the browser: +curl -s -o /dev/null -w '%{http_code}\n' --max-time 7 http://192.168.0.112:8989/ + +# 2. From a LAN peer, same port: +ssh dankclaw 'for p in 8989 7878 9696; do curl -s -o /dev/null -w "$p %{http_code}\n" \ + --max-time 6 http://192.168.0.112:$p/; done' + +# 3. Liveness of the Mac itself (ICMP, not TCP): +ping -c 2 -W 3 192.168.0.112 +``` + +## Reading the matrix + +| peer probe | blankpad probe | ping | meaning | +|---|---|---|---| +| 200 | 200 | ok | transient — re-run the spec | +| timeout | timeout | fails | Mac asleep; wake, ports return in seconds | +| **200** | **rc=28** | **ok** | **path problem, not sleep** — ICMP allowed, TCP to those ports blocked. Waking and re-running cannot help | + +## If it is the third row, confirm the Mac is not at fault + +Through the dankclaw jump (`ssh dankclaw "ssh dank@192.168.0.112 ..."`): + +```bash +pgrep -fl socat # forwarders bound to 192.168.0.112:port +netstat -an | grep -c 8989 # listening +curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8989/ # 200 = app fine +/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate +``` + +Forwarders up + loopback 200 + `Firewall is disabled` ⇒ the Mac is innocent. The +block is between the dev box and the Mac, so stop optimising the Mac. + +## What to do instead + +Don't hold a deploy hostage to a known-environmental red gate: + +1. Verify the deployed service **directly** — its own ledger/health/real + completion — and state that the e2e gate was skipped or partially red, and + why (`deploy.sh --skip-e2e` prints exactly that warning, so the skip is + recorded, not hidden). +2. Report the residual gap plainly: *"29/30 e2e pass; 11-admin fails on Sonarr's + raw IP from the dev box; peer probe returns 200, so the deploy is verified by + other means and the gate gap is a dev-box network path issue."* +3. Do not weaken or skip the spec to make it green — it is guarding a real + SSO-plus-RBAC contract. + +## Note on the sleep case + +The classic flake is real: with the Mac asleep, ports refuse from **everywhere** +including peers, ping latency runs ~150 ms, and an SSH/ping wake restores +200/302 within seconds. That is the cheap first check — just don't stop there +when the peer probe is already green. diff --git a/agent/managed-skills/halogen-kv-pool-fit/SKILL.md b/agent/managed-skills/halogen-kv-pool-fit/SKILL.md new file mode 100644 index 0000000..32d0a8d --- /dev/null +++ b/agent/managed-skills/halogen-kv-pool-fit/SKILL.md @@ -0,0 +1,90 @@ +--- +name: halogen-kv-pool-fit +description: "Resize halogen-flash-server's KV pool or prefill arena without getting silently clamped or OOMing the boot: pool, arena and vision share one device budget, HALOGEN_HOST_RESERVE_GIB only scales the fit-check ceiling and is NOT an allocation, HALOGEN_KV_POOL_FIT only ever lowers, and the sizing warning prints only when a clamp happened." +--- + +# Sizing a halogen-flash-server KV pool + +`HALOGEN_KV_POOL_FIT=1` (default) computes a device budget at startup and **only ever lowers** the configured `HALOGEN_KV_POOL_POSITIONS`. It never raises it, and never below the context. + +``` +device budget = host RAM - resident weights - HALOGEN_HOST_RESERVE_GIB +fit check = needs budget >= (KV + arena + vision) + 1.5 GiB margin +KV = 28 KiB/position +arena = 3.80 GiB + 8.40 GiB x (HALOGEN_MAX_TOK / 16384) # measured, linear +vision = 1.97 GiB when HALOGEN_VISION_TOWER=1 +``` + +Prefer the engine's printed requirement over the model: a clamped boot states +it directly, e.g. `524288 positions need ~35.0 GiB (plus 1.5 of margin)`. + +## The reserve is NOT an allocation + +`HALOGEN_HOST_RESERVE_GIB` is a term in the fit-check inequality, nothing more. Proved by A/B: reserve 15 and reserve 16 both held **103.5 GiB** with **7.1 GiB left for everything else** — identical footprint. Actual footprint is `weights + KV + arena + vision`, none of which the reserve touches. + +So the knob has only two regimes: below the pool's requirement it changes nothing observable; above it, it silently deletes the pool. **Raising the reserve can never give memory back to co-tenants.** To free host RAM, cut the pool or the arena: + +| change | RAM handed back | +|---|---| +| `HALOGEN_KV_POOL_POSITIONS` 524288 → 262144 | 7.2 GiB | +| `HALOGEN_MAX_TOK` 32768 → 16384 | 8.4 GiB | + +Lowering the reserve DOES work — it buys fit margin for a bigger pool or arena. On a 121.2 GiB host with 67.7 GiB weights, for 524288 + max_tok 32768 (needs 36.5): + +| reserve | budget | vs 36.5 | outcome | +|---|---|---|---| +| 24 | 29.5 | −7.0 | clamps | +| **18** | **35.5** | **−1.0** | **clamps — looks like a healthy deploy** | +| 17 | 36.5 | +0.02 | knife edge, inside the noise of the weights term | +| 16 | 37.5 | +1.0 | fits, zero benefit over 15 | +| **15** | **38.5** | +2.0 | **deployed** | + +Ceiling here is 16; pick the lowest reserve that fits, not the highest. + +## Gotchas + +- **The sizing block and its `WARNING` print ONLY after a clamp.** A fitting boot prints no `kv pool:` lines at all, so absence of a warning in old logs is NOT evidence of headroom. +- **`deploy.sh` health-passes a clamped pool.** Container `active`, `/health` 200, in ~12 s. Only the ledger proves the pool size. +- **Beware the false positive when grepping for a clamp**: the NOTE line contains the sentence "Lowering HALOGEN_KV_POOL_POSITIONS gives some back", which matches `LOWERING` case-insensitively. Grep `LOWERING THE POOL` / `cannot spare it`. +- **`free`/`MemAvailable` cannot see this server.** Weights are pinned and the kernel counts locked pages as reclaimable page cache — `MemAvailable` read ~74 GiB while the engine held 103.5 of 121.2 GiB. Read the engine's lines instead. +- **Host RAM is rounded DOWN when displayed.** `121` is `MemTotal 127104060 kB` = `121.2159`; subtracting displayed figures gives 29.3 instead of the true 29.5. +- **Vendor "on the device" figures are pool + arena totals, not pool.** Reading them as pool-only makes a doubling look affordable. +- **A 2.0 GiB firmware iGPU carve-out** is reported by the ledger and is not free memory. +- `HALOGEN_MAX_TOK` is the arena / per-request generation budget (thinking included) — do not conflate with `HALOGEN_CTX` or the pool. `HALOGEN_KV_SLOTS` costs only ~111 MiB/slot, irrelevant to sizing. + +## Three failure modes, worst last + +1. **Silent clamp** — need exceeds budget; pool drops to the floor, service healthy. Caught only in the ledger. +2. **Boot OOM** — enough `HALOGEN_MAX_TOK` and the arena alone beats the budget: `dmalloc: FAILED requesting 0.250 GiB after 39.970 GiB in 566 allocations (out of memory)`. Container stays `active / Up (starting)` forever; health never passes. +3. **Minutes-long stall** — reserve cut so far that the engine holds most of a shared host. It prints its own note: neighbours and this server "can stall for minutes at a time at full CPU with no disk activity and no output, which is not a crash and needs no restart." Looks like a hang, invisible to `free`, hits co-tenants too. + +## Verify + +``` +podman logs frank-large 2>&1 | grep -aiE 'for the device|reserving the KV|ONE pool of|memory: [0-9.]+ GiB of weights|left for everything|LOWERING THE POOL|cannot spare it|listening on 127' +``` + +Expect the budget line, `reserving the KV pool, positions`, `ONE pool of positions`, zero clamp matches, a `host memory left for everything else` figure, then `listening on 127.0.0.1:8730`. (A failing 7770 publish on restart is a stale-netns race — self-heals; `podman network reload --all` forces it.) + +Then a real completion — `/health` 200 alone proves nothing: + +``` +curl -s http://127.0.0.1:7770/v1/chat/completions -H 'Content-Type: application/json' -d '{"model":"frank-large","messages":[{"role":"user","content":"Reply with exactly: OK"}],"max_tokens":48,"reasoning_effort":"low"}' +``` + +Expect `finish_reason: stop`. A silent client decodes **greedy** — this server ships no sampler defaults. + +## Reference points (121.2 GiB host, 67.7 GiB weights, vision on) + +| pool | max_tok | reserve | need | budget | outcome | +|---|---|---|---|---|---| +| 262144 | 32768 | 24 | 29.8 | 29.5 | boots (0.3 over), no sizing lines | +| 524288 | 16384 | 24 | 28.6 | 29.5 | fits clean | +| 524288 | 32768 | 24 | 37.0 | 29.5 | clamped, deploy green | +| 524288 | 65536 | 24 | 46.5 | 29.5 | OOM, will not boot | +| 524288 | 32768 | 15 | 37.0 | 38.5 | **both features, deployed** | +| 524288 | 32768 | 16 | 37.0 | 37.5 | fits, footprint identical | + +## Procedure + +Compute `need` vs `budget` with unrounded RAM, then confirm against the engine's own printed requirement. After each deploy read the ledger for actual pool, clamp matches, **and the host-left line** — and A/B it: if two settings report the same `held`, the knob you turned doesn't do what its name says. Keep reserve and pool documented as a matched pair. Roll back with `git revert` (never `--no-verify`). diff --git a/agent/managed-skills/lan-client-split-dns-fix/SKILL.md b/agent/managed-skills/lan-client-split-dns-fix/SKILL.md new file mode 100644 index 0000000..9d91eea --- /dev/null +++ b/agent/managed-skills/lan-client-split-dns-fix/SKILL.md @@ -0,0 +1,36 @@ +--- +name: lan-client-split-dns-fix +description: Fix a LAN client timing out on *.franksdank.cloud because it resolves the public WAN IP (no 443 forward) instead of the Pi-hole LAN answer; diagnose by resolver and fix with a systemd-resolved route-only drop-in. +--- + +# LAN client split-DNS fix (*.franksdank.cloud) + +Symptom: a LAN machine gets `connection timeout` (curl rc=28) on `https://*.franksdank.cloud`, or LAN-only names return NXDOMAIN, while the server is actually healthy. + +## Root cause +Public DNS for `franksdank.cloud` = WAN IP `71.227.227.172`, which has **no 443 port-forward** (filtered). Pi-hole `192.168.0.143` returns the LAN answer `192.168.0.143` via `addn-hosts`. If the client's resolver is using an external server (1.1.1.1/quad9), it gets the WAN answer → SYN to a filtered port → timeout. + +## Diagnose (fast) +```bash +dig +short www.franksdank.cloud @192.168.0.143 # expect 192.168.0.143 +dig +short www.franksdank.cloud @1.1.1.1 # expect 71.227.227.172 (the trap) +resolvectl status | grep -iE 'Current DNS|DNS Server' # which server is active? +# prove the server is fine by pinning the LAN IP: +curl -sk --resolve www.franksdank.cloud:443:192.168.0.143 https://www.franksdank.cloud/ -o /dev/null -w '%{http_code}\n' # 200 +``` + +## Fix (systemd-resolved route-only drop-in) +```bash +sudo mkdir -p /etc/systemd/resolved.conf.d +sudo tee /etc/systemd/resolved.conf.d/franksdank-lan.conf >/dev/null <<'EOF' +[Resolve] +DNS=192.168.0.143 +Domains=~franksdank.cloud ~franksdank.casa +EOF +sudo systemctl restart systemd-resolved +resolvectl flush-caches +``` +The `~` (route-only) prefix sends `*.franksdank.cloud` ONLY to Pi-hole and never to the default-route servers. Verify: `getent hosts www.franksdank.cloud` → 192.168.0.143. + +## Note +This is a per-client fix. Every LAN client that must reach the homelab by name needs Pi-hole as its DNS (or this drop-in). The server side needs no change. diff --git a/agent/managed-skills/noctalia-reference/SKILL.md b/agent/managed-skills/noctalia-reference/SKILL.md new file mode 100644 index 0000000..bed10bb --- /dev/null +++ b/agent/managed-skills/noctalia-reference/SKILL.md @@ -0,0 +1,32 @@ +--- +name: noctalia-reference +description: "Quick reference for Noctalia desktop environment: config paths, IPC commands, app templates, palette, and migration checklist for Niri-based setups." +--- + +# Noctalia Desktop Environment + +Noctalia is a Niri-based desktop environment at `/usr/bin/noctalia`. + +## Config +- Main: `~/.config/noctalia/config.toml` +- Niri config: includes `noctalia.kdl` for display settings + +## IPC +Direct `noctalia msg ` replaces `qs -c noctalia-shell ipc call` wrapper. +Key commands: `panel-toggle`, `session`, `lock`, `launcher`. + +## App Templates +`/usr/share/noctalia/assets/templates/` contains: gtk, ghostty, alacritty, hyprland, sway, wezterm, helix, btop, starship, qt, kde, cava, scroll, mango, umbriel, foot, emacs, kitty. + +## Palette +Generate from image: `noctalia theme [image]` + +## Migration Checklist +- [ ] Niri keybinds → `noctalia msg` commands +- [ ] Alacritty → import noctalia.toml theme +- [ ] Ghostty → `theme = noctalia` +- [ ] GTK 3/4 → update accent palette (#b267e4 purple) + +## Text Antialiasing +- **Ghostty**: `font-subpixel-render = "off"` to disable subpixel AA +- **Alacritty**: `font.antialias = false` in `alacritty.toml` diff --git a/agent/managed-skills/rootless-quadlet-log-retrieval/SKILL.md b/agent/managed-skills/rootless-quadlet-log-retrieval/SKILL.md new file mode 100644 index 0000000..d0454e5 --- /dev/null +++ b/agent/managed-skills/rootless-quadlet-log-retrieval/SKILL.md @@ -0,0 +1,42 @@ +--- +name: rootless-quadlet-log-retrieval +description: "Retrieve logs for a rootless Podman Quadlet service on the Dank fleet — which command covers which time window, and the sudo/per-user-storage trap" +--- + +# Rootless Quadlet Log Retrieval (Dank fleet) + +Applies to any service deployed as a **rootless** Quadlet `.container` unit (frank-large on Dankclaw is the reference case). Rootless storage is per-user, so the deploy user matters more than the container name. + +## Step 0 — establish ownership before choosing a command + +```bash +# Which manager owns the unit, and which user runs it? +systemctl --user status .service # works => rootless user unit +loginctl show-user -p Linger # Linger=yes => starts at boot +``` + +If `systemctl --user` knows the unit, **every** subsequent command runs as that user with NO `sudo`. + +## Step 1 — pick the log source by time window + +| Need | Command | +|---|---| +| Current instance, tail | `podman logs --tail 200 ` | +| Current instance, follow | `podman logs -f ` | +| Across restarts/recreates | `journalctl --user -u .service -n 200 --no-pager` | +| Time-bounded | `journalctl --user -u .service --since "14:30"` | +| Crash/restart signal only | `journalctl --user -u .service -p warning` | +| Kernel-side evidence (OOM, SVM) | `sudo journalctl -k --since ...` | + +Quadlet units commonly run `--replace --rm`. Consequence: **`podman logs` is destroyed when the container is recreated**; the journal captured `podman run`'s stdout and is the only durable record. When the question is "what happened before the last restart", go straight to `journalctl --user`. + +## Step 2 — the traps + +- `sudo podman logs ` → `Error: no such container`. Root's store does not contain rootless containers. Same for `sudo podman ps`. +- A system-level `journalctl -u ` returns nothing for a user unit — the `-u` must be paired with `--user`. +- During a first-start weight download, health checks report unhealthy **by design**. Do not read that as a crash; check whether the payload file is still growing. +- Vendor verbose switches (e.g. `HALOGEN_VERBOSE=1`, `HALOGEN_DMALLOC_LOG=1`) are a **config change**: they go through git and `deploy.sh`, never a live env poke on the host. + +## Step 3 — if you cannot reach the host + +No SSH transport is configured on the `blank` dev box. Do not probe `dank@`/`frank@` and hope. Ask Frank for a key or alias, and label any answer derived only from repo files as **not live-verified**. diff --git a/agent/managed-skills/ssh-key-bootstrap-no-sshpass/SKILL.md b/agent/managed-skills/ssh-key-bootstrap-no-sshpass/SKILL.md new file mode 100644 index 0000000..e22ea00 --- /dev/null +++ b/agent/managed-skills/ssh-key-bootstrap-no-sshpass/SKILL.md @@ -0,0 +1,64 @@ +--- +name: ssh-key-bootstrap-no-sshpass +description: "Bootstrap passwordless SSH from a dev box to fleet hosts when sshpass/expect are unavailable, using OpenSSH SSH_ASKPASS_REQUIRE=force so the password never touches disk." +--- + +# Passwordless SSH bootstrap without sshpass/expect + +Use when you must install your pubkey on remote hosts but `sshpass` and `expect` +are not installed, and you only have a password. Modern OpenSSH (>= 8.4) can +drive the password prompt from a helper program, so the password flows through +the **process env only** — never a file. + +## Procedure + +1. **Generate a key** (if none): + ```bash + ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N "" -C "host-deploy-$(date +%Y%m%d)" + ``` + +2. **Create an askpass helper** that echoes the password from an env var + (the helper itself stores NO secret): + ```bash + printf '#!/bin/sh\necho "$DEPLOY_PW"\n' > /tmp/askpass.sh + chmod 700 /tmp/askpass.sh + ``` + +3. **Copy the pubkey**, forcing the askpass path and detaching from the TTY + with `setsid` (ssh only uses SSH_ASKPASS when there is no controlling TTY, + unless `SSH_ASKPASS_REQUIRE=force`): + ```bash + env DEPLOY_PW='' \ + SSH_ASKPASS=/tmp/askpass.sh \ + SSH_ASKPASS_REQUIRE=force \ + setsid ssh-copy-id -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10 user@host + ``` + Pass the password via the `env` argument (transient), never in a committed + file or shell history. + +4. **Add host aliases** in `~/.ssh/config` so deploy scripts resolve names: + ``` + Host myhost + HostName 10.0.0.5 + User deployuser + IdentityFile ~/.ssh/id_ed25519 + IdentitiesOnly yes + ``` + +5. **Verify** key auth with no prompt: + ```bash + ssh -o BatchMode=yes myhost 'echo OK; hostname' + ``` + `BatchMode=yes` fails fast if a password would be required — a clean + success proves the key works. + +## Gotchas +- Without `SSH_ASKPASS_REQUIRE=force`, ssh ignores SSH_ASKPASS when a TTY is + present and just prompts interactively (hangs in a non-interactive tool). +- `setsid` detaches the controlling terminal — required on older OpenSSH even + with `force`, harmless otherwise. +- The helper must be executable (`chmod 700`) and print ONLY the password. +- `IdentitiesOnly yes` stops ssh offering other agent keys that could be + rejected before yours, avoiding "Too many authentication failures". +- Passwordless **sudo** on the target is a separate concern (NOPASSWD / + `FRANK_SUDO_PW`); this skill only covers SSH key auth. diff --git a/agent/managed-skills/swapfile-resize-safe-ordering/SKILL.md b/agent/managed-skills/swapfile-resize-safe-ordering/SKILL.md new file mode 100644 index 0000000..bab2661 --- /dev/null +++ b/agent/managed-skills/swapfile-resize-safe-ordering/SKILL.md @@ -0,0 +1,62 @@ +--- +name: swapfile-resize-safe-ordering +description: "Resize a swapfile on a host whose MemAvailable is inflated by pinned model weights or huge page cache: put the replacement swap online BEFORE swapoff so live pages migrate to disk instead of into RAM that does not exist." +--- + +# Resizing a swapfile without triggering the OOM killer + +You cannot grow an active swapfile — it must be swapped off first. On a host that is already tight, **`swapoff` must move every live swapped page somewhere**, and if the replacement is not online yet, that somewhere is RAM. + +The headline number lies. `MemAvailable` counts reclaimable page cache, and pinned/locked model weights plus ordinary cache make it read tens of GiB while only a few GiB is genuinely free. Never size a `swapoff` against `free` or `MemAvailable`. + +## The ordering + +Create and enable the replacement **while the old swap is still live**, so pages migrate to disk: + +```bash +# 1. Replacement online first — old swap untouched +sudo fallocate -l 64G /swap2.img +sudo chmod 600 /swap2.img +sudo mkswap /swap2.img # also the hole check; refuses sparse files +sudo swapon /swap2.img # total swap = old + new + +# 2. Now drain the old one — pages go into the new swap, not RAM +sudo swapoff /swap.img # minutes; watch `free` for used-RAM creep + +# 3. Consolidate onto the conventional path so fstab needs no edit +sudo swapoff /swap2.img # only the residual is left +sudo mkdir -p ~/.AGENTTRASH/-swapfile- +sudo mv /swap.img ~/.AGENTTRASH/-swapfile-/swap.img +sudo mv /swap2.img /swap.img +sudo swapon /swap.img +``` + +Chain step 3 as one tight command — it leaves the host with **zero swap** for a few seconds. Do not leave that window open. + +## Why each step + +- **`mkswap` is a free correctness gate.** It rejects swapfiles containing holes, so a sparse `fallocate` fails loudly at format time instead of at 3 a.m. under memory pressure. Verify with `filefrag` if unsure; `unwritten` extents are allocated, not holes, and are fine. + +- **Consolidating to the original path means fstab never changes**, so boot persistence carries over with no edit and no risk of a stale swap entry dropping boot to emergency. Renaming an *active* swapfile works (inode-based) but leaves a stale path in `/proc/swaps` — swap it off first, then rename, then `swapon`. + +- **Migration is observable.** In the reference run, `swapoff` of 4 GiB took 65 s and used-RAM rose only ~3 GiB (pages touched during migration), the rest relocated on disk. If used-RAM climbs toward the whole swapped amount, abort reasoning and let it finish or re-enable. + +## Verify + +```bash +swapon --show=NAME,TYPE,SIZE,USED,PRIO +systemctl is-active .swap # e.g. swap.img.swap — generated from fstab +grep swap /etc/fstab # entry intact -> boot persistence +``` + +Then prove the workload survived with a **real completion**, not `/health`. + +## Filesystem caveat + +ext4 and xfs take swapfiles directly. **btrfs needs `chattr +C` (NOCOW)**, no compression, and no snapshots in the path — check `df -T` before planning, or the swapfile will be rejected. + +## After the change + +More swap does not relieve pinned memory. Check `vm.swappiness`: at the default 60 the kernel will page anonymous memory under pressure, and if a large service's hot working set is anonymous (an LLM KV pool, for instance), paging it is worse than the OOM kill it prevents. The knob for that is lower swappiness or a cgroup cap on the service — not a bigger swapfile. + +Stage the old file in trash rather than deleting it, and get explicit approval before purging. diff --git a/agent/memories/--home-blank-.omp--/MEMORY.md b/agent/memories/--home-blank-.omp--/MEMORY.md new file mode 100644 index 0000000..d5c9280 --- /dev/null +++ b/agent/memories/--home-blank-.omp--/MEMORY.md @@ -0,0 +1,35 @@ +# Consolidated Memory + +## OMP Harness & Server App Config +- **Location**: `~/.omp` (config/dotfiles), `~/Projects/serverAppConfig` (cloned). +- **Repository Details**: Remote repo `serverAppConfigs` (plural), local dir `serverAppConfig` (singular). Head `701ab14` on `main` (tracks `origin/main`). HTTP transport with embedded dank token, no SSH keys. +- **Structure** (308 files, 4.2 MB): + - `dankclaw/`: Dankclaw stack definitions. + - `dankserver/`: Inference backend config. + - `frankbook-pro/`: 7 quadlet units (sonarr, radarr, lidarr, readarr, prowlarr, qbittorrent, gluetun). + - `shared/`: Homepage (html/css/js + services.json), alloy, loki, prometheus, searxng. + - `tests/`: 47 files, E2E suite. + - `serverAppConfigs-docs/`: 115 files, Architecture KB (deployment flow, network/storage/testing policy, container netns, Dankclaw service pages). + - `old/`, `report/`, `scripts/`, `.githooks/`, `AGENTS.md`: Legacy, reports, tooling, hooks. +- **Recent Commits**: Observability quadlet cutover (loki/alloy/comfyui images, grafana OAuth secret per-service env seeding, docs commit recording exposure posture + unconfigured state). + +## Dotfiles & Git Workflow +- **State Inspection**: `git status --short | git branch --show-current | git log --oneline HEAD..@{u}`. Untracked dirs: `.pre-pull-backup/`, `home/`. +- **Modified Configs**: `alacritty.toml`, `config.ghostty`, `noctalia.css` (gtk3/4), niri `.kdl` files. Shift to noctalia theme. +- **Ghostty Fix**: Replace `font-hinting = hinting` with `freetype-load-flags = hinting,light` (line 10). +- **Commit/Push Pattern**: `git add && git commit -m "" && git push`. Mitigate LFS lock warnings via `git config lfs.http:///info/lfs.locksverify true`. +- **Process Reload**: Ghostty reload via USR1: `pgrep ghostty | while read pid; do /usr/bin/kill -USR1 "$pid"; done`. + +## Handy Application Lifecycle +- **Binary**: `/usr/bin/handy`. +- **Kill**: `pkill -f handy`. +- **Start**: `hub op=start, application=/usr/bin/handy, pty=true, persist=true, ready={log:'.*', timeout:10}`. +- **Status**: Persistent daemon running. 69 catalog models seeded. + +## Device & Input Permissions +- **Issue**: `Platform error: permission denied opening 16 device node(s) under /dev/input` causing keyboard listener failure. +- **Diagnosis**: `/dev/input/*` owned by `root:input` with `crw-rw----` (660). User `blank` was missing the `input` group. +- **Resolution**: `sudo usermod -aG input blank`. +- **Pitfalls**: + 1. `usermod` without `sudo` fails to lock `/etc/passwd`. + 2. `sudo` password entry in CLI environment requires PTY for reliable input. diff --git a/agent/memories/--home-blank-.omp--/learned.md b/agent/memories/--home-blank-.omp--/learned.md new file mode 100644 index 0000000..5dbd61a --- /dev/null +++ b/agent/memories/--home-blank-.omp--/learned.md @@ -0,0 +1,4 @@ +- Frank's Gitea lives on Dankclaw at http://192.168.0.143:3000, single org/user dank. Repo inventory (verified 2026-09-12 via /api/v1/user/repos): agent (default master), command-center (main — this is the ~/.omp dotfiles/KB repo), frank-llm-bench (main), serverAppConfigs (main), vllmScripts (main). LESSON — never clone from a repo name Frank states verbally; resolve the exact full_name via the Gitea API first. He said "serverAppConfig"; the real repo is serverAppConfigs (plural). Cloning the guessed name would 404 with no useful signal. Working procedure: read the dank HTTP token from the existing origin URL in ~/.omp (git remote -v) — do NOT copy that token into memory, docs, or skills; reference its location only. Then GET /api/v1/user/repos and match on the real name/case/plurality before cloning. Local-vs-remote naming: the clone was placed at ~/Projects/serverAppConfig (singular, as Frank explicitly requested) while origin points at the plural repo. The path mismatch is intentional, not drift — pull/push work normally. Repo layout: dankclaw/, dankserver/, frankbook-pro/ (7 quadlet units: sonarr, radarr, lidarr, readarr, prowlarr, qbittorrent, gluetun), shared/ (homepage, alloy, loki, prometheus, searxng), tests/ (47-file E2E suite), serverAppConfigs-docs/ (115-file KB — read its index.md before touching the stack). _(context: Frank asked to "pull my serverAppConfig repository into ~/Projects/serverAppConfig"; Gitea API revealed the plural name. Credential handling: token reused from existing ~/.omp remote, deliberately not persisted here.)_ +- Ghostty 1.3.1 (and the 1.3.x line) has NO font-hinting and NO font-subpixel-render config key. The only font-smoothing key is freetype-load-flags, a comma-separated list: hinting, force-autohint, monochrome, autohint, light (each default-enabled except monochrome/force-autohint; prefix no- to disable). Ground truth: /usr/share/ghostty/doc/ghostty.5.md:598-628. To actually DISABLE anti-aliasing you need freetype-load-flags = hinting,monochrome (docs line 617: monochrome = 1-bit rendering, disables AA). Note hinting,light is a NO-OP — both are already Ghostty defaults. Verification workflow before editing any ghostty key: ghostty +validate-config (exit 0 = valid) plus grep the manpage for the key name; an unknown key silently does nothing useful and +validate-config is the authoritative gate. This matters because a poisoned memory claim about font-subpixel-render/font-hinting caused 3 wasted commits (4c84f02, df33310, 268dcbd) editing a nonexistent key on 2026-09-11. _(context: Verified 2026-09-12 against ghostty 1.3.1-arch2.2 manpage and ghostty +validate-config on Dankware. Corrects contradictory entries in agent/memories/--home-blank-.omp--/learned.md (line 2 asserts font-subpixel-render is the key; it is not).)_ +- Ghostty's font antialiasing is controlled by font-hinting = no-hinting in the config file, not font-subpixel-render = off as the Noctalia reference skill states. When editing ghostty config, check /usr/share/ghostty/doc/ghostty.5.md or src/config/Config.zig — ghostty's --help confirms this. Invalid field causes font-subpixel-render: unknown field on restart. _(context: Discovered when user reported "font-subpixel-render: unknown field" after my initial edit to ghostty config)_ +- Noctalia is a Niri-based desktop environment at /usr/bin/noctalia. Config: ~/.config/noctalia/config.toml. Provides IPC via 'noctalia msg', launcher, lock screen, session menu, and 19 app templates (GTK, Ghostty, Alacritty, Hyprland, Sway, WezTerm, Helix, Btop, Starship, Qt, KDE, Cava, scroll, mango, umbriel, foot, emacs, kitty, alacritty). Templates under /usr/share/noctalia/assets/templates/. Color palette generator: 'noctalia theme [image]'. Text antialiasing: Ghostty uses 'font-subpixel-render' config key (set to 'off' to disable). Alacritty uses 'font.antialias = false' in alacritty.toml. diff --git a/agent/memories/--home-blank-.omp--/memory_summary.md b/agent/memories/--home-blank-.omp--/memory_summary.md new file mode 100644 index 0000000..03cfa99 --- /dev/null +++ b/agent/memories/--home-blank-.omp--/memory_summary.md @@ -0,0 +1 @@ +Current state: OMP harness at ~/.omp, serverAppConfig cloned to ~/Projects (HTTP/dank token, head 701ab14). Git workflow relies on state inspection, USR1 ghostty reloads, and LFS lock mitigation. Handy runs as persistent hub daemon (69 models). Input permissions fixed for user 'blank' (root:input group, 660 perms). Dotfiles shifted to noctalia theme with Ghostty font-hinting adjustment. Reference consolidated details for repo structure, commit patterns, and CLI sudo/PTY pitfalls. diff --git a/agent/memories/--home-blank-.omp--/raw_memories.md b/agent/memories/--home-blank-.omp--/raw_memories.md new file mode 100644 index 0000000..23b5a48 --- /dev/null +++ b/agent/memories/--home-blank-.omp--/raw_memories.md @@ -0,0 +1,33 @@ +# Raw Memories + +## 01a09618-6a77-71d5-a5b2-36e82045b695 +updated_at: 1789227719 +OMP harness config/dotfiles repo located at ~/.omp. +Cloned serverAppConfig to ~/Projects/serverAppConfig (HEAD 701ab14, main tracks origin/main, HTTP transport with embedded dank token, no SSH keys). +Local dir is singular (serverAppConfig), remote repo is plural (serverAppConfigs). +Structure (308 files, 4.2 MB): +- dankclaw/: Dankclaw stack defs. +- dankserver/: Inference backend config. +- frankbook-pro/: 7 quadlet units (sonarr, radarr, lidarr, readarr, prowlarr, qbittorrent, gluetun). +- shared/: Homepage (html/css/js + services.json), alloy, loki, prometheus, searxng. +- tests/: 47 files, E2E suite. +- serverAppConfigs-docs/: 115 files, Architecture KB (deployment flow, network/storage/ testing policy, container netns, Dankclaw service pages). +- old/, report/, scripts/, .githooks/, AGENTS.md: legacy, reports, tooling, hooks. +Recent commits: observability quadlet cutover (loki/alloy/comfyui images, grafana OAuth secret per-service env seeding, docs commit recording exposure posture + unconfigured state). +Pitfall: Local dir name mismatch vs remote repo name (trivial, pull/push unaffected). + +## 01a08f58-4d8f-7356-8bcc-63a767971a5a +updated_at: 1789224571 +[GIT_WORKFLOW] State check: git status --short | git branch --show-current | git log --oneline HEAD..@{u}. Branch: main. Untracked dirs: .pre-pull-backup/, home/. +[DOTFILES_STATE] Modified: alacritty.toml, config.ghostty, noctalia.css (gtk3/4), niri .kdl files. Shift to noctalia theme. +[GHOSTTY_FIX] Config edit: replace font-hinting = hinting with freetype-load-flags = hinting,light (line 10). +[COMMIT_PUSH] Pattern: git add && git commit -m "" && git push. Remote LFS lock warning present; mitigate via git config lfs.http:///info/lfs.locksverify true. +[RELOAD] Ghostty reload via USR1: pgrep ghostty | while read pid; do /usr/bin/kill -USR1 "$pid"; done. + +## 01a08f59-61c3-7103-bd4f-18ea24369233 +updated_at: 1789111781 +Target: handy app. Binary: /usr/bin/handy. Kill: pkill -f handy. Start: hub op=start, application=/usr/bin/handy, pty=true, persist=true, ready={log:'.*', timeout:10}. Result: Persistent daemon, 69 catalog models seeded. + +## 01a08f50-c405-7763-8a24-b52be5af422f +updated_at: 1789110966 +Error: 'Platform error: permission denied opening 16 device node(s) under /dev/input' causing handy keyboard listener failure. Diagnosis: ls -la /dev/input confirms devices are root:input with crw-rw---- (660). groups shows user 'blank' missing 'input'. Resolution: sudo usermod -aG input blank. Pitfalls: 1. usermod without sudo fails to lock /etc/passwd; 2. sudo password entry in this CLI environment requires PTY for reliable input. diff --git a/agent/memories/--home-blank-.omp--/rollout_summaries/01a08f50-c405-7763-8a24-b52be5af422f-dev_input_input_grou.md b/agent/memories/--home-blank-.omp--/rollout_summaries/01a08f50-c405-7763-8a24-b52be5af422f-dev_input_input_grou.md new file mode 100644 index 0000000..bc559f4 --- /dev/null +++ b/agent/memories/--home-blank-.omp--/rollout_summaries/01a08f50-c405-7763-8a24-b52be5af422f-dev_input_input_grou.md @@ -0,0 +1,4 @@ +thread_id: 01a08f50-c405-7763-8a24-b52be5af422f +updated_at: 1789110966 + +Fix 'permission denied opening device node(s)' in /dev/input by adding user to the 'input' group. Root cause: /dev/input/* devices are owned by root:input (660 permissions); user was missing 'input' group. Resolution: sudo usermod -aG input $USER. Note: sudo password handling in CLI requires PTY, and usermod needs sudo to lock /etc/passwd. diff --git a/agent/memories/--home-blank-.omp--/rollout_summaries/01a08f58-4d8f-7356-8bcc-63a767971a5a-dotfile_git_ghostty_.md b/agent/memories/--home-blank-.omp--/rollout_summaries/01a08f58-4d8f-7356-8bcc-63a767971a5a-dotfile_git_ghostty_.md new file mode 100644 index 0000000..fbbb829 --- /dev/null +++ b/agent/memories/--home-blank-.omp--/rollout_summaries/01a08f58-4d8f-7356-8bcc-63a767971a5a-dotfile_git_ghostty_.md @@ -0,0 +1,4 @@ +thread_id: 01a08f58-4d8f-7356-8bcc-63a767971a5a +updated_at: 1789224571 + +Durable git workflow for dotfile repositories: state inspection (short status, unpushed check), targeted config edits (Ghostty font-hinting fix), commit/push with LFS lock awareness, and USR1 process reloads. diff --git a/agent/memories/--home-blank-.omp--/rollout_summaries/01a08f59-61c3-7103-bd4f-18ea24369233-handy_reload.md b/agent/memories/--home-blank-.omp--/rollout_summaries/01a08f59-61c3-7103-bd4f-18ea24369233-handy_reload.md new file mode 100644 index 0000000..714fac1 --- /dev/null +++ b/agent/memories/--home-blank-.omp--/rollout_summaries/01a08f59-61c3-7103-bd4f-18ea24369233-handy_reload.md @@ -0,0 +1,4 @@ +thread_id: 01a08f59-61c3-7103-bd4f-18ea24369233 +updated_at: 1789111781 + +Restart 'handy' application (/usr/bin/handy). Kill via pkill -f handy, start via hub daemon (pty:true, persist:true, ready timeout 10s). Status: Up, 69 catalog models seeded. diff --git a/agent/memories/--home-blank-.omp--/rollout_summaries/01a09618-6a77-71d5-a5b2-36e82045b695-omp_harness_serverap.md b/agent/memories/--home-blank-.omp--/rollout_summaries/01a09618-6a77-71d5-a5b2-36e82045b695-omp_harness_serverap.md new file mode 100644 index 0000000..aa4a524 --- /dev/null +++ b/agent/memories/--home-blank-.omp--/rollout_summaries/01a09618-6a77-71d5-a5b2-36e82045b695-omp_harness_serverap.md @@ -0,0 +1,4 @@ +thread_id: 01a09618-6a77-71d5-a5b2-36e82045b695 +updated_at: 1789227719 + +OMP harness lives at ~/.omp. Cloned serverAppConfig to ~/Projects/serverAppConfig (remote serverAppConfigs, HTTP/dank token transport). 308 files across dankclaw/, dankserver/, frankbook-pro/ (7 quadlets), shared/, tests/ (47 E2E), serverAppConfigs-docs/ (115). Recent work: observability quadlet cutover (loki/alloy/comfyui, grafana OAuth env seeding). diff --git a/agent/memories/--home-blank-.omp--/skills/dotfiles_git/SKILL.md b/agent/memories/--home-blank-.omp--/skills/dotfiles_git/SKILL.md new file mode 100644 index 0000000..64425b8 --- /dev/null +++ b/agent/memories/--home-blank-.omp--/skills/dotfiles_git/SKILL.md @@ -0,0 +1 @@ +Workflow for managing dotfiles and serverAppConfig repos. Includes state inspection, targeted config edits, commit/push with LFS awareness, and USR1 process reloads. diff --git a/agent/memories/--home-blank-.omp--/skills/dotfiles_git/examples/ghostty_font_fix.md b/agent/memories/--home-blank-.omp--/skills/dotfiles_git/examples/ghostty_font_fix.md new file mode 100644 index 0000000..b9fdd91 --- /dev/null +++ b/agent/memories/--home-blank-.omp--/skills/dotfiles_git/examples/ghostty_font_fix.md @@ -0,0 +1 @@ +Fix Ghostty font rendering by replacing 'font-hinting = hinting' with 'freetype-load-flags = hinting,light' at line 10 of config.ghostty. diff --git a/agent/memories/--home-blank-.omp--/skills/dotfiles_git/scripts/check_state.sh b/agent/memories/--home-blank-.omp--/skills/dotfiles_git/scripts/check_state.sh new file mode 100644 index 0000000..1ed0443 --- /dev/null +++ b/agent/memories/--home-blank-.omp--/skills/dotfiles_git/scripts/check_state.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# Checks git status, branch, and unpushed logs +# Outputs short status, current branch, and HEAD vs upstream diff +git status --short | git branch --show-current | git log --oneline HEAD..@{u} diff --git a/agent/memories/--home-blank-.omp--/skills/dotfiles_git/scripts/reload_ghostty.sh b/agent/memories/--home-blank-.omp--/skills/dotfiles_git/scripts/reload_ghostty.sh new file mode 100644 index 0000000..62155c6 --- /dev/null +++ b/agent/memories/--home-blank-.omp--/skills/dotfiles_git/scripts/reload_ghostty.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# Sends USR1 signal to reload Ghostty config +grep ghostty | while read pid; do /usr/bin/kill -USR1 "$pid"; done diff --git a/agent/memories/--home-blank-.omp--/skills/dotfiles_git/templates/commit_push.sh b/agent/memories/--home-blank-.omp--/skills/dotfiles_git/templates/commit_push.sh new file mode 100644 index 0000000..fc24e38 --- /dev/null +++ b/agent/memories/--home-blank-.omp--/skills/dotfiles_git/templates/commit_push.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# Standard commit/push pattern with LFS lock mitigation +LFS_VERIFY=true git config lfs.http://$(git remote get-url origin)/info/lfs.locksverify true +git add "$1" && git commit -m "$2" && git push diff --git a/agent/memories/--home-blank-.omp--/skills/handy_lifecycle/SKILL.md b/agent/memories/--home-blank-.omp--/skills/handy_lifecycle/SKILL.md new file mode 100644 index 0000000..c38189e --- /dev/null +++ b/agent/memories/--home-blank-.omp--/skills/handy_lifecycle/SKILL.md @@ -0,0 +1 @@ +Manage the handy application daemon. Covers binary paths, pkill termination, hub startup with persistent/PTY settings, and readiness timeout. diff --git a/agent/memories/--home-blank-.omp--/skills/handy_lifecycle/examples/hub_status.json b/agent/memories/--home-blank-.omp--/skills/handy_lifecycle/examples/hub_status.json new file mode 100644 index 0000000..e6d665f --- /dev/null +++ b/agent/memories/--home-blank-.omp--/skills/handy_lifecycle/examples/hub_status.json @@ -0,0 +1 @@ +Status: Up. Catalog models seeded: 69. Persistent daemon. Ready timeout: 10s. diff --git a/agent/memories/--home-blank-.omp--/skills/handy_lifecycle/scripts/kill_handy.sh b/agent/memories/--home-blank-.omp--/skills/handy_lifecycle/scripts/kill_handy.sh new file mode 100644 index 0000000..b9edf97 --- /dev/null +++ b/agent/memories/--home-blank-.omp--/skills/handy_lifecycle/scripts/kill_handy.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# Gracefully terminates handy process +pkill -f handy diff --git a/agent/memories/--home-blank-.omp--/skills/handy_lifecycle/scripts/start_handy.sh b/agent/memories/--home-blank-.omp--/skills/handy_lifecycle/scripts/start_handy.sh new file mode 100644 index 0000000..57f8fb0 --- /dev/null +++ b/agent/memories/--home-blank-.omp--/skills/handy_lifecycle/scripts/start_handy.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# Starts handy via hub with PTY and persistence +hub op=start, application=/usr/bin/handy, pty=true, persist=true, ready={log:'.*', timeout:10} diff --git a/agent/memories/--home-blank-.omp--/skills/input_permissions/SKILL.md b/agent/memories/--home-blank-.omp--/skills/input_permissions/SKILL.md new file mode 100644 index 0000000..77d90a6 --- /dev/null +++ b/agent/memories/--home-blank-.omp--/skills/input_permissions/SKILL.md @@ -0,0 +1 @@ +Resolve /dev/input permission denied errors. Targets root:input group assignment (660 permissions), sudo/PTY requirements, and /etc/passwd locking. diff --git a/agent/memories/--home-blank-.omp--/skills/input_permissions/examples/permission_pitfalls.md b/agent/memories/--home-blank-.omp--/skills/input_permissions/examples/permission_pitfalls.md new file mode 100644 index 0000000..1ebc45e --- /dev/null +++ b/agent/memories/--home-blank-.omp--/skills/input_permissions/examples/permission_pitfalls.md @@ -0,0 +1,2 @@ +1. usermod without sudo fails to lock /etc/passwd. +2. sudo password entry in CLI requires PTY for reliable input. diff --git a/agent/memories/--home-blank-.omp--/skills/input_permissions/scripts/fix_input_group.sh b/agent/memories/--home-blank-.omp--/skills/input_permissions/scripts/fix_input_group.sh new file mode 100644 index 0000000..390ff4d --- /dev/null +++ b/agent/memories/--home-blank-.omp--/skills/input_permissions/scripts/fix_input_group.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# Adds user to input group, requires sudo to lock /etc/passwd and PTY for CLI password +sudo usermod -aG input $USER diff --git a/agent/memories/--home-blank-.omp--/skills/input_permissions/templates/dev_input_check.md b/agent/memories/--home-blank-.omp--/skills/input_permissions/templates/dev_input_check.md new file mode 100644 index 0000000..4351cf6 --- /dev/null +++ b/agent/memories/--home-blank-.omp--/skills/input_permissions/templates/dev_input_check.md @@ -0,0 +1,3 @@ +Diagnose /dev/input/* nodes: ls -la /dev/input +Check groups: groups $USER +Expected: root:input with crw-rw---- (660) diff --git a/agent/memories/--home-blank-Projects-serverAppConfig--/MEMORY.md b/agent/memories/--home-blank-Projects-serverAppConfig--/MEMORY.md new file mode 100644 index 0000000..f22e382 --- /dev/null +++ b/agent/memories/--home-blank-Projects-serverAppConfig--/MEMORY.md @@ -0,0 +1,10 @@ +SERVICE: frank-large (rootless Quadlet, boot-persistent) on dankclaw. +Unit: frank-large.service | Container: frank-large. + +PORTS: Engine :8730 (container loopback, unauth), Front-end :8731, Published :7770. + +CONSTRAINTS: Weights pinned (~68 GiB). Env vars to avoid naming: TEMP, HOST, GPU_MAX_HEAP_SIZE (HIP runtime reads bare TEMP for JIT cache path). + +TEST_ENV: pyyaml missing for test suite (uv available, pip missing on system python3). Most suite failures are pre-existing environment issues, not commit regressions. + +LOG_RETRIEVAL: podman logs frank-large (full); podman logs frank-large 2>&1 | grep -E '^(dmalloc|kv pool):' (filtered); journalctl --user -u frank-large (journald). diff --git a/agent/memories/--home-blank-Projects-serverAppConfig--/learned.md b/agent/memories/--home-blank-Projects-serverAppConfig--/learned.md new file mode 100644 index 0000000..6c75e01 --- /dev/null +++ b/agent/memories/--home-blank-Projects-serverAppConfig--/learned.md @@ -0,0 +1,15 @@ +- Dankclaw host swap (as of 2026-09-12): 64 GiB file at /swap.img, ext4 on /dev/nvme0n1p2, fallocated hole-free, driven by /etc/fstab line 12 (/swap.img none swap sw 0 0) which generates the swap.img.swap systemd unit — fstab is the only control point, there is no hand-written unit and no repo file, so deploy.sh cannot see swap state. vm.swappiness=60 (distro default, UNTESTED against this workload), zswap disabled. Raised from 8 GiB; 748 GiB still free on /. The old 8.1 GiB file is staged at dankclaw:~/.AGENTTRASH/20260912-swapfile-8g/swap.img awaiting Frank's approval to purge. Important framing: swap on this host protects the ~28 co-tenant containers, NOT frank-large — its ~68 GiB of pinned weights are not relieved by swap, and paging the KV pool (which IS anonymous) would be worse than the OOM kill it prevents, so if the box thrashes the knob is lower swappiness or a cgroup cap, not more swap. _(context: Raised swap on Dankclaw 2026-09-12 after the frank-large reserve-15 config left only ~7.1 GiB for co-tenants. Documented in serverAppConfigs-docs/Dankclaw/Swap Configuration.md and AGENTS.md memory-model section.)_ +- In the serverAppConfig repo, ./tests/run.sh exits 1 on a **clean tree** because of one pre-existing failure: tests/comfyui_rocm.bats test 73 "comfyui Containerfile is based on the gfx1151 torch image". It is a test-side selector bug, not a product regression — its find ... | head -1 picks dankclaw/alloy/Containerfile instead of the ComfyUI one, so the assertion reads the wrong file. Verify before blaming your own change: a good run reports 285 ok plus this single not ok 73. Confirmed present at HEAD across several sessions (2026-09-12 frank-large work), independent of frank-large/Caddy/quadlet edits. Do NOT treat a green deploy as blocked by it, and do NOT "fix" it by weakening the assertion — the correct fix is to target the actual ComfyUI Containerfile path. Note deploy.sh gates on its own per-service health plus the Playwright e2e suite, not on tests/run.sh, so this failure does not block deploys. _(context: Every ./tests/run.sh run during the 2026-09-12 frank-large KV-pool/reserve work returned exit=1 with exactly not ok 73 comfyui Containerfile is based on the gfx1151 torch image while 285 tests passed, including on commits where nothing ComfyUI-related changed.)_ +- Frank resolves contested tuning calls by **running the experiment**, not by reading the argument. On 2026-09-12 he overruled documentation-based pushback twice in a row with "lets just try it". Working with this, rather than against it: when he pushes back on a knob, state the prediction numerically up front, then run it and report measured vs predicted — he engages with that and it twice produced a better model than the docs (the arena linearity, and the vision-tower term). Corollaries: (1) get the rollback staged and named BEFORE the risky deploy, because the experiment may take production down — the 65536 trial OOMed frank-large for ~2 minutes; (2) use deploy.sh --skip-e2e for risky single-service experiments so the 4-minute e2e gate is not sitting on top of an outage; (3) after a failure, git revert sha + redeploy restores in ~10s, because the weights are already page-cached. Also learned the hard way: when a config experiment ends in a state that quietly reverses the user's original request (32768 + KV_POOL_FIT clamp meant the pool stayed 262144, i.e. the doubling never happened), say so plainly — do not let a green health check imply the goal was met. Separately, on log forensics: journalctl --user -u frank-large.service did NOT reach back to the 2026-09-11 boot even when asked with --since 2026-09-11 (rootless user journal retention is short). To compare against a container's ORIGINAL startup ledger, capture podman logs BEFORE the first deploy of the session — that is the only reliable copy of pre-change boot state. _(context: frank-large KV-pool doubling work on 2026-09-12: Frank overruled doc-based pushback twice ("let's just try it") — once raising HALOGEN_MAX_TOK to 65536 (OOMed the production service for ~2 min) and once back to 32768, which quietly un-did the doubling he had originally asked for. Also: journalctl --user retention for frank-large.service did not cover the 2026-09-11 boot even when queried with --si)_ +- CORRECTION to the earlier "e2e 11-admin.spec.ts failing on 192.168.0.112:8989 means the Mac is asleep — wake it and re-run" lesson. That diagnosis is incomplete and can send you in circles. On 2026-09-12 the admin SSO spec failed on page.goto http://192.168.0.112:8989/ with ERR_CONNECTION_TIMED_OUT, and waking the Mac did NOT fix it: the Mac was verifiably awake (ping 33-54ms), all six socat forwarders were bound to 192.168.0.112, macOS Application Firewall reported State = 0 (disabled), netstat showed 8989 listening, and from **dankclaw** the ports answered 8989→200, 7878→302, 9696→302. Yet from **blankpad** (the dev box where Playwright actually runs) the same ports returned curl rc=28 / 000, while ICMP ping to .112 succeeded. So the blocker is a blankpad↔192.168.0.112 path problem — ICMP allowed, TCP to the arr ports not — not sleep and not the Mac's own firewall. Distinguish the two causes by probing from the box the browser runs on, not from a fleet peer: curl -s -o /dev/null -w '%{http_code}' --max-time 7 http://192.168.0.112:8989/ on blankpad vs ssh dankclaw 'curl ... 192.168.0.112:8989'. Peer 200 + local timeout = path/firewall issue, and no amount of waking or re-running will make that spec pass from this box. Consequence: the mandated e2e gate currently cannot go fully green from blankpad; 29/30 pass, 11-admin fails on Sonarr's raw IP. It is environmental and unrelated to what is being deployed — verify the deployed service directly (ledger, /health, real completion) rather than treating the red gate as a regression, and say which gate was skipped and why. _(context: 2026-09-12 frank-large KV-pool work: e2e admin SSO spec failed on Sonarr raw IP while the Mac was provably awake and dankclaw reached it fine; blankpad could not open the TCP ports at all.)_ +- halogen-flash-server (frank-large, Dankclaw) device-RAM sizing is ONE shared budget, and it is now measured rather than estimated: device budget = host RAM - resident weights - HALOGEN_HOST_RESERVE_GIB (121 - 67.7 - 24.0 = 29.5 GiB here, plus a 1.5 GiB vision margin). The KV pool costs ~28 KiB/position (524,288 = 14.4 GiB) and the PREFILL ARENA IS LINEAR IN HALOGEN_MAX_TOK at 3.80 GiB base + 8.40 GiB per 16,384 tokens — verified against three restarts to 0.1 GiB. So the ceiling is max_tok = (budget - margin - pool - 3.8)/8.4 * 16384; with the doubled pool that is max_tok = 19,115, making the deployed 16384 the CEILING, not a conservative setting. Corner check confirms: max_tok 32768 leaves 7.4 GiB = ~269k positions, exactly the old 262144 config. TWO DIFFERENT FAILURE MODES when you violate this: (a) under the ceiling, HALOGEN_KV_POOL_FIT silently LOWERS the pool and deploy.sh still reports healthy in 12s — you think you doubled it, you did not; (b) OVER the ceiling the arena alone outgrows the budget and the server WILL NOT BOOT at all — measured at max_tok=65536: "WARNING even 262144 positions ... needs ~44.5 GiB", then "dmalloc: FAILED requesting 0.250 GiB after 39.970 GiB (out of memory)". Mode (b) is the dangerous one because systemctl stays active, the container reads Up (starting), and deploy.sh waits out the full LLM_WAIT instead of failing fast — an outage that looks like a slow deploy. Always grep podman logs frank-large for kv pool: and confirm the reserved position count equals the requested one; /health 200 proves nothing about pool size. +- The e2e suite's 11-admin.spec.ts navigates to the *arr services at their raw LAN IPs (192.168.0.112:8989 Sonarr, 7878 Radarr, 9696 Prowlarr), and Playwright runs from the DEV BOX (blankpad), not from Dankclaw. There are therefore TWO independent ways that spec fails, and they need different fixes: (1) The known one: FrankBook-Pro asleep - arr ports unreachable from everywhere. Wake it, re-run. (2) NEW, found 2026-09-12: blankpad cannot reach those forwarded ports AT ALL, even with the Mac fully awake. Measured: from blankpad, ping 192.168.0.112 succeeds (33-54 ms, 0% loss) but TCP to 8989/7878/9696 all return curl rc=28 (connection timeout). From dankclaw the same ports return 200/302/302. On the Mac itself: socat forwarders ARE running and bound to bind=192.168.0.112, curl 127.0.0.1:8989 - 200, and socketfilterfw --getglobalstate reports "Firewall is disabled (State = 0)". So the Mac is not the blocker — something on the blankpad-FrankBook-Pro path drops TCP to those ports while passing ICMP. Diagnostic order that separates them, before assuming "the Mac is asleep": a. From the dev box: curl -s -o /dev/null -w '%{http_code}' --max-time 7 http://192.168.0.112:8989/ AND ping -c2 192.168.0.112. b. From dankclaw: same curl. If dankclaw gets 200 but the dev box times out while ping works, it is failure (2), not sleep — re-running the spec will never go green from the dev box. c. On the Mac (via ssh dankclaw "ssh dank@192.168.0.112 ..."): pgrep -fl socat, curl 127.0.0.1:8989, firewall state. Implication: a red 11-admin.spec.ts is NOT automatically evidence of a fleet outage, and it is NOT caused by whatever was just deployed. Confirm which failure mode by comparing the dev-box probe against the dankclaw probe before attributing the failure to your change. _(context: 2026-09-12, after doubling frank-large's KV pool on Dankclaw. The e2e gate's 11-admin.spec.ts failed on http://192.168.0.112:8989/ (ERR_CONNECTION_TIMED_OUT) on both the initial run and retry. Waking the Mac did NOT fix it, which is why this qualifies the older sleep lesson.)_ +- halogen-flash-server (frank-large on Dankclaw): raising HALOGEN_KV_POOL_POSITIONS can SILENTLY NOT TAKE EFFECT, while the deploy still reports healthy. HALOGEN_KV_POOL_FIT=1 (default) measures the device budget at startup and lowers the pool itself if the configured value will not fit — and it never raises. The budget is ONE pool covering the KV AND the prefill arena together: host RAM − resident weights − HALOGEN_HOST_RESERVE_GIB. Measured on Dankclaw 2026-09-12: 121 GiB − 67.7 weights − 24.0 reserve = 29.5 GiB device budget. Pool 524288 with HALOGEN_MAX_TOK=32768 needs ~35.0 GiB (+1.5 margin) → refused, silently lowered to 262144, deploy reported "healthy after 12s". Fix: HALOGEN_MAX_TOK=16384 halves the arena (~20.6 → ~12.3 GiB working mem), bringing the 524288 need to ~26.6 GiB, which fits with ~2.9 GiB margin at ~8-9% prefill cost. Net result was CHEAPER than the old config: 94.6 GiB held / 13.8 GiB host-left vs 96.3 / 12.1. RULE: after ANY halogen restart, read podman logs frank-large | grep 'kv pool:' and confirm the pool it reserved matches what the unit asked for — a green /health and a green deploy.sh say nothing about pool size. The engine prints "LOWERING THE POOL TO N" in the refusal; that line is the tell. +- frankbook-pro (FrankBook-Pro.local, macOS 192.168.0.112) is reachable from this dev box ONLY via the dankclaw jump host: ssh dankclaw "ssh dank@192.168.0.112 'bash -l -s'" script.sh (fleet inter-host key works for dank@112; frank@/admin@ are denied; NO passwordless sudo on the Mac — sudo -n fails). The *arr stack (sonarr 8989, radarr 7878, readarr 8686, lidarr 8787, prowlarr 9696, qbittorrent, gluetun) runs inside podman-machine-default (applehv); on the macOS host, socat forwarders bind 192.168.0.112:port → 127.0.0.1:port (gvproxy → VM). macOS Application Firewall is DISABLED and /etc/pf.conf is stock. THE FLAKE: when the Mac sleeps, the arr ports go unreachable from the LAN (curl connect 0.000s refused / ERR_CONNECTION_TIMED_OUT in Playwright), which fails the e2e 11-admin.spec.ts SSO test. Waking the Mac (SSH/ping) restores all ports to 200/302 within seconds. So an admin-SSO e2e failure on a *arr link is usually "the Mac is asleep" — wake it and re-run before treating it as a real outage. Diagnostic order: (1) ping 112 (high latency ~150ms = asleep/waking), (2) from the Mac host curl 127.0.0.1:8989 and curl 192.168.0.112:8989 (both 200 = chain healthy, block is external/sleep), (3) podman machine list + podman ps -a (Up = services fine). _(context: Diagnosing the e2e admin SSO test failing on Sonarr raw-IP (112:8989) during the 2026-09-12 marreta work — turned out to be frankbook-pro sleeping, not a real outage.)_ +- authentik embedded-outpost proxy providers: a ProxyProvider is INVISIBLE to the Rust embedded outpost unless it is linked to an authentik Application. The outpost's config API (ProxyOutpostConfigViewSet in authentik/providers/proxy/api.py) filters ProxyProvider.objects.filter(application__isnull=False). So a provider created via ProxyProvider.objects.create(...) with no Application never reaches the outpost, and requests to its external_host fall through to raw flow serving on the app's own host — where the IdP session cookie (scoped to auth.domain) is invisible, so the user is stuck on a login wall (e.g. /if/flow/default-authentication-flow/ or /flows/-/default/authentication/). FIX: create Application(name=..., slug=..., provider=proxy_provider, launch_url=...) and restart authentik-server + the outpost. Also required for the same provider to work: invalidation_flow must be a real UUID (null breaks the Rust outpost's serde with "invalid type: null, expected a formatted UUID string" and takes down ALL providers on that outpost), and external_host must exactly match the Host header (trailing-slash mismatch = no match). Diagnose by: (1) ak shell → list providers with application/invalidation_flow; (2) journalctl --user -u authentik-outpost.service | grep -iE "error|invalid" for the serde error; (3) compare a working provider's full field set against the broken one. _(context: Fixed marreta.franksdank.cloud login-wall on Dankclaw 2026-09-12: the marreta-proxy ProxyProvider was created without a linked Application, so the embedded outpost never loaded it. Root cause chain: null invalidation_flow (serde crash) - missing flows - missing Application link.)_ +- NEVER send SIGHUP to PID 1 inside a podman container to reload a service. In podman containers PID 1 is the container supervisor/init, not the app — SIGHUP to it TERMINATES the whole container. On 2026-09-12 I tried podman exec pihole kill -HUP 1 to reload dnsmasq addn-hosts; it killed the pihole container and took LAN DNS down for ~15s until the quadlet unit's Restart=always (plus an explicit systemctl --user restart pihole.service) brought it back. The app process (pihole-FTL) was PID 54, not 1. Correct ways to reload a service in a podman container: (a) systemctl --user restart svc.service (the quadlet unit — clean, intended), or (b) signal the ACTUAL service PID found via podman exec c ps -e -o pid,comm, never PID 1. For Pi-hole specifically, the repo flow is deploy.sh dankclaw --service pihole which does the addn-hosts cp + unit restart. _(context: 2026-09-12, closing the marreta addn-hosts gap on the live Pi-hole. pihole reload is not a valid subcommand (printed usage, exit 0), so the || kill -HUP 1 fallback fired and killed the container.)_ +- On the Dankware/Dankclaw homelab, a "connection timeout" to any *.franksdank.cloud service from a LAN client is usually a CLIENT split-DNS problem, not a server outage. The public DNS record for franksdank.cloud points at the WAN IP (71.227.227.172) which has NO 443 port-forward (filtered). Pi-hole (192.168.0.143) returns the correct LAN answer 192.168.0.143 via addn-hosts. If a client's systemd-resolved is using 1.1.1.1 (or any external resolver) as its current server, franksdank.cloud resolves to the WAN IP → TCP SYN to a filtered port → timeout (curl rc=28). LAN-only names (dankclaw/grafana/marreta) return NXDOMAIN instead. Diagnose: dig +short www.franksdank.cloud @192.168.0.143 (→143) vs @1.1.1.1 (→WAN); resolvectl status shows the current server. Fix on the client: a systemd-resolved drop-in routing the domain route-only to Pi-hole. Verify the server itself is fine by pinning the LAN IP: curl -sk --resolve www.franksdank.cloud:443:192.168.0.143 https://www.franksdank.cloud/ → 200. _(context: 2026-09-12 homepage "links gone / not loading / connection timeout" on dankware. Server was fully healthy (Caddy @home 200, Anubis challenge valid, Authentik outpost healthy, files render). Root cause was dankware's resolved using 1.1.1.1. Fixed with /etc/systemd/resolved.conf.d/franksdank-lan.conf [Resolve] DNS=192.168.0.143 Domains=~franksdank.cloud ~franksdank.casa + restart systemd-resolved.)_ +- Dankware fleet — "service unreachable / connection timeout" triage for Anubis+Authentik+Caddy guard-chain vhosts. Two durable gotchas: (1) Rootless-podman published-port SNAT makes Anubis log x-real-ip: 10.89.1.22 (the podman gateway) for EVERY client — the real client IP is lost at the published-port layer, so never use Anubis x-real-ip for per-client identity/rate-limit reasoning. (2) The WAN IP 71.227.227.172 has NO 443 port-forward; any client resolving *.franksdank.cloud to the WAN IP (i.e. NOT using Pi-hole 192.168.0.143 as DNS, or off-LAN) gets a TCP connection timeout — the SYN hits the filtered public address. LAN clients must resolve via Pi-hole to 192.168.0.143. To PROVE a guard-chain service is server-side healthy (and thus the fault is the complaining client's DNS/network, not the server): test from a LAN peer — ssh dankserver then dig +short www.franksdank.cloud (expect 192.168.0.143), /dev/tcp/192.168.0.143/443 (expect OPEN), curl -sk https://www.franksdank.cloud/ (expect 200). Also check Pi-hole addn-hosts has the 192.168.0.143 host line and iptables -L INPUT policy is ACCEPT. If the LAN peer gets 200 but one device times out, the server is fine — fix that device's DNS (point at 192.168.0.143) or its LAN connectivity. Note: a curl to the bare IP without matching SNI returns code 35 (SSL name mismatch) — that is NOT a real failure; always use the hostname URL with --resolve or real DNS. _(context: 2026-09-12: homepage reported "links gone" then "connection timeout" from danklivingroom. Exhaustive server-side trace (Anubis challenge page valid, Authentik outpost healthy, Caddy @home/@data 200, files render with visible links, no CSP/redirect-loop, no fail2ban) proved the stack healthy; dankserver LAN peer got https 200 in 7ms. Root cause isolated to the complaining client resolving to the fi)_ +- On dankclaw's Anubis→Authentik→Caddy guard chain, the rootless-podman published-port SNAT masks the real client IP: Anubis logs x-real-ip: 10.89.1.22 (the Caddy container's podman-network address) for EVERY client — verified with both host-local curl and a browser from a different machine (blankpad). So Anubis cannot distinguish clients by IP; clearance is cookie-based, not IP-based. This is pre-existing fleet behavior, NOT caused by config edits, and does not break clearance. When a user reports a guarded page "broken" but the server is healthy, the break is client-side/session — you need the authenticated view (Authentik creds) or the exact symptom, not more server probing. _(context: 2026-09-12 homepage "links gone / not loading" investigation; server-side fully verified healthy, root cause not reached (awaiting user symptom).)_ +- TRANSPORT UPDATE (2026-09-12, supersedes the old "blankpad has no fleet SSH" lesson): blankpad (~/.ssh on user blank) NOW has passwordless SSH to the whole fleet. An ed25519 key was generated (id_ed25519, comment blankpad-deploy-20260912) and its pubkey installed on dank@192.168.0.143 (dankclaw) and dank@192.168.0.235 (dankserver). ~/.ssh/config defines Host aliases dankclaw and dankserver (User dank, IdentityFile ~/.ssh/id_ed25519, IdentitiesOnly yes). So ssh dankclaw / ssh dankserver and ./scripts/deploy.sh dankclaw ... work from this box now — no more "ask Frank for a transport." Passwordless sudo on the targets is separate (FRANK_SUDO_PW / NOPASSWD). MARRETA/LARAVEL APP_KEY GOTCHA: a Laravel APP_KEY must be the base64:-prefixed form — base64:$(openssl rand -base64 32). A bare base64 string (no prefix) is parsed as a raw key of the wrong length and every app route 500s with Unsupported cipher or incorrect key length at Encrypter.php:61, while the health route /up (which skips the encrypter) still returns 200 — so a green healthcheck masks a fully-broken app. Fix before first boot (no encrypted data persisted yet). _(context: Deployed marreta on dankclaw 2026-09-12; had to set up SSH keys from blankpad first, then fix a bare-base64 APP_KEY that 500'd all app routes.)_ +- On the Dankware/Dankclaw fleet, LLM services that are rootless Quadlet units (e.g. frank-large on Dankclaw, user dank) must be read WITHOUT sudo: sudo podman logs name fails with "no such container" because rootless storage is per-user. And because Quadlet runs --replace --rm, podman logs only covers the CURRENT container instance — history across recreations lives only in journalctl --user -u name.service. So: current instance → podman logs; anything before the last recreate → journalctl --user. Separately: the dev box /home/blank/Projects/serverAppConfig (user blank) has NO ssh key or host alias for the fleet hosts — ~/.ssh holds only known_hosts, and dank@192.168.0.143 / frank@192.168.0.143 both return Permission denied (publickey,password). Do not burn calls probing fleet SSH from this box; ask Frank for a working transport before claiming live verification, and label repo-grounded answers as not live-verified. _(context: Answering "how do I retrieve logs for frank-large?" (2026-09-12); grounded in dankclaw/frank-large/quadlet/frank-large.container and serverAppConfigs-docs/LLM/halogen-flash-server.md:191-219. Live verification attempt failed on missing SSH credentials.)_ diff --git a/agent/memories/--home-blank-Projects-serverAppConfig--/memory_summary.md b/agent/memories/--home-blank-Projects-serverAppConfig--/memory_summary.md new file mode 100644 index 0000000..b63212a --- /dev/null +++ b/agent/memories/--home-blank-Projects-serverAppConfig--/memory_summary.md @@ -0,0 +1 @@ +Frank-large rootless Quadlet service on dankclaw. Ports 7770/8731/8730. Weights ~68 GiB. Avoid TEMP/HOST/GPU_MAX_HEAP_SIZE env vars. Logs via podman/journalctl. diff --git a/agent/memories/--home-blank-Projects-serverAppConfig--/raw_memories.md b/agent/memories/--home-blank-Projects-serverAppConfig--/raw_memories.md new file mode 100644 index 0000000..8378d9c --- /dev/null +++ b/agent/memories/--home-blank-Projects-serverAppConfig--/raw_memories.md @@ -0,0 +1,9 @@ +# Raw Memories + +## 01a09686-789e-7042-8774-55507faa78b2 +updated_at: 1789232480 +SERVICE: frank-large (rootless Quadlet, boot-persistent) on dankclaw. Unit: frank-large.service | Container: frank-large. +LOG_RETRIEVAL: podman logs frank-large (full); podman logs frank-large 2>&1 | grep -E '^(dmalloc|kv pool):' (filtered); journalctl --user -u frank-large (journald). +PORTS: Engine :8730 (container loopback, unauth), Front-end :8731, Published :7770. +CONSTRAINTS: Weights pinned (~68 GiB). Env vars to avoid naming: TEMP, HOST, GPU_MAX_HEAP_SIZE (HIP runtime reads bare TEMP for JIT cache path). +TEST_ENV: pyyaml missing for test suite (uv available, pip missing on system python3). Most suite failures are pre-existing environment issues, not commit regressions. diff --git a/agent/memories/--home-blank-Projects-serverAppConfig--/rollout_summaries/01a09686-789e-7042-8774-55507faa78b2-frank_large_quadlet_.md b/agent/memories/--home-blank-Projects-serverAppConfig--/rollout_summaries/01a09686-789e-7042-8774-55507faa78b2-frank_large_quadlet_.md new file mode 100644 index 0000000..73bfa6d --- /dev/null +++ b/agent/memories/--home-blank-Projects-serverAppConfig--/rollout_summaries/01a09686-789e-7042-8774-55507faa78b2-frank_large_quadlet_.md @@ -0,0 +1,4 @@ +thread_id: 01a09686-789e-7042-8774-55507faa78b2 +updated_at: 1789232480 + +frank-large is a rootless Quadlet service on dankclaw. Logs retrieved via podman logs frank-large or journalctl --user -u frank-large. Key constraint: avoid naming TEMP/HOST/GPU_MAX_HEAP_SIZE env vars. Weights pinned (~68 GiB). diff --git a/agent/memories/--home-blank-Projects-serverAppConfig--/skills/frank_large_quadlet/SKILL.md b/agent/memories/--home-blank-Projects-serverAppConfig--/skills/frank_large_quadlet/SKILL.md new file mode 100644 index 0000000..6e244eb --- /dev/null +++ b/agent/memories/--home-blank-Projects-serverAppConfig--/skills/frank_large_quadlet/SKILL.md @@ -0,0 +1,17 @@ +Rootless Quadlet service 'frank-large' on dankclaw. + +Ports: +- Engine: :8730 (loopback, unauth) +- Front-end: :8731 +- Published: :7770 + +Constraints: +- Weights: Pinned (~68 GiB) +- Env Vars: Avoid TEMP, HOST, GPU_MAX_HEAP_SIZE (HIP runtime issue) + +Logging: +- Use podman logs or journalctl. + +Test Env: +- pyyaml missing, uv available, pip missing. +- Failures likely pre-existing. diff --git a/agent/memories/--home-blank-Projects-serverAppConfig--/skills/frank_large_quadlet/scripts/log_retrieval.sh b/agent/memories/--home-blank-Projects-serverAppConfig--/skills/frank_large_quadlet/scripts/log_retrieval.sh new file mode 100644 index 0000000..5730746 --- /dev/null +++ b/agent/memories/--home-blank-Projects-serverAppConfig--/skills/frank_large_quadlet/scripts/log_retrieval.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# Retrieve logs for frank-large service + +# Full logs +podman logs frank-large + +# Filtered logs +podman logs frank-large 2>&1 | grep -E '^(dmalloc|kv pool):' + +# Journald logs +journalctl --user -u frank-large