Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
- ADD_CHAIN138_TO_LEDGER_LIVE: Ledger form done; public code review repo bis-innovations/LedgerLive; init/push commands - CONTRACT_DEPLOYMENT_RUNBOOK: Chain 138 gas price 1 gwei, 36-addr check, TransactionMirror workaround - CONTRACT_*: AddressMapper, MirrorManager deployed 2026-02-12; 36-address on-chain check - NEXT_STEPS_FOR_YOU: Ledger done; steps completable now (no LAN); run-completable-tasks-from-anywhere - MASTER_INDEX, OPERATOR_OPTIONAL, SMART_CONTRACTS_INVENTORY_SIMPLE: updates - LEDGER_BLOCKCHAIN_INTEGRATION_COMPLETE: bis-innovations/LedgerLive reference Co-authored-by: Cursor <cursoragent@cursor.com>
352 lines
12 KiB
Bash
Executable File
352 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Update all Cloudflare DNS records to point to single public IP (76.53.10.36)
|
|
# Sets all records to DNS only mode (gray cloud) for direct NAT routing
|
|
# Supports multiple zones: sankofa.nexus, d-bis.org, mim4u.org, defi-oracle.io
|
|
# UDM Pro port forwarding: 76.53.10.36:80/443 → 192.168.11.167:80/443 (NPMplus)
|
|
|
|
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"; }
|
|
|
|
# Script directory
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
|
# Source .env file (set +u so values with $ in them don't trigger unbound variable)
|
|
if [ -f "$PROJECT_ROOT/.env" ]; then
|
|
set +u
|
|
# shellcheck source=/dev/null
|
|
source "$PROJECT_ROOT/.env"
|
|
set -u
|
|
else
|
|
log_error ".env file not found at $PROJECT_ROOT/.env"
|
|
exit 1
|
|
fi
|
|
|
|
# Public IP for all services (76.53.10.36 - UDM Pro port forwarding to NPMplus 192.168.11.167)
|
|
PUBLIC_IP="${PUBLIC_IP:-76.53.10.36}"
|
|
|
|
# Cloudflare authentication
|
|
if [ -n "${CLOUDFLARE_API_TOKEN:-}" ]; then
|
|
AUTH_HEADER="Authorization: Bearer $CLOUDFLARE_API_TOKEN"
|
|
log_info "Using API Token authentication"
|
|
elif [ -n "${CLOUDFLARE_EMAIL:-}" ] && [ -n "${CLOUDFLARE_API_KEY:-}" ]; then
|
|
AUTH_HEADER_EMAIL="$CLOUDFLARE_EMAIL"
|
|
AUTH_HEADER_KEY="$CLOUDFLARE_API_KEY"
|
|
log_info "Using Email/API Key authentication"
|
|
else
|
|
log_error "Missing Cloudflare credentials"
|
|
log_error "Required: CLOUDFLARE_API_TOKEN OR (CLOUDFLARE_EMAIL + CLOUDFLARE_API_KEY)"
|
|
exit 1
|
|
fi
|
|
|
|
# Zone IDs (can be set in .env or passed as parameters)
|
|
ZONE_SANKOFA_NEXUS="${CLOUDFLARE_ZONE_ID_SANKOFA_NEXUS:-}"
|
|
ZONE_D_BIS_ORG="${CLOUDFLARE_ZONE_ID_D_BIS_ORG:-${CLOUDFLARE_ZONE_ID:-}}"
|
|
ZONE_MIM4U_ORG="${CLOUDFLARE_ZONE_ID_MIM4U_ORG:-}"
|
|
ZONE_DEFI_ORACLE_IO="${CLOUDFLARE_ZONE_ID_DEFI_ORACLE_IO:-}"
|
|
|
|
# Function to make Cloudflare API request
|
|
cf_api_request() {
|
|
local method="$1"
|
|
local zone_id="$2"
|
|
local endpoint="$3"
|
|
local data="${4:-}"
|
|
|
|
local url="https://api.cloudflare.com/client/v4/zones/${zone_id}${endpoint}"
|
|
|
|
if [ -n "${CLOUDFLARE_API_TOKEN:-}" ]; then
|
|
if [ -n "$data" ]; then
|
|
curl -s -X "$method" "$url" \
|
|
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
--data "$data"
|
|
else
|
|
curl -s -X "$method" "$url" \
|
|
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
|
|
-H "Content-Type: application/json"
|
|
fi
|
|
else
|
|
if [ -n "$data" ]; then
|
|
curl -s -X "$method" "$url" \
|
|
-H "X-Auth-Email: $AUTH_HEADER_EMAIL" \
|
|
-H "X-Auth-Key: $AUTH_HEADER_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
--data "$data"
|
|
else
|
|
curl -s -X "$method" "$url" \
|
|
-H "X-Auth-Email: $AUTH_HEADER_EMAIL" \
|
|
-H "X-Auth-Key: $AUTH_HEADER_KEY" \
|
|
-H "Content-Type: application/json"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Function to get existing DNS record
|
|
get_dns_record() {
|
|
local zone_id="$1"
|
|
local name="$2"
|
|
local type="${3:-A}"
|
|
|
|
local response=$(cf_api_request "GET" "$zone_id" "/dns_records?name=${name}&type=${type}")
|
|
echo "$response" | jq -r '.result[0] // empty' 2>/dev/null || echo ""
|
|
}
|
|
|
|
# Function to delete DNS record
|
|
delete_dns_record() {
|
|
local zone_id="$1"
|
|
local record_id="$2"
|
|
|
|
local response=$(cf_api_request "DELETE" "$zone_id" "/dns_records/$record_id")
|
|
if echo "$response" | jq -e '.success' >/dev/null 2>&1; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to get all DNS records (any type) for a name
|
|
get_all_dns_records() {
|
|
local zone_id="$1"
|
|
local name="$2"
|
|
|
|
local response=$(cf_api_request "GET" "$zone_id" "/dns_records?name=${name}")
|
|
echo "$response" | jq -r '.result[] // empty' 2>/dev/null || echo ""
|
|
}
|
|
|
|
# Function to create or update DNS A record
|
|
create_or_update_dns_record() {
|
|
local zone_id="$1"
|
|
local zone_name="$2"
|
|
local name="$3"
|
|
local ip="$4"
|
|
local proxied="${5:-false}"
|
|
|
|
# Handle apex domain (@ symbol) and construct full domain name
|
|
local full_name
|
|
if [ "$name" = "@" ]; then
|
|
full_name="${zone_name}"
|
|
elif [[ "$name" == *".${zone_name}" ]] || [[ "$name" == "${zone_name}" ]]; then
|
|
# Already a full domain name (ends with zone name or is zone name)
|
|
full_name="${name}"
|
|
else
|
|
# Subdomain - append zone name
|
|
full_name="${name}.${zone_name}"
|
|
fi
|
|
|
|
log_info "Processing: $full_name → $ip (proxied: $proxied)"
|
|
|
|
# Check for existing CNAME records (must delete before creating A record)
|
|
local all_records=$(get_all_dns_records "$zone_id" "$full_name")
|
|
if [ -n "$all_records" ] && [ "$all_records" != "null" ]; then
|
|
echo "$all_records" | jq -r 'select(.type == "CNAME") | .id' | while read -r cname_id; do
|
|
if [ -n "$cname_id" ] && [ "$cname_id" != "null" ]; then
|
|
log_info " Deleting existing CNAME record (ID: ${cname_id:0:8}...)"
|
|
if delete_dns_record "$zone_id" "$cname_id"; then
|
|
log_success " Deleted CNAME record"
|
|
else
|
|
log_warn " Failed to delete CNAME record"
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Get existing A record
|
|
local existing=$(get_dns_record "$zone_id" "$full_name" "A")
|
|
|
|
local data=$(jq -n \
|
|
--arg name "$full_name" \
|
|
--arg content "$ip" \
|
|
--argjson proxied "$proxied" \
|
|
'{
|
|
type: "A",
|
|
name: $name,
|
|
content: $content,
|
|
proxied: $proxied,
|
|
ttl: 1
|
|
}')
|
|
|
|
if [ -n "$existing" ] && [ "$existing" != "null" ]; then
|
|
local record_id=$(echo "$existing" | jq -r '.id')
|
|
log_info " Updating existing record (ID: ${record_id:0:8}...)"
|
|
|
|
local response=$(cf_api_request "PUT" "$zone_id" "/dns_records/$record_id" "$data")
|
|
|
|
if echo "$response" | jq -e '.success' >/dev/null 2>&1; then
|
|
log_success " Updated: $full_name"
|
|
return 0
|
|
else
|
|
local error=$(echo "$response" | jq -r '.errors[0].message // "Unknown error"' 2>/dev/null || echo "Unknown error")
|
|
log_error " Failed to update: $error"
|
|
return 1
|
|
fi
|
|
else
|
|
log_info " Creating new record"
|
|
|
|
local response=$(cf_api_request "POST" "$zone_id" "/dns_records" "$data")
|
|
|
|
if echo "$response" | jq -e '.success' >/dev/null 2>&1; then
|
|
log_success " Created: $full_name"
|
|
return 0
|
|
else
|
|
local error=$(echo "$response" | jq -r '.errors[0].message // "Unknown error"' 2>/dev/null || echo "Unknown error")
|
|
log_error " Failed to create: $error"
|
|
return 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Function to process a zone
|
|
process_zone() {
|
|
local zone_id="$1"
|
|
local zone_name="$2"
|
|
shift 2
|
|
local records=("$@")
|
|
|
|
if [ -z "$zone_id" ]; then
|
|
log_warn "Skipping zone $zone_name (no zone ID configured)"
|
|
return 0
|
|
fi
|
|
|
|
log_info ""
|
|
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
log_info "Processing Zone: $zone_name"
|
|
log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
local success_count=0
|
|
local fail_count=0
|
|
|
|
for record in "${records[@]}"; do
|
|
if create_or_update_dns_record "$zone_id" "$zone_name" "$record" "$PUBLIC_IP" "false"; then
|
|
((success_count++))
|
|
else
|
|
((fail_count++))
|
|
fi
|
|
done
|
|
|
|
log_info ""
|
|
log_info "Zone Summary: $success_count succeeded, $fail_count failed"
|
|
|
|
return $fail_count
|
|
}
|
|
|
|
# Main execution
|
|
main() {
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo "🔧 Cloudflare DNS Update - Direct Public IP Routing"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
log_info "Public IP: $PUBLIC_IP"
|
|
log_info "Proxy Mode: DNS Only (gray cloud)"
|
|
echo ""
|
|
|
|
local total_failures=0
|
|
|
|
# sankofa.nexus domain records
|
|
if [ -n "$ZONE_SANKOFA_NEXUS" ]; then
|
|
SANKOFA_RECORDS=(
|
|
"@" # sankofa.nexus
|
|
"www" # www.sankofa.nexus
|
|
"phoenix" # phoenix.sankofa.nexus
|
|
"www.phoenix" # www.phoenix.sankofa.nexus
|
|
"the-order" # the-order.sankofa.nexus
|
|
)
|
|
if ! process_zone "$ZONE_SANKOFA_NEXUS" "sankofa.nexus" "${SANKOFA_RECORDS[@]}"; then
|
|
((total_failures++))
|
|
fi
|
|
else
|
|
log_warn "Skipping sankofa.nexus (no zone ID configured)"
|
|
fi
|
|
|
|
# d-bis.org domain records
|
|
if [ -n "$ZONE_D_BIS_ORG" ]; then
|
|
DBIS_RECORDS=(
|
|
"rpc-http-pub" # rpc-http-pub.d-bis.org
|
|
"rpc-ws-pub" # rpc-ws-pub.d-bis.org
|
|
"rpc" # rpc.d-bis.org (primary RPC)
|
|
"rpc2" # rpc2.d-bis.org (secondary RPC)
|
|
"ws.rpc" # ws.rpc.d-bis.org (primary WebSocket)
|
|
"ws.rpc2" # ws.rpc2.d-bis.org (secondary WebSocket)
|
|
"rpc-http-prv" # rpc-http-prv.d-bis.org
|
|
"rpc-ws-prv" # rpc-ws-prv.d-bis.org
|
|
"explorer" # explorer.d-bis.org
|
|
"dbis-admin" # dbis-admin.d-bis.org
|
|
"dbis-api" # dbis-api.d-bis.org
|
|
"dbis-api-2" # dbis-api-2.d-bis.org
|
|
"secure" # secure.d-bis.org
|
|
)
|
|
if ! process_zone "$ZONE_D_BIS_ORG" "d-bis.org" "${DBIS_RECORDS[@]}"; then
|
|
((total_failures++))
|
|
fi
|
|
else
|
|
log_warn "Skipping d-bis.org (no zone ID configured)"
|
|
fi
|
|
|
|
# mim4u.org domain records
|
|
if [ -n "$ZONE_MIM4U_ORG" ]; then
|
|
MIM4U_RECORDS=(
|
|
"@" # mim4u.org
|
|
"www" # www.mim4u.org
|
|
"secure" # secure.mim4u.org
|
|
"training" # training.mim4u.org
|
|
)
|
|
if ! process_zone "$ZONE_MIM4U_ORG" "mim4u.org" "${MIM4U_RECORDS[@]}"; then
|
|
((total_failures++))
|
|
fi
|
|
else
|
|
log_warn "Skipping mim4u.org (no zone ID configured)"
|
|
fi
|
|
|
|
# defi-oracle.io domain records
|
|
if [ -n "$ZONE_DEFI_ORACLE_IO" ]; then
|
|
DEFI_ORACLE_RECORDS=(
|
|
"explorer" # explorer.defi-oracle.io (Blockscout - same as explorer.d-bis.org)
|
|
"rpc.public-0138" # rpc.public-0138.defi-oracle.io
|
|
"rpc" # rpc.defi-oracle.io (HTTP RPC)
|
|
"wss" # wss.defi-oracle.io (WebSocket RPC)
|
|
)
|
|
if ! process_zone "$ZONE_DEFI_ORACLE_IO" "defi-oracle.io" "${DEFI_ORACLE_RECORDS[@]}"; then
|
|
((total_failures++))
|
|
fi
|
|
else
|
|
log_warn "Skipping defi-oracle.io (no zone ID configured)"
|
|
fi
|
|
|
|
# Summary
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
if [ $total_failures -eq 0 ]; then
|
|
log_success "✅ DNS Update Complete"
|
|
else
|
|
log_warn "⚠️ DNS Update Complete with $total_failures zone(s) having failures"
|
|
fi
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
log_info "📋 Summary:"
|
|
log_info " • All records point to: $PUBLIC_IP"
|
|
log_info " • Proxy mode: DNS Only (gray cloud)"
|
|
log_info " • Routing: Direct NAT → Nginx → Backend Services"
|
|
echo ""
|
|
log_info "⏳ Wait 1-5 minutes for DNS propagation"
|
|
log_info "🧪 Test with: dig sankofa.nexus +short"
|
|
log_info "🧪 Test with: dig secure.d-bis.org +short"
|
|
echo ""
|
|
|
|
return $total_failures
|
|
}
|
|
|
|
# Run main function
|
|
main "$@"
|