Files
OpenFrame/.env.example
T
yusufipk 880d0ac0fa feat: chunked (S3 multipart) uploads for R2/S3 video backend
Self-hosted instances on the R2/S3 backend could only upload a video as a
single PUT, which fails behind a Cloudflare proxy/tunnel (100MB request-body
cap) and is capped at 5GiB with no resilience. Bunny already avoids this via
tus; this brings the R2/S3 path to parity.

Files larger than a threshold (default 90MiB) are now split into parts
(default 32MiB, min 5MiB) and uploaded directly browser->R2 via presigned
UploadPart URLs, then reassembled server-side with CompleteMultipartUpload.
Each request stays under the 100MB cap, lifts the size ceiling well past
5GiB, and adds per-chunk retry. Files at/under the threshold keep the
existing single-PUT path unchanged. Bunny path is untouched.

Thresholds are env-overridable via OPENFRAME_R2_MULTIPART_THRESHOLD_BYTES
and OPENFRAME_R2_MULTIPART_PART_SIZE_BYTES.

Verified end-to-end against real Cloudflare R2 and a local MinIO behind an
nginx 90MB cap (single 141MB PUT 413s on master; 32MB parts pass here).

Closes #22
2026-07-10 19:19:07 +07:00

148 lines
6.9 KiB
Bash

# 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"
# Max size per uploaded video file in bytes (default 5GB if unset)
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"
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="[email protected]"
SMTP_PASSWORD="your-app-specific-password"
SMTP_FROM="[email protected]"
# ============================================================================
# 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., "[email protected],[email protected]"
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_..."