# ============================================================================= # env.example (committed template — copy to .env and fill in real values) # Mirrors the keys used in production. See docs/ACCOUNTS_AND_SERVICES.md for # where to obtain each value, and DEPLOYMENT.md for the deploy steps. # ============================================================================= ## OpenAI (meme caption suggestions) -- REQUIRED OPENAI_API_KEY=your-openai-api-key ## Giphy (GIF search) -- REQUIRED GIPHY_API_KEY=your-giphy-api-key ## Tenor (GIF search) -- OPTIONAL (leave blank; Giphy covers search) TENOR_API_KEY= # auto = use GPT-4o vision when OPENAI_API_KEY is set, else fall back to local BLIP. # Options: auto | openai | huggingface GIF_SUGGESTION_ENGINE=auto # Local BLIP fallback model. Keep in sync with the checkpoint baked in the Dockerfile. BLIP_MODEL=Salesforce/blip-image-captioning-base ## Freemium limits -- OPTIONAL (sensible defaults applied if unset) # Master switch for monetization gates (daily meme cap + premium-only formats). # Keep false until the app's subscription UI is enabled, or free users get capped # with no way to upgrade. Abuse/cost rate limits below stay active regardless. FREEMIUM_ENABLED=false # Free users: max saved memes per day (premium = unlimited). FREE_DAILY_MEMES=10 # Hourly cap on AI suggestion/caption calls, per user, by tier. FREE_AI_PER_HOUR=30 PREMIUM_AI_PER_HOUR=200 # Per-minute cap on keyword GIF search / trending, per user. GIF_SEARCH_PER_MINUTE=60 # Output formats reserved for paying users (comma-separated). PREMIUM_ONLY_FORMATS=mp4 # Watermark free-tier exports (virality + upgrade incentive). Premium never watermarked. WATERMARK_ENABLED=false WATERMARK_TEXT=Meme Jockey ## GIF export performance -- OPTIONAL (tune if saves feel slow) # Cap/subsample frames of long GIFs (0 = unlimited). ~48 keeps saves snappy. MEME_MAX_FRAMES=0 # Cap the longest side in pixels before encoding (0 = no limit). e.g. 720. MEME_MAX_DIM=0 # Floyd-Steinberg dithering on GIF frames. 0 = off (faster, slight banding). MEME_GIF_DITHER=1 ## POSTGRESQL -- REQUIRED # Local dev (docker/podman compose): POSTGRES_HOST=localhost POSTGRES_PORT=5432 POSTGRES_DB=memes_dev POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} # Managed Postgres (e.g. Neon) — use the asyncpg-compatible SSL param: # DATABASE_URL=postgresql://user:pass@host/dbname?ssl=require ## Guest Account Configuration GUEST_MODE_ENABLED=false GUEST_PHONE_NUMBER=+10000000000 GUEST_OTP_CODE=000000 GUEST_TOKEN_TTL_SECONDS=3600 GUEST_DATA_QUOTA_MB=200 GUEST_DATA_RETENTION_HOURS=24 ## Fast2SMS (OTP delivery via SMS) -- OPTIONAL # Leave blank: OTP is printed to logs ([DEV SMS] to=… otp=…) instead of texted, # so login still works in dev (read the code from the logs). If SET, the key must # be valid AND the Fast2SMS wallet funded / account KYC-complete, or sends fail # with HTTP 502 "Couldn't send the OTP" and login breaks. See auth/sms_provider.py. FAST2SMS_API_KEY= # Most Indian Fast2SMS accounts must use the DLT template route (the bare "otp" # route returns HTTP 400 unless explicitly enabled). Set BOTH from your approved # DLT template to send real OTP SMS; leave blank to try the plain otp route. # FAST2SMS_SENDER_ID = your approved 3-6 char DLT header/sender ID # FAST2SMS_DLT_TEMPLATE_ID = the numeric ID of your approved OTP template # The template must have one variable for the code, e.g. # "Your Meme Jockey OTP is {#var#}. Do not share it." FAST2SMS_SENDER_ID= FAST2SMS_DLT_TEMPLATE_ID= # Debug aid: when true, /auth/request-otp returns the raw Fast2SMS error to the # client (e.g. invalid key / no balance). Keep FALSE in production. OTP_DEBUG=false ## Object storage — MinIO (local) or any S3-compatible (e.g. Backblaze B2) -- REQUIRED MINIO_ENDPOINT=localhost:9000 MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minioadmin MINIO_SECURE=false MINIO_BUCKET=memes-dev # MinIO root creds (local dev only!) MINIO_ROOT_USER=minioadmin MINIO_ROOT_PASSWORD=minioadmin ## Auth / JWT -- REQUIRED (generate long random strings) JWT_SECRET=change-me-256-bit-secret JWT_REFRESH_SECRET=change-me-another-secret JWT_ISS=meme-be JWT_AUD=app ACCESS_TTL_SECONDS=900 REFRESH_TTL_SECONDS=2592000 ARGON2_PEPPER=optional-static-pepper ## Google Sign-In -- OPTIONAL (OAuth 2.0 Web client ID; token audience) GOOGLE_WEB_CLIENT_ID= ## Razorpay (subscriptions) -- REQUIRED for in-app purchases (optional otherwise) # Test-mode keys look like rzp_test_...; live keys rzp_live_... (live needs a # KYC-activated account). Both id + secret MUST come from the same key pair and # the same mode, or Razorpay rejects them with "Authentication failed". # Verify config without a full checkout: GET /subscriptions/config-check # curl -H "X-Admin-Secret: " https:///subscriptions/config-check RAZORPAY_KEY_ID= RAZORPAY_KEY_SECRET= # Debug aid: when true, /subscriptions/order returns the raw Razorpay error to # the client so payment failures can be diagnosed from the app. Keep FALSE in # production (real users would see raw internals). PAYMENTS_DEBUG=false ## Environment APP_ENV=development ADMIN_SECRET=change-me-admin-secret # Set CONTAINER_ENV=true when running in Docker/HF Space if APP_ENV isn't # "production" — switches the DB engine to the asyncpg driver. Optional. CONTAINER_ENV=false ## API Configuration API_BASE_URL=http://localhost:8000