#!/bin/bash set -e VM_NAME="vm-ubuntu2204" VCPUS=16 RAM=16384 DISK_SIZE=50G IMG_URL="https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img" POOL_DIR="/var/lib/libvirt/images" BASE_IMG="$POOL_DIR/jammy-base.img" VM_DISK="$POOL_DIR/${VM_NAME}.qcow2" SEED_ISO="$POOL_DIR/${VM_NAME}-seed.iso" PASSWORD="ubuntu" GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m' log() { echo -e "${GREEN}[$(date '+%H:%M:%S')] [INFO]${NC} $1"; } warn() { echo -e "${YELLOW}[$(date '+%H:%M:%S')] [WARN]${NC} $1"; } err() { echo -e "${RED}[$(date '+%H:%M:%S')] [ERROR]${NC} $1"; } step() { echo -e "\n${GREEN}=========================================="; echo -e " $1"; echo -e "==========================================${NC}"; } trap 'err "Script failed at line $LINENO. Aborting."' ERR echo -e "${GREEN}" echo "##############################################" echo "# DEPLOY UBUNTU 22.04 VM ON KVM #" echo "# VM: $VM_NAME | ${VCPUS} vCPU | $((RAM/1024))GB RAM | $DISK_SIZE #" echo "##############################################" echo -e "${NC}" # --------------------------------------------- step "STEP 0/6: ENVIRONMENT CHECKS" # --------------------------------------------- [ "$EUID" -eq 0 ] || { err "Must be run as root/sudo."; exit 1; } log "Running as root: OK" if grep -qE 'vmx|svm' /proc/cpuinfo; then log "CPU supports hardware virtualization: OK" else err "CPU does not support VT-x/AMD-V, or it is disabled in BIOS." exit 1 fi HOST_CPU=$(nproc); HOST_RAM=$(free -m | awk '/^Mem:/{print $2}') log "Host: ${HOST_CPU} CPUs, ${HOST_RAM} MB RAM" [ "$HOST_CPU" -lt "$VCPUS" ] && warn "Host has only ${HOST_CPU} CPUs but VM requests ${VCPUS} (overcommit)." [ "$HOST_RAM" -lt "$RAM" ] && warn "Host has only ${HOST_RAM} MB RAM but VM requests ${RAM} MB." log "Free disk space: $(df -h /var/lib | awk 'NR==2{print $4}')" # --------------------------------------------- step "STEP 1/6: INSTALLING LIBVIRT / KVM" # --------------------------------------------- log "Updating apt index..." apt-get update -qq log "Installing packages: qemu-kvm, libvirt, virtinst, cloud-image-utils..." DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \ qemu-kvm libvirt-daemon-system libvirt-clients \ virtinst bridge-utils cloud-image-utils wget whois \ libosinfo-bin osinfo-db-tools log "Package installation complete." log "Starting libvirtd..." systemctl enable --now libvirtd systemctl is-active --quiet libvirtd && log "libvirtd is running: OK" || { err "libvirtd failed to start."; exit 1; } log "Versions: $(virsh --version) | $(kvm --version | head -1)" log "Configuring 'default' NAT network..." virsh net-start default 2>/dev/null && log "Started default network." || warn "Default network already running." virsh net-autostart default >/dev/null 2>&1 || true log "Network status:" virsh net-list --all # --------------------------------------------- step "STEP 2/6: RESOLVING OS-VARIANT" # --------------------------------------------- os_exists() { osinfo-query os short-id="$1" 2>/dev/null | grep -qw "$1"; } OS_VARIANT="" log "Checking local osinfo database for ubuntu22.04..." if os_exists ubuntu22.04; then OS_VARIANT="ubuntu22.04" log "Found ubuntu22.04 in osinfo database." else warn "ubuntu22.04 not found (Ubuntu 20.04 ships an older osinfo-db)." log "Attempting to import an updated osinfo database..." OSINFO_TAR="/tmp/osinfo-db.tar.xz" if wget -q -O "$OSINFO_TAR" "https://releases.pagure.org/libosinfo/osinfo-db-20240701.tar.xz"; then if osinfo-db-import --local "$OSINFO_TAR" >/dev/null 2>&1; then log "osinfo database imported successfully." else warn "osinfo-db-import failed." fi rm -f "$OSINFO_TAR" else warn "Could not download osinfo-db (no network or URL changed)." fi log "Re-checking database after import..." for v in ubuntu22.04 ubuntu20.04 ubuntu18.04 generic; do if [ "$v" = "generic" ] || os_exists "$v"; then OS_VARIANT="$v"; break fi done fi OS_VARIANT="${OS_VARIANT:-generic}" log "Using --os-variant $OS_VARIANT" [ "$OS_VARIANT" != "ubuntu22.04" ] && warn "Falling back to '$OS_VARIANT'. This only affects device/perf defaults; virtio + cloud image still works." # --------------------------------------------- step "STEP 3/6: PREPARING CLOUD IMAGE" # --------------------------------------------- if [ -f "$BASE_IMG" ]; then log "Base image already exists ($(du -h "$BASE_IMG" | cut -f1)), skipping download." else log "Downloading Ubuntu 22.04 cloud image (~600MB)..." log "URL: $IMG_URL" wget -q --show-progress -O "$BASE_IMG" "$IMG_URL" log "Download complete: $(du -h "$BASE_IMG" | cut -f1)" fi log "Base image info:" qemu-img info "$BASE_IMG" | sed 's/^/ /' # --------------------------------------------- step "STEP 4/6: CREATING DISK AND CLOUD-INIT" # --------------------------------------------- log "Creating ${DISK_SIZE} overlay disk for the VM..." rm -f "$VM_DISK" qemu-img create -f qcow2 -F qcow2 -b "$BASE_IMG" "$VM_DISK" "$DISK_SIZE" >/dev/null log "Disk created: $VM_DISK" log "Generating password hash for root..." HASH=$(mkpasswd -m sha-512 "$PASSWORD") WORKDIR=$(mktemp -d) log "Temp directory: $WORKDIR" log "Writing user-data (enable root login, password auth)..." cat > "$WORKDIR/user-data" < "$WORKDIR/meta-data" </dev/null 2>&1; then warn "VM '$VM_NAME' already exists (possibly from a failed run). Removing it..." virsh destroy "$VM_NAME" 2>/dev/null || true virsh undefine "$VM_NAME" 2>/dev/null || true log "Old VM removed." fi log "Running virt-install with --os-variant $OS_VARIANT ..." virt-install \ --name "$VM_NAME" \ --memory "$RAM" \ --vcpus "$VCPUS" \ --cpu host-passthrough \ --disk path="$VM_DISK",device=disk,bus=virtio \ --disk path="$SEED_ISO",device=cdrom \ --os-variant "$OS_VARIANT" \ --network network=default,model=virtio \ --graphics none \ --console pty,target_type=serial \ --import \ --noautoconsole log "VM defined and started." virsh autostart "$VM_NAME" >/dev/null log "Autostart enabled for the VM." # --------------------------------------------- step "STEP 6/6: WAITING FOR BOOT AND IP" # --------------------------------------------- log "Waiting for cloud-init to finish and the VM to get an IP (up to 120s)..." IP="" for i in $(seq 1 24); do IP=$(virsh domifaddr "$VM_NAME" 2>/dev/null | awk '/ipv4/{split($4,a,"/"); print a[1]}') if [ -n "$IP" ]; then log "Got IP after $((i*5))s: $IP" break fi echo -e "${YELLOW} ... waiting ($((i*5))s/120s)${NC}" sleep 5 done [ -z "$IP" ] && warn "No IP yet. Check later with: virsh domifaddr $VM_NAME" # --------------------------------------------- step "DONE" # --------------------------------------------- virsh dominfo "$VM_NAME" | sed 's/^/ /' echo log "VM Name : $VM_NAME" log "vCPU/RAM : ${VCPUS} / $((RAM/1024))GB" log "Disk : $VM_DISK ($DISK_SIZE)" log "OS variant : $OS_VARIANT" log "IP : ${IP:-}" log "Login : root / $PASSWORD" echo log "Console : virsh console $VM_NAME (exit: Ctrl+])" [ -n "$IP" ] && log "SSH : ssh root@$IP" warn "Cloud-init may need another 30-60s to finish SSH configuration."