Back

3_network_deploy

#!/usr/bin/env bash
# ================================================================
#  VPC NETWORK — DEPLOY
#  Set Up a VPC with Public & Private Subnets,
#  Internet Gateway, NAT Gateway, EC2 in each subnet
#
#  AMI    : Latest Ubuntu Server LTS
#  TYPE   : t3.micro (Free Tier eligible)
#  USER   : ubuntu
#  RUN    : chmod +x 3_network_deploy.sh && ./3_network_deploy.sh
#  DELETE : ./3_network_cleanup.sh
#  STATE  : $HOME/aws_state_network.txt
#  ⚠️   NAT Gateway costs ~$0.045/hr — cleanup when done!
# ================================================================
set -euo pipefail

REGION="ap-south-1"
AMI_ID=$(aws ec2 describe-images --owners 099720109477 \
  --filters "Name=name,Values=ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*" \
  --region "$REGION" --query "sort_by(Images, &CreationDate)[-1].ImageId" --output text)
INSTANCE_TYPE="t3.micro"
KEY_NAME="Practical-KeyPair"
VPC_NAME="Practical-VPC"
PUB_SUB_NAME="Practical-Public-Subnet"
PRIV_SUB_NAME="Practical-Private-Subnet"
STATE_FILE="$HOME/aws_state_network.txt"

G='\033[0;32m'; B='\033[0;34m'; Y='\033[1;33m'; NC='\033[0m'
info()    { echo -e "${B}[INFO]${NC}  $*"; }
success() { echo -e "${G}[OK]${NC}    $*"; }
banner()  { echo -e "\n${Y}══════════════════════════════════════════${NC}"; \
            echo -e "${Y}  $*${NC}"; \
            echo -e "${Y}══════════════════════════════════════════${NC}\n"; }

banner "VPC NETWORK — Set Up Infrastructure"
echo "  AMI    : $AMI_ID (Ubuntu 24.04 LTS)"
echo "  Type   : $INSTANCE_TYPE | Region: $REGION"
echo ""

# ── Step 1: Key Pair ─────────────────────────────────────────
info "Step 1 — Key Pair: $KEY_NAME"
if aws ec2 describe-key-pairs --key-names "$KEY_NAME" --region "$REGION" >/dev/null 2>&1; then
  info "  Key Pair '$KEY_NAME' already exists. Using it."
else
  info "  Creating Key Pair: $KEY_NAME"
  aws ec2 create-key-pair --key-name "$KEY_NAME" --region "$REGION" \
    --query "KeyMaterial" --output text > "$HOME/${KEY_NAME}.pem"
  chmod 400 "$HOME/${KEY_NAME}.pem"
  success "Key Pair created → $HOME/${KEY_NAME}.pem"
fi

# ── Step 2: VPC ──────────────────────────────────────────────
info "Step 2 — VPC: $VPC_NAME"
VPC_ID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=$VPC_NAME" \
  --region "$REGION" --query "Vpcs[0].VpcId" --output text 2>/dev/null || echo "None")

if [[ "$VPC_ID" == "None" || "$VPC_ID" == "null" ]]; then
  info "  Creating VPC (10.10.0.0/16)..."
  VPC_ID=$(aws ec2 create-vpc --cidr-block "10.10.0.0/16" --region "$REGION" \
    --query "Vpc.VpcId" --output text)
  aws ec2 create-tags --resources "$VPC_ID" --region "$REGION" \
    --tags Key=Name,Value="$VPC_NAME"
  aws ec2 modify-vpc-attribute --vpc-id "$VPC_ID" --enable-dns-hostnames --region "$REGION"
  aws ec2 modify-vpc-attribute --vpc-id "$VPC_ID" --enable-dns-support   --region "$REGION"
  success "VPC created: $VPC_ID"
else
  info "  VPC '$VPC_NAME' already exists ($VPC_ID). Using it."
fi

# ── Step 3: Public Subnet ────────────────────────────────────
info "Step 3 — Public Subnet: $PUB_SUB_NAME"
PUB_SUBNET=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" "Name=tag:Name,Values=$PUB_SUB_NAME" \
  --region "$REGION" --query "Subnets[0].SubnetId" --output text 2>/dev/null || echo "None")

if [[ "$PUB_SUBNET" == "None" || "$PUB_SUBNET" == "null" ]]; then
  info "  Creating Public Subnet (10.10.1.0/24)..."
  AZ=$(aws ec2 describe-availability-zones --region "$REGION" --query "AvailabilityZones[0].ZoneName" --output text)
  PUB_SUBNET=$(aws ec2 create-subnet \
    --vpc-id "$VPC_ID" --cidr-block "10.10.1.0/24" \
    --availability-zone "$AZ" --region "$REGION" \
    --query "Subnet.SubnetId" --output text)
  aws ec2 create-tags --resources "$PUB_SUBNET" --region "$REGION" \
    --tags Key=Name,Value="$PUB_SUB_NAME" Key=Type,Value=Public
  aws ec2 modify-subnet-attribute \
    --subnet-id "$PUB_SUBNET" --map-public-ip-on-launch --region "$REGION"
  success "Public Subnet created: $PUB_SUBNET"
else
  info "  Public Subnet already exists ($PUB_SUBNET). Using it."
fi

# ── Step 4: Private Subnet ───────────────────────────────────
info "Step 4 — Private Subnet: $PRIV_SUB_NAME"
PRIV_SUBNET=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" "Name=tag:Name,Values=$PRIV_SUB_NAME" \
  --region "$REGION" --query "Subnets[0].SubnetId" --output text 2>/dev/null || echo "None")

if [[ "$PRIV_SUBNET" == "None" || "$PRIV_SUBNET" == "null" ]]; then
  info "  Creating Private Subnet (10.10.2.0/24)..."
  PRIV_SUBNET=$(aws ec2 create-subnet \
    --vpc-id "$VPC_ID" --cidr-block "10.10.2.0/24" \
    --availability-zone "$AZ" --region "$REGION" \
    --query "Subnet.SubnetId" --output text)
  aws ec2 create-tags --resources "$PRIV_SUBNET" --region "$REGION" \
    --tags Key=Name,Value="$PRIV_SUB_NAME" Key=Type,Value=Private
  success "Private Subnet created: $PRIV_SUBNET"
else
  info "  Private Subnet already exists ($PRIV_SUBNET). Using it."
fi

# ── Step 5: Internet Gateway ─────────────────────────────────
info "Step 5 — Internet Gateway"
IGW_ID=$(aws ec2 describe-internet-gateways --filters "Name=attachment.vpc-id,Values=$VPC_ID" \
  --region "$REGION" --query "InternetGateways[0].InternetGatewayId" --output text 2>/dev/null || echo "None")

if [[ "$IGW_ID" == "None" || "$IGW_ID" == "null" ]]; then
  info "  Creating & Attaching Internet Gateway..."
  IGW_ID=$(aws ec2 create-internet-gateway --region "$REGION" \
    --query "InternetGateway.InternetGatewayId" --output text)
  aws ec2 create-tags --resources "$IGW_ID" --region "$REGION" \
    --tags Key=Name,Value="Practical-IGW"
  aws ec2 attach-internet-gateway \
    --internet-gateway-id "$IGW_ID" --vpc-id "$VPC_ID" --region "$REGION"
  success "Internet Gateway created and attached: $IGW_ID"
else
  info "  Internet Gateway already exists and attached ($IGW_ID). Using it."
fi

# ── Step 6: Public Route Table ───────────────────────────────
info "Step 6 — Public Route Table"
PUB_RT=$(aws ec2 describe-route-tables --filters "Name=vpc-id,Values=$VPC_ID" "Name=tag:Name,Values=Public-RouteTable" \
  --region "$REGION" --query "RouteTables[0].RouteTableId" --output text 2>/dev/null || echo "None")

if [[ "$PUB_RT" == "None" || "$PUB_RT" == "null" ]]; then
  info "  Creating Public Route Table..."
  PUB_RT=$(aws ec2 create-route-table --vpc-id "$VPC_ID" --region "$REGION" \
    --query "RouteTable.RouteTableId" --output text)
  aws ec2 create-tags --resources "$PUB_RT" --region "$REGION" \
    --tags Key=Name,Value=Public-RouteTable
  aws ec2 create-route \
    --route-table-id "$PUB_RT" \
    --destination-cidr-block "0.0.0.0/0" \
    --gateway-id "$IGW_ID" --region "$REGION" > /dev/null
  PUB_RT_ASSOC=$(aws ec2 associate-route-table \
    --route-table-id "$PUB_RT" --subnet-id "$PUB_SUBNET" --region "$REGION" \
    --query "AssociationId" --output text)
  success "Public Route Table created: $PUB_RT"
else
  info "  Public Route Table already exists ($PUB_RT). Using it."
  PUB_RT_ASSOC=$(aws ec2 describe-route-tables --route-table-ids "$PUB_RT" --region "$REGION" \
    --query "RouteTables[0].Associations[?SubnetId=='$PUB_SUBNET'].RouteTableAssociationId" --output text 2>/dev/null || echo "None")
fi

# ── Step 7: Elastic IP for NAT ───────────────────────────────
info "Step 7 — Elastic IP for NAT Gateway"
EIP_ALLOC=$(aws ec2 describe-addresses --filters "Name=tag:Name,Values=NAT-ElasticIP" \
  --region "$REGION" --query "Addresses[0].AllocationId" --output text 2>/dev/null || echo "None")

if [[ "$EIP_ALLOC" == "None" || "$EIP_ALLOC" == "null" ]]; then
  info "  Allocating new Elastic IP..."
  EIP_ALLOC=$(aws ec2 allocate-address --domain vpc --region "$REGION" \
    --query "AllocationId" --output text)
  aws ec2 create-tags --resources "$EIP_ALLOC" --region "$REGION" \
    --tags Key=Name,Value=NAT-ElasticIP
fi
EIP_IP=$(aws ec2 describe-addresses --allocation-ids "$EIP_ALLOC" --region "$REGION" \
  --query "Addresses[0].PublicIp" --output text)
success "Elastic IP: $EIP_IP (Allocation: $EIP_ALLOC)"

# ── Step 8: NAT Gateway ──────────────────────────────────────
info "Step 8 — NAT Gateway"
NAT_GW=$(aws ec2 describe-nat-gateways --filter "Name=vpc-id,Values=$VPC_ID" "Name=tag:Name,Values=NAT-Gateway" "Name=state,Values=available,pending" \
  --region "$REGION" --query "NatGateways[0].NatGatewayId" --output text 2>/dev/null || echo "None")

if [[ "$NAT_GW" == "None" || "$NAT_GW" == "null" ]]; then
  info "  Creating NAT Gateway in Public Subnet (~60 seconds)..."
  NAT_GW=$(aws ec2 create-nat-gateway \
    --subnet-id "$PUB_SUBNET" --allocation-id "$EIP_ALLOC" --region "$REGION" \
    --tag-specifications "ResourceType=natgateway,Tags=[{Key=Name,Value=NAT-Gateway}]" \
    --query "NatGateway.NatGatewayId" --output text)
  echo "  NAT GW: $NAT_GW — waiting for available state..."
  aws ec2 wait nat-gateway-available --nat-gateway-ids "$NAT_GW" --region "$REGION"
  success "NAT Gateway created: $NAT_GW"
else
  info "  NAT Gateway already exists ($NAT_GW). Using it."
fi

# ── Step 9: Private Route Table ──────────────────────────────
info "Step 9 — Private Route Table"
PRIV_RT=$(aws ec2 describe-route-tables --filters "Name=vpc-id,Values=$VPC_ID" "Name=tag:Name,Values=Private-RouteTable" \
  --region "$REGION" --query "RouteTables[0].RouteTableId" --output text 2>/dev/null || echo "None")

if [[ "$PRIV_RT" == "None" || "$PRIV_RT" == "null" ]]; then
  info "  Creating Private Route Table (via NAT)..."
  PRIV_RT=$(aws ec2 create-route-table --vpc-id "$VPC_ID" --region "$REGION" \
    --query "RouteTable.RouteTableId" --output text)
  aws ec2 create-tags --resources "$PRIV_RT" --region "$REGION" \
    --tags Key=Name,Value=Private-RouteTable
  aws ec2 create-route \
    --route-table-id "$PRIV_RT" \
    --destination-cidr-block "0.0.0.0/0" \
    --nat-gateway-id "$NAT_GW" --region "$REGION" > /dev/null
  PRIV_RT_ASSOC=$(aws ec2 associate-route-table \
    --route-table-id "$PRIV_RT" --subnet-id "$PRIV_SUBNET" --region "$REGION" \
    --query "AssociationId" --output text)
  success "Private Route Table created: $PRIV_RT"
else
  info "  Private Route Table already exists ($PRIV_RT). Using it."
  PRIV_RT_ASSOC=$(aws ec2 describe-route-tables --route-table-ids "$PRIV_RT" --region "$REGION" \
    --query "RouteTables[0].Associations[?SubnetId=='$PRIV_SUBNET'].RouteTableAssociationId" --output text 2>/dev/null || echo "None")
fi

# ── Step 10: Security Groups ─────────────────────────────────
info "Step 10 — Security Groups"
PUB_SG=$(aws ec2 describe-security-groups --filters "Name=vpc-id,Values=$VPC_ID" "Name=group-name,Values=Practical-Public-SG" \
  --region "$REGION" --query "SecurityGroups[0].GroupId" --output text 2>/dev/null || echo "None")

if [[ "$PUB_SG" == "None" || "$PUB_SG" == "null" ]]; then
  info "  Creating Public Security Group..."
  PUB_SG=$(aws ec2 create-security-group \
    --group-name "Practical-Public-SG" --description "SSH from internet" \
    --vpc-id "$VPC_ID" --region "$REGION" --query "GroupId" --output text)
  aws ec2 create-tags --resources "$PUB_SG" --region "$REGION" \
    --tags Key=Name,Value="Practical-Public-SG"
else
  info "  Public Security Group already exists ($PUB_SG)."
fi
aws ec2 authorize-security-group-ingress \
  --group-id "$PUB_SG" --protocol tcp --port 22 --cidr "0.0.0.0/0" --region "$REGION" 2>/dev/null || true

PRIV_SG=$(aws ec2 describe-security-groups --filters "Name=vpc-id,Values=$VPC_ID" "Name=group-name,Values=Practical-Private-SG" \
  --region "$REGION" --query "SecurityGroups[0].GroupId" --output text 2>/dev/null || echo "None")

if [[ "$PRIV_SG" == "None" || "$PRIV_SG" == "null" ]]; then
  info "  Creating Private Security Group..."
  PRIV_SG=$(aws ec2 create-security-group \
    --group-name "Practical-Private-SG" --description "SSH from public subnet only" \
    --vpc-id "$VPC_ID" --region "$REGION" --query "GroupId" --output text)
  aws ec2 create-tags --resources "$PRIV_SG" --region "$REGION" \
    --tags Key=Name,Value="Practical-Private-SG"
else
  info "  Private Security Group already exists ($PRIV_SG)."
fi
aws ec2 authorize-security-group-ingress \
  --group-id "$PRIV_SG" --protocol tcp --port 22 --cidr "10.10.1.0/24" --region "$REGION" 2>/dev/null || true
aws ec2 authorize-security-group-ingress \
  --group-id "$PRIV_SG" --protocol icmp --port -1 --cidr "10.10.0.0/16" --region "$REGION" 2>/dev/null || true
success "Security Groups checked/configured."

# ── Step 11: Launch EC2 instances ────────────────────────────
info "Step 11 — Public Compute Instance"
PUB_INST=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=Public-EC2-Instance" "Name=instance-state-name,Values=running" "Name=vpc-id,Values=$VPC_ID" \
  --region "$REGION" --query "Reservations[0].Instances[0].InstanceId" --output text 2>/dev/null || echo "None")

if [[ "$PUB_INST" == "None" || "$PUB_INST" == "null" ]]; then
  info "  Launching EC2 in Public Subnet..."
  PUB_INST=$(aws ec2 run-instances \
    --image-id "$AMI_ID" --instance-type "$INSTANCE_TYPE" \
    --key-name "$KEY_NAME" --subnet-id "$PUB_SUBNET" \
    --security-group-ids "$PUB_SG" --region "$REGION" --count 1 \
    --associate-public-ip-address \
    --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=Public-EC2-Instance}]" \
    --query "Instances[0].InstanceId" --output text)
  success "Public EC2 launched: $PUB_INST"
else
  info "  Public Compute Node already exists ($PUB_INST). Using it."
fi

info "Step 12 — Private Compute Instance"
PRIV_INST=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=Private-EC2-Instance" "Name=instance-state-name,Values=running" "Name=vpc-id,Values=$VPC_ID" \
  --region "$REGION" --query "Reservations[0].Instances[0].InstanceId" --output text 2>/dev/null || echo "None")

if [[ "$PRIV_INST" == "None" || "$PRIV_INST" == "null" ]]; then
  info "  Launching EC2 in Private Subnet..."
  PRIV_INST=$(aws ec2 run-instances \
    --image-id "$AMI_ID" --instance-type "$INSTANCE_TYPE" \
    --key-name "$KEY_NAME" --subnet-id "$PRIV_SUBNET" \
    --security-group-ids "$PRIV_SG" --region "$REGION" --count 1 \
    --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=Private-EC2-Instance}]" \
    --query "Instances[0].InstanceId" --output text)
  success "Private EC2 launched: $PRIV_INST"
else
  info "  Private Compute Node already exists ($PRIV_INST). Using it."
fi

info "Waiting for both instances to be running..."
aws ec2 wait instance-running \
  --instance-ids "$PUB_INST" "$PRIV_INST" --region "$REGION"

PUB_IP=$(aws ec2 describe-instances --instance-ids "$PUB_INST" --region "$REGION" \
  --query "Reservations[0].Instances[0].PublicIpAddress" --output text)
PRIV_IP=$(aws ec2 describe-instances --instance-ids "$PRIV_INST" --region "$REGION" \
  --query "Reservations[0].Instances[0].PrivateIpAddress" --output text)

# ── Save State ────────────────────────────────────────────────
cat > "$STATE_FILE" <<EOF
REGION=$REGION
VPC_ID=$VPC_ID
PUB_SUBNET=$PUB_SUBNET
PRIV_SUBNET=$PRIV_SUBNET
IGW_ID=$IGW_ID
PUB_RT=$PUB_RT
PRIV_RT=$PRIV_RT
PUB_RT_ASSOC=$PUB_RT_ASSOC
PRIV_RT_ASSOC=$PRIV_RT_ASSOC
EIP_ALLOC=$EIP_ALLOC
EIP_IP=$EIP_IP
NAT_GW=$NAT_GW
PUB_SG=$PUB_SG
PRIV_SG=$PRIV_SG
PUB_INST=$PUB_INST
PRIV_INST=$PRIV_INST
KEY_NAME=$KEY_NAME
PUB_IP=$PUB_IP
PRIV_IP=$PRIV_IP
AMI_ID=$AMI_ID
EOF

banner "VPC NETWORK — COMPLETE SUMMARY"
cat <<SUMMARY
  ╔══════════════════════════════════════════════════════════════╗
  ║              RESOURCES DEPLOYED                              ║
  ╠═══════════════════════╦══════════════════════════════════════╣
  ║ Resource              ║ Value                                ║
  ╠═══════════════════════╬══════════════════════════════════════╣
  ║ VPC ID                ║ $VPC_ID                               ║
  ║ VPC CIDR              ║ 10.0.0.0/16                          ║
  ║ Public Subnet         ║ $PUB_SUBNET  10.0.1.0/24             ║
  ║ Private Subnet        ║ $PRIV_SUBNET  10.0.2.0/24            ║
  ║ Internet Gateway      ║ $IGW_ID                              ║
  ║ NAT Gateway           ║ $NAT_GW                              ║
  ║ NAT EIP               ║ $EIP_IP  ($EIP_ALLOC)                ║
  ║ Public Route Table    ║ $PUB_RT                              ║
  ║ Private Route Table   ║ $PRIV_RT                             ║
  ║ Public SG             ║ $PUB_SG                              ║
  ║ Private SG            ║ $PRIV_SG                             ║
  ║ Public Compute Node   ║ $PUB_INST   IP: $PUB_IP              ║
  ║ Private Compute Node  ║ $PRIV_INST  IP: $PRIV_IP             ║
  ║ Key Pair              ║ $KEY_NAME                            ║
  ║ Key File              ║ $HOME/${KEY_NAME}.pem                ║
  ║ AMI Used              ║ $AMI_ID                              ║
  ║ Region                ║ $REGION                              ║
  ╠═══════════════════════╩══════════════════════════════════════╣
  ║              DETAILS NEEDED TO CLEANUP                       ║
  ╠══════════════════════════════════════════════════════════════╣
  ║  VPC:         $VPC_ID                                        ║
  ║  Instances:   $PUB_INST  $PRIV_INST                          ║
  ║  NAT Gateway: $NAT_GW                                        ║
  ║  EIP Alloc:   $EIP_ALLOC                                     ║
  ║  IGW:         $IGW_ID                                        ║
  ║  State File:  $STATE_FILE                                    ║
  ╠══════════════════════════════════════════════════════════════╣
  ║  Run: ./3_network_cleanup.sh  (reads state file automatically) ║
  ║  ⚠️  NAT Gateway costs ~\$0.045/hr — cleanup when done!      ║
  ╚══════════════════════════════════════════════════════════════╝

  Connect: EC2 Console → $PUB_INST → Connect → EC2 Instance Connect
SUMMARY