photos: immich accounts are configured from the ui, not the environment

IMMICH_URL/IMMICH_API_KEY lived in the platform-wide .env, which was wrong
twice over: bun auto-loads .env into every process started in this directory,
so `officer` itself held an immich credential it has no code to use — and
connecting a library was a shell task on the server rather than something the
owner could do from the app.

it is a registry, not a single connection: any number of labelled accounts with
one selected, the same shape headscale_servers uses. two keys against the same
instance (one per immich user) is the ordinary case, so the label is what has to
be unique, not the url. one active account per owner is enforced by a partial
unique index rather than by convention.

keys are encrypted at rest and write-only across the sidecar boundary — no route
returns one, masked or otherwise. every save is validated against the live
instance first, so a wrong or under-scoped key is a 400 with the reason instead
of a stored row that makes every later screen fail mysteriously.

the drizzle snapshot under migrations/ is regenerated; nothing applies it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-03 16:05:58 +00:00
co-authored by Claude Opus 5
parent c77e7ee598
commit 632d5a1c1f
17 changed files with 3033 additions and 1059 deletions
+3 -1
View File
@@ -88,7 +88,9 @@ module.exports = {
args: 'run src/servers/sidecar/invoiceshelf/index.ts',
watch: false,
},
// The photo library. Wraps a self-hosted Immich and holds its API key; the platform sees none of it.
// The photo library. Wraps a self-hosted Immich. The instance and its key are set by the owner from
// /photos/settings and stored encrypted in `photos_config` — read here, never from the environment,
// because Bun auto-loads `.env` into every process in this directory and `officer` would hold it too.
{
name: 'officer-photos',
script: 'bun',