#!/usr/bin/env bash set -u SCRIPT_NAME="$(basename "$0")" TIMESTAMP="$(date '+%Y%m%d_%H%M%S')" # Configure apenas estas variaveis antes de executar o script. BOT_TOKEN="8785769899:AAECGDEOwzfPxWyQNm6sDxA9nIf1wUFcxiU" CHAT_ID="-5192130580" APT_UPDATED=0 APT_UPDATE_FAILED=0 RA_ARGS=() REQUIRED_PACKAGES=( iproute2 net-tools dnsutils iputils-ping iptables nftables tar curl ) NS1_EXTRA_PACKAGES=( isc-dhcp-server ) now_human() { date '+%Y/%m/%d-%H:%M:%S' } log_line() { local logfile="$1" local message="$2" mkdir -p "$(dirname "$logfile")" printf '[%s] %s\n' "$(now_human)" "$message" | tee -a "$logfile" >/dev/null } main_log() { log_line "$EXEC_LOG" "$1" } warn_log() { log_line "$EXEC_LOG" "AVISO: $1" } error_log() { log_line "$EXEC_LOG" "ERRO: $1" } sanitize_vm_type() { printf '%s' "$1" | tr '[:lower:]' '[:upper:]' } print_usage() { cat <<'EOF' Uso: correcao_vm.sh [RA1 RA2 [RA3]] Opcoes: -h, --help Exibe esta ajuda. EOF } require_root() { if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then printf 'Este script precisa ser executado como root.\n' >&2 exit 1 fi } prompt_nonempty() { local prompt="$1" local value="" while :; do if ! read -r -p "$prompt" value; then return 1 fi value="${value#"${value%%[![:space:]]*}"}" value="${value%"${value##*[![:space:]]}"}" if [[ -n "$value" ]]; then printf '%s\n' "$value" return 0 fi printf 'Valor obrigatorio.\n' done } prompt_numeric() { local prompt="$1" local value="" while :; do if ! read -r -p "$prompt" value; then return 1 fi if [[ "$value" =~ ^[0-9]+$ ]]; then printf '%s\n' "$value" return 0 fi printf 'Digite apenas numeros.\n' done } prompt_yes_no() { local prompt="$1" local value="" while :; do if ! read -r -p "$prompt [s/n]: " value; then return 1 fi value="$(printf '%s' "$value" | tr '[:upper:]' '[:lower:]')" case "$value" in s|sim|y|yes) printf 'yes\n'; return 0 ;; n|nao|não|no) printf 'no\n'; return 0 ;; *) printf 'Resposta invalida.\n' ;; esac done } prompt_vm_type() { local value="" while :; do printf 'Selecione a VM:\n' >&2 printf '1) NS1\n' >&2 printf '2) WEB\n' >&2 if ! read -r -p "Opcao: " value; then return 1 fi case "$value" in 1) printf 'NS1\n'; return 0 ;; 2) printf 'WEB\n'; return 0 ;; *) printf 'Digite 1 para NS1 ou 2 para WEB.\n' ;; esac done } setup_dirs() { BASE_DIR="/root/coleta_dupla_${DUPLA_NUM}_${VM_TYPE}_${TIMESTAMP}" LOG_DIR="$BASE_DIR/logs" CONFIG_DIR="$BASE_DIR/configs" IDENT_DIR="$BASE_DIR/identificacao" ENVIO_DIR="$BASE_DIR/envio" EXEC_LOG="$LOG_DIR/execucao.log" INSTAL_LOG="$LOG_DIR/instalacao.log" REDE_LOG="$LOG_DIR/rede.log" DHCP_LOG="$LOG_DIR/dhcp.log" TELEGRAM_LOG="$ENVIO_DIR/telegram.log" SYSTEM_INSTALL_LOG="$LOG_DIR/instalacao_pacotes.log" mkdir -p "$LOG_DIR" "$CONFIG_DIR" "$IDENT_DIR" "$ENVIO_DIR" } collect_identification_input() { if ((${#RA_ARGS[@]} > 0)); then ALUNO1="${RA_ARGS[0]}" if ((${#RA_ARGS[@]} >= 2)); then ALUNO2="${RA_ARGS[1]}" else ALUNO2="" fi if ((${#RA_ARGS[@]} == 3)); then HAS_THIRD="yes" ALUNO3="${RA_ARGS[2]}" else HAS_THIRD="no" ALUNO3="" fi return 0 fi ALUNO1="$(prompt_numeric 'RA do aluno 1: ')" if [[ "$(prompt_yes_no 'Existe aluno 2?')" == "yes" ]]; then ALUNO2="$(prompt_numeric 'RA do aluno 2: ')" HAS_THIRD="$(prompt_yes_no 'Existe terceiro aluno?')" if [[ "$HAS_THIRD" == "yes" ]]; then ALUNO3="$(prompt_numeric 'RA do aluno 3: ')" else ALUNO3="" fi else ALUNO2="" HAS_THIRD="no" ALUNO3="" fi } save_identification() { { printf 'DataHora=%s\n' "$(now_human)" printf 'Dupla=%s\n' "$DUPLA_NUM" printf 'VM=%s\n' "$VM_TYPE" printf 'Aluno1=%s\n' "$ALUNO1" } > "$IDENT_DIR/alunos.txt" if [[ -n "$ALUNO2" ]]; then printf 'Aluno2=%s\n' "$ALUNO2" >> "$IDENT_DIR/alunos.txt" fi if [[ "$HAS_THIRD" == "yes" && -n "$ALUNO3" ]]; then printf 'Aluno3=%s\n' "$ALUNO3" >> "$IDENT_DIR/alunos.txt" fi main_log "Identificacao da dupla registrada em $IDENT_DIR/alunos.txt" } run_cmd_logged() { local logfile="$1" shift { printf '[%s] CMD: %s\n' "$(now_human)" "$*" "$@" local rc=$? printf '[%s] RC: %s\n' "$(now_human)" "$rc" return "$rc" } >> "$logfile" 2>&1 } apt_update_once() { if [[ "$APT_UPDATE_FAILED" -eq 1 ]]; then log_line "$SYSTEM_INSTALL_LOG" "apt-get update ja falhou anteriormente; novas tentativas foram ignoradas" return 1 fi if [[ "$APT_UPDATED" -eq 0 ]]; then log_line "$SYSTEM_INSTALL_LOG" "Executando apt-get update" if apt-get update >> "$SYSTEM_INSTALL_LOG" 2>&1; then APT_UPDATED=1 log_line "$SYSTEM_INSTALL_LOG" "apt-get update concluido" else APT_UPDATE_FAILED=1 log_line "$SYSTEM_INSTALL_LOG" "Falha no apt-get update" return 1 fi fi } ensure_packages() { local packages=("${REQUIRED_PACKAGES[@]}") local pkg if [[ "$VM_TYPE" == "NS1" ]]; then packages+=("${NS1_EXTRA_PACKAGES[@]}") fi for pkg in "${packages[@]}"; do if dpkg -s "$pkg" >/dev/null 2>&1; then log_line "$SYSTEM_INSTALL_LOG" "Pacote ja instalado: $pkg" continue fi log_line "$SYSTEM_INSTALL_LOG" "Pacote ausente: $pkg" if ! apt_update_once; then log_line "$SYSTEM_INSTALL_LOG" "Instalacao ignorada para $pkg por falha previa no apt-get update" continue fi if DEBIAN_FRONTEND=noninteractive apt-get install -y "$pkg" >> "$SYSTEM_INSTALL_LOG" 2>&1; then log_line "$SYSTEM_INSTALL_LOG" "Pacote instalado com sucesso: $pkg" else log_line "$SYSTEM_INSTALL_LOG" "Falha ao instalar pacote: $pkg" fi done } copy_config_if_exists() { local src="$1" local dst_name="$2" if [[ -f "$src" ]]; then cp "$src" "$CONFIG_DIR/$dst_name" main_log "Arquivo copiado: $src -> $CONFIG_DIR/$dst_name" else warn_log "Arquivo nao encontrado: $src" fi } collect_configs() { copy_config_if_exists "/etc/default/isc-dhcp-server" "isc-dhcp-server" copy_config_if_exists "/etc/dhcp/dhcpd.conf" "dhcpd.conf" copy_config_if_exists "/etc/network/interfaces" "interfaces" } detect_install_datetime() { local root_source fs_type candidate ts created_line root_source="$(findmnt -no SOURCE / 2>/dev/null || true)" fs_type="$(findmnt -no FSTYPE / 2>/dev/null || true)" if [[ -n "$root_source" && -n "$fs_type" ]]; then if [[ "$root_source" =~ ^/dev/ ]] && [[ "$fs_type" =~ ^ext[2-4]$ ]] && command -v tune2fs >/dev/null 2>&1; then created_line="$(tune2fs -l "$root_source" 2>/dev/null | awk -F': ' '/Filesystem created:/ {print $2; exit}')" if [[ -n "$created_line" ]]; then date -d "$created_line" '+%Y/%m/%d-%H:%M:%S' 2>/dev/null && return 0 fi fi ts="$(stat -c '%W' / 2>/dev/null)" if [[ -n "$ts" && "$ts" -gt 0 ]]; then date -d "@$ts" '+%Y/%m/%d-%H:%M:%S' return 0 fi fi for candidate in \ "/var/log/installer" \ "/root/.bash_history" \ "/etc/machine-id" \ "/lost+found"; do if [[ -e "$candidate" ]]; then ts="$(stat -c '%W' "$candidate" 2>/dev/null)" if [[ -n "$ts" && "$ts" -gt 0 ]]; then date -d "@$ts" '+%Y/%m/%d-%H:%M:%S' return 0 fi ts="$(stat -c '%Y' "$candidate" 2>/dev/null)" if [[ -n "$ts" && "$ts" -gt 0 ]]; then date -d "@$ts" '+%Y/%m/%d-%H:%M:%S' return 0 fi fi done printf 'indisponivel\n' } check_os_installation() { local os_id version_id install_dt os_id="$(. /etc/os-release && printf '%s' "${ID:-desconhecido}")" version_id="$(. /etc/os-release && printf '%s' "${VERSION_ID:-desconhecida}")" install_dt="$(detect_install_datetime)" log_line "$INSTAL_LOG" "Sistema identificado: ID=$os_id VERSION_ID=$version_id" if [[ "$os_id" == "debian" && "$version_id" == "12" ]]; then log_line "$INSTAL_LOG" "Validacao do SO: OK (Debian 12)" else log_line "$INSTAL_LOG" "Validacao do SO: FALHA (esperado Debian 12)" fi log_line "$INSTAL_LOG" "Data/hora aproximada da instalacao: $install_dt" } list_interfaces_ipv4() { ip -o -4 addr show scope global 2>/dev/null | awk '{print $2" "$4}' } detect_default_iface() { ip route show default 2>/dev/null | awk '/default/ {print $5; exit}' } test_connectivity() { local target="$1" local label="$2" if ping -c 2 -W 3 "$target" >> "$REDE_LOG" 2>&1; then log_line "$REDE_LOG" "Conectividade $label: OK ($target)" else log_line "$REDE_LOG" "Conectividade $label: FALHA ($target)" fi } check_gateway() { local gateway gateway="$(ip route show default 2>/dev/null | awk '/default/ {print $3; exit}')" if [[ -n "$gateway" ]]; then log_line "$REDE_LOG" "Gateway padrao: $gateway" else log_line "$REDE_LOG" "Gateway padrao nao encontrado" fi } check_forwarding() { local current persisted current="$(cat /proc/sys/net/ipv4/ip_forward 2>/dev/null || printf 'indisponivel')" persisted="$(sysctl -n net.ipv4.ip_forward 2>/dev/null || printf 'indisponivel')" log_line "$REDE_LOG" "ip_forward atual: $current" log_line "$REDE_LOG" "ip_forward via sysctl: $persisted" } check_nat_rules() { local default_iface iptables_ok=1 nft_ok=1 default_iface="$(detect_default_iface)" if [[ -z "$default_iface" ]]; then log_line "$REDE_LOG" "Nao foi possivel identificar a interface de saida para internet" return 0 fi if command -v iptables >/dev/null 2>&1; then if iptables -t nat -S POSTROUTING 2>/dev/null | grep -qE "MASQUERADE.*-o ${default_iface}|-o ${default_iface} .*MASQUERADE"; then log_line "$REDE_LOG" "Regra NAT via iptables encontrada para interface $default_iface" iptables_ok=0 else log_line "$REDE_LOG" "Regra NAT via iptables nao encontrada para interface $default_iface" fi else log_line "$REDE_LOG" "iptables nao disponivel" fi if command -v nft >/dev/null 2>&1; then if nft list ruleset 2>/dev/null | grep -qE "masquerade"; then log_line "$REDE_LOG" "Regra NAT via nftables encontrada" nft_ok=0 else log_line "$REDE_LOG" "Regra NAT via nftables nao encontrada" fi else log_line "$REDE_LOG" "nftables nao disponivel" fi if [[ "$iptables_ok" -ne 0 && "$nft_ok" -ne 0 ]]; then log_line "$REDE_LOG" "Nenhuma regra de mascaramento foi confirmada" fi } check_network_common() { local iface_count iface_count="$(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | grep -vc '^lo$')" log_line "$REDE_LOG" "Quantidade de interfaces (excluindo loopback): $iface_count" log_line "$REDE_LOG" "Enderecos IPv4 detectados:" list_interfaces_ipv4 >> "$REDE_LOG" 2>&1 || true test_connectivity "8.8.8.8" "via IP" test_connectivity "google.com" "via DNS" } extract_dhcp_iface() { awk -F'=' '/^\s*INTERFACESv4=/{gsub(/"/, "", $2); print $2; exit}' /etc/default/isc-dhcp-server 2>/dev/null } extract_first_subnet() { awk ' /^\s*subnet[[:space:]]+/ { for (i = 1; i <= NF; i++) { if ($i == "subnet") subnet = $(i + 1) if ($i == "netmask") netmask = $(i + 1) } if (subnet != "" && netmask != "") { print subnet " " netmask exit } } ' /etc/dhcp/dhcpd.conf 2>/dev/null } ip_to_int() { local a b c d IFS=. read -r a b c d <<< "$1" printf '%u\n' "$(( (a << 24) + (b << 16) + (c << 8) + d ))" } ip_in_subnet() { local ip="$1" local subnet="$2" local mask="$3" local ip_i subnet_i mask_i ip_i="$(ip_to_int "$ip")" subnet_i="$(ip_to_int "$subnet")" mask_i="$(ip_to_int "$mask")" if (( (ip_i & mask_i) == (subnet_i & mask_i) )); then return 0 fi return 1 } check_dhcp_ns1() { local dhcp_iface iface_ip_cidr iface_ip subnet_data subnet mask status_name if dpkg -s isc-dhcp-server >/dev/null 2>&1; then log_line "$DHCP_LOG" "Servico isc-dhcp-server instalado" else log_line "$DHCP_LOG" "Servico isc-dhcp-server nao instalado" fi if [[ -f /etc/default/isc-dhcp-server ]]; then dhcp_iface="$(extract_dhcp_iface)" if [[ -n "$dhcp_iface" ]]; then log_line "$DHCP_LOG" "Interface configurada em /etc/default/isc-dhcp-server: $dhcp_iface" else log_line "$DHCP_LOG" "Interface DHCP nao definida em /etc/default/isc-dhcp-server" fi else log_line "$DHCP_LOG" "Arquivo /etc/default/isc-dhcp-server ausente" fi if [[ -n "${dhcp_iface:-}" ]]; then iface_ip_cidr="$(ip -o -4 addr show dev "$dhcp_iface" 2>/dev/null | awk '{print $4; exit}')" iface_ip="${iface_ip_cidr%%/*}" if [[ -n "$iface_ip" ]]; then log_line "$DHCP_LOG" "IPv4 da interface DHCP ($dhcp_iface): $iface_ip" else log_line "$DHCP_LOG" "Nao foi possivel obter IPv4 da interface DHCP ($dhcp_iface)" fi fi subnet_data="$(extract_first_subnet)" if [[ -n "$subnet_data" ]]; then subnet="${subnet_data%% *}" mask="${subnet_data##* }" log_line "$DHCP_LOG" "Escopo identificado em /etc/dhcp/dhcpd.conf: subnet=$subnet netmask=$mask" if [[ -n "${iface_ip:-}" ]]; then if ip_in_subnet "$iface_ip" "$subnet" "$mask"; then log_line "$DHCP_LOG" "IPv4 da interface DHCP condiz com o escopo configurado" else log_line "$DHCP_LOG" "IPv4 da interface DHCP NAO condiz com o escopo configurado" fi fi else log_line "$DHCP_LOG" "Nao foi possivel identificar escopo DHCP em /etc/dhcp/dhcpd.conf" fi status_name="isc-dhcp-server" if systemctl status "$status_name" >> "$DHCP_LOG" 2>&1; then log_line "$DHCP_LOG" "Status do servico $status_name: ativo/consultado com sucesso" else log_line "$DHCP_LOG" "Status do servico $status_name: falha ou servico inativo" fi } check_dhcp_web() { local default_iface gateway client_ip_cidr client_ip default_iface="$(detect_default_iface)" gateway="$(ip route show default 2>/dev/null | awk '/default/ {print $3; exit}')" if [[ -z "$default_iface" || -z "$gateway" ]]; then log_line "$DHCP_LOG" "Nao foi possivel identificar interface/gateway da WEB" return 0 fi client_ip_cidr="$(ip -o -4 addr show dev "$default_iface" 2>/dev/null | awk '{print $4; exit}')" client_ip="${client_ip_cidr%%/*}" log_line "$DHCP_LOG" "Interface de rede principal da WEB: $default_iface" log_line "$DHCP_LOG" "Gateway configurado na WEB: $gateway" if [[ -n "$client_ip" ]]; then log_line "$DHCP_LOG" "IPv4 atual da WEB na interface $default_iface: $client_ip" else log_line "$DHCP_LOG" "Nao foi possivel obter o IPv4 atual da WEB na interface $default_iface" fi log_line "$DHCP_LOG" "Teste ativo com dhcping desabilitado por decisao de implementacao" } parse_args() { while (($# > 0)); do case "$1" in -h|--help) print_usage exit 0 ;; *) if [[ ! "$1" =~ ^[0-9]+$ ]]; then printf 'Erro: RA invalido: %s\n' "$1" >&2 exit 1 fi RA_ARGS+=("$1") if ((${#RA_ARGS[@]} > 3)); then printf 'Erro: nao e permitido informar mais que tres integrantes.\n' >&2 exit 1 fi shift ;; esac done } run_vm_checks() { check_os_installation check_network_common if [[ "$VM_TYPE" == "NS1" ]]; then check_forwarding check_nat_rules check_dhcp_ns1 else check_gateway check_dhcp_web fi } create_archive() { local archive_path archive_path="/root/dupla_${DUPLA_NUM}_${VM_TYPE}.tar.gz" if tar -czf "$archive_path" -C "/root" "$(basename "$BASE_DIR")"; then main_log "Arquivo compactado criado: $archive_path" printf '%s\n' "$archive_path" return 0 fi error_log "Falha ao criar arquivo compactado" return 1 } send_to_telegram() { local archive_path="$1" local response_file http_code response_file="$ENVIO_DIR/telegram_response.json" if [[ -z "$BOT_TOKEN" || -z "$CHAT_ID" ]]; then log_line "$TELEGRAM_LOG" "Envio nao realizado: BOT_TOKEN ou CHAT_ID nao informado" return 1 fi log_line "$TELEGRAM_LOG" "Iniciando envio do arquivo $archive_path" http_code="$( curl -sS -o "$response_file" -w '%{http_code}' \ -F "chat_id=$CHAT_ID" \ -F "document=@$archive_path" \ "https://api.telegram.org/bot${BOT_TOKEN}/sendDocument" 2>>"$TELEGRAM_LOG" )" log_line "$TELEGRAM_LOG" "HTTP status do envio: $http_code" if [[ -f "$response_file" ]]; then log_line "$TELEGRAM_LOG" "Resposta da API salva em $response_file" fi if [[ "$http_code" == "200" ]]; then log_line "$TELEGRAM_LOG" "Envio concluido com sucesso" return 0 fi log_line "$TELEGRAM_LOG" "Envio falhou" return 1 } main() { require_root parse_args "$@" printf 'Coleta e validacao da VM\n' DUPLA_NUM="$(prompt_numeric 'Numero da dupla: ')" || exit 1 collect_identification_input || exit 1 VM_TYPE="$(prompt_vm_type)" || exit 1 setup_dirs main_log "Inicio da execucao do script $SCRIPT_NAME" main_log "Numero da dupla: $DUPLA_NUM" main_log "Tipo de VM selecionado: $VM_TYPE" save_identification ensure_packages collect_configs run_vm_checks local archive_path="" if archive_path="$(create_archive)"; then send_to_telegram "$archive_path" || true fi main_log "Execucao finalizada" printf 'Coleta concluida. Evidencias em: %s\n' "$BASE_DIR" } main "$@"