memos sidecar

wraps the self-hosted memos instance, same shape as transmission and slskd. no
schema change was needed: service_connections already says `service` is text
because "adding a service should not be a schema change", and memos is the
one-instance-per-owner case that table was built for.

the sidecar holds the url and the personal access token; the platform side is
16 lines of createSidecarProxy and holds neither.

/_api/* is a pass-through onto the instance's own /api/v1 rather than a
hand-written wrapper per endpoint — memos generates its rest api from protobufs
and it moves between minor versions, so re-describing it here would be a second
thing to keep in sync. the allow-list is the one piece of policy, and it keeps
this from being a general ssrf hop. auth routes are excluded: signin/signout
would mint sessions on the instance, and this authenticates with a stored token.

probing is two calls on purpose. /healthz answers unauthenticated, so a bad url
is distinguishable from a bad token — memos returns 200 and an empty list for
unauthenticated reads rather than 401, so "the list came back" proves nothing.

verified against the live container: unconfigured reports not-connected, a bad
token is rejected WITH the reason and nothing is stored, and the platform mount
401s without a session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 03:24:15 +00:00
co-authored by Claude Opus 5
parent 36c7b1f3fd
commit edf26323da
7 changed files with 288 additions and 1 deletions
@@ -13,7 +13,7 @@ import { encryptSecret, decryptSecret } from '../crypto';
// moment someone forgot to strip it.
/** The services that keep a connection here. Extending it is a one-line change, not a migration. */
export type ServiceName = 'transmission' | 'slskd' | 'esplora' | 'nbxplorer';
export type ServiceName = 'transmission' | 'slskd' | 'esplora' | 'nbxplorer' | 'memos' | 'opengist';
export type ServiceConnection = {
id: number;