summaryrefslogtreecommitdiff
path: root/prompts/skills/f3s/references
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-07-18 22:03:55 +0300
committerPaul Buetow <paul@buetow.org>2026-07-18 22:03:55 +0300
commitfbf9d4815ea25bd85be0f232ac8670bda1a64955 (patch)
tree5cdf0f6d2ce069f2682b7abeb53da660e27c1b39 /prompts/skills/f3s/references
parentcee544948612841c8c7831b6de6776b288ce9d17 (diff)
skills: split f3s into six focused sibling skills (index pattern)
Execute plans/f3s-skill-split-plan.md. Carve the oversized f3s skill into six f3s-prefixed siblings so each loads on its own triggers, and slim f3s to a hub (38 reference files -> 10; 119 -> 73 lines): - f3s-storage ZFS/zrepl/CARP/NFS-stunnel, nfs-mount-monitor, backups - f3s-k3s cluster install, off-LAN access, ingress, etcd, r-node Rex - f3s-observability Prometheus/Alloy/Loki/Tempo, FreeBSD node_exporter - f3s-workloads Immich, Garage, Player, yChat, goprecords/uptimed - f3s-raspberry-pi pi0/pi1 NetBSD static site, pi2/pi3 Pi-hole/LAN DNS - f3s-dtail dserver deployment/ops (SSH 2222) f3s hub keeps the master Host-IP table, physical hosts, bhyve, WireGuard mesh, and off-LAN access as the canonical inward-pointing context, plus a Related skills block. Applies skill-maintenance best practices: fixes the three inlined SKILL.md duplications (Pi/webserver, DTail) by moving prose to one canonical home and keeping each new SKILL.md a slim index that points to its references rather than re-inlining them. All cross-skill links rewritten to ../../<skill>/references/ form (incl. inbound links from pkgrepo and rocky-vm-setup, and two pre-existing broken links); verified all 112 relative links resolve. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'prompts/skills/f3s/references')
-rw-r--r--prompts/skills/f3s/references/bootstrap-netbsd-pi.md337
-rw-r--r--prompts/skills/f3s/references/dserver.d7
-rw-r--r--prompts/skills/f3s/references/dtail.md233
-rw-r--r--prompts/skills/f3s/references/garage.md158
-rw-r--r--prompts/skills/f3s/references/goprecords-uptimed.md130
-rw-r--r--prompts/skills/f3s/references/immich.md98
-rw-r--r--prompts/skills/f3s/references/k3s-setup.md12
-rw-r--r--prompts/skills/f3s/references/k3s-setup/ingress.md120
-rw-r--r--prompts/skills/f3s/references/k3s-setup/install.md169
-rw-r--r--prompts/skills/f3s/references/k3s-setup/remote-access.md108
-rw-r--r--prompts/skills/f3s/references/k3s-setup/troubleshooting.md49
-rw-r--r--prompts/skills/f3s/references/observability.md35
-rw-r--r--prompts/skills/f3s/references/observability/freebsd.md112
-rw-r--r--prompts/skills/f3s/references/observability/stack.md158
-rw-r--r--prompts/skills/f3s/references/pihole-pi.md45
-rw-r--r--prompts/skills/f3s/references/player.md167
-rw-r--r--prompts/skills/f3s/references/r-node-deploy.md144
-rw-r--r--prompts/skills/f3s/references/remote-access.md2
-rw-r--r--prompts/skills/f3s/references/rocky-linux-vms.md2
-rw-r--r--prompts/skills/f3s/references/storage.md29
-rw-r--r--prompts/skills/f3s/references/storage/backups.md40
-rw-r--r--prompts/skills/f3s/references/storage/carp.md95
-rw-r--r--prompts/skills/f3s/references/storage/nfs-mount-monitor.md107
-rw-r--r--prompts/skills/f3s/references/storage/nfs.md204
-rw-r--r--prompts/skills/f3s/references/storage/troubleshooting.md198
-rw-r--r--prompts/skills/f3s/references/storage/usb-keys.md116
-rw-r--r--prompts/skills/f3s/references/storage/zfs.md90
-rw-r--r--prompts/skills/f3s/references/storage/zrepl.md237
-rw-r--r--prompts/skills/f3s/references/ychat.md83
29 files changed, 2 insertions, 3283 deletions
diff --git a/prompts/skills/f3s/references/bootstrap-netbsd-pi.md b/prompts/skills/f3s/references/bootstrap-netbsd-pi.md
deleted file mode 100644
index 44d4b58..0000000
--- a/prompts/skills/f3s/references/bootstrap-netbsd-pi.md
+++ /dev/null
@@ -1,337 +0,0 @@
-# NetBSD services on pi0/pi1
-
-`pi0` and `pi1` run NetBSD 10.1 (evbarm-aarch64). This documents how their
-services are installed and configured — useful reference for troubleshooting,
-rebuilding a service, or reinstalling either node.
-
-**Do this one node at a time.** Never take down both of `pi0`/`pi1` (the
-static-HTTP pair) simultaneously — one must always keep serving
-`f3s.buetow.org`/`snonux.foo`.
-
-## Base state
-
-- NetBSD 10.1 `GENERIC64` evbarm64 (aarch64)
-- User `paul`, in group `wheel`, SSH key auth
-- Static LAN IP via `rc.conf` (`ifconfig_mue0="inet 192.168.1.12N netmask
- 0xffffff00"`, `defaultroute="192.168.1.1"`)
-- Hostname set (`hostname="piN.lan.buetow.org"`)
-- No `doas`/`sudo`, no pkgsrc/pkgin by default — bootstrapped below
-- A pre-baked root crontab entry for the hourly goprecords upload already
- points at `/usr/pkg/bin/goprecords-upload-client.sh` with `GOPRECORDS_HOST`
- set correctly — check `doas crontab -l` before deploying that script
- manually to a different path.
-
-## Bootstrap pkgin + real doas
-
-```sh
-ssh paul@piN.lan.buetow.org
-su -
-export PKG_PATH=https://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/aarch64/10.1/All/
-pkg_add -v pkgin
-pkgin -y update
-pkgin -y install doas rsync curl
-printf 'permit nopass :wheel\n' > /usr/pkg/etc/doas.conf # NOT "permit persist" --
- # that still prompts once
- # per session, which never
- # succeeds over a
- # non-interactive SSH
- # command (no tty)
-chmod 644 /usr/pkg/etc/doas.conf
-exit # back to paul
-doas true # should succeed with no password prompt
-```
-
-**Why real `doas`, not the Rocky pattern**: `pi2`–`pi3` only alias `doas` to
-`sudo` via `/etc/profile.d/doas.sh`, which doesn't expand in the
-non-interactive shell an SSH command runs in — so
-`~/git/dotfiles/scripts/wol-f3s`'s `shutdown-pis`/`shutdown-all` (which runs
-`ssh paul@pi "doas poweroff"`) is silently broken on the Rocky Pis today. A
-real `doas` binary is why it works on `pi0`/`pi1`.
-
-**Gotcha**: commands run via `doas` get a minimal `PATH` that excludes
-`/usr/sbin` and `/usr/pkg/bin` — always use full paths (`doas
-/usr/sbin/chown`, `doas /usr/pkg/bin/wg`) or an explicit `PATH=` for cron.
-
-## WireGuard (userspace — no native `wg(4)` on this platform)
-
-**`wg(4)` doesn't exist on evbarm-aarch64 10.1** — the module is absent from
-all 249 files under `/stand/evbarm/10.1/modules`, so `ifconfig wg0 create`
-fails outright (`clone_command: Invalid argument`), despite `wg(4)` being
-upstream NetBSD since 9.2. Don't waste time on it; `wireguard-go` + `wg`
-(pkgsrc `wireguard-tools`, **no `wg-quick`** in this build) is the working
-path:
-
-```sh
-pkgin -y install wireguard-go wireguard-tools
-```
-
-**`wireguardmeshgenerator` (`~/git/wireguardmeshgenerator`) has full NetBSD
-support** — for a host with `os: NetBSD` in `wireguardmeshgenerator.yaml`,
-`--generate` produces both the stripped `tun0.conf` (no `Address`/`DNS` lines
-— `wg setconf` rejects wg-quick extensions with "Line unrecognized") and the
-`/etc/rc.d/wireguard` script itself, with one `route add`/`delete` pair per
-peer AllowedIPs prefix (wg-quick would normally manage these automatically;
-`wg` only does the crypto/routing decision inside the tunnel). `--install`
-uploads and places both files with the right ownership/permissions and
-restarts the service. Per-host YAML fields that matter for a NetBSD entry:
-
-```yaml
-pi0:
- os: NetBSD
- ssh:
- user: paul
- conf_dir: /usr/pkg/etc/wireguard
- sudo_cmd: doas
- reload_cmd: /etc/rc.d/wireguard restart
- wg_bin: /usr/pkg/bin/wg # doas's PATH excludes /usr/pkg/bin
-```
-
-Key facts the generator's implementation encodes:
-
-- The interface **must** be named `tunN` (`wireguard-go` rejects `wg0`:
- "Interface name must be tun[0-9]*"). The generator always uses `tun0`.
-- **The interface must be addressed before `wireguard-go` starts**, or its
- read loop dies immediately with `EHOSTDOWN` ("host is down") and does not
- retry — the generated rc.d script's `wireguard_start` does `ifconfig`
- before `wireguard-go`.
-- `doas` on NetBSD resets `PATH` to exclude both `/usr/pkg/bin` (hence the
- `wg_bin` override above) and `/usr/sbin` (hence the generator using a full
- path for `chown` during install — caught by actually running `--install`
- against a live host, not by inspection).
-
-To (re)deploy after any topology change: `ruby wireguardmeshgenerator.rb
---generate --install --hosts=pi0,pi1`.
-
-## Webserver — bozohttpd
-
-Built into NetBSD base, no package or config file. No stock rc.d exists that
-actually uses `httpd_flags` (the shipped `/etc/rc.d/httpd` computes
-`command_args` itself and never references that variable) — write a
-dedicated `/etc/rc.d/bozohttpd`:
-
-```sh
-command="/usr/libexec/httpd"
-pidfile="/var/run/bozohttpd.pid"
-command_args="-b -X -U _httpd -P ${pidfile} -v /var/www/html -V /var/www/html"
-required_dirs="/var/www/html"
-```
-
-- `-v /var/www/html -V /var/www/html`: vhost directory = same tree as the
- default docroot. A `Host:` header matching a **literally-named**
- subdirectory (e.g. `snonux.foo/`) is served from there; anything unmatched
- falls back to the plain docroot via `-V`.
-- `www.snonux.foo` needs to be a symlink to `snonux.foo` (bozohttpd matches
- the literal Host header as a directory name, not a regex like lighttpd's
- `$HTTP["host"] =~ "^(www\.)?snonux\.foo$"`).
-- **`-X` (directory indexing) is required**, not optional: bare directories
- with no `index.html` (e.g. a photo gallery folder under `/fotos/`) 404
- without it.
-- **Give every real routed hostname its own vhost entry, even the "default"
- one** — don't rely on `-V` fallback for anything actually reachable from
- the internet. `f3s.buetow.org` (checked in `relayd.conf` on the frontends:
- the real routed names are `f3s.buetow.org`, `www.f3s.buetow.org`,
- `standby.f3s.buetow.org` — `/scifi/` etc. are **paths** under it, not
- separate subdomains) needs a vhost dir, or it hits `-V`, and bozohttpd's
- directory-without-trailing-slash redirect in that fallback path uses its
- own **system hostname**, not the client's `Host:` header (unlike a real
- vhost match, which correctly echoes back e.g. `snonux.foo`). Since the
- system hostname (`piN.lan.buetow.org`) doesn't resolve outside the LAN,
- this produces redirects that hang for external clients. Fix:
- self-referencing symlinks so these become vhost matches instead of
- fallbacks — `ln -sf . /var/www/html/f3s.buetow.org` (and the
- `www.`/`standby.` variants).
-
-Enable with `bozohttpd=YES` in `/etc/rc.conf`.
-
-## Static content sync
-
-`pi0` is the source of truth for `/var/www/html`; `pi1` pulls hourly:
-
-```sh
-#!/bin/sh
-set -e
-STAGE=/tmp/wwwsync-cron
-mkdir -p "$STAGE"
-rsync -a --delete -e "ssh -o StrictHostKeyChecking=accept-new" \
- paul@pi0.lan.buetow.org:/var/www/html/ "$STAGE/"
-doas rsync -a --delete --exclude=snonux.foo "$STAGE/" /var/www/html/
-doas rsync -a --delete "$STAGE/snonux.foo/" /var/www/html/snonux.foo/
-doas /usr/sbin/chown -R root:wheel /var/www/html/index.html /var/www/html/fotos /var/www/html/scifi
-# snonux.foo is owned by paul, not root: the snonux microblog tool rsyncs
-# directly into it as paul (no doas hop), so it must stay paul-writable.
-doas /usr/sbin/chown -R paul:wheel /var/www/html/snonux.foo
-```
-
-Needs an SSH keypair for `paul` on `pi1`, authorized on `pi0`'s
-`~/.ssh/authorized_keys`, plus a static `/etc/hosts` entry for `pi0` (Pi-to-Pi
-`.lan.buetow.org` resolution isn't reliable — add the IP directly rather
-than debugging DNS).
-
-Install as `paul`'s crontab on `pi1` (not root's — needs the SSH key):
-`47 * * * * /usr/local/bin/sync-from-pi0.sh >$HOME/sync-from-pi0.log 2>&1`
-
-## uptimed (built from source — no prebuilt package)
-
-**No aarch64 binary package exists** in pkgsrc for `uptimed` on any branch
-checked (10.0, 10.1, 11.0, 9.4). Build from upstream instead — small C
-project, NetBSD base already has `gcc`/`make`:
-
-```sh
-pkgin -y install autoconf automake libtool pkg-config
-cd /tmp
-curl -sLO https://github.com/rpodgorny/uptimed/archive/refs/tags/v0.4.7.tar.gz
-tar xzf v0.4.7.tar.gz && cd uptimed-0.4.7
-PATH=/usr/pkg/bin:$PATH ./autogen.sh
-PATH=/usr/pkg/bin:$PATH ./configure --prefix=/usr/pkg --sysconfdir=/etc
-PATH=/usr/pkg/bin:$PATH make
-doas env PATH=/usr/pkg/bin:/usr/bin:/bin:/usr/sbin:/sbin make install
-```
-
-Installs `uptimed` to `/usr/pkg/sbin`, `uprecords` to `/usr/pkg/bin`, and uses
-`/var/spool/uptimed/records` (hardcoded upstream, not an OS convention thing).
-
-**Before first start**, write `/etc/uptimed.conf` with `LOG_MAXIMUM_ENTRIES=0`
-(keep forever) plus milestone lines. If restoring a backed-up uptime history,
-seed **both** `records` and `records.old` with the same content:
-
-```sh
-doas cp <backed-up-records-file> /var/spool/uptimed/records
-doas cp <backed-up-records-file> /var/spool/uptimed/records.old # both, not just one
-doas /usr/sbin/chown root:wheel /var/spool/uptimed/records /var/spool/uptimed/records.old
-```
-
-**Critical bug to know about**: `read_records()` in `libuptimed/urec.c`
-unconditionally sets `useold = -1` ("no useable database found") if
-`records.old` doesn't exist yet — **regardless of whether the primary
-`records` file is valid**. Seeding only `records` and starting the daemon
-loses the imported history immediately (it gets shunted to a fresh
-`records.old` on the first periodic rewrite, then overwritten again 60s
-later). Seed **both** files with the same content before the first start.
-
-Write a custom `/etc/rc.d/uptimed` (upstream ships a Linux-init `etc/rc.uptimed`,
-not usable directly):
-
-```sh
-# PROVIDE: uptimed
-# REQUIRE: NETWORKING ntpdate
-# KEYWORD: shutdown
-
-command="/usr/pkg/sbin/uptimed"
-pidfile="/var/run/uptimed.pid"
-command_args="-p ${pidfile}"
-```
-
-These Raspberry Pis have no hardware RTC. NetBSD initializes the wall clock
-from the root filesystem timestamp, which can be stale after power-off. Merely
-starting `ntpd` is not a synchronization barrier, so enable the synchronous
-boot-time correction in `/etc/rc.conf`:
-
-```sh
-ntpdate=YES
-```
-
-The explicit `ntpdate` requirement above ensures that uptimed starts only after
-the clock has been set from the network. Verify the boot ordering with:
-
-```sh
-/sbin/rcorder /etc/rc.d/* | egrep '/(NETWORKING|ntpdate|ntpd|uptimed)$'
-```
-
-The expected order is `NETWORKING`, `ntpdate`, `ntpd`, then `uptimed`. This was
-deployed and reboot-tested one node at a time on both pi0 and pi1 on
-**2026-07-16**. `ntpdate` corrected pi0 by 12.29 seconds and pi1 by 12.68
-seconds before uptimed started; their new current records had the correct boot
-times, both webservers returned HTTP 200, and no historical record repair was
-necessary.
-
-Run `uptimed -b` once (creates the boot ID), enable with `uptimed=YES`.
-
-## goprecords upload
-
-```sh
-kubectl exec -n services deployment/goprecords -- \
- goprecords --create-client-key <host> -stats-dir=/data/stats
-```
-(from a machine with cluster access — this can occasionally 502 if the
-apiserver's exec proxy can't reach whichever k3s node the pod landed on; just
-retry, it's a transient networking issue, not a token problem.)
-
-Deploy `goprecords-upload-client.sh` (from `~/git/goprecords/scripts/`,
-already POSIX/generic and already handles `/var/spool/uptimed/records` and a
-NetBSD `dmesg.boot`/`sysctl` fallback for `os.txt`/`cpuinfo.txt` — no changes
-needed) to **`/usr/pkg/bin/`** to match the pre-baked crontab's path, token at
-`/etc/goprecords-upload.token` (`0600`), `GOPRECORDS_HOST=<host>`.
-
-`curl` and `uprecords` need to be resolvable via whatever `PATH` the cron
-entry sets — test with that exact `PATH` before trusting a manual test run
-under plain `doas` (which won't have it).
-
-## Firewall — npf, not firewalld
-
-```
-$ext_if = "mue0"
-
-group "external" on $ext_if {
- pass stateful out final all
- pass stateful in final family inet4 proto tcp to $ext_if port 22
- pass stateful in final family inet4 proto tcp to $ext_if port 80
- pass stateful in final family inet4 proto tcp to $ext_if port 2222
- pass stateful in final family inet4 proto icmp all
-}
-
-group "wireguard" on tun0 {
- pass stateful out final all
- pass stateful in final family inet4 all
- pass stateful in final family inet6 all
-}
-
-group default {
- pass final on lo0 all
- block all
-}
-```
-
-Port 2222 is dserver (DTail) — see the `pkgrepo` skill's `dtail-package.md`
-for the install steps.
-
-`family inet4`/`inet6` must be explicit on multi-family interfaces or
-`npfctl validate` fails with "address family mismatch". `proto <name>` must
-be followed by `all` or a `from`/`to` clause, or it's a syntax error — e.g.
-`proto icmp` alone fails, `proto icmp all` doesn't. Don't forget the ICMP
-rule: without it, ping-dependent tooling (e.g. the `snonux` publishing
-tool's reachability pre-check) silently breaks while SSH/HTTP keep working
-fine.
-
-Sequence carefully to avoid locking yourself out over SSH:
-
-```sh
-doas npfctl validate # syntax-check first
-doas npfctl reload # loads config, does NOT enable filtering yet
-doas npfctl start # enables filtering
-# from a FRESH ssh connection (not the one you're already in), confirm:
-# - ssh still connects
-# - curl http://localhost/ still works
-doas sh -c 'echo npf=YES >> /etc/rc.conf' # only after confirming the above
-```
-
-If you get a JIT warning (`error loading the bpfjit module... Operation not
-permitted`) — harmless, just means `kern.securelevel` blocks loading that
-optional performance module; filtering still works, just slightly slower
-packet matching.
-
-## Verification
-
-- `curl -fsI http://<host>.lan.buetow.org/` and the vhost via `Host:` header.
-- `wg show tun0` shows recent handshakes with `blowfish` and `fishfinger` (and
- `rocky` if that VM happens to be up — it's often not, unrelated to this).
-- goprecords report (`https://goprecords.f3s.buetow.org/report`) picks up the
- host after the hourly cron fires (won't rank in the "top 20 all-time" table
- with a short history — that's expected, not a failure).
-- **Redundancy test**: stop the *other* node's webserver entirely, then curl
- every real page through the **public** domains (not just localhost) — root
- page, each vhost, and any bare directory paths (e.g. `/fotos/`). Restore
- the other node's webserver immediately after.
-- `wol-f3s shutdown-pis` (or a targeted `ssh paul@<host> "doas poweroff"`)
- actually powers the Pi off — confirms `doas` works non-interactively, but
- there's no WoL for Pis, so only do this when you can physically power it
- back on.
diff --git a/prompts/skills/f3s/references/dserver.d b/prompts/skills/f3s/references/dserver.d
deleted file mode 100644
index 49d5c54..0000000
--- a/prompts/skills/f3s/references/dserver.d
+++ /dev/null
@@ -1,7 +0,0 @@
-# dserver on f3s (index)
-
-- **r0–r2 Rocky bhyve / k3s VMs** — install context and SSH notes: [Rocky Linux VMs – DTail (dserver) on r0–r2](rocky-linux-vms.md#dtail-dserver-on-r0r2)
-- **pi0–pi1 NetBSD Pis** — dserver installed from the custom pkgrepo (`dtail-4.3.2ng` package): build with `make dtail-netbsd` in `~/git/conf/packages` (cross-compile netbsd/arm64, package natively on pi0, upload to pkgrepo), deploy via `pkg_add https://pkgrepo.f3s.buetow.org/netbsd/10.1/packages/aarch64/dtail-<version>.tgz`. Full build/install/rc.d/npf details and gotchas: `pkgrepo` skill's `dtail-package.md`
-- **Full DTail reference** (NetBSD + Rocky Pis, r VMs amd64, firewalld, key cache, clients): [dtail.md](dtail.md)
-
-Upstream repo: `https://codeberg.org/snonux/dtail` — `doc/installation.md`, `examples/`.
diff --git a/prompts/skills/f3s/references/dtail.md b/prompts/skills/f3s/references/dtail.md
deleted file mode 100644
index c7963a5..0000000
--- a/prompts/skills/f3s/references/dtail.md
+++ /dev/null
@@ -1,233 +0,0 @@
-# DTail / dserver on f3s
-
-[DTail](https://codeberg.org/snonux/dtail) is a distributed log tool (tail/cat/grep/MapReduce) over SSH. The **dserver** daemon listens on **TCP 2222** (not port 22). Clients (`dtail`, `dcat`, `dgrep`, `dmap`, …) use normal SSH keys against dserver.
-
-Upstream install and examples live in the repo: `doc/installation.md`, `examples/`.
-
-## Host roles in this lab
-
-| Hosts | OS / arch | dserver binary | Typical SSH user |
-|-------|-----------|----------------|------------------|
-| **pi0–pi1** | NetBSD 10.1 **aarch64** (Raspberry Pi 3, see `bootstrap-netbsd-pi.md`) | Cross-build **netbsd/arm64**, `nozstd`; installed via `pkg_add` from the custom pkgrepo | `paul@piN.lan.buetow.org` |
-| **pi2–pi3** | Rocky Linux 9 **aarch64** (Raspberry Pi 3) | Cross-build **linux/arm64**, `nozstd` | `paul@piN.lan.buetow.org` |
-| **r0–r2** | Rocky Linux 9 **x86_64** (bhyve VMs, k3s nodes) | Cross-build **linux/amd64**, `nozstd` | Often `root@rN.lan.buetow.org` (see [Rocky Linux VMs](rocky-linux-vms.md)); add `root` (and `paul` if present) to **Server.Permissions.Users** in `dtail.json` |
-| **blowfish, fishfinger** | OpenBSD 7.8 **amd64** | Native OpenBSD package build | `rex@blowfish.buetow.org`, `rex@fishfinger.buetow.org` |
-
-`pi0`/`pi1` (NetBSD) run dserver since 2026-07-09 from the `dtail` package in the custom pkgrepo — build pipeline (`make dtail-netbsd`), install steps, rc.d/npf details, and gotchas live in the `pkgrepo` skill's `dtail-package.md`.
-
-**Key cache filenames matter:** `examples/update_key_cache.sh.example` only scans `/home/*` and writes `/var/run/dserver/cache/USER.authorized_keys`. In this lab, DTail auth worked only after writing the exact cache filename for the login user:
-
-- **r0–r2**: `root.authorized_keys`
-- **pi0–pi1**: `paul.authorized_keys` (NetBSD rc.d `start_precmd` repopulates it on every start — `/var/run` is volatile)
-- **pi2–pi3**: `paul.authorized_keys`
-- **blowfish, fishfinger**: `rex.authorized_keys`
-
-If clients connect as **root**, copy keys once (e.g. after install) and on key changes:
-
-```bash
-cp /root/.ssh/authorized_keys /var/run/dserver/cache/root.authorized_keys
-chown dserver:dserver /var/run/dserver/cache/root.authorized_keys
-chmod 600 /var/run/dserver/cache/root.authorized_keys
-```
-
-For the Pi nodes:
-
-```bash
-cp /home/paul/.ssh/authorized_keys /var/run/dserver/cache/paul.authorized_keys
-chown dserver:dserver /var/run/dserver/cache/paul.authorized_keys
-chmod 600 /var/run/dserver/cache/paul.authorized_keys
-```
-
-## dserver on r0, r1, r2 (k3s Rocky VMs, amd64)
-
-These hosts are the **x86_64** guests on f0/f1/f2. SSH and VM background: [Rocky Linux VMs](rocky-linux-vms.md), **DTail subsection** (same content in short form): [DTail (dserver) on r0–r2](rocky-linux-vms.md#dtail-dserver-on-r0r2). Shortcut index file: [dserver.d](dserver.d). **Do not** install the Pi **arm64** binary here.
-
-| Item | Value |
-|------|--------|
-| Hostnames | `r0.lan.buetow.org`, `r1.lan.buetow.org`, `r2.lan.buetow.org` |
-| LAN IPs | `192.168.1.120`–`122` |
-| Admin SSH (normal) | `ssh -p 22 root@rN.lan.buetow.org` (key-based; see Rocky VM doc) |
-| dserver port | **2222/tcp** (DTail clients); **22** remains sshd |
-
-### Build the binaries (on earth)
-
-```bash
-cd ~/git/dtail # your checkout of https://codeberg.org/snonux/dtail
-
-# exact cross-builds for the Rocky VMs
-for bin in dserver dtail dcat dgrep dmap dtailhealth; do
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags nozstd -o "$bin-linux-amd64" ./cmd/$bin/main.go
-done
-```
-
-Use direct `go build -tags nozstd` when you need a deterministic cross-build artifact. Reusing `make dserver` after a local native build can leave an old binary in place because the target name is just `dserver`.
-
-### Install on each rN (run as root over SSH)
-
-1. Copy to the VM, e.g. `scp -P 22 dserver root@r0.lan.buetow.org:/tmp/` and the files from `examples/` (see checklist below), or unpack a small staging dir on `/tmp/dtail-install/`.
-
-2. **System user and dirs**
- ```bash
- id dserver &>/dev/null || useradd -r -d /var/lib/dserver -s /sbin/nologin -U dserver
- mkdir -p /etc/dserver /var/run/dserver/cache /var/run/dserver/log
- chown -R dserver:dserver /var/run/dserver
- install -m 755 /tmp/dserver /usr/local/bin/dserver
- ```
-
-3. **`/etc/dserver/dtail.json`** — start from `examples/dtail.json.example` and ensure **`Server.Permissions.Users` includes `"root"`** (with the same `readfiles` rules you need). The stock example lists `paul` / `pbuetow`; without **`root`**, root cannot use dserver even with a valid key.
-
-4. **systemd units** (from repo `examples/`):
- - `dserver.service` → `/etc/systemd/system/dserver.service`
- - `update_key_cache.sh.example` → `/var/run/dserver/update_key_cache.sh` (mode `0755`)
- - `dserver-update-keycache.service` + **timer** (use a timer unit with `[Install] WantedBy=timers.target` if the raw example omits it) → `/etc/systemd/system/`
- - Optional: `prune_dserver_logs.sh.example`, `dserver-prune-logs.service`, `dserver-prune-logs.timer`
-
- ```bash
- systemctl daemon-reload
- systemctl enable --now dserver-update-keycache.timer
- systemctl enable --now dserver-prune-logs.timer # if installed
- systemctl start dserver-update-keycache.service # populate cache once
- ```
-
- **Important:** the stock unit uses `WorkingDirectory=/var/run/dserver`. On Rocky and Pi hosts, `dserver` failed with `status=200/CHDIR` until the unit also recreated that tmpfs path at service start. Add:
-
- ```ini
- RuntimeDirectory=dserver
- RuntimeDirectoryMode=0755
- ExecStartPre=/usr/bin/mkdir -p /var/run/dserver/cache /var/run/dserver/log
- ```
-
- Then run:
-
- ```bash
- systemctl daemon-reload
- systemctl reset-failed dserver
- systemctl start dserver
- ```
-
-5. **Root SSH key cache (required on r VMs)**
- The stock `update_key_cache.sh` only copies `/home/*/.ssh/authorized_keys`. **Root’s keys live in `/root`**, so mirror them explicitly (after any change to root’s authorized_keys):
-
- ```bash
- cp /root/.ssh/authorized_keys /var/run/dserver/cache/root.authorized_keys
- chown dserver:dserver /var/run/dserver/cache/root.authorized_keys
- chmod 600 /var/run/dserver/cache/root.authorized_keys
- ```
-
-6. **firewalld** (default on Rocky):
-
- ```bash
- firewall-cmd --permanent --add-port=2222/tcp && firewall-cmd --reload
- ```
-
-7. **Start dserver** (unit is **disabled** by default for boot; start manually or `enable` if desired):
-
- ```bash
- systemctl start dserver
- systemctl is-active dserver
- ss -tlnp | grep 2222
- ```
-
- Expect `dserver` listening on `*:2222` and `ssh_host_key` plus `root.authorized_keys` under `/var/run/dserver/cache/`.
-
-### Client usage from earth
-
-DTail uses the **same username** as normal SSH unless overridden. If dserver is set up for **root** only on r VMs, run as root or pass the client user your config expects. Example:
-
-```bash
-dcat --plain --noColor --trustAllHosts --user root \
- --servers r0.lan.buetow.org,r1.lan.buetow.org,r2.lan.buetow.org \
- --files /etc/fstab
-```
-
-Add **2222** host keys to `~/.ssh/known_hosts` the first time (interactive trust, or `ssh-keyscan -p 2222`).
-
-## Cross-compiling dserver (from earth or any dev machine)
-
-From a clone of the repo:
-
-```bash
-cd ~/git/dtail # or your checkout
-
-# Raspberry Pi 4× — linux/arm64, static, no CGO zstd
-CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -tags nozstd -o dserver-linux-arm64 ./cmd/dserver/main.go
-
-# k3s VMs r0–r2 — linux/amd64
-CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags nozstd -o dserver-linux-amd64 ./cmd/dserver/main.go
-```
-
-`DTAIL_NO_ZSTD=yes` sets the `nozstd` build tag so the binary does not link DataDog’s CGO zstd (required for static cross-compiles). **`.zst` log files are not supported** in that binary; gzip still works.
-
-## Installation checklist (each server)
-
-Do this on **each** Linux target (pi2–pi3 and/or r0–r2). pi0/pi1 are NetBSD and install the `dtail` package from the custom pkgrepo instead — see the `pkgrepo` skill's `dtail-package.md`. Adjust **user** if you are not using `paul` on the node.
-
-1. **Binary**: `/usr/local/bin/dserver`, mode `0755`, owned by root.
-2. **OS user**: `dserver` system account (`useradd -r -d /var/lib/dserver -s /sbin/nologin -U dserver`).
-3. **Directories**: `/etc/dserver`, `/var/run/dserver` (tmpfs — recreated at boot; use systemd `RuntimeDirectory` in the example unit if you adopt it).
-4. **Config**: `/etc/dserver/dtail.json` from `examples/dtail.json.example` — ensure **Server.Permissions.Users** includes every login name that will connect (e.g. `paul`, `root`).
-5. **systemd**: `examples/dserver.service.example` → `/etc/systemd/system/dserver.service` — unit stays **disabled** by default; start with `systemctl start dserver`. Add `RuntimeDirectory=dserver`, `RuntimeDirectoryMode=0755`, and `ExecStartPre=/usr/bin/mkdir -p /var/run/dserver/cache /var/run/dserver/log` so the tmpfs working directory exists before `dserver` starts.
-6. **SSH host keys for clients**: dserver cannot read users’ `~/.ssh/authorized_keys` as user `dserver`. Use `examples/update_key_cache.sh.example` + `dserver-update-keycache.service` / `.timer` to mirror keys into `/var/run/dserver/cache/USER.authorized_keys`.
-7. **firewalld**: allow **2222/tcp** (ping may work while TCP is blocked):
-
- ```bash
- sudo firewall-cmd --permanent --add-port=2222/tcp && sudo firewall-cmd --reload
- ```
-
- Or run `examples/firewalld-dserver-port.sh.example` once.
-
-8. **Optional**: log pruning — `examples/prune_dserver_logs.sh.example` + `dserver-prune-logs` service/timer (see `doc/installation.md`).
-
-## SSH from laptops (earth)
-
-- **Port 2222** is dserver, **port 22** is normal sshd.
-- If `~/.ssh/config` has a broad `Host *.buetow.org` with a wrong `Port`, **narrow overrides** for `*.lan.buetow.org` with `Port 22`, or use `ssh -p 22` / `dcat` default port 2222 only for DTail targets.
-- First-time **host keys** go to `~/.ssh/known_hosts` for `[hostname]:2222` (and IP lines). A **stdout logger deadlock** when trusting new hosts was fixed upstream (release including commit `28f6319`+); rebuild clients if you still see a hang after “trust these hosts”.
-
-## Client examples
-
-```bash
-dcat --plain --noColor --trustAllHosts --user paul \
- --servers pi2.lan.buetow.org,pi3.lan.buetow.org \
- --files /etc/fstab
-
-dcat --plain --noColor --trustAllHosts --user root \
- --servers r0.lan.buetow.org,r1.lan.buetow.org,r2.lan.buetow.org \
- --files /etc/fstab
-```
-
-Use hostnames that resolve from where you run the client (often `*.lan.buetow.org`).
-
-## Package-managed DTail
-
-For package-repo-backed DTail and other custom package repo tasks, use the sibling `pkgrepo` skill and its reference:
-
-- [Package Repositories](../../pkgrepo/references/package-repos.md)
-
-That skill now owns:
-
-- FreeBSD, OpenBSD, and Rocky client repo configuration
-- custom repo layout, publication, and verification notes
-- Rocky repo client configuration and RPM install flow
-- OpenBSD DTail package build, publish, replace, and cache-refresh steps
-- DTail package publication details that depend on the repo
-
-## Verified lab state
-
-On 2026-04-11 this setup was verified end-to-end with:
-
-- the full Linux binary set (`dserver`, `dtail`, `dcat`, `dgrep`, `dmap`, `dtailhealth`) installed on `r0`-`r2` and `pi0`-`pi3`
-- `dserver` active and listening on `*:2222` on all seven hosts
-- successful `dcat /etc/fstab` reads from earth using `--user root` for `r0`-`r2` and `--user paul` for `pi0`-`pi3`
-
-## k3s / r0–r2 notes
-
-- **amd64** binaries only; do not deploy the Pi **arm64** build there.
-- Same **2222** and **firewalld** rules apply.
-- These VMs are heavier than the Pis; defaults in `dtail.json` (`MaxConcurrentCats`, etc.) can be raised if needed.
-- Full install sequence for these three nodes: section **dserver on r0, r1, r2** above.
-
-## Related upstream fixes (f3s-relevant)
-
-- **Host key file**: first boot uses `RootedPath.Stat` + `errors.Is(…, fs.ErrNotExist)` so missing `cache/ssh_host_key` triggers generation (older `os.IsNotExist` missed wrapped errors).
-- **Known-hosts prompt deadlock**: `internal/io/dlog/loggers/stdout.go` must not hold its mutex across the pause/resume wait when the trust prompt runs.
diff --git a/prompts/skills/f3s/references/garage.md b/prompts/skills/f3s/references/garage.md
deleted file mode 100644
index 52120df..0000000
--- a/prompts/skills/f3s/references/garage.md
+++ /dev/null
@@ -1,158 +0,0 @@
-# Garage
-
-Garage S3 runs as a 3-node cluster on FreeBSD hosts `f0`, `f1`, and `f2`.
-
-## Topology
-
-- Nodes: `f0.lan.buetow.org`, `f1.lan.buetow.org`, `f2.lan.buetow.org`
-- RPC: `:3901`
-- S3 API: `:3900`
-- Admin/metrics: `:3903`
-- Layout capacity target: `f0=8`, `f1=8`, `f2=4` (same ratio currently applied)
-- Zone: currently all in `dc1`
-- Garage version: `2.2.0` (cargo build)
-
-## Local Data and Service Setup
-
-- Encrypted ZFS datasets created per host:
- - `zroot/garage/meta` mounted at `/var/db/garage/meta`
- - `zroot/garage/data` mounted at `/var/db/garage/data`
-- Service enabled:
- - `garage_enable=YES` in `/etc/rc.conf`
-- Config deployed by repo automation in `f3s/garage/`:
- - `f3s/garage/Rexfile`
- - `f3s/garage/Justfile`
- - `f3s/garage/etc/garage.f0.toml`
- - `f3s/garage/etc/garage.f1.toml`
- - `f3s/garage/etc/garage.f2.toml`
-- Shared RPC secret is read from:
- - `f3s/garage/secrets/rpc_secret` (intentionally gitignored)
-
-## Edge Domain and Frontend Routing
-
-- Public hostname: `garage.f3s.buetow.org`
-- Frontend wiring exists:
- - Domain included in `frontends/Rexfile` f3s host list
- - `relayd` backend table and host match added in `frontends/etc/relayd.conf.tpl`
- - TLS certificate for `garage.f3s.buetow.org` is issued and served
-- Current routing health:
- - DNS resolves on public edge hosts (`A` + `AAAA`)
- - HTTPS