Last active
July 7, 2026 22:14
-
-
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 [-y] <imagem-original> [imagem-saida] | |
| # Versão: 3.0 | |
| # Autor: Eduardo Mazolini | |
| set -euo pipefail | |
| # =================================================================== | |
| # CONFIGURAÇÕES GLOBAIS | |
| # =================================================================== | |
| readonly SCRIPT_NAME="$(basename "$0")" | |
| readonly SERIAL_DEVICE="ttyS0" | |
| readonly BAUD_RATE="115200" | |
| readonly TIMEZONE="America/Sao_Paulo" | |
| readonly PACOTES="qemu-guest-agent,openssh-server,fail2ban,curl,wget,htop,vim" | |
| 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') | |
| AUTO_YES=0 | |
| # =================================================================== | |
| # FUNÇÕES AUXILIARES | |
| # =================================================================== | |
| log() { | |
| local level="$1"; shift | |
| local ts; ts=$(date '+%Y-%m-%d %H:%M:%S') | |
| case "$level" in | |
| INFO) echo -e "${BLUE}[INFO]${NC} ${ts} - $*" ;; | |
| WARN) echo -e "${YELLOW}[WARN]${NC} ${ts} - $*" ;; | |
| ERROR) echo -e "${RED}[ERROR]${NC} ${ts} - $*" ;; | |
| SUCCESS) echo -e "${GREEN}[SUCCESS]${NC} ${ts} - $*" ;; | |
| *) echo "[$level] ${ts} - $*" ;; | |
| esac | |
| } | |
| show_help() { | |
| cat << EOF | |
| ${SCRIPT_NAME} - Customização de cloud images Debian | |
| USO: | |
| $SCRIPT_NAME [-y] <imagem-original> [imagem-saida] | |
| OPÇÕES: | |
| -h Mostra esta ajuda | |
| -y Sobrescreve imagem de saída sem perguntar | |
| EXEMPLOS: | |
| $SCRIPT_NAME debian-13-generic-amd64.qcow2 | |
| $SCRIPT_NAME -y debian-13-generic-amd64.qcow2 minha-imagem-custom.qcow2 | |
| DESCRIÇÃO: | |
| Customiza uma cópia da imagem original adicionando: | |
| • QEMU Guest Agent, SSH, Fail2ban, ferramentas básicas | |
| • Console serial (${SERIAL_DEVICE} @ ${BAUD_RATE} baud) com autologin root | |
| • SSH hardening (sem senha, apenas chave) | |
| • Timezone ${TIMEZONE} | |
| • Remoção de pacotes órfãos (apt autoremove/autoclean) | |
| • Preparo para clonagem (virt-sysprep, operações padrão): reseta | |
| machine-id, chaves SSH do host, DHCP state, logs, /tmp, utmp, | |
| bash-history e cache do apt — evita conflitos entre clones | |
| A imagem original nunca é modificada. Ao final, gera também um | |
| arquivo <saida>-proxmox-commands.sh com os comandos de deploy. | |
| REQUISITOS: | |
| apt install libguestfs-tools qemu-utils | |
| EOF | |
| } | |
| check_dependencies() { | |
| local missing=() | |
| command -v virt-customize &> /dev/null || missing+=("libguestfs-tools") | |
| command -v virt-sysprep &> /dev/null || missing+=("libguestfs-tools") | |
| command -v qemu-img &> /dev/null || missing+=("qemu-utils") | |
| mapfile -t missing < <(printf '%s\n' "${missing[@]:-}" | sort -u | sed '/^$/d') | |
| if [ ${#missing[@]} -gt 0 ]; then | |
| log ERROR "Dependências não encontradas: ${missing[*]}" | |
| log INFO "Execute: apt install ${missing[*]}" | |
| return 1 | |
| fi | |
| } | |
| validate_image() { | |
| local image="$1" | |
| [ -f "$image" ] || { log ERROR "Arquivo não encontrado: $image"; return 1; } | |
| [ -r "$image" ] || { log ERROR "Sem permissão de leitura: $image"; return 1; } | |
| qemu-img info "$image" &> /dev/null || { log ERROR "Não é uma imagem válida: $image"; return 1; } | |
| local size_mb=$(( $(stat -c%s "$image") / 1024 / 1024 )) | |
| log SUCCESS "Imagem válida (${size_mb}MB): $image" | |
| } | |
| 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 | |
| } | |
| PROXMOX_SCRIPT_URL="https://gist.githubusercontent.com/eduardomazolini/a83b111a93904f209202e41060d51638/raw/create-vm-linux.sh" | |
| PROXMOX_SCRIPT_LOCAL="criar-template-vm.sh" | |
| # Garante uma cópia local do script de deploy Proxmox (baixado do Gist). | |
| # Fica fora do script principal porque é um assunto à parte (deploy), | |
| # não faz parte da customização da imagem em si. | |
| fetch_proxmox_script() { | |
| if [ ! -f "$PROXMOX_SCRIPT_LOCAL" ]; then | |
| curl -fsSL "$PROXMOX_SCRIPT_URL" -o "$PROXMOX_SCRIPT_LOCAL" \ | |
| || { echo "Erro: falha ao baixar $PROXMOX_SCRIPT_URL" >&2; return 1; } | |
| chmod +x "$PROXMOX_SCRIPT_LOCAL" | |
| fi | |
| } | |
| # Gera um wrapper que chama o script de deploy já com a imagem certa. | |
| write_proxmox_commands() { | |
| local image="$1" outfile="$2" | |
| fetch_proxmox_script || return 1 | |
| cat > "$outfile" << EOF | |
| #!/bin/bash | |
| # Wrapper gerado automaticamente para $image | |
| # Script de deploy real: $PROXMOX_SCRIPT_LOCAL (baixado do Gist) | |
| IMAGE="$image" exec "\$(dirname "\$0")/$PROXMOX_SCRIPT_LOCAL" | |
| EOF | |
| chmod +x "$outfile" | |
| } | |
| # =================================================================== | |
| # FUNÇÃO PRINCIPAL | |
| # =================================================================== | |
| main() { | |
| while getopts ":hy" opt; do | |
| case "$opt" in | |
| h) show_help; exit 0 ;; | |
| y) AUTO_YES=1 ;; | |
| \?) log ERROR "Opção inválida: -$OPTARG"; show_help; exit 1 ;; | |
| esac | |
| done | |
| shift $((OPTIND - 1)) | |
| if [ $# -lt 1 ]; then | |
| log ERROR "Nenhuma imagem especificada" | |
| show_help | |
| exit 1 | |
| fi | |
| local original_image="$1" | |
| local output_image="${2:-${original_image%.qcow2}-custom.qcow2}" | |
| readonly ORIGINAL_IMAGE="$original_image" | |
| readonly OUTPUT_IMAGE="$output_image" | |
| trap cleanup EXIT INT TERM | |
| check_dependencies || exit 1 | |
| validate_image "$original_image" || exit 1 | |
| if [ -f "$output_image" ]; then | |
| if [ "$AUTO_YES" -ne 1 ]; then | |
| read -rp "Imagem de saída já existe. Sobrescrever? (y/N): " -n 1 REPLY | |
| echo | |
| [[ "$REPLY" =~ ^[Yy]$ ]] || { log INFO "Operação cancelada"; exit 0; } | |
| fi | |
| rm -f "$output_image" | |
| fi | |
| log INFO "=== INICIANDO CUSTOMIZAÇÃO ===" | |
| log INFO "Original: $original_image → Saída: $output_image" | |
| cp "$original_image" "$output_image" | |
| # Todas as customizações em uma única chamada ao virt-customize: | |
| # cada --run-command/--edit/etc. é aplicado na ordem abaixo, dentro | |
| # de um único boot do appliance (em vez de um boot por etapa). | |
| local args=( | |
| --update | |
| --install "$PACOTES" | |
| --run-command 'systemctl enable qemu-guest-agent' | |
| --run-command 'systemctl enable ssh' | |
| --run-command 'systemctl enable fail2ban' | |
| # 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' | |
| # Console serial com autologin root | |
| --run-command "mkdir -p /etc/systemd/system/serial-getty@${SERIAL_DEVICE}.service.d" | |
| --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" | |
| --run-command "systemctl enable serial-getty@${SERIAL_DEVICE}" | |
| # GRUB para console serial | |
| --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" | |
| --timezone "$TIMEZONE" | |
| # Só o que virt-sysprep NÃO cobre por padrão: remover pacotes | |
| # órfãos. (virt-sysprep limpa cache do apt, mas não faz autoremove.) | |
| --run-command 'apt autoremove -y && apt autoclean' | |
| ) | |
| log INFO "Aplicando customizações (uma única execução do virt-customize)..." | |
| if ! virt-customize -a "$output_image" "${args[@]}"; then | |
| log ERROR "virt-customize falhou" | |
| exit 1 | |
| fi | |
| log SUCCESS "Customizações aplicadas" | |
| # virt-sysprep prepara a imagem para ser clonada como template, | |
| # usando o conjunto padrão de operações da própria ferramenta: | |
| # reseta machine-id, chaves SSH do host, estado de DHCP, regras udev | |
| # de MAC persistente, trunca logs, limpa /tmp, utmp, bash-history, | |
| # cache do apt, etc. Sem isso, todos os clones nascem com o mesmo | |
| # machine-id e podem ter conflitos de DHCP mesmo com MAC diferente | |
| # (o DUID do DHCP é derivado do machine-id, não só do MAC). | |
| log INFO "Preparando imagem para clonagem (virt-sysprep, operações padrão)..." | |
| if ! virt-sysprep -a "$output_image"; then | |
| log ERROR "virt-sysprep falhou" | |
| exit 1 | |
| fi | |
| log SUCCESS "Imagem pronta para clonagem" | |
| local proxmox_file="${output_image%.qcow2}-proxmox-commands.sh" | |
| write_proxmox_commands "$output_image" "$proxmox_file" | |
| local final_size_mb=$(( $(stat -c%s "$output_image") / 1024 / 1024 )) | |
| log SUCCESS "=== CUSTOMIZAÇÃO CONCLUÍDA ===" | |
| log INFO "Imagem final: $output_image (${final_size_mb}MB)" | |
| log INFO "Comandos de deploy Proxmox: $proxmox_file" | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment