# PhotoPrism stack — replaces the legacy mule-image backend over the course of
# milestones M0–M5 (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}
    # Loopback-only host port so the mule-sidecar (running as a host process
    # in M4) can reach `mule_sidecar.*` over TCP. Not exposed beyond
    # 127.0.0.1; the photoprism container still resolves mariadb by service
    # name on the photoprism-network bridge.
    ports:
      - "127.0.0.1:${PP_DB_PORT:-3306}:3306"
    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}
      # Indexer concurrency. Defaults to NumCPU/2 (= 3 on a 6-core LXC),
      # but each worker forks TF + ffmpeg + libvips so effective load is
      # much higher — a fresh index of 1.2k photos on M0 pushed the LXC
      # load to 50+ and starved sibling containers. Pin to a low value
      # for shared hosts; raise on dedicated machines.
      PHOTOPRISM_WORKERS: ${PP_WORKERS:-2}
      PHOTOPRISM_INDEX_WORKERS: ${PP_INDEX_WORKERS:-${PP_WORKERS:-2}}
      # 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's CLI flags are --oidc-uri / --oidc-client / --oidc-secret
      # / --oidc-provider, so the env-var names it actually reads are
      # PHOTOPRISM_OIDC_URI / _CLIENT / _SECRET / _PROVIDER (NOT _ISSUER_URL
      # / _CLIENT_ID / _CLIENT_SECRET / _PROVIDER_NAME — those are silently
      # ignored, OIDC stays dormant, and `photoprism show config` reports
      # blank oidc-uri / oidc-client). PHOTOPRISM_OIDC_REDIRECT is a bool
      # (auto-redirect-from-/library/login), not a URL — PhotoPrism builds
      # the callback from PHOTOPRISM_SITE_URL.
      PHOTOPRISM_OIDC_PROVIDER: ${OIDC_PROVIDER_NAME:-${OIDC_PROVIDER:-}}
      PHOTOPRISM_OIDC_URI: ${OIDC_ISSUER_URL:-${OIDC_URI:-}}
      PHOTOPRISM_OIDC_CLIENT: ${OIDC_CLIENT_ID:-${OIDC_CLIENT:-}}
      PHOTOPRISM_OIDC_SECRET: ${OIDC_CLIENT_SECRET:-${OIDC_SECRET:-}}
      PHOTOPRISM_OIDC_SCOPES: ${OIDC_SCOPES:-openid profile email}
      PHOTOPRISM_OIDC_REGISTER: ${OIDC_REGISTER:-true}
      PHOTOPRISM_OIDC_ROLE: ${OIDC_ROLE:-user}
      PHOTOPRISM_OIDC_REDIRECT: ${OIDC_REDIRECT:-false}
    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]

  # mule-sidecar — Go + Gin + GORM service for endpoints PhotoPrism's API
  # does not expose (file rename, folder mutations, heap convert, duplicate
  # scan, per-photo marks). Same wire contract as the M3 Node prototype;
  # the SvelteKit dev server proxies /api/sidecar/* here.
  sidecar:
    build:
      context: ./sidecar
    container_name: pp-sidecar
    restart: unless-stopped
    depends_on:
      mariadb:
        condition: service_healthy
      photoprism:
        condition: service_started
    # Match PhotoPrism's UID/GID so renames/folder mutations preserve the
    # ownership the indexer expects on the bind-mounted originals.
    user: "${PP_UID:-1000}:${PP_GID:-1000}"
    ports:
      # Loopback only — Vite (host) proxies /api/sidecar/* to this port.
      # Behind a reverse proxy in production; never published beyond the
      # host.
      - "127.0.0.1:${SIDECAR_PORT:-8000}:8000"
    environment:
      ORIGINALS_ROOT: /photoprism/originals
      PHOTOPRISM_BASE_URL: http://photoprism:2342
      # Bind on all interfaces inside the container so the host-side
      # 127.0.0.1:8000 port mapping can reach the listener. The Go
      # binary defaults to 127.0.0.1 for the host-mode dev loop.
      SIDECAR_LISTEN_ADDR: 0.0.0.0
      SIDECAR_PORT: "8000"
      SIDECAR_DB_HOST: mariadb
      SIDECAR_DB_PORT: "3306"
      SIDECAR_DB_USER: sidecar
      # Rotate before any non-local deployment. Provisioned by
      # mariadb/init/01-sidecar.sql on first boot of the mariadb volume.
      SIDECAR_DB_PASSWORD: ${SIDECAR_DB_PASSWORD:-replace-at-m4-bringup}
      SIDECAR_DB_NAME: mule_sidecar
    volumes:
      # Sidecar mutates originals (rename, folder mutations, heap
      # convert) — always rw regardless of PhotoPrism's mount mode.
      - "${PHOTO_DIRS:?set PHOTO_DIRS in .env.photoprism}:/photoprism/originals:rw,Z"
    networks: [photoprism-network]

networks:
  photoprism-network:
    driver: bridge

volumes:
  pp_mariadb_data:
