# =============================================================================
# Ulti Backend — Configuration
# =============================================================================
# Secrets : definir UNE FOIS dans la section ci-dessous.
# Le reste du fichier utilise {{NOM_VARIABLE}} — expansion au lancement
# (docker compose, go run, migrate) via cmd/envexpand → .env.resolved
#
# Chaque brique peut etre en mode "local" (stack Docker) ou "external".
# =============================================================================

# -----------------------------------------------------------------------------
# Secrets — source unique (rotation ici uniquement)
# -----------------------------------------------------------------------------
POSTGRES_PASSWORD=changeme
RUSTFS_ACCESS_KEY=ultiadmin
RUSTFS_SECRET_KEY=changeme123
AUTHENTIK_SECRET_KEY=changeme-generate-a-long-random-string
ULTID_OIDC_CLIENT_SECRET=changeme
MAIL_CREDENTIAL_KEYS=v1:MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=
MAIL_ACTIVE_CREDENTIAL_KEY_ID=v1
MAIL_WEBHOOK_SHARED_SECRET=changeme-webhook-signing-secret
NC_ADMIN_PASSWORD=changeme
NC_OIDC_CLIENT_SECRET=changeme
ONLYOFFICE_OIDC_CLIENT_SECRET=changeme
IMMICH_OIDC_CLIENT_SECRET=changeme
JITSI_APP_SECRET=changeme-jwt-secret
JITSI_INTERNAL_AUTH_PASSWORD=changeme
KEYDB_PASSWORD=
MEILISEARCH_API_KEY=changeme
TYPESENSE_API_KEY=changeme
# Cloudflare Tunnel — dev local exposé publiquement (npm run expose)
# CLOUDFLARE_TUNNEL_TOKEN=
# CLOUDFLARE_TUNNEL_PUBLIC_URL=https://dev.ultispace.fr

# -----------------------------------------------------------------------------
# General
# -----------------------------------------------------------------------------
DOMAIN=localhost
ULTID_PORT=8080
# Origines navigateur autorisees (web app sur autre port/origine que l'API).
# Vide = auto : localhost/127.0.0.1/LAN prive en dev ; http(s)://${DOMAIN} en prod.
# Exemple dev explicite : ULTID_CORS_ALLOWED_ORIGINS=http://localhost:3004,http://127.0.0.1:3004
# ULTID_CORS_ALLOWED_ORIGINS=

# -----------------------------------------------------------------------------
# PostgreSQL
# Mode local : deploye dans la stack Docker (deploy/docker-compose.yml)
# Mode externe : pointer vers une instance existante
# -----------------------------------------------------------------------------
POSTGRES_USER=ulti
POSTGRES_DB=ultidb
POSTGRES_MULTIPLE_DATABASES=ultidb,authentik,nextcloud,immich

# Connection utilisee par ultid (changer host pour instance externe)
ULTID_DB_URL=postgres://{{POSTGRES_USER}}:{{POSTGRES_PASSWORD}}@postgres:5432/{{POSTGRES_DB}}?sslmode=disable
# Exemple externe :
# ULTID_DB_URL=postgres://user:{{POSTGRES_PASSWORD}}@db.example.com:5432/ultidb?sslmode=require

# -----------------------------------------------------------------------------
# KeyDB (Redis-compatible)
# Mode local : deploye dans la stack
# Mode externe : pointer vers un Redis/KeyDB/Valkey existant
# -----------------------------------------------------------------------------
ULTID_KEYDB_URL=keydb:6379
ULTID_KEYDB_PASSWORD={{KEYDB_PASSWORD}}
ULTID_KEYDB_DB=0
# Exemple externe : ULTID_KEYDB_URL=redis.example.com:6379

KEYDB_HOST=keydb
KEYDB_PORT=6379

# -----------------------------------------------------------------------------
# Object Storage (S3-compatible : RustFS, MinIO, AWS S3, Backblaze, etc.)
# Mode local : RustFS deploye dans la stack
# Mode externe : n'importe quel endpoint S3-compatible
# -----------------------------------------------------------------------------
ULTID_RUSTFS_ENDPOINT=rustfs:9000
ULTID_RUSTFS_ACCESS_KEY={{RUSTFS_ACCESS_KEY}}
ULTID_RUSTFS_SECRET_KEY={{RUSTFS_SECRET_KEY}}
ULTID_RUSTFS_USE_SSL=false
ULTID_RUSTFS_REGION=us-east-1
# Exemple AWS S3 :
#   ULTID_RUSTFS_ENDPOINT=s3.amazonaws.com
#   ULTID_RUSTFS_USE_SSL=true
#   ULTID_RUSTFS_REGION=eu-west-1

# -----------------------------------------------------------------------------
# Auth / OIDC (Authentik, Keycloak, ou tout provider OIDC)
# Mode local : Authentik deploye dans la stack
# Mode externe : n'importe quel provider OIDC existant
# -----------------------------------------------------------------------------
# Issuer vu par ultid (via nginx interne Docker). DOMAIN sert de Host header pour discovery
# afin que l'issuer attendu = iss des tokens navigateur (http://localhost/auth/application/o/ulti/).
ULTID_OIDC_ISSUER=http://nginx/auth/application/o/ulti/
ULTID_OIDC_CLIENT_ID=ulti-backend
# ULTID_OIDC_CLIENT_SECRET — defini dans la section Secrets (doit matcher blueprint Authentik)
ULTID_AUTO_MIGRATE=true
# Exemple Keycloak externe :
#   ULTID_OIDC_ISSUER=https://auth.example.com/realms/ulti
#   ULTID_OIDC_CLIENT_ID=ulti-backend

# Config du container Authentik local (ignoree si provider externe)
# AUTHENTIK_SECRET_KEY — defini dans la section Secrets
AUTHENTIK_POSTGRESQL__HOST=postgres
AUTHENTIK_POSTGRESQL__USER={{POSTGRES_USER}}
AUTHENTIK_POSTGRESQL__PASSWORD={{POSTGRES_PASSWORD}}
AUTHENTIK_POSTGRESQL__NAME=authentik
AUTHENTIK_REDIS__HOST=keydb
AUTHENTIK_WEB__PATH=/auth/
# URL publique affichee dans les redirects OIDC (navigateur)
AUTHENTIK_HOST=http://{{DOMAIN}}
# API interne (ultid → authentik-server) pour provisionner les apps OIDC au demarrage
AUTHENTIK_API_URL=http://authentik-server:9000
# Token admin Authentik (Flows & Stages → Tokens) — active le provisioning API ultid
# AUTHENTIK_API_TOKEN — defini dans la section Secrets (optionnel ; sinon blueprints seuls)

# -----------------------------------------------------------------------------
# Nextcloud (Drive / Calendar / Contacts)
# Mode local : Nextcloud FPM deploye dans la stack
# Mode externe : instance Nextcloud existante
# -----------------------------------------------------------------------------
# URL interne (ultid → nginx Nextcloud, racine WebDAV)
NEXTCLOUD_URL=http://nextcloud:80
# URL publique UI (edge nginx /cloud/) — aussi base Destination WebDAV MOVE/COPY
NC_PUBLIC_URL=http://{{DOMAIN}}/cloud
NC_OVERWRITE_PROTOCOL=http
NC_ADMIN_USER=admin
# NC_ADMIN_PASSWORD — defini dans la section Secrets
# Exemple externe :
#   NEXTCLOUD_URL=https://cloud.example.com
#   NC_ADMIN_USER=ulti-service
# Desactiver (pas de Drive/Cal/Contacts) : NEXTCLOUD_ENABLED=false

NEXTCLOUD_ENABLED=true
# Ce flag pilote aussi le lancement Docker via ./deploy/compose-up.sh

NC_OIDC_CLIENT_ID=ulti-nextcloud
# NC_OIDC_CLIENT_SECRET — defini dans la section Secrets
NC_OIDC_DISCOVERY_URL=http://nginx/auth/application/o/nextcloud/.well-known/openid-configuration

NC_S3_BUCKET=nextcloud
NC_S3_HOST=rustfs
NC_S3_PORT=9000
NC_S3_KEY={{RUSTFS_ACCESS_KEY}}
NC_S3_SECRET={{RUSTFS_SECRET_KEY}}
NC_S3_SSL=false

# -----------------------------------------------------------------------------
# OnlyOffice (Docs / Sheets / Slides)
# -----------------------------------------------------------------------------
ONLYOFFICE_ENABLED=false
ONLYOFFICE_URL=http://onlyoffice
ONLYOFFICE_PUBLIC_URL=http://{{DOMAIN}}/office
# URL ultid joignable depuis le conteneur OnlyOffice (fetch doc + callback)
ONLYOFFICE_API_INTERNAL_URL=http://ultid:8080
# URL Nextcloud joignable depuis OnlyOffice (host nginx Docker). Nginx route /index.php/* → Nextcloud.
# NC_ONLYOFFICE_STORAGE_URL=http://nginx
# Docker compose OnlyOffice also sets ALLOW_PRIVATE_IP_ADDRESS=true (required for internal URLs)
ONLYOFFICE_JWT_SECRET=changeme-onlyoffice-jwt
ONLYOFFICE_OIDC_CLIENT_ID=ulti-onlyoffice
# ONLYOFFICE_OIDC_CLIENT_SECRET — defini dans la section Secrets
ULTID_PUBLIC_URL=http://{{DOMAIN}}
# Base URL for public share links (default: {ULTID_PUBLIC_URL}/drive → /drive/s/{token})
# DRIVE_PUBLIC_URL=http://{{DOMAIN}}/drive

# -----------------------------------------------------------------------------
# Rich text editor (TipTap + Hocuspocus)
# -----------------------------------------------------------------------------
RICHTEXT_ENABLED=true
HOCUSPOCUS_PUBLIC_URL=ws://{{DOMAIN}}/collab
HOCUSPOCUS_SECRET=changeme-hocuspocus-secret
RICHTEXT_STORAGE_MODE=sidecar
# RICHTEXT_EXPORT_MIRROR=docx

# -----------------------------------------------------------------------------
# UltiAI (OpenWebUI + gateway LLM)
# -----------------------------------------------------------------------------
AI_ASSISTANT_ENABLED=false
OPENWEBUI_URL=http://openwebui:8080
WEBUI_SECRET_KEY=changeme-openwebui-dev-secret
AI_GATEWAY_API_KEY=ulti-gateway
# OpenWebUI embed : modèles ultid visibles sans entrée DB par modèle
BYPASS_MODEL_ACCESS_CONTROL=true
WEBUI_NAME=UltiAI
AI_ASSISTANT_PUBLIC_PATH=/ai
ULTIMAIL_MCP_URL=http://ultimail-mcp:3100
# Public MCP endpoint (nginx → ultid → ultimail-mcp): /api/v1/ai/mcp
# OpenWebUI uses AI_GATEWAY_API_KEY + forwarded X-OpenWebUI-User-* headers for per-user tokens.
# Ultimail MCP auto-enabled for all users (see deploy/openwebui/docker-compose.openwebui.yml):
#   DEFAULT_MODEL_PARAMS={"function_calling":"native"}
#   DEFAULT_MODEL_METADATA={"toolIds":["server:mcp:ultimail"],...}
#   TOOL_SERVER_CONNECTIONS with config.access_grants public read
# OpenWebUI utilise POSTGRES_USER/POSTGRES_PASSWORD (base openwebui créée dans init-db.sh)

# -----------------------------------------------------------------------------
# Jitsi Meet (Visioconference)
# Mode local : Jitsi deploye dans la stack
# Mode externe : instance Jitsi existante avec JWT configure
# -----------------------------------------------------------------------------
JITSI_ENABLED=true
# Ce flag pilote aussi le lancement Docker via ./deploy/compose-up.sh
JITSI_DOMAIN=meet.jitsi
JITSI_APP_ID=ulti
# JITSI_APP_SECRET — defini dans la section Secrets
JITSI_PUBLIC_URL=https://{{DOMAIN}}/meet
# Secret partagé avec Jigasi pour POST /api/v1/meet/transcripts
MEET_TRANSCRIPT_WEBHOOK_SECRET=changeme-meet-transcript-secret
# Modèle Faster Whisper (Skynet) : tiny, base, small…
SKYNET_WHISPER_MODEL=tiny

JICOFO_AUTH_PASSWORD={{JITSI_INTERNAL_AUTH_PASSWORD}}
JVB_AUTH_PASSWORD={{JITSI_INTERNAL_AUTH_PASSWORD}}
JIGASI_XMPP_PASSWORD={{JITSI_INTERNAL_AUTH_PASSWORD}}
JIGASI_TRANSCRIBER_PASSWORD={{JITSI_INTERNAL_AUTH_PASSWORD}}
JVB_STUN_SERVERS=stun.l.google.com:19302

# -----------------------------------------------------------------------------
# Immich (Photos)
# Mode local : Immich deploye dans la stack
# Mode externe : instance Immich existante
# -----------------------------------------------------------------------------
IMMICH_ENABLED=true
# Ce flag pilote aussi le lancement Docker via ./deploy/compose-up.sh
# Immich utilise son propre Postgres (pgvecto.rs) via immich-postgres
IMMICH_API_URL=http://immich-server:2283/api
# Quota photos partagé avec Ultidrive (0 = désactivé)
ULTID_PHOTOS_MAX_UPLOAD_BYTES=0
ULTID_PHOTOS_QUOTA_RESERVED_BYTES=0

IMMICH_DB_NAME=immich
IMMICH_UPLOAD_LOCATION=/upload
IMMICH_ML_URL=http://immich-ml:3003

# -----------------------------------------------------------------------------
# Health checks / Observability
# -----------------------------------------------------------------------------
# Endpoints vérifiés par /healthz (surchargables pour déploiements externes)
HEALTH_NEXTCLOUD_URL={{NEXTCLOUD_URL}}/status.php
HEALTH_IMMICH_URL={{IMMICH_API_URL}}/server-info/ping
# Stack Docker locale : probe interne jitsi-web
# Déploiement externe : https://{{DOMAIN}}/about/health (ou JITSI_PUBLIC_URL sans /meet + /about/health)
HEALTH_JITSI_URL=http://jitsi-web:80/about/health
HEALTH_HTTP_TIMEOUT=3s
# Grafana local (monitoring)
GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=admin

# -----------------------------------------------------------------------------
# Mail (Ultimail) — toujours gere par ultid
# -----------------------------------------------------------------------------
MAIL_ATTACHMENTS_BUCKET=mail-attachments
MAIL_SYNC_INTERVAL=2m
MAIL_OUTBOX_INTERVAL=10s
MAIL_OUTBOX_MAX_RETRIES=8
MAIL_SEND_RATE_PER_MINUTE=30
MAIL_SEND_BURST=10
MAIL_SMTP_CIRCUIT_FAILURES=5
MAIL_SMTP_CIRCUIT_COOLDOWN=5m
# Credentials IMAP/SMTP chiffrés AES-GCM (format keyring: key_id:base64key,key_id2:base64key2)
# Rotation: ajouter nouvelle clé dans MAIL_CREDENTIAL_KEYS puis basculer MAIL_ACTIVE_CREDENTIAL_KEY_ID.
# Les anciennes clés restent présentes temporairement pour déchiffrement.
# Runtime secrets possibles via *_FILE (ex: MAIL_CREDENTIAL_KEYS_FILE=/run/secrets/mail_keys)

# Politique rotation secrets (RFC3339 + durée)
SECRET_ROTATION_MAX_AGE=2160h
ULTID_OIDC_CLIENT_SECRET_ROTATED_AT=2026-01-01T00:00:00Z
MAIL_CREDENTIAL_KEY_ROTATED_AT=2026-01-01T00:00:00Z

# Mail provider OAuth (Gmail / Microsoft 365) — optional
# Redirect URI must match Google/Azure app config, e.g. https://api.example.com/api/v1/mail/accounts/oauth/callback
MAIL_GOOGLE_OAUTH_CLIENT_ID=
MAIL_GOOGLE_OAUTH_CLIENT_SECRET=
MAIL_MICROSOFT_OAUTH_CLIENT_ID=
MAIL_MICROSOFT_OAUTH_CLIENT_SECRET=
MAIL_MICROSOFT_OAUTH_TENANT=common
MAIL_OAUTH_REDIRECT_URL=
MAIL_APP_URL=http://localhost/mail
# Cible nginx → suite frontend unifié mail+drive (dev: Next sur l'hôte :3004 ; prod: suite-frontend:3000)

# -----------------------------------------------------------------------------
# Stalwart hosted mail (optional — enable for @ultisuite.fr / custom domains)
# -----------------------------------------------------------------------------
STALWART_ENABLED=false
STALWART_API_URL=http://stalwart:8080
# STALWART_API_KEY — API key from Stalwart webadmin (management JMAP API)
STALWART_IMAP_HOST=stalwart
STALWART_IMAP_PORT=993
STALWART_IMAP_TLS=true
STALWART_SMTP_HOST=stalwart
STALWART_SMTP_PORT=587
STALWART_SMTP_TLS=true
STALWART_RECOVERY_ADMIN=admin:changeme-stalwart-admin
PLATFORM_MAIL_DOMAIN=ultisuite.fr
# PROVISION_WEBHOOK_SECRET — shared secret for Authentik enrollment webhook → ultid
PROVISION_WEBHOOK_SECRET=changeme-provision-webhook

# -----------------------------------------------------------------------------
# Migration OAuth (Google Workspace / Microsoft 365 import)
# Falls back to MAIL_* OAuth credentials when unset.
# -----------------------------------------------------------------------------
MIGRATION_GOOGLE_OAUTH_CLIENT_ID=
MIGRATION_GOOGLE_OAUTH_CLIENT_SECRET=
MIGRATION_MICROSOFT_OAUTH_CLIENT_ID=
MIGRATION_MICROSOFT_OAUTH_CLIENT_SECRET=
MIGRATION_MICROSOFT_OAUTH_TENANT=common
MIGRATION_OAUTH_REDIRECT_URL=
MIGRATION_WORKER_INTERVAL=30s
# Worker picks up to JOB_LIMIT pending jobs per tick (0 = concurrency*3, min 5).
MIGRATION_WORKER_CONCURRENCY=2
MIGRATION_WORKER_JOB_LIMIT=0
# Items processed per worker tick (mail/contacts/calendar vs drive file downloads).
MIGRATION_IMPORT_BATCH_SIZE=25
MIGRATION_DRIVE_BATCH_SIZE=10
# Rate-limit backoff for Google/Microsoft migration API calls (429 + Retry-After).
MIGRATION_RATE_LIMIT_MAX_RETRIES=6
MIGRATION_RATE_LIMIT_BASE_DELAY=2s
MIGRATION_RATE_LIMIT_MAX_DELAY=2m
# Microsoft 365 app-only (client credentials) uses MIGRATION_MICROSOFT_OAUTH_* above.
# Requires Azure AD application permissions (Mail.Read, Calendars.Read, Contacts.Read, Files.Read.All)
# and tenant admin consent; per-project tenant id is stored after admin consent redirect.
# Google Workspace domain-wide delegation (service account JSON, one line)
MIGRATION_GOOGLE_SERVICE_ACCOUNT_JSON=
# MX hosts expected at cutover (comma-separated). Defaults: mail.{PLATFORM_MAIL_DOMAIN}, then STALWART_IMAP_HOST if FQDN.
MIGRATION_CUTOVER_MX_HOSTS=
# Block cutover when live MX does not match expected hosts (requires domain_id on project).
MIGRATION_CUTOVER_REQUIRE_MX=false

MAIL_FRONTEND_UPSTREAM=host.docker.internal:3004
MAIL_WEBHOOK_SHARED_SECRET_ROTATED_AT=2026-01-01T00:00:00Z

# -----------------------------------------------------------------------------
# Recherche
# SEARCH_ENGINE: postgres (defaut) | meilisearch | typesense
# -----------------------------------------------------------------------------
SEARCH_ENGINE=postgres

# --- Meilisearch (SEARCH_ENGINE=meilisearch) ---
# MEILISEARCH_URL=http://meilisearch:7700
# MEILISEARCH_API_KEY={{MEILISEARCH_API_KEY}}
# MEILISEARCH_INDEX=ulti

# --- Typesense (SEARCH_ENGINE=typesense) ---
# TYPESENSE_URL=http://typesense:8108
# TYPESENSE_API_KEY={{TYPESENSE_API_KEY}}
# TYPESENSE_COLLECTION=ulti

# -----------------------------------------------------------------------------
# VirusTotal (optional env fallback; prefer admin Settings > File policies)
# -----------------------------------------------------------------------------
# VIRUSTOTAL_API_KEY=

# -----------------------------------------------------------------------------
# Mobile push notifications (FCM Android + APNS iOS) — optional
# When unset, the push dispatcher logs and skips (local dev works without creds).
# Tokens are registered by mobile apps via POST /api/v1/devices/register and a
# new incoming mail fans out a push alongside the existing WS broadcast.
# -----------------------------------------------------------------------------
# --- FCM (Android), HTTP v1 API ---
# Service account JSON (single line, e.g. from Firebase console > Service accounts).
# project_id is read from the JSON unless FCM_PROJECT_ID overrides it.
# Runtime secret supported via FCM_SERVICE_ACCOUNT_JSON_FILE.
FCM_SERVICE_ACCOUNT_JSON=
# FCM_PROJECT_ID=

# --- APNS (iOS), token-based .p8 auth ---
# PEM contents of the AuthKey_XXXXXXXXXX.p8 file (multi-line OK with quotes, or via APNS_PRIVATE_KEY_FILE).
APNS_PRIVATE_KEY=
# 10-char Key ID from the Apple Developer key, the Apple Team ID, and the app bundle id (apns-topic).
APNS_KEY_ID=
APNS_TEAM_ID=
APNS_BUNDLE_ID=
# false = sandbox gateway (development builds); true = production gateway.
APNS_PRODUCTION=false
