# Pull-only install: one Specmate image serves app, workers and explicit migrations.
# See docs/INSTALL.md. Do not combine this file with the development compose files.
name: specmate-install

x-image: &image
  image: ${APP_IMAGE:-ghcr.io/formless63/specmate:edge}

x-environment: &environment
  PGBOSS_USE_LISTEN_NOTIFY: "${PGBOSS_USE_LISTEN_NOTIFY:-false}"
  PGBOSS_SCHEMA: "${PGBOSS_SCHEMA:-pgboss}"
  ORGANIZATIONS_INVITATION_TTL_SECONDS: "${ORGANIZATIONS_INVITATION_TTL_SECONDS:-172800}"
  ORGANIZATIONS_INVITATION_LIMIT: "${ORGANIZATIONS_INVITATION_LIMIT:-100}"
  ORGANIZATIONS_MEMBERSHIP_LIMIT: "${ORGANIZATIONS_MEMBERSHIP_LIMIT:-100}"
  ORGANIZATIONS_CREATION_LIMIT: "${ORGANIZATIONS_CREATION_LIMIT:-10}"
  STORAGE_KEY_PREFIX: "${STORAGE_KEY_PREFIX:-}"
  STORAGE_PRESIGN_TTL_SECONDS: "${STORAGE_PRESIGN_TTL_SECONDS:-600}"
  STORAGE_FORCE_PATH_STYLE: "${STORAGE_FORCE_PATH_STYLE:-}"
  STORAGE_SESSION_TOKEN: "${STORAGE_SESSION_TOKEN:-}"
  STORAGE_SECRET_ACCESS_KEY: "${STORAGE_SECRET_ACCESS_KEY:-}"
  STORAGE_ACCESS_KEY_ID: "${STORAGE_ACCESS_KEY_ID:-}"
  STORAGE_ENDPOINT: "${STORAGE_ENDPOINT:-}"
  STORAGE_REGION: "${STORAGE_REGION:-}"
  STORAGE_BUCKET: "${STORAGE_BUCKET:-}"
  IMPORT_EXPORT_ARTIFACT_TTL_SECONDS: "${IMPORT_EXPORT_ARTIFACT_TTL_SECONDS:-86400}"
  IMPORT_EXPORT_TIMEOUT_SECONDS: "${IMPORT_EXPORT_TIMEOUT_SECONDS:-60}"
  IMPORT_EXPORT_MAX_ROWS: "${IMPORT_EXPORT_MAX_ROWS:-10000}"
  IMPORT_EXPORT_MAX_BYTES: "${IMPORT_EXPORT_MAX_BYTES:-16777216}"
  WEBHOOK_REFERENCE_SECRET: "${WEBHOOK_REFERENCE_SECRET:-}"
  WEBHOOK_REFERENCE_URL: "${WEBHOOK_REFERENCE_URL:-}"
  PGBOSS_DATABASE_URL: "${PGBOSS_DATABASE_URL:-postgresql://specmate:${POSTGRES_PASSWORD}@postgres:5432/specmate}"
  DATABASE_URL: "postgresql://specmate:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}@postgres:5432/specmate"
  NTFY_REFERENCE_TOPIC: "${NTFY_REFERENCE_TOPIC:-}"
  NTFY_REFERENCE_RECIPIENT_ID: "${NTFY_REFERENCE_RECIPIENT_ID:-}"
  NTFY_TIMEOUT_SECONDS: "${NTFY_TIMEOUT_SECONDS:-10}"
  NTFY_TOKEN: "${NTFY_TOKEN:-}"
  NTFY_BASE_URL: "${NTFY_BASE_URL:-}"
  APP_BASE_URL: "${APP_BASE_URL:?Set APP_BASE_URL}"
  BETTER_AUTH_SECRET: "${BETTER_AUTH_SECRET:?Set BETTER_AUTH_SECRET}"
  NODE_ENV: "production"
  LOG_REDACT_FIELDS: "${LOG_REDACT_FIELDS:-}"
  LOG_LEVEL: "${LOG_LEVEL:-info}"
  DEPLOYMENT_ENVIRONMENT: "${DEPLOYMENT_ENVIRONMENT:-}"
  APP_REVISION: "${APP_REVISION:-}"
  APP_VERSION: "${APP_VERSION:-}"
  OTEL_RESOURCE_ATTRIBUTES: "${OTEL_RESOURCE_ATTRIBUTES:-}"
  OTEL_METRICS_EXPORTER: "${OTEL_METRICS_EXPORTER:-}"
  OTEL_TRACES_EXPORTER: "${OTEL_TRACES_EXPORTER:-}"
  OTEL_EXPORTER_OTLP_HEADERS: "${OTEL_EXPORTER_OTLP_HEADERS:-}"
  OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: "${OTEL_EXPORTER_OTLP_METRICS_ENDPOINT:-}"
  OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT:-}"
  OTEL_EXPORTER_OTLP_ENDPOINT: "${OTEL_EXPORTER_OTLP_ENDPOINT:-}"
  OTEL_SDK_DISABLED: "${OTEL_SDK_DISABLED:-true}"
  OTEL_SERVICE_NAME: "${OTEL_SERVICE_NAME:-specmate-app}"
  OPS_ADMIN_USER_IDS: "${OPS_ADMIN_USER_IDS:-}"
  REALTIME_TRANSPORTS: "${REALTIME_TRANSPORTS:-sse}"
  GITHUB_CLIENT_ID: "${GITHUB_CLIENT_ID:-}"
  GITHUB_CLIENT_SECRET: "${GITHUB_CLIENT_SECRET:-}"
  OIDC_DISCOVERY_URL: "${OIDC_DISCOVERY_URL:-}"
  OIDC_CLIENT_ID: "${OIDC_CLIENT_ID:-}"
  OIDC_CLIENT_SECRET: "${OIDC_CLIENT_SECRET:-}"
  MAGIC_LINK_ENABLED: "${MAGIC_LINK_ENABLED:-false}"
  SMTP_HOST: "${SMTP_HOST:-}"
  SMTP_PORT: "${SMTP_PORT:-}"
  SMTP_SECURITY: "${SMTP_SECURITY:-}"
  SMTP_USER: "${SMTP_USER:-}"
  SMTP_PASSWORD: "${SMTP_PASSWORD:-}"
  EMAIL_FROM_ADDRESS: "${EMAIL_FROM_ADDRESS:-}"
  EMAIL_FROM_NAME: "${EMAIL_FROM_NAME:-}"
  EMAIL_REPLY_TO_ADDRESS: "${EMAIL_REPLY_TO_ADDRESS:-}"
  EMAIL_REPLY_TO_NAME: "${EMAIL_REPLY_TO_NAME:-}"
  EMAIL_MAX_RECIPIENTS: "${EMAIL_MAX_RECIPIENTS:-50}"

services:
  postgres:
    image: postgres:18.1-alpine
    environment:
      POSTGRES_DB: specmate
      POSTGRES_USER: specmate
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}
    volumes: ["postgres-data:/var/lib/postgresql"]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U specmate -d specmate"]
      interval: 2s
      timeout: 3s
      retries: 30
    restart: unless-stopped

  migrate:
    <<: *image
    profiles: [setup]
    environment: *environment
    command: ["node", ".output/migrate.mjs"]
    depends_on:
      postgres: { condition: service_healthy }
    restart: "no"

  jobs-migrate:
    <<: *image
    profiles: [setup]
    environment: *environment
    command: ["node", ".output/jobs-migrate.mjs"]
    depends_on:
      postgres: { condition: service_healthy }
    restart: "no"

  app:
    <<: *image
    environment: *environment
    ports: ["${APP_BIND:-127.0.0.1}:${APP_PORT:-3000}:3000"]
    depends_on:
      postgres: { condition: service_healthy }
    healthcheck:
      test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/api/health').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
      interval: 5s
      timeout: 3s
      retries: 30
      start_period: 15s
    init: true
    stop_grace_period: 35s
    restart: unless-stopped

  worker:
    <<: *image
    command: ["node", ".output/jobs-worker.mjs"]
    environment: *environment
    depends_on:
      postgres: { condition: service_healthy }
    init: true
    stop_grace_period: 35s
    restart: unless-stopped

  mailpit:
    image: axllent/mailpit:v1.31.3
    profiles: [mailpit]
    ports: ["127.0.0.1:${MAILPIT_UI_PORT:-8025}:8025"]
    restart: unless-stopped

  storage:
    image: rustfs/rustfs:1.0.0
    profiles: [storage]
    environment:
      RUSTFS_ACCESS_KEY: ${STORAGE_ACCESS_KEY_ID:-}
      RUSTFS_SECRET_KEY: ${STORAGE_SECRET_ACCESS_KEY:-}
      RUSTFS_CONSOLE_ENABLE: "false"
      RUSTFS_OBS_LOGGER_LEVEL: warn
    # Loopback only; storage.localhost resolves to this service internally and to
    # localhost in the browser. Tunnel port 9000 together with the web app.
    ports: ["127.0.0.1:${STORAGE_PORT:-9000}:9000"]
    networks:
      default:
        aliases: [storage.localhost]
    volumes: ["storage-data:/data", "storage-logs:/logs"]
    restart: unless-stopped

volumes:
  postgres-data:
  storage-data:
  storage-logs:
