Скрипт автоматизации создания виртуальных машин в KVM/QEMU

Решил набросать небольшой скрипт для упрощения создания виртуальных машин при помощи Debootstrap. Привожу здесь исходный код.
#!/bin/bash
#
# Create a virtual machine based on debootstrap.
#
# Usage:
# create-vm.sh
# VM_NAME : virtual machine name
# DISK_SIZE_GB : [1-200] in gigabytes
# BLK_DEVICE : /dev/nbdX should be used
# VM_IP_ADDR : virtual machine IP-address (this script dont check an IP existance)
# VM_NETMASK : netmask
# VM_GATEWAY : gateway
# RAM_KB : [1024-4096] in megabytes
# VNC_PORT : [5900-5999]
# BRIDGE : virtual bridge ID (e.g. br0)
#
EXIT_SUCCESS=0
EXIT_ERROR=1
MSG_USAGE='usage: '"$0"' VM_NAME DISK_SIZE_GB BLK_DEVICE VM_IP_ADDR VM_NETMASK VM_GATEWAY RAM_KB VNC_PORT BRIDGE'
str_eandu() {
CLEARED="`expr $1 : '[.]*\([a-zA-Z0-9.-]*\)'`"
if [ "$CLEARED" != $1 ]; then
return "$EXIT_ERROR"
fi
return "$EXIT_SUCCESS"
}
ip_check() {
MIN=1
MAX=253
if [ "$#" -eq 3 ]; then
MIN="$2"
MAX="$3"
fi
IP_ADDR=$(echo $1 | tr '.' ' ')
for OCTET in $IP_ADDR; do
if ! [[ "$OCTET" =~ ^-?[0-9]+$ ]]; then
exit_error "$VM_IP_ADDR"': '"$OCTET"' is NOT an integer'
else
if [ "$OCTET" -lt "$MIN" ] || [ "$OCTET" -gt "$MAX" ]; then
exit_error "$VM_IP_ADDR"': '"$OCTET is bad"
fi
fi
done
}
integer_check() {
if ! [[ "$1" =~ ^-?[0-9]+$ ]]; then
exit_error "$1"' is NOT an integer'
fi
}
exit_error() {
>&2 echo "$1"
exit "$EXIT_ERROR"
}
if [ "$#" == 0 ] || [ "$#" -lt 9 ]; then
exit_error "$MSG_USAGE"
fi
IMG_TYPE="qcow2"
VM_ROOT="/hdd"
VM_NAME="$1"
DISK_SIZE_MIN=8
DISK_SIZE_MAX=200
DISK_SIZE_GB="$2"
DISK_FULL_FILENAME="${VM_ROOT}/${1}.${IMG_TYPE}"
BLK_DEVICE="$3"
VM_IP_ADDR="$4"
VM_NETMASK="$5"
VM_GATEWAY="$6"
RAM_MIN=1024
RAM_MAX=4096
RAM_KB="$7"
let SWAP_SIZE=RAM_KB/2
EFI_PART_SIZE=10
let SWAP_SIZE_OFFSET=SWAP_SIZE+EFI_PART_SIZE
VNC_PORT_MIN=5900
VNC_PORT_MAX=5999
VNC_PORT="$8"
BRIDGE="$9"
#
# proxy settings
#
# use: export http_proxy="http://username:password@proxy-ip:proxy-port"
VM_PROXY="# no proxy"
if [ ! "$http_proxy" == "" ]; then
VM_PROXY='Acquire::http::Proxy \"'"$http_proxy"'\";'
fi
#
# kernel module load
#
NBD=$(lsmod | grep nbd)
if [ -z "$NBD" ]; then
modprobe nbd
fi
#
# VM name check
#
if ! str_eandu "$VM_NAME" -eq 0; then
exit_error 'VM name '"$VM_NAME"' is not valid'
fi
#
# VM existance check
#
IS_ANY_VM_EXISTS=$(virsh list --name)
if [ ! "$IS_ANY_VM_EXISTS" == "" ]; then
IS_VM_EXISTS=$(virsh list --name | grep -e ^${VM_NAME}$)
if [ ! "$IS_VM_EXISTS" == "" ]; then
exit_error 'VM '"$VM_NAME"' is exists'
fi
fi
#
# VM image file check
#
if [ -f "$DISK_FULL_FILENAME" ]; then
exit_error "$DISK_FULL_FILENAME"' is already exists'
fi
integer_check "$DISK_SIZE_GB"
if [ "$DISK_SIZE_GB" -lt "$DISK_SIZE_MIN" ] || [ "$DISK_SIZE_GB" -gt "$DISK_SIZE_MAX" ]; then
exit_error "$DISK_SIZE_GB"' is invalid value'
fi
#
# value "$DISK_SIZE_GB"G or "${DISK_SIZE_GB}G" should be used later
#
#
# block device check
#
if [[ $(file "$BLK_DEVICE") != *"block special"* ]]; then
exit_error "$BLK_DEVICE"' is NOT a block device'
fi
DEVICE_NAME=$(basename ${BLK_DEVICE} | grep ^nbd)
if [ "$DEVICE_NAME" == "" ]; then
exit_error "$BLK_DEVICE"' is not supported (should be /dev/nbd)'
fi
#
# VM IP-address check
#
# Warning! There is no IP-address existance checking here
#
ip_check "$VM_IP_ADDR"
ip_check "$VM_NETMASK" 0 255
ip_check "$VM_GATEWAY"
#
# RAM size check
#
integer_check "$RAM_KB"
if [ "$RAM_KB" -lt "$RAM_MIN" ] || [ "$RAM_KB" -gt "$RAM_MAX" ]; then
exit_error "$RAM_KB"' is invalid value'
fi
#
# VM VNC port check
#
integer_check "$VNC_PORT"
if [ "$VNC_PORT" -lt "$VNC_PORT_MIN" ] || [ "$VNC_PORT" -gt "$VNC_PORT_MAX" ]; then
exit_error "$VNC_PORT"' is invalid value'
fi
virsh list --name --all | while read VM ; do
if [ ! "$VM" == "" ]; then
RESULT=$(virsh vncdisplay ${VM} 2>/dev/null)
if [ ! "$RESULT" == "" ]; then
CUR_PORT=$(echo ${RESULT} | awk -F ':' '{print $2}')
let FULL_PORT=CUR_PORT+5900
if [ "$VNC_PORT" == "$FULL_PORT" ]; then
exit_error "$VNC_PORT is already used"
fi
fi
fi
done
#
# bridge check
#
if [ "$(ip a | grep ' '${BRIDGE}:)" == "" ]; then
exit_error "Bridge $BRIDGE check error"
fi
#
# additional check
#
if [ "$?" -ne 0 ]; then
exit_error "Some errors before image creating"
fi
#
# start VM image creating
#
qemu-img create -f "$IMG_TYPE" "$DISK_FULL_FILENAME" "$DISK_SIZE_GB"G
if [ "$?" -eq 1 ]; then
exit_error "$DISK_FULL_FILENAME creating error"
fi
qemu-nbd -c "$BLK_DEVICE" "$DISK_FULL_FILENAME" 2>/dev/null
if [ "$?" -eq 1 ]; then
rm -rf "$DISK_FULL_FILENAME"
exit_error "Device $BLK_DEVICE is already used"
fi
# Set partition table to GPT (UEFI)
parted "$BLK_DEVICE" --script mktable gpt
if [ "$?" -ne 0 ]; then
qemu-nbd --disconnect "$BLK_DEVICE"
rm -rf "$DISK_FULL_FILENAME"
exit_error "Parted: GPT creating error"
fi
# Create EFI partition
parted "$BLK_DEVICE" --script mkpart EFI fat16 1MiB "$EFI_PART_SIZE"MiB
if [ "$?" -ne 0 ]; then
qemu-nbd --disconnect "$BLK_DEVICE"
rm -rf "$DISK_FULL_FILENAME"
exit_error "Parted: EFI part creating error"
fi
parted "$BLK_DEVICE" --script set 1 msftdata on
# Create SWAP partition
parted "$BLK_DEVICE" --script mkpart linux-swap "$EFI_PART_SIZE"MiB "$SWAP_SIZE_OFFSET"MiB
if [ "$?" -ne 0 ]; then
qemu-nbd --disconnect "$BLK_DEVICE"
rm -rf "$DISK_FULL_FILENAME"
exit_error "Parted: SWAP part creating error"
fi
# Create OS partition
parted "$BLK_DEVICE" --script mkpart LINUX ext4 "$SWAP_SIZE_OFFSET"MiB 100%
if [ "$?" -ne 0 ]; then
qemu-nbd --disconnect "$BLK_DEVICE"
rm -rf "$DISK_FULL_FILENAME"
exit_error "Parted: EXT4 (rootfs) part creating error"
fi
# Format partitions
mkfs.vfat -n EFI "$BLK_DEVICE"p1
if [ "$?" -ne 0 ]; then
qemu-nbd --disconnect "$BLK_DEVICE"
rm -rf "$DISK_FULL_FILENAME"
exit_error "EFI format error"
fi
mkswap "$BLK_DEVICE"p2
if [ "$?" -ne 0 ]; then
qemu-nbd --disconnect "$BLK_DEVICE"
rm -rf "$DISK_FULL_FILENAME"
exit_error "SWAP format error"
fi
mkfs.ext4 -L LINUX "$BLK_DEVICE"p3
if [ "$?" -ne 0 ]; then
qemu-nbd --disconnect "$BLK_DEVICE"
rm -rf "$DISK_FULL_FILENAME"
exit_error "EXT4 (rootfs) format error"
fi
# Mount OS partition
ROOTFS="/tmp/installing-rootfs"
mkdir -p "$ROOTFS"
if [ "$?" -ne 0 ]; then
qemu-nbd --disconnect "$BLK_DEVICE"
rm -rf "$ROOTFS"
rm -rf "$DISK_FULL_FILENAME"
exit_error "$ROOTFS creating error"
fi
mount "$BLK_DEVICE"p3 "$ROOTFS"
if [ "$?" -ne 0 ]; then
qemu-nbd --disconnect "$BLK_DEVICE"
rm -rf "$ROOTFS"
rm -rf "$DISK_FULL_FILENAME"
exit_error "$ROOTFS mount error"
fi
# Debootstrap system
debootstrap --no-check-gpg --arch=amd64 --include="openssh-server,sudo" stable "$ROOTFS" http://httpredir.debian.org/debian/
if [ "$?" -ne 0 ]; then
umount "$ROOTFS"
qemu-nbd --disconnect "$BLK_DEVICE"
rm -rf "$ROOTFS"
rm -rf "$DISK_FULL_FILENAME"
exit_error "Debootstrap failed"
fi
# Mount EFI partition
mkdir -p "$ROOTFS"/boot/efi
if [ "$?" -ne 0 ]; then
umount "$ROOTFS"
qemu-nbd --disconnect "$BLK_DEVICE"
rm -rf "$ROOTFS"
rm -rf "$DISK_FULL_FILENAME"
exit_error "$ROOTFS/boot/efi creating error"
fi
mount "$BLK_DEVICE"p1 "$ROOTFS"/boot/efi
if [ "$?" -ne 0 ]; then
umount "$ROOTFS"
qemu-nbd --disconnect "$BLK_DEVICE"
rm -rf "$ROOTFS"
rm -rf "$ROOTFS"/boot/efi
rm -rf "$DISK_FULL_FILENAME"
exit_error "$ROOTFS/boot/efi mount error"
fi
# Get ready for chroot
mount --bind /dev "$ROOTFS"/dev
if [ "$?" -ne 0 ]; then
umount "$ROOTFS"/boot/efi
umount "$ROOTFS"
qemu-nbd --disconnect "$BLK_DEVICE"
rm -rf "$ROOTFS"
rm -rf "$ROOTFS"/boot/efi
rm -rf "$DISK_FULL_FILENAME"
exit_error "$ROOTFS/dev mount error"
fi
mount -t devpts /dev/pts "$ROOTFS"/dev/pts
if [ "$?" -ne 0 ]; then
umount "$ROOTFS"/dev
umount "$ROOTFS"/boot/efi
umount "$ROOTFS"
qemu-nbd --disconnect "$BLK_DEVICE"
rm -rf "$ROOTFS"
rm -rf "$ROOTFS"/boot/efi
rm -rf "$DISK_FULL_FILENAME"
exit_error "$ROOTFS/dev/pts mount error"
fi
mount -t proc proc "$ROOTFS"/proc
if [ "$?" -ne 0 ]; then
umount "$ROOTFS"/dev/pts
umount "$ROOTFS"/dev
umount "$ROOTFS"/boot/efi
umount "$ROOTFS"
qemu-nbd --disconnect "$BLK_DEVICE"
rm -rf "$ROOTFS"
rm -rf "$ROOTFS"/boot/efi
rm -rf "$DISK_FULL_FILENAME"
exit_error "$ROOTFS/proc mount error"
fi
mount -t sysfs sysfs "$ROOTFS"/sys
if [ "$?" -ne 0 ]; then
umount "$ROOTFS"/proc
umount "$ROOTFS"/dev/pts
umount "$ROOTFS"/dev
umount "$ROOTFS"/boot/efi
umount "$ROOTFS"
qemu-nbd --disconnect "$BLK_DEVICE"
rm -rf "$ROOTFS"
rm -rf "$ROOTFS"/boot/efi
rm -rf "$DISK_FULL_FILENAME"
exit_error "$ROOTFS/sys mount error"
fi
mount -t tmpfs tmpfs "$ROOTFS"/tmp
if [ "$?" -ne 0 ]; then
umount "$ROOTFS"/sys
umount "$ROOTFS"/proc
umount "$ROOTFS"/dev/pts
umount "$ROOTFS"/dev
umount "$ROOTFS"/boot/efi
umount "$ROOTFS"
qemu-nbd --disconnect "$BLK_DEVICE"
rm -rf "$ROOTFS"
rm -rf "$ROOTFS"/boot/efi
rm -rf "$DISK_FULL_FILENAME"
exit_error "$ROOTFS/tmp mount error"
fi
# Entering chroot, installing Linux kernel and Grub
cat << EOF | chroot "$ROOTFS"
set -e
echo "$VM_NAME" > /etc/hostname
echo 127.0.0.1 localhost $VM_NAME >> /etc/hosts
chpasswd <<<"root:root"
useradd -m administrator
chpasswd <<<"administrator:administrator"
usermod -aG sudo administrator
export HOME=/root
export DEBIAN_FRONTEND=noninteractive
debconf-set-selections <<< 'grub-efi-amd64 grub2/update_nvram boolean false'
echo 'deb http://deb.debian.org/debian/ bookworm main non-free-firmware' >> /etc/apt/sources.list
echo 'deb http://security.debian.org/debian-security bookworm-security main non-free-firmware' >> /etc/apt/sources.list
echo 'deb http://deb.debian.org/debian/ bookworm-updates main non-free-firmware' >> /etc/apt/sources.list
echo "$VM_PROXY" >> /etc/apt/apt.conf
apt update
apt upgrade -y
apt install -y firmware-realtek linux-image-amd64 linux-headers-amd64 grub-efi
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian --recheck --no-nvram --removable
update-grub
echo '/dev/sda1 /boot/efi vfat umask=0077 0 1' >> /etc/fstab
echo '/dev/sda2 none swap sw 0 0' >> /etc/fstab
echo '/dev/sda3 / ext4 errors=remount-ro 0 1' >> /etc/fstab
echo 'auto enp1s0' >> /etc/network/interfaces
echo 'iface enp1s0 inet static' >> /etc/network/interfaces
echo 'address '"$VM_IP_ADDR" >> /etc/network/interfaces
echo 'netmask '"$VM_NETMASK" >> /etc/network/interfaces
echo 'gateway '"$VM_GATEWAY" >> /etc/network/interfaces
echo 'administrator ALL=(ALL:ALL) ALL' >> /etc/sudoers
EOF
umount "$ROOTFS"/dev/pts
umount "$ROOTFS"/dev
umount "$ROOTFS"/proc
umount "$ROOTFS"/sys
umount "$ROOTFS"/tmp
umount "$ROOTFS"/boot/efi
umount "$ROOTFS"
qemu-nbd --disconnect "$BLK_DEVICE"
if [ "$?" -ne 0 ]; then
rm -rf "$ROOTFS"
rm -rf "$ROOTFS"/boot/efi
rm -rf "$DISK_FULL_FILENAME"
exit_error "$BLK_DEVICE disconnect error"
fi
# test start
#qemu-system-x86_64 -bios /usr/share/ovmf/OVMF.fd -m $RAM_KB -drive file="$DISK_FULL_FILENAME" -vnc "0.0.0.0":"$VNC_PORT" -daemonize
virt-install --name="$VM_NAME" \
--ram="$RAM_KB" \
--cpu host \
--boot firmware="efi" \
--import \
--disk path="$DISK_FULL_FILENAME",bus=scsi,format=qcow \
--os-variant=debian11 \
--graphics type=vnc,port="$VNC_PORT",listen=0.0.0.0 \
--network bridge="$BRIDGE" \
--controller usb2 \
--controller type=scsi,model=virtio-scsi \
--noreboot
if [ "$?" -ne 0 ]; then
rm -rf "$ROOTFS"
rm -rf "$ROOTFS"/boot/efi
rm -rf "$DISK_FULL_FILENAME"
virsh undefine --nvram "$VM_NAME"
exit_error "$VM_NAME creating error"
fi
virsh start $VM_NAME
if [ "$?" -ne 0 ]; then
rm -rf "$ROOTFS"
rm -rf "$ROOTFS"/boot/efi
rm -rf "$DISK_FULL_FILENAME"
virsh undefine --nvram "$VM_NAME"
exit_error "$VM_NAME starting error"
fi
echo 'Done'
exit "$EXIT_SUCCESS"