PhotoPrism's OSS edition has no way to map OIDC claims to BasePath, so
every freshly-registered OIDC user lands with BasePath="" and either
sees the whole library (admin) or nothing (guest) — never their own
subfolder.
Introduces a sidecar-driven reconciler with a single env knob the
admin sets in docker-compose / .env.photoprism:
USER_BASEPATHS="test:test, alice:family/alice, bob:bob"
(`user:originals-relative-path` pairs, comma-separated.) On boot and
every 60s thereafter the sidecar:
- mkdir -p's the target subdirectory under ORIGINALS_ROOT so
PhotoPrism's path: ACL filter has somewhere real to point;
- UPDATEs photoprism.auth_users.base_path for the matching row
where it differs (idempotent, missing users skipped — they
materialise on first OIDC login and the next pass catches them).
The reconciler uses a separate gorm connection scoped to the
`photoprism` schema with PhotoPrism's own DB user, since the existing
`sidecar` user only has grants on `mule_sidecar.*`. Connection stays
dormant when PP_DB_PASSWORD is empty — the feature is opt-in via env.
Compose changes: thread PP_DB_* + USER_BASEPATHS through to the
sidecar service. New users.go file isolates the reconciler logic;
main.go calls startUserBasepathReconciler() during boot.
- New sidecar/Dockerfile: multi-stage golang:1.25-alpine → distroless/
static, ~12 MB final image, static CGO-free binary.
- Wire pp-sidecar into docker-compose.photoprism.yml so the whole
stack (mariadb + photoprism + sidecar) starts with one
`podman-compose up`. Container reaches mariadb + photoprism on the
internal network; the host gets 127.0.0.1:8000 for Vite's proxy.
- New SIDECAR_LISTEN_ADDR env var (default 127.0.0.1 for the host-mode
dev loop) so the container can bind 0.0.0.0:8000 and let the port
mapping reach it. Without this the loopback bind was invisible to
the host.
- Delete sidecar/legacy/server.mjs — the Node prototype's archival
window is over; git history is its home now.
- Update sidecar/README with compose-first bringup; keep the host
`go build` flow as the fast-iteration loop.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the Node prototype (server.mjs) with the stack the merge plan
calls for: Go 1.25, Gin for routing, GORM + MariaDB for persistence.
Same wire contract on /api/sidecar/* so the SvelteKit client doesn't
change.
- Marks move from a JSON file on disk to mule_sidecar.marks (auto-
migrated by GORM on first boot). The Node prototype's marks.json
was dev-only; not migrated.
- Folder/rename/heap-convert/duplicates handlers reproduce the
prototype's behaviour, including the path-traversal defence
(resolveUnderRoot + EvalSymlinks), the size-bucket prefilter for
the duplicate hasher, and the background reindex fire-and-forget
pattern.
- Auth model unchanged: requireSession middleware proxies the
caller's X-Auth-Token to PhotoPrism's /api/v1/photos?count=1
before any destructive op.
- Expose pp-mariadb on 127.0.0.1:3306 in docker-compose so the
host Go process can reach mule_sidecar.* without joining the
container network.
- Archive the Node prototype under sidecar/legacy/server.mjs for
one cycle as reference.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>