130 lines
7.0 KiB
Markdown
130 lines
7.0 KiB
Markdown
# 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 (`<source> · <n> frames · <fps> fps · motion <n>%`) 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.
|