#!/bin/bash echo "#############################################################" echo "# " echo "# DNS Setup for Changemaker.lite Services " echo "# " echo "# This script will ADD DNS records for your services. " echo "# Existing DNS records will NOT be deleted. " echo "# " echo "#############################################################" echo "" echo "-------------------------------------------------------------" echo "Cloudflare Credentials Required" echo "Please ensure your .env file contains the following variables:" echo " CF_API_TOKEN=your_cloudflare_api_token" echo " CF_ZONE_ID=your_cloudflare_zone_id" echo " CF_TUNNEL_ID=your_cloudflared_tunnel_id" echo " CF_DOMAIN=yourdomain.com" echo "" echo "You can find these values in your Cloudflare dashboard:" echo " - API Token: https://dash.cloudflare.com/profile/api-tokens (Create a token with Zone:DNS:Edit and Access:Apps:Edit permissions for your domain)" echo " - Zone ID: On your domain's overview page" echo " - Tunnel ID: In the Zero Trust dashboard under Access > Tunnels" echo " - Domain: The domain you want to use for your services" echo "" echo "-------------------------------------------------------------" echo "" read -p "Type 'y' to continue or any other key to abort: " consent if [[ "$consent" != "y" && "$consent" != "Y" ]]; then echo "Aborted by user." exit 1 fi # Source environment variables from the .env file in the same directory ENV_FILE="$(dirname "$0")/.env" if [ -f "$ENV_FILE" ]; then export $(grep -v '^#' "$ENV_FILE" | xargs) else echo "Error: .env file not found at $ENV_FILE" exit 1 fi # Check if required Cloudflare variables are set if [ -z "$CF_API_TOKEN" ] || [ -z "$CF_ZONE_ID" ] || [ -z "$CF_TUNNEL_ID" ] || [ -z "$CF_DOMAIN" ]; then echo "Error: One or more required Cloudflare environment variables (CF_API_TOKEN, CF_ZONE_ID, CF_TUNNEL_ID, CF_DOMAIN) are not set in $ENV_FILE." exit 1 fi # Check if jq is installed if ! command -v jq &> /dev/null; then echo "Error: jq is required but not installed. Please install jq to continue." echo "On Debian/Ubuntu: sudo apt-get install jq" echo "On RHEL/CentOS: sudo yum install jq" exit 1 fi # Array of subdomains that need DNS records - updated to match our active services SUBDOMAINS=( "dashboard" "code" "listmonk" "docs" "n8n" "db" "git" ) # Function to check if DNS record already exists record_exists() { local subdomain=$1 local records=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records?name=$subdomain.$CF_DOMAIN" \ -H "Authorization: Bearer $CF_API_TOKEN" \ -H "Content-Type: application/json") local count=$(echo $records | jq -r '.result | length') [ "$count" -gt 0 ] } # Add CNAME records for each subdomain (only if they don't exist) echo "Adding DNS records for services..." for subdomain in "${SUBDOMAINS[@]}"; do if record_exists "$subdomain"; then echo "DNS record for $subdomain.$CF_DOMAIN already exists, skipping..." else echo "Adding CNAME record for $subdomain.$CF_DOMAIN..." 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\": \"$subdomain\", \"content\": \"$CF_TUNNEL_ID.cfargotunnel.com\", \"ttl\": 1, \"proxied\": true }") success=$(echo $response | jq -r '.success') if [ "$success" == "true" ]; then echo "✓ Successfully added CNAME record for $subdomain.$CF_DOMAIN" else echo "✗ Failed to add CNAME record for $subdomain.$CF_DOMAIN" echo "Error: $(echo $response | jq -r '.errors[0].message')" fi fi done # Add root domain record if it doesn't exist if record_exists "@"; then echo "Root domain DNS record already exists, skipping..." else echo "Adding root domain CNAME 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\": \"@\", \"content\": \"$CF_TUNNEL_ID.cfargotunnel.com\", \"ttl\": 1, \"proxied\": true }") success=$(echo $response | jq -r '.success') if [ "$success" == "true" ]; then echo "✓ Successfully added root domain CNAME record" else echo "✗ Failed to add root domain CNAME record" echo "Error: $(echo $response | jq -r '.errors[0].message')" fi fi echo "" echo "DNS records setup complete!" echo "" # Prompt for admin email for secured services echo "-------------------------------------------------------------" echo "Setting up Cloudflare Access Protection" echo "-------------------------------------------------------------" echo "" echo "The following services will be protected with authentication:" echo " - dashboard.$CF_DOMAIN" echo " - code.$CF_DOMAIN" echo "" echo "Please enter the admin email address that should have access:" read ADMIN_EMAIL # Validate email format if [[ ! "$ADMIN_EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then echo "Error: Invalid email format. Please provide a valid email address." exit 1 fi # Services that require authentication - updated for our use case PROTECTED_SERVICES=("dashboard" "code") # Services that should have bypass policies (public access) - updated for our use case BYPASS_SERVICES=("listmonk" "docs" "n8n" "db" "git") # Function to create access application with email authentication create_protected_app() { local service=$1 echo "Setting up authentication for $service.$CF_DOMAIN..." 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_launcher_visible\": true, \"skip_interstitial\": true }") app_id=$(echo $app_response | jq -r '.result.id') if [ -z "$app_id" ] || [ "$app_id" == "null" ]; then echo "✗ Error creating access application for $service" return 1 fi echo "✓ Created access application for $service (ID: $app_id)" # Create authentication 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\": \"Admin Access\", \"decision\": \"allow\", \"include\": [{ \"email\": { \"email\": \"$ADMIN_EMAIL\" } }], \"require\": [], \"exclude\": [] }") policy_success=$(echo $policy_response | jq -r '.success') if [ "$policy_success" == "true" ]; then echo "✓ Created authentication policy for $service" else echo "✗ Failed to create authentication policy for $service" fi } # Function to create bypass application (public access) create_bypass_app() { local service=$1 echo "Setting up public access for $service.$CF_DOMAIN..." 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_launcher_visible\": false, \"skip_interstitial\": true }") app_id=$(echo $app_response | jq -r '.result.id') if [ -z "$app_id" ] || [ "$app_id" == "null" ]; then echo "✗ Error creating access application for $service" return 1 fi echo "✓ Created access application for $service (ID: $app_id)" # Create bypass 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\": \"Public Access\", \"decision\": \"bypass\", \"include\": [{ \"everyone\": {} }], \"require\": [], \"exclude\": [] }") policy_success=$(echo $policy_response | jq -r '.success') if [ "$policy_success" == "true" ]; then echo "✓ Created public access policy for $service" else echo "✗ Failed to create public access policy for $service" fi } echo "Creating Cloudflare Access applications..." echo "" # Create protected applications for service in "${PROTECTED_SERVICES[@]}"; do create_protected_app "$service" echo "" done # Create bypass applications for public services for service in "${BYPASS_SERVICES[@]}"; do create_bypass_app "$service" echo "" done echo "-------------------------------------------------------------" echo "Setup Complete!" echo "-------------------------------------------------------------" echo "" echo "Protected services (require authentication with $ADMIN_EMAIL):" for service in "${PROTECTED_SERVICES[@]}"; do echo " - https://$service.$CF_DOMAIN" done echo "" echo "Public services (no authentication required):" for service in "${BYPASS_SERVICES[@]}"; do echo " - https://$service.$CF_DOMAIN" done echo "" echo "All services should be accessible through your Cloudflare tunnel."