diff --git a/.env.example b/.env.example index 9718fc6156..0754d8ebaa 100644 --- a/.env.example +++ b/.env.example @@ -1,198 +1,450 @@ -# Configuration reference: http://docs.postiz.com/configuration/reference +# ============================================================================== +# CROVE POST / POSTIZ - COMPLETE CONFIGURATION REFERENCE (.env.example) +# Documentation: https://docs.postiz.com / https://docs.crove.com +# ============================================================================== -# === Required Settings -DATABASE_URL="postgresql://postiz-user:postiz-password@localhost:5432/postiz-db-local" +# ============================================================================== +# 1. Required Core Settings (Database, Redis, Security) +# ============================================================================== +# PostgreSQL Connection URL (PgBouncer pooler mode or direct) +DATABASE_URL="postgresql://postiz-user:postiz-password@localhost:5432/postiz-db-local?schema=public&sslmode=prefer" +# Direct PostgreSQL Connection URL (Used for Prisma migrations and direct connections) +DATABASE_DIRECT_URL="postgresql://postiz-user:postiz-password@localhost:5432/postiz-db-local?schema=public&sslmode=prefer" +# Node TLS verification (set to 0 for self-signed certificates or Supabase poolers) +NODE_TLS_REJECT_UNAUTHORIZED="0" +# Redis Cache & Session Store URL REDIS_URL="redis://localhost:6379" -JWT_SECRET="random string for your JWT secret, make it long" +# Cryptographic secret for signing JWT sessions (must be 32+ random characters) +JWT_SECRET="replace-with-a-random-secure-string-min-32-chars" +# Crove internal JWT signing secret +CROVE_POSTIZ_JWT_SECRET="replace-with-a-crove-specific-jwt-secret" -# === This needs to be exactly the URL you're accessing Postiz on +# ============================================================================== +# 2. Application URLs & Routing +# ============================================================================== +# Public URL of the frontend application FRONTEND_URL="http://localhost:4200" +# Main website URL (for navigation links and landing pages) +MAIN_URL="http://localhost:4200" +# Public URL of the backend API (accessible by browser clients) NEXT_PUBLIC_BACKEND_URL="http://localhost:3000" +# Internal URL of the backend API (for server-to-server calls within Docker network) BACKEND_INTERNAL_URL="http://localhost:3000" +# Public URL for MCP (Model Context Protocol) tool endpoints +MCP_URL="http://localhost:3000/api" +# Backend HTTP server listening port (default: 3000) +PORT="3000" +# Multi-tenant / general platform mode flag (required: true) +IS_GENERAL="true" +# Disable public self-registration (true: invite/SSO only, false: open registration) +DISABLE_REGISTRATION="false" +# Disallow email addresses with plus signs (e.g. user+test@example.com) +DISALLOW_PLUS="true" +# Run internal cron jobs for post scheduling and analytics synchronization +RUN_CRON="true" +# Run only MCP server without Web API (optional, default: false) +MCP_ONLY="false" +# Disable Secure flag on cookies for non-HTTPS local development (optional) +NOT_SECURED="false" +# Custom mobile application URL scheme for auth callbacks (optional) +MOBILE_APP_SCHEME="postiz://auth/callback" +# Hourly rate limit for public API requests per organization +API_LIMIT="100" + +# ============================================================================== +# 3. Temporal Orchestration (Background Workflows & Post Scheduling) +# ============================================================================== +# Temporal server gRPC address +TEMPORAL_ADDRESS="localhost:7233" +# Temporal namespace (default: default) +TEMPORAL_NAMESPACE="default" +# Enable TLS for Temporal connection (true/false) +TEMPORAL_TLS="false" +# Temporal Cloud API key (optional, for Temporal Cloud) +TEMPORAL_API_KEY="temporal-cloud-api-key-sample" +# Task queues to exclude from worker polling (comma-separated, optional) +EXCLUDE_QUEUE="" +# Concurrency divider for worker task allocation (default: 1) +WORKER_CONCURRENCY_DIVIDER="1" -## Remember to set your public internet IP address in the allow-list for the API token. -## -## Cloudflare is currently required to save things like social media avatars for accounts. -CLOUDFLARE_ACCOUNT_ID="your-account-id" -CLOUDFLARE_ACCESS_KEY="your-access-key" -CLOUDFLARE_SECRET_ACCESS_KEY="your-secret-access-key" -CLOUDFLARE_BUCKETNAME="your-bucket-name" -CLOUDFLARE_BUCKET_URL="https://your-bucket-url.r2.cloudflarestorage.com/" +# ============================================================================== +# 4. Storage Providers (Cloudflare R2 or Local Disk) +# ============================================================================== +# Storage provider type: "cloudflare" (recommended) or "local" +STORAGE_PROVIDER="local" +# Cloudflare Account ID for R2 Object Storage +CLOUDFLARE_ACCOUNT_ID="your-cloudflare-account-id-sample" +# Cloudflare R2 Access Key ID +CLOUDFLARE_ACCESS_KEY="your-cloudflare-r2-access-key-sample" +# Cloudflare R2 Secret Access Key +CLOUDFLARE_SECRET_ACCESS_KEY="your-cloudflare-r2-secret-access-key-sample" +# Cloudflare R2 Bucket Name +CLOUDFLARE_BUCKETNAME="crove-media" +# Cloudflare R2 Public CDN / Custom Domain URL +CLOUDFLARE_BUCKET_URL="https://media.example.com" +# Cloudflare R2 Region (default: auto) CLOUDFLARE_REGION="auto" +# Local filesystem upload directory path (when STORAGE_PROVIDER="local") +UPLOAD_DIRECTORY="/uploads" +# Public URL path prefix for uploaded files +NEXT_PUBLIC_UPLOAD_DIRECTORY="/uploads" +# Public static asset directory path +NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY="/uploads" +# Restrict upload domain validation (optional) +RESTRICT_UPLOAD_DOMAINS="" +# Disable client-side image compression before upload (true/false) +DISABLE_IMAGE_COMPRESSION="false" + +# ============================================================================== +# 5. Email Delivery & Transactional Notification (Nodemailer SMTP / Resend) +# ============================================================================== +# Email provider: "nodemailer" or "resend" +EMAIL_PROVIDER="nodemailer" +# Default sender email address +EMAIL_FROM_ADDRESS="support@example.com" +# Default sender display name +EMAIL_FROM_NAME="Crove Post" +# SMTP server hostname (e.g. Amazon SES, SendGrid, Mailgun, Postmark) +EMAIL_HOST="email-smtp.ap-southeast-1.amazonaws.com" +# SMTP server port (587 for TLS, 465 for SSL) +EMAIL_PORT="587" +# Require TLS connection for SMTP (true/false) +EMAIL_SECURE="false" +# SMTP authentication username +EMAIL_USER="AKIAEXAMPLEUSER12345" +# SMTP authentication password +EMAIL_PASS="SAMPLE_SMTP_PASSWORD_SECRET" +# Resend API Key (used when EMAIL_PROVIDER="resend") +RESEND_API_KEY="re_sample_resend_api_key_123456" + +# ============================================================================== +# 6. Newsletter Providers (Beehiiv / Listmonk) +# ============================================================================== +# Beehiiv API key for newsletter subscription sync +BEEHIIVE_API_KEY="beehiiv_api_key_sample" +# Beehiiv publication ID +BEEHIIVE_PUBLICATION_ID="pub_sample_publication_id" +# Listmonk instance domain URL +LISTMONK_DOMAIN="https://newsletter.example.com" +# Listmonk API username +LISTMONK_USER="api_user" +# Listmonk API key +LISTMONK_API_KEY="listmonk_api_key_sample" +# Listmonk default list ID +LISTMONK_LIST_ID="1" +# Listmonk welcome email template ID +LISTMONK_WELCOME_TEMPLATE_ID="1" -# === Common optional Settings +# ============================================================================== +# 7. Brand Customization & White-labeling Engine +# ============================================================================== +# Full application brand name +BRAND_NAME="Crove Post" +# Short application brand name +BRAND_SHORT_NAME="Crove" +# Brand meta description +BRAND_DESCRIPTION="The AI-powered multi-channel social media management platform" +# Company legal entity name +BRAND_COMPANY_NAME="Crove Corporation" +# Light mode logo image URL +BRAND_LOGO_URL="https://example.com/logo.svg" +# Dark mode logo image URL +BRAND_LOGO_DARK_URL="https://example.com/logo-dark.svg" +# App icon URL +BRAND_ICON_URL="https://example.com/icon.png" +# Favicon URL +BRAND_FAVICON_URL="/favicon.ico" +# Transactional email logo URL +BRAND_EMAIL_LOGO_URL="https://example.com/email-logo.png" +# Primary brand theme hex color +BRAND_PRIMARY_COLOR="#7c3aed" +# Main landing page website URL +BRAND_WEBSITE_URL="https://example.com" +# Default email domain for support fallbacks +BRAND_DEFAULT_EMAIL_DOMAIN="example.com" +# Community support / Discord invite URL +BRAND_SUPPORT_URL="https://discord.gg/example" +# Documentation site URL +BRAND_DOCS_URL="https://docs.example.com" +# Source repository URL (AGPL-3.0 requirement) +BRAND_SOURCE_URL="https://github.com/DOS/Crove-Post" +# Terms of Service URL +BRAND_TERMS_URL="/terms" +# Privacy Policy URL +BRAND_PRIVACY_URL="/privacy" +# Dedicated support email address +BRAND_SUPPORT_EMAIL="support@example.com" +# Chrome Web Store extension URL +BRAND_EXTENSION_STORE_URL="https://chromewebstore.google.com/detail/sample-extension-id" +# User guide / video tutorial URL +BRAND_TUTORIAL_URL="https://example.com/tutorials" +# Affiliate program URL +BRAND_AFFILIATE_URL="https://example.com/affiliates" -## This is a dummy key, you must create your own from Resend. -## If this variable exists, user activation is required. -## If it is commented out, users are activated automatically. -#RESEND_API_KEY="RzeTwHijvxvPUerScFcenUZUALuQJzSaGSMJ" -#EMAIL_FROM_ADDRESS="" -#EMAIL_FROM_NAME="" -#DISABLE_REGISTRATION=false +# ============================================================================== +# 8. Single Sign-On (Generic OAuth 2.0 / DOS ID via PKCE Bridge) +# ============================================================================== +# Enable Generic OAuth 2.0 / OIDC authentication provider +POSTIZ_GENERIC_OAUTH="true" +# Button display label on login and registration pages +NEXT_PUBLIC_POSTIZ_OAUTH_DISPLAY_NAME="DOS ID" +# Logo icon URL for the SSO provider button +NEXT_PUBLIC_POSTIZ_OAUTH_LOGO_URL="/icons/generic-oauth.svg" +# Central IdP / PKCE Bridge base URL +POSTIZ_OAUTH_URL="https://api.dos.me" +# OAuth 2.0 authorization endpoint URL +POSTIZ_OAUTH_AUTH_URL="https://api.dos.me/sso/authorize" +# OAuth 2.0 token exchange endpoint URL +POSTIZ_OAUTH_TOKEN_URL="https://api.dos.me/sso/token" +# OIDC userinfo profile endpoint URL +POSTIZ_OAUTH_USERINFO_URL="https://api.dos.me/sso/userinfo" +# OAuth 2.0 Client ID registered on the IdP +POSTIZ_OAUTH_CLIENT_ID="crove-postiz" +# OAuth 2.0 Client Secret +POSTIZ_OAUTH_CLIENT_SECRET="sample_oauth_client_secret_string" +# Requested OAuth scopes +POSTIZ_OAUTH_SCOPE="openid profile email organizations offline_access" -# Where will social media icons be saved - local or cloudflare. -STORAGE_PROVIDER="local" +# ============================================================================== +# 9. Ecosystem & First-Party Bootstrap Integration (DOS.AI / DOS-Me / Crove OS) +# ============================================================================== +# Enable ecosystem-level 2-tier sync and headless provisioning (true/false) +ENABLE_ECOSYSTEM_SYNC="false" +# Ecosystem provider identifier +ECOSYSTEM_PROVIDER="dos" +# Central ecosystem API endpoint for outbound entity delegation +ECOSYSTEM_API_URL="https://api.dos.me" +# HMAC-SHA256 secret for verifying inbound webhook events (/api/webhooks/dos-org-sync) +DOS_SYNC_WEBHOOK_SECRET="sample_dos_sync_webhook_secret_key" +# Legacy webhook secret alias +DOS_WEBHOOK_SECRET="sample_dos_webhook_secret_key" +# Bearer secret token for First-Party Headless Provisioning (/v1/provision) +PROVISIONING_SECRET_KEY="sample_provisioning_secret_bearer_key" +# Legacy provisioning secret alias +DOS_PROVISIONING_SECRET="sample_dos_provisioning_secret_key" +# Internal service authentication API key +INTERNAL_API_KEY="sample_internal_service_api_key" +# First-Party OAuth Client ID for signed M2M bootstrap +CROVE_POST_CLIENT_ID="crove-post-client-id-sample" +# First-Party OAuth Client Secret +CROVE_POST_CLIENT_SECRET="sample_crove_post_client_secret" +# HMAC secret for signed M2M bootstrap requests (/api/internal/first-party/bootstrap) +CROVE_POST_BOOTSTRAP_SIGNING_SECRET="sample_bootstrap_hmac_signing_secret" + +# ============================================================================== +# 10. AI Providers & OpenAI-Compatible Gateway Settings (BYOK / DOS.AI / OpenAI) +# ============================================================================== +# OpenAI API Key or OpenAI-compatible Gateway Bearer token +OPENAI_API_KEY="sk-proj-sample-openai-api-key-or-gateway-token" +# Custom OpenAI-compatible Base URL (e.g. DOS.AI AI Gateway, LiteLLM, vLLM, DeepSeek, Ollama) +# Leave blank for official OpenAI (https://api.openai.com/v1) +OPENAI_BASE_URL="https://api.dos.ai/v1" +# Default LLM model identifier for text generation and social post drafting +OPENAI_MODEL_NAME="gpt-4.1" +# Default image generation model identifier +OPENAI_IMAGE_MODEL="chatgpt-image-latest" +# OAuth client ID for ChatGPT app integration (optional) +OPENAI_OAUTH_CLIENT_ID="sample_openai_oauth_client_id" +# OpenAI App domain verification challenge string (optional) +OPENAI_APP_CHALLANGE="sample_openai_app_challenge_token" +# Tavily AI Search API Key for real-time web grounding in agent workflows (optional) +TAVILY_API_KEY="tvly-sample_tavily_api_key" +# Fal.ai API Key for image and video rendering generation (optional) +FAL_KEY="sample_fal_ai_api_key" +# Chatbase chatbot embedding token (optional) +CHATBASE_TOKEN="sample_chatbase_token" +# Agent Media SSO signing key (optional) +AGENT_MEDIA_SSO_KEY="sample_agent_media_sso_key" + +# ============================================================================== +# 11. Social Media & Channel Integrations (28+ Providers) +# ============================================================================== +# --- X (Twitter) --- +X_URL="https://api.x.com" +X_API_KEY="sample_x_consumer_api_key" +X_API_SECRET="sample_x_consumer_api_secret" +DISABLE_X_ANALYTICS="false" +STRIP_LINKS_FROM_X_POSTS="false" + +# --- LinkedIn --- +LINKEDIN_CLIENT_ID="sample_linkedin_client_id" +LINKEDIN_CLIENT_SECRET="sample_linkedin_client_secret" -# Your upload directory path if you host your files locally, otherwise Cloudflare will be used. -#UPLOAD_DIRECTORY="" - -# Your upload directory path if you host your files locally, otherwise Cloudflare will be used. -#NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY="" - -# Sign in with Apple (login provider) -# APPLE_BUNDLE_ID: iOS app bundle id (native mobile sign-in) -# APPLE_SERVICE_ID / TEAM_ID / KEY_ID / PRIVATE_KEY: web sign-in (Services ID + .p8 key) -APPLE_BUNDLE_ID="" -APPLE_SERVICE_ID="" -APPLE_TEAM_ID="" -APPLE_KEY_ID="" -APPLE_PRIVATE_KEY="" - -# Social Media API Settings -X_URL="" -X_API_KEY="" -X_API_SECRET="" -LINKEDIN_CLIENT_ID="" -LINKEDIN_CLIENT_SECRET="" -REDDIT_CLIENT_ID="" -REDDIT_CLIENT_SECRET="" -GITHUB_CLIENT_ID="" -GITHUB_CLIENT_SECRET="" -# Sign in with Apple, client id is the Services ID identifier -APPLE_CLIENT_ID="" -APPLE_TEAM_ID="" -APPLE_KEY_ID="" -APPLE_PRIVATE_KEY="" -BEEHIIVE_API_KEY="" -BEEHIIVE_PUBLICATION_ID="" -LISTMONK_DOMAIN="" -LISTMONK_USER="" -LISTMONK_API_KEY="" -LISTMONK_LIST_ID="" -THREADS_APP_ID="" -THREADS_APP_SECRET="" -FACEBOOK_APP_ID="" -FACEBOOK_APP_SECRET="" -YOUTUBE_CLIENT_ID="" -YOUTUBE_CLIENT_SECRET="" -TIKTOK_CLIENT_ID="" -TIKTOK_CLIENT_SECRET="" -TIKTOK_BUSINESS_CLIENT_ID="" -TIKTOK_BUSINESS_CLIENT_SECRET="" -PINTEREST_CLIENT_ID="" -PINTEREST_CLIENT_SECRET="" -DRIBBBLE_CLIENT_ID="" -DRIBBBLE_CLIENT_SECRET="" -TUMBLR_CLIENT_ID="" -TUMBLR_CLIENT_SECRET="" -DISCORD_CLIENT_ID="" -DISCORD_CLIENT_SECRET="" -DISCORD_BOT_TOKEN_ID="" -SLACK_ID="" -SLACK_SECRET="" -SLACK_SIGNING_SECRET="" +# --- Reddit --- +REDDIT_CLIENT_ID="sample_reddit_client_id" +REDDIT_CLIENT_SECRET="sample_reddit_client_secret" + +# --- GitHub --- +GITHUB_CLIENT_ID="sample_github_oauth_client_id" +GITHUB_CLIENT_SECRET="sample_github_oauth_client_secret" + +# --- Threads (Meta) --- +THREADS_APP_ID="1234567890123456" +THREADS_APP_SECRET="sample_threads_app_secret_hex" + +# --- Facebook (Meta Graph API) --- +FACEBOOK_APP_ID="1234567890123456" +FACEBOOK_APP_SECRET="sample_facebook_app_secret_hex" + +# --- Instagram (Meta Graph API & Standalone) --- +INSTAGRAM_APP_ID="1234567890123456" +INSTAGRAM_APP_SECRET="sample_instagram_app_secret_hex" + +# --- YouTube & Google Business Messages (Google Cloud Platform) --- +YOUTUBE_CLIENT_ID="sample_google_client_id.apps.googleusercontent.com" +YOUTUBE_CLIENT_SECRET="sample_google_client_secret" +GOOGLE_GMB_CLIENT_ID="sample_google_client_id.apps.googleusercontent.com" +GOOGLE_GMB_CLIENT_SECRET="sample_google_client_secret" + +# --- TikTok (Consumer & Business API) --- +TIKTOK_CLIENT_ID="sample_tiktok_consumer_client_key" +TIKTOK_CLIENT_SECRET="sample_tiktok_consumer_client_secret" +TIKTOK_BUSINESS_CLIENT_ID="sample_tiktok_business_client_id" +TIKTOK_BUSINESS_CLIENT_SECRET="sample_tiktok_business_client_secret" + +# --- Pinterest --- +PINTEREST_CLIENT_ID="1234567" +PINTEREST_CLIENT_SECRET="sample_pinterest_client_secret_hex" + +# --- Discord --- +DISCORD_CLIENT_ID="123456789012345678" +DISCORD_CLIENT_SECRET="sample_discord_client_secret" +DISCORD_BOT_TOKEN_ID="sample_discord_bot_token_string" + +# --- Telegram --- +TELEGRAM_TOKEN="1234567890:SAMPLE_TELEGRAM_BOT_TOKEN" +TELEGRAM_BOT_NAME="Sample_Bot" + +# --- Twitch --- +TWITCH_CLIENT_ID="sample_twitch_client_id" +TWITCH_CLIENT_SECRET="sample_twitch_client_secret" + +# --- Kick --- +KICK_CLIENT_ID="sample_kick_client_id" +KICK_SECRET="sample_kick_client_secret" + +# --- Slack --- +SLACK_ID="1234567890.1234567890" +SLACK_SECRET="sample_slack_client_secret" +SLACK_SIGNING_SECRET="sample_slack_signing_secret" + +# --- Mastodon --- MASTODON_URL="https://mastodon.social" -MASTODON_CLIENT_ID="" -MASTODON_CLIENT_SECRET="" - -# Chrome Extension Settings (for cookie-based platform integrations like Skool) -EXTENSION_ID="" - -# Misc Settings -OPENAI_API_KEY="" -# OAuth client configured for the ChatGPT app. Only this client can receive -# verified user email claims for ChatGPT Enterprise domain restrictions. -OPENAI_OAUTH_CLIENT_ID="" -# MCP OAuth Dynamic Client Registration (RFC 7591) redirect-domain allowlist. -# When set (comma separated), POST /oauth/register only accepts redirect_uris -# whose host matches a listed domain or one of its subdomains; unset or empty -# means any client can self-register. Example locks DCR to the claude.ai -# connector (note: also blocks localhost callbacks, e.g. Claude Code - -# those clients can use the API-key /mcp/:id endpoints instead). -# DCR_VERIFIED_DOMAINS="claude.ai,claude.com" -NEXT_PUBLIC_DISCORD_SUPPORT="" -NEXT_PUBLIC_POLOTNO="" -# NOT_SECURED=false -API_LIMIT=30 # The limit of the public API hour limit - -# === Brand Customization & White-labeling Settings (Optional) -# Override Postiz branding at runtime without rebuilding Docker images. -# BRAND_NAME="Postiz" -# BRAND_SHORT_NAME="Postiz" -# BRAND_DESCRIPTION="The open-source social media management platform" -# BRAND_COMPANY_NAME="Postiz" -# BRAND_LOGO_URL="" -# BRAND_LOGO_DARK_URL="" -# BRAND_ICON_URL="" -# BRAND_FAVICON_URL="/favicon.ico" -# BRAND_EMAIL_LOGO_URL="" -# BRAND_PRIMARY_COLOR="#612BD3" -# BRAND_WEBSITE_URL="" # Defaults to MAIN_URL or FRONTEND_URL if not set -# BRAND_SUPPORT_URL="https://discord.gg/postiz" -# BRAND_DOCS_URL="https://docs.postiz.com" -# BRAND_SOURCE_URL="https://github.com/gitroomhq/postiz-app" # AGPL-3.0 requirement -# BRAND_TERMS_URL="/terms" -# BRAND_PRIVACY_URL="/privacy" -# BRAND_SUPPORT_EMAIL="" # Defaults to support@ -# BRAND_DEFAULT_EMAIL_DOMAIN="postiz.com" -# BRAND_EXTENSION_STORE_URL="" -# BRAND_TUTORIAL_URL="" - -# When connecting providers that take a self-hosted URL (WordPress, Mastodon, -# Lemmy, Listmonk, Bluesky PDS, etc.) Postiz fetches that URL server-side and -# blocks requests that resolve to private/internal/loopback/link-local IPs to -# prevent SSRF. Set to true ONLY if your Postiz instance must reach those -# services on a trusted private network (e.g. same Docker network or VPC). -# DISABLE_SSRF_PROTECTION=false - -# Payment settings -FEE_AMOUNT=0.05 -STRIPE_PUBLISHABLE_KEY="" -STRIPE_SECRET_KEY="" -STRIPE_SIGNING_KEY="" -STRIPE_SIGNING_KEY_CONNECT="" - -# Developer Settings -NX_ADD_PLUGINS=false -IS_GENERAL="true" # required for now -NEXT_PUBLIC_POSTIZ_OAUTH_DISPLAY_NAME="Authentik" -NEXT_PUBLIC_POSTIZ_OAUTH_LOGO_URL="https://raw.githubusercontent.com/walkxcode/dashboard-icons/master/png/authentik.png" -POSTIZ_GENERIC_OAUTH="false" -POSTIZ_OAUTH_URL="https://auth.example.com" -POSTIZ_OAUTH_AUTH_URL="https://auth.example.com/application/o/authorize" -POSTIZ_OAUTH_TOKEN_URL="https://auth.example.com/application/o/token" -POSTIZ_OAUTH_USERINFO_URL="https://authentik.example.com/application/o/userinfo" -POSTIZ_OAUTH_CLIENT_ID="" -POSTIZ_OAUTH_CLIENT_SECRET="" -# POSTIZ_OAUTH_SCOPE="openid profile email" # default values - -# Short Link Service Settings -# DUB_TOKEN="" # Your self-hosted Dub API token -# DUB_API_ENDPOINT="https://api.dub.co" # Your self-hosted Dub API endpoint -# DUB_SHORT_LINK_DOMAIN="dub.sh" # Your self-hosted Dub domain - -# SHORT_IO_SECRET_KEY="" # Your Short.io API secret key - -# KUTT_API_KEY="" # Your Kutt.it API key -# KUTT_API_ENDPOINT="https://kutt.it/api/v2" # Your self-hosted Kutt API endpoint -# KUTT_SHORT_LINK_DOMAIN="kutt.it" # Your self-hosted Kutt domain - -# LINK_DRIP_API_KEY="" # Your LinkDrip API key -# LINK_DRIP_API_ENDPOINT="https://api.linkdrip.com/v1/" # Your self-hosted LinkDrip API endpoint -# LINK_DRIP_SHORT_LINK_DOMAIN="dripl.ink" # Your self-hosted LinkDrip domain - -# Provider visibility / migration -# HIDDEN_PROVIDERS="tiktok" # comma-separated identifiers hidden from the add-channel screen -# MIGRATE_PROVIDERS="tiktok:tiktok-business" # comma-separated "old:new" pairs, a reconnect of "old" goes through "new" and the channel is migrated in place - -# ============================================================================== -# Ecosystem & Identity Provider Integration (Optional - disabled by default) -# ============================================================================== -# When running as a standalone open-source deployment, leave ENABLE_ECOSYSTEM_SYNC=false. -# When integrating into an ecosystem (e.g. Crove OS / DOS ID / centralized identity), -# set ENABLE_ECOSYSTEM_SYNC=true to enable headless provisioning and 2-tier sync webhooks. -# ENABLE_ECOSYSTEM_SYNC="false" -# ECOSYSTEM_PROVIDER="dos" -# DOS_SYNC_WEBHOOK_SECRET="" -# PROVISIONING_SECRET_KEY="" +MASTODON_CLIENT_ID="sample_mastodon_client_id" +MASTODON_CLIENT_SECRET="sample_mastodon_client_secret" + +# --- Dribbble --- +DRIBBBLE_CLIENT_ID="sample_dribbble_client_id" +DRIBBBLE_CLIENT_SECRET="sample_dribbble_client_secret" + +# --- Tumblr --- +TUMBLR_CLIENT_ID="sample_tumblr_client_id" +TUMBLR_CLIENT_SECRET="sample_tumblr_client_secret" + +# --- VK (VKontakte) --- +VK_ID="12345678" +# --- Whop --- +WHOP_CLIENT_ID="sample_whop_client_id" + +# --- MeWe --- +MEWE_HOST="https://mewe.com" +MEWE_APP_ID="sample_mewe_app_id" +MEWE_API_KEY="sample_mewe_api_key" + +# --- Farcaster (Neynar API) --- +NEYNAR_CLIENT_ID="sample_neynar_client_id" +NEYNAR_SECRET_KEY="sample_neynar_secret_key" +NEYNAR_LOGIN_URL="https://app.neynar.com/login" + +# --- Sign in with Apple --- +APPLE_BUNDLE_ID="com.example.postiz.mobile" +APPLE_SERVICE_ID="com.example.postiz.web" +APPLE_CLIENT_ID="com.example.postiz.web" +APPLE_APP_BUNDLE_ID="com.example.postiz.ios" +APPLE_TEAM_ID="SAMPLE_TEAM_ID" +APPLE_KEY_ID="SAMPLE_KEY_ID" +APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nSAMPLE_APPLE_PRIVATE_KEY\n-----END PRIVATE KEY-----" + +# ============================================================================== +# 12. Browser Extension, Canvas & Short Links +# ============================================================================== +# Chrome Extension Store ID (for cookie-based platform authentications e.g. Skool) +EXTENSION_ID="sample_chrome_extension_id_32_chars" +# Polotno Studio Canvas Editor API Key (optional) +NEXT_PUBLIC_POLOTNO="sample_polotno_key" +# Dub.co Short Link Service API Key +DUB_TOKEN="dub_sample_token" +DUB_API_ENDPOINT="https://api.dub.co" +DUB_SHORT_LINK_DOMAIN="dub.sh" +# Short.io API Key +SHORT_IO_SECRET_KEY="shortio_sample_secret_key" +# Kutt.it Short Link Service API Key +KUTT_API_KEY="kutt_sample_api_key" +KUTT_API_ENDPOINT="https://kutt.it/api/v2" +KUTT_SHORT_LINK_DOMAIN="kutt.it" +# LinkDrip Short Link API Key +LINK_DRIP_API_KEY="linkdrip_sample_api_key" +LINK_DRIP_API_ENDPOINT="https://api.linkdrip.com/v1/" +LINK_DRIP_SHORT_LINK_DOMAIN="dripl.ink" +# Transloadit Video Transcoding & Processing Keys +TRANSLOADIT_AUTH="sample_transloadit_auth_key" +TRANSLOADIT_TEMPLATE="sample_transloadit_template_id" + +# ============================================================================== +# 13. Security, SSRF & Provider Routing Settings +# ============================================================================== +# Disable SSRF protection for self-hosted instances on private VPC networks (true/false) +DISABLE_SSRF_PROTECTION="false" +# MCP Dynamic Client Registration (RFC 7591) allowed redirect domains (comma-separated) +DCR_VERIFIED_DOMAINS="claude.ai,claude.com,chatgpt.com" +# Providers hidden from the Connect Channel modal (comma-separated) +HIDDEN_PROVIDERS="" +# Provider identifier migration mappings (comma-separated old:new pairs) +MIGRATE_PROVIDERS="tiktok:tiktok-business" +# Add Nx bundler plugins flag +NX_ADD_PLUGINS="false" + +# ============================================================================== +# 14. Stripe Billing & Subscription Plans +# ============================================================================== +# Platform fee percentage (0.05 = 5%) +FEE_AMOUNT="0.05" +# Stripe Publishable API Key +STRIPE_PUBLISHABLE_KEY="pk_test_51SAMPLE_STRIPE_PUBLISHABLE_KEY" +# Stripe Secret API Key +STRIPE_SECRET_KEY="sk_test_51SAMPLE_STRIPE_SECRET_KEY" +# Stripe Webhook Signing Secret +STRIPE_SIGNING_KEY="whsec_sample_stripe_webhook_signing_secret" +# Stripe Connect Webhook Signing Secret +STRIPE_SIGNING_KEY_CONNECT="whsec_sample_stripe_connect_signing_secret" +# Stripe coupon / promotional discount ID +STRIPE_DISCOUNT_ID="sample_stripe_coupon_discount_id" + +# ============================================================================== +# 15. Telemetry, Observability & Analytics +# ============================================================================== +# Sentry DSN for error reporting +NEXT_PUBLIC_SENTRY_DSN="https://sample_sentry_key@o0.ingest.sentry.io/0000000" +# Enable Sentry Spotlight debugging tool (1: on, 0: off) +SENTRY_SPOTLIGHT="0" +# Build application version identifier +NEXT_PUBLIC_APP_VERSION="2.0.0" +# Datafast Website ID for web analytics +DATAFAST_WEBSITE_ID="sample_datafast_website_id" +# Datafast API Key for goal tracking +DATAFAST_API_KEY="sample_datafast_api_key" +# Google Tag Manager ID +NEXT_PUBLIC_GTM_ID="GTM-SAMPLE01" +# Google Ads conversion tracking label for trials +NEXT_PUBLIC_TRACKING_TRIAL="sample_tracking_trial_label" +# PostHog Project API Key +NEXT_PUBLIC_POSTHOG_KEY="phc_sample_posthog_project_key" +# PostHog API Host URL +NEXT_PUBLIC_POSTHOG_HOST="https://us.i.posthog.com" +# Meta (Facebook) Pixel ID +NEXT_PUBLIC_FACEBOOK_PIXEL="123456789012345" +# Meta Conversion API Access Token +FACEBOOK_PIXEL_ACCESS_TOKEN="sample_facebook_pixel_access_token" +# Discord Support Server Widget URL +NEXT_PUBLIC_DISCORD_SUPPORT="https://discord.gg/example" diff --git a/apps/backend/src/api/routes/copilot.controller.ts b/apps/backend/src/api/routes/copilot.controller.ts index 9a8f0a272d..61577bd07f 100644 --- a/apps/backend/src/api/routes/copilot.controller.ts +++ b/apps/backend/src/api/routes/copilot.controller.ts @@ -8,6 +8,7 @@ import { Query, Param, } from '@nestjs/common'; +import OpenAI from 'openai'; import { CopilotRuntime, OpenAIAdapter, @@ -46,11 +47,17 @@ export class CopilotController { return; } + const openai = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY || 'sk-proj-', + ...(process.env.OPENAI_BASE_URL ? { baseURL: process.env.OPENAI_BASE_URL } : {}), + }); + const copilotRuntimeHandler = copilotRuntimeNodeHttpEndpoint({ endpoint: '/copilot/chat', runtime: new CopilotRuntime(), serviceAdapter: new OpenAIAdapter({ - model: 'gpt-4.1', + openai: openai as any, + model: process.env.OPENAI_MODEL_NAME || 'gpt-4.1', }), }); @@ -91,12 +98,18 @@ export class CopilotController { agents, }); + const openai = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY || 'sk-proj-', + ...(process.env.OPENAI_BASE_URL ? { baseURL: process.env.OPENAI_BASE_URL } : {}), + }); + const copilotRuntimeHandler = copilotRuntimeNextJSAppRouterEndpoint({ endpoint: '/copilot/agent', runtime, // properties: req.body.variables.properties, serviceAdapter: new OpenAIAdapter({ - model: 'gpt-4.1', + openai: openai as any, + model: process.env.OPENAI_MODEL_NAME || 'gpt-4.1', }), }); diff --git a/libraries/nestjs-libraries/src/agent/agent.graph.insert.service.ts b/libraries/nestjs-libraries/src/agent/agent.graph.insert.service.ts index 1a60b7399c..a69b32da39 100644 --- a/libraries/nestjs-libraries/src/agent/agent.graph.insert.service.ts +++ b/libraries/nestjs-libraries/src/agent/agent.graph.insert.service.ts @@ -10,8 +10,11 @@ import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/po const model = new ChatOpenAI({ apiKey: process.env.OPENAI_API_KEY || 'sk-proj-', - model: 'gpt-4o-2024-08-06', + model: process.env.OPENAI_MODEL_NAME || 'gpt-4o-2024-08-06', temperature: 0, + ...(process.env.OPENAI_BASE_URL + ? { configuration: { baseURL: process.env.OPENAI_BASE_URL } } + : {}), }); interface WorkflowChannelsState { diff --git a/libraries/nestjs-libraries/src/agent/agent.graph.service.ts b/libraries/nestjs-libraries/src/agent/agent.graph.service.ts index ada9684daa..bedbd3214b 100644 --- a/libraries/nestjs-libraries/src/agent/agent.graph.service.ts +++ b/libraries/nestjs-libraries/src/agent/agent.graph.service.ts @@ -24,13 +24,16 @@ const toolNode = new ToolNode(tools); const model = new ChatOpenAI({ apiKey: process.env.OPENAI_API_KEY || 'sk-proj-', - model: 'gpt-4.1', + model: process.env.OPENAI_MODEL_NAME || 'gpt-4.1', temperature: 0.7, + ...(process.env.OPENAI_BASE_URL + ? { configuration: { baseURL: process.env.OPENAI_BASE_URL } } + : {}), }); const dalle = new DallEAPIWrapper({ apiKey: process.env.OPENAI_API_KEY || 'sk-proj-', - model: 'chatgpt-image-latest', + model: process.env.OPENAI_IMAGE_MODEL || 'chatgpt-image-latest', }); interface WorkflowChannelsState { diff --git a/libraries/nestjs-libraries/src/database/prisma/autopost/autopost.service.ts b/libraries/nestjs-libraries/src/database/prisma/autopost/autopost.service.ts index 9b7f9e9eea..7bb54cbd7f 100644 --- a/libraries/nestjs-libraries/src/database/prisma/autopost/autopost.service.ts +++ b/libraries/nestjs-libraries/src/database/prisma/autopost/autopost.service.ts @@ -37,13 +37,16 @@ interface WorkflowChannelsState { const model = new ChatOpenAI({ apiKey: process.env.OPENAI_API_KEY || 'sk-proj-', - model: 'gpt-4.1', + model: process.env.OPENAI_MODEL_NAME || 'gpt-4.1', temperature: 0.7, + ...(process.env.OPENAI_BASE_URL + ? { configuration: { baseURL: process.env.OPENAI_BASE_URL } } + : {}), }); const dalle = new DallEAPIWrapper({ apiKey: process.env.OPENAI_API_KEY || 'sk-proj-', - model: 'chatgpt-image-latest', + model: process.env.OPENAI_IMAGE_MODEL || 'chatgpt-image-latest', }); const generateContent = z.object({ diff --git a/libraries/nestjs-libraries/src/openai/openai.service.ts b/libraries/nestjs-libraries/src/openai/openai.service.ts index d1eb8ac5d7..3d08b8a785 100644 --- a/libraries/nestjs-libraries/src/openai/openai.service.ts +++ b/libraries/nestjs-libraries/src/openai/openai.service.ts @@ -4,9 +4,15 @@ import { shuffle } from 'lodash'; import { zodResponseFormat } from 'openai/helpers/zod'; import { z } from 'zod'; -const openai = new OpenAI({ - apiKey: process.env.OPENAI_API_KEY || 'sk-proj-', -}); +const getOpenAIClient = () => { + return new OpenAI({ + apiKey: process.env.OPENAI_API_KEY || 'sk-proj-', + ...(process.env.OPENAI_BASE_URL ? { baseURL: process.env.OPENAI_BASE_URL } : {}), + }); +}; + +const getModel = () => process.env.OPENAI_MODEL_NAME || 'gpt-4.1'; +const getImageModel = () => process.env.OPENAI_IMAGE_MODEL || 'chatgpt-image-latest'; const PicturePrompt = z.object({ prompt: z.string(), @@ -19,12 +25,13 @@ const VoicePrompt = z.object({ @Injectable() export class OpenaiService { async generateImage(prompt: string, isVertical = false) { + const openai = getOpenAIClient(); // gpt-image models always return base64 (b64_json) and do not accept the // `response_format` parameter, unlike the deprecated dall-e-3. const generate = ( await openai.images.generate({ prompt, - model: 'chatgpt-image-latest', + model: getImageModel(), size: isVertical ? '1024x1536' : '1024x1024', }) ).data[0]; @@ -33,10 +40,11 @@ export class OpenaiService { } async generatePromptForPicture(prompt: string) { + const openai = getOpenAIClient(); return ( ( await openai.chat.completions.parse({ - model: 'gpt-4.1', + model: getModel(), messages: [ { role: 'system', @@ -54,10 +62,11 @@ export class OpenaiService { } async generateVoiceFromText(prompt: string) { + const openai = getOpenAIClient(); return ( ( await openai.chat.completions.parse({ - model: 'gpt-4.1', + model: getModel(), messages: [ { role: 'system', @@ -75,6 +84,7 @@ export class OpenaiService { } async generatePosts(content: string) { + const openai = getOpenAIClient(); const posts = ( await Promise.all([ openai.chat.completions.create({ @@ -91,7 +101,7 @@ export class OpenaiService { ], n: 5, temperature: 1, - model: 'gpt-4.1', + model: getModel(), }), openai.chat.completions.create({ messages: [ @@ -107,7 +117,7 @@ export class OpenaiService { ], n: 5, temperature: 1, - model: 'gpt-4.1', + model: getModel(), }), ]) ).flatMap((p) => p.choices); @@ -133,6 +143,7 @@ export class OpenaiService { ); } async extractWebsiteText(content: string) { + const openai = getOpenAIClient(); const websiteContent = await openai.chat.completions.create({ messages: [ { @@ -145,7 +156,7 @@ export class OpenaiService { content, }, ], - model: 'gpt-4.1', + model: getModel(), }); const { content: articleContent } = websiteContent.choices[0].message; @@ -154,6 +165,7 @@ export class OpenaiService { } async separatePosts(content: string, len: number) { + const openai = getOpenAIClient(); const SeparatePostsPrompt = z.object({ posts: z.array(z.string()), }); @@ -165,7 +177,7 @@ export class OpenaiService { const posts = ( await openai.chat.completions.parse({ - model: 'gpt-4.1', + model: getModel(), messages: [ { role: 'system', @@ -198,7 +210,7 @@ export class OpenaiService { return ( ( await openai.chat.completions.parse({ - model: 'gpt-4.1', + model: getModel(), messages: [ { role: 'system', @@ -228,13 +240,14 @@ export class OpenaiService { } async generateSlidesFromText(text: string) { + const openai = getOpenAIClient(); for (let i = 0; i < 3; i++) { try { const message = `You are an assistant that takes a text and break it into slides, each slide should have an image prompt and voice text to be later used to generate a video and voice, image prompt should capture the essence of the slide and also have a back dark gradient on top, image prompt should not contain text in the picture, generate between 3-5 slides maximum`; const parse = ( await openai.chat.completions.parse({ - model: 'gpt-4.1', + model: getModel(), messages: [ { role: 'system',