#!/usr/bin/env bash # Deploy All - Complete deployment automation for ChainID 138 # This script automates the entire deployment process including infrastructure, contracts, and MetaMask integration set -euo pipefail # Configuration SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../lib/init.sh" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" DEPLOYMENT_LOG="${PROJECT_ROOT}/deployment.log" CONTRACT_ADDRESSES_FILE="${PROJECT_ROOT}/contracts-deployed.json" # Load environment variables if [ -f "${PROJECT_ROOT}/.env" ]; then set -a source "${PROJECT_ROOT}/.env" set +a else log_error "Error: .env file not found. Please create .env file from .env.example" exit 1 fi # Logging function log() { log_success "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$DEPLOYMENT_LOG" } error() { log_error "[ERROR] $1" | tee -a "$DEPLOYMENT_LOG" exit 1 } warn() { log_warn "[WARNING] $1" | tee -a "$DEPLOYMENT_LOG" } # Check Azure authentication check_azure_auth() { log "Checking Azure authentication..." # Check if Azure CLI is installed if ! command -v az &> /dev/null; then error "Azure CLI is not installed. Please install it first: https://docs.microsoft.com/cli/azure/install-azure-cli" fi # Check if user is logged in if ! az account show &> /dev/null; then error "Azure CLI is not authenticated. Please run 'az login' first." error " error "For WSL users:" error " 1. Run 'az login' in WSL" error " 2. Or use service principal: az login --service-principal -u -p --tenant " error " 3. Or use managed identity if running on Azure VM" error " error "After logging in, verify with: az account show" exit 1 fi # Get current subscription local current_sub=$(az account show --query id -o tsv 2>/dev/null || echo "") if [ -z "$current_sub" ]; then error "Failed to get current Azure subscription" exit 1 fi # Check if subscription matches (if AZURE_SUBSCRIPTION_ID is set) if [ -n "${AZURE_SUBSCRIPTION_ID:-}" ] && [ "$current_sub" != "$AZURE_SUBSCRIPTION_ID" ]; then warn "Current Azure subscription ($current_sub) does not match AZURE_SUBSCRIPTION_ID ($AZURE_SUBSCRIPTION_ID)" warn "Setting subscription to: $AZURE_SUBSCRIPTION_ID" az account set --subscription "$AZURE_SUBSCRIPTION_ID" || error "Failed to set Azure subscription" fi log "Azure authentication verified" log "Current subscription: $current_sub" } # Check prerequisites check_prerequisites() { log "Checking prerequisites..." local missing=0 # Check required tools for cmd in az terraform kubectl helm forge cast jq; do if ! command -v "$cmd" &> /dev/null; then error "$cmd is not installed" missing=1 fi done # Check Azure authentication (if Azure-related tasks are not skipped) if [ "$SKIP_INFRASTRUCTURE" != "true" ] || [ "$SKIP_KUBERNETES" != "true" ] || [ "$SKIP_CLOUDFLARE" != "true" ]; then check_azure_auth fi # Check environment variables (conditional based on what's being deployed) local required_vars=() if [ "$SKIP_INFRASTRUCTURE" != "true" ] || [ "$SKIP_KUBERNETES" != "true" ] || [ "$SKIP_CLOUDFLARE" != "true" ]; then required_vars+=("AZURE_SUBSCRIPTION_ID" "AZURE_RESOURCE_GROUP") fi if [ "$SKIP_CLOUDFLARE" != "true" ]; then required_vars+=("CLOUDFLARE_API_TOKEN" "CLOUDFLARE_ZONE_ID") fi if [ "$SKIP_CONTRACTS" != "true" ]; then required_vars+=("RPC_URL" "PRIVATE_KEY") fi for var in "${required_vars[@]}"; do if [ -z "${!var:-}" ]; then error "Required environment variable $var is not set" fi done if [ $missing -eq 0 ]; then log "All prerequisites met" fi } # Deploy Azure Infrastructure deploy_infrastructure() { log "Deploying Azure infrastructure..." cd "${PROJECT_ROOT}/terraform" || error "Failed to change directory to terraform" # Initialize Terraform if [ ! -d ".terraform" ]; then log "Initializing Terraform..." terraform init -upgrade || error "Terraform initialization failed" fi # Plan deployment log "Planning Terraform deployment..." terraform plan -out=tfplan || error "Terraform plan failed" # Apply deployment log "Applying Terraform deployment..." terraform apply tfplan || error "Terraform apply failed" # Get outputs (if they exist) log "Getting Terraform outputs..." AKS_NAME=$(terraform output -raw aks_name 2>/dev/null || echo "") AKS_RG=$(terraform output -raw aks_resource_group 2>/dev/null || echo "$AZURE_RESOURCE_GROUP") # Configure kubectl (if AKS name is available) if [ -n "$AKS_NAME" ]; then log "Configuring kubectl..." az aks get-credentials --resource-group "$AKS_RG" --name "$AKS_NAME" --overwrite-existing || error "Failed to get AKS credentials" else warn "AKS name not found in Terraform outputs, skipping kubectl configuration" fi log "Infrastructure deployment completed" } # Deploy Kubernetes Resources deploy_kubernetes() { log "Deploying Kubernetes resources..." # Create namespace kubectl create namespace besu-network --dry-run=client -o yaml | kubectl apply -f - || warn "Namespace may already exist" # Deploy validators log "Deploying validators..." helm upgrade --install besu-validators "${PROJECT_ROOT}/helm/besu-network" \ -f "${PROJECT_ROOT}/helm/besu-network/values-validators.yaml" \ -n besu-network \ --wait --timeout=10m || error "Failed to deploy validators" # Deploy sentries log "Deploying sentries..." helm upgrade --install besu-sentries "${PROJECT_ROOT}/helm/besu-network" \ -f "${PROJECT_ROOT}/helm/besu-network/values-sentries.yaml" \ -n besu-network \ --wait --timeout=10m || error "Failed to deploy sentries" # Deploy RPC nodes log "Deploying RPC nodes..." helm upgrade --install besu-rpc "${PROJECT_ROOT}/helm/besu-network" \ -f "${PROJECT_ROOT}/helm/besu-network/values-rpc.yaml" \ -n besu-network \ --wait --timeout=10m || error "Failed to deploy RPC nodes" # Wait for RPC nodes to be ready log "Waiting for RPC nodes to be ready..." kubectl wait --for=condition=ready pod -l component=rpc -n besu-network --timeout=300s || error "RPC nodes not ready" log "Kubernetes deployment completed" } # Deploy Blockscout deploy_blockscout() { log "Deploying Blockscout..." # Deploy Blockscout database kubectl apply -f "${PROJECT_ROOT}/k8s/blockscout/deployment.yaml" || error "Failed to deploy Blockscout" # Wait for database to be ready log "Waiting for Blockscout database to be ready..." kubectl wait --for=condition=ready pod -l app=blockscout-db -n besu-network --timeout=300s || error "Blockscout database not ready" # Wait for Blockscout to be ready log "Waiting for Blockscout to be ready..." kubectl wait --for=condition=ready pod -l app=blockscout -n besu-network --timeout=600s || error "Blockscout not ready" log "Blockscout deployment completed" } # Deploy Contracts deploy_contracts() { log "Deploying contracts..." cd "${PROJECT_ROOT}" || error "Failed to change directory to project root" # Initialize contract addresses file echo "{}" > "$CONTRACT_ADDRESSES_FILE" # Deploy WETH log "Deploying WETH..." WETH_ADDRESS=$(forge script script/DeployWETH.s.sol \ --rpc-url "$RPC_URL" \ --broadcast \ --private-key "$PRIVATE_KEY" \ -vvv 2>&1 | grep -oP 'Contract deployed at: \K0x[a-fA-F0-9]{40}' || echo "") if [ -z "$WETH_ADDRESS" ]; then error "Failed to deploy WETH" fi log "WETH deployed at: $WETH_ADDRESS" jq ".weth = \"$WETH_ADDRESS\"" "$CONTRACT_ADDRESSES_FILE" > "${CONTRACT_ADDRESSES_FILE}.tmp" && mv "${CONTRACT_ADDRESSES_FILE}.tmp" "$CONTRACT_ADDRESSES_FILE" # Deploy Multicall individually (if not using Deploy.s.sol) if [ -z "$MULTICALL_ADDRESS" ] || [ "$MULTICALL_ADDRESS" = "null" ]; then log "Deploying Multicall..." DEPLOY_OUTPUT=$(forge script script/DeployMulticall.s.sol \ --rpc-url "$RPC_URL" \ --broadcast \ --private-key "$PRIVATE_KEY" \ -vvv 2>&1 | tee "${PROJECT_ROOT}/deploy-multicall.log" || echo "") MULTICALL_ADDRESS=$(echo "$DEPLOY_OUTPUT" | grep -oP 'Contract deployed at: \K0x[a-fA-F0-9]{40}' || \ echo "$DEPLOY_OUTPUT" | grep -oP 'Deployed to: \K0x[a-fA-F0-9]{40}' || \ jq -r '.transactions[0].contractAddress // empty' "${PROJECT_ROOT}/broadcast/DeployMulticall.s.sol/138/run-latest.json" 2>/dev/null || echo "") if [ -z "$MULTICALL_ADDRESS" ] || [ "$MULTICALL_ADDRESS" = "null" ]; then error "Failed to deploy Multicall. Check deployment logs: ${PROJECT_ROOT}/deploy-multicall.log" fi fi log "Multicall deployed at: $MULTICALL_ADDRESS" jq ".multicall = \"$MULTICALL_ADDRESS\"" "$CONTRACT_ADDRESSES_FILE" > "${CONTRACT_ADDRESSES_FILE}.tmp" && mv "${CONTRACT_ADDRESSES_FILE}.tmp" "$CONTRACT_ADDRESSES_FILE" # Deploy Oracle Aggregator individually (if not using Deploy.s.sol) if [ -z "$ORACLE_ADDRESS" ] || [ "$ORACLE_ADDRESS" = "null" ]; then log "Deploying Oracle Aggregator..." DEPLOY_OUTPUT=$(forge script script/DeployOracle.s.sol \ --rpc-url "$RPC_URL" \ --broadcast \ --private-key "$PRIVATE_KEY" \ -vvv 2>&1 | tee "${PROJECT_ROOT}/deploy-oracle.log" || echo "") ORACLE_ADDRESS=$(echo "$DEPLOY_OUTPUT" | grep -oP 'Contract deployed at: \K0x[a-fA-F0-9]{40}' || \ echo "$DEPLOY_OUTPUT" | grep -oP 'Deployed to: \K0x[a-fA-F0-9]{40}' || \ jq -r '.transactions[0].contractAddress // empty' "${PROJECT_ROOT}/broadcast/DeployOracle.s.sol/138/run-latest.json" 2>/dev/null || echo "") if [ -z "$ORACLE_ADDRESS" ] || [ "$ORACLE_ADDRESS" = "null" ]; then error "Failed to deploy Oracle Aggregator. Check deployment logs: ${PROJECT_ROOT}/deploy-oracle.log" fi fi log "Oracle Aggregator deployed at: $ORACLE_ADDRESS" jq ".oracle = \"$ORACLE_ADDRESS\"" "$CONTRACT_ADDRESSES_FILE" > "${CONTRACT_ADDRESSES_FILE}.tmp" && mv "${CONTRACT_ADDRESSES_FILE}.tmp" "$CONTRACT_ADDRESSES_FILE" # Save all addresses if [ -n "$WETH_ADDRESS" ] && [ "$WETH_ADDRESS" != "null" ]; then jq ".weth = \"$WETH_ADDRESS\"" "$CONTRACT_ADDRESSES_FILE" > "${CONTRACT_ADDRESSES_FILE}.tmp" && mv "${CONTRACT_ADDRESSES_FILE}.tmp" "$CONTRACT_ADDRESSES_FILE" fi if [ -n "$MULTICALL_ADDRESS" ] && [ "$MULTICALL_ADDRESS" != "null" ]; then jq ".multicall = \"$MULTICALL_ADDRESS\"" "$CONTRACT_ADDRESSES_FILE" > "${CONTRACT_ADDRESSES_FILE}.tmp" && mv "${CONTRACT_ADDRESSES_FILE}.tmp" "$CONTRACT_ADDRESSES_FILE" fi if [ -n "$ORACLE_ADDRESS" ] && [ "$ORACLE_ADDRESS" != "null" ]; then jq ".oracle = \"$ORACLE_ADDRESS\"" "$CONTRACT_ADDRESSES_FILE" > "${CONTRACT_ADDRESSES_FILE}.tmp" && mv "${CONTRACT_ADDRESSES_FILE}.tmp" "$CONTRACT_ADDRESSES_FILE" fi # Deploy CCIP Router (optional) if [ -f "${PROJECT_ROOT}/script/DeployCCIPRouter.s.sol" ]; then log "Deploying CCIP Router..." DEPLOY_OUTPUT=$(forge script script/DeployCCIPRouter.s.sol \ --rpc-url "$RPC_URL" \ --broadcast \ --private-key "$PRIVATE_KEY" \ -vvv 2>&1 | tee "${PROJECT_ROOT}/deploy-ccip.log" || echo "") CCIP_ROUTER_ADDRESS=$(echo "$DEPLOY_OUTPUT" | grep -oP 'Contract deployed at: \K0x[a-fA-F0-9]{40}' || \ echo "$DEPLOY_OUTPUT" | grep -oP 'Deployed to: \K0x[a-fA-F0-9]{40}' || \ jq -r '.transactions[0].contractAddress // empty' "${PROJECT_ROOT}/broadcast/DeployCCIPRouter.s.sol/138/run-latest.json" 2>/dev/null || echo "") if [ -n "$CCIP_ROUTER_ADDRESS" ] && [ "$CCIP_ROUTER_ADDRESS" != "null" ]; then log "CCIP Router deployed at: $CCIP_ROUTER_ADDRESS" jq ".ccipRouter = \"$CCIP_ROUTER_ADDRESS\"" "$CONTRACT_ADDRESSES_FILE" > "${CONTRACT_ADDRESSES_FILE}.tmp" && mv "${CONTRACT_ADDRESSES_FILE}.tmp" "$CONTRACT_ADDRESSES_FILE" else warn "CCIP Router deployment failed (may be optional)" fi else warn "CCIP Router deployment script not found, skipping" fi log "Contract deployment completed" log "Contract addresses saved to: $CONTRACT_ADDRESSES_FILE" } # Update Token List update_token_list() { log "Updating token list with deployed addresses..." if [ ! -f "$CONTRACT_ADDRESSES_FILE" ]; then error "Contract addresses file not found: $CONTRACT_ADDRESSES_FILE" fi WETH_ADDRESS=$(jq -r '.weth' "$CONTRACT_ADDRESSES_FILE") if [ -z "$WETH_ADDRESS" ] || [ "$WETH_ADDRESS" = "null" ]; then error "WETH address not found in contract addresses file" fi # Update token-list.json log "Updating token-list.json with WETH address: $WETH_ADDRESS" jq ".tokens[0].address = \"$WETH_ADDRESS\"" "${PROJECT_ROOT}/metamask/token-list.json" > "${PROJECT_ROOT}/metamask/token-list.json.tmp" && \ mv "${PROJECT_ROOT}/metamask/token-list.json.tmp" "${PROJECT_ROOT}/metamask/token-list.json" log "Token list updated" } # Configure Cloudflare DNS configure_cloudflare_dns() { log "Configuring Cloudflare DNS..." # Get Application Gateway IP cd "${PROJECT_ROOT}/terraform" || error "Failed to change directory to terraform" APP_GATEWAY_NAME=$(terraform output -raw app_gateway_name 2>/dev/null || echo "") if [ -z "$APP_GATEWAY_NAME" ]; then error "Application Gateway name not found in Terraform outputs" fi APP_GATEWAY_IP=$(az network application-gateway show \ --resource-group "$AZURE_RESOURCE_GROUP" \ --name "$APP_GATEWAY_NAME" \ --query "frontendIPConfigurations[0].publicIpAddress.id" \ -o tsv 2>/dev/null | xargs az network public-ip show --ids --query ipAddress -o tsv 2>/dev/null || echo "") if [ -z "$APP_GATEWAY_IP" ]; then error "Failed to get Application Gateway IP. Please provide IP address manually or check Application Gateway configuration." fi log "Application Gateway IP: $APP_GATEWAY_IP" cd "${PROJECT_ROOT}" || error "Failed to change directory to project root" # Create DNS records "${PROJECT_ROOT}/scripts/deployment/cloudflare-dns.sh" \ --zone-id "$CLOUDFLARE_ZONE_ID" \ --api-token "$CLOUDFLARE_API_TOKEN" \ --ip "$APP_GATEWAY_IP" || error "Failed to configure Cloudflare DNS" log "Cloudflare DNS configured" } # Verify Deployment verify_deployment() { log "Verifying deployment..." # Check RPC endpoint log "Checking RPC endpoint..." if curl -s -X POST "$RPC_URL" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ | jq -e '.result' > /dev/null; then log "RPC endpoint is accessible" else error "RPC endpoint is not accessible" fi # Check Blockscout EXPLORER_URL="${EXPLORER_URL:-https://explorer.d-bis.org}" log "Checking Blockscout explorer..." if curl -s -f "$EXPLORER_URL" > /dev/null; then log "Blockscout explorer is accessible" else warn "Blockscout explorer is not accessible (may take time to start)" fi # Check contracts log "Checking deployed contracts..." if [ -f "$CONTRACT_ADDRESSES_FILE" ]; then WETH_ADDRESS=$(jq -r '.weth' "$CONTRACT_ADDRESSES_FILE") if [ -n "$WETH_ADDRESS" ] && [ "$WETH_ADDRESS" != "null" ]; then # Verify contract code CODE=$(cast code "$WETH_ADDRESS" --rpc-url "$RPC_URL" || echo "0x") if [ "$CODE" != "0x" ]; then log "WETH contract verified at: $WETH_ADDRESS" else warn "WETH contract code not found (may need to wait for block confirmation)" fi fi fi log "Deployment verification completed" } # Parse command line arguments parse_args() { SKIP_INFRASTRUCTURE=false SKIP_KUBERNETES=false SKIP_BLOCKSCOUT=false SKIP_CONTRACTS=false SKIP_CLOUDFLARE=false SKIP_TOKEN_LIST=false while [[ $# -gt 0 ]]; do case $1 in --skip-infrastructure) SKIP_INFRASTRUCTURE=true shift ;; --skip-kubernetes) SKIP_KUBERNETES=true shift ;; --skip-blockscout) SKIP_BLOCKSCOUT=true shift ;; --skip-contracts) SKIP_CONTRACTS=true shift ;; --skip-cloudflare) SKIP_CLOUDFLARE=true shift ;; --skip-token-list) SKIP_TOKEN_LIST=true shift ;; --help) echo "Usage: $0 [options]" echo "Options:" echo " --skip-infrastructure Skip infrastructure deployment" echo " --skip-kubernetes Skip Kubernetes deployment" echo " --skip-blockscout Skip Blockscout deployment" echo " --skip-contracts Skip contract deployment" echo " --skip-cloudflare Skip Cloudflare DNS configuration" echo " --skip-token-list Skip token list update" echo " --help Show this help message" exit 0 ;; *) error "Unknown option: $1. Use --help for usage information." ;; esac done } # Main deployment function main() { log "Starting complete deployment process..." log "Deployment log: $DEPLOYMENT_LOG" # Parse command line arguments parse_args "$@" # Step 0: Check Azure authentication (if needed) if [ "$SKIP_INFRASTRUCTURE" != "true" ] || [ "$SKIP_KUBERNETES" != "true" ] || [ "$SKIP_CLOUDFLARE" != "true" ]; then log "Azure authentication is required for this deployment" log "If not already logged in, run: ./scripts/deployment/azure-login.sh" log "Or for WSL users: az login" fi # Step 1: Check prerequisites check_prerequisites # Step 2: Deploy infrastructure if [ "$SKIP_INFRASTRUCTURE" != "true" ]; then deploy_infrastructure else log "Skipping infrastructure deployment" fi # Step 3: Deploy Kubernetes resources if [ "$SKIP_KUBERNETES" != "true" ]; then deploy_kubernetes else log "Skipping Kubernetes deployment" fi # Step 4: Deploy Blockscout if [ "$SKIP_BLOCKSCOUT" != "true" ]; then deploy_blockscout else log "Skipping Blockscout deployment" fi # Step 5: Configure Cloudflare DNS if [ "$SKIP_CLOUDFLARE" != "true" ]; then configure_cloudflare_dns else log "Skipping Cloudflare DNS configuration" fi # Step 6: Deploy contracts if [ "$SKIP_CONTRACTS" != "true" ]; then deploy_contracts else log "Skipping contract deployment" fi # Step 7: Update token list if [ "$SKIP_TOKEN_LIST" != "true" ] && [ "$SKIP_CONTRACTS" != "true" ]; then update_token_list else log "Skipping token list update" fi # Step 8: Verify deployment verify_deployment log "Deployment completed successfully!" log "Contract addresses: $CONTRACT_ADDRESSES_FILE" log "Next steps:" log " 1. Verify contracts on Blockscout: $EXPLORER_URL" log " 2. Update token-list.json with deployed addresses" log " 3. Submit Ethereum-Lists PR: ./scripts/deployment/submit-ethereum-lists-pr.sh" log " 4. Submit token list: ./scripts/deployment/submit-token-list.sh" } # Run main function main "$@"