# ============================================================================= # Staging Environment - Template for api-gateway, worker, frontend # ============================================================================= # Deploy script (deploy-staging.ps1) builds .env.staging by MERGING this file with # ALL keys from your local backend/.env, worker/.env, frontend/.env. Real keys # and secrets stay on your machine (SFTP upload only; no GitHub). Placeholders # STAGING_* are replaced by the script; any variable in your local .env overrides # or is added to the bundle. Replace STAGING_SERVER_IP / STAGING_DB_PASSWORD etc. # in this example only if you do not have them in local .env. # ============================================================================= NODE_ENV=development API_PORT=4000 API_HOST=0.0.0.0 # Database (compose + api-gateway/worker) DB_NAME=toolsplatform DB_USER=postgres DB_PASSWORD=STAGING_DB_PASSWORD_CHANGE_ME # connection_limit and pool_timeout for production/staging (avoid Prisma defaults; tune per host) DATABASE_URL=postgresql://postgres:STAGING_DB_PASSWORD_CHANGE_ME@postgres:5432/toolsplatform?schema=app&connection_limit=20&pool_timeout=10 # Redis / MinIO (service names in Docker) REDIS_HOST=redis REDIS_PORT=6379 MINIO_ENDPOINT=minio MINIO_PORT=9000 MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin MINIO_BUCKET=uploads MINIO_USE_SSL=false # Keycloak (compose + api-gateway; PUBLIC_URL = browser-reachable) # keycloak-init creates realm + clients on first deploy. Must set KEYCLOAK_ADMIN_CLIENT_SECRET. KEYCLOAK_ADMIN=admin KEYCLOAK_ADMIN_PASSWORD=STAGING_KEYCLOAK_PASSWORD_CHANGE_ME KEYCLOAK_URL=http://keycloak:8080 # Use https://auth.getlinkzen.com after adding auth A record and running certbot KEYCLOAK_PUBLIC_URL=https://auth.getlinkzen.com KEYCLOAK_ISSUER_URI=https://auth.getlinkzen.com/realms/toolsplatform KEYCLOAK_REALM=toolsplatform KEYCLOAK_CLIENT_ID=api-gateway KEYCLOAK_CLIENT_SECRET= KEYCLOAK_ADMIN_CLIENT_ID=toolsplatform-admin # REQUIRED for register/user management. Generate: openssl rand -base64 32 KEYCLOAK_ADMIN_CLIENT_SECRET=ZfhqZxgxMpRXvx0oFhzdGNd5rIyI0V5n KEYCLOAK_USER_CLIENT_ID=toolsplatform-users # Auth / security JWT_ACCESS_TOKEN_TTL=15m JWT_REFRESH_TOKEN_TTL=7d JWT_REAUTH_WINDOW=5m RATE_LIMIT_LOGIN_MAX=5 RATE_LIMIT_LOGIN_WINDOW=60000 RATE_LIMIT_REGISTER_MAX=3 RATE_LIMIT_REGISTER_WINDOW=60000 IP_HASH_SALT=staging-salt-change-for-production RATE_LIMIT_GLOBAL_MAX=300 # Per-tier API rate limits (req/min); seed uses these, runtime reads from AppConfig # RATE_LIMIT_GUEST=60 # RATE_LIMIT_FREE=120 # RATE_LIMIT_DAYPASS=180 # RATE_LIMIT_PRO=400 # Admin + frontend URLs ADMIN_ROLE=platform-admin ADMIN_DASHBOARD_ENABLED=true FRONTEND_BASE_URL=https://app.getlinkzen.com FRONTEND_PORT=3000 # Frontend uses NEXT_PUBLIC_API_BASE_URL in browser (lib/api.ts, config/constants.ts). # MUST be the public domain (same origin). Traefik routes /api to the API. # Do NOT set to http://SERVER_IP:4000 or API will receive wrong paths (POST /app, /api/route) and return 404. NEXT_PUBLIC_API_BASE_URL=https://app.getlinkzen.com NEXT_PUBLIC_API_URL=https://app.getlinkzen.com NEXT_PUBLIC_KEYCLOAK_URL=https://auth.getlinkzen.com # Next.js Server Actions: persistent encryption key (base64). REQUIRED for production/staging to avoid # "Failed to find Server Action" and 504 after redeploys. Generate once, keep the same forever (all deploys): # node -e "console.log(require('crypto').randomBytes(32).toString('base64'))" NEXT_SERVER_ACTIONS_ENCRYPTION_KEY= # Tier 2 (022) – fallback only; DB is source of truth. See docs/runtime-configuration-implementation.md §1.4 FEATURE_ADS_ENABLED=false # Per-tier ads level: full | reduced | none (used when key missing in DB or for seed) ADS_GUEST_LEVEL=full ADS_FREE_LEVEL=reduced ADS_DAYPASS_LEVEL=none ADS_PRO_LEVEL=none FEATURE_PAYMENTS_ENABLED=false FEATURE_PREMIUM_TOOLS_ENABLED=true FEATURE_REGISTRATION_ENABLED=true RETENTION_GUEST_HOURS=1 RETENTION_FREE_HOURS=720 RETENTION_DAY_PASS_HOURS=720 RETENTION_PRO_HOURS=4320 ENABLE_SCHEDULED_CLEANUP=true DAY_PASS_PRICE_USD=2.99 PRO_MONTHLY_PRICE_USD=9.99 PRO_YEARLY_PRICE_USD=99.99 # Processing services (Docker service names) # Production (e.g. 8GB host): increase Stirling-PDF container memory in docker-compose.yml # (e.g. deploy.resources.limits.memory: 1g or 2g) for PDF→EPUB and heavy conversions. STIRLING_PDF_URL=http://stirling-pdf:8080 IMAGOR_URL=http://imagor:8000 REMBG_URL=http://rembg:7000 LANGUAGETOOL_URL=http://languagetool:8010 # Email (Resend) – deploy script uses RESEND_API_KEY from backend/.env if present; otherwise adds placeholder RESEND_API_KEY=re_dummy_staging_placeholder # Always enable email on staging (script ensures this) EMAIL_ENABLED=true # Paddle (sandbox for staging) – webhook URL: https://app.getlinkzen.com/api/v1/webhooks/paddle FEATURE_PADDLE_ENABLED=true PADDLE_VENDOR_ID=47301 PADDLE_API_KEY=pdl_sdbx_apikey_01kgjbr76ej9557epyqhkqctb2_aCTHTA3gSVCms9BEzVMCbt_Aef PADDLE_WEBHOOK_SECRET=ntfset_01kgjc7kwad46116eaba5hra6d PADDLE_ENVIRONMENT=sandbox NEXT_PUBLIC_PADDLE_ENVIRONMENT=sandbox NEXT_PUBLIC_PADDLE_CLIENT_TOKEN=test_a6db5d833f9caf2762d3266edf3 NEXT_PUBLIC_PADDLE_PRICE_ID_DAY_PASS=pri_01kgjcdd3jbw444yd2v48cswj5 NEXT_PUBLIC_PADDLE_PRICE_ID_PRO_MONTHLY=pri_01kgjcfhbshf3yv1s7k0qsx42q NEXT_PUBLIC_PADDLE_PRICE_ID_PRO_YEARLY=pri_01kgjcjg9rh0jkymqgjndk6tb9 # Worker WORKER_CONCURRENCY=3 # Phase 10 monitoring (Grafana admin password; used when docker-compose.monitoring.yml is used) GRAFANA_PASSWORD=staging-grafana-change-me