name: mc-cars

############################################################
# MC Cars - fully Dockerized self-hosted Supabase + web
#
# Host deployment root:
#   - ALL bind mounts resolve under /mnt/user/appdata/mc-cars.
#   - Runtime state lives under /mnt/user/appdata/mc-cars/data.
#   - Config lives under /mnt/user/appdata/mc-cars/{supabase,frontend}.
#   - Deploy this stack from that host folder so Docker sees the same
#     absolute paths on every restart.
############################################################

networks:
  mccars:
    driver: bridge

services:
  # -------------------------------------------------------------------------
  # Postgres with logical replication enabled (needed by supabase/realtime).
  # First-boot SQL creates the Supabase service roles.
  # -------------------------------------------------------------------------
  db:
    image: postgres:15-alpine
    container_name: mccars-db
    restart: unless-stopped
    command:
      - postgres
      - -c
      - wal_level=logical
      - -c
      - max_wal_senders=10
      - -c
      - max_replication_slots=10
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
      interval: 5s
      timeout: 5s
      retries: 30
    environment:
      POSTGRES_USER: postgres
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - /mnt/user/appdata/mc-cars/data/db:/var/lib/postgresql/data
      - /mnt/user/appdata/mc-cars/supabase/migrations/00-run-init.sh:/docker-entrypoint-initdb.d/00-run-init.sh:ro
      - /mnt/user/appdata/mc-cars/supabase/migrations/01-init.sql:/sql/01-init.sql:ro
    ports:
      - "55532:5432"
    networks: [mccars]
    logging: { driver: json-file, options: { max-size: "10m", max-file: "3" } }

  # -------------------------------------------------------------------------
  # GoTrue (Supabase Auth)
  # -------------------------------------------------------------------------
  auth:
    image: supabase/gotrue:v2.158.1
    container_name: mccars-auth
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      GOTRUE_API_HOST: 0.0.0.0
      GOTRUE_API_PORT: 9999
      API_EXTERNAL_URL: ${SUPABASE_PUBLIC_URL}

      GOTRUE_DB_DRIVER: postgres
      GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?search_path=auth
      DB_NAMESPACE: auth

      GOTRUE_SITE_URL: ${SITE_URL}
      GOTRUE_URI_ALLOW_LIST: ${SITE_URL},${SITE_URL}/admin.html
      GOTRUE_DISABLE_SIGNUP: ${DISABLE_SIGNUP}

      GOTRUE_JWT_ADMIN_ROLES: service_role
      GOTRUE_JWT_AUD: authenticated
      GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
      GOTRUE_JWT_EXP: ${JWT_EXPIRY}
      GOTRUE_JWT_SECRET: ${JWT_SECRET}

      GOTRUE_EXTERNAL_EMAIL_ENABLED: ${ENABLE_EMAIL_SIGNUP}
      GOTRUE_MAILER_AUTOCONFIRM: ${ENABLE_EMAIL_AUTOCONFIRM}
      GOTRUE_SMTP_ADMIN_EMAIL: ${ADMIN_EMAIL}
      GOTRUE_SMTP_HOST: ${SMTP_HOST}
      GOTRUE_SMTP_PORT: ${SMTP_PORT}
      GOTRUE_SMTP_USER: ${SMTP_USER}
      GOTRUE_SMTP_PASS: ${SMTP_PASS}
      GOTRUE_SMTP_SENDER_NAME: MC Cars
      GOTRUE_PASSWORD_MIN_LENGTH: 10
    networks: [mccars]
    logging: { driver: json-file, options: { max-size: "10m", max-file: "3" } }

  # -------------------------------------------------------------------------
  # PostgREST (REST API generated from Postgres schema)
  # -------------------------------------------------------------------------
  rest:
    image: postgrest/postgrest:v12.2.0
    container_name: mccars-rest
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
      PGRST_DB_SCHEMAS: public,storage
      PGRST_DB_ANON_ROLE: anon
      PGRST_JWT_SECRET: ${JWT_SECRET}
      PGRST_DB_USE_LEGACY_GUCS: "false"
      PGRST_DB_CHANNEL_ENABLED: "true"
    networks: [mccars]
    logging: { driver: json-file, options: { max-size: "10m", max-file: "3" } }

  # -------------------------------------------------------------------------
  # Storage API + imgproxy
  # -------------------------------------------------------------------------
  storage:
    image: supabase/storage-api:v1.11.13
    container_name: mccars-storage
    restart: unless-stopped
    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_DB}
      FILE_SIZE_LIMIT: ${FILE_SIZE_LIMIT}
      STORAGE_BACKEND: ${STORAGE_BACKEND}
      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:
      - /mnt/user/appdata/mc-cars/data/storage:/var/lib/storage
    networks: [mccars]
    logging: { driver: json-file, options: { max-size: "10m", max-file: "3" } }

  imgproxy:
    image: darthsim/imgproxy:v3.8.0
    container_name: mccars-imgproxy
    restart: unless-stopped
    environment:
      IMGPROXY_BIND: ":5001"
      IMGPROXY_LOCAL_FILESYSTEM_ROOT: /
      IMGPROXY_USE_ETAG: "true"
      IMGPROXY_ENABLE_WEBP_DETECTION: "true"
    volumes:
      - /mnt/user/appdata/mc-cars/data/storage:/var/lib/storage
    networks: [mccars]

  # -------------------------------------------------------------------------
  # Realtime (Phoenix/Elixir) - live subscriptions for leads/customers
  # -------------------------------------------------------------------------
  realtime:
    image: supabase/realtime:v2.30.23
    container_name: mccars-realtime
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      PORT: 4000
      DB_HOST: db
      DB_PORT: 5432
      DB_USER: postgres
      DB_PASSWORD: ${POSTGRES_PASSWORD}
      DB_NAME: ${POSTGRES_DB}
      DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'
      DB_ENC_KEY: supabaserealtime
      API_JWT_SECRET: ${JWT_SECRET}
      FLY_ALLOC_ID: fly123
      FLY_APP_NAME: realtime
      SECRET_KEY_BASE: UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq
      ERL_AFLAGS: -proto_dist inet_tcp
      ENABLE_TAILSCALE: "false"
      DNS_NODES: "''"
      RLIMIT_NOFILE: "10000"
      APP_NAME: realtime
      REALTIME_IP_VERSION: IPV4
    command: >
      sh -c "/app/bin/migrate && /app/bin/server"
    networks: [mccars]
    logging: { driver: json-file, options: { max-size: "10m", max-file: "3" } }

  # -------------------------------------------------------------------------
  # Post-init: admin user + storage bucket + leads/customers migration.
  # Exits 0 once done. Re-running is idempotent.
  # -------------------------------------------------------------------------
  post-init:
    image: postgres:15-alpine
    container_name: mccars-postinit
    depends_on:
      auth:
        condition: service_started
      storage:
        condition: service_started
    environment:
      PGPASSWORD: ${POSTGRES_PASSWORD}
      ADMIN_EMAIL: ${ADMIN_EMAIL}
      ADMIN_PASSWORD: ${ADMIN_PASSWORD}
    volumes:
      - /mnt/user/appdata/mc-cars/supabase/migrations/post-boot.sql:/sql/post-boot.sql:ro
      - /mnt/user/appdata/mc-cars/supabase/migrations/02-leads.sql:/sql/02-leads.sql:ro
      - /mnt/user/appdata/mc-cars/supabase/migrations/03-booking-flow.sql:/sql/03-booking-flow.sql:ro
      - /mnt/user/appdata/mc-cars/supabase/migrations/04-kaution-weekend-km.sql:/sql/04-kaution-weekend-km.sql:ro
      - /mnt/user/appdata/mc-cars/supabase/migrations/05-create-lead-rpc.sql:/sql/05-create-lead-rpc.sql:ro
      - /mnt/user/appdata/mc-cars/supabase/migrations/06-admin-pricing-documents.sql:/sql/06-admin-pricing-documents.sql:ro
      - /mnt/user/appdata/mc-cars/supabase/migrations/07-sales-orders.sql:/sql/07-sales-orders.sql:ro
      - /mnt/user/appdata/mc-cars/supabase/migrations/08-backend-pricing-and-security.sql:/sql/08-backend-pricing-and-security.sql:ro
      - /mnt/user/appdata/mc-cars/supabase/migrations/09-site-settings.sql:/sql/09-site-settings.sql:ro
      - /mnt/user/appdata/mc-cars/supabase/migrations/10-mietvertrag-workflow.sql:/sql/10-mietvertrag-workflow.sql:ro
      - /mnt/user/appdata/mc-cars/supabase/migrations/11-consolidate-km-rental.sql:/sql/11-consolidate-km-rental.sql:ro
      - /mnt/user/appdata/mc-cars/supabase/migrations/12-email-sent-and-more.sql:/sql/12-email-sent-and-more.sql:ro
      - /mnt/user/appdata/mc-cars/supabase/migrations/13-rental-type-daily-and-email-guard.sql:/sql/13-rental-type-daily-and-email-guard.sql:ro
      - /mnt/user/appdata/mc-cars/supabase/migrations/14-email-requested-trigger.sql:/sql/14-email-requested-trigger.sql:ro
      - /mnt/user/appdata/mc-cars/supabase/migrations/15-individuell-vat-subtotal-fix.sql:/sql/15-individuell-vat-subtotal-fix.sql:ro
      - /mnt/user/appdata/mc-cars/supabase/migrations/16-rental-type-weekend-gap-fix.sql:/sql/16-rental-type-weekend-gap-fix.sql:ro
      - /mnt/user/appdata/mc-cars/supabase/migrations/17-vehicle-photos.sql:/sql/17-vehicle-photos.sql:ro
    entrypoint: ["sh","-c"]
    command:
      - |
        set -e
        echo "Waiting for auth.users and storage.buckets..."
        for i in $$(seq 1 60); do
          if psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -tAc "select 1 from auth.users limit 1" >/dev/null 2>&1 \
             && psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -tAc "select 1 from storage.buckets limit 1" >/dev/null 2>&1; then
            echo "Schemas ready."
            break
          fi
          sleep 2
        done
        psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 \
             -v admin_email="$$ADMIN_EMAIL" \
             -v admin_password="$$ADMIN_PASSWORD" \
             -f /sql/post-boot.sql
        psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/02-leads.sql
        psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/03-booking-flow.sql
        psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/04-kaution-weekend-km.sql
        psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/05-create-lead-rpc.sql
        psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/06-admin-pricing-documents.sql
        psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/07-sales-orders.sql
        psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/08-backend-pricing-and-security.sql
        psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/09-site-settings.sql
        psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/10-mietvertrag-workflow.sql
        psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/11-consolidate-km-rental.sql
        psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/12-email-sent-and-more.sql
        psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/13-rental-type-daily-and-email-guard.sql
        psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/14-email-requested-trigger.sql
        psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/15-individuell-vat-subtotal-fix.sql
        psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/16-rental-type-weekend-gap-fix.sql
        psql "postgresql://postgres:$$PGPASSWORD@db:5432/postgres" -v ON_ERROR_STOP=1 -f /sql/17-vehicle-photos.sql
        echo "post-init done."
    restart: "no"
    networks: [mccars]

  # -------------------------------------------------------------------------
  # postgres-meta (Studio uses this for schema introspection)
  # -------------------------------------------------------------------------
  meta:
    image: supabase/postgres-meta:v0.84.2
    container_name: mccars-meta
    restart: unless-stopped
    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_DB}
      PG_META_DB_USER: postgres
      PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD}
    networks: [mccars]

  # -------------------------------------------------------------------------
  # Kong - single API gateway for the browser
  # -------------------------------------------------------------------------
  kong:
    image: kong:2.8.1
    container_name: mccars-kong
    restart: unless-stopped
    depends_on:
      - auth
      - rest
      - storage
      - meta
      - realtime
    environment:
      KONG_DATABASE: "off"
      KONG_DECLARATIVE_CONFIG: /home/kong/kong.yml
      KONG_DNS_ORDER: LAST,A,CNAME
      KONG_PLUGINS: bundled,request-transformer,cors,key-auth,acl,basic-auth
      KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k
      KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k
    volumes:
      - /mnt/user/appdata/mc-cars/supabase/kong.yml:/home/kong/kong.yml:ro
    ports:
      - "55521:8000/tcp"
      - "55543:8443/tcp"
    networks: [mccars]
    logging: { driver: json-file, options: { max-size: "10m", max-file: "3" } }

  # -------------------------------------------------------------------------
  # Supabase Studio
  # -------------------------------------------------------------------------
  studio:
    image: supabase/studio:20241202-71e5240
    container_name: mccars-studio
    restart: unless-stopped
    depends_on:
      meta:
        condition: service_started
    environment:
      STUDIO_PG_META_URL: http://meta:8080
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

      DEFAULT_ORGANIZATION_NAME: MC Cars
      DEFAULT_PROJECT_NAME: mc-cars

      SUPABASE_URL: http://kong:8000
      SUPABASE_PUBLIC_URL: ${SUPABASE_PUBLIC_URL}
      SUPABASE_ANON_KEY: ${ANON_KEY}
      SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
      AUTH_JWT_SECRET: ${JWT_SECRET}

      LOGFLARE_API_KEY: stub
      LOGFLARE_URL: http://localhost:4000
      NEXT_PUBLIC_ENABLE_LOGS: "false"
    ports:
      - "55530:3000"
    networks: [mccars]

  # -------------------------------------------------------------------------
  # Public website (nginx + static assets). Anon key injected at boot.
  # No build step — Portainer-compatible (image + bind mounts).
  # config.js is generated inline (Unraid doesn't preserve execute bits).
  # -------------------------------------------------------------------------
  web:
    image: nginx:1.27-alpine
    container_name: mccars-web
    restart: unless-stopped
    depends_on:
      - kong
    environment:
      SUPABASE_URL: ${SUPABASE_PUBLIC_URL}
      SUPABASE_ANON_KEY: ${ANON_KEY}
      N8N_WEBHOOK_URL: ${N8N_WEBHOOK_URL}
    volumes:
      - /mnt/user/appdata/mc-cars/frontend:/usr/share/nginx/html
      - /mnt/user/appdata/mc-cars/frontend/nginx.conf:/etc/nginx/conf.d/default.conf:ro
    entrypoint: ["/bin/sh", "-c", "printf 'window.MCCARS_CONFIG={SUPABASE_URL:\"%s\",SUPABASE_ANON_KEY:\"%s\",N8N_WEBHOOK_URL:\"%s\"};\\n' \"$$SUPABASE_URL\" \"$$SUPABASE_ANON_KEY\" \"$$N8N_WEBHOOK_URL\" > /usr/share/nginx/html/config.js && exec nginx -g 'daemon off;'"]
    ports:
      - "55580:80"
    networks: [mccars]
    logging: { driver: json-file, options: { max-size: "10m", max-file: "3" } }

  # -------------------------------------------------------------------------
  # Admin panel (separate nginx instance on its own port)
  # -------------------------------------------------------------------------
  web-admin:
    image: nginx:1.27-alpine
    container_name: mccars-web-admin
    restart: unless-stopped
    depends_on:
      - kong
    environment:
      SUPABASE_URL: ${SUPABASE_PUBLIC_URL}
      SUPABASE_ANON_KEY: ${ANON_KEY}
      N8N_WEBHOOK_URL: ${N8N_WEBHOOK_URL}
    volumes:
      - /mnt/user/appdata/mc-cars/frontend:/usr/share/nginx/html
      - /mnt/user/appdata/mc-cars/frontend/nginx-admin.conf:/etc/nginx/conf.d/default.conf:ro
    entrypoint: ["/bin/sh", "-c", "printf 'window.MCCARS_CONFIG={SUPABASE_URL:\"%s\",SUPABASE_ANON_KEY:\"%s\",N8N_WEBHOOK_URL:\"%s\"};\\n' \"$$SUPABASE_URL\" \"$$SUPABASE_ANON_KEY\" \"$$N8N_WEBHOOK_URL\" > /usr/share/nginx/html/config.js && exec nginx -g 'daemon off;'"]
    ports:
      - "55581:80"
    networks: [mccars]
    logging: { driver: json-file, options: { max-size: "10m", max-file: "3" } }

  # -------------------------------------------------------------------------
  # n8n - workflow automation (connects to Supabase via Postgres + REST)
  # UI: http://localhost:55590
  # -------------------------------------------------------------------------
  n8n:
    image: n8nio/n8n:latest
    container_name: mccars-n8n
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      kong:
        condition: service_started
    environment:
      # Core
      N8N_HOST: 0.0.0.0
      N8N_PORT: 5678
      N8N_PROTOCOL: http
      WEBHOOK_URL: http://localhost:55590/
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: "false"
      N8N_SECURE_COOKIE: "false"

      # Database (n8n stores its own data in the same Postgres)
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: db
      DB_POSTGRESDB_PORT: 5432
      DB_POSTGRESDB_DATABASE: ${POSTGRES_DB}
      DB_POSTGRESDB_USER: postgres
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
      DB_POSTGRESDB_SCHEMA: n8n

      # Auto-create owner account on first boot
      N8N_DEFAULT_USER_EMAIL: ${N8N_USER_EMAIL}
      N8N_DEFAULT_USER_PASSWORD: ${N8N_USER_PASSWORD}

      # Timezone
      GENERIC_TIMEZONE: Europe/Vienna
      TZ: Europe/Vienna

      # Workflow/credential bootstrap (re-import on every start)
      N8N_POSTGRES_CREDENTIAL_ID: ${N8N_POSTGRES_CREDENTIAL_ID}
      N8N_POSTGRES_CREDENTIAL_NAME: ${N8N_POSTGRES_CREDENTIAL_NAME}
      N8N_SMTP_CREDENTIAL_ID: ${N8N_SMTP_CREDENTIAL_ID}
      N8N_SMTP_CREDENTIAL_NAME: ${N8N_SMTP_CREDENTIAL_NAME}
      N8N_SMTP_HOST: ${N8N_SMTP_HOST}
      N8N_SMTP_USER: ${N8N_SMTP_USER}
      N8N_SMTP_PASS: ${N8N_SMTP_PASS}
      N8N_PAYPAL_KAUTION_LINK: ${N8N_PAYPAL_KAUTION_LINK}
      N8N_PAYPAL_MIETE_LINK: ${N8N_PAYPAL_MIETE_LINK}
      N8N_PAYMENT_WORKFLOW_ID: ${N8N_PAYMENT_WORKFLOW_ID}
      N8N_WORKFLOW_TEMPLATE: /opt/mc-cars/workflows/01-qualification-payment-email.json
    volumes:
      - /mnt/user/appdata/mc-cars/data/n8n:/home/node/.n8n
      - /mnt/user/appdata/mc-cars/n8n/workflows:/opt/mc-cars/workflows:ro
      - /mnt/user/appdata/mc-cars/n8n/bootstrap:/opt/mc-cars/bootstrap:ro
    user: "0:0"
    entrypoint: ["/bin/sh", "-c"]
    command:
      - |
        set -e
        mkdir -p /home/node/.n8n
        chown -R 1000:1000 /home/node/.n8n
        chmod 700 /home/node/.n8n
        exec su node -s /bin/sh -c '/bin/sh /opt/mc-cars/bootstrap/bootstrap-n8n.sh && exec n8n start'
    ports:
      - "55590:5678"
    networks: [mccars]
    logging: { driver: json-file, options: { max-size: "10m", max-file: "3" } }

  # -------------------------------------------------------------------------
  # Gotenberg - headless document converter (DOCX → PDF)
  # Used by n8n to generate Mietvertrag PDFs from DOCX templates.
  # -------------------------------------------------------------------------
  gotenberg:
    image: gotenberg/gotenberg:8
    container_name: mccars-gotenberg
    restart: unless-stopped
    command:
      - "gotenberg"
      - "--api-port=3000"
      - "--api-timeout=60s"
      - "--libreoffice-restart-after=10"
    networks: [mccars]
    logging: { driver: json-file, options: { max-size: "10m", max-file: "3" } }
