Skip to content

Instantly share code, notes, and snippets.

@denoww
Last active August 29, 2025 22:07
Show Gist options
  • Save denoww/4d75e0f73d877b932c9c32e3766bef7f to your computer and use it in GitHub Desktop.
Save denoww/4d75e0f73d877b932c9c32e3766bef7f to your computer and use it in GitHub Desktop.

Revisions

  1. denoww revised this gist Aug 29, 2025. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions criar_maquina_ec2.sh
    Original file line number Diff line number Diff line change
    @@ -9,9 +9,9 @@


    ######################################
    # midia_indoor_player + socket-server-seucondominio
    # midia_indoor_player + socket-server-seucondominio + erp_staging
    ######################################
    # curl -fsSL https://gist.github.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
    # curl -fsSL https://gist.github.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.medium --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,erp_staging" --save_tag_projetos_on_ec2 "midia_indoor_player,socket-server-seucondominio,erp_staging" --storage_gb 150 --memoria_swap_gb 20 --region us-east-1 --get_iam_role_by_name AdminAccess
    ######################################

    ######################################
  2. denoww revised this gist Aug 27, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion criar_maquina_ec2.sh
    Original file line number Diff line number Diff line change
    @@ -429,7 +429,7 @@ if [[ -n "${MEMORIA_SWAP_GB}" && "${MEMORIA_SWAP_GB}" != "0" ]]; then
    fi
    # ---------- SSM Agent ----------
    apt_retry curl jq snapd
    apt_retry curl jq snapd ncdu
    systemctl enable --now snapd || true
    # aguarda o snapd "semear" para evitar race
    snap wait system seed.loaded || true
  3. denoww revised this gist Aug 20, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion criar_maquina_ec2.sh
    Original file line number Diff line number Diff line change
    @@ -11,7 +11,7 @@
    ######################################
    # midia_indoor_player + socket-server-seucondominio
    ######################################
    # curl -fsSL https://gist.github.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.small --subnet-id subnet-04173f5d --get_ssh_key_par_name scTunnel --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
    # curl -fsSL https://gist.github.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
    ######################################

    ######################################
  4. denoww revised this gist Aug 20, 2025. 1 changed file with 2 additions and 8 deletions.
    10 changes: 2 additions & 8 deletions criar_maquina_ec2.sh
    Original file line number Diff line number Diff line change
    @@ -9,15 +9,9 @@


    ######################################
    # sctunnel_server + midia_indoor_player
    # midia_indoor_player + socket-server-seucondominio
    ######################################
    # curl -fsSL https://gist.github.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,midia_indoor_player" --save_tag_projetos_on_ec2 "sctunnel_server,midia_indoor_player" --storage_gb 100 --memoria_swap_gb 20 --region us-east-1 --get_iam_role_by_name AdminAccess
    ######################################

    ######################################
    # sctunnel_server + midia_indoor_player + socket-server-seucondominio
    ######################################
    # curl -fsSL https://gist.github.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,midia_indoor_player,socket-server-seucondominio" --save_tag_projetos_on_ec2 "sctunnel_server,midia_indoor_player,socket-server-seucondominio" --storage_gb 100 --memoria_swap_gb 20 --region us-east-1 --get_iam_role_by_name AdminAccess
    # curl -fsSL https://gist.github.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.small --subnet-id subnet-04173f5d --get_ssh_key_par_name scTunnel --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
    ######################################

    ######################################
  5. denoww revised this gist Aug 20, 2025. 1 changed file with 19 additions and 0 deletions.
    19 changes: 19 additions & 0 deletions criar_maquina_ec2.sh
    Original file line number Diff line number Diff line change
    @@ -7,6 +7,25 @@
    # curl -fsSL https://gist.github.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
    ######################################


    ######################################
    # sctunnel_server + midia_indoor_player
    ######################################
    # curl -fsSL https://gist.github.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,midia_indoor_player" --save_tag_projetos_on_ec2 "sctunnel_server,midia_indoor_player" --storage_gb 100 --memoria_swap_gb 20 --region us-east-1 --get_iam_role_by_name AdminAccess
    ######################################

    ######################################
    # sctunnel_server + midia_indoor_player + socket-server-seucondominio
    ######################################
    # curl -fsSL https://gist.github.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,midia_indoor_player,socket-server-seucondominio" --save_tag_projetos_on_ec2 "sctunnel_server,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.github.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
    ######################################
  6. denoww revised this gist Aug 19, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion criar_maquina_ec2.sh
    Original file line number Diff line number Diff line change
    @@ -16,7 +16,7 @@
    ######################################
    # midia_indoor_player
    ######################################
    # curl -fsSL https://gist.github.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t2.medium --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
    # curl -fsSL https://gist.github.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
    ######################################


  7. denoww revised this gist Aug 19, 2025. 1 changed file with 7 additions and 1 deletion.
    8 changes: 7 additions & 1 deletion criar_maquina_ec2.sh
    Original file line number Diff line number Diff line change
    @@ -26,6 +26,12 @@
    # curl -fsSL https://gist.github.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.github.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; }; }
    @@ -527,4 +533,4 @@ aws ec2 describe-instances --region "${REGION}" "${PROFILE_OPTS[@]}" \

    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"
    echo "aws ec2 describe-instances --region ${REGION} ${PROFILE_OPTS[*]} --instance-ids ${INSTANCE_ID} --query 'Reservations[0].Instances[0].PublicDnsName' --output text"
  8. denoww revised this gist Aug 17, 2025. 1 changed file with 70 additions and 11 deletions.
    81 changes: 70 additions & 11 deletions criar_maquina_ec2.sh
    Original file line number Diff line number Diff line change
    @@ -4,26 +4,26 @@
    ######################################
    # portaria + sc_linker + scsip
    ######################################
    # curl -fsSL https://gist.github.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.small --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
    # curl -fsSL https://gist.github.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
    ######################################

    ######################################
    # sctunnel_server
    ######################################
    # curl -fsSL https://gist.github.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.medium --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
    # curl -fsSL https://gist.github.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.github.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t2.medium --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
    # curl -fsSL https://gist.github.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t2.medium --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.github.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.xlarge --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
    # curl -fsSL https://gist.github.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
    ######################################

    set -euo pipefail
    @@ -43,13 +43,18 @@ 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:
    @@ -59,6 +64,9 @@ Exemplo:
    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
    @@ -67,6 +75,9 @@ EOF

    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;;
    @@ -94,6 +105,37 @@ if [[ -n "${MEMORIA_SWAP_GB}" ]]; then
    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
    @@ -262,17 +304,34 @@ 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 default..."
    SUBNET_ID="$(find_default_subnet)"
    [[ -z "${SUBNET_ID}" || "${SUBNET_ID}" == "None" ]] && { echo "Sem subnet default. Ajuste find_default_subnet() p/ sua VPC."; exit 1; }
    echo " -> SubnetId: ${SUBNET_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}"

    echo "[4/6] Resolvendo Security Groups na VPC pela tag 'projetos' contendo [${SG_TOKEN}]..."
    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}"
  9. denoww revised this gist Aug 15, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion criar_maquina_ec2.sh
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,7 @@
    ######################################
    # portaria + sc_linker + scsip
    ######################################
    # curl -fsSL https://gist.github.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t2.medium --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
    # curl -fsSL https://gist.github.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.small --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
    ######################################

    ######################################
  10. denoww revised this gist Aug 15, 2025. 1 changed file with 98 additions and 36 deletions.
    134 changes: 98 additions & 36 deletions criar_maquina_ec2.sh
    Original file line number Diff line number Diff line change
    @@ -304,71 +304,134 @@ VOLUME_TAGS="${INSTANCE_TAGS}"


    ###################################################
    # SWAP MEMÓRIA
    # 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_PARAM=()
    USER_DATA="$(cat <<EOF
    # --- USER DATA (sem expansão local) ---
    # --- USER DATA (sem expansão local) ---
    USER_DATA="$(cat <<'EOF'
    #!/bin/bash
    set -euxo pipefail
    if [[ "${MEMORIA_SWAP_GB:-}" != "" && "${MEMORIA_SWAP_GB}" != "0" ]]; then
    SWAPFILE="/swapfile"
    SIZE_GB=${MEMORIA_SWAP_GB}
    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 "$@"
    }
    if [ ! -f "\$SWAPFILE" ]; then
    # ---------- 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"
    fallocate -l "${SIZE_GB}G" "$SWAPFILE"
    else
    dd if=/dev/zero of="\$SWAPFILE" bs=1G count="\${SIZE_GB}" status=progress
    dd if=/dev/zero of="$SWAPFILE" bs=1G count="${SIZE_GB}" status=progress
    fi
    chmod 600 "\$SWAPFILE"
    mkswap "\$SWAPFILE"
    swapon "\$SWAPFILE"
    chmod 600 "$SWAPFILE"
    mkswap "$SWAPFILE"
    swapon "$SWAPFILE"
    fi
    # garante persistência
    if ! grep -qE '^/swapfile\\s' /etc/fstab; then
    echo '/swapfile swap swap defaults 0 0' >> /etc/fstab
    fi
    # swappiness baixo (10)
    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
    sed -i '/^vm\.swappiness=/d' /etc/sysctl.conf
    echo 'vm.swappiness=10' >> /etc/sysctl.conf
    fi
    # ---------- SSM Agent (opcional) ----------
    # Ubuntu 22.04/24.04: preferir snap, com fallback
    if ! command -v snap >/dev/null 2>&1; then
    apt-get update -y
    apt-get install -y snapd
    systemctl enable --now snapd
    fi
    if ! snap list | grep -q '^amazon-ssm-agent'; then
    snap install amazon-ssm-agent --classic || true
    fi
    # Garante serviço ativo
    # ---------- 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
    # Algumas imagens usam nome de serviço legacy:
    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")





    ###################################################
    ###################################################
    ###################################################



    SON="$(aws ec2 run-instances --region "${REGION}" "${PROFILE_OPTS[@]}" \
    RUN_JSON="$(aws ec2 run-instances --region "${REGION}" "${PROFILE_OPTS[@]}" \
    --image-id "${AMI_ID}" \
    --instance-type "${INSTANCE_TYPE}" \
    --key-name "${KP_LOOKUP}" \
    @@ -384,7 +447,6 @@ SON="$(aws ec2 run-instances --region "${REGION}" "${PROFILE_OPTS[@]}" \
    --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
  11. denoww revised this gist Aug 15, 2025. 1 changed file with 4 additions and 2 deletions.
    6 changes: 4 additions & 2 deletions criar_maquina_ec2.sh
    Original file line number Diff line number Diff line change
    @@ -368,7 +368,7 @@ USER_DATA_PARAM=(--user-data "$USER_DATA")



    RUN_JSON="$(aws ec2 run-instances --region "${REGION}" "${PROFILE_OPTS[@]}" \
    SON="$(aws ec2 run-instances --region "${REGION}" "${PROFILE_OPTS[@]}" \
    --image-id "${AMI_ID}" \
    --instance-type "${INSTANCE_TYPE}" \
    --key-name "${KP_LOOKUP}" \
    @@ -381,7 +381,9 @@ RUN_JSON="$(aws ec2 run-instances --region "${REGION}" "${PROFILE_OPTS[@]}" \
    --metadata-options "HttpTokens=required,HttpEndpoint=enabled" \
    --capacity-reservation-specification "CapacityReservationPreference=none" \
    --count 1 \
    --output json)"
    --output json)" \
    || { rc=$?; echo "ERRO: aws ec2 run-instances falhou (rc=$rc)"; exit $rc; }


    INSTANCE_ID="$(echo "${RUN_JSON}" | jq -r '.Instances[0].InstanceId')"

  12. denoww revised this gist Aug 14, 2025. 1 changed file with 40 additions and 22 deletions.
    62 changes: 40 additions & 22 deletions criar_maquina_ec2.sh
    Original file line number Diff line number Diff line change
    @@ -310,38 +310,56 @@ VOLUME_TAGS="${INSTANCE_TAGS}"
    # 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_PARAM=()
    if [[ -n "${MEMORIA_SWAP_GB}" && "${MEMORIA_SWAP_GB}" != "0" ]]; then
    USER_DATA="$(cat <<EOF
    USER_DATA="$(cat <<EOF
    #!/bin/bash
    set -euxo pipefail
    if [[ "${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
    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
    # garante persistência
    if ! grep -qE '^/swapfile\\s' /etc/fstab; then
    echo '/swapfile swap swap defaults 0 0' >> /etc/fstab
    fi
    chmod 600 "\$SWAPFILE"
    mkswap "\$SWAPFILE"
    swapon "\$SWAPFILE"
    # swappiness baixo (10)
    sysctl -w vm.swappiness=10
    sed -i '/^vm\\.swappiness=/d' /etc/sysctl.conf
    echo 'vm.swappiness=10' >> /etc/sysctl.conf
    fi
    # garante persistência
    if ! grep -qE '^/swapfile\\s' /etc/fstab; then
    echo '/swapfile swap swap defaults 0 0' >> /etc/fstab
    # ---------- SSM Agent (opcional) ----------
    # Ubuntu 22.04/24.04: preferir snap, com fallback
    if ! command -v snap >/dev/null 2>&1; then
    apt-get update -y
    apt-get install -y snapd
    systemctl enable --now snapd
    fi
    # swappiness baixo (10)
    sysctl -w vm.swappiness=10
    sed -i '/^vm\\.swappiness=/d' /etc/sysctl.conf
    echo 'vm.swappiness=10' >> /etc/sysctl.conf
    if ! snap list | grep -q '^amazon-ssm-agent'; then
    snap install amazon-ssm-agent --classic || true
    fi
    # Garante serviço ativo
    systemctl enable --now snap.amazon-ssm-agent.amazon-ssm-agent || true
    # Algumas imagens usam nome de serviço legacy:
    systemctl enable --now amazon-ssm-agent || true
    EOF
    )"
    USER_DATA_PARAM=(--user-data "$USER_DATA")
    fi
    USER_DATA_PARAM=(--user-data "$USER_DATA")


    ###################################################
  13. denoww revised this gist Aug 14, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion criar_maquina_ec2.sh
    Original file line number Diff line number Diff line change
    @@ -10,7 +10,7 @@
    ######################################
    # sctunnel_server
    ######################################
    # curl -fsSL https://gist.github.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.micro --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
    # curl -fsSL https://gist.github.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.medium --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
    ######################################

    ######################################
  14. denoww revised this gist Aug 14, 2025. No changes.
  15. denoww revised this gist Aug 14, 2025. 1 changed file with 8 additions and 2 deletions.
    10 changes: 8 additions & 2 deletions criar_maquina_ec2.sh
    Original file line number Diff line number Diff line change
    @@ -40,12 +40,11 @@ PROJETOS_CSV="" # --projetos ou --save_tag_projetos_on_ec2
    VOLUME_SIZE_GB=""
    INSTANCE_TYPE=""
    PROFILE_OPTS=()
    DATE_STR="$(date +%Y%m%d-%H%M)"
    NAME="NOVA-${DATE_STR}"
    IAM_INSTANCE_PROFILE=""
    MEMORIA_SWAP_GB="" # --memoria_swap_gb <int>
    USER_DATA_PARAM=() # será preenchido se swap solicitado


    usage() {
    cat <<EOF
    Uso:
    @@ -288,6 +287,13 @@ 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}" \
  16. denoww revised this gist Aug 14, 2025. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions criar_maquina_ec2.sh
    Original file line number Diff line number Diff line change
    @@ -4,26 +4,26 @@
    ######################################
    # portaria + sc_linker + scsip
    ######################################
    # bash criar_maquina_ec2.sh --instance_type t2.medium --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
    # curl -fsSL https://gist.github.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t2.medium --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
    ######################################

    ######################################
    # sctunnel_server
    ######################################
    # bash criar_maquina_ec2.sh --instance_type t3.micro --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
    # curl -fsSL https://gist.github.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.micro --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
    ######################################
    # bash criar_maquina_ec2.sh --instance_type t2.medium --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
    # curl -fsSL https://gist.github.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t2.medium --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
    ######################################
    # bash criar_maquina_ec2.sh --instance_type t3.xlarge --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
    # curl -fsSL https://gist.github.com/denoww/4d75e0f73d877b932c9c32e3766bef7f/raw | bash -s -- --instance_type t3.xlarge --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
    ######################################

    set -euo pipefail
  17. denoww created this gist Aug 14, 2025.
    383 changes: 383 additions & 0 deletions criar_maquina_ec2.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,383 @@
    #!/usr/bin/env bash
    # criar_maquina_ec2.sh

    ######################################
    # portaria + sc_linker + scsip
    ######################################
    # bash criar_maquina_ec2.sh --instance_type t2.medium --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
    ######################################

    ######################################
    # sctunnel_server
    ######################################
    # bash criar_maquina_ec2.sh --instance_type t3.micro --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
    ######################################
    # bash criar_maquina_ec2.sh --instance_type t2.medium --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
    ######################################
    # bash criar_maquina_ec2.sh --instance_type t3.xlarge --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
    ######################################

    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=()
    DATE_STR="$(date +%Y%m%d-%H%M)"
    NAME="NOVA-${DATE_STR}"
    IAM_INSTANCE_PROFILE=""
    MEMORIA_SWAP_GB="" # --memoria_swap_gb <int>
    USER_DATA_PARAM=() # será preenchido se swap solicitado

    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>
    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>
    Compat:
    --save_tag_projetos_on_ec2 "csv" # alias de --projetos
    EOF
    exit 1
    }

    while [[ $# -gt 0 ]]; do
    case "$1" in
    --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

    # ---------- 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 default..."
    SUBNET_ID="$(find_default_subnet)"
    [[ -z "${SUBNET_ID}" || "${SUBNET_ID}" == "None" ]] && { echo "Sem subnet default. Ajuste find_default_subnet() p/ sua VPC."; exit 1; }
    echo " -> SubnetId: ${SUBNET_ID}"


    # 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}"

    echo "[4/6] Resolvendo Security Groups na VPC pela tag 'projetos' contendo [${SG_TOKEN}]..."
    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..."
    # 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
    ###################################################
    # 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_PARAM=()
    if [[ -n "${MEMORIA_SWAP_GB}" && "${MEMORIA_SWAP_GB}" != "0" ]]; then
    USER_DATA="$(cat <<EOF
    #!/bin/bash
    set -euxo pipefail
    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
    # garante persistência
    if ! grep -qE '^/swapfile\\s' /etc/fstab; then
    echo '/swapfile swap swap defaults 0 0' >> /etc/fstab
    fi
    # swappiness baixo (10)
    sysctl -w vm.swappiness=10
    sed -i '/^vm\\.swappiness=/d' /etc/sysctl.conf
    echo 'vm.swappiness=10' >> /etc/sysctl.conf
    EOF
    )"
    USER_DATA_PARAM=(--user-data "$USER_DATA")
    fi


    ###################################################
    ###################################################
    ###################################################



    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)"

    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"