freealberta/start-production.sh

348 lines
12 KiB
Bash

#!/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 <<EOF
{
"tunnel_id": "$TUNNEL_ID",
"hostname": "$CF_DOMAIN",
"service": "http://mkdocs-site-server:80"
}
EOF
)
ROOT_RESPONSE=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/tunnels/$TUNNEL_ID/configurations" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data "$ROOT_CONFIG")
# Configure subdomain routes
for subdomain in "${!SERVICES[@]}"; do
service="${SERVICES[$subdomain]}"
echo "Configuring route for $subdomain.$CF_DOMAIN -> $service"
CONFIG=$(cat <<EOF
{
"tunnel_id": "$TUNNEL_ID",
"hostname": "$subdomain.$CF_DOMAIN",
"service": "http://$service"
}
EOF
)
RESPONSE=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/tunnels/$TUNNEL_ID/configurations" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data "$CONFIG")
done
# Create DNS records
echo ""
echo "Creating DNS records..."
# Function to create/update DNS record
create_dns_record() {
local name=$1
local content="$TUNNEL_ID.cfargotunnel.com"
# Check if record exists
EXISTING=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records?name=$name.$CF_DOMAIN&type=CNAME" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json")
RECORD_ID=$(echo "$EXISTING" | jq -r '.result[0].id')
if [ ! -z "$RECORD_ID" ] && [ "$RECORD_ID" != "null" ]; then
# Update existing record
RESPONSE=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records/$RECORD_ID" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data "{
\"type\": \"CNAME\",
\"name\": \"$name\",
\"content\": \"$content\",
\"ttl\": 1,
\"proxied\": true
}")
else
# Create new record
RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data "{
\"type\": \"CNAME\",
\"name\": \"$name\",
\"content\": \"$content\",
\"ttl\": 1,
\"proxied\": true
}")
fi
if echo "$RESPONSE" | jq -e '.success' > /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"