Created
September 22, 2025 16:24
-
-
Save eduardomazolini/124d62de2b0c50b0a15de2d25ca766e2 to your computer and use it in GitHub Desktop.
Edita a imagem do Debian13 para a as minhas necessidades mas de forma bem generica.
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
| #!/bin/bash | |
| # Script para customizar cloud images Debian sem modificar a original | |
| # Uso: ./customize-image.sh <imagem-original> [imagem-saida] | |
| # Versão: 2.0 | |
| # Autor: Eduardo Mazolini | |
| set -euo pipefail # Melhor tratamento de erros | |
| # =================================================================== | |
| # CONFIGURAÇÕES GLOBAIS | |
| # =================================================================== | |
| readonly SCRIPT_NAME="$(basename "$0")" | |
| readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| # Configurações do console serial | |
| readonly SERIAL_DEVICE="ttyS0" | |
| readonly BAUD_RATE="115200" | |
| # Cores para output | |
| readonly RED=$(printf '\033[0;31m') | |
| readonly GREEN=$(printf '\033[0;32m') | |
| readonly YELLOW=$(printf '\033[1;33m') | |
| readonly BLUE=$(printf '\033[0;34m') | |
| readonly NC=$(printf '\033[0m') # No Color | |
| # =================================================================== | |
| # FUNÇÕES AUXILIARES | |
| # =================================================================== | |
| # Função para logging colorido | |
| log() { | |
| local level="$1" | |
| shift | |
| local message="$*" | |
| local timestamp=$(date '+%Y-%m-%d %H:%M:%S') | |
| case "$level" in | |
| "INFO") echo -e "${BLUE}[INFO]${NC} ${timestamp} - $message" ;; | |
| "WARN") echo -e "${YELLOW}[WARN]${NC} ${timestamp} - $message" ;; | |
| "ERROR") echo -e "${RED}[ERROR]${NC} ${timestamp} - $message" ;; | |
| "SUCCESS") echo -e "${GREEN}[SUCCESS]${NC} ${timestamp} - $message" ;; | |
| *) echo "[$level] ${timestamp} - $message" ;; | |
| esac | |
| } | |
| # Função para mostrar ajuda | |
| show_help() { | |
| cat << EOF | |
| ${SCRIPT_NAME} - Script para customização de cloud images Debian | |
| USO: | |
| $SCRIPT_NAME <imagem-original> [imagem-saida] | |
| EXEMPLOS: | |
| $SCRIPT_NAME debian-12-generic-amd64.qcow2 | |
| $SCRIPT_NAME debian-12-generic-amd64.qcow2 minha-imagem-custom.qcow2 | |
| OPÇÕES: | |
| -h, --help Mostra esta ajuda | |
| -v, --verbose Modo verboso (não implementado) | |
| DESCRIÇÃO: | |
| Este script customiza uma cloud image Debian adicionando: | |
| • QEMU Guest Agent | |
| • Console serial com autologin root | |
| • SSH hardening | |
| • Fail2ban | |
| • Ferramentas básicas (curl, wget, htop, vim) | |
| • Limpeza de logs e arquivos temporários | |
| REQUISITOS: | |
| • libguestfs-tools (apt install libguestfs-tools) | |
| • qemu-utils (apt install qemu-utils) | |
| EOF | |
| } | |
| # Função para verificar dependências | |
| check_dependencies() { | |
| log "INFO" "Verificando dependências..." | |
| local missing_deps=() | |
| if ! command -v virt-customize &> /dev/null; then | |
| missing_deps+=("libguestfs-tools") | |
| fi | |
| if ! command -v qemu-img &> /dev/null; then | |
| missing_deps+=("qemu-utils") | |
| fi | |
| if [ ${#missing_deps[@]} -gt 0 ]; then | |
| log "ERROR" "Dependências não encontradas: ${missing_deps[*]}" | |
| log "INFO" "Execute: apt install ${missing_deps[*]}" | |
| return 1 | |
| fi | |
| log "SUCCESS" "Todas as dependências encontradas" | |
| return 0 | |
| } | |
| # Função para validar imagem | |
| validate_image() { | |
| local image="$1" | |
| log "INFO" "Validando imagem: $image" | |
| if [ ! -f "$image" ]; then | |
| log "ERROR" "Arquivo não encontrado: $image" | |
| return 1 | |
| fi | |
| if [ ! -r "$image" ]; then | |
| log "ERROR" "Sem permissão de leitura: $image" | |
| return 1 | |
| fi | |
| # Verificar se é uma imagem válida usando qemu-img | |
| if ! qemu-img info "$image" &> /dev/null; then | |
| log "ERROR" "Arquivo não é uma imagem válida: $image" | |
| return 1 | |
| fi | |
| local size_mb=$(( $(stat -c%s "$image") / 1024 / 1024 )) | |
| log "SUCCESS" "Imagem válida (${size_mb}MB): $image" | |
| return 0 | |
| } | |
| # Função para criar backup se não existir | |
| create_backup() { | |
| local original="$1" | |
| local backup="${original}.backup" | |
| if [ ! -f "$backup" ]; then | |
| log "INFO" "Criando backup da imagem original..." | |
| cp "$original" "$backup" | |
| log "SUCCESS" "Backup criado: $backup" | |
| else | |
| log "INFO" "Backup já existe: $backup" | |
| fi | |
| } | |
| # Função para executar virt-customize com tratamento de erro | |
| safe_virt_customize() { | |
| local image="$1" | |
| shift | |
| local description="$1" | |
| shift | |
| log "INFO" "$description" | |
| if virt-customize -a "$image" "$@"; then | |
| log "SUCCESS" "$description - Concluído" | |
| return 0 | |
| else | |
| log "ERROR" "$description - Falhou" | |
| return 1 | |
| fi | |
| } | |
| # Função de limpeza em caso de erro | |
| cleanup() { | |
| local exit_code=$? | |
| if [ $exit_code -ne 0 ] && [ -n "${OUTPUT_IMAGE:-}" ] && [ -f "${OUTPUT_IMAGE:-}" ]; then | |
| log "WARN" "Script falhou. Removendo imagem parcialmente criada..." | |
| rm -f "$OUTPUT_IMAGE" | |
| fi | |
| exit $exit_code | |
| } | |
| # =================================================================== | |
| # FUNÇÃO PRINCIPAL | |
| # =================================================================== | |
| main() { | |
| # Processar argumentos | |
| case "${1:-}" in | |
| -h|--help) | |
| show_help | |
| exit 0 | |
| ;; | |
| "") | |
| log "ERROR" "Nenhuma imagem especificada" | |
| show_help | |
| exit 1 | |
| ;; | |
| esac | |
| # Variáveis principais | |
| local original_image="$1" | |
| local output_image="${2:-${original_image%.qcow2}-custom.qcow2}" | |
| # Tornar variáveis disponíveis globalmente para cleanup | |
| readonly ORIGINAL_IMAGE="$original_image" | |
| readonly OUTPUT_IMAGE="$output_image" | |
| # Configurar trap para limpeza | |
| trap cleanup EXIT INT TERM | |
| # Verificações iniciais | |
| check_dependencies || exit 1 | |
| validate_image "$original_image" || exit 1 | |
| # Verificar se imagem de saída já existe | |
| if [ -f "$output_image" ]; then | |
| log "WARN" "Imagem de saída já existe: $output_image" | |
| read -p "Deseja sobrescrever? (y/N): " -n 1 -r | |
| echo | |
| if [[ ! $REPLY =~ ^[Yy]$ ]]; then | |
| log "INFO" "Operação cancelada pelo usuário" | |
| exit 0 | |
| fi | |
| rm -f "$output_image" | |
| fi | |
| log "INFO" "=== INICIANDO CUSTOMIZAÇÃO ===" | |
| log "INFO" "Imagem original: $original_image" | |
| log "INFO" "Imagem de saída: $output_image" | |
| log "INFO" "Console serial: ${SERIAL_DEVICE} @ ${BAUD_RATE} baud" | |
| # Criar cópia da imagem | |
| log "INFO" "Copiando imagem original..." | |
| cp "$original_image" "$output_image" | |
| log "SUCCESS" "Cópia criada com sucesso" | |
| # =================================================================== | |
| # CUSTOMIZAÇÕES | |
| # =================================================================== | |
| # 1. Atualização e instalação de pacotes | |
| safe_virt_customize "$output_image" "Atualizando sistema e instalando pacotes" \ | |
| --update \ | |
| --install "qemu-guest-agent,openssh-server,fail2ban,curl,wget,htop,vim" || exit 1 | |
| # 2. Configuração de serviços | |
| safe_virt_customize "$output_image" "Configurando serviços básicos" \ | |
| --run-command 'systemctl enable qemu-guest-agent' \ | |
| --run-command 'systemctl enable ssh' \ | |
| --run-command 'systemctl enable fail2ban' || exit 1 | |
| # 3. Configuração SSH hardening | |
| safe_virt_customize "$output_image" "Aplicando SSH hardening" \ | |
| --edit '/etc/ssh/sshd_config:s/^#\?PermitRootLogin.*/PermitRootLogin no/' \ | |
| --edit '/etc/ssh/sshd_config:s/^#\?PasswordAuthentication.*/PasswordAuthentication no/' \ | |
| --edit '/etc/ssh/sshd_config:s/^#\?PubkeyAuthentication.*/PubkeyAuthentication yes/' \ | |
| --edit '/etc/ssh/sshd_config:s/^#\?X11Forwarding.*/X11Forwarding no/' \ | |
| --append-line '/etc/ssh/sshd_config:ClientAliveInterval 300' \ | |
| --append-line '/etc/ssh/sshd_config:ClientAliveCountMax 2' \ | |
| --append-line '/etc/ssh/sshd_config:MaxAuthTries 3' \ | |
| --append-line '/etc/ssh/sshd_config:Protocol 2' || exit 1 | |
| # 4. Configuração do console serial | |
| log "INFO" "Configurando console serial com autologin..." | |
| safe_virt_customize "$output_image" "Criando diretório de override" \ | |
| --run-command "mkdir -p /etc/systemd/system/serial-getty@${SERIAL_DEVICE}.service.d" || exit 1 | |
| safe_virt_customize "$output_image" "Configurando autologin root" \ | |
| --write "/etc/systemd/system/serial-getty@${SERIAL_DEVICE}.service.d/override.conf:[Service] | |
| ExecStart= | |
| ExecStart=-/sbin/agetty --autologin root --keep-baud ${BAUD_RATE},38400,9600 %I \$TERM | |
| TTYVTDisallocate=no" || exit 1 | |
| safe_virt_customize "$output_image" "Habilitando serial getty" \ | |
| --run-command "systemctl enable serial-getty@${SERIAL_DEVICE}" || exit 1 | |
| # 5. Configuração do GRUB | |
| log "INFO" "Configurando GRUB para console serial..." | |
| safe_virt_customize "$output_image" "Configurando GRUB" \ | |
| --edit "/etc/default/grub:s/^GRUB_CMDLINE_LINUX=.*/GRUB_CMDLINE_LINUX=\"console=tty0 console=${SERIAL_DEVICE},${BAUD_RATE}\"/" \ | |
| --edit '/etc/default/grub:s/^#\?GRUB_TERMINAL=.*/GRUB_TERMINAL="console serial"/' \ | |
| --edit "/etc/default/grub:s/^#\?GRUB_SERIAL_COMMAND=.*/GRUB_SERIAL_COMMAND=\"serial --speed=${BAUD_RATE} --unit=0 --parity=no --stop=1\"/" \ | |
| --run-command "update-grub" || exit 1 | |
| # 6. Time Zone Brasil SP | |
| safe_virt_customize "$output_image" "Time Zone Brasil SP" \ | |
| --timezone "America/Sao_Paulo" || exit 1 | |
| # 7. Limpeza e otimizações finais | |
| safe_virt_customize "$output_image" "Aplicando limpeza final" \ | |
| --run-command 'apt autoremove -y && apt autoclean' \ | |
| --run-command 'find /var/log -name "*.log" -exec truncate -s 0 {} \;' \ | |
| --run-command 'rm -rf /tmp/* /var/tmp/*' || exit 1 | |
| # Aplicar SELinux relabel se necessário (comentado pois Debian não usa SELinux por padrão) | |
| # --selinux-relabel | |
| log "SUCCESS" "=== CUSTOMIZAÇÃO CONCLUÍDA ===" | |
| # Mostrar informações da imagem final | |
| local final_size_mb=$(( $(stat -c%s "$output_image") / 1024 / 1024 )) | |
| log "INFO" "Imagem final: $output_image (${final_size_mb}MB)" | |
| # Mostrar resumo das configurações | |
| show_summary | |
| } | |
| # Função para mostrar resumo | |
| show_summary() { | |
| cat << EOF | |
| ${GREEN}=== CONFIGURAÇÕES APLICADAS ===${NC} | |
| ✓ Console serial: ${SERIAL_DEVICE} @ ${BAUD_RATE} baud | |
| ✓ Autologin root no console serial | |
| ✓ SSH hardening (sem senha, apenas chaves) | |
| ✓ QEMU Guest Agent habilitado | |
| ✓ Fail2ban configurado | |
| ✓ Ferramentas instaladas: curl, wget, htop, vim | |
| ✓ Sistema limpo (logs e arquivos temporários removidos) | |
| ${YELLOW}=== COMANDOS PARA PROXMOX ===${NC} | |
| # Definir VMID | |
| TEMPLATEID=9000 | |
| VMID=9001 | |
| #Imagem | |
| IMAGE="$OUTPUT_IMAGE" | |
| # Senha | |
| SENHA="Mude a senha que esta aqui" | |
| # Criar VM base | |
| qm create \$TEMPLATEID --name debian13-custom --memory 2024 --cores 1 --cpu host --agent enabled=1,fstrim_cloned_disks=1 | |
| # BIOS e hardware básico (sem EFI ainda) | |
| qm set \$TEMPLATEID --scsihw virtio-scsi-pci | |
| qm set \$TEMPLATEID --bios ovmf | |
| qm set \$TEMPLATEID --vga serial0 | |
| qm set \$TEMPLATEID --ostype l26 | |
| qm set \$TEMPLATEID --balloon 1024 | |
| # Console serial | |
| qm set \$TEMPLATEID --serial0 socket | |
| qm set \$TEMPLATEID --vga serial0 | |
| # Rede | |
| qm set \$TEMPLATEID --net0 virtio,bridge=vmbr0 | |
| # Importar disco customizado | |
| qm importdisk \$TEMPLATEID \$IMAGE local-lvm | |
| qm set \$TEMPLATEID --virtio0 local-lvm:vm-\$TEMPLATEID-disk-0,discard=on,iothread=1 | |
| # AGORA criar EFI (criará disk-1) | |
| qm set \$TEMPLATEID --efidisk0 local-lvm:0,efitype=4m,pre-enrolled-keys=1,format=raw | |
| # Cria cloud-init | |
| qm set \$TEMPLATEID --ide2 local-lvm:cloudinit | |
| ${BLUE}=== CONVERTE PARA TEMPLATE ===${NC} | |
| # Converter para template | |
| qm template \$TEMPLATEID | |
| ${BLUE}=== USO DO TEMPLATE ===${NC} | |
| # Clonar nova VM | |
| qm clone \$TEMPLATEID \$VMID --name minha-vm-debian | |
| ${BLUE}=== CONFIGURAÇÂO INDIVIDUAL ===${NC} | |
| # Configurar cloud-init | |
| qm set \$VMID --ciuser emazolini | |
| qm set \$VMID --cipassword "\$SENHA" | |
| qm set \$VMID --sshkeys sshkeys.pub | |
| qm set \$VMID --ipconfig0 ip=dhcp | |
| qm set \$VMID --nameserver 8.8.8.8,1.1.1.1 | |
| # Iniciar VM | |
| qm start \$VMID | |
| ${GREEN}=== ACESSO ===${NC} | |
| • Console serial: Proxmox Web UI > VM > Console (autologin como root) | |
| • SSH: ssh admin@<ip-da-vm> (usando chave privada) | |
| EOF | |
| } | |
| # =================================================================== | |
| # EXECUÇÃO | |
| # =================================================================== | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment