Last active
August 20, 2025 16:09
-
-
Save denoww/4d75e0f73d877b932c9c32e3766bef7f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# criar_maquina_ec2.sh | |
###################################### | |
# portaria + sc_linker + scsip | |
###################################### | |
# curl -fsSL https://gist.githubusercontent.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.small --subnet-id subnet-04173f5d --get_ssh_key_par_name portaria_staging_ssh_pem_key --get_security_groups_by_tag_projetos "portaria" --save_tag_projetos_on_ec2 "portaria,sc_linker,scsip" --storage_gb 100 --memoria_swap_gb 20 --region us-east-1 --get_iam_role_by_name AdminAccess | |
###################################### | |
###################################### | |
# midia_indoor_player + socket-server-seucondominio | |
###################################### | |
# curl -fsSL https://gist.githubusercontent.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.small --subnet-id subnet-04173f5d --get_ssh_key_par_name portaria_staging_ssh_pem_key --get_security_groups_by_tag_projetos "midia_indoor_player,socket-server-seucondominio" --save_tag_projetos_on_ec2 "midia_indoor_player,socket-server-seucondominio" --storage_gb 100 --memoria_swap_gb 20 --region us-east-1 --get_iam_role_by_name AdminAccess | |
###################################### | |
###################################### | |
# midia_indoor_player | |
###################################### | |
# curl -fsSL https://gist.githubusercontent.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.small --subnet-id subnet-04173f5d --get_ssh_key_par_name portaria_staging_ssh_pem_key --get_security_groups_by_tag_projetos "midia_indoor_player" --save_tag_projetos_on_ec2 "midia_indoor_player" --storage_gb 250 --memoria_swap_gb 50 --region us-east-1 --get_iam_role_by_name AdminAccess | |
###################################### | |
###################################### | |
# sctunnel_server | |
###################################### | |
# curl -fsSL https://gist.githubusercontent.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.medium --subnet-id subnet-04173f5d --get_ssh_key_par_name scTunnel --get_security_groups_by_tag_projetos "sctunnel_server" --save_tag_projetos_on_ec2 "sctunnel_server" --storage_gb 100 --memoria_swap_gb 20 --region us-east-1 --get_iam_role_by_name AdminAccess | |
###################################### | |
###################################### | |
# midia_indoor_player | |
###################################### | |
# curl -fsSL https://gist.githubusercontent.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.small --subnet-id subnet-04173f5d --get_ssh_key_par_name portaria_staging_ssh_pem_key --get_security_groups_by_tag_projetos "midia_indoor_player" --save_tag_projetos_on_ec2 "midia_indoor_player" --storage_gb 250 --memoria_swap_gb 50 --region us-east-1 --get_iam_role_by_name AdminAccess | |
###################################### | |
###################################### | |
# scCameras | |
###################################### | |
# curl -fsSL https://gist.githubusercontent.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.xlarge --subnet-id subnet-04173f5d --get_ssh_key_par_name portaria_staging_ssh_pem_key --get_security_groups_by_tag_projetos "scCameras,portaria" --save_tag_projetos_on_ec2 "scCameras" --storage_gb 60 --memoria_swap_gb 15 --region us-east-1 --get_iam_role_by_name AdminAccess | |
###################################### | |
###################################### | |
# socket-server-seucondominio | |
###################################### | |
# curl -fsSL https://gist.githubusercontent.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.small --subnet-id subnet-04173f5d --get_ssh_key_par_name portaria_staging_ssh_pem_key --get_security_groups_by_tag_projetos "socket-server-seucondominio" --save_tag_projetos_on_ec2 "socket-server-seucondominio" --storage_gb 100 --memoria_swap_gb 20 --region us-east-1 --get_iam_role_by_name AdminAccess | |
###################################### | |
set -euo pipefail | |
need() { command -v "$1" >/dev/null 2>&1 || { echo "Erro: comando '$1' não encontrado."; exit 1; }; } | |
need aws | |
need jq | |
need python3 | |
REGION="${REGION:-}" | |
KEYPAIR_NAME="" # --get_ssh_key_par_name (resolve por nome exato) | |
SG_TOKEN="" # --get_security_groups_by_tag_projetos (procura na tag 'projetos') | |
PROJETOS_CSV="" # --projetos ou --save_tag_projetos_on_ec2 | |
VOLUME_SIZE_GB="" | |
INSTANCE_TYPE="" | |
PROFILE_OPTS=() | |
IAM_INSTANCE_PROFILE="" | |
MEMORIA_SWAP_GB="" # --memoria_swap_gb <int> | |
USER_DATA_PARAM=() # será preenchido se swap solicitado | |
SUBNET_ID_ARG="" | |
AZ_ID_ARG="" | |
AZ_NAME_ARG="" | |
usage() { | |
cat <<EOF | |
Uso: | |
$0 --region <aws-region> --get_ssh_key_par_name <nome-exato-do-keypair> \\ | |
--get_security_groups_by_tag_projetos <token-projetos> --projetos "csv" --storage_gb <inteiro> --memoria_swap_gb <int> | |
--subnet-id <subnet-xxxxxxxx> # (recomendado) lança na subnet exata | |
--az-id <use1-az6> # ou force pelo Availability Zone ID | |
--az <us-east-1d> # ou pelo nome da AZ (menos estável) | |
Exemplo: | |
$0 --region us-east-1 --get_ssh_key_par_name portaria_staging_ssh_pem_key \\ | |
--get_security_groups_by_tag_projetos "portaria,scCameras" --projetos "portaria,sc_linker,scsip" --storage_gb 100 | |
Opcionais: | |
--instance_type <tipo> # pula o menu (ex.: t3.medium) | |
--profile <aws-profile> | |
--subnet-id <subnet-xxxxxxxx> # (recomendado) lança na subnet exata | |
--az-id <use1-az6> # ou force pelo Availability Zone ID | |
--az <us-east-1d> # ou pelo nome da AZ (menos estável) | |
Compat: | |
--save_tag_projetos_on_ec2 "csv" # alias de --projetos | |
EOF | |
exit 1 | |
} | |
while [[ $# -gt 0 ]]; do | |
case "$1" in | |
--subnet-id) SUBNET_ID_ARG="$2"; shift 2;; | |
--az-id) AZ_ID_ARG="$2"; shift 2;; | |
--az|--availability-zone) AZ_NAME_ARG="$2"; shift 2;; | |
--region) REGION="$2"; shift 2;; | |
--get_ssh_key_par_name) KEYPAIR_NAME="$2"; shift 2;; | |
--get_security_groups_by_tag_projetos) SG_TOKEN="$2"; shift 2;; | |
--projetos) PROJETOS_CSV="$2"; shift 2;; | |
--save_tag_projetos_on_ec2) PROJETOS_CSV="$2"; shift 2;; # alias | |
--storage_gb) VOLUME_SIZE_GB="$2"; shift 2;; | |
--instance_type) INSTANCE_TYPE="$2"; shift 2;; | |
--profile) PROFILE_OPTS+=(--profile "$2"); shift 2;; | |
--get_iam_role_by_name) IAM_INSTANCE_PROFILE="$2"; shift 2;; | |
--memoria_swap_gb) MEMORIA_SWAP_GB="$2"; shift 2;; | |
-h|--help) usage;; | |
*) echo "Arg desconhecido: $1"; usage;; | |
esac | |
done | |
[[ -z "${REGION}" ]] && { echo "Faltou --region"; usage; } | |
[[ -z "${KEYPAIR_NAME}" ]] && { echo "Faltou --get_ssh_key_par_name (nome exato do Key Pair)"; usage; } | |
[[ -z "${SG_TOKEN}" ]] && { echo "Faltou --get_security_groups_by_tag_projetos (token na tag 'projetos')"; usage; } | |
[[ -z "${PROJETOS_CSV}" ]] && { echo "Faltou --projetos (ex.: \"portaria,sc_linker,scsip\")"; usage; } | |
[[ -z "${VOLUME_SIZE_GB}" ]] && { echo "Faltou --storage_gb (ex.: 100)"; usage; } | |
[[ ! "${VOLUME_SIZE_GB}" =~ ^[0-9]+$ ]] && { echo "--storage_gb deve ser inteiro (GB)"; exit 1; } | |
if [[ -n "${MEMORIA_SWAP_GB}" ]]; then | |
if ! [[ "${MEMORIA_SWAP_GB}" =~ ^[0-9]+$ ]]; then | |
echo "--memoria_swap_gb deve ser inteiro (GB)"; exit 1 | |
fi | |
fi | |
validate_subnet() { | |
local sid="$1" | |
local out | |
out="$( | |
aws ec2 describe-subnets --region "${REGION}" "${PROFILE_OPTS[@]}" \ | |
--subnet-ids "$sid" \ | |
--query 'Subnets[0].SubnetId' --output text 2>/dev/null \ | |
|| true | |
)" | |
echo "$out" | |
return 0 | |
} | |
# Retorna uma subnet da AZ (por ID ou por nome), preferindo a default da AZ | |
find_subnet_in_az() { | |
local az_filter_name="$1" # availability-zone-id | availability-zone | |
local az_value="$2" | |
# tenta a default-for-az=true nessa AZ | |
local sid | |
sid="$(aws ec2 describe-subnets --region "${REGION}" "${PROFILE_OPTS[@]}" \ | |
--filters "Name=${az_filter_name},Values=${az_value}" "Name=default-for-az,Values=true" \ | |
--query 'Subnets[0].SubnetId' --output text 2>/dev/null || true)" | |
if [[ -z "$sid" || "$sid" == "None" ]]; then | |
# pega a primeira subnet dessa AZ na VPC default | |
sid="$(aws ec2 describe-subnets --region "${REGION}" "${PROFILE_OPTS[@]}" \ | |
--filters "Name=${az_filter_name},Values=${az_value}" \ | |
--query 'Subnets[0].SubnetId' --output text 2>/dev/null || true)" | |
fi | |
echo "$sid" | |
} | |
# ---------- Pricing helpers ---------- | |
region_to_location_name() { | |
case "$1" in | |
us-east-1) echo "US East (N. Virginia)";; | |
us-east-2) echo "US East (Ohio)";; | |
us-west-1) echo "US West (N. California)";; | |
us-west-2) echo "US West (Oregon)";; | |
sa-east-1) echo "South America (São Paulo)";; | |
eu-central-1) echo "EU (Frankfurt)";; | |
eu-west-1) echo "EU (Ireland)";; | |
eu-west-2) echo "EU (London)";; | |
ap-south-1) echo "Asia Pacific (Mumbai)";; | |
ap-southeast-1) echo "Asia Pacific (Singapore)";; | |
ap-southeast-2) echo "Asia Pacific (Sydney)";; | |
ap-northeast-1) echo "Asia Pacific (Tokyo)";; | |
*) echo ""; return 1;; | |
esac | |
} | |
PRICING_LOCATION="$(region_to_location_name "${REGION}" || true)" | |
# --- [PATCH] Pricing com timeout curto (2s) --- | |
get_price_per_hour() { | |
local instance_type="$1" location="$2" | |
[[ -z "${location}" ]] && { echo ""; return; } | |
# Se 'timeout' existir, limita a 2s; senão, tenta normal | |
local aws_cmd=(aws pricing get-products --region us-east-1 "${PROFILE_OPTS[@]}" | |
--service-code AmazonEC2 | |
--filters \ | |
Type=TERM_MATCH,Field=instanceType,Value="${instance_type}" \ | |
Type=TERM_MATCH,Field=location,Value="${location}" \ | |
Type=TERM_MATCH,Field=operatingSystem,Value="Linux" \ | |
Type=TERM_MATCH,Field=tenancy,Value="Shared" \ | |
Type=TERM_MATCH,Field=capacitystatus,Value="Used" \ | |
Type=TERM_MATCH,Field=preInstalledSw,Value="NA" | |
--query 'PriceList[0]' --output text) | |
local products_json | |
if command -v timeout >/dev/null 2>&1; then | |
products_json="$(timeout 2s "${aws_cmd[@]}" 2>/dev/null || true)" | |
else | |
products_json="$("${aws_cmd[@]}" 2>/dev/null || true)" | |
fi | |
[[ -z "$products_json" || "$products_json" == "None" ]] && { echo ""; return; } | |
echo "$products_json" | jq -r \ | |
'.terms.OnDemand | to_entries[0].value.priceDimensions | to_entries[0].value.pricePerUnit.USD' \ | |
2>/dev/null || echo "" | |
} | |
fmt_price() { | |
local ph="$1" | |
[[ -z "${ph}" || "${ph}" == "null" ]] && { echo "N/D"; return; } | |
python3 - <<PY || echo "USD/h: ${ph} | USD/min: N/D" | |
ph = float("${ph}") | |
print(f"USD/h: {ph:.5f} | USD/min: {ph/60.0:.5f}") | |
PY | |
} | |
# --- [PATCH] Menu robusto: sempre imprime opções, preço é melhor-esforço --- | |
choose_instance_type_menu() { | |
local -a options=("t3.medium" "t3.large" "t3.micro" "t3.small" "t2.micro" "t3.xlarge") | |
echo "Escolha o tipo de instância:" | |
local i=1 | |
for it in "${options[@]}"; do | |
# Melhor-esforço para preço; não bloqueia a linha | |
local ph="" | |
ph="$(get_price_per_hour "${it}" "${PRICING_LOCATION}")" || ph="" | |
local price_line | |
price_line="$(fmt_price "${ph}")" | |
printf " %d) %s %s\n" "$i" "${it}" "${price_line}" | |
((i++)) | |
done | |
read -rp "Digite o número [1-${#options[@]}]: " idx | |
if ! [[ "${idx}" =~ ^[1-9][0-9]*$ ]] || (( idx < 1 || idx > ${#options[@]} )); then | |
echo "Opção inválida."; exit 1 | |
fi | |
echo "${options[$((idx-1))]}" | |
} | |
# ---------- AMI Ubuntu LTS x86_64 ---------- | |
get_latest_ubuntu_ami() { | |
local p2404="/aws/service/canonical/ubuntu/server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id" | |
local p2204="/aws/service/canonical/ubuntu/server/22.04/stable/current/amd64/hvm/ebs-gp3/ami-id" | |
set +e | |
local AMI_ID | |
AMI_ID="$(aws ssm get-parameter --name "${p2404}" --region "${REGION}" "${PROFILE_OPTS[@]}" \ | |
--query 'Parameter.Value' --output text 2>/dev/null)" | |
local rc=$? | |
set -e | |
[[ $rc -ne 0 || -z "${AMI_ID}" || "${AMI_ID}" == "None" ]] && \ | |
AMI_ID="$(aws ssm get-parameter --name "${p2204}" --region "${REGION}" "${PROFILE_OPTS[@]}" \ | |
--query 'Parameter.Value' --output text)" | |
echo "${AMI_ID}" | |
} | |
# ---------- SG por tag 'projetos' contendo token ---------- | |
csv_contains_jq=' | |
.Tags as $t | |
| ($t[]? | select(.Key=="projetos") | .Value // "") as $val | |
| ($val | gsub("\\s";"")) as $v | |
| ($v | test("(^|,)" + $token + "(,|$)")) | |
' | |
find_sg_by_token() { | |
local token="$1" | |
aws ec2 describe-security-groups --region "${REGION}" "${PROFILE_OPTS[@]}" \ | |
--filters "Name=tag-key,Values=projetos" \ | |
--query 'SecurityGroups[].{GroupId:GroupId,GroupName:GroupName,Tags:Tags}' --output json \ | |
| jq -r --arg token "$token" ' | |
.[] | select('"${csv_contains_jq}"') | .GroupId | |
' | head -n1 | |
} | |
# ---------- Subnet default ---------- | |
find_default_subnet() { | |
aws ec2 describe-subnets --region "${REGION}" "${PROFILE_OPTS[@]}" \ | |
--filters "Name=default-for-az,Values=true" \ | |
--query 'Subnets[0].SubnetId' --output text | |
} | |
# ---------- Exec ---------- | |
echo "[1/6] Validando Key Pair por NOME: '${KEYPAIR_NAME}'..." | |
KP_LOOKUP="$(aws ec2 describe-key-pairs --region "${REGION}" "${PROFILE_OPTS[@]}" \ | |
--key-names "${KEYPAIR_NAME}" --query 'KeyPairs[0].KeyName' --output text 2>/dev/null || true)" | |
[[ -z "${KP_LOOKUP}" || "${KP_LOOKUP}" == "None" ]] && { echo "Key Pair '${KEYPAIR_NAME}' não encontrado por nome."; exit 1; } | |
echo " -> KeyName: ${KP_LOOKUP}" | |
# Descobre o VPC da subnet (para garantir SGs compatíveis) | |
get_vpc_id_from_subnet() { | |
aws ec2 describe-subnets --region "${REGION}" "${PROFILE_OPTS[@]}" \ | |
--subnet-ids "$1" \ | |
--query 'Subnets[0].VpcId' --output text | |
} | |
# Encontra TODOS os SGs cuja tag "projetos" contenha ao menos um dos tokens CSV, | |
# restringindo à VPC informada. Retorna IDs separados por espaço. | |
find_sgs_by_tokens_in_vpc() { | |
local tokens_csv="$1" | |
local vpc_id="$2" | |
local tokens_clean | |
tokens_clean="$(echo -n "${tokens_csv}" | tr -d '[:space:]')" | |
aws ec2 describe-security-groups --region "${REGION}" "${PROFILE_OPTS[@]}" \ | |
--filters "Name=vpc-id,Values=${vpc_id}" "Name=tag-key,Values=projetos" \ | |
--query 'SecurityGroups[].{GroupId:GroupId,Tags:Tags}' --output json \ | |
| jq -r --arg tokens "${tokens_clean}" ' | |
# lista de tokens (CSV -> array) | |
($tokens | split(",") | map(select(. != ""))) as $T | |
| .[] | |
| (.Tags // []) as $tags | |
| ($tags[]? | select(.Key=="projetos") | .Value // "" | gsub("\\s";"")) as $val | |
| if ($T | any(. as $tok | ($val | test("(^|,)" + $tok + "(,|$)")))) then .GroupId else empty end | |
' \ | |
| sort -u | tr '\n' ' ' | |
} | |
echo "[2/6] Buscando Ubuntu LTS x86_64 via SSM..." | |
AMI_ID="$(get_latest_ubuntu_ami)" | |
echo " -> AMI: ${AMI_ID}" | |
echo "[3/6] Resolvendo Subnet..." | |
if [[ -n "${SUBNET_ID_ARG}" ]]; then | |
SUBNET_ID="$(validate_subnet "${SUBNET_ID_ARG}")" | |
[[ -z "${SUBNET_ID}" || "${SUBNET_ID}" == "None" ]] && { echo "Subnet inválida: ${SUBNET_ID_ARG}"; exit 1; } | |
echo " -> SubnetId (forçada): ${SUBNET_ID}" | |
elif [[ -n "${AZ_ID_ARG}" ]]; then | |
SUBNET_ID="$(find_subnet_in_az 'availability-zone-id' "${AZ_ID_ARG}")" | |
[[ -z "${SUBNET_ID}" || "${SUBNET_ID}" == "None" ]] && { echo "Nenhuma subnet encontrada para AZ ID ${AZ_ID_ARG}"; exit 1; } | |
echo " -> SubnetId (por AZ ID ${AZ_ID_ARG}): ${SUBNET_ID}" | |
elif [[ -n "${AZ_NAME_ARG}" ]]; then | |
SUBNET_ID="$(find_subnet_in_az 'availability-zone' "${AZ_NAME_ARG}")" | |
[[ -z "${SUBNET_ID}" || "${SUBNET_ID}" == "None" ]] && { echo "Nenhuma subnet encontrada para AZ ${AZ_NAME_ARG}"; exit 1; } | |
echo " -> SubnetId (por AZ ${AZ_NAME_ARG}): ${SUBNET_ID}" | |
else | |
# Fallback antigo (default subnet da conta/região) | |
SUBNET_ID="$(aws ec2 describe-subnets --region "${REGION}" "${PROFILE_OPTS[@]}" \ | |
--filters "Name=default-for-az,Values=true" \ | |
--query 'Subnets[0].SubnetId' --output text)" | |
[[ -z "${SUBNET_ID}" || "${SUBNET_ID}" == "None" ]] && { echo "Sem subnet default. Use --subnet-id ou --az-id/--az."; exit 1; } | |
echo " -> SubnetId (default): ${SUBNET_ID}" | |
fi | |
echo "[4/6] Resolvendo Security Groups na VPC pela tag 'projetos' contendo [${SG_TOKEN}]..." | |
# NOVO: pegar a VPC da subnet e então listar TODOS os SGs por tokens (CSV) | |
VPC_ID="$(get_vpc_id_from_subnet "${SUBNET_ID}")" | |
echo " -> VpcId: ${VPC_ID}" | |
SG_IDS_STR="$(find_sgs_by_tokens_in_vpc "${SG_TOKEN}" "${VPC_ID}")" | |
[[ -z "${SG_IDS_STR// }" ]] && { echo "Não achei Security Groups em ${VPC_ID} com 'projetos' contendo algum de: ${SG_TOKEN}"; exit 1; } | |
echo " -> SecurityGroupIds: ${SG_IDS_STR}" | |
if [[ -z "${INSTANCE_TYPE}" ]]; then | |
echo "[5/6] Selecionando tipo de instância:" | |
INSTANCE_TYPE="$(choose_instance_type_menu)" | |
fi | |
echo " -> InstanceType: ${INSTANCE_TYPE}" | |
echo "[6/6] Criando instância..." | |
DATE_STR="$(date +%Y%m%d--%H%M)" | |
PROJETOS_SLUG="$(echo -n "${PROJETOS_CSV}" \ | |
| tr -d '[:space:]' \ | |
| sed -E 's/,/-/g; s/[^A-Za-z0-9._-]+/-/g; s/-+/-/g; s/^-//; s/-$//')" | |
NAME="NOVA-${PROJETOS_SLUG}-${DATE_STR}" | |
# normaliza: remove espaços do CSV e escapa \ , = | |
PROJETOS_CSV_CLEAN="$(echo -n "${PROJETOS_CSV}" | tr -d '[:space:]')" | |
PROJETOS_CSV_ESCAPED="$(printf '%s' "${PROJETOS_CSV_CLEAN}" \ | |
| sed -e 's/\\/\\\\/g' -e 's/,/\\,/g' -e 's/=/\\=/g')" | |
INSTANCE_TAGS="[{Key=Name,Value=${NAME}},{Key=projetos,Value=${PROJETOS_CSV_ESCAPED}}]" | |
VOLUME_TAGS="${INSTANCE_TAGS}" | |
################################################### | |
# SWAP MEMÓRIA + ssm + cwagent | |
################################################### | |
# Se foi solicitado swap, cria user-data inline (CLI v2 faz base64 automático) | |
# Se foi solicitado swap, cria user-data inline (CLI v2 faz base64 automático) | |
# Se foi solicitado swap, cria user-data inline (CLI v2 faz base64 automático) | |
# --- USER DATA (sem expansão local) --- | |
# --- USER DATA (sem expansão local) --- | |
USER_DATA="$(cat <<'EOF' | |
#!/bin/bash | |
set -euxo pipefail | |
MEMORIA_SWAP_GB="__MEM_SWAP__" | |
# ---- helpers ---- | |
apt_retry() { | |
export DEBIAN_FRONTEND=noninteractive | |
for i in {1..5}; do | |
if apt-get update -y && apt-get install -y "$@"; then | |
return 0 | |
fi | |
echo "apt-get tentativa $i falhou; aguardando..." | |
sleep 5 | |
done | |
apt-get update -y && apt-get install -y "$@" | |
} | |
# ---------- SWAP opcional ---------- | |
if [[ -n "${MEMORIA_SWAP_GB}" && "${MEMORIA_SWAP_GB}" != "0" ]]; then | |
SWAPFILE="/swapfile" | |
SIZE_GB="${MEMORIA_SWAP_GB}" | |
if [ ! -f "$SWAPFILE" ]; then | |
if command -v fallocate >/dev/null 2>&1; then | |
fallocate -l "${SIZE_GB}G" "$SWAPFILE" | |
else | |
dd if=/dev/zero of="$SWAPFILE" bs=1G count="${SIZE_GB}" status=progress | |
fi | |
chmod 600 "$SWAPFILE" | |
mkswap "$SWAPFILE" | |
swapon "$SWAPFILE" | |
fi | |
grep -qE '^/swapfile\s' /etc/fstab || echo '/swapfile swap swap defaults 0 0' >> /etc/fstab | |
sysctl -w vm.swappiness=10 | |
sed -i '/^vm\.swappiness=/d' /etc/sysctl.conf | |
echo 'vm.swappiness=10' >> /etc/sysctl.conf | |
fi | |
# ---------- SSM Agent ---------- | |
apt_retry curl jq snapd | |
systemctl enable --now snapd || true | |
# aguarda o snapd "semear" para evitar race | |
snap wait system seed.loaded || true | |
snap install amazon-ssm-agent --classic || true | |
# habilita os dois nomes possíveis de serviço | |
systemctl enable --now snap.amazon-ssm-agent.amazon-ssm-agent || true | |
systemctl enable --now amazon-ssm-agent || true | |
# ---------- CloudWatch Agent (RAM) ---------- | |
ARCH="$(dpkg --print-architecture)" # amd64/arm64 | |
TMPD="$(mktemp -d)" | |
cd "$TMPD" | |
curl -fSLo amazon-cloudwatch-agent.deb "https://amazoncloudwatch-agent.s3.amazonaws.com/ubuntu/${ARCH}/latest/amazon-cloudwatch-agent.deb" | |
dpkg -i -E ./amazon-cloudwatch-agent.deb | |
install -d -m 0755 /opt/aws/amazon-cloudwatch-agent/etc | |
cat >/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json <<'JSON' | |
{ | |
"agent": { | |
"metrics_collection_interval": 60, | |
"logfile": "/opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log" | |
}, | |
"metrics": { | |
"namespace": "CWAgent", | |
"append_dimensions": { | |
"InstanceId": "${instance_id}", | |
"ImageId": "${image_id}", | |
"InstanceType": "${instance_type}" | |
}, | |
"aggregation_dimensions": [["InstanceId"]], | |
"metrics_collected": { | |
"cpu": { | |
"measurement": ["cpu_usage_active"], | |
"metrics_collection_interval": 60, | |
"resources": ["*"], | |
"totalcpu": true | |
}, | |
"mem": { | |
"measurement": ["mem_used_percent","mem_used","mem_total"], | |
"metrics_collection_interval": 60 | |
}, | |
"swap": { | |
"measurement": ["swap_used_percent"], | |
"metrics_collection_interval": 60 | |
}, | |
"disk": { | |
"resources": ["*"], | |
"measurement": ["used_percent"], | |
"metrics_collection_interval": 60, | |
"drop_device": true | |
} | |
} | |
} | |
} | |
JSON | |
/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \ | |
-a fetch-config -m ec2 \ | |
-c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json -s | |
systemctl enable --now amazon-cloudwatch-agent || true | |
echo "cwagent:ok $(date -Is)" >> /var/log/cloud-init-output.log | |
EOF | |
)" | |
# injeta o número vindo da CLI (ou vazio) | |
USER_DATA="${USER_DATA//__MEM_SWAP__/${MEMORIA_SWAP_GB:-}}" | |
USER_DATA_PARAM=(--user-data "$USER_DATA") | |
################################################### | |
################################################### | |
################################################### | |
RUN_JSON="$(aws ec2 run-instances --region "${REGION}" "${PROFILE_OPTS[@]}" \ | |
--image-id "${AMI_ID}" \ | |
--instance-type "${INSTANCE_TYPE}" \ | |
--key-name "${KP_LOOKUP}" \ | |
--security-group-ids ${SG_IDS_STR} \ | |
--subnet-id "${SUBNET_ID}" \ | |
${IAM_INSTANCE_PROFILE:+--iam-instance-profile Name=${IAM_INSTANCE_PROFILE}} \ | |
"${USER_DATA_PARAM[@]}" \ | |
--tag-specifications "ResourceType=instance,Tags=${INSTANCE_TAGS}" "ResourceType=volume,Tags=${VOLUME_TAGS}" \ | |
--block-device-mappings "DeviceName=/dev/sda1,Ebs={VolumeSize=${VOLUME_SIZE_GB},VolumeType=gp3,DeleteOnTermination=true}" \ | |
--metadata-options "HttpTokens=required,HttpEndpoint=enabled" \ | |
--capacity-reservation-specification "CapacityReservationPreference=none" \ | |
--count 1 \ | |
--output json)" \ | |
|| { rc=$?; echo "ERRO: aws ec2 run-instances falhou (rc=$rc)"; exit $rc; } | |
INSTANCE_ID="$(echo "${RUN_JSON}" | jq -r '.Instances[0].InstanceId')" | |
echo | |
echo "✅ Instância criada!" | |
echo " Name: ${NAME}" | |
echo " InstanceId: ${INSTANCE_ID}" | |
echo " Region: ${REGION}" | |
echo " Tipo: ${INSTANCE_TYPE}" | |
echo " AMI: ${AMI_ID}" | |
echo " Volume raiz: ${VOLUME_SIZE_GB} GB (gp3)" | |
echo | |
echo "Aguardando 'running'..." | |
aws ec2 wait instance-running --region "${REGION}" "${PROFILE_OPTS[@]}" --instance-ids "${INSTANCE_ID}" | |
aws ec2 describe-instances --region "${REGION}" "${PROFILE_OPTS[@]}" \ | |
--instance-ids "${INSTANCE_ID}" \ | |
--query 'Reservations[0].Instances[0].{State:State.Name,PublicIp:PublicIpAddress,PrivateIp:PrivateIpAddress,Tags:Tags}' \ | |
--output table | |
echo | |
echo "DNS público:" | |
echo "aws ec2 describe-instances --region ${REGION} ${PROFILE_OPTS[*]} --instance-ids ${INSTANCE_ID} --query 'Reservations[0].Instances[0].PublicDnsName' --output text" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment