diff --git a/tools/extract_kernel.sh b/tools/extract_kernel.sh new file mode 100755 index 0000000..b004043 --- /dev/null +++ b/tools/extract_kernel.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# Extract kernel and initrd from Ubuntu cloud image +# This script ensures kernel boot works automatically + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +log() { + echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}" +} + +error() { + echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}" + exit 1 +} + +BASE_SUBVOL="/var/lib/vms/base" +BASE_IMAGE_PATH="$BASE_SUBVOL/ubuntu-24.04-server-cloudimg-amd64.raw" +KERNEL_PATH="$BASE_SUBVOL/vmlinuz" +INITRD_PATH="$BASE_SUBVOL/initrd.img" + +# Check if kernel and initrd already exist +if [ -f "$KERNEL_PATH" ] && [ -f "$INITRD_PATH" ]; then + log "Kernel and initrd already extracted" + exit 0 +fi + +# Check if base image exists +if [ ! -f "$BASE_IMAGE_PATH" ]; then + error "Base image not found at $BASE_IMAGE_PATH" +fi + +log "Extracting kernel and initrd from base image..." + +# Create temporary mount point +TEMP_MOUNT=$(mktemp -d) +trap "umount '$TEMP_MOUNT' 2>/dev/null || true; losetup -d /dev/loop1 2>/dev/null || true; rmdir '$TEMP_MOUNT'" EXIT + +# Mount the image +losetup -P /dev/loop1 "$BASE_IMAGE_PATH" + +# Mount the boot partition (partition 16 contains kernel/initrd) +mount /dev/loop1p16 "$TEMP_MOUNT" + +# Copy kernel and initrd +if [ -f "$TEMP_MOUNT/vmlinuz-6.8.0-60-generic" ] && [ -f "$TEMP_MOUNT/initrd.img-6.8.0-60-generic" ]; then + cp "$TEMP_MOUNT/vmlinuz-6.8.0-60-generic" "$KERNEL_PATH" + cp "$TEMP_MOUNT/initrd.img-6.8.0-60-generic" "$INITRD_PATH" + log "✓ Kernel and initrd extracted successfully" +else + error "Kernel or initrd not found in boot partition" +fi + +# Cleanup is handled by trap +log "Kernel extraction completed" \ No newline at end of file diff --git a/tools/ubuntu_vm_manage.sh b/tools/ubuntu_vm_manage.sh index b00fdd9..f6da388 100755 --- a/tools/ubuntu_vm_manage.sh +++ b/tools/ubuntu_vm_manage.sh @@ -1,7 +1,7 @@ #!/bin/bash # Ubuntu VM Management Script -# Usage: ubuntu_vm_manage.sh [vm_name] +# Usage: ubuntu_vm_manage.sh [vm_number] set -e @@ -36,65 +36,73 @@ info() { show_usage() { echo "Ubuntu VM Management Script" echo "" - echo "Usage: $0 [vm_name]" + echo "Usage: $0 [vm_number]" echo "" echo "Commands:" echo " list - List all VMs and their status" - echo " status - Show detailed status of a specific VM" - echo " console - Connect to VM console (serial)" - echo " ssh - SSH to VM (requires network setup)" - echo " stop - Stop a running VM" - echo " start - Start a stopped VM" - echo " delete - Delete a VM completely" - echo " ip - Show VM IP address" - echo " logs - Show VM logs" + echo " status - Show detailed status of a specific VM" + echo " console - Connect to VM console (serial)" + echo " ssh - SSH to VM (requires network setup)" + echo " stop - Stop a running VM" + echo " start - Start a stopped VM" + echo " delete - Delete a VM completely" + echo " ip - Show VM IP address" + echo " logs - Show VM logs" echo "" echo "Examples:" echo " $0 list" - echo " $0 status test-vm" - echo " $0 console test-vm" - echo " $0 ssh test-vm" + echo " $0 status 1" + echo " $0 console 1" + echo " $0 ssh 1" } list_vms() { log "Listing all VMs..." echo "" - printf "%-15s %-10s %-8s %-15s %-20s\n" "VM NAME" "STATUS" "PID" "MEMORY" "STARTED" - printf "%-15s %-10s %-8s %-15s %-20s\n" "-------" "------" "---" "------" "-------" + printf "%-8s %-15s %-15s %-10s %-8s %-15s %-20s\n" "VM #" "IP ADDRESS" "VM NAME" "STATUS" "PID" "MEMORY" "STARTED" + printf "%-8s %-15s %-15s %-10s %-8s %-15s %-20s\n" "----" "----------" "-------" "------" "---" "------" "-------" if [ ! -d "$VMS_SUBVOL" ]; then warn "No VMs directory found at $VMS_SUBVOL" return fi - for vm_dir in "$VMS_SUBVOL"/*; do + for vm_dir in "$VMS_SUBVOL"/vm*; do if [ -d "$vm_dir" ]; then - vm_name=$(basename "$vm_dir") - vm_info_file="$vm_dir/vm-info.txt" - - if [ -f "$vm_info_file" ]; then - # Safely source the file with error handling - if source "$vm_info_file" 2>/dev/null; then - # Check if VM is running - if [ -n "$VM_PID" ] && kill -0 "$VM_PID" 2>/dev/null; then - status="${GREEN}RUNNING${NC}" - pid="$VM_PID" + vm_dirname=$(basename "$vm_dir") + # Extract number from vm directory name (vm1, vm2, etc.) + vm_number=${vm_dirname#vm} + if [[ "$vm_number" =~ ^[0-9]+$ ]]; then + vm_ip="192.168.100.$vm_number" + vm_info_file="$vm_dir/vm-info.txt" + + if [ -f "$vm_info_file" ]; then + # Safely source the file with error handling + if source "$vm_info_file" 2>/dev/null; then + # Check if VM is running + if [ -n "$VM_PID" ] && kill -0 "$VM_PID" 2>/dev/null; then + status="${GREEN}RUNNING${NC}" + pid="$VM_PID" + else + status="${RED}STOPPED${NC}" + pid="N/A" + fi + + # Handle missing or malformed STARTED field + if [ -z "$STARTED" ]; then + STARTED="Unknown" + fi + + # Use VM_NAME from config, fallback to vm number + display_name="${VM_NAME:-vm$vm_number}" + + printf "%-8s %-15s %-15s %-18s %-8s %-15s %-20s\n" "$vm_number" "$vm_ip" "$display_name" "$(printf "%b" "$status")" "$pid" "${MEMORY_MB}MB" "$STARTED" else - status="${RED}STOPPED${NC}" - pid="N/A" + printf "%-8s %-15s %-15s %-18s %-8s %-15s %-20s\n" "$vm_number" "$vm_ip" "vm$vm_number" "$(printf "%b" "${RED}ERROR${NC}")" "N/A" "N/A" "Config Error" fi - - # Handle missing or malformed STARTED field - if [ -z "$STARTED" ]; then - STARTED="Unknown" - fi - - printf "%-15s %-18s %-8s %-15s %-20s\n" "$vm_name" "$(printf "%b" "$status")" "$pid" "${MEMORY_MB}MB" "$STARTED" else - printf "%-15s %-18s %-8s %-15s %-20s\n" "$vm_name" "$(printf "%b" "${RED}ERROR${NC}")" "N/A" "N/A" "Config Error" + printf "%-8s %-15s %-15s %-18s %-8s %-15s %-20s\n" "$vm_number" "$vm_ip" "vm$vm_number" "$(printf "%b" "${YELLOW}UNKNOWN${NC}")" "N/A" "N/A" "N/A" fi - else - printf "%-15s %-18s %-8s %-15s %-20s\n" "$vm_name" "$(printf "%b" "${YELLOW}UNKNOWN${NC}")" "N/A" "N/A" "N/A" fi fi done @@ -102,23 +110,30 @@ list_vms() { } show_vm_status() { - local vm_name="$1" - local vm_dir="$VMS_SUBVOL/$vm_name" + local vm_number="$1" + local vm_dir="$VMS_SUBVOL/vm$vm_number" local vm_info_file="$vm_dir/vm-info.txt" + # Validate VM number + if ! [[ "$vm_number" =~ ^[0-9]+$ ]]; then + error "VM number must be a number" + fi + if [ ! -d "$vm_dir" ]; then - error "VM '$vm_name' not found" + error "VM number '$vm_number' not found" fi if [ ! -f "$vm_info_file" ]; then - error "VM info file not found for '$vm_name'" + error "VM info file not found for VM number '$vm_number'" fi source "$vm_info_file" - log "VM Status for: $vm_name" + log "VM Status for VM number: $vm_number" echo "" - echo "VM Name: $VM_NAME" + echo "VM Number: $vm_number" + echo "VM Name: ${VM_NAME:-vm$vm_number}" + echo "Static IP: 192.168.100.$vm_number" echo "Memory: ${MEMORY_MB}MB" echo "CPU Cores: $CPU_CORES" echo "Started: $STARTED" @@ -152,21 +167,26 @@ show_vm_status() { } connect_console() { - local vm_name="$1" - local vm_dir="$VMS_SUBVOL/$vm_name" + local vm_number="$1" + local vm_dir="$VMS_SUBVOL/vm$vm_number" local vm_info_file="$vm_dir/vm-info.txt" + # Validate VM number + if ! [[ "$vm_number" =~ ^[0-9]+$ ]]; then + error "VM number must be a number" + fi + if [ ! -f "$vm_info_file" ]; then - error "VM '$vm_name' not found" + error "VM number '$vm_number' not found" fi source "$vm_info_file" if [ -z "$VM_PID" ] || ! kill -0 "$VM_PID" 2>/dev/null; then - error "VM '$vm_name' is not running" + error "VM number '$vm_number' is not running" fi - info "Connecting to console for VM '$vm_name'" + info "Connecting to console for VM number '$vm_number'" info "Press Ctrl+A then X to exit console" echo "" @@ -186,39 +206,50 @@ connect_console() { } ssh_to_vm() { - local vm_name="$1" + local vm_number="$1" + local vm_ip="192.168.100.$vm_number" - warn "SSH connection requires:" - warn "1. VM to be fully booted" - warn "2. Network bridge configured with IP" - warn "3. VM to have received IP via DHCP" - echo "" - info "To set up networking:" - info "1. Configure bridge IP: ip addr add 192.168.100.1/24 dev br0" - info "2. Enable IP forwarding: echo 1 > /proc/sys/net/ipv4/ip_forward" - info "3. Set up NAT: iptables -t nat -A POSTROUTING -s 192.168.100.0/24 -j MASQUERADE" - echo "" + # Validate VM number + if ! [[ "$vm_number" =~ ^[0-9]+$ ]]; then + error "VM number must be a number" + fi + + info "Attempting SSH connection to VM number $vm_number at $vm_ip" info "Default login: ubuntu / ubuntu" - info "SSH command: ssh ubuntu@" + echo "" + + # Try to SSH directly + if command -v sshpass &>/dev/null; then + info "Using sshpass for automatic login..." + sshpass -p 'ubuntu' ssh -o StrictHostKeyChecking=no ubuntu@"$vm_ip" + else + info "Manual SSH connection (enter password 'ubuntu'):" + ssh -o StrictHostKeyChecking=no ubuntu@"$vm_ip" + fi } stop_vm() { - local vm_name="$1" - local vm_dir="$VMS_SUBVOL/$vm_name" + local vm_number="$1" + local vm_dir="$VMS_SUBVOL/vm$vm_number" local vm_info_file="$vm_dir/vm-info.txt" + # Validate VM number + if ! [[ "$vm_number" =~ ^[0-9]+$ ]]; then + error "VM number must be a number" + fi + if [ ! -f "$vm_info_file" ]; then - error "VM '$vm_name' not found" + error "VM number '$vm_number' not found" fi source "$vm_info_file" if [ -z "$VM_PID" ] || ! kill -0 "$VM_PID" 2>/dev/null; then - warn "VM '$vm_name' is not running" + warn "VM number '$vm_number' is not running" return fi - log "Stopping VM '$vm_name' (PID: $VM_PID)..." + log "Stopping VM number '$vm_number' (PID: $VM_PID)..." # Graceful shutdown first kill -TERM "$VM_PID" 2>/dev/null || true @@ -242,15 +273,20 @@ stop_vm() { rm -f "$VM_SOCKET" fi - log "VM '$vm_name' stopped" + log "VM number '$vm_number' stopped" } delete_vm() { - local vm_name="$1" - local vm_dir="$VMS_SUBVOL/$vm_name" + local vm_number="$1" + local vm_dir="$VMS_SUBVOL/vm$vm_number" + + # Validate VM number + if ! [[ "$vm_number" =~ ^[0-9]+$ ]]; then + error "VM number must be a number" + fi if [ ! -d "$vm_dir" ]; then - error "VM '$vm_name' not found" + error "VM number '$vm_number' not found" fi # Stop VM first if running @@ -258,45 +294,50 @@ delete_vm() { source "$vm_dir/vm-info.txt" if [ -n "$VM_PID" ] && kill -0 "$VM_PID" 2>/dev/null; then log "Stopping VM before deletion..." - stop_vm "$vm_name" + stop_vm "$vm_number" fi fi # Confirm deletion - echo -e "${RED}WARNING: This will permanently delete VM '$vm_name' and all its data!${NC}" + echo -e "${RED}WARNING: This will permanently delete VM number '$vm_number' and all its data!${NC}" read -p "Are you sure? (yes/no): " confirm if [ "$confirm" = "yes" ]; then - log "Deleting VM '$vm_name'..." + log "Deleting VM number '$vm_number'..." btrfs subvolume delete "$vm_dir" - log "VM '$vm_name' deleted successfully" + log "VM number '$vm_number' deleted successfully" else info "Deletion cancelled" fi } start_vm() { - local vm_name="$1" - local vm_dir="$VMS_SUBVOL/$vm_name" + local vm_number="$1" + local vm_dir="$VMS_SUBVOL/vm$vm_number" local vm_info_file="$vm_dir/vm-info.txt" + # Validate VM number + if ! [[ "$vm_number" =~ ^[0-9]+$ ]]; then + error "VM number must be a number" + fi + if [ ! -d "$vm_dir" ]; then - error "VM '$vm_name' not found" + error "VM number '$vm_number' not found" fi if [ ! -f "$vm_info_file" ]; then - error "VM info file not found for '$vm_name'" + error "VM info file not found for VM number '$vm_number'" fi source "$vm_info_file" # Check if VM is already running if [ -n "$VM_PID" ] && kill -0 "$VM_PID" 2>/dev/null; then - warn "VM '$vm_name' is already running (PID: $VM_PID)" + warn "VM number '$vm_number' is already running (PID: $VM_PID)" return fi - log "Starting VM '$vm_name'..." + log "Starting VM number '$vm_number'..." # Create TAP interface if ! ip link show "$TAP_NAME" &>/dev/null; then @@ -309,12 +350,17 @@ start_vm() { # Remove existing socket if it exists rm -f "$VM_SOCKET" - # Start Cloud Hypervisor in background + # Start Cloud Hypervisor in background with kernel boot + KERNEL_PATH="/var/lib/vms/base/vmlinuz" + INITRD_PATH="/var/lib/vms/base/initrd.img" + cloud-hypervisor \ --api-socket "$VM_SOCKET" \ --memory "size=${MEMORY_MB}M" \ --cpus "boot=$CPU_CORES" \ - --kernel "/var/lib/vms/base/hypervisor-fw" \ + --kernel "$KERNEL_PATH" \ + --initramfs "$INITRD_PATH" \ + --cmdline "root=LABEL=cloudimg-rootfs ro console=tty1 console=ttyS0" \ --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 \ @@ -326,10 +372,10 @@ start_vm() { # Update VM info file with new PID sed -i "s/VM_PID=.*/VM_PID=$NEW_VM_PID/" "$vm_info_file" - log "VM '$vm_name' started with PID $NEW_VM_PID" + log "VM number '$vm_number' started with PID $NEW_VM_PID" log "VM socket: $VM_SOCKET" log "TAP interface: $TAP_NAME" - log "To connect to console: ./ubuntu_vm_manage.sh console $vm_name" + log "To connect to console: ./ubuntu_vm_manage.sh console $vm_number" } # Main script logic @@ -339,50 +385,65 @@ if [ $# -eq 0 ]; then fi COMMAND="$1" -VM_NAME="$2" +VM_NUMBER="$2" case "$COMMAND" in "list") list_vms ;; "status") - if [ -z "$VM_NAME" ]; then - error "VM name required for status command" + if [ -z "$VM_NUMBER" ]; then + error "VM number required for status command" fi - show_vm_status "$VM_NAME" + show_vm_status "$VM_NUMBER" ;; "console") - if [ -z "$VM_NAME" ]; then - error "VM name required for console command" + if [ -z "$VM_NUMBER" ]; then + error "VM number required for console command" fi - connect_console "$VM_NAME" + connect_console "$VM_NUMBER" ;; "ssh") - if [ -z "$VM_NAME" ]; then - error "VM name required for ssh command" + if [ -z "$VM_NUMBER" ]; then + error "VM number required for ssh command" fi - ssh_to_vm "$VM_NAME" + ssh_to_vm "$VM_NUMBER" ;; "stop") - if [ -z "$VM_NAME" ]; then - error "VM name required for stop command" + if [ -z "$VM_NUMBER" ]; then + error "VM number required for stop command" fi - stop_vm "$VM_NAME" + stop_vm "$VM_NUMBER" ;; "start") - if [ -z "$VM_NAME" ]; then - error "VM name required for start command" + if [ -z "$VM_NUMBER" ]; then + error "VM number required for start command" fi - start_vm "$VM_NAME" + start_vm "$VM_NUMBER" ;; "delete") - if [ -z "$VM_NAME" ]; then - error "VM name required for delete command" + if [ -z "$VM_NUMBER" ]; then + error "VM number required for delete command" fi - delete_vm "$VM_NAME" + delete_vm "$VM_NUMBER" ;; - "ip"|"logs") - warn "Command '$COMMAND' not yet implemented" + "ip") + if [ -z "$VM_NUMBER" ]; then + error "VM number required for ip command" + fi + echo "VM $VM_NUMBER IP address: 192.168.100.$VM_NUMBER" + ;; + "logs") + if [ -z "$VM_NUMBER" ]; then + error "VM number required for logs command" + fi + log_file="/tmp/cloud-hypervisor-vm$VM_NUMBER.log" + if [ -f "$log_file" ]; then + log "Showing logs for VM number $VM_NUMBER:" + tail -f "$log_file" + else + warn "Log file not found: $log_file" + fi ;; *) error "Unknown command: $COMMAND" diff --git a/tools/ubuntu_vm_start.sh b/tools/ubuntu_vm_start.sh index 6098cb4..ea9f820 100755 --- a/tools/ubuntu_vm_start.sh +++ b/tools/ubuntu_vm_start.sh @@ -383,10 +383,36 @@ if [ ! -f "$FIRMWARE_PATH" ]; then chmod +x "$FIRMWARE_PATH" test_step "Firmware executable check" "[ -x '$FIRMWARE_PATH' ]" log "✓ Firmware downloaded to $FIRMWARE_PATH" -else - log "✓ Firmware already exists at $FIRMWARE_PATH" - test_file_exists "$FIRMWARE_PATH" "Firmware verification" -fi + else + log "✓ Firmware already exists at $FIRMWARE_PATH" + test_file_exists "$FIRMWARE_PATH" "Firmware verification" + fi + + # Extract kernel and initrd from base image + log "Extracting kernel and initrd for kernel boot..." + EXTRACT_SCRIPT="$(dirname "$0")/extract_kernel.sh" + if [ -f "$EXTRACT_SCRIPT" ]; then + "$EXTRACT_SCRIPT" + else + warn "Kernel extraction script not found, attempting manual extraction..." + # Fallback manual extraction + KERNEL_PATH="$BASE_SUBVOL/vmlinuz" + INITRD_PATH="$BASE_SUBVOL/initrd.img" + if [ ! -f "$KERNEL_PATH" ] || [ ! -f "$INITRD_PATH" ]; then + log "Extracting kernel and initrd manually..." + TEMP_MOUNT=$(mktemp -d) + losetup -P /dev/loop1 "$BASE_IMAGE_PATH" + mount /dev/loop1p16 "$TEMP_MOUNT" + cp "$TEMP_MOUNT/vmlinuz-6.8.0-60-generic" "$KERNEL_PATH" 2>/dev/null || true + cp "$TEMP_MOUNT/initrd.img-6.8.0-60-generic" "$INITRD_PATH" 2>/dev/null || true + umount "$TEMP_MOUNT" + losetup -d /dev/loop1 + rmdir "$TEMP_MOUNT" + fi + fi + + test_file_exists "$BASE_SUBVOL/vmlinuz" "Kernel extraction" + test_file_exists "$BASE_SUBVOL/initrd.img" "Initrd extraction" # Create VM subvolume by cloning from base log "Setting up VM-specific storage..." @@ -625,14 +651,19 @@ log "Generated MAC address for VM: $VM_MAC" log "Launching Cloud Hypervisor..." # Try to start Cloud Hypervisor and capture any error output -log "Starting Cloud Hypervisor with command:" -log "cloud-hypervisor --api-socket $VM_SOCKET --memory size=${MEMORY_MB}M --cpus boot=$CPU_CORES --firmware $FIRMWARE_PATH --disk path=$VM_IMAGE_PATH path=$CLOUD_INIT_PATH,readonly=on --net tap=$TAP_NAME,mac=$VM_MAC --serial file=$VM_LOG_FILE --console off --event-monitor path=${VM_LOG_FILE}.events" +log "Starting Cloud Hypervisor with kernel boot:" +log "cloud-hypervisor --memory size=${MEMORY_MB}M --cpus boot=$CPU_CORES --kernel $KERNEL_PATH --initramfs $INITRD_PATH --cmdline 'root=LABEL=cloudimg-rootfs ro console=tty1 console=ttyS0' --disk path=$VM_IMAGE_PATH path=$CLOUD_INIT_PATH,readonly=on --net tap=$TAP_NAME,mac=$VM_MAC --serial file=$VM_LOG_FILE --console off --event-monitor path=${VM_LOG_FILE}.events" + +# Use kernel boot instead of firmware boot to properly pass root device +KERNEL_PATH="$BASE_SUBVOL/vmlinuz" +INITRD_PATH="$BASE_SUBVOL/initrd.img" -# Try with API socket first, fall back without it if needed cloud-hypervisor \ --memory "size=${MEMORY_MB}M" \ --cpus "boot=$CPU_CORES" \ - --firmware "$FIRMWARE_PATH" \ + --kernel "$KERNEL_PATH" \ + --initramfs "$INITRD_PATH" \ + --cmdline "root=LABEL=cloudimg-rootfs ro console=tty1 console=ttyS0" \ --disk "path=$VM_IMAGE_PATH" "path=$CLOUD_INIT_PATH,readonly=on" \ --net "tap=$TAP_NAME,mac=$VM_MAC" \ --serial "file=$VM_LOG_FILE" \