# Disclosure Bureau — full deployment stack.
# Routed via the host's existing plegal-traefik (network: traefik-public).
# Internal services share the disclosure-internal network and are NOT exposed
# to the host. Public services (web, kong, studio, search) get Traefik labels.

name: disclosure

networks:
  internal:
    name: disclosure-internal
    driver: bridge
  traefik:
    name: traefik-public
    external: true

volumes:
  db-data:
  storage-data:
  meili-data:
  hf-cache:
  glitchtip-redis-data:
  glitchtip-uploads:
  forgejo-data:
  forgejo-runner-config:

services:
  # ─── Database ─────────────────────────────────────────────────────────────
  db:
    container_name: disclosure-db
    image: supabase/postgres:15.8.1.060
    restart: unless-stopped
    networks: [internal]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -h localhost"]
      interval: 10s
      timeout: 5s
      retries: 12
    environment:
      POSTGRES_HOST: /var/run/postgresql
      POSTGRES_PORT: 5432
      POSTGRES_DB: postgres
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      PGPASSWORD: ${POSTGRES_PASSWORD}
      JWT_SECRET: ${JWT_SECRET}
      JWT_EXP: 3600
      POSTGRES_INITDB_ARGS: "--data-checksums"
    command:
      - postgres
      - -c
      - shared_buffers=${POSTGRES_SHARED_BUFFERS:-384MB}
      - -c
      - work_mem=${POSTGRES_WORK_MEM:-12MB}
      - -c
      - maintenance_work_mem=${POSTGRES_MAINTENANCE_WORK_MEM:-96MB}
      - -c
      - max_connections=${POSTGRES_MAX_CONNECTIONS:-80}
    volumes:
      - db-data:/var/lib/postgresql/data

  # ─── Auth (GoTrue) ────────────────────────────────────────────────────────
  auth:
    container_name: disclosure-auth
    image: supabase/gotrue:v2.170.0
    restart: unless-stopped
    networks: [internal]
    depends_on:
      db: { condition: service_healthy }
    environment:
      GOTRUE_API_HOST: 0.0.0.0
      GOTRUE_API_PORT: 9999
      API_EXTERNAL_URL: https://${DOMAIN_API}
      GOTRUE_DB_DRIVER: postgres
      GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@db:5432/postgres?search_path=auth
      GOTRUE_SITE_URL: https://${DOMAIN_MAIN}
      # Explicit external URL so confirmation links land on the public site
      # (Next.js /auth/callback), not on the Kong gateway host.
      GOTRUE_API_EXTERNAL_URL: https://${DOMAIN_MAIN}
      GOTRUE_URI_ALLOW_LIST: https://${DOMAIN_MAIN},https://www.${DOMAIN_MAIN}
      GOTRUE_DISABLE_SIGNUP: "false"
      GOTRUE_JWT_ADMIN_ROLES: service_role
      GOTRUE_JWT_AUD: authenticated
      GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
      GOTRUE_JWT_EXP: 3600
      GOTRUE_JWT_SECRET: ${JWT_SECRET}
      GOTRUE_EXTERNAL_EMAIL_ENABLED: "true"
      # SMTP configured (Spacemail) → email confirmation required for signups.
      GOTRUE_MAILER_AUTOCONFIRM: "false"
      GOTRUE_MAILER_OTP_EXP: 3600
      GOTRUE_SMTP_HOST: ${SMTP_HOST}
      GOTRUE_SMTP_PORT: ${SMTP_PORT}
      GOTRUE_SMTP_USER: ${SMTP_USER}
      GOTRUE_SMTP_PASS: ${SMTP_PASS}
      GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_FROM}
      GOTRUE_SMTP_SENDER_NAME: ${SMTP_FROM_NAME}
      GOTRUE_MAILER_URLPATHS_INVITE: /auth/callback
      GOTRUE_MAILER_URLPATHS_CONFIRMATION: /auth/callback
      GOTRUE_MAILER_URLPATHS_RECOVERY: /auth/callback
      GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: /auth/callback

  # ─── PostgREST ────────────────────────────────────────────────────────────
  rest:
    container_name: disclosure-rest
    image: postgrest/postgrest:v12.2.8
    restart: unless-stopped
    networks: [internal]
    depends_on:
      db: { condition: service_healthy }
    environment:
      PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD}@db:5432/postgres
      PGRST_DB_SCHEMAS: public,storage
      PGRST_DB_ANON_ROLE: anon
      PGRST_JWT_SECRET: ${JWT_SECRET}
      PGRST_DB_USE_LEGACY_GUCS: "false"
      PGRST_APP_SETTINGS_JWT_SECRET: ${JWT_SECRET}
      PGRST_APP_SETTINGS_JWT_EXP: 3600

  # ─── Realtime ─────────────────────────────────────────────────────────────
  realtime:
    container_name: disclosure-realtime
    image: supabase/realtime:v2.30.34
    restart: unless-stopped
    networks: [internal]
    depends_on:
      db: { condition: service_healthy }
    environment:
      PORT: 4000
      DB_HOST: db
      DB_PORT: 5432
      DB_USER: supabase_admin
      DB_PASSWORD: ${POSTGRES_PASSWORD}
      DB_NAME: postgres
      DB_ENC_KEY: ${VAULT_ENC_KEY}
      API_JWT_SECRET: ${JWT_SECRET}
      SECRET_KEY_BASE: ${SECRET_KEY_BASE}
      ERL_AFLAGS: -proto_dist inet_tcp
      DNS_NODES: "''"
      RLIMIT_NOFILE: "10000"
      APP_NAME: realtime
      SEED_SELF_HOST: "true"
      RUN_JANITOR: "true"

  # ─── Storage ──────────────────────────────────────────────────────────────
  storage:
    container_name: disclosure-storage
    image: supabase/storage-api:v1.14.3
    restart: unless-stopped
    networks: [internal]
    depends_on:
      db: { condition: service_healthy }
      rest: { condition: service_started }
      imgproxy: { condition: service_started }
    environment:
      ANON_KEY: ${ANON_KEY}
      SERVICE_KEY: ${SERVICE_ROLE_KEY}
      POSTGREST_URL: http://rest:3000
      PGRST_JWT_SECRET: ${JWT_SECRET}
      DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD}@db:5432/postgres
      FILE_SIZE_LIMIT: 52428800
      STORAGE_BACKEND: file
      FILE_STORAGE_BACKEND_PATH: /var/lib/storage
      TENANT_ID: stub
      REGION: stub
      GLOBAL_S3_BUCKET: stub
      ENABLE_IMAGE_TRANSFORMATION: "true"
      IMGPROXY_URL: http://imgproxy:5001
    volumes:
      - storage-data:/var/lib/storage

  imgproxy:
    container_name: disclosure-imgproxy
    image: darthsim/imgproxy:v3.8.0
    restart: unless-stopped
    networks: [internal]
    environment:
      IMGPROXY_BIND: ":5001"
      # W0-F2: tighten filesystem root from "/" (whole VPS) to the Storage
      # backend mount only. Imgproxy never reads outside Storage objects.
      IMGPROXY_LOCAL_FILESYSTEM_ROOT: /var/lib/storage
      IMGPROXY_USE_ETAG: "true"
      IMGPROXY_ENABLE_WEBP_DETECTION: "true"
    volumes:
      - storage-data:/var/lib/storage

  # ─── pg-meta + Studio ─────────────────────────────────────────────────────
  meta:
    container_name: disclosure-meta
    image: supabase/postgres-meta:v0.83.2
    restart: unless-stopped
    networks: [internal]
    depends_on:
      db: { condition: service_healthy }
    environment:
      PG_META_PORT: 8080
      PG_META_DB_HOST: db
      PG_META_DB_PORT: 5432
      PG_META_DB_NAME: postgres
      PG_META_DB_USER: supabase_admin
      PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD}

  studio:
    container_name: disclosure-studio
    image: supabase/studio:20241202-71e5240
    restart: unless-stopped
    networks: [internal, traefik]
    depends_on:
      meta: { condition: service_started }
    environment:
      # W1: Next.js standalone server binds to the container hostname by
      # default, leaving 127.0.0.1 unreachable — the Docker healthcheck
      # (fetch 127.0.0.1:3000/api/profile) then loops on ECONNREFUSED and
      # the service never goes healthy. HOSTNAME=0.0.0.0 forces it to bind
      # on all interfaces so both the loopback and the docker IP respond.
      HOSTNAME: 0.0.0.0
      STUDIO_PG_META_URL: http://meta:8080
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      DEFAULT_ORGANIZATION_NAME: "Disclosure Bureau"
      DEFAULT_PROJECT_NAME: "disclosure"
      SUPABASE_URL: http://kong:8000
      SUPABASE_PUBLIC_URL: https://${DOMAIN_API}
      SUPABASE_ANON_KEY: ${ANON_KEY}
      SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
      AUTH_JWT_SECRET: ${JWT_SECRET}
      DASHBOARD_USERNAME: ${DASHBOARD_USERNAME}
      DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD}
    labels:
      - traefik.enable=true
      - traefik.docker.network=traefik-public
      - traefik.http.routers.disclosure-studio.rule=Host(`${DOMAIN_STUDIO}`)
      - traefik.http.routers.disclosure-studio.entrypoints=websecure
      - traefik.http.routers.disclosure-studio.tls=true
      - traefik.http.routers.disclosure-studio.tls.certresolver=letsencrypt
      - traefik.http.services.disclosure-studio.loadbalancer.server.port=3000
      # W0-F3: real basic auth (was effectively disabled with usersfile=/dev/null).
      # The user/password is DASHBOARD_USERNAME / DASHBOARD_PASSWORD from .env;
      # the bcrypt hash below was generated with $$ doubled for compose escaping.
      # Rotate by regenerating: htpasswd -nbB <user> <pass> (then double every $).
      - traefik.http.middlewares.disclosure-studio-auth.basicauth.users=admin:$$2b$$05$$tFLAMGNWX7xDbVyQ/O0G1.ruLwm3Le1.ErgdUTB9IYeJeH2FHd4ha
      - traefik.http.routers.disclosure-studio.middlewares=disclosure-studio-auth@docker

  # ─── Kong API gateway ─────────────────────────────────────────────────────
  kong:
    container_name: disclosure-kong
    image: kong:2.8.1
    restart: unless-stopped
    networks: [internal, traefik]
    depends_on:
      auth: { condition: service_started }
      rest: { condition: service_started }
      realtime: { condition: service_started }
      storage: { condition: service_started }
    environment:
      KONG_DATABASE: "off"
      # Read rendered config (envsubst happens at startup, see entrypoint below)
      KONG_DECLARATIVE_CONFIG: /tmp/kong.yml
      KONG_DNS_ORDER: LAST,A,CNAME
      KONG_PLUGINS: request-transformer,cors,key-auth,acl,basic-auth
      KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k
      KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k
      SUPABASE_ANON_KEY: ${ANON_KEY}
      SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
      DASHBOARD_USERNAME: ${DASHBOARD_USERNAME}
      DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD}
    # Kong declarative config does NOT do env substitution by itself. We
    # render the template into /tmp/kong.yml at container start so the JWT
    # keys land literally in the config Kong actually loads.
    user: root
    entrypoint:
      - /bin/sh
      - -c
      - |
        apk add --no-cache gettext >/dev/null 2>&1 || true
        envsubst < /usr/local/kong/kong.yml.tmpl > /tmp/kong.yml
        chown kong:kong /tmp/kong.yml
        exec /docker-entrypoint.sh kong docker-start
    volumes:
      - ./kong.yml:/usr/local/kong/kong.yml.tmpl:ro
    labels:
      - traefik.enable=true
      - traefik.docker.network=traefik-public
      - traefik.http.routers.disclosure-api.rule=Host(`${DOMAIN_API}`)
      - traefik.http.routers.disclosure-api.entrypoints=websecure
      - traefik.http.routers.disclosure-api.tls=true
      - traefik.http.routers.disclosure-api.tls.certresolver=letsencrypt
      - traefik.http.services.disclosure-api.loadbalancer.server.port=8000

  # ─── Meilisearch ──────────────────────────────────────────────────────────
  meilisearch:
    container_name: disclosure-meili
    image: getmeili/meilisearch:v1.10
    restart: unless-stopped
    networks: [internal, traefik]
    environment:
      MEILI_MASTER_KEY: ${MEILI_MASTER_KEY}
      MEILI_NO_ANALYTICS: "true"
      MEILI_ENV: production
      MEILI_MAX_INDEXING_MEMORY: ${MEILI_MAX_INDEXING_MEMORY:-512MB}
    volumes:
      - meili-data:/meili_data
    labels:
      - traefik.enable=true
      - traefik.docker.network=traefik-public
      - traefik.http.routers.disclosure-search.rule=Host(`${DOMAIN_SEARCH}`)
      - traefik.http.routers.disclosure-search.entrypoints=websecure
      - traefik.http.routers.disclosure-search.tls=true
      - traefik.http.routers.disclosure-search.tls.certresolver=letsencrypt
      - traefik.http.services.disclosure-search.loadbalancer.server.port=7700

  # ─── Next.js web (Disclosure Bureau frontend) ─────────────────────────────
  web:
    container_name: disclosure-web
    build:
      context: /data/disclosure/web      # rsynced from laptop, see scripts/sync-data.sh
      dockerfile: Dockerfile
      args:
        NEXT_PUBLIC_SUPABASE_URL: https://${DOMAIN_API}
        NEXT_PUBLIC_SUPABASE_ANON_KEY: ${ANON_KEY}
        NEXT_PUBLIC_SITE_URL: https://${DOMAIN_MAIN}
    restart: unless-stopped
    networks: [internal, traefik]
    depends_on:
      kong: { condition: service_started }
    environment:
      NODE_ENV: production
      NODE_OPTIONS: ${NEXT_NODE_OPTIONS:---max-old-space-size=768}
      NEXT_PUBLIC_SUPABASE_URL: https://${DOMAIN_API}
      NEXT_PUBLIC_SUPABASE_ANON_KEY: ${ANON_KEY}
      SUPABASE_SERVICE_ROLE_KEY: ${SERVICE_ROLE_KEY}
      NEXT_PUBLIC_SITE_URL: https://${DOMAIN_MAIN}
      UFO_ROOT: /data/ufo
      # W1-TD#10: bump pg pool from default 5 to 20 (chat agent + hybrid_search
      # can saturate the smaller pool under concurrent load).
      PG_POOL_MAX: ${PG_POOL_MAX:-20}
      # Chat agent. Single source of truth — `CLAUDE_CODE_OAUTH_TOKEN` is the
      # only OAuth var in the stack. The investigator-runtime reads the same
      # one. When CHAT_PROVIDER=openrouter (default) the web container ignores
      # this var at runtime, so no harm in exposing it.
      CLAUDE_CODE_OAUTH_TOKEN: ${CLAUDE_CODE_OAUTH_TOKEN}
      CLAUDE_CODE_MODEL: ${CLAUDE_CODE_MODEL}
      OPENROUTER_API_KEY: ${OPENROUTER_API_KEY}
      OPENROUTER_MODEL: ${OPENROUTER_MODEL}
      OPENROUTER_FALLBACK_MODEL: ${OPENROUTER_FALLBACK_MODEL}
      CHAT_PROVIDER: ${CHAT_PROVIDER}
      # Meilisearch (used by /api/search)
      MEILISEARCH_URL: http://meilisearch:7700
      MEILISEARCH_API_KEY: ${MEILI_MASTER_KEY}
      # Embed service (used by /lib/retrieval)
      EMBED_SERVICE_URL: http://embed:8000
      # pgvector + chunks (hybrid_search)
      DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db:5432/postgres
      # W1.2 — Glitchtip error monitoring (DSN issued by manage.py bootstrap)
      SENTRY_DSN: ${GLITCHTIP_WEB_DSN}
      NEXT_PUBLIC_SENTRY_DSN: ${GLITCHTIP_WEB_DSN}
    volumes:
      - ${DATA_WIKI}:/data/ufo/wiki:ro
      - ${DATA_PROCESSING}:/data/ufo/processing:ro
      - ${DATA_RAW}:/data/ufo/raw:ro
    labels:
      - traefik.enable=true
      - traefik.docker.network=traefik-public
      - traefik.http.routers.disclosure-web.rule=Host(`app.${DOMAIN_MAIN}`) || Host(`${DOMAIN_MAIN}`) || Host(`www.${DOMAIN_MAIN}`)
      - traefik.http.routers.disclosure-web.entrypoints=websecure
      - traefik.http.routers.disclosure-web.tls=true
      - traefik.http.routers.disclosure-web.tls.certresolver=letsencrypt
      - traefik.http.services.disclosure-web.loadbalancer.server.port=3000
      # www → apex redirect
      - traefik.http.middlewares.disclosure-www-redir.redirectregex.regex=^https?://www\.${DOMAIN_MAIN}/(.*)
      - traefik.http.middlewares.disclosure-www-redir.redirectregex.replacement=https://${DOMAIN_MAIN}/$${1}
      - traefik.http.middlewares.disclosure-www-redir.redirectregex.permanent=true

  # ─── Investigation Bureau runtime — W3.1+ ─────────────────────────────────
  #
  # 8 detectives + chief-detective orchestrator. Listens on Postgres
  # LISTEN/NOTIFY (channel `investigation_jobs`), spawns `claude -p`
  # subprocesses (Sonnet via OAuth Max 20x) to produce evidence, hypotheses,
  # contradictions, etc. Writes go through gated tools that validate schema
  # + chunk references before INSERT.
  #
  # Connects with the LOW-PRIVILEGE `investigator` role (not service_role).
  # Mounts case/ as RW and wiki/ as RO. Reads its OAuth token from env.
  investigator:
    container_name: disclosure-investigator
    build:
      context: /data/disclosure/investigator-runtime   # synced from laptop, mirrors web/ pattern
      dockerfile: Dockerfile
    restart: unless-stopped
    networks: [internal]
    depends_on:
      db: { condition: service_healthy }
      embed: { condition: service_healthy }
    environment:
      DATABASE_URL: postgres://investigator:${INVESTIGATOR_DB_PASSWORD}@db:5432/postgres
      EMBED_SERVICE_URL: http://embed:8000
      CLAUDE_CODE_OAUTH_TOKEN: ${CLAUDE_CODE_OAUTH_TOKEN}
      CLAUDE_MODEL: ${INVESTIGATOR_MODEL:-sonnet}
      MAX_PARALLEL_WORKERS: ${INVESTIGATOR_MAX_PARALLEL_WORKERS:-2}
      BUDGET_CAP_USD_PER_JOB: ${INVESTIGATOR_BUDGET_CAP_USD_PER_JOB:-1.00}
      JOB_TIMEOUT_SECONDS: ${INVESTIGATOR_JOB_TIMEOUT_SECONDS:-300}
      CASE_ROOT: /data/ufo/case
      WIKI_ROOT: /data/ufo/wiki
      AUDIT_LOG: /data/ufo/case/audit.jsonl
    volumes:
      - ${DATA_RAW}:/data/ufo/raw:ro
      - ${DATA_WIKI}:/data/ufo/wiki:ro
      - ${CASE_ROOT:-/data/disclosure/case}:/data/ufo/case
    deploy:
      resources:
        limits:
          memory: 512m

  # ─── BGE-M3 embedding + reranker service (CPU only) ───────────────────────
  embed:
    container_name: disclosure-embed
    build:
      context: ../embed-service
    restart: unless-stopped
    networks: [internal]
    environment:
      DEVICE: cpu
      EMBED_MODEL: BAAI/bge-m3
      RERANK_MODEL: BAAI/bge-reranker-v2-m3
      HF_HUB_DOWNLOAD_TIMEOUT: 600
    volumes:
      - hf-cache:/cache
    healthcheck:
      test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 180s
    deploy:
      resources:
        limits:
          memory: 3g

  # ─── Glitchtip — self-hosted Sentry-compatible error monitor (W1.2) ───────
  glitchtip-redis:
    container_name: disclosure-glitchtip-redis
    image: redis:7-alpine
    restart: unless-stopped
    networks: [internal]
    volumes:
      - glitchtip-redis-data:/data
    command: redis-server --appendonly yes

  glitchtip-web:
    container_name: disclosure-glitchtip-web
    image: glitchtip/glitchtip:v4.2
    restart: unless-stopped
    networks: [internal, traefik]
    depends_on:
      db: { condition: service_healthy }
      glitchtip-redis: { condition: service_started }
    environment:
      DATABASE_URL: postgres://glitchtip:${GLITCHTIP_DB_PASSWORD}@db:5432/glitchtip
      SECRET_KEY: ${GLITCHTIP_SECRET_KEY}
      REDIS_URL: redis://glitchtip-redis:6379/0
      PORT: "8080"
      GLITCHTIP_DOMAIN: ${GLITCHTIP_DOMAIN}
      DEFAULT_FROM_EMAIL: ${GLITCHTIP_DEFAULT_FROM_EMAIL}
      EMAIL_URL: consolemail://
      ENABLE_USER_REGISTRATION: "false"   # bootstrap admin via manage.py
      ENABLE_ORGANIZATION_CREATION: "false"
      CELERY_WORKER_AUTOSCALE: "1,3"
      CELERY_WORKER_MAX_TASKS_PER_CHILD: "10000"
    volumes:
      - glitchtip-uploads:/code/uploads
    labels:
      - traefik.enable=true
      - traefik.docker.network=traefik-public
      - traefik.http.routers.disclosure-glitchtip.rule=Host(`glitchtip.disclosure.top`)
      - traefik.http.routers.disclosure-glitchtip.entrypoints=websecure
      - traefik.http.routers.disclosure-glitchtip.tls=true
      - traefik.http.routers.disclosure-glitchtip.tls.certresolver=letsencrypt
      - traefik.http.services.disclosure-glitchtip.loadbalancer.server.port=8080

  glitchtip-worker:
    container_name: disclosure-glitchtip-worker
    image: glitchtip/glitchtip:v4.2
    restart: unless-stopped
    networks: [internal]
    depends_on:
      db: { condition: service_healthy }
      glitchtip-redis: { condition: service_started }
    environment:
      DATABASE_URL: postgres://glitchtip:${GLITCHTIP_DB_PASSWORD}@db:5432/glitchtip
      SECRET_KEY: ${GLITCHTIP_SECRET_KEY}
      REDIS_URL: redis://glitchtip-redis:6379/0
      GLITCHTIP_DOMAIN: ${GLITCHTIP_DOMAIN}
      DEFAULT_FROM_EMAIL: ${GLITCHTIP_DEFAULT_FROM_EMAIL}
      EMAIL_URL: consolemail://
      CELERY_WORKER_AUTOSCALE: "1,3"
      CELERY_WORKER_MAX_TASKS_PER_CHILD: "10000"
    volumes:
      - glitchtip-uploads:/code/uploads
    command: ./bin/run-celery-with-beat.sh

  # ─── Forgejo — self-hosted Git + Actions CI (W1.2) ────────────────────────
  forgejo:
    container_name: disclosure-forgejo
    image: codeberg.org/forgejo/forgejo:9
    restart: unless-stopped
    networks: [internal, traefik]
    depends_on:
      db: { condition: service_healthy }
    environment:
      USER_UID: "1000"
      USER_GID: "1000"
      FORGEJO__database__DB_TYPE: postgres
      FORGEJO__database__HOST: db:5432
      FORGEJO__database__NAME: forgejo
      FORGEJO__database__USER: forgejo
      FORGEJO__database__PASSWD: ${FORGEJO_DB_PASSWORD}
      FORGEJO__server__DOMAIN: ${FORGEJO_DOMAIN}
      FORGEJO__server__ROOT_URL: https://${FORGEJO_DOMAIN}
      FORGEJO__server__SSH_DOMAIN: ${FORGEJO_DOMAIN}
      FORGEJO__service__DISABLE_REGISTRATION: "true"   # admin invites only
      FORGEJO__actions__ENABLED: "true"
      FORGEJO__security__INSTALL_LOCK: "true"
    volumes:
      - forgejo-data:/data
    labels:
      - traefik.enable=true
      - traefik.docker.network=traefik-public
      - traefik.http.routers.disclosure-forgejo.rule=Host(`forgejo.disclosure.top`)
      - traefik.http.routers.disclosure-forgejo.entrypoints=websecure
      - traefik.http.routers.disclosure-forgejo.tls=true
      - traefik.http.routers.disclosure-forgejo.tls.certresolver=letsencrypt
      - traefik.http.services.disclosure-forgejo.loadbalancer.server.port=3000

  forgejo-runner:
    container_name: disclosure-forgejo-runner
    image: code.forgejo.org/forgejo/runner:6
    restart: unless-stopped
    networks: [internal]
    # GID of the docker group on the host — lets the runner (uid 1000) talk
    # to the docker socket without running as root.
    group_add:
      - "988"
    depends_on:
      forgejo: { condition: service_started }
    environment:
      FORGEJO_INSTANCE_URL: http://forgejo:3000
      FORGEJO_RUNNER_REGISTRATION_TOKEN: ${FORGEJO_RUNNER_TOKEN}
      FORGEJO_RUNNER_NAME: disclosure-runner
    volumes:
      - forgejo-runner-config:/data
      - /var/run/docker.sock:/var/run/docker.sock
    command:
      - sh
      - -c
      - |
        sleep 10
        if [ ! -f /data/.runner ]; then
          forgejo-runner register --no-interactive --instance "$$FORGEJO_INSTANCE_URL" --token "$$FORGEJO_RUNNER_REGISTRATION_TOKEN" --name "$$FORGEJO_RUNNER_NAME" --labels 'ubuntu-latest:docker://node:20-bookworm,docker:host'
        fi
        forgejo-runner daemon
