Add production deployment tooling: Nginx, Gitea, prod Dockerfiles
Sets up everything needed to deploy behind Cloudflare with a self-hosted git server: multi-stage prod Dockerfiles (non-root), docker-compose.prod.yml (Postgres/Redis with no host ports, Nginx reverse proxy, Gitea with public-read/admin-write access control), scripts/bootstrap-env.sh to auto-generate required secrets on first clone, and DEPLOYMENT.md covering the full runbook. Provider API keys (Anthropic/Brave/NinjaPear/USPTO/ Turnstile) are deliberately kept out of .env in favor of the existing DB-backed Settings UI, so the public repo stays safe to expose. Also fixes two bugs only surfaced by live-testing the prod stack: Celery beat couldn't write its schedule file as a non-root user, and Gitea's embedded SSH server conflicted with the base image's own sshd on port 22. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user