#!/usr/bin/env bash # Prepares a real .env for a fresh clone with minimal manual setup: # 1. Copies .env.example -> .env if .env doesn't exist yet. # 2. Replaces the three "must be a real random secret, no safe shared # default" values (JWT_SECRET, API_KEY_ENCRYPTION_SECRET, # POSTGRES_PASSWORD) with freshly generated ones, but ONLY if they # still equal the known .env.example placeholder or are blank - # running this again after you've customized .env is a no-op. # # Deliberately does NOT touch DATABASE_URL: docker-compose.prod.yml builds # it from POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB directly (same as the # dev compose file already does with its hardcoded values), so there's # nothing to keep in sync by hand here. # # Deliberately does NOT set ANTHROPIC_API_KEY / BRAVE_SEARCH_API_KEY / # NINJAPEAR_API_KEY / USPTO_API_KEY / TURNSTILE_SITE_KEY / TURNSTILE_SECRET - # those are meant to be configured after first boot via the app's own # Settings page ("Your API keys" / "Server secrets"), not .env. See # KNOWN_LIMITATIONS.md. set -euo pipefail cd "$(dirname "$0")/.." ENV_FILE=".env" ENV_EXAMPLE=".env.example" if [ ! -f "$ENV_FILE" ]; then if [ ! -f "$ENV_EXAMPLE" ]; then echo "error: $ENV_EXAMPLE not found" >&2 exit 1 fi cp "$ENV_EXAMPLE" "$ENV_FILE" echo "Created $ENV_FILE from $ENV_EXAMPLE" fi # $1 = var name, $2 = known placeholder value in .env.example, $3 = generator command _replace_if_placeholder() { local var_name="$1" placeholder="$2" generator="$3" local current current=$(grep -E "^${var_name}=" "$ENV_FILE" | head -n1 | cut -d= -f2-) if [ -z "$current" ] || [ "$current" = "$placeholder" ]; then local new_value new_value=$(eval "$generator") # Escape characters that are special to sed's replacement text. local escaped escaped=$(printf '%s' "$new_value" | sed -e 's/[\/&]/\\&/g') sed -i "s/^${var_name}=.*/${var_name}=${escaped}/" "$ENV_FILE" echo "Generated a new ${var_name}" fi } _replace_if_placeholder "JWT_SECRET" "dev-only-change-me-32-characters-minimum" \ "openssl rand -hex 32" # Fernet requires exactly 32 raw bytes, urlsafe-base64-encoded - plain hex # would pass silently until the first encrypt/decrypt call, then crash. _replace_if_placeholder "API_KEY_ENCRYPTION_SECRET" "_wYtsm3nJ070987snBFp2eWVI5pyC0H9gGFUb6Cy4cQ=" \ "openssl rand -base64 32 | tr '+/' '-_'" _replace_if_placeholder "POSTGRES_PASSWORD" "ciagent" \ "openssl rand -hex 32" cat <<'EOF' Done. Before running this for real, still set manually in .env: - APP_ENV=production, AUTH_MODE=jwt (AUTH_MODE=local refuses to start when APP_ENV=production - this is an intentional safety check) - FRONTEND_URL / NEXT_PUBLIC_API_URL / BACKEND_URL for your real domain - TRUSTED_PROXY_IP_HEADER=CF-Connecting-IP once behind Cloudflare - RESEND_API_KEY (or SMTP_*) for real security emails - no admin UI for this one Once the app is running, sign in as the admin account and set these from the Settings page instead of .env: - Your API keys: Anthropic, Brave Search, NinjaPear, USPTO - Server secrets: Cloudflare Turnstile site key + secret EOF