# oko — Matrix storage backend + dev homeserver (session log) Date: 2026-09-05 Author: opencode session (big-pickle) ## Objective Replace the oko coordinator's backend storage (previously MySQL + local filesystem) with Matrix: every clip is stored in a Matrix room as an `m.video` event, with its metadata posted as an `m.text` reply directly beneath the video event. A real development homeserver (Conduit) was provisioned on the FreeBSD VPS `magbot.online` so the system is usable end-to-end. ## Final routing rules (agreed behavior) | criteria | outcome | |-----------------------------------|------------------------------------------------| | no motion | not stored (dropped before decode/encode) | | motion, no detections | clips room only (+ view room, caption only) | | motion + detections | clips room **and** detections room (+ view room, caption only) | Every motion clip is also posted to a third **view room** (`-view-room`): the same uploaded `mxc://` media as an `m.video` event whose body is a one-line human caption (` · frames · fps · motion %`) and no JSON metadata reply, so a human can browse clips without scrolling through JSON. Livestreaming is explicitly out of scope. ## Coordinator rewrite (Matrix backend) - `coordinator/matrix.go` (new) - `matrixStore` type wrapping `mautrix.Client` (maunium.net/go/mautrix v0.29.0). - `newMatrixStore`: `Whoami` self-check to fail fast on bad token; both room IDs must start with `!` (validated at startup). - `postClip`: uploads a single encode (`UploadBytesWithName`) to get one `mxc://` URL, then sends the `m.video` event and a metadata `m.text` reply with `m.in_reply_to` pointing at the video event. Both rooms reference the same MXC. - `routeTargets`: the routing truth table (no motion -> nil; motion -> clips room; motion+detections -> clips room + detections room). - `hasDetections`: true when any frame has non-empty detections. - `clipJSON`/`buildClipMetadata`: metadata body shape (see schema below). - `coordinator/main.go` - New flags: `-listen`, `-passphrase`, `-matrix-homeserver`, `-matrix-user`, `-matrix-token-file` (or `OKO_MATRIX_TOKEN` env), `-clips-room`, `-detections-room`, `-view-room` (third room, minimal caption only). - Removed `-storage`, `-db-*`, `-keepalive-url`-era MySQL/filesystem writes. - Token is read from a file or env — never from argv (audit #10). - No-motion clips dropped before decode/encode; retained `computeFPS`, temp-file MP4 encode, TCP ingest, passphrase decrypt. - Metadata `m.text` body (pretty-printed JSON), shown in the live rooms: `source_data`, `width`, `height`, `channels`, `gocv_image_type`, `frame_count`, `fps`, `timestamps`, `guids`, `detections` (per-frame map: title/version/…), plus `motion_percent`. ## terp change - `terp/main.go`: removed the "no classifier detections -> drop" branch; all motion clips are now forwarded to the coordinator (required so motion-without-DaemonSet clips reach Matrix). - `terp/terp_test.go`: updated four tests for the new forwarding behavior. - Note: `terp/terp` committed binary was rebuilt to match source (tracked, modified). ## Tests - `coordinator/matrix_test.go` (new): `TestRouteTargets`, `TestHasDetections`, `TestBuildClipMetadata`. - `coordinator/coordinator_test.go`: rewritten; keeps `computeFPS` tests; removed replaced filesystem/MySQL tests. - All pass: `go build`, `go vet`, `go test ./...` in `coordinator` and `terp`. ## Config / repo wiring - `config.yaml`: coord1 node now points at the real homeserver/rooms. - `run-stack.sh`: env-overridable defaults `OKO_MATRIX_HS`, `OKO_MATRIX_USER`, `OKO_MATRIX_TOKEN_FILE`, `OKO_CLIPS_ROOM`, `OKO_DETECTIONS_ROOM`. - `.gitignore`: added `matrix.token`, `*.token`, `logs/`, `storage/`. - `matrix.credentials.example`: redacted template (real creds live on the VPS and in a local 0600 file — see below). ## Dev Matrix homeserver (Conduit) on magbot.online - FreeBSD 14.2 VPS, 1 CPU / ~1 GB RAM / ~70 GB free; SSH as `nolan` (no passwordless sudo, root SSH denied) -> everything installed user-space. - `nolan`'s shell is fish: run remote logic via script files: `sh /path/script.sh`. - Conduit 0.10.13 + rocksdb/gflags/snappy extracted to `/home/nolan/matrixroot`; binary needs `LD_LIBRARY_PATH=/home/nolan/matrixroot/usr/local/lib`. - Config: `/home/nolan/conduit/conduit.toml` (server_name magbot.online, port 8008, rocksdb, max_request_size 100MB, registration now DISABLED, federation off). - Runtime/DB/log: `/home/nolan/conduit/{data,conduit.log}`. - Start script: `/home/nolan/start-conduit.sh` (sets LD_LIBRARY_PATH + CONDUIT_CONFIG, nohup). `@reboot` autostart installed in `nolan` crontab. - Accounts: bot `@okobot:magbot.online` (used by coordinator), viewer `@nolan:magbot.online` (joined both rooms). Bot access token -> repo `matrix.token` (0600, gitignored). - Rooms: clips `!mSnSuG3WTmoTfN6lvNHjFsbwFoMeEXlk4xFRmbg824k` (#oko-clips:magbot.online), detections `!bAkHwtJ5FhfXpEsUFesllGshMejkgKwSQeAPJ8muOq0` (#oko-detections:magbot.online). Both private, nolan invited+joined. - Real credentials stored (0600): VPS `/home/nolan/oko-matrix-credentials.txt` and local `/tmp/opencode/oko-matrix-credentials.txt`. - Reachable over plain HTTP on :8008; no TLS yet (later: nginx reverse proxy on magbot.online, which already runs nginx). ## End-to-end verification (live) - `curl` client API + register (token-based) worked before registration was locked; unprivileged register now returns `M_FORBIDDEN`. - mautrix path health-checked against the live server with a throwaway client (`/tmp/opencode/matrixtest`): Whoami, media upload -> `mxc://`, `m.video` event, metadata reply with `m.in_reply_to` all OK; viewer sync/timeline fetch confirmed. - Ran the real `coordinator` binary against the live homeserver with synthetic gob clips (`/tmp/opencode/clipsender`): - motion only -> clip stored in clips room only. - motion + person detection -> stored in clips room AND detections room. - Viewer `@nolan` retrieved both events + metadata replies. ## Operational notes / TODOs - Smoke/test clips remain in both rooms (Conduit client API returns `M_UNRECOGNIZED` for redaction) — clean up via an admin tool when convenient. - Conduit has no TLS; enable nginx reverse proxy (or tailscale) before deploying to real cameras. Federation currently off. - `terp/terp` tracked binary was back in sync with source (rebuilt; modified). - Local `config.yaml` placeholders were replaced with live values; the livestream side-channel (livestream-cache module + runCam livestream glue) and its config schema were removed entirely; `config.example.yaml` now documents the clip pipeline schema. - No live `OKO_TEST_MATRIX`-gated e2e test was added to the Go test suite; the live path was validated manually as described above. - Coordinator not currently running as a service on the dev box — start via `./run-stack.sh` (or manually) when cameras are attached.