# Copy this file to .env and fill in your values # ============================================================================ # DATABASE # ============================================================================ # PostgreSQL connection string DATABASE_URL="postgresql://user:password@localhost:5432/openframe?schema=public" # Enable PostgreSQL pool connect/acquire debug logs (set to "true" only when debugging) DB_POOL_DEBUG="false" # ============================================================================ # AUTHENTICATION - NextAuth.js # ============================================================================ NEXTAUTH_URL="http://localhost:3000" NEXTAUTH_SECRET="your-secret-key-here-generate-with-openssl-rand-base64-32" # ============================================================================ # OPTIONAL SELF-HOSTING FEATURE FLAGS # ============================================================================ OPENFRAME_ENABLE_STRIPE="true" OPENFRAME_ENABLE_BUNNY_UPLOADS="true" # Self-hosted direct video uploads to your S3-compatible storage (R2_* vars below). # Mutually exclusive with Bunny: set OPENFRAME_ENABLE_BUNNY_UPLOADS=false when enabling this. OPENFRAME_ENABLE_S3_VIDEO_UPLOADS="false" # An absolute per-file ceiling for uploaded videos, in bytes. Leave it unset on a # billed instance: the ceiling is then 80% of the account's own storage quota, # leaving room for the renditions the provider derives from the file. When set, # the lower of the two applies. Instances running without billing have no quota # to divide and fall back to 5 GiB. # OPENFRAME_MAX_VIDEO_UPLOAD_BYTES="5368709120" # Files larger than this use chunked (S3 multipart) uploads instead of a single PUT. # Default 90MiB keeps each request under the common 100MB Cloudflare proxy/tunnel cap. # Lower it if your proxy enforces a stricter request-body limit. OPENFRAME_R2_MULTIPART_THRESHOLD_BYTES="94371840" # Size of each multipart chunk in bytes (default 32MiB, minimum 5MiB). OPENFRAME_R2_MULTIPART_PART_SIZE_BYTES="33554432" # Direct browser uploads require bucket CORS allowing PUT from your app origin(s). # Run once after creating the bucket: bun run r2:configure-cors # Or set CORS manually in Cloudflare R2 -> bucket -> Settings -> CORS policy. OPENFRAME_REQUIRE_INVITE_CODE="true" # Acquisition attribution and funnel events, read back on /admin/growth. Off by # default: the rows only pay for themselves if you are running a signup funnel. # Everything is written to this instance's own database and sent nowhere. OPENFRAME_ENABLE_ANALYTICS="false" # The date the cardless trial replaced the card-first one, as an ISO date. Set it # to have /admin/growth compare signup-to-paid either side of the switchover; # leave it empty and that section is simply not shown. OPENFRAME_CARDLESS_TRIAL_LAUNCHED_AT="" SELF_HOSTED_AUTO_CREATE_BUCKET="false" # ============================================================================ # OAUTH PROVIDERS # ============================================================================ # Google OAuth # Create credentials at: https://console.cloud.google.com/apis/credentials GOOGLE_CLIENT_ID="your-google-client-id.apps.googleusercontent.com" GOOGLE_CLIENT_SECRET="your-google-client-secret" # GitHub OAuth # Create credentials at: https://github.com/settings/developers GITHUB_CLIENT_ID="your-github-client-id" GITHUB_CLIENT_SECRET="your-github-client-secret" # ============================================================================ # FILE STORAGE # ============================================================================ # Cloudflare R2 (S3-compatible) # For self-hosted S3-compatible storage such as MinIO, set: # - R2_ENDPOINT (server/container endpoint) # - R2_PRESIGN_ENDPOINT (browser-reachable endpoint for presigned upload URLs) # - R2_PUBLIC_BASE_URL (public base URL for served files) R2_ACCOUNT_ID="your-account-id" R2_ENDPOINT="" R2_PRESIGN_ENDPOINT="" R2_PUBLIC_BASE_URL="" R2_ACCESS_KEY_ID="your-access-key" R2_SECRET_ACCESS_KEY="your-secret-key" R2_BUCKET_NAME="openframe" # R2 orphan cleanup configuration (script + external cron; app runtime does not schedule this) # R2_ORPHAN_CLEANUP_CRON="*/15 * * * *" # Scheduled delete mode: # */15 * * * * cd /home/yusuf/Programming/OpenFrame && bun run r2:cleanup-orphans # ============================================================================ # EMAIL & NOTIFICATIONS # ============================================================================ # Telegram bot token from @BotFather — shared across all users # Users only need to provide their own Chat ID in Settings TELEGRAM_BOT_TOKEN="your-telegram-bot-token" SMTP_HOST="smtp.gmail.com" SMTP_PORT="587" SMTP_USER="your-email@gmail.com" SMTP_PASSWORD="your-app-specific-password" SMTP_FROM="noreply@openframe.dev" # ============================================================================ # APPLICATION # ============================================================================ NEXT_PUBLIC_APP_URL="http://localhost:3000" # development | production | test NODE_ENV="development" # Disable all app-level rate limiting for local testing. Leave unset to keep rate limiting enabled. # Accepted truthy values: "true", "1", "yes", "on" # DISABLE_RATE_LIMIT="true" # Trusted reverse proxy mode — controls which headers getClientIp() trusts for rate limiting. # Set this only when you have confirmed that your proxy strips/overwrites client-supplied headers. # cloudflare — trust cf-connecting-ip (Cloudflare edge in front of the origin) # nginx — trust x-real-ip / last x-forwarded-for (Nginx real_ip_header with set_real_ip_from) # Leave unset for local dev or when no trusted proxy is in place. TRUSTED_PROXY_MODE="cloudflare" # Admin emails for accessing the /admin panel (comma separated list) # e.g., "yusuf@example.com,admin@example.com" ADMIN_EMAILS="" # Invite code for internal registration (required to sign up) # Generate a secure code for your team INVITE_CODE="your-secret-invite-code" # Enable debug logging # DEBUG="openframe:*" # ============================================================================ # DOWNLOADS # ============================================================================ # Project bulk-download manifest limits (GET /api/projects/[projectId]/download). # Caps how many files and total known bytes a single manifest may enumerate. OPENFRAME_PROJECT_DOWNLOAD_MAX_FILES="250" # 20 GiB in bytes (20 * 1024 * 1024 * 1024) OPENFRAME_PROJECT_DOWNLOAD_MAX_BYTES="21474836480" # Comma-separated hostnames allowed for direct (non-proxied) version download URLs # in manifests and the video page. Bunny CDN host is always allowed when configured. # Example: "cdn.example.com,files.example.com" NEXT_PUBLIC_DIRECT_DOWNLOAD_ALLOWED_HOSTS="" # ============================================================================ # VIDEO PROCESSING # ============================================================================ # Bunny Stream for direct video uploads BUNNY_STREAM_API_KEY="your-stream-library-api-key" BUNNY_STREAM_LIBRARY_ID="your-library-id" # Bunny Core API key (account-level) used to enforce KeepOriginalFiles/ExposeOriginals on the library BUNNY_API_KEY="your-account-api-key" # Bunny Stream CDN base URL (for HLS streaming) BUNNY_CDN_URL="your-url-to-bunny-cdn" NEXT_PUBLIC_BUNNY_CDN_URL="your-url-to-bunny-cdn" # Bunny orphan cleanup configuration (script + external cron; app runtime does not schedule this) # Grace period is fixed at 24 hours in the script. # */15 * * * * cd /home/yusuf/Programming/OpenFrame && bun run bunny:cleanup-orphans # ============================================================================ # BILLING # ============================================================================ # Stripe recurring price used for paid accounts STRIPE_SECRET_KEY="sk_test_..." STRIPE_PRICE_ID="prod_UBWFFKZC3d80z4" STRIPE_WEBHOOK_SECRET="whsec_..."