#!/bin/bash # Complete Cloudflare Configuration for Explorer - Automated # Uses .env credentials to configure DNS, SSL, and tunnel routes set -euo pipefail # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_success() { echo -e "${GREEN}[✓]${NC} $1"; } log_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; } log_error() { echo -e "${RED}[✗]${NC} $1"; } log_section() { echo -e "${CYAN}════════════════════════════════════════${NC}"; } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ENV_FILE="${ENV_FILE:-$SCRIPT_DIR/../.env}" # Configuration DOMAIN="${DOMAIN:-d-bis.org}" EXPLORER_DOMAIN="explorer.d-bis.org" EXPLORER_IP="${EXPLORER_IP:-192.168.11.140}" EXPLORER_PORT="${EXPLORER_PORT:-80}" VMID=5000 PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.10}" TUNNEL_TOKEN="eyJhIjoiNTJhZDU3YTcxNjcxYzVmYzAwOWVkZjA3NDQ2NTgxOTYiLCJ0IjoiYjAyZmUxZmUtY2I3ZC00ODRlLTkwOWItN2NjNDEyOThlYmU4IiwicyI6Ik5HTmtOV0kwWXpNdFpUVmxaUzAwTVRFMkxXRXdNMk10WlRJNU1ETTFaRFF4TURBMiJ9" echo "" log_section log_info " COMPLETE CLOUDFLARE EXPLORER CONFIGURATION" log_info " Using .env Credentials for Full Automation" log_section echo "" # Step 1: Load .env file log_info "Step 1: Loading credentials from .env file..." if [ ! -f "$ENV_FILE" ]; then log_error ".env file not found: $ENV_FILE" log_info "Looking for .env files..." find "$SCRIPT_DIR/.." -maxdepth 2 -name ".env" -type f 2>/dev/null | head -3 log_info "" log_info "Please create .env file with:" echo " CLOUDFLARE_API_TOKEN=your-token" echo " CLOUDFLARE_ZONE_ID=your-zone-id (optional)" echo " CLOUDFLARE_ACCOUNT_ID=your-account-id (optional)" echo " DOMAIN=d-bis.org" exit 1 fi # Source .env file set -a source "$ENV_FILE" set +a log_success ".env file loaded" # Check for required credentials CLOUDFLARE_API_TOKEN="${CLOUDFLARE_API_TOKEN:-}" CLOUDFLARE_API_KEY="${CLOUDFLARE_API_KEY:-}" CLOUDFLARE_EMAIL="${CLOUDFLARE_EMAIL:-}" CLOUDFLARE_ZONE_ID="${CLOUDFLARE_ZONE_ID:-}" CLOUDFLARE_ACCOUNT_ID="${CLOUDFLARE_ACCOUNT_ID:-}" # Determine auth method AUTH_METHOD="" AUTH_HEADERS=() # Check for API_TOKEN first (preferred), then API_KEY if [ -n "$CLOUDFLARE_API_TOKEN" ]; then AUTH_METHOD="token" AUTH_HEADERS=(-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN") log_success "Using API Token authentication" elif [ -n "$CLOUDFLARE_API_KEY" ] && [ -n "$CLOUDFLARE_EMAIL" ]; then AUTH_METHOD="key" # Remove quotes from API_KEY if present CLOUDFLARE_API_KEY=$(echo "$CLOUDFLARE_API_KEY" | tr -d '"') CLOUDFLARE_EMAIL=$(echo "$CLOUDFLARE_EMAIL" | tr -d '"') AUTH_HEADERS=(-H "X-Auth-Email: $CLOUDFLARE_EMAIL" -H "X-Auth-Key: $CLOUDFLARE_API_KEY") log_success "Using API Key authentication" else log_error "No Cloudflare API credentials found in .env" log_info "Required: CLOUDFLARE_API_TOKEN or (CLOUDFLARE_API_KEY + CLOUDFLARE_EMAIL)" exit 1 fi # Function to find container node find_container_node() { ssh -o StrictHostKeyChecking=no root@"$PROXMOX_HOST" \ "for node in ml110 pve pve2; do if pvesh get /nodes/\$node/lxc/$VMID/status/current --output-format json >/dev/null 2>&1; then echo \$node; break; fi; done" 2>/dev/null || echo "pve2" } # Function to execute command in container exec_container() { local cmd="$1" # Try direct pct exec via main host first ssh -o StrictHostKeyChecking=no root@"$PROXMOX_HOST" "pct exec $VMID -- bash -c '$cmd'" 2>&1 } # Step 2: Install Cloudflare Tunnel Service log_section log_info "Step 2: Installing Cloudflare Tunnel Service" log_section log_info "Checking cloudflared installation..." if ! exec_container "command -v cloudflared >/dev/null 2>&1"; then log_info "Installing cloudflared..." exec_container "cd /tmp && wget -q https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb && dpkg -i cloudflared-linux-amd64.deb || apt install -f -y" log_success "cloudflared installed" else log_success "cloudflared already installed" fi log_info "Installing tunnel service with token..." INSTALL_OUTPUT=$(exec_container "cloudflared service install $TUNNEL_TOKEN 2>&1" || echo "INSTALL_FAILED") if echo "$INSTALL_OUTPUT" | grep -q -E "successfully|installed|Service installed"; then log_success "Tunnel service installed" else log_warn "Installation output: $INSTALL_OUTPUT" # Continue - service might already be installed fi log_info "Starting cloudflared service..." exec_container "systemctl start cloudflared" || true exec_container "systemctl enable cloudflared" || true sleep 3 CLOUDFLARED_STATUS=$(exec_container "systemctl is-active cloudflared 2>/dev/null || echo 'inactive'") if [ "$CLOUDFLARED_STATUS" = "active" ]; then log_success "Cloudflared service is running" else log_warn "Cloudflared service is $CLOUDFLARED_STATUS" fi # Get tunnel ID log_info "Getting tunnel ID..." TUNNEL_LIST=$(exec_container "cloudflared tunnel list 2>&1" || echo "") TUNNEL_ID=$(echo "$TUNNEL_LIST" | grep -v "NAME" | head -1 | awk '{print $1}' || echo "") if [ -n "$TUNNEL_ID" ]; then log_success "Tunnel ID: $TUNNEL_ID" else log_warn "Could not get tunnel ID from tunnel list" # Try to extract from token (base64 decode) TUNNEL_ID=$(echo "$TUNNEL_TOKEN" | base64 -d 2>/dev/null | jq -r '.TunnelID // empty' 2>/dev/null || echo "") if [ -n "$TUNNEL_ID" ]; then log_success "Tunnel ID from token: $TUNNEL_ID" else log_error "Cannot determine tunnel ID" exit 1 fi fi # Step 3: Get Zone ID log_section log_info "Step 3: Getting Cloudflare Zone ID" log_section if [ -z "$CLOUDFLARE_ZONE_ID" ]; then log_info "Fetching zone ID for $DOMAIN..." ZONE_RESPONSE=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$DOMAIN" \ "${AUTH_HEADERS[@]}" \ -H "Content-Type: application/json") ZONE_ID=$(echo "$ZONE_RESPONSE" | jq -r '.result[0].id // empty' 2>/dev/null || echo "") if [ -z "$ZONE_ID" ] || [ "$ZONE_ID" = "null" ]; then ERROR=$(echo "$ZONE_RESPONSE" | jq -r '.errors[0].message // "Unknown error"' 2>/dev/null || echo "API call failed") log_error "Failed to get zone ID: $ERROR" exit 1 fi log_success "Zone ID: $ZONE_ID" else ZONE_ID="$CLOUDFLARE_ZONE_ID" log_success "Using provided Zone ID: $ZONE_ID" fi # Step 4: Get Account ID (for tunnel configuration) log_section log_info "Step 4: Getting Cloudflare Account ID" log_section if [ -z "$CLOUDFLARE_ACCOUNT_ID" ]; then log_info "Fetching account ID..." ACCOUNT_RESPONSE=$(curl -s -X GET "https://api.cloudflare.com/client/v4/accounts" \ "${AUTH_HEADERS[@]}" \ -H "Content-Type: application/json") CLOUDFLARE_ACCOUNT_ID=$(echo "$ACCOUNT_RESPONSE" | jq -r '.result[0].id // empty' 2>/dev/null || echo "") if [ -z "$CLOUDFLARE_ACCOUNT_ID" ] || [ "$CLOUDFLARE_ACCOUNT_ID" = "null" ]; then log_warn "Could not get account ID automatically" else log_success "Account ID: $CLOUDFLARE_ACCOUNT_ID" fi else log_success "Using provided Account ID: $CLOUDFLARE_ACCOUNT_ID" fi # Step 5: Configure DNS Record log_section log_info "Step 5: Configuring DNS Record" log_section TARGET="${TUNNEL_ID}.cfargotunnel.com" log_info "DNS Target: $TARGET" # Check if record exists EXISTING_RECORD=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?name=$EXPLORER_DOMAIN" \ "${AUTH_HEADERS[@]}" \ -H "Content-Type: application/json") RECORD_ID=$(echo "$EXISTING_RECORD" | jq -r '.result[0].id // empty' 2>/dev/null || echo "") EXISTING_TYPE=$(echo "$EXISTING_RECORD" | jq -r '.result[0].type // empty' 2>/dev/null || echo "") DNS_DATA=$(jq -n \ --arg name "explorer" \ --arg target "$TARGET" \ '{ type: "CNAME", name: $name, content: $target, proxied: true, ttl: 1 }') if [ -n "$RECORD_ID" ] && [ "$RECORD_ID" != "null" ]; then log_info "Updating existing DNS record (ID: $RECORD_ID, Type: $EXISTING_TYPE)..." if [ "$EXISTING_TYPE" != "CNAME" ]; then log_warn "Existing record is type $EXISTING_TYPE, deleting and creating CNAME..." curl -s -X DELETE "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \ "${AUTH_HEADERS[@]}" \ -H "Content-Type: application/json" >/dev/null 2>&1 DNS_RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \ "${AUTH_HEADERS[@]}" \ -H "Content-Type: application/json" \ --data "$DNS_DATA") else DNS_RESPONSE=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \ "${AUTH_HEADERS[@]}" \ -H "Content-Type: application/json" \ --data "$DNS_DATA") fi else log_info "Creating new DNS record..." DNS_RESPONSE=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \ "${AUTH_HEADERS[@]}" \ -H "Content-Type: application/json" \ --data "$DNS_DATA") fi if echo "$DNS_RESPONSE" | jq -e '.success' >/dev/null 2>&1; then log_success "DNS record configured successfully" DNS_NAME=$(echo "$DNS_RESPONSE" | jq -r '.result.name' 2>/dev/null || echo "$EXPLORER_DOMAIN") DNS_TARGET=$(echo "$DNS_RESPONSE" | jq -r '.result.content' 2>/dev/null || echo "$TARGET") DNS_PROXIED=$(echo "$DNS_RESPONSE" | jq -r '.result.proxied' 2>/dev/null || echo "true") log_info " Name: $DNS_NAME" log_info " Target: $DNS_TARGET" log_info " Proxied: $DNS_PROXIED" else ERROR=$(echo "$DNS_RESPONSE" | jq -r '.errors[0].message // "Unknown error"' 2>/dev/null || echo "API call failed") log_error "Failed to configure DNS: $ERROR" echo "$DNS_RESPONSE" | jq '.' 2>/dev/null || echo "$DNS_RESPONSE" exit 1 fi # Step 6: Configure Tunnel Route log_section log_info "Step 6: Configuring Tunnel Route" log_section if [ -z "$CLOUDFLARE_ACCOUNT_ID" ] || [ "$CLOUDFLARE_ACCOUNT_ID" = "null" ]; then log_warn "Account ID not available - tunnel route must be configured manually" log_info "Configure in Cloudflare Zero Trust Dashboard:" echo " 1. Go to: https://one.dash.cloudflare.com/" echo " 2. Zero Trust → Networks → Tunnels" echo " 3. Select tunnel: $TUNNEL_ID" echo " 4. Configure → Public Hostnames → Add hostname" echo " 5. Subdomain: explorer, Domain: $DOMAIN" echo " 6. Service: http://$EXPLORER_IP:$EXPLORER_PORT" else log_info "Configuring tunnel route via API..." # Get current tunnel configuration TUNNEL_CONFIG=$(curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/cfd_tunnel/$TUNNEL_ID/configurations" \ "${AUTH_HEADERS[@]}" \ -H "Content-Type: application/json") # Build new ingress configuration NEW_CONFIG=$(jq -n \ --arg hostname "$EXPLORER_DOMAIN" \ --arg service "http://$EXPLORER_IP:$EXPLORER_PORT" \ '{ config: { ingress: [ { hostname: $hostname, service: $service }, { service: "http_status:404" } ] } }') # Update tunnel configuration TUNNEL_UPDATE=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/cfd_tunnel/$TUNNEL_ID/configurations" \ "${AUTH_HEADERS[@]}" \ -H "Content-Type: application/json" \ --data "$NEW_CONFIG") if echo "$TUNNEL_UPDATE" | jq -e '.success' >/dev/null 2>&1; then log_success "Tunnel route configured successfully" else ERROR=$(echo "$TUNNEL_UPDATE" | jq -r '.errors[0].message // "Unknown error"' 2>/dev/null || echo "API call failed") log_warn "Tunnel route API configuration failed: $ERROR" log_info "Please configure manually in Cloudflare Zero Trust Dashboard" fi fi # Step 7: SSL/TLS Configuration (automatic with Cloudflare proxy) log_section log_info "Step 7: SSL/TLS Configuration" log_section log_info "SSL/TLS is automatically handled by Cloudflare when DNS is proxied" log_success "SSL will be enabled automatically (Universal SSL)" # Step 8: Verify Configuration log_section log_info "Step 8: Verifying Configuration" log_section log_info "Waiting for DNS propagation (10 seconds)..." sleep 10 # Test DNS resolution DNS_RESULT=$(dig +short "$EXPLORER_DOMAIN" 2>/dev/null | head -1 || echo "") if [ -n "$DNS_RESULT" ]; then log_success "DNS resolves to: $DNS_RESULT" if echo "$DNS_RESULT" | grep -qE "^(104\.|172\.64\.|172\.65\.|172\.66\.|172\.67\.)"; then log_success "DNS points to Cloudflare (proxied correctly)" fi else log_warn "DNS not resolving yet (may need more time)" fi # Test public URL log_info "Testing public URL..." PUBLIC_HTTP=$(curl -s -o /dev/null -w "%{http_code}" "https://$EXPLORER_DOMAIN/api/v2/stats" 2>&1) if [ "$PUBLIC_HTTP" = "200" ]; then log_success "Public URL: HTTP 200 - Working!" PUBLIC_RESPONSE=$(curl -s "https://$EXPLORER_DOMAIN/api/v2/stats" 2>&1) if echo "$PUBLIC_RESPONSE" | grep -q -E "total_blocks|chain_id"; then log_success "Public API: Valid response" echo "$PUBLIC_RESPONSE" | jq -r '.total_blocks, .total_transactions, .total_addresses' 2>/dev/null || echo "$PUBLIC_RESPONSE" | head -5 fi elif [ "$PUBLIC_HTTP" = "404" ]; then log_warn "Public URL: HTTP 404 - May need more time for DNS/tunnel propagation" elif [ "$PUBLIC_HTTP" = "502" ]; then log_warn "Public URL: HTTP 502 - Tunnel routing issue, check tunnel route configuration" else log_warn "Public URL: HTTP $PUBLIC_HTTP" fi # Final Summary echo "" log_section log_info " CONFIGURATION SUMMARY" log_section echo "" log_success "✓ Cloudflared service: Installed and running" log_success "✓ Tunnel ID: $TUNNEL_ID" log_success "✓ DNS Record: $EXPLORER_DOMAIN → $TARGET (🟠 Proxied)" if [ -n "$CLOUDFLARE_ACCOUNT_ID" ]; then log_success "✓ Tunnel Route: Configured via API" else log_warn "⚠ Tunnel Route: Manual configuration required" fi log_success "✓ SSL/TLS: Automatic (Cloudflare Universal SSL)" echo "" log_info "Configuration complete!" log_info "" log_info "Access your explorer at:" echo " https://$EXPLORER_DOMAIN" echo "" log_info "If public URL is not working yet, wait 1-5 minutes for DNS propagation" echo ""