itenv_tools/tools/ubuntu_vm_start.sh
2025-06-15 18:49:18 +02:00

295 lines
8.2 KiB
Bash
Executable File

#!/bin/bash
# Ubuntu VM Start Script with Cloud Hypervisor and Btrfs Thin Provisioning
# Usage: ubuntu_vm_start.sh $name $mem $cores
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
BASE_IMAGE_NAME="ubuntu-24.04-server-cloudimg-amd64"
BASE_IMAGE_URL="https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
FIRMWARE_URL="https://github.com/cloud-hypervisor/rust-hypervisor-firmware/releases/download/0.5.0/hypervisor-fw"
VM_BASE_DIR="/var/lib/vms"
BTRFS_MOUNT_POINT="/var/lib/vms"
BASE_SUBVOL="$BTRFS_MOUNT_POINT/base"
VMS_SUBVOL="$BTRFS_MOUNT_POINT/vms"
log() {
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}"
}
warn() {
echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}"
}
error() {
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}"
exit 1
}
info() {
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $1${NC}"
}
# Check if running as root
if [ "$EUID" -ne 0 ]; then
error "This script must be run as root for btrfs operations"
fi
# Parse arguments
if [ $# -ne 3 ]; then
error "Usage: $0 <vm_name> <memory_mb> <cpu_cores>"
fi
VM_NAME="$1"
MEMORY_MB="$2"
CPU_CORES="$3"
# Validate arguments
if ! [[ "$MEMORY_MB" =~ ^[0-9]+$ ]]; then
error "Memory must be a number (in MB)"
fi
if ! [[ "$CPU_CORES" =~ ^[0-9]+$ ]]; then
error "CPU cores must be a number"
fi
if [[ "$VM_NAME" =~ [^a-zA-Z0-9_-] ]]; then
error "VM name can only contain alphanumeric characters, hyphens, and underscores"
fi
log "Starting VM: $VM_NAME with ${MEMORY_MB}MB RAM and $CPU_CORES CPU cores"
# Check if cloud-hypervisor is available
if ! command -v cloud-hypervisor &> /dev/null; then
error "cloud-hypervisor not found. Please install it first."
fi
# Check if qemu-img is available (for image conversion)
if ! command -v qemu-img &> /dev/null; then
warn "qemu-img not found. Installing qemu-utils..."
apt update && apt install -y qemu-utils
fi
# Create base directory structure
mkdir -p "$VM_BASE_DIR"
# Check if the base directory is on btrfs
FILESYSTEM_TYPE=$(stat -f -c %T "$VM_BASE_DIR" 2>/dev/null)
if [ "$FILESYSTEM_TYPE" != "btrfs" ]; then
error "Base directory $VM_BASE_DIR is not on a btrfs filesystem (detected: $FILESYSTEM_TYPE). Please create a btrfs filesystem first."
fi
log "Btrfs filesystem detected at $VM_BASE_DIR"
# Create base and vms subvolumes if they don't exist
if [ ! -d "$BASE_SUBVOL" ]; then
log "Creating base subvolume at $BASE_SUBVOL"
btrfs subvolume create "$BASE_SUBVOL"
fi
if [ ! -d "$VMS_SUBVOL" ]; then
log "Creating VMs subvolume at $VMS_SUBVOL"
btrfs subvolume create "$VMS_SUBVOL"
fi
# Define paths
BASE_IMAGE_PATH="$BASE_SUBVOL/${BASE_IMAGE_NAME}.raw"
FIRMWARE_PATH="$BASE_SUBVOL/hypervisor-fw"
VM_SUBVOL_PATH="$VMS_SUBVOL/$VM_NAME"
VM_IMAGE_PATH="$VM_SUBVOL_PATH/${VM_NAME}.raw"
CLOUD_INIT_PATH="$VM_SUBVOL_PATH/cloud-init.img"
# Download and prepare base image if it doesn't exist
if [ ! -f "$BASE_IMAGE_PATH" ]; then
log "Base image not found. Downloading Ubuntu cloud image..."
# Download the qcow2 image
TEMP_QCOW2="/tmp/${BASE_IMAGE_NAME}.img"
if ! curl -L --fail --progress-bar -o "$TEMP_QCOW2" "$BASE_IMAGE_URL"; then
error "Failed to download Ubuntu cloud image from $BASE_IMAGE_URL"
fi
log "Converting qcow2 image to raw format..."
qemu-img convert -p -f qcow2 -O raw "$TEMP_QCOW2" "$BASE_IMAGE_PATH"
# Cleanup temporary file
rm -f "$TEMP_QCOW2"
log "Base image created at $BASE_IMAGE_PATH"
fi
# Download firmware if it doesn't exist
if [ ! -f "$FIRMWARE_PATH" ]; then
log "Downloading Cloud Hypervisor firmware..."
if ! curl -L --fail --progress-bar -o "$FIRMWARE_PATH" "$FIRMWARE_URL"; then
error "Failed to download firmware from $FIRMWARE_URL"
fi
chmod +x "$FIRMWARE_PATH"
log "Firmware downloaded to $FIRMWARE_PATH"
fi
# Create VM subvolume by cloning from base
if [ -d "$VM_SUBVOL_PATH" ]; then
warn "VM subvolume $VM_NAME already exists. Removing it..."
btrfs subvolume delete "$VM_SUBVOL_PATH"
fi
log "Creating VM subvolume by cloning base subvolume..."
btrfs subvolume snapshot "$BASE_SUBVOL" "$VM_SUBVOL_PATH"
# Copy the base image to VM subvolume (this will be a CoW copy initially)
log "Creating VM disk image (thin provisioned)..."
cp --reflink=always "$BASE_IMAGE_PATH" "$VM_IMAGE_PATH"
# Create cloud-init image for first boot
log "Creating cloud-init configuration..."
cat > "/tmp/user-data" << EOF
#cloud-config
users:
- name: ubuntu
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
lock_passwd: false
passwd: \$6\$rounds=4096\$saltsalt\$L9.LKkHxeed8Kn9.Kk8nNWn8W.XhHPyjKJJXYqKoTFJJy7P8dMCFK
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC... # Add your SSH public key here
groups: sudo
home: /home/ubuntu
ssh_pwauth: true
disable_root: false
chpasswd:
expire: false
# Enable SSH
ssh_authorized_keys: []
# Package updates and installs
package_update: true
package_upgrade: true
packages:
- curl
- wget
- git
- htop
- vim
# Final message
final_message: "Cloud-init setup complete. VM is ready!"
EOF
# Create meta-data file
cat > "/tmp/meta-data" << EOF
instance-id: $VM_NAME
local-hostname: $VM_NAME
EOF
# Create cloud-init ISO
log "Creating cloud-init ISO..."
if command -v genisoimage &> /dev/null; then
genisoimage -output "$CLOUD_INIT_PATH" -volid cidata -joliet -rock /tmp/user-data /tmp/meta-data
elif command -v mkisofs &> /dev/null; then
mkisofs -o "$CLOUD_INIT_PATH" -V cidata -J -r /tmp/user-data /tmp/meta-data
else
error "Neither genisoimage nor mkisofs found. Please install genisoimage or cdrtools."
fi
# Cleanup temporary files
rm -f /tmp/user-data /tmp/meta-data
log "Cloud-init ISO created at $CLOUD_INIT_PATH"
# Resize the VM disk to give it more space (optional, expand to 20GB)
log "Resizing VM disk to 20GB..."
qemu-img resize "$VM_IMAGE_PATH" 20G
# Create network configuration
BRIDGE_NAME="br0"
TAP_NAME="tap-$VM_NAME"
# Check if bridge exists, create if not
if ! ip link show "$BRIDGE_NAME" &>/dev/null; then
log "Creating bridge interface $BRIDGE_NAME..."
ip link add name "$BRIDGE_NAME" type bridge
ip link set dev "$BRIDGE_NAME" up
# You may want to configure the bridge with an IP address
# ip addr add 192.168.100.1/24 dev "$BRIDGE_NAME"
fi
# Create TAP interface for the VM
log "Creating TAP interface $TAP_NAME..."
ip tuntap add dev "$TAP_NAME" mode tap
ip link set dev "$TAP_NAME" up
ip link set dev "$TAP_NAME" master "$BRIDGE_NAME"
# Start the VM with Cloud Hypervisor
log "Starting VM $VM_NAME..."
VM_SOCKET="/tmp/cloud-hypervisor-$VM_NAME.sock"
# Remove existing socket if it exists
rm -f "$VM_SOCKET"
# Start Cloud Hypervisor in background
cloud-hypervisor \
--api-socket "$VM_SOCKET" \
--memory "size=${MEMORY_MB}M" \
--cpus "boot=$CPU_CORES" \
--kernel "$FIRMWARE_PATH" \
--disk "path=$VM_IMAGE_PATH" "path=$CLOUD_INIT_PATH,readonly=on" \
--net "tap=$TAP_NAME,mac=52:54:00:$(printf '%02x:%02x:%02x' $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)))" \
--serial tty \
--console off \
--log-file /tmp/cloud-hypervisor-$VM_NAME.log &
VM_PID=$!
log "VM $VM_NAME started with PID $VM_PID"
log "VM socket: $VM_SOCKET"
log "TAP interface: $TAP_NAME"
log "Bridge interface: $BRIDGE_NAME"
# Save VM information for management
VM_INFO_FILE="$VM_SUBVOL_PATH/vm-info.txt"
cat > "$VM_INFO_FILE" << EOF
VM_NAME=$VM_NAME
VM_PID=$VM_PID
VM_SOCKET=$VM_SOCKET
TAP_NAME=$TAP_NAME
BRIDGE_NAME=$BRIDGE_NAME
MEMORY_MB=$MEMORY_MB
CPU_CORES=$CPU_CORES
VM_IMAGE_PATH=$VM_IMAGE_PATH
CLOUD_INIT_PATH=$CLOUD_INIT_PATH
STARTED="$(date '+%Y-%m-%d %H:%M:%S')"
EOF
log "VM information saved to $VM_INFO_FILE"
# Function to cleanup on exit
cleanup() {
log "Cleaning up VM $VM_NAME..."
if kill -0 "$VM_PID" 2>/dev/null; then
kill "$VM_PID"
wait "$VM_PID" 2>/dev/null
fi
ip link delete "$TAP_NAME" 2>/dev/null || true
rm -f "$VM_SOCKET"
}
# Set trap for cleanup on script exit
trap cleanup EXIT INT TERM
log "VM $VM_NAME is running. Press Ctrl+C to stop."
log "To connect via SSH (once VM is booted): ssh ubuntu@<vm-ip>"
log "Default password: ubuntu (change after first login)"
# Wait for the VM process
wait "$VM_PID"