feat: PhotoPrism M0 bring-up — compose stack, web client, sidecar, migrate

Replace the legacy mule-image backend with PhotoPrism plus a thin
SvelteKit client and a Node sidecar for endpoints PhotoPrism doesn't
expose (file rename), and add a two-phase migrator (metadata via PUT,
heaps → albums) for the existing Postgres library.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 16:06:58 +02:00
parent 423a73a8a6
commit 8c2526d982
69 changed files with 12048 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
# PhotoPrism stack — replaces the legacy mule-image backend over the course of
# milestones M0M5 (see /home/dtoro/.claude/plans/i-want-you-to-twinkly-galaxy.md).
#
# This compose file is intentionally separate from docker-compose.yml so the
# legacy mule-image stack keeps running side-by-side until M5 cutover, when
# data is migrated and the legacy backend is retired.
#
# docker compose -f docker-compose.photoprism.yml up -d
#
# M0 scope: mariadb + photoprism only. Library mounted READ-ONLY so initial
# indexing cannot mutate originals while we validate. Backwrite, sidecar
# service, web frontend, and reverse proxy land in later milestones.
services:
mariadb:
# Fully-qualified for podman (which refuses short names by default).
# Docker resolves the same digest.
image: docker.io/library/mariadb:11
container_name: pp-mariadb
restart: unless-stopped
command:
- --innodb-buffer-pool-size=512M
- --transaction-isolation=READ-COMMITTED
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --max-connections=512
- --innodb-rollback-on-timeout=OFF
- --innodb-lock-wait-timeout=120
environment:
MARIADB_AUTO_UPGRADE: "1"
MARIADB_INITDB_SKIP_TZINFO: "1"
MARIADB_DATABASE: ${PP_DB_NAME:-photoprism}
MARIADB_USER: ${PP_DB_USER:-photoprism}
MARIADB_PASSWORD: ${PP_DB_PASSWORD:?set PP_DB_PASSWORD in .env.photoprism}
MARIADB_ROOT_PASSWORD: ${PP_DB_ROOT_PASSWORD:?set PP_DB_ROOT_PASSWORD in .env.photoprism}
volumes:
- pp_mariadb_data:/var/lib/mysql
# The init script creates the mule_sidecar database + user that the Go
# sidecar service will use in M4. Idempotent; no-op on subsequent boots.
# ":Z" is the SELinux private-relabel flag — needed on Fedora/RHEL hosts,
# silently no-op on Debian/Ubuntu and macOS Docker Desktop.
- ./mariadb/init:/docker-entrypoint-initdb.d:ro,Z
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 12
start_period: 60s
networks: [photoprism-network]
photoprism:
image: docker.io/photoprism/photoprism:latest
container_name: pp-app
restart: unless-stopped
depends_on:
mariadb:
condition: service_healthy
# PhotoPrism's container drops to a non-root user via PHOTOPRISM_UID /
# PHOTOPRISM_GID. Match the host user that owns ${PHOTO_DIRS} so the
# process can read originals (and later write sidecars).
user: "${PP_UID:-1000}:${PP_GID:-1000}"
security_opt:
- seccomp:unconfined
- apparmor:unconfined
ports:
- "${PP_PORT:-2342}:2342"
environment:
PHOTOPRISM_ADMIN_USER: ${PP_ADMIN_USER:-admin}
PHOTOPRISM_ADMIN_PASSWORD: ${PP_ADMIN_PASSWORD:?set PP_ADMIN_PASSWORD in .env.photoprism}
PHOTOPRISM_AUTH_MODE: ${PP_AUTH_MODE:-password}
PHOTOPRISM_SITE_URL: ${PP_SITE_URL:-http://localhost:2342/}
PHOTOPRISM_ORIGINALS_LIMIT: ${PP_ORIGINALS_LIMIT:-50000}
PHOTOPRISM_HTTP_COMPRESSION: gzip
PHOTOPRISM_LOG_LEVEL: ${PP_LOG_LEVEL:-info}
# M0 safety: keep originals read-only. Flip to "false" in M2 when the
# right-sidebar enables metadata edits and we want EXIF backwrite.
PHOTOPRISM_READONLY: ${PP_READONLY:-true}
PHOTOPRISM_EXPERIMENTAL: "false"
PHOTOPRISM_DISABLE_CHOWN: "true"
PHOTOPRISM_DISABLE_WEBDAV: ${PP_DISABLE_WEBDAV:-false}
PHOTOPRISM_DISABLE_SETTINGS: "false"
PHOTOPRISM_DISABLE_TLS: "true"
PHOTOPRISM_DEFAULT_TLS: "false"
# AI/vision pipeline back on — per plan we re-introduce TF labels + faces.
PHOTOPRISM_TENSORFLOW_OFF: "false"
PHOTOPRISM_DETECT_NSFW: "true"
PHOTOPRISM_UPLOAD_NSFW: "true"
# Database
PHOTOPRISM_DATABASE_DRIVER: mysql
PHOTOPRISM_DATABASE_SERVER: mariadb:3306
PHOTOPRISM_DATABASE_NAME: ${PP_DB_NAME:-photoprism}
PHOTOPRISM_DATABASE_USER: ${PP_DB_USER:-photoprism}
PHOTOPRISM_DATABASE_PASSWORD: ${PP_DB_PASSWORD}
# Sidecars next to originals — read by the migrator at M5.
PHOTOPRISM_SIDECAR_PATH: ""
PHOTOPRISM_SIDECAR_YAML: "true"
# EXIF backwrite — disabled in M0 (READONLY blocks writes anyway).
# Re-enable in M2 by overriding in .env.photoprism: PP_BACKUP_DATABASE=true.
PHOTOPRISM_DISABLE_BACKUPS: "false"
PHOTOPRISM_BACKUP_DATABASE: ${PP_BACKUP_DATABASE:-true}
PHOTOPRISM_DISABLE_EXIFTOOL: "false"
# OIDC — set in .env.photoprism when the IdP (Authentik) is wired up.
# Empty values keep OIDC dormant; the username/password login still works.
PHOTOPRISM_OIDC_PROVIDER_NAME: ${OIDC_PROVIDER_NAME:-}
PHOTOPRISM_OIDC_ISSUER_URL: ${OIDC_ISSUER_URL:-}
PHOTOPRISM_OIDC_CLIENT_ID: ${OIDC_CLIENT_ID:-}
PHOTOPRISM_OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET:-}
PHOTOPRISM_OIDC_REDIRECT_URI: ${OIDC_REDIRECT_URI:-}
PHOTOPRISM_OIDC_SCOPES: ${OIDC_SCOPES:-openid profile email}
PHOTOPRISM_OIDC_REGISTER: ${OIDC_REGISTER:-true}
PHOTOPRISM_OIDC_ROLE: ${OIDC_ROLE:-user}
working_dir: /photoprism
volumes:
# Existing photo library — mounted read-only in M0; flip to :rw in M2
# when the right-sidebar starts saving edits. ",Z" relabels for SELinux
# on Fedora/RHEL; silent no-op elsewhere.
- "${PHOTO_DIRS:?set PHOTO_DIRS in .env.photoprism}:/photoprism/originals:${PP_ORIGINALS_MODE:-ro},Z"
- "./pp/storage:/photoprism/storage:Z"
- "./pp/import:/photoprism/import:Z"
networks: [photoprism-network]
networks:
photoprism-network:
driver: bridge
volumes:
pp_mariadb_data: