name: drivervault

# Production DriverVault stack — pulls prebuilt images from the registry instead
# of building from source. Everything an operator needs to set lives in .env.
#
#   1. cp .env.prod.example .env   (then edit it — all secrets/ports/volumes)
#   2. docker compose -f docker-compose.prod.yml pull
#   3. docker compose -f docker-compose.prod.yml up -d
#
# Traffic flow (browser): Web App BFF --/api--> API Server --> PocketBase.
#
# On first boot:
#   • PocketBase upserts the superuser from PB_ADMIN_* (create-if-missing).
#   • the API Server creates any missing collections, reconciles existing ones,
#     and creates the DriverVault super-admin from DRIVERVAULT_SUPERADMIN_*.
# Both steps are idempotent, so restarts and upgrades are safe.

services:
  pocketbase:
    image: "${PB_IMAGE:-10.2.1.10:5500/admin/drivervault-pocketbase:latest}"
    container_name: drivervault-pocketbase
    restart: unless-stopped
    environment:
      # The superuser is created/updated on boot (the API Server authenticates
      # with it). This is the only place the first superuser can be created — the
      # REST API cannot bootstrap it.
      PB_ADMIN_EMAIL: "${PB_ADMIN_EMAIL:?set PB_ADMIN_EMAIL in .env}"
      PB_ADMIN_PASSWORD: "${PB_ADMIN_PASSWORD:?set PB_ADMIN_PASSWORD in .env}"
    volumes:
      # Named volume by default; set PB_DATA to a host path in .env for a bind mount.
      - "${PB_DATA:-pb_data}:/pb/pb_data"
    ports:
      # Bound to localhost by default — the admin UI (/_/) is reachable only on
      # the host. Set PB_BIND=0.0.0.0 in .env to expose it on the network.
      - "${PB_BIND:-127.0.0.1}:${PB_PORT:-8070}:8070"
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8070/api/health || exit 1"]
      interval: 10s
      timeout: 3s
      retries: 12
      start_period: 10s

  api-server:
    image: "${API_IMAGE:-10.2.1.10:5500/admin/drivervault-api-server:latest}"
    container_name: drivervault-api
    restart: unless-stopped
    depends_on:
      pocketbase:
        condition: service_healthy
    environment:
      API_ADDR: ":8080"
      # Reach PocketBase by its service name on the internal network. Override
      # POCKETBASE_URL in .env to point the API Server at a database outside
      # this stack — that is also how you make a retarget done from the panel
      # permanent, since the panel's change lasts only for the container's life.
      POCKETBASE_URL: "${POCKETBASE_URL:-http://pocketbase:8070}"
      POCKETBASE_ADMIN_EMAIL: "${PB_ADMIN_EMAIL}"
      POCKETBASE_ADMIN_PASSWORD: "${PB_ADMIN_PASSWORD}"
      # Probed by the panel status page. This is a server-to-server call inside
      # the compose network, so the default is the service name — plain
      # localhost:8090 would resolve to this container itself. Override
      # WEBAPP_URL in .env to make a change from the panel's Web App screen
      # permanent; the panel alone only holds it for the container's life.
      WEBAPP_URL: "${WEBAPP_URL:-http://web-app:8090}"
      CORS_ALLOW_ORIGINS: "${CORS_ALLOW_ORIGINS:-http://localhost:8090}"
      AUTH_USERS_COLLECTION: "${AUTH_USERS_COLLECTION:-users}"
      # Schema + super-admin bootstrap (idempotent). Leave this ON: a release can
      # add collections or fields the server needs, and a stack that skips the
      # bootstrap never gets them. The API Server self-heals exactly one thing —
      # app_settings, the collection holding the plugin settings, which it
      # creates on demand because it cannot serve the plugin panel without it.
      # Every other schema change still depends on this flag. Turn it off only
      # for a database you know already matches the release.
      PB_BOOTSTRAP: "${PB_BOOTSTRAP:-true}"
      DRIVERVAULT_SUPERADMIN_EMAIL: "${DRIVERVAULT_SUPERADMIN_EMAIL:-}"
      DRIVERVAULT_SUPERADMIN_PASSWORD: "${DRIVERVAULT_SUPERADMIN_PASSWORD:-}"
      DRIVERVAULT_SUPERADMIN_NAME: "${DRIVERVAULT_SUPERADMIN_NAME:-Administrator}"
      # OCPP charger control (Anker Solix). Chargers are rejected unless they
      # reach the server over TLS. Behind a TLS-terminating reverse proxy, set
      # OCPP_PUBLIC_URL to the public wss:// base and API_BIND so the proxy can
      # reach this port; only drop OCPP_REQUIRE_TLS on a trusted network.
      OCPP_REQUIRE_TLS: "${OCPP_REQUIRE_TLS:-true}"
      OCPP_PUBLIC_URL: "${OCPP_PUBLIC_URL:-}"
    ports:
      # Localhost-only by default (the Web App reaches it over the internal
      # network). Set API_BIND=0.0.0.0 to expose the API panel — and the
      # /ocpp/{serial} endpoint chargers dial into — on the network.
      - "${API_BIND:-127.0.0.1}:${API_PORT:-8080}:8080"
    # No volume: the API Server keeps no state on disk — every setting it owns,
    # plugin settings included, lives in PocketBase under PB_DATA.
    healthcheck:
      # Declared here rather than relying only on the image's HEALTHCHECK, so the
      # depends_on gate below still works against an older pulled image.
      test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/healthz || exit 1"]
      interval: 10s
      timeout: 3s
      retries: 12
      start_period: 20s

  web-app:
    image: "${WEB_IMAGE:-10.2.1.10:5500/admin/drivervault-web-app:latest}"
    container_name: drivervault-web
    restart: unless-stopped
    depends_on:
      # The image now ships a HEALTHCHECK, so wait for the API Server to be
      # serving rather than merely started.
      api-server:
        condition: service_healthy
    environment:
      # The BFF reverse-proxies /api/* — and /ocpp/*, the address chargers are
      # told to dial — to the API Server over the internal network.
      API_BASE: "http://api-server:8080"
      # Believe an inbound X-Forwarded-Proto. The API Server reads it to decide a
      # charger arrived over TLS, so leave this off unless a TLS-terminating
      # proxy in front of the stack is the only way in: otherwise a charger could
      # claim wss over a plaintext connection. Set it to true when TLS ends at
      # that proxy and OCPP_PUBLIC_URL names a wss:// base through it.
      TRUST_FORWARDED_PROTO: "${TRUST_FORWARDED_PROTO:-false}"
    ports:
      # The public front door. Bound on all interfaces so browsers can reach it.
      - "${WEB_PORT:-8090}:8090"
    healthcheck:
      test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8090/healthz || exit 1"]
      interval: 10s
      timeout: 3s
      retries: 12
      start_period: 10s

volumes:
  pb_data:
