This commit is contained in:
kristof 2025-06-15 19:31:47 +02:00
parent 63f4f77366
commit d336f36929
2 changed files with 225 additions and 78 deletions

View File

@ -1,7 +1,7 @@
#!/bin/bash
# Ubuntu VM Delete Script with Comprehensive Cleanup
# Usage: ubuntu_vm_delete.sh <vm_name|all>
# Usage: ubuntu_vm_delete.sh <vm_number|all>
# Use 'all' to delete all VMs
set -e
@ -39,15 +39,18 @@ info() {
show_usage() {
echo "Ubuntu VM Delete Script"
echo ""
echo "Usage: $0 <vm_name|all>"
echo "Usage: $0 <vm_number|all|list>"
echo ""
echo "Arguments:"
echo " vm_name - Name of the VM to delete"
echo " vm_number - Number of the VM to delete (1-200)"
echo " all - Delete ALL VMs (use with extreme caution)"
echo " list - List all existing VMs"
echo ""
echo "Examples:"
echo " $0 test-vm # Delete specific VM"
echo " $0 1 # Delete VM number 1"
echo " $0 42 # Delete VM number 42"
echo " $0 all # Delete all VMs"
echo " $0 list # List all VMs"
echo ""
echo "This script will:"
echo " - Stop running VM processes"
@ -70,9 +73,15 @@ fi
VM_TARGET="$1"
# Validate VM name (unless it's 'all')
if [ "$VM_TARGET" != "all" ] && [[ "$VM_TARGET" =~ [^a-zA-Z0-9_-] ]]; then
error "VM name can only contain alphanumeric characters, hyphens, and underscores"
# Validate VM number (unless it's 'all' or 'list')
if [ "$VM_TARGET" != "all" ] && [ "$VM_TARGET" != "list" ]; then
if ! [[ "$VM_TARGET" =~ ^[0-9]+$ ]]; then
error "VM number must be a number"
fi
if [ "$VM_TARGET" -lt 1 ] || [ "$VM_TARGET" -gt 200 ]; then
error "VM number must be between 1 and 200"
fi
fi
# Check if VMs directory exists
@ -159,7 +168,7 @@ cleanup_network() {
# Function to clean up VM files and sockets
cleanup_vm_files() {
local vm_socket="$1"
local vm_name="$2"
local vm_number="$2"
# Remove VM socket
if [ -n "$vm_socket" ] && [ -e "$vm_socket" ]; then
@ -168,28 +177,28 @@ cleanup_vm_files() {
fi
# Remove log files
local log_file="/tmp/cloud-hypervisor-$vm_name.log"
local log_file="/tmp/cloud-hypervisor-vm$vm_number.log"
if [ -f "$log_file" ]; then
log "Removing VM log file '$log_file'"
rm -f "$log_file" || warn "Failed to remove log file '$log_file'"
fi
# Remove any other temporary files
rm -f "/tmp/cloud-hypervisor-$vm_name"* 2>/dev/null || true
rm -f "/tmp/cloud-hypervisor-vm$vm_number"* 2>/dev/null || true
}
# Function to delete a single VM
delete_single_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"
if [ ! -d "$vm_dir" ]; then
warn "VM '$vm_name' not found at '$vm_dir'"
warn "VM number '$vm_number' not found at '$vm_dir'"
return 1
fi
log "Deleting VM: $vm_name"
log "Deleting VM number: $vm_number"
# Initialize variables with defaults
local VM_PID=""
@ -210,33 +219,33 @@ delete_single_vm() {
else
warn "VM info file not found at '$vm_info_file', proceeding with best-effort cleanup"
# Try to guess some values
TAP_NAME="tap-$vm_name"
TAP_NAME="tap-vm$vm_number"
BRIDGE_NAME="br0"
VM_SOCKET="/tmp/cloud-hypervisor-$vm_name.sock"
VM_SOCKET="/tmp/cloud-hypervisor-vm$vm_number.sock"
fi
# Stop VM process
if [ -n "$VM_PID" ]; then
stop_vm_process "$VM_PID" "$vm_name"
stop_vm_process "$VM_PID" "vm$vm_number"
else
# Try to find the process by name
local found_pids=$(pgrep -f "cloud-hypervisor.*$vm_name" 2>/dev/null || echo "")
local found_pids=$(pgrep -f "cloud-hypervisor.*vm$vm_number" 2>/dev/null || echo "")
if [ -n "$found_pids" ]; then
warn "Found VM process(es) by name: $found_pids"
# Process each PID separately
echo "$found_pids" | while read -r pid; do
if [ -n "$pid" ]; then
stop_vm_process "$pid" "$vm_name"
stop_vm_process "$pid" "vm$vm_number"
fi
done
fi
fi
# Clean up network interfaces
cleanup_network "$TAP_NAME" "$BRIDGE_NAME" "$vm_name"
cleanup_network "$TAP_NAME" "$BRIDGE_NAME" "vm$vm_number"
# Clean up VM files and sockets
cleanup_vm_files "$VM_SOCKET" "$vm_name"
cleanup_vm_files "$VM_SOCKET" "$vm_number"
# Verify the directory is a btrfs subvolume before attempting deletion
if btrfs subvolume show "$vm_dir" &>/dev/null; then
@ -253,7 +262,7 @@ delete_single_vm() {
log "Directory '$vm_dir' removed successfully"
fi
log "VM '$vm_name' deleted successfully"
log "VM number '$vm_number' deleted successfully"
return 0
}
@ -265,33 +274,93 @@ list_all_vms() {
return 0
fi
for vm_dir in "$VMS_SUBVOL"/*; do
for vm_dir in "$VMS_SUBVOL"/vm*; do
if [ -d "$vm_dir" ]; then
local vm_name=$(basename "$vm_dir")
vm_list+=("$vm_name")
# Extract number from vm directory name (vm1, vm2, etc.)
local vm_number=${vm_name#vm}
if [[ "$vm_number" =~ ^[0-9]+$ ]]; then
vm_list+=("$vm_number")
fi
fi
done
printf '%s\n' "${vm_list[@]}"
# Sort numerically
if [ ${#vm_list[@]} -gt 0 ]; then
printf '%s\n' "${vm_list[@]}" | sort -n
fi
}
# Main deletion logic
if [ "$VM_TARGET" = "all" ]; then
# Function to show detailed VM list
show_vm_list() {
local vm_numbers=($(list_all_vms))
if [ ${#vm_numbers[@]} -eq 0 ]; then
info "No VMs found"
return 0
fi
echo ""
echo "Existing VMs:"
echo "============="
for vm_number in "${vm_numbers[@]}"; do
local vm_dir="$VMS_SUBVOL/vm$vm_number"
local vm_info_file="$vm_dir/vm-info.txt"
local vm_ip="192.168.100.$vm_number"
echo -n "VM $vm_number: "
# Check if VM is running
if [ -f "$vm_info_file" ]; then
local VM_PID=""
local VM_NAME=""
local STARTED=""
source "$vm_info_file" 2>/dev/null || true
if [ -n "$VM_PID" ] && kill -0 "$VM_PID" 2>/dev/null; then
echo -e "${GREEN}RUNNING${NC} (PID: $VM_PID, IP: $vm_ip)"
if [ -n "$VM_NAME" ]; then
echo " Name: $VM_NAME"
fi
if [ -n "$STARTED" ]; then
echo " Started: $STARTED"
fi
else
echo -e "${YELLOW}STOPPED${NC} (IP: $vm_ip)"
if [ -n "$VM_NAME" ]; then
echo " Name: $VM_NAME"
fi
fi
else
echo -e "${RED}UNKNOWN${NC} (no info file)"
fi
done
echo ""
}
# Main logic
if [ "$VM_TARGET" = "list" ]; then
# List all VMs
show_vm_list
exit 0
elif [ "$VM_TARGET" = "all" ]; then
# Delete all VMs
warn "You are about to delete ALL VMs!"
echo ""
# List all VMs
vm_list=($(list_all_vms))
vm_numbers=($(list_all_vms))
if [ ${#vm_list[@]} -eq 0 ]; then
if [ ${#vm_numbers[@]} -eq 0 ]; then
info "No VMs found to delete"
exit 0
fi
echo "VMs to be deleted:"
for vm in "${vm_list[@]}"; do
echo " - $vm"
echo "VM numbers to be deleted:"
for vm_number in "${vm_numbers[@]}"; do
echo " - VM $vm_number (IP: 192.168.100.$vm_number)"
done
echo ""
@ -301,8 +370,8 @@ if [ "$VM_TARGET" = "all" ]; then
success_count=0
failure_count=0
for vm_name in "${vm_list[@]}"; do
if delete_single_vm "$vm_name"; then
for vm_number in "${vm_numbers[@]}"; do
if delete_single_vm "$vm_number"; then
success_count=$((success_count + 1))
else
failure_count=$((failure_count + 1))
@ -338,14 +407,14 @@ if [ "$VM_TARGET" = "all" ]; then
else
# Delete single VM
vm_name="$VM_TARGET"
vm_number="$VM_TARGET"
if [ ! -d "$VMS_SUBVOL/$vm_name" ]; then
error "VM '$vm_name' not found"
if [ ! -d "$VMS_SUBVOL/vm$vm_number" ]; then
error "VM number '$vm_number' not found"
fi
log "Deleting VM '$vm_name' without confirmation..."
delete_single_vm "$vm_name"
log "Deleting VM number '$vm_number' without confirmation..."
delete_single_vm "$vm_number"
fi
log "VM deletion script completed successfully"

View File

@ -1,7 +1,7 @@
#!/bin/bash
# Ubuntu VM Start Script with Cloud Hypervisor and Btrfs Thin Provisioning
# Usage: ubuntu_vm_start.sh $name $mem $cores
# Usage: ubuntu_vm_start.sh $vm_number $name $mem $cores
set -e
@ -37,9 +37,9 @@ warn() {
error() {
echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}"
# If VM_NAME is set and we're in VM creation phase, clean up
if [ -n "$VM_NAME" ] && [ -n "$VM_PID" ]; then
cleanup_failed_vm "$VM_NAME"
# If VM_NUMBER is set and we're in VM creation phase, clean up
if [ -n "$VM_NUMBER" ] && [ -n "$VM_PID" ]; then
cleanup_failed_vm "$VM_NUMBER"
fi
exit 1
@ -96,14 +96,14 @@ test_process_running() {
# Cleanup function for failed VM creation
cleanup_failed_vm() {
local vm_name="$1"
local vm_number="$1"
warn "VM creation failed, cleaning up..."
# Call the delete script to clean up
local delete_script="$(dirname "$0")/ubuntu_vm_delete.sh"
if [ -f "$delete_script" ]; then
log "Running cleanup script: $delete_script"
"$delete_script" "$vm_name" || warn "Cleanup script failed, manual cleanup may be required"
"$delete_script" "$vm_number" || warn "Cleanup script failed, manual cleanup may be required"
else
warn "Delete script not found at $delete_script, manual cleanup required"
fi
@ -115,32 +115,77 @@ generate_password_hash() {
python3 -c "import crypt; print(crypt.crypt('ubuntu', crypt.mksalt(crypt.METHOD_SHA512)))"
}
# Wait for VM to boot and get IP
# Wait for VM to boot and verify static IP
wait_for_vm_boot() {
local vm_name="$1"
local max_wait=120 # 2 minutes
local expected_ip="$2"
local max_wait=120 # 3 minutes
local count=0
log "Waiting for VM '$vm_name' to boot and get IP address..."
log "Waiting for VM '$vm_name' to boot with static IP $expected_ip..."
while [ $count -lt $max_wait ]; do
# Check if VM got an IP from DHCP
local vm_ip=$(arp -a | grep "192.168.100" | grep -v "192.168.100.1" | head -1 | sed 's/.*(\([^)]*\)).*/\1/')
# Check if VM process is still running
if ! kill -0 "$VM_PID" 2>/dev/null; then
error "VM process died while waiting for boot. Check log: $VM_LOG_FILE"
fi
if [ -n "$vm_ip" ] && [ "$vm_ip" != "192.168.100.1" ]; then
log "VM got IP address: $vm_ip"
echo "$vm_ip"
# Try to ping the expected static IP
if ping -c 1 -W 2 "$expected_ip" >/dev/null 2>&1; then
log "VM is responding at static IP address: $expected_ip"
echo "$expected_ip"
return 0
fi
sleep 2
count=$((count + 2))
if [ $((count % 10)) -eq 0 ]; then
# Also check ARP table for our MAC address
local vm_ip=$(arp -a | grep "$VM_MAC" | sed 's/.*(\([^)]*\)).*/\1/' | head -1)
if [ -n "$vm_ip" ] && [ "$vm_ip" = "$expected_ip" ]; then
log "VM MAC address found in ARP table with expected IP: $expected_ip"
echo "$expected_ip"
return 0
fi
sleep 3
count=$((count + 3))
if [ $((count % 15)) -eq 0 ]; then
info "Still waiting for VM to boot... ($count/$max_wait seconds)"
info "VM process PID $VM_PID is still running"
info "Expected static IP: $expected_ip"
# Show recent log entries
if [ -f "$VM_LOG_FILE" ]; then
info "Recent VM log entries:"
tail -3 "$VM_LOG_FILE" 2>/dev/null || true
fi
fi
done
warn "VM did not get an IP address within $max_wait seconds"
warn "VM did not respond at expected IP $expected_ip within $max_wait seconds"
warn "VM may still be booting - check manually with: ping $expected_ip"
return 1
}
# Test IP connectivity
test_ip_connectivity() {
local vm_ip="$1"
local max_attempts=10
local attempt=1
log "Testing IP connectivity to $vm_ip..."
while [ $attempt -le $max_attempts ]; do
info "Ping attempt $attempt/$max_attempts to $vm_ip"
# Test ping connectivity with timeout
if ping -c 3 -W 2 "$vm_ip" >/dev/null 2>&1; then
log "✓ IP connectivity successful to $vm_ip"
return 0
fi
sleep 3
attempt=$((attempt + 1))
done
error "✗ IP connectivity failed after $max_attempts attempts to $vm_ip"
return 1
}
@ -175,15 +220,24 @@ if [ "$EUID" -ne 0 ]; then
fi
# Parse arguments
if [ $# -ne 3 ]; then
error "Usage: $0 <vm_name> <memory_mb> <cpu_cores>"
if [ $# -ne 4 ]; then
error "Usage: $0 <vm_number> <vm_name> <memory_mb> <cpu_cores>"
fi
VM_NAME="$1"
MEMORY_MB="$2"
CPU_CORES="$3"
VM_NUMBER="$1"
VM_NAME="$2"
MEMORY_MB="$3"
CPU_CORES="$4"
# Validate arguments
if ! [[ "$VM_NUMBER" =~ ^[0-9]+$ ]]; then
error "VM number must be a number"
fi
if [ "$VM_NUMBER" -lt 1 ] || [ "$VM_NUMBER" -gt 200 ]; then
error "VM number must be between 1 and 200"
fi
if ! [[ "$MEMORY_MB" =~ ^[0-9]+$ ]]; then
error "Memory must be a number (in MB)"
fi
@ -196,7 +250,11 @@ 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"
# Calculate static IP address based on VM number
VM_STATIC_IP="192.168.100.$VM_NUMBER"
log "Starting VM: $VM_NAME (number $VM_NUMBER) with ${MEMORY_MB}MB RAM and $CPU_CORES CPU cores"
log "VM will be assigned static IP: $VM_STATIC_IP"
# Comprehensive prerequisite checks
log "Performing prerequisite checks..."
@ -278,8 +336,8 @@ test_step "VMs subvolume verification" "btrfs subvolume show '$VMS_SUBVOL' &>/de
# 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"
VM_SUBVOL_PATH="$VMS_SUBVOL/vm$VM_NUMBER"
VM_IMAGE_PATH="$VM_SUBVOL_PATH/vm$VM_NUMBER.raw"
CLOUD_INIT_PATH="$VM_SUBVOL_PATH/cloud-init.img"
# Download and prepare base image if it doesn't exist
@ -388,13 +446,19 @@ chpasswd:
# SSH configuration
ssh_authorized_keys: []
# Network configuration to ensure DHCP works
# Network configuration with static IP
network:
version: 2
ethernets:
eth0:
dhcp4: true
dhcp-identifier: mac
dhcp4: false
addresses:
- $VM_STATIC_IP/24
gateway4: 192.168.100.1
nameservers:
addresses:
- 8.8.8.8
- 8.8.4.4
# Package updates and installs
package_update: true
@ -492,6 +556,7 @@ else
fi
# Create TAP interface for the VM
TAP_NAME="tap-vm$VM_NUMBER"
log "Creating TAP interface $TAP_NAME..."
# Remove existing TAP interface if it exists
@ -546,8 +611,8 @@ fi
# Start the VM with Cloud Hypervisor
log "Starting VM $VM_NAME..."
VM_SOCKET="/tmp/cloud-hypervisor-$VM_NAME.sock"
VM_LOG_FILE="/tmp/cloud-hypervisor-$VM_NAME.log"
VM_SOCKET="/tmp/cloud-hypervisor-vm$VM_NUMBER.sock"
VM_LOG_FILE="/tmp/cloud-hypervisor-vm$VM_NUMBER.log"
# Remove existing socket and log file if they exist
rm -f "$VM_SOCKET" "$VM_LOG_FILE"
@ -617,6 +682,7 @@ log "✓ VM initialization completed"
log "Saving VM configuration..."
VM_INFO_FILE="$VM_SUBVOL_PATH/vm-info.txt"
cat > "$VM_INFO_FILE" << EOF
VM_NUMBER=$VM_NUMBER
VM_NAME=$VM_NAME
VM_PID=$VM_PID
VM_SOCKET=$VM_SOCKET
@ -628,6 +694,7 @@ VM_IMAGE_PATH=$VM_IMAGE_PATH
CLOUD_INIT_PATH=$CLOUD_INIT_PATH
VM_MAC=$VM_MAC
VM_LOG_FILE=$VM_LOG_FILE
VM_STATIC_IP=$VM_STATIC_IP
STARTED="$(date '+%Y-%m-%d %H:%M:%S')"
EOF
@ -645,16 +712,27 @@ cleanup_on_exit() {
rm -f "$VM_SOCKET"
}
# Test VM boot and SSH connectivity
log "Testing VM boot and SSH connectivity..."
# Test VM boot and connectivity
log "Testing VM boot and connectivity..."
# Wait for VM to boot and get IP
VM_IP=$(wait_for_vm_boot "$VM_NAME")
# Show the static IP that will be used
log "VM $VM_NAME will use static IP address: $VM_STATIC_IP"
# Wait for VM to boot and verify static IP
VM_IP=$(wait_for_vm_boot "$VM_NAME" "$VM_STATIC_IP")
if [ $? -ne 0 ] || [ -z "$VM_IP" ]; then
error "VM failed to boot or get IP address. Check log: $VM_LOG_FILE"
error "VM failed to boot or respond at static IP $VM_STATIC_IP. Check log: $VM_LOG_FILE"
fi
log "✓ VM booted successfully and got IP: $VM_IP"
log "✓ VM booted successfully and is using IP: $VM_IP"
# Test IP connectivity first
log "Testing IP connectivity before SSH..."
if test_ip_connectivity "$VM_IP"; then
log "✓ IP connectivity test passed for $VM_IP"
else
error "IP connectivity test failed for $VM_IP"
fi
# Test SSH connectivity
if test_ssh_connection "$VM_IP"; then
@ -667,12 +745,12 @@ if test_ssh_connection "$VM_IP"; then
info "VM $VM_NAME is ready for use!"
info "Connect via SSH: ssh ubuntu@$VM_IP"
info "Default password: ubuntu (please change after first login)"
info "To stop the VM: sudo $(dirname "$0")/ubuntu_vm_delete.sh $VM_NAME"
info "To stop the VM: sudo $(dirname "$0")/ubuntu_vm_delete.sh $VM_NUMBER"
echo ""
# Don't set trap for successful VMs - let them run
log "VM $VM_NAME will continue running in the background."
log "Use 'sudo $(dirname "$0")/ubuntu_vm_delete.sh $VM_NAME' to stop and delete it."
log "Use 'sudo $(dirname "$0")/ubuntu_vm_delete.sh $VM_NUMBER' to stop and delete it."
else
error "SSH connectivity test failed. VM will be deleted for retry."