# Paperless-ngx service stack (PIHA) — UI + API + Postgres + Redis broker.
#
# KB module 2 (kb/phases/kb-m2-paperless.md). Heavy OCR runs on a
# SEPARATE worker on SOLARIA (services/paperless-worker/, module 3) that shares
# this stack's Redis broker, Postgres and document storage (NFS export from
# PIHA). The built-in celery worker here stays at concurrency 1 as the slow
# fallback when SOLARIA is asleep — the stock image cannot disable it anyway.
#
# EXPOSURE: LAN/Tailscale ONLY (documents are sensitive — kb-00 rule #4).
# Every published port binds to LAN_BIND_IP (192.168.31.5), never 0.0.0.0.
# Users enter through npm@PIHA (paper.kapala.org vhost); the worker on SOLARIA
# reaches Redis/Postgres over the same fast LAN, NOT Tailscale.
services:
  paperless:
    # Multi-arch manifest includes linux/arm64 — runs natively on the Pi 5.
    # The paperless-worker on SOLARIA MUST run the exact same tag (shared DB
    # schema + task signatures); bump both together.
    image: ghcr.io/paperless-ngx/paperless-ngx:2.14
    container_name: paperless
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      broker:
        condition: service_healthy
    # Same trick as vikunja: pin the Forgejo OIDC issuer to npm on PIHA so
    # discovery resolves over the LAN instead of flaky public DNS.
    extra_hosts:
      - "forgejo.kapala.org:192.168.31.5"
    # Secrets via env_file only (vikunja pattern): PAPERLESS_SECRET_KEY,
    # PAPERLESS_DBPASS, PAPERLESS_SOCIALACCOUNT_PROVIDERS (holds the OIDC
    # client secret, hence the whole JSON lives in .env).
    env_file:
      - .env
    environment:
      # Domain confirmed: paper.kapala.org (mesh-only, *.kapala.org wildcard
      # cert already covers it — no new cert needed). DNS + npm vhost are
      # deploy-time steps, see README Cutover checklist.
      - PAPERLESS_URL=https://paper.kapala.org
      - PAPERLESS_TIME_ZONE=Europe/Warsaw
      # Password-protected broker (requirepass) — see PAPERLESS_REDIS_PASSWORD
      # in .env. MUST match the broker's --requirepass value below.
      - PAPERLESS_REDIS=redis://:${PAPERLESS_REDIS_PASSWORD}@broker:6379
      - PAPERLESS_DBHOST=db
      - PAPERLESS_DBNAME=paperless
      - PAPERLESS_DBUSER=paperless
      # OCR: Polish + English. `pol` is downloaded into the container at boot.
      - PAPERLESS_OCR_LANGUAGES=pol
      - PAPERLESS_OCR_LANGUAGE=pol+eng
      # Fallback-only OCR on the Pi: single worker, single thread. The real
      # OCR muscle is paperless-worker@SOLARIA pulling from the same queue.
      - PAPERLESS_TASK_WORKERS=1
      - PAPERLESS_THREADS_PER_WORKER=1
      - PAPERLESS_WEBSERVER_WORKERS=1
      # Files on disk (media/, data/, consume/) are owned by this numeric UID.
      # MUST equal USERMAP_UID/GID of paperless-worker@SOLARIA — the NFS export
      # carries numeric IDs, not names. See kb/services/paperless-worker.md.
      - USERMAP_UID=1000
      - USERMAP_GID=1000
      # OIDC via Forgejo (django-allauth openid_connect). Provider JSON with
      # client_id/secret comes from .env: PAPERLESS_SOCIALACCOUNT_PROVIDERS.
      - PAPERLESS_APPS=allauth.socialaccount.providers.openid_connect
      # First Forgejo login auto-creates the matching Paperless account.
      - PAPERLESS_SOCIALACCOUNT_ALLOW_SIGNUPS=true
      # No self-service local signups; local admin login stays for bootstrap.
      # TODO AT DEPLOY: after OIDC login is verified working, set
      # PAPERLESS_DISABLE_REGULAR_LOGIN=true + PAPERLESS_REDIRECT_LOGIN_TO_SSO=true
      # (decision already made — Vikunja runs both modes in parallel today too).
      - PAPERLESS_ACCOUNT_ALLOW_SIGNUPS=false
    volumes:
      # Bind mounts under /opt/homelab/data (runtime path convention). These
      # exact directories are NFS-exported to SOLARIA for the OCR worker —
      # container paths (/usr/src/paperless/...) MUST be identical on both
      # hosts because the DB and task payloads carry absolute paths.
      - /opt/homelab/data/paperless/data:/usr/src/paperless/data
      - /opt/homelab/data/paperless/media:/usr/src/paperless/media
      - /opt/homelab/data/paperless/consume:/usr/src/paperless/consume
      - /opt/homelab/data/paperless/export:/usr/src/paperless/export
      # SCRATCH_DIR — where paperless stages an uploaded file before
      # consuming it (default /tmp/paperless; see paperless-ngx
      # src/paperless/settings.py, tempfile.gettempdir()/"paperless"). The
      # celery task payload carries this staged file's path as an ABSOLUTE
      # path. Since the built-in worker here and the worker@SOLARIA share one
      # task queue, whichever one picks up the task must be able to open that
      # exact path — so, same rule as data/media/consume above, this MUST be
      # NFS-exported and mounted at the identical container path on both
      # hosts. Was missed when module 3 was first wired up: documents
      # consumed on PIHA but OCR'd by the SOLARIA worker failed with "Cannot
      # consume ...: File not found" because SOLARIA had its own local,
      # empty /tmp/paperless.
      - /opt/homelab/data/paperless/scratch:/tmp/paperless
    ports:
      # LAN-only bind; npm@PIHA proxies paper.kapala.org -> 192.168.31.5:8210.
      # TODO AT DEPLOY: port 8210 free per the 2026-06-30 inventory —
      # reconfirm on the live host: ss -tlnp | grep 8210
      - "${LAN_BIND_IP}:8210:8000"
    healthcheck:
      test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s

  db:
    image: postgres:16-alpine
    container_name: paperless-db
    restart: unless-stopped
    env_file:
      - .env
    environment:
      - POSTGRES_DB=paperless
      - POSTGRES_USER=paperless
      - TZ=Europe/Warsaw
    volumes:
      - paperless_pgdata:/var/lib/postgresql/data
    ports:
      # Published on the LAN for paperless-worker@SOLARIA (192.168.31.70).
      # 5433 is taken by kb-postgres. Postgres itself still enforces
      # scram-sha-256 auth — the bind is exposure control, not the only lock.
      - "${LAN_BIND_IP}:5434:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U paperless -d paperless"]
      interval: 10s
      timeout: 5s
      retries: 5

  broker:
    image: redis:7-alpine
    container_name: paperless-broker
    restart: unless-stopped
    # AOF persistence is REQUIRED: when SOLARIA is offline, queued OCR tasks
    # (up to the 70k-attachment backlog) live only in this Redis. Without AOF a
    # broker restart would silently drop the queue — violating the "zero lost
    # tasks" criterion of module 3. Do NOT set maxmemory/eviction here.
    # requirepass: broker is bound to the LAN interface (trusted home network,
    # but still reachable by anything on that LAN) — password auth decided
    # over relying on the bind alone. Same value MUST be set as
    # PAPERLESS_REDIS_PASSWORD in .env on both PIHA (this file) and
    # paperless-worker@SOLARIA.
    command: redis-server --appendonly yes --requirepass ${PAPERLESS_REDIS_PASSWORD}
    volumes:
      - paperless_redisdata:/data
    ports:
      # LAN-only bind for paperless-worker@SOLARIA. 6379 is taken by
      # agent-system-redis.
      # TODO AT DEPLOY: port 6380 free per the 2026-06-30 inventory —
      # reconfirm on the live host: ss -tlnp | grep 6380
      - "${LAN_BIND_IP}:6380:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${PAPERLESS_REDIS_PASSWORD}", "--no-auth-warning", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  # DB + broker state are host-local (NOT part of the NFS export). Document
  # storage above is bind-mounted, not a named volume, because it doubles as
  # the NFS export root for the SOLARIA worker.
  paperless_pgdata:
  paperless_redisdata:
