#!/bin/bash echo "#############################################################" echo "# " echo "# Changemaker.lite Production Setup " echo "# " echo "# This script will: " echo "# 1. Create a Cloudflare tunnel " echo "# 2. Configure DNS records " echo "# 3. Set up access policies " echo "# 4. Enable the cloudflared container " echo "# " echo "#############################################################" echo "" # Get script directory SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" ENV_FILE="$SCRIPT_DIR/.env" DOCKER_COMPOSE_FILE="$SCRIPT_DIR/docker-compose.yml" # Source environment variables if [ -f "$ENV_FILE" ]; then export $(grep -v '^#' "$ENV_FILE" | xargs) else echo "Error: .env file not found. Please run ./config.sh first." exit 1 fi # Check if Cloudflare credentials are properly configured if [ -z "$CF_API_TOKEN" ] || [ "$CF_API_TOKEN" == "your_cloudflare_api_token" ] || \ [ -z "$CF_ZONE_ID" ] || [ "$CF_ZONE_ID" == "your_cloudflare_zone_id" ] || \ [ -z "$CF_DOMAIN" ]; then echo "Error: Cloudflare credentials not properly configured in .env file." echo "" echo "Current values:" echo " CF_API_TOKEN: ${CF_API_TOKEN:-not set}" echo " CF_ZONE_ID: ${CF_ZONE_ID:-not set}" echo " CF_DOMAIN: ${CF_DOMAIN:-not set}" echo "" echo "Please run ./config.sh and configure Cloudflare settings, or manually update your .env file." exit 1 fi # Check if CF_ACCOUNT_ID exists in .env, if not prompt for it if [ -z "$CF_ACCOUNT_ID" ]; then echo "Cloudflare Account ID is required for tunnel creation." echo "You can find it in your Cloudflare dashboard at the top right." read -p "Enter your Cloudflare Account ID: " CF_ACCOUNT_ID # Update .env with account ID echo "" >> "$ENV_FILE" echo "# Cloudflare Account ID (added by start-production.sh)" >> "$ENV_FILE" echo "CF_ACCOUNT_ID=$CF_ACCOUNT_ID" >> "$ENV_FILE" # Re-export the variable export CF_ACCOUNT_ID fi # Check if required variables are set if [ -z "$CF_API_TOKEN" ] || [ -z "$CF_ZONE_ID" ] || [ -z "$CF_DOMAIN" ]; then echo "Error: Cloudflare credentials not found in .env file." echo "Please run ./config.sh and configure Cloudflare settings." exit 1 fi # Check if services are running echo "Checking if services are running..." if ! docker compose ps | grep -q "Up"; then echo "Error: No services are running. Please run 'docker compose up -d' first." exit 1 fi echo "✓ Services are running" echo "" # Show current service URLs echo "Current local service URLs:" echo " - Homepage: http://localhost:${HOMEPAGE_PORT:-3010}" echo " - Code Server: http://localhost:${CODE_SERVER_PORT:-8888}" echo " - Listmonk: http://localhost:${LISTMONK_PORT:-9000}" echo " - Documentation (Dev): http://localhost:${MKDOCS_PORT:-4000}" echo " - Documentation (Built): http://localhost:${MKDOCS_SITE_SERVER_PORT:-4001}" echo " - n8n: http://localhost:${N8N_PORT:-5678}" echo " - NocoDB: http://localhost:${NOCODB_PORT:-8090}" echo " - Gitea: http://localhost:${GITEA_WEB_PORT:-3030}" echo "" read -p "Have you tested all services locally and are ready to go to production? (yes/no): " confirm if [[ "$confirm" != "yes" ]]; then echo "Please test all services locally before running production setup." exit 0 fi echo "" echo "Starting production setup..." # Install jq if not present if ! command -v jq &> /dev/null; then echo "Installing jq..." sudo apt-get update && sudo apt-get install -y jq fi # Create Cloudflare tunnel echo "" echo "Creating Cloudflare tunnel..." TUNNEL_NAME="changemaker-${HOSTNAME}-$(date +%s)" # Create tunnel via API TUNNEL_RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/cfd_tunnel" \ -H "Authorization: Bearer $CF_API_TOKEN" \ -H "Content-Type: application/json" \ --data "{\"name\":\"$TUNNEL_NAME\",\"config_src\":\"cloudflare\"}") # Check if we need account ID if echo "$TUNNEL_RESPONSE" | jq -e '.errors[] | select(.code == 0)' > /dev/null 2>&1; then echo "" echo "Account ID is required. You can find it in your Cloudflare dashboard." read -p "Enter your Cloudflare Account ID: " CF_ACCOUNT_ID # Update .env with account ID if grep -q "^CF_ACCOUNT_ID=" "$ENV_FILE"; then sed -i "s/^CF_ACCOUNT_ID=.*/CF_ACCOUNT_ID=$CF_ACCOUNT_ID/" "$ENV_FILE" else echo "CF_ACCOUNT_ID=$CF_ACCOUNT_ID" >> "$ENV_FILE" fi # Retry with account ID TUNNEL_RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/cfd_tunnel" \ -H "Authorization: Bearer $CF_API_TOKEN" \ -H "Content-Type: application/json" \ --data "{\"name\":\"$TUNNEL_NAME\",\"config_src\":\"cloudflare\"}") fi TUNNEL_ID=$(echo "$TUNNEL_RESPONSE" | jq -r '.result.id') TUNNEL_TOKEN=$(echo "$TUNNEL_RESPONSE" | jq -r '.result.token') if [ -z "$TUNNEL_ID" ] || [ "$TUNNEL_ID" == "null" ]; then echo "Error: Failed to create tunnel" echo "$TUNNEL_RESPONSE" | jq . exit 1 fi echo "✓ Created tunnel: $TUNNEL_NAME (ID: $TUNNEL_ID)" # Update .env with tunnel information echo "CF_TUNNEL_ID=$TUNNEL_ID" >> "$ENV_FILE" echo "CF_TUNNEL_TOKEN=$TUNNEL_TOKEN" >> "$ENV_FILE" echo "CF_TUNNEL_NAME=$TUNNEL_NAME" >> "$ENV_FILE" # Configure tunnel routes echo "" echo "Configuring tunnel routes..." # Define services and their configurations declare -A SERVICES=( ["dashboard"]="homepage-changemaker:3000" ["code"]="code-server:8080" ["listmonk"]="listmonk-app:9000" ["docs"]="mkdocs:8000" ["n8n"]="n8n:5678" ["db"]="nocodb:8080" ["git"]="gitea-app:3000" ) # Configure root domain to mkdocs-site-server echo "Configuring route for root domain..." ROOT_CONFIG=$(cat < $service" CONFIG=$(cat < /dev/null; then echo "✓ DNS record for $name.$CF_DOMAIN configured" else echo "✗ Failed to configure DNS record for $name.$CF_DOMAIN" echo "$RESPONSE" | jq '.errors' fi } # Create DNS records for all subdomains for subdomain in "${!SERVICES[@]}"; do create_dns_record "$subdomain" done # Create root domain record create_dns_record "@" # Set up access policies echo "" echo "Setting up access policies..." read -p "Enter admin email for protected services (dashboard, code): " ADMIN_EMAIL if [[ "$ADMIN_EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then # Create access applications for protected services PROTECTED_SERVICES=("dashboard" "code") for service in "${PROTECTED_SERVICES[@]}"; do echo "Creating access policy for $service.$CF_DOMAIN..." # Create access application APP_RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/access/apps" \ -H "Authorization: Bearer $CF_API_TOKEN" \ -H "Content-Type: application/json" \ --data "{ \"name\": \"$service.$CF_DOMAIN\", \"domain\": \"$service.$CF_DOMAIN\", \"type\": \"self_hosted\", \"session_duration\": \"24h\" }") APP_ID=$(echo "$APP_RESPONSE" | jq -r '.result.id') if [ ! -z "$APP_ID" ] && [ "$APP_ID" != "null" ]; then # Create policy POLICY_RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/access/apps/$APP_ID/policies" \ -H "Authorization: Bearer $CF_API_TOKEN" \ -H "Content-Type: application/json" \ --data "{ \"name\": \"Email Policy\", \"decision\": \"allow\", \"include\": [{ \"email\": {\"email\": \"$ADMIN_EMAIL\"} }] }") echo "✓ Access policy created for $service.$CF_DOMAIN" fi done else echo "Invalid email format. Skipping access policy setup." fi # Enable cloudflared in docker-compose echo "" echo "Enabling cloudflared container..." # Uncomment cloudflared service in docker-compose.yml sed -i '/# cloudflared:/,/# gitea-app/s/^ # / /' "$DOCKER_COMPOSE_FILE" # Start cloudflared container docker compose up -d cloudflared echo "" echo "#############################################################" echo "# " echo "# Production Setup Complete! " echo "# " echo "#############################################################" echo "" echo "Your services are now accessible at:" echo "" echo "Public services:" echo " - Main Site: https://$CF_DOMAIN" echo " - Listmonk: https://listmonk.$CF_DOMAIN" echo " - Documentation: https://docs.$CF_DOMAIN" echo " - n8n: https://n8n.$CF_DOMAIN" echo " - NocoDB: https://db.$CF_DOMAIN" echo " - Gitea: https://git.$CF_DOMAIN" echo "" if [ ! -z "$ADMIN_EMAIL" ]; then echo "Protected services (login required with $ADMIN_EMAIL):" echo " - Dashboard: https://dashboard.$CF_DOMAIN" echo " - Code Server: https://code.$CF_DOMAIN" else echo "Admin services (no protection configured):" echo " - Dashboard: https://dashboard.$CF_DOMAIN" echo " - Code Server: https://code.$CF_DOMAIN" fi echo "" echo "Note: DNS propagation may take a few minutes." echo "" echo "To stop production mode and return to local-only:" echo " docker compose stop cloudflared" echo "" echo "To fully remove production setup:" echo " docker compose down cloudflared"