|
| 1 | +#!/bin/bash |
| 2 | +set -euo pipefail |
| 3 | + |
| 4 | +# Variables injected by CDK via Fn::Sub |
| 5 | +REGION="${_REGION_}" |
| 6 | +ASSETS_S3_PATH="${_ASSETS_S3_PATH_}" |
| 7 | +POLYGON_NETWORK="${_POLYGON_NETWORK_}" |
| 8 | +POLYGON_ERIGON_IMAGE="${_POLYGON_ERIGON_IMAGE_}" |
| 9 | +POLYGON_HEIMDALL_API_URL="${_POLYGON_HEIMDALL_API_URL_}" |
| 10 | +STACK_NAME="${_STACK_NAME_}" |
| 11 | +DATA_VOLUME_TYPE="${_DATA_VOLUME_TYPE_}" |
| 12 | +DATA_VOLUME_SIZE="${_DATA_VOLUME_SIZE_}" |
| 13 | + |
| 14 | +# Map network name to Erigon chain name |
| 15 | +case "$POLYGON_NETWORK" in |
| 16 | + mainnet) POLYGON_CHAIN_NAME="bor-mainnet" ;; |
| 17 | + amoy) POLYGON_CHAIN_NAME="amoy" ;; |
| 18 | + *) POLYGON_CHAIN_NAME="bor-mainnet" ;; |
| 19 | +esac |
| 20 | + |
| 21 | +echo "========== Polygon Node Setup Starting ==========" |
| 22 | +echo "Network: $POLYGON_NETWORK (chain: $POLYGON_CHAIN_NAME)" |
| 23 | +echo "Erigon Image: $POLYGON_ERIGON_IMAGE" |
| 24 | + |
| 25 | +# Install dependencies |
| 26 | +yum update -y |
| 27 | +yum install -y docker jq aws-cfn-bootstrap amazon-cloudwatch-agent cronie |
| 28 | + |
| 29 | +# Start Docker |
| 30 | +systemctl enable docker |
| 31 | +systemctl start docker |
| 32 | + |
| 33 | +# Install docker-compose |
| 34 | +ARCH=$(uname -m) |
| 35 | +curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-linux-$ARCH" -o /usr/local/bin/docker-compose |
| 36 | +chmod +x /usr/local/bin/docker-compose |
| 37 | + |
| 38 | +# Format and mount data volume |
| 39 | +# Note: CloudFormation VolumeAttachment may take several minutes after instance launch. |
| 40 | +# We poll for up to 10 minutes for the device to appear. |
| 41 | +DATA_DIR="/data" |
| 42 | +mkdir -p "$DATA_DIR" |
| 43 | +if [ "$DATA_VOLUME_TYPE" != "instance-store" ]; then |
| 44 | + echo "Waiting for EBS data volume to be attached..." |
| 45 | + WAIT_SECONDS=0 |
| 46 | + MAX_WAIT=600 |
| 47 | + DEVICE="" |
| 48 | + while [ $WAIT_SECONDS -lt $MAX_WAIT ]; do |
| 49 | + # Look for unformatted nvme device larger than 100GB (skip root volume) |
| 50 | + DEVICE=$(lsblk -lnb | awk '{if ($7 == "" && $4 > 100000000000) {print "/dev/"$1}}' | grep nvme | sort | head -1) |
| 51 | + if [ -n "$DEVICE" ]; then |
| 52 | + echo "Found data volume: $DEVICE after $WAIT_SECONDS seconds" |
| 53 | + break |
| 54 | + fi |
| 55 | + # Also check traditional device names |
| 56 | + for dev in /dev/sdf /dev/xvdf; do |
| 57 | + if [ -e "$dev" ]; then DEVICE="$dev"; break 2; fi |
| 58 | + done |
| 59 | + sleep 10 |
| 60 | + WAIT_SECONDS=$((WAIT_SECONDS + 10)) |
| 61 | + echo "Waiting for data volume... ($WAIT_SECONDS seconds/$MAX_WAIT seconds)" |
| 62 | + done |
| 63 | + |
| 64 | + if [ -n "$DEVICE" ]; then |
| 65 | + echo "Using device: $DEVICE" |
| 66 | + if ! blkid "$DEVICE" 2>/dev/null; then |
| 67 | + mkfs.xfs "$DEVICE" |
| 68 | + fi |
| 69 | + mount "$DEVICE" "$DATA_DIR" |
| 70 | + VOLUME_UUID=$(blkid -s UUID -o value "$DEVICE") |
| 71 | + echo "UUID=$VOLUME_UUID $DATA_DIR xfs defaults,nofail 0 2" >> /etc/fstab |
| 72 | + else |
| 73 | + echo "WARNING: No data volume found after $MAX_WAIT seconds. Using root volume for data." |
| 74 | + fi |
| 75 | +fi |
| 76 | + |
| 77 | +# Create data directory |
| 78 | +mkdir -p "$DATA_DIR/erigon" |
| 79 | +chmod -R 777 "$DATA_DIR/erigon" |
| 80 | + |
| 81 | +# Create docker-compose file |
| 82 | +cat > /home/ec2-user/docker-compose.yml << COMPOSEOF |
| 83 | +services: |
| 84 | + erigon: |
| 85 | + image: $POLYGON_ERIGON_IMAGE |
| 86 | + container_name: erigon |
| 87 | + restart: always |
| 88 | + command: |
| 89 | + - --chain=$POLYGON_CHAIN_NAME |
| 90 | + - --bor.heimdall=$POLYGON_HEIMDALL_API_URL |
| 91 | + - --datadir=/var/lib/erigon/data |
| 92 | + - --http |
| 93 | + - --http.api=eth,debug,net,trace,web3,erigon,txpool,bor |
| 94 | + - --http.addr=0.0.0.0 |
| 95 | + - --http.vhosts=* |
| 96 | + - --torrent.download.rate=512mb |
| 97 | + - --metrics |
| 98 | + - --metrics.addr=0.0.0.0 |
| 99 | + - --maxpeers=100 |
| 100 | + ports: |
| 101 | + - "8545:8545" |
| 102 | + - "30303:30303/tcp" |
| 103 | + - "30303:30303/udp" |
| 104 | + - "42069:42069/tcp" |
| 105 | + - "42069:42069/udp" |
| 106 | + volumes: |
| 107 | + - $DATA_DIR/erigon:/var/lib/erigon/data |
| 108 | +COMPOSEOF |
| 109 | + |
| 110 | +# Start services |
| 111 | +cd /home/ec2-user |
| 112 | +/usr/local/bin/docker-compose up -d |
| 113 | + |
| 114 | +# Setup sync checker cron (publish metrics to CloudWatch every 5 min) |
| 115 | +cat > /home/ec2-user/sync-checker.sh << 'CHECKEREOF' |
| 116 | +#!/bin/bash |
| 117 | +INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id) |
| 118 | +
|
| 119 | +# Check if Erigon is syncing |
| 120 | +SYNCING=$(curl -s -X POST -H "Content-Type: application/json" \ |
| 121 | + --data '{"method":"eth_syncing","params":[],"id":1,"jsonrpc":"2.0"}' \ |
| 122 | + http://localhost:8545 2>/dev/null | jq -r '.result') |
| 123 | +
|
| 124 | +BLOCK_HEX=$(curl -s -X POST -H "Content-Type: application/json" \ |
| 125 | + --data '{"method":"eth_blockNumber","params":[],"id":1,"jsonrpc":"2.0"}' \ |
| 126 | + http://localhost:8545 2>/dev/null | jq -r '.result // "0x0"') |
| 127 | +BLOCK=$((BLOCK_HEX)) |
| 128 | +
|
| 129 | +IS_SYNCING=1 |
| 130 | +if [ "$SYNCING" = "false" ]; then IS_SYNCING=0; fi |
| 131 | +
|
| 132 | +aws cloudwatch put-metric-data --namespace "Polygon/Node" --dimensions InstanceId="$INSTANCE_ID" \ |
| 133 | + --metric-data "[{\"MetricName\":\"ErigonBlockHeight\",\"Value\":$BLOCK,\"Unit\":\"Count\"},{\"MetricName\":\"ErigonSyncing\",\"Value\":$IS_SYNCING,\"Unit\":\"Count\"}]" 2>/dev/null || true |
| 134 | +CHECKEREOF |
| 135 | + |
| 136 | +chmod +x /home/ec2-user/sync-checker.sh |
| 137 | +echo "*/5 * * * * /home/ec2-user/sync-checker.sh" | crontab - |
| 138 | + |
| 139 | +# Note: cfn-signal is not used because CreationPolicy is disabled |
| 140 | +# (avoids circular dependency with VolumeAttachment). |
| 141 | +# Node health is monitored via CloudWatch metrics instead. |
| 142 | + |
| 143 | +echo "========== Polygon Node Setup Complete ==========" |
0 commit comments