Skip to content

Instantly share code, notes, and snippets.

@eduardomazolini
Last active July 7, 2026 22:14
Show Gist options
  • Select an option

  • Save eduardomazolini/124d62de2b0c50b0a15de2d25ca766e2 to your computer and use it in GitHub Desktop.

Select an option

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.
#!/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