#!/bin/bash # dd0c Fly.io Deployment Script # Deploys all 5 Node services + shared Postgres + Redis # Budget target: ~$5/mo total (shared-cpu-1x, 256MB, auto-stop) # # Prerequisites: # 1. flyctl installed: curl -L https://fly.io/install.sh | sh # 2. flyctl auth login # 3. dd0c.dev DNS pointed to Fly.io (CNAME or A records) set -euo pipefail ORG="${FLY_ORG:-personal}" REGION="${FLY_REGION:-iad}" JWT_SECRET="${JWT_SECRET:-$(openssl rand -hex 32)}" RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" step() { echo -e "\n${YELLOW}▸ $1${NC}"; } ok() { echo -e " ${GREEN}✓${NC} $1"; } fail() { echo -e " ${RED}✗${NC} $1"; exit 1; } # ─── Shared Infrastructure ─── step "Creating shared Postgres cluster (dd0c-db)" if flyctl postgres list --org "$ORG" 2>/dev/null | grep -q dd0c-db; then ok "dd0c-db already exists" else flyctl postgres create \ --name dd0c-db \ --org "$ORG" \ --region "$REGION" \ --vm-size shared-cpu-1x \ --volume-size 1 \ --initial-cluster-size 1 \ --password "$(openssl rand -hex 16)" ok "dd0c-db created" fi step "Creating shared Redis (dd0c-redis via Upstash)" if flyctl redis list 2>/dev/null | grep -q dd0c-redis; then ok "dd0c-redis already exists" else flyctl redis create \ --name dd0c-redis \ --org "$ORG" \ --region "$REGION" \ --no-eviction ok "dd0c-redis created" fi # Get connection strings PG_CONN=$(flyctl postgres config show dd0c-db --app dd0c-db 2>/dev/null | grep "internal" | awk '{print $NF}' || echo "") REDIS_URL=$(flyctl redis status dd0c-redis 2>/dev/null | grep "Private URL" | awk '{print $NF}' || echo "") # ─── Create per-product databases ─── step "Creating per-product databases" for db in dd0c_drift dd0c_alert dd0c_portal dd0c_cost dd0c_run; do flyctl postgres connect -a dd0c-db -c "CREATE DATABASE $db;" 2>/dev/null || true ok "$db" done # ─── Deploy Services ─── declare -A APPS=( ["dd0c-drift"]="02-iac-drift-detection/saas" ["dd0c-alert"]="03-alert-intelligence" ["dd0c-portal"]="04-lightweight-idp" ["dd0c-cost"]="05-aws-cost-anomaly" ["dd0c-run"]="06-runbook-automation" ) declare -A DBS=( ["dd0c-drift"]="dd0c_drift" ["dd0c-alert"]="dd0c_alert" ["dd0c-portal"]="dd0c_portal" ["dd0c-cost"]="dd0c_cost" ["dd0c-run"]="dd0c_run" ) for app in dd0c-alert dd0c-portal dd0c-cost dd0c-run dd0c-drift; do dir="${APPS[$app]}" db="${DBS[$app]}" step "Deploying $app (from $dir)" # Create app if needed if ! flyctl apps list --org "$ORG" 2>/dev/null | grep -q "$app"; then flyctl apps create "$app" --org "$ORG" ok "App created" else ok "App exists" fi # Attach Postgres (creates DATABASE_URL secret automatically) flyctl postgres attach dd0c-db --app "$app" --database-name "$db" 2>/dev/null || true ok "Postgres attached ($db)" # Set secrets flyctl secrets set \ --app "$app" \ JWT_SECRET="$JWT_SECRET" \ REDIS_URL="${REDIS_URL:-redis://dd0c-redis.internal:6379}" \ 2>/dev/null ok "Secrets set" # Deploy cd "$SCRIPT_DIR/$dir" flyctl deploy --app "$app" --region "$REGION" --ha=false ok "$app deployed" cd "$SCRIPT_DIR" done # ─── Custom Domains ─── step "Setting up custom domains" declare -A DOMAINS=( ["dd0c-drift"]="drift.dd0c.dev" ["dd0c-alert"]="alert.dd0c.dev" ["dd0c-portal"]="portal.dd0c.dev" ["dd0c-cost"]="cost.dd0c.dev" ["dd0c-run"]="run.dd0c.dev" ) for app in "${!DOMAINS[@]}"; do domain="${DOMAINS[$app]}" flyctl certs add "$domain" --app "$app" 2>/dev/null || true ok "$app → $domain" done # ─── Summary ─── echo "" echo -e "${GREEN}═══════════════════════════════════${NC}" echo -e "${GREEN}dd0c deployed to Fly.io!${NC}" echo "" echo " JWT_SECRET: $JWT_SECRET" echo " Region: $REGION" echo "" echo " Services:" for app in "${!DOMAINS[@]}"; do echo " https://${DOMAINS[$app]}" done echo "" echo -e "${YELLOW}DNS: Point these CNAMEs to $app.fly.dev${NC}" echo -e "${YELLOW}Save the JWT_SECRET — it's shared across all services.${NC}"