sidecar: declarative USER_BASEPATHS reconciler

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.
This commit is contained in:
Claudio
2026-05-18 20:25:19 +00:00
parent 85847848c4
commit 2a75896274
4 changed files with 175 additions and 0 deletions

View File

@@ -40,6 +40,12 @@ func main() {
}
pp := newPPClient(cfg.PhotoprismBaseURL)
// Apply any declared username→BasePath mapping to PhotoPrism's
// auth_users table. Runs immediately + every 60s thereafter so a
// user who logs in after the sidecar booted still gets their
// BasePath wired without an admin restart.
startUserBasepathReconciler(cfg)
gin.SetMode(gin.ReleaseMode)
r := gin.New()
// Keep `%2F` literal in path params so callers can pass URL-encoded