diff options
Diffstat (limited to 'common/scripts/virt')
| -rwxr-xr-x | common/scripts/virt/checksum.sh | 36 | ||||
| -rwxr-xr-x | common/scripts/virt/dos.sh | 998 | ||||
| -rwxr-xr-x | common/scripts/virt/server.sh | 128 | ||||
| -rwxr-xr-x | common/scripts/virt/ubuntu | 222 | ||||
| -rwxr-xr-x | common/scripts/virt/windows.sh | 1156 |
5 files changed, 2540 insertions, 0 deletions
diff --git a/common/scripts/virt/checksum.sh b/common/scripts/virt/checksum.sh new file mode 100755 index 0000000..adb6a24 --- /dev/null +++ b/common/scripts/virt/checksum.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +# Check if the correct number of arguments are provided +if [[ $# -ne 2 ]]; then + echo "Usage: checksum <file> <website>" + exit 1 +fi + +FILE=$1 +CHECKSUM_PAGE=$2 + +# Fetch the HTML content of the page +HTML_CONTENT=$(curl -s "$CHECKSUM_PAGE") + +# Try searching for checksums with a more targeted approach, like matching a checksum pattern or look for files with checksum labels +CHECKSUM=$(echo "$HTML_CONTENT" | grep -oP '([a-f0-9]{64})' | head -n 1) # Try specifically looking for SHA256 checksum patterns + +# Check if any checksum was found +if [[ -z "$CHECKSUM" ]]; then + echo "Checksum not found on the page." + exit 1 +fi + +# Calculate the checksum of the file locally +LOCAL_CHECKSUM=$(sha256sum "$FILE" | awk '{print $1}') + +echo "Local checksum: $LOCAL_CHECKSUM" +echo "Remote checksum: $CHECKSUM" + +# Compare the local checksum with the one from the website +if [[ "$LOCAL_CHECKSUM" == "$CHECKSUM" ]]; then + echo "The checksums match! The file is verified." + +else + echo "The checksums do not match. The file may be corrupted or tampered with." +fi diff --git a/common/scripts/virt/dos.sh b/common/scripts/virt/dos.sh new file mode 100755 index 0000000..0b50c4b --- /dev/null +++ b/common/scripts/virt/dos.sh @@ -0,0 +1,998 @@ +#!/usr/bin/env bash + +# Set variables +HOST_DIR="virt" +VM_NAME="dos" +VM_SIZE="80G" # Disk size in GB +VM_RAM="8G" # RAM size +VM_CPU="6" # Number of virtual CPUs +CORES=$((VM_CPU / 2)) +THREADS_PER_CORE=2 +SOCKETS=1 + +VM_DIR="$HOST_DIR/machines" +IMAGE_DIR="$HOST_DIR/images" +WIN_ISO_DIR="${IMAGE_DIR}/${VM_NAME}" # Directory for Windows ISO +VM_DIR="$WIN_ISO_DIR" +SOCKET_DIR="$VM_DIR" +SHARED_DIR="${HOST_DIR}/shared" +FIRMWARE_DIR="${HOST_DIR}/firmware" +TPM_DIR="$WIN_ISO_DIR" +TPM_SOCKET="${WIN_ISO_DIR}/${VM_NAME}.swtpm-sock" +GUEST_PORT=22 +QCOW2_FILE="${VM_DIR}/${VM_NAME}.qcow2" +RAW_FILE="${VM_DIR}/${VM_NAME}.raw" + +# Anti-detection: Generate realistic hardware identifiers +REAL_MAC="00:1A:2B:3C:4D:5E" # Example Dell MAC - replace with your choice +REAL_SERIAL="$(openssl rand -hex 8 | tr '[:lower:]' '[:upper:]')" +REAL_UUID="$(uuidgen)" +REAL_VENDOR="Dell Inc." +REAL_PRODUCT="OptiPlex 7090" +REAL_VERSION="01" +REAL_FAMILY="OptiPlex" + +# Try to find an available host port starting from 22220 +HOST_PORT_START=22220 +HOST_PORT_END=22300 + +for ((port = HOST_PORT_START; port <= HOST_PORT_END; port++)); do + if ! ss -tuln | grep -q ":$port\b"; then + HOST_PORT=$port + echo "Using available port: $HOST_PORT" + break + fi +done + +if [[ $port -gt $HOST_PORT_END ]]; then + echo "Error: No available ports found between $HOST_PORT_START and $HOST_PORT_END" >&2 + exit 1 +fi + +# Set SMP configuration +SMP_CONFIG="cores=$CORES,threads=$THREADS_PER_CORE,sockets=$SOCKETS" + +# Create necessary directories +mkdir -p "${HOME}/${HOST_DIR}" +mkdir -p "$IMAGE_DIR" "$SHARED_DIR" "$FIRMWARE_DIR" +mkdir -p "$WIN_ISO_DIR" "$VM_DIR" +mkdir -p "${WIN_ISO_DIR}/unattended" + +# Define ISO paths and URLs +ISO_VIRTIO="${WIN_ISO_DIR}/virtio-win.iso" +ISO_UNATTENDED="${WIN_ISO_DIR}/unattended.iso" + +# Find Windows ISO with flexible pattern matching +find_windows_iso() { + # Check if directory exists + if [[ ! -d "$WIN_ISO_DIR" ]]; then + mkdir -p "$WIN_ISO_DIR" + fi + + # Try to find any Windows ISO using case-insensitive patterns + local found_iso + found_iso=$(find "$WIN_ISO_DIR" -maxdepth 1 -type f \( \ + -iname "*win11*.iso" -o \ + -iname "*win*11*.iso" -o \ + -iname "Win*.iso" -o \ + -iname "Win11*.iso" -o \ + -iname "Win*11*.iso" -o \ + -iname "*windows*11*.iso" -o \ + -iname "*windows11*.iso" \ + \) -exec stat --format="%Y %n" {} \; | sort -n | tail -n 1 | cut -d' ' -f2-) + + if [[ -n "$found_iso" && -f "$found_iso" ]]; then + echo "$found_iso" + return 0 + fi + + return 1 +} + +# Define download URLs +VIRTIO_ISO_URL="https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso" + +# Print colored messages +print_info() { echo -e "\033[1;34m[INFO]\033[0m $1" >&2; } +print_success() { echo -e "\033[1;32m[SUCCESS]\033[0m $1" >&2; } +print_warning() { echo -e "\033[1;33m[WARNING]\033[0m $1" >&2; } +print_error() { echo -e "\033[1;31m[ERROR]\033[0m $1" >&2; } + +# Helper: verify file integrity +verify_file() { + local file="$1" + local expected_sha256="$2" + + if [[ ! -f "$file" ]]; then + return 1 + fi + + if [[ -n "$expected_sha256" ]]; then + local actual_sha256 + actual_sha256=$(sha256sum "$file" | cut -d' ' -f1) + if [[ "$actual_sha256" != "$expected_sha256" ]]; then + print_error "File integrity check failed for $file" + return 1 + fi + fi + + return 0 +} + +# Helper: download file with verification +download_file() { + local url="$1" + local dest="$2" + local expected_sha256="$3" + local allow_failure="$4" + + # Check if file exists and is valid + if [[ -f "$dest" ]]; then + if verify_file "$dest" "$expected_sha256"; then + print_info "File $dest already exists and verified." + return 0 + else + print_warning "File $dest exists but failed verification. Redownloading..." + rm -f "$dest" + fi + fi + + print_info "Downloading $url..." + if ! curl -fL --progress-bar -o "$dest" "$url"; then + print_error "Failed to download $url." + if [[ "$allow_failure" != "true" ]]; then + return 1 + fi + else + # Verify downloaded file + if ! verify_file "$dest" "$expected_sha256"; then + print_error "Downloaded file failed verification" + rm -f "$dest" + return 1 + fi + print_success "Successfully downloaded and verified $dest" + fi + + return 0 +} + +# Handle curl errors +handle_curl_error() { + local exit_code=$1 + case $exit_code in + 6) print_error "Couldn't resolve host" ;; + 7) print_error "Failed to connect to host" ;; + 22) print_error "HTTP page not retrieved (404, etc.)" ;; + 28) print_error "Operation timeout" ;; + *) print_error "Curl failed with exit code $exit_code" ;; + esac +} + +# Download Windows 11 ISO using Microsoft's API +download_windows_iso() { + local windows_version="11" # Default to Windows 11 + local language="English (United States)" # Default language + + # Parse arguments if provided + if [[ -n "$1" ]]; then + windows_version="$1" + fi + + print_info "Attempting to download Windows $windows_version ISO from Microsoft..." + + # Set required variables + local user_agent="Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0" + local session_id="$(uuidgen)" + local profile="606624d44113" + local url="https://www.microsoft.com/en-us/software-download/windows$windows_version" + + # Add ISO to URL for Windows 10 + case "$windows_version" in + 10) url="${url}ISO" ;; + esac + + # Step 1: Get download page HTML + print_info "Fetching download page: $url" + local iso_download_page_html + iso_download_page_html="$(curl --disable --silent --user-agent "$user_agent" --header "Accept:" --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url")" || { + handle_curl_error $? + print_error "Failed to fetch the download page. Please download Windows $windows_version ISO manually from $url" + return 1 + } + + # Step 2: Extract Product Edition ID + print_info "Getting Product Edition ID..." + local product_edition_id + product_edition_id="$(echo "$iso_download_page_html" | grep -Eo '<option value="[0-9]+">Windows' | cut -d '"' -f 2 | head -n 1 | tr -cd '0-9' | head -c 16)" + + if [[ -z "$product_edition_id" ]]; then + print_error "Failed to extract product edition ID." + print_error "Please download Windows $windows_version ISO manually from $url" + return 1 + fi + + print_success "Product Edition ID: $product_edition_id" + + # Step 3: Register session ID + print_info "Registering session ID: $session_id" + curl --disable --silent --output /dev/null --user-agent "$user_agent" \ + --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 \ + --http1.1 -- "https://vlscppe.microsoft.com/tags?org_id=y6jn8c31&session_id=$session_id" || { + print_error "Failed to register session ID." + return 1 + } + + # Step 4: Get language SKU ID + print_info "Getting language SKU ID..." + local language_skuid_table_json + language_skuid_table_json="$(curl --disable -s --fail --max-filesize 100K --proto =https --tlsv1.2 --http1.1 \ + "https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition?profile=${profile}&ProductEditionId=${product_edition_id}&SKU=undefined&friendlyFileName=undefined&Locale=en-US&sessionID=${session_id}")" || { + handle_curl_error $? + print_error "Failed to get language SKU information." + return 1 + } + + # Extract SKU ID for selected language + local sku_id + + # Try with jq if available (more reliable) + if command -v jq >/dev/null 2>&1; then + sku_id="$(echo "$language_skuid_table_json" | jq -r '.Skus[] | select(.LocalizedLanguage=="'"$language"'" or .Language=="'"$language"'").Id')" + else + # Fallback to grep/cut if jq not available + sku_id="$(echo "$language_skuid_table_json" | grep -o '"Id":"[^"]*","Language":"'"$language"'"' | cut -d'"' -f4)" + + if [[ -z "$sku_id" ]]; then + # Try alternative extraction method + sku_id="$(echo "$language_skuid_table_json" | grep -o '"LocalizedLanguage":"'"$language"'","Id":"[^"]*"' | cut -d'"' -f6)" + fi + fi + + if [[ -z "$sku_id" ]]; then + print_error "Failed to extract SKU ID for $language." + return 1 + fi + + print_success "SKU ID: $sku_id" + + # Step 5: Get ISO download link + print_info "Getting ISO download link..." + local iso_download_link_json + iso_download_link_json="$(curl --disable -s --fail --referer "$url" \ + "https://www.microsoft.com/software-download-connector/api/GetProductDownloadLinksBySku?profile=${profile}&productEditionId=undefined&SKU=${sku_id}&friendlyFileName=undefined&Locale=en-US&sessionID=${session_id}")" + + local failed=0 + + if [[ -z "$iso_download_link_json" ]]; then + print_error "Microsoft servers gave an empty response to the download request." + failed=1 + fi + + if echo "$iso_download_link_json" | grep -q "Sentinel marked this request as rejected."; then + print_error "Microsoft blocked the automated download request based on your IP address." + failed=1 + fi + + if [[ "$failed" -eq 1 ]]; then + print_warning "Please manually download the Windows $windows_version ISO using a web browser from: $url" + print_warning "Save the downloaded ISO to: $WIN_ISO_DIR" + return 1 + fi + + # Extract 64-bit ISO download URL + local iso_download_link + + # Try with jq if available + if command -v jq >/dev/null 2>&1; then + iso_download_link="$(echo "$iso_download_link_json" | jq -r '.ProductDownloadOptions[].Uri' | grep x64 | head -n 1)" + else + # Fallback to grep/cut if jq not available + iso_download_link="$(echo "$iso_download_link_json" | grep -o '"Uri":"[^"]*x64[^"]*"' | cut -d'"' -f4 | head -n 1)" + fi + + if [[ -z "$iso_download_link" ]]; then + print_error "Failed to extract the download link from Microsoft's response." + print_warning "Manually download the Windows $windows_version ISO using a web browser from: $url" + return 1 + fi + + print_success "Got download link: ${iso_download_link%%\?*}" + + # Extract filename from URL + local file_name="$(echo "$iso_download_link" | cut -d'?' -f1 | rev | cut -d'/' -f1 | rev)" + + # If filename couldn't be extracted, use default + if [[ -z "$file_name" || "$file_name" == "$iso_download_link" ]]; then + file_name="windows-$windows_version.iso" + fi + + # Step 6: Download the ISO + print_info "Downloading Windows $windows_version ISO to $WIN_ISO_DIR/$file_name. This may take a while..." + + # Direct curl download + curl --disable --progress-bar --fail --location --proto '=https' --tlsv1.2 --http1.1 \ + --retry 3 --retry-delay 3 --connect-timeout 30 \ + --output "$WIN_ISO_DIR/$file_name" "$iso_download_link" || { + handle_curl_error $? + return 1 + } + + if [[ $? -ne 0 ]]; then + print_error "Failed to download the Windows $windows_version ISO." + return 1 + fi + + print_success "Successfully downloaded Windows $windows_version ISO to $WIN_ISO_DIR/$file_name" + + # Return the downloaded filename so calling code can use it + echo "$file_name" + return 0 +} + +# Create unattended installation ISO with proper Windows 11 bypass +create_unattended_iso() { + print_info "Creating unattended installation ISO..." + + # Create enhanced autounattend.xml with Windows 11 TPM/Secure Boot bypass + if [ ! -f "$WIN_ISO_DIR/unattended/autounattend.xml" ]; then + print_info "Creating enhanced autounattend.xml with Windows 11 bypass..." + mkdir -p "$WIN_ISO_DIR/unattended" + cat >"$WIN_ISO_DIR/unattended/autounattend.xml" <<'EOF' +<?xml version="1.0" encoding="utf-8"?> +<unattend xmlns="urn:schemas-microsoft-com:unattend"> + <settings pass="windowsPE"> + <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SetupUILanguage> + <UILanguage>en-US</UILanguage> + </SetupUILanguage> + <InputLocale>en-US</InputLocale> + <SystemLocale>en-US</SystemLocale> + <UILanguage>en-US</UILanguage> + <UILanguageFallback>en-US</UILanguageFallback> + <UserLocale>en-US</UserLocale> + </component> + <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <DiskConfiguration> + <Disk wcm:action="add"> + <CreatePartitions> + <CreatePartition wcm:action="add"> + <Order>1</Order> + <Type>Primary</Type> + <Size>100</Size> + </CreatePartition> + <CreatePartition wcm:action="add"> + <Order>2</Order> + <Type>EFI</Type> + <Size>100</Size> + </CreatePartition> + <CreatePartition wcm:action="add"> + <Order>3</Order> + <Type>MSR</Type> + <Size>16</Size> + </CreatePartition> + <CreatePartition wcm:action="add"> + <Order>4</Order> + <Type>Primary</Type> + <Extend>true</Extend> + </CreatePartition> + </CreatePartitions> + <ModifyPartitions> + <ModifyPartition wcm:action="add"> + <Order>1</Order> + <PartitionID>1</PartitionID> + <Label>WINRE</Label> + <Format>NTFS</Format> + <TypeID>DE94BBA4-06D1-4D40-A16A-BFD50179D6AC</TypeID> + </ModifyPartition> + <ModifyPartition wcm:action="add"> + <Order>2</Order> + <PartitionID>2</PartitionID> + <Label>System</Label> + <Format>FAT32</Format> + </ModifyPartition> + <ModifyPartition wcm:action="add"> + <Order>3</Order> + <PartitionID>3</PartitionID> + </ModifyPartition> + <ModifyPartition wcm:action="add"> + <Order>4</Order> + <PartitionID>4</PartitionID> + <Label>Windows</Label> + <Format>NTFS</Format> + </ModifyPartition> + </ModifyPartitions> + <DiskID>0</DiskID> + <WillWipeDisk>true</WillWipeDisk> + </Disk> + </DiskConfiguration> + <ImageInstall> + <OSImage> + <InstallTo> + <DiskID>0</DiskID> + <PartitionID>4</PartitionID> + </InstallTo> + <InstallToAvailablePartition>false</InstallToAvailablePartition> + <WillShowUI>OnError</WillShowUI> + <InstallFrom> + <MetaData wcm:action="add"> + <Key>/IMAGE/INDEX</Key> + <Value>6</Value> + </MetaData> + </InstallFrom> + </OSImage> + </ImageInstall> + <UserData> + <AcceptEula>true</AcceptEula> + <FullName>Windows User</FullName> + <Organization>Windows</Organization> + <ProductKey> + <WillShowUI>Never</WillShowUI> + </ProductKey> + </UserData> + <!-- Windows 11 Requirements Bypass --> + <RunSynchronous> + <RunSynchronousCommand wcm:action="add"> + <Order>1</Order> + <Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassTPMCheck /t REG_DWORD /d 1 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>2</Order> + <Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassSecureBootCheck /t REG_DWORD /d 1 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>3</Order> + <Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassRAMCheck /t REG_DWORD /d 1 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>4</Order> + <Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassStorageCheck /t REG_DWORD /d 1 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>5</Order> + <Path>reg add HKLM\SYSTEM\Setup\LabConfig /v BypassCPUCheck /t REG_DWORD /d 1 /f</Path> + </RunSynchronousCommand> + </RunSynchronous> + <EnableFirewall>false</EnableFirewall> + <EnableNetwork>true</EnableNetwork> + </component> + </settings> + <settings pass="specialize"> + <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <InputLocale>en-US</InputLocale> + <SystemLocale>en-US</SystemLocale> + <UILanguage>en-US</UILanguage> + <UILanguageFallback>en-US</UILanguageFallback> + <UserLocale>en-US</UserLocale> + </component> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <ComputerName>DESKTOP-PC</ComputerName> + <TimeZone>UTC</TimeZone> + </component> + <component name="Microsoft-Windows-Security-SPP-UX" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SkipAutoActivation>true</SkipAutoActivation> + </component> + </settings> + <settings pass="oobeSystem"> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <AutoLogon> + <Password> + <Value>password</Value> + <PlainText>true</PlainText> + </Password> + <LogonCount>1</LogonCount> + <Username>Administrator</Username> + <Enabled>true</Enabled> + </AutoLogon> + <OOBE> + <HideEULAPage>true</HideEULAPage> + <HideLocalAccountScreen>true</HideLocalAccountScreen> + <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> + <HideOnlineAccountScreens>true</HideOnlineAccountScreens> + <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> + <NetworkLocation>Home</NetworkLocation> + <ProtectYourPC>1</ProtectYourPC> + <SkipMachineOOBE>true</SkipMachineOOBE> + <SkipUserOOBE>true</SkipUserOOBE> + </OOBE> + <UserAccounts> + <AdministratorPassword> + <Value>password</Value> + <PlainText>true</PlainText> + </AdministratorPassword> + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Password> + <Value>password</Value> + <PlainText>true</PlainText> + </Password> + <Name>User</Name> + <Group>Administrators</Group> + <DisplayName>User</DisplayName> + </LocalAccount> + </LocalAccounts> + </UserAccounts> + <!-- Additional Windows 11 bypass commands --> + <FirstLogonCommands> + <SynchronousCommand wcm:action="add"> + <Order>1</Order> + <CommandLine>reg add HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU /v NoAutoUpdate /t REG_DWORD /d 1 /f</CommandLine> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>2</Order> + <CommandLine>reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v EnableLUA /t REG_DWORD /d 0 /f</CommandLine> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>3</Order> + <CommandLine>powershell -Command "Set-ExecutionPolicy Unrestricted -Force"</CommandLine> + </SynchronousCommand> + </FirstLogonCommands> + </component> + </settings> +</unattend> +EOF + fi + + # Create the unattended ISO + if command -v genisoimage >/dev/null 2>&1; then + print_info "Creating unattended ISO using genisoimage..." + genisoimage -J -r -o "$WIN_ISO_DIR/unattended.iso" "$WIN_ISO_DIR/unattended" 2>/dev/null + return $? + elif command -v mkisofs >/dev/null 2>&1; then + print_info "Creating unattended ISO using mkisofs..." + mkisofs -J -r -o "$WIN_ISO_DIR/unattended.iso" "$WIN_ISO_DIR/unattended" 2>/dev/null + return $? + elif command -v xorriso >/dev/null 2>&1; then + print_info "Creating unattended ISO using xorriso..." + xorriso -as genisoimage -J -r -o "$WIN_ISO_DIR/unattended.iso" "$WIN_ISO_DIR/unattended" 2>/dev/null + return $? + else + print_warning "No ISO creation tool found (genisoimage, mkisofs, or xorriso)" + print_warning "Installing genisoimage..." + if command -v apt-get >/dev/null 2>&1; then + sudo apt-get update && sudo apt-get install -y genisoimage + elif command -v yum >/dev/null 2>&1; then + sudo yum install -y genisoimage + elif command -v pacman >/dev/null 2>&1; then + sudo pacman -S --noconfirm cdrtools + fi + + # Try again after installation + if command -v genisoimage >/dev/null 2>&1; then + print_info "Creating unattended ISO using newly installed genisoimage..." + genisoimage -J -r -o "$WIN_ISO_DIR/unattended.iso" "$WIN_ISO_DIR/unattended" 2>/dev/null + return $? + else + print_error "Failed to install ISO creation tools" + return 1 + fi + fi +} + +# Download or locate essential files +prepare_files() { + # Find Windows ISO + WINDOWS_ISO_PATH=$(find_windows_iso | tail -n 1 | xargs) + local iso_found=false + + # Check if we found a valid ISO + if [[ -n "$WINDOWS_ISO_PATH" && -f "$WINDOWS_ISO_PATH" ]]; then + print_success "Using Windows ISO: $WINDOWS_ISO_PATH" + iso_found=true + else + print_warning "Windows ISO not found, attempting to download..." + if download_windows_iso; then + # Check again after download attempt + WINDOWS_ISO_PATH=$(find_windows_iso | tail -n 1 | xargs) + if [[ -n "$WINDOWS_ISO_PATH" && -f "$WINDOWS_ISO_PATH" ]]; then + print_success "Using downloaded Windows ISO: $WINDOWS_ISO_PATH" + iso_found=true + fi + fi + fi + + # If we still don't have an ISO, exit + if [[ "$iso_found" != "true" ]]; then + print_error "Could not find or download Windows ISO" + print_info "Please place your Windows ISO file in: $WIN_ISO_DIR" + exit 1 + fi + + # Download VirtIO drivers if missing (optional for stealth mode) + if [[ ! -f "$ISO_VIRTIO" ]]; then + print_info "VirtIO drivers ISO not found, downloading (for fallback)..." + download_file "$VIRTIO_ISO_URL" "$ISO_VIRTIO" "" "true" + else + print_success "VirtIO drivers ISO already exists: $ISO_VIRTIO" + fi + + # Create unattended ISO if it doesn't exist + if [[ ! -f "$ISO_UNATTENDED" ]]; then + create_unattended_iso + if [[ $? -eq 0 ]]; then + print_success "Created unattended ISO: $ISO_UNATTENDED" + else + print_error "Failed to create unattended ISO" + exit 1 + fi + else + print_success "Unattended ISO already exists: $ISO_UNATTENDED" + fi +} + +# Locate OVMF firmware files +locate_ovmf() { + OVMF_DIRS=( + "/usr/share/OVMF" + "/usr/share/qemu" + "/usr/lib/qemu" + "/usr/share/edk2" + "/usr/lib/edk2" + "/usr/share/edk2/ovmf" + "/usr/share/edk2-ovmf" + "/usr/share/qemu/edk2-x86_64-code.fd" + ) + + OVMF_CODE="" + OVMF_VARS="" + + for dir in "${OVMF_DIRS[@]}"; do + if [[ -f "$dir" ]]; then + # Handle direct file paths + if [[ "$dir" == *"code.fd" ]]; then + OVMF_CODE="$dir" + OVMF_VARS="$(dirname "$dir")/edk2-x86_64-vars.fd" + fi + elif [[ -d "$dir" ]]; then + # Handle directories + [[ -z "$OVMF_CODE" ]] && OVMF_CODE=$(find "$dir" -type f -name "OVMF_CODE.fd" -o -name "edk2-x86_64-code.fd" 2>/dev/null | head -n 1) + [[ -z "$OVMF_VARS" ]] && OVMF_VARS=$(find "$dir" -type f -name "OVMF_VARS.fd" -o -name "edk2-x86_64-vars.fd" 2>/dev/null | head -n 1) + fi + [[ -n "$OVMF_CODE" && -n "$OVMF_VARS" ]] && break + done + + # Try package-specific locations + if [[ -z "$OVMF_CODE" || -z "$OVMF_VARS" ]]; then + # Ubuntu/Debian locations + [[ -z "$OVMF_CODE" ]] && OVMF_CODE="/usr/share/OVMF/OVMF_CODE_4M.fd" + [[ -z "$OVMF_VARS" && -f "/usr/share/OVMF/OVMF_VARS_4M.fd" ]] && OVMF_VARS="/usr/share/OVMF/OVMF_VARS_4M.fd" + + # Arch Linux locations + [[ -z "$OVMF_CODE" ]] && OVMF_CODE="/usr/share/edk2-ovmf/x64/OVMF_CODE.fd" + [[ -z "$OVMF_VARS" && -f "/usr/share/edk2-ovmf/x64/OVMF_VARS.fd" ]] && OVMF_VARS="/usr/share/edk2-ovmf/x64/OVMF_VARS.fd" + fi + + # Ensure a writable copy of OVMF_VARS.fd + local original_ovmf_vars="$OVMF_VARS" + OVMF_VARS="$FIRMWARE_DIR/OVMF_VARS.fd" + + if [[ ! -f "$OVMF_VARS" && -f "$original_ovmf_vars" ]]; then + print_info "Copying OVMF_VARS.fd to $OVMF_VARS" + cp "$original_ovmf_vars" "$OVMF_VARS" 2>/dev/null || { + print_error "Failed to copy OVMF_VARS.fd!" + print_info "Trying to install OVMF firmware..." + + # Try to install OVMF + if command -v apt-get >/dev/null 2>&1; then + sudo apt-get update && sudo apt-get install -y ovmf + elif command -v yum >/dev/null 2>&1; then + sudo yum install -y edk2-ovmf + elif command -v pacman >/dev/null 2>&1; then + sudo pacman -S --noconfirm edk2-ovmf + fi + + # Try to locate again after installation + locate_ovmf + return + } + fi + + # Check if required files exist + if [[ -z "$OVMF_CODE" || ! -f "$OVMF_CODE" ]]; then + print_error "OVMF_CODE.fd not found!" + print_info "Please install OVMF firmware package:" + print_info " Ubuntu/Debian: sudo apt install ovmf" + print_info " RHEL/CentOS: sudo yum install edk2-ovmf" + print_info " Arch: sudo pacman -S edk2-ovmf" + exit 1 + fi + if [[ ! -f "$OVMF_VARS" ]]; then + print_error "OVMF_VARS.fd not found or could not be copied!" + exit 1 + fi + + print_success "Found OVMF firmware: $OVMF_CODE" + print_success "Using OVMF vars: $OVMF_VARS" +} + +# Create VM disk image +create_disk() { + # Check if the qcow2 image file exists; if not, create it + if [[ ! -f "$QCOW2_FILE" ]]; then + print_info "Creating $QCOW2_FILE with a size of $VM_SIZE" + qemu-img create -f qcow2 "$QCOW2_FILE" "$VM_SIZE" || { + print_error "Failed to create qcow2 image!" + exit 1 + } + print_success "Created VM disk image: $QCOW2_FILE" + else + print_success "VM disk image already exists: $QCOW2_FILE" + fi +} + +# Helper function to convert QCOW2 to RAW for stealth +convert_to_raw() { + if [[ -f "$QCOW2_FILE" && ! -f "$RAW_FILE" ]]; then + print_info "Converting QCOW2 to RAW format for stealth..." + qemu-img convert -f qcow2 -O raw "$QCOW2_FILE" "$RAW_FILE" || { + print_error "Failed to convert to RAW format" + exit 1 + } + print_success "Successfully converted to RAW format: $RAW_FILE" + elif [[ ! -f "$RAW_FILE" ]]; then + print_info "Creating RAW disk image..." + qemu-img create -f raw "$RAW_FILE" "$VM_SIZE" || { + print_error "Failed to create RAW disk image" + exit 1 + } + print_success "Created RAW disk image: $RAW_FILE" + else + print_success "RAW disk image already exists: $RAW_FILE" + fi +} + +# Check dependencies +check_dependencies() { + local missing_deps=() + + # Check for essential tools + command -v qemu-system-x86_64 >/dev/null 2>&1 || missing_deps+=("qemu-system-x86_64") + command -v swtpm >/dev/null 2>&1 || missing_deps+=("swtpm") + command -v curl >/dev/null 2>&1 || missing_deps+=("curl") + command -v uuidgen >/dev/null 2>&1 || missing_deps+=("uuidgen") + command -v openssl >/dev/null 2>&1 || missing_deps+=("openssl") + + if [[ ${#missing_deps[@]} -gt 0 ]]; then + print_error "Missing required dependencies: ${missing_deps[*]}" + print_info "Please install them using your package manager:" + print_info " Ubuntu/Debian: sudo apt install qemu-system-x86 swtpm curl uuid-runtime openssl" + print_info " RHEL/CentOS: sudo yum install qemu-kvm swtpm curl util-linux openssl" + print_info " Arch: sudo pacman -S qemu swtpm curl util-linux openssl" + exit 1 + fi +} + +start_vm() { + # Verify that we have a Windows ISO + if [[ -z "$WINDOWS_ISO_PATH" || ! -f "$WINDOWS_ISO_PATH" ]]; then + print_error "Windows ISO file not found. Cannot start VM." + print_info "Please download Windows 11 ISO and save it to: $WIN_ISO_DIR" + exit 1 + fi + + # Anti-detection: Generate realistic hardware identifiers + REAL_MAC="00:1A:79:$(openssl rand -hex 3 | sed 's/../&:/g; s/:$//')" # Dell OUI + REAL_SERIAL="$(openssl rand -hex 8 | tr '[:lower:]' '[:upper:]')" + REAL_UUID="$(uuidgen)" + REAL_VENDOR="Dell Inc." + REAL_PRODUCT="OptiPlex 7090" + REAL_VERSION="01" + REAL_FAMILY="OptiPlex" + + # Stop any existing swtpm process for this VM + if [[ -f "$TPM_SOCKET" ]]; then + print_info "Cleaning up existing TPM socket..." + rm -f "$TPM_SOCKET" + fi + + # Start swtpm (TPM emulator) + print_info "Starting TPM emulator..." + /sbin/swtpm socket \ + --ctrl type=unixio,path="$TPM_SOCKET" \ + --terminate \ + --tpmstate dir="$TPM_DIR" \ + --tpm2 & + + # Give swtpm a moment to create the socket + sleep 1 + + # Verify TPM socket exists + if [[ ! -S "$TPM_SOCKET" ]]; then + print_error "TPM socket not created: $TPM_SOCKET" + exit 1 + fi + + print_info "Starting stealth Windows 11 VM..." + print_info "Using Windows ISO: $WINDOWS_ISO_PATH" + print_info "VM will boot from unattended installation ISO" + print_info "Default login: Username=User, Password=password" + print_info "Anti-detection measures enabled" + + # Build qemu arguments in an array for safety and readability + QEMU_ARGS=( + # Basic VM configuration + -name "$REAL_PRODUCT",process="$VM_NAME" + -machine q35,hpet=off,smm=on,vmport=off,accel=kvm + + # Global optimizations + -global kvm-pit.lost_tick_policy=discard + -global ICH9-LPC.disable_s3=1 + + # CPU configuration (stealth) + -cpu host,-hypervisor,+invtsc,+ssse3,l3-cache=on,migratable=no + -smp "$SMP_CONFIG" + -m "$VM_RAM" + + # SMBIOS spoofing for anti-detection + -smbios type=0,vendor="$REAL_VENDOR",version="$REAL_VERSION",date="03/15/2023" + -smbios type=1,manufacturer="$REAL_VENDOR",product="$REAL_PRODUCT",version="$REAL_VERSION",serial="$REAL_SERIAL",uuid="$REAL_UUID",family="$REAL_FAMILY" + -smbios type=2,manufacturer="$REAL_VENDOR",product="0NK70N",version="A00",serial="$REAL_SERIAL" + -smbios type=3,manufacturer="$REAL_VENDOR",version="$REAL_VERSION",serial="$REAL_SERIAL" + -smbios type=4,manufacturer="Intel(R) Corporation",version="11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz" + + # Process management + -pidfile "$VM_DIR/$VM_NAME.pid" + -rtc base=localtime,clock=host,driftfix=slew + + # Display and graphics + -vga qxl + -display sdl + + # Boot configuration + -boot menu=on,splash-time=0,order=d,reboot-timeout=5000 + + # Random number generator + -object rng-random,id=rng0,filename=/dev/urandom + -device i82801b11-bridge,id=pci.1 + + # usb + -device usb-ehci,id=usb + -device usb-kbd,bus=usb.0 + -device usb-tablet,bus=usb.0 + -k en-us + + # audio (pipewire) + -audiodev pipewire,id=audio0 + -device AC97,audiodev=audio0 + + # Network (realistic NIC with stealth MAC) + -device rtl8139,netdev=nic,mac="$REAL_MAC" + -netdev user,hostname="$VM_NAME",hostfwd=tcp::"$HOST_PORT"-:"$GUEST_PORT",smb="$SHARED_DIR",id=nic + + # UEFI firmware + -global driver=cfi.pflash01,property=secure,value=on + -drive if=pflash,format=raw,unit=0,file="$OVMF_CODE",readonly=on + -drive if=pflash,format=raw,unit=1,file="$OVMF_VARS" + + # Main storage (AHCI for compatibility) + -device ahci,id=ahci + -drive if=none,id=disk0,format=raw,file="$RAW_FILE",cache=writeback + -device ide-hd,bus=ahci.0,drive=disk0 + + # CD-ROM drives (Windows ISO + Unattended) + -drive media=cdrom,index=0,file="$WINDOWS_ISO_PATH",if=ide + -drive media=cdrom,index=1,file="$ISO_UNATTENDED",if=ide + + # TPM 2.0 + -chardev socket,id=chrtpm,path="$TPM_SOCKET" + -tpmdev emulator,id=tpm0,chardev=chrtpm + -device tpm-tis,tpmdev=tpm0 + + # Monitoring and management sockets + -monitor unix:"$SOCKET_DIR/$VM_NAME-monitor.socket",server,nowait + -serial unix:"$SOCKET_DIR/$VM_NAME-serial.socket",server,nowait + ) + + # Add VirtIO ISO if it exists (as fallback) + if [[ -f "$ISO_VIRTIO" ]]; then + QEMU_ARGS+=(-drive media=cdrom,index=2,file="$ISO_VIRTIO",if=ide,readonly=on) + fi + + print_info "Starting QEMU with stealth configuration..." + print_info "Monitor socket: $SOCKET_DIR/$VM_NAME-monitor.socket" + print_info "Serial socket: $SOCKET_DIR/$VM_NAME-serial.socket" + print_info "SSH port forwarding: localhost:$HOST_PORT -> VM:$GUEST_PORT" + + # Execute qemu + exec qemu-system-x86_64 "${QEMU_ARGS[@]}" + +} + +# Helper function to convert QCOW2 to RAW for stealth +convert_to_raw() { + if [[ -f "$QCOW2_FILE" && ! -f "$RAW_FILE" ]]; then + print_info "Converting QCOW2 to RAW format for stealth..." + qemu-img convert -f qcow2 -O raw "$QCOW2_FILE" "$RAW_FILE" + if [[ $? -eq 0 ]]; then + print_success "Successfully converted to RAW format" + else + print_error "Failed to convert to RAW format" + exit 1 + fi + elif [[ ! -f "$RAW_FILE" ]]; then + print_info "Creating RAW disk image..." + qemu-img create -f raw "$RAW_FILE" "$VM_SIZE" + fi +} + +# Anti-detection validation commands +show_validation_commands() { + cat << 'EOF' +=== VM DETECTION VALIDATION COMMANDS === + +Run these commands inside Windows to verify stealth: + +# PowerShell - Check hardware info: +Get-WmiObject -Class Win32_ComputerSystem | Select-Object Manufacturer, Model, TotalPhysicalMemory +Get-WmiObject -Class Win32_BIOS | Select-Object Manufacturer, Version, SerialNumber +Get-WmiObject -Class Win32_BaseBoard | Select-Object Manufacturer, Product, SerialNumber +Get-WmiObject -Class Win32_Processor | Select-Object Name, Manufacturer, MaxClockSpeed + +# Check for hypervisor presence: +Get-WmiObject -Class Win32_ComputerSystem | Select-Object HypervisorPresent +bcdedit /enum | findstr hypervisorlaunchtype + +# Registry checks for VM artifacts: +reg query "HKLM\HARDWARE\DESCRIPTION\System" /v SystemBiosVersion +reg query "HKLM\HARDWARE\DESCRIPTION\System\BIOS" /v SystemManufacturer +reg query "HKLM\SYSTEM\CurrentControlSet\Services" | findstr -i "vbox\|vmware\|qemu\|virtio" + +# Check network adapter: +Get-WmiObject -Class Win32_NetworkAdapter | Where-Object {$_.NetConnectionStatus -eq 2} | Select-Object Name, MACAddress, Manufacturer + +# Check PCI devices for VM signatures: +Get-WmiObject -Class Win32_PnPEntity | Where-Object {$_.Name -match "VirtIO|QEMU|VMware|VirtualBox|Hyper-V"} | Select-Object Name, DeviceID + +# Check running services: +Get-Service | Where-Object {$_.Name -match "vbox|vmware|qemu|virtio|spice"} + +# Check for VM-specific processes: +Get-Process | Where-Object {$_.ProcessName -match "vbox|vmware|qemu|virtio"} + +# Additional checks: +systeminfo | findstr /C:"System Manufacturer" /C:"System Model" /C:"BIOS Version" +wmic computersystem get manufacturer,model,name,systemtype + +EOF +} + +# Cleanup function +cleanup() { + print_info "Cleaning up..." + + # Kill swtpm if running + if [[ -f "$TPM_SOCKET" ]]; then + pkill -f "swtpm.*$TPM_SOCKET" 2>/dev/null || true + rm -f "$TPM_SOCKET" 2>/dev/null || true + fi + + # Remove PID file + if [[ -f "$VM_DIR/$VM_NAME.pid" ]]; then + rm -f "$VM_DIR/$VM_NAME.pid" 2>/dev/null || true + fi +} + +# Trap cleanup on exit +trap cleanup EXIT + +# Main execution function +main() { + print_info "=== Windows 11 Stealth VM Setup ===" + print_info "VM Name: $VM_NAME" + print_info "VM Size: $VM_SIZE" + print_info "VM RAM: $VM_RAM" + print_info "VM CPUs: $VM_CPU" + print_info "Host Port: $HOST_PORT" + + check_dependencies + prepare_files + locate_ovmf + create_disk + convert_to_raw + show_validation_commands + + print_success "Setup complete! Starting VM..." + start_vm +} + +# Run main function +main "$@" diff --git a/common/scripts/virt/server.sh b/common/scripts/virt/server.sh new file mode 100755 index 0000000..6d937fd --- /dev/null +++ b/common/scripts/virt/server.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash + +# Set variables +HOST_DIR="machines" +VM_NAME="proxmox" +VM_DIR="$HOME/machines/vm" +IMAGE_DIR="$HOME/machines/images" +SOCKET_DIR="$VM_DIR" +#ISO_FILE="$IMAGE_DIR/proxmox-ve_8.3-1.iso" +ISO_FILE=$(find "$IMAGE_DIR" -type f -iname "proxmox*.iso" -exec stat --format="%Y %n" {} \; | sort -n | tail -n 1 | cut -d' ' -f2-) +QCOW2_FILE="$VM_DIR/$VM_NAME.qcow2" +HOST_PORT=22220 +GUEST_PORT=22 +SHARED_DIR="$HOME/machines/shared" +FIRMWARE_DIR="$HOME/machines/firmware" +VM_SIZE="300G" # Disk size in GB +VM_RAM="12G" # RAM size +VM_CPU="6" # Number of virtual CPUs +CORES=$((VM_CPU / 2)) +THREADS_PER_CORE=2 +SOCKETS=1 + +# Set SMP configuration +SMP_CONFIG="cores=$CORES,threads=$THREADS_PER_CORE,sockets=$SOCKETS" + +# Ensure necessary directories exist +mkdir -p "$HOME"/"$HOST_DIR" +mkdir -p "$VM_DIR" "$IMAGE_DIR" "$SHARED_DIR" "$FIRMWARE_DIR" + +# Locate OVMF firmware files +OVMF_DIRS=( + "/usr/share/OVMF" + "/usr/share/qemu" + "/usr/lib/qemu" + "/usr/share/edk2" + "/usr/lib/edk2" +) + +OVMF_CODE="" +OVMF_VARS="" + +for dir in "${OVMF_DIRS[@]}"; do + [[ -z "$OVMF_CODE" ]] && OVMF_CODE=$(find "$dir" -type f -name "OVMF_CODE.fd" -o -name "edk2-x86_64-code.fd" 2>/dev/null | head -n 1) + [[ -z "$OVMF_VARS" ]] && OVMF_VARS=$(find "$dir" -type f -name "OVMF_VARS.fd" 2>/dev/null | head -n 1) + [[ -n "$OVMF_CODE" && -n "$OVMF_VARS" ]] && break +done + +# Ensure a writable copy of OVMF_VARS.fd +OVMF_VARS="$FIRMWARE_DIR/OVMF_VARS.fd" + +if [[ ! -f "$OVMF_VARS" ]]; then + echo "Copying OVMF_VARS.fd to $OVMF_VARS" + cp /usr/share/edk2/OvmfX64/OVMF_VARS.fd "$OVMF_VARS" 2>/dev/null || { + echo "Error: Failed to copy OVMF_VARS.fd!" >&2 + exit 1 + } +fi + +# Check if required files exist +if [[ -z "$OVMF_CODE" ]]; then + echo "Error: OVMF_CODE.fd not found!" >&2 + exit 1 +fi +if [[ ! -f "$OVMF_VARS" ]]; then + echo "Error: OVMF_VARS.fd not found or could not be copied!" >&2 + exit 1 +fi +if [[ ! -f "$ISO_FILE" ]]; then + echo "Warning: $ISO_FILE ISO not found at $IMAGE_DIR" +fi + +# Check if the qcow2 image file exists; if not, create it +if [[ ! -f "$QCOW2_FILE" ]]; then + echo "Creating $QCOW2_FILE with a size of $VM_SIZE" + qemu-img create -f qcow2 "$QCOW2_FILE" "$VM_SIZE" || { + echo "Error: Failed to create qcow2 image!" >&2 + exit 1 + } +else + echo "" +fi + +# Run QEMU +/sbin/qemu-system-x86_64 \ + -name "$VM_NAME",process="$VM_NAME" \ + -machine q35,smm=off,vmport=off,accel=kvm \ + -global kvm-pit.lost_tick_policy=discard \ + -cpu host \ + -smp "$SMP_CONFIG" \ + -m "$VM_RAM" \ + -device virtio-balloon \ + -pidfile "$VM_DIR/$VM_NAME.pid" \ + -rtc base=utc,clock=host \ + -vga none \ + -device virtio-vga-gl,xres=1280,yres=800 \ + -display sdl,gl=on \ + -device virtio-rng-pci,rng=rng0 \ + -object rng-random,id=rng0,filename=/dev/urandom \ + -device qemu-xhci,id=spicepass \ + -chardev spicevmc,id=usbredirchardev1,name=usbredir \ + -device usb-redir,chardev=usbredirchardev1,id=usbredirdev1 \ + -chardev spicevmc,id=usbredirchardev2,name=usbredir \ + -device usb-redir,chardev=usbredirchardev2,id=usbredirdev2 \ + -chardev spicevmc,id=usbredirchardev3,name=usbredir \ + -device usb-redir,chardev=usbredirchardev3,id=usbredirdev3 \ + -device pci-ohci,id=smartpass \ + -device usb-ccid \ + -device usb-ehci,id=input \ + -device usb-kbd,bus=input.0 \ + -k en-us \ + -device usb-tablet,bus=input.0 \ + -audiodev pipewire,id=audio0 \ + -device intel-hda \ + -device hda-micro,audiodev=audio0 \ + -device virtio-net,netdev=nic \ + -netdev user,hostname="$VM_NAME",hostfwd=tcp::"$HOST_PORT"-:"$GUEST_PORT",id=nic \ + -fsdev local,id=fsdev0,path="$SHARED_DIR",security_model=mapped-xattr \ + -device virtio-9p-pci,fsdev=fsdev0,mount_tag="SharedDir" \ + -global driver=cfi.pflash01,property=secure,value=on \ + -drive if=pflash,format=raw,unit=0,file="$OVMF_CODE",readonly=on \ + -drive if=pflash,format=raw,unit=1,file="$OVMF_VARS" \ + -drive media=cdrom,index=0,file="$ISO_FILE" \ + -device virtio-blk-pci,drive=SystemDisk \ + -drive id=SystemDisk,if=none,format=qcow2,file="$QCOW2_FILE" \ + -monitor unix:"$SOCKET_DIR/$VM_NAME-monitor.socket",server,nowait \ + -serial unix:"$SOCKET_DIR/$VM_NAME-serial.socket",server,nowait +#-serial unix:"$SOCKET_DIR/$VM_NAME-serial.socket",server,nowait 2>/dev/null +#-netdev user,hostname="$VM_NAME",hostfwd=tcp::"$HOST_PORT"-:"$GUEST_PORT",smb="$SHARED_DIR",id=nic \ diff --git a/common/scripts/virt/ubuntu b/common/scripts/virt/ubuntu new file mode 100755 index 0000000..c93941d --- /dev/null +++ b/common/scripts/virt/ubuntu @@ -0,0 +1,222 @@ +#!/usr/bin/env bash + +# Set variables +HOST_DIR="$HOME/virt" +VM_DIR="$HOME/virt/machines" +IMAGE_DIR="$HOME/virt/images" +SOCKET_DIR="$VM_DIR" + +BASE_NAME=$(basename "$0" .sh | sed 's/[0-9]*$//') +VM_INDEX=$(basename "$0" .sh | grep -o '[0-9]*$') +VM_INDEX=${VM_INDEX:-1} # default to 1 if no number + +VM_NAME="${BASE_NAME}${VM_INDEX}" +QCOW2_FILE="$VM_DIR/$VM_NAME.qcow2" + +# If the file exists, use it +if [[ -f "$QCOW2_FILE" ]]; then + echo "Using existing VM image: $QCOW2_FILE" +else + # Loop to find first available index if not + while [[ -f "$QCOW2_FILE" ]]; do + ((VM_INDEX++)) + VM_NAME="${BASE_NAME}${VM_INDEX}" + QCOW2_FILE="$VM_DIR/$VM_NAME.qcow2" + done + echo "Creating new VM image: $QCOW2_FILE" + qemu-img create -f qcow2 "$QCOW2_FILE" "$VM_SIZE" || { + echo "Error: Failed to create qcow2 image!" >&2 + exit 1 + } +fi + +#ISO_FILE=$(ls -1t "$IMAGE_DIR"/ubuntu-*-desktop-*-amd64.iso 2>/dev/null | head -n1) +#if [[ -z "$ISO_FILE" ]]; then +# echo "Error: No Ubuntu ISO found in $IMAGE_DIR" >&2 +# exit 1 +#fi +#echo "Using ISO: $ISO_FILE" + +# Directory to remember ISO choices +CHOICES_DIR="$VM_DIR/.iso_choices" +mkdir -p "$CHOICES_DIR" + +CHOICE_FILE="$CHOICES_DIR/$BASE_NAME.choice" + +if [[ -f "$CHOICE_FILE" ]]; then + # Already have a saved ISO for this VM + ISO_FILE=$(<"$CHOICE_FILE") + echo "Using previously selected ISO: $ISO_FILE" +else + # List all ISO files newest first + mapfile -t ISO_LIST < <(ls -1t "$IMAGE_DIR"/*.iso 2>/dev/null) + if [[ ${#ISO_LIST[@]} -eq 0 ]]; then + echo "Error: No ISO files found in $IMAGE_DIR" >&2 + exit 1 + fi + + echo "Available ISOs:" + PS3="Select an ISO to boot (default: 1): " + select ISO_FILE in "${ISO_LIST[@]}"; do + ISO_FILE=${ISO_FILE:-${ISO_LIST[0]}} + echo "Using ISO: $ISO_FILE" + # Save choice so we don’t ask again + echo "$ISO_FILE" > "$CHOICE_FILE" + break + done +fi + +QCOW2_FILE="$VM_DIR/$VM_NAME.qcow2" +GUEST_PORT=22 +SHARED_DIR="$HOST_DIR/shared" +VM_SIZE="60G" # Disk size in GB +VM_RAM="8G" # RAM size +VM_CPU="6" # Number of virtual CPUs +CORES=$((VM_CPU / 2)) +THREADS_PER_CORE=2 +SOCKETS=1 +SHARED_DIR="$HOST_DIR/shared" +FIRMWARE_DIR="$HOST_DIR/firmware" + +SMP_CONFIG="cores=$CORES,threads=$THREADS_PER_CORE,sockets=$SOCKETS" + +# Set SPICE_NOGRAB environment variable +export SPICE_NOGRAB=1 + +# Try to find an available host port starting from 22220 +HOST_PORT_START=22220 +HOST_PORT_END=22300 + +for ((port = HOST_PORT_START; port <= HOST_PORT_END; port++)); do + if ! ss -tuln | grep -q ":$port\b"; then + HOST_PORT=$port + echo "Using available port: $HOST_PORT" + break + fi +done + +if [[ $port -gt $HOST_PORT_END ]]; then + echo "Error: No available ports found between $HOST_PORT_START and $HOST_PORT_END" >&2 + exit 1 +fi + +# Ensure necessary directories exist +mkdir -p "$HOST_DIR" +mkdir -p "$VM_DIR" "$IMAGE_DIR" "$SHARED_DIR" "$FIRMWARE_DIR" + +# Locate OVMF firmware files +OVMF_DIRS=( + "/usr/share/OVMF" + "/usr/share/qemu" + "/usr/lib/qemu" + "/usr/share/edk2" + "/usr/lib/edk2" +) + +OVMF_CODE="" +OVMF_VARS="" + +for dir in "${OVMF_DIRS[@]}"; do + [[ -z "$OVMF_CODE" ]] && OVMF_CODE=$(find "$dir" -type f -name "OVMF_CODE.fd" -o -name "edk2-x86_64-code.fd" 2>/dev/null | head -n 1) + [[ -z "$OVMF_VARS" ]] && OVMF_VARS=$(find "$dir" -type f -name "OVMF_VARS.fd" 2>/dev/null | head -n 1) + [[ -n "$OVMF_CODE" && -n "$OVMF_VARS" ]] && break +done + +# Ensure a writable copy of OVMF_VARS.fd +OVMF_VARS="$IMAGE_DIR/OVMF_VARS.fd" + +if [[ ! -f "$OVMF_VARS" ]]; then + echo "Copying OVMF_VARS.fd to $OVMF_VARS" + cp /usr/share/edk2/OvmfX64/OVMF_VARS.fd "$OVMF_VARS" 2>/dev/null || { + echo "Error: Failed to copy OVMF_VARS.fd!" >&2 + exit 1 + } +fi + +# Check if required files exist +if [[ -z "$OVMF_CODE" ]]; then + echo "Error: OVMF_CODE.fd not found!" >&2 + exit 1 +fi +if [[ ! -f "$OVMF_VARS" ]]; then + echo "Error: OVMF_VARS.fd not found or could not be copied!" >&2 + exit 1 +fi +if [[ ! -f "$ISO_FILE" ]]; then + echo "Warning: $ISO_FILE ISO not found at $IMAGE_DIR" +fi + +# Check if the qcow2 image file exists; if not, create it +if [[ ! -f "$QCOW2_FILE" ]]; then + echo "Creating $QCOW2_FILE with a size of $VM_SIZE" + qemu-img create -f qcow2 "$QCOW2_FILE" "$VM_SIZE" || { + echo "Error: Failed to create qcow2 image!" >&2 + exit 1 + } +else + echo "" +fi + +# Run QEMU +/sbin/qemu-system-x86_64 \ + -name "$VM_NAME",process="$VM_NAME" \ + -machine q35,smm=off,vmport=off,accel=kvm \ + -enable-kvm \ + -global kvm-pit.lost_tick_policy=discard \ + -cpu host \ + -smp "$SMP_CONFIG" \ + -m "$VM_RAM" \ + -device virtio-balloon \ + -pidfile "$VM_DIR/$VM_NAME.pid" \ + -rtc base=utc,clock=host \ + -vga none \ + -device virtio-vga-gl,xres=1280,yres=800 \ + -display sdl,gl=on \ + -device virtio-rng-pci,rng=rng0 \ + -device virtio-serial \ + -object rng-random,id=rng0,filename=/dev/urandom \ + -device qemu-xhci,id=spicepass \ + -chardev spicevmc,id=usbredirchardev1,name=usbredir \ + -device usb-redir,chardev=usbredirchardev1,id=usbredirdev1 \ + -chardev spicevmc,id=usbredirchardev2,name=usbredir \ + -device usb-redir,chardev=usbredirchardev2,id=usbredirdev2 \ + -chardev spicevmc,id=usbredirchardev3,name=usbredir \ + -device usb-redir,chardev=usbredirchardev3,id=usbredirdev3 \ + -device pci-ohci,id=smartpass \ + -device usb-ccid \ + -device usb-ehci,id=input \ + -device usb-kbd,bus=input.0 \ + -k en-us \ + -device usb-tablet,bus=input.0 \ + -audiodev pipewire,id=audio0 \ + -device intel-hda \ + -device hda-micro,audiodev=audio0 \ + -device virtio-net,netdev=nic \ + -netdev user,hostname="$VM_NAME",hostfwd=tcp::"$HOST_PORT"-:"$GUEST_PORT",id=nic \ + -device virtio-9p-pci,fsdev=fsdev0,mount_tag="Public-$(whoami)" \ + -global driver=cfi.pflash01,property=secure,value=on \ + -drive if=pflash,format=raw,unit=0,file="$OVMF_CODE",readonly=on \ + -drive if=pflash,format=raw,unit=1,file="$OVMF_VARS" \ + -drive media=cdrom,index=0,file="$ISO_FILE" \ + -device virtio-blk-pci,drive=SystemDisk \ + -drive id=SystemDisk,if=none,format=qcow2,file="$QCOW2_FILE" \ + -fsdev local,id=fsdev0,path="$SHARED_DIR",security_model=mapped-xattr \ + -monitor unix:"$SOCKET_DIR/$VM_NAME-monitor.socket",server,nowait \ + -serial unix:"$SOCKET_DIR/$VM_NAME-serial.socket",server,nowait + +#-display sdl,gl=on \ +#-display gtk,gl=on \ +#-display gtk,grab-on-hover=on,grab-mod=none \ +#-qmp unix:/tmp/qmp-sock,server,nowait \ +#-qmp unix:"$SOCKET_DIR/$VM_NAME-qmp.socket",server,nowait \ +#-chardev socket,path="$VM_DIR/$VM_NAME-qga.sock",server=on,wait=off,id=qga0 \ + +# Network Isolation: +#-netdev user,hostname="$VM_NAME",restrict=yes,id=nic \ + +# No file-sharing: +#-netdev user,hostname="$VM_NAME",hostfwd=tcp::"$HOST_PORT"-:"$GUEST_PORT",id=nic \ + +# File-sharing and networking: +#-netdev user,hostname="$VM_NAME",hostfwd=tcp::"$HOST_PORT"-:"$GUEST_PORT",smb="$SHARED_DIR",id=nic \ +#-device virtio-9p-pci,fsdev=fsdev0,mount_tag="Public-$(whoami)" \ diff --git a/common/scripts/virt/windows.sh b/common/scripts/virt/windows.sh new file mode 100755 index 0000000..1863f8a --- /dev/null +++ b/common/scripts/virt/windows.sh @@ -0,0 +1,1156 @@ +#!/usr/bin/env bash + +# Windows VM Creation Script +# Description: Creates and manages Windows virtual machines using QEMU/KVM +# Features: +# - Supports Windows 10, 11, and Server +# - Automatic dependency checking +# - Multiple VM instance support +# - Cache management +# - Unattended installation +# - SMB sharing support +# - Network control + +# Usage: ./windows.sh [VERSION] [OPTIONS] +# Versions: +# 10 Windows 10 +# 11 Windows 11 (default) +# server Windows Server 2022 +# server2019 Windows Server 2019 +# server2016 Windows Server 2016 + +# Options: +# --help Show this help message +# --clean Clean dependency cache +# --check-deps Only check dependencies and exit +# --download-iso Download Windows ISO and exit +# --name NAME Set custom VM name +# --ram SIZE Set RAM size (default: 8G) +# --cpu NUM Set number of CPUs (default: 6) +# --disk SIZE Set disk size (default: 80G) +# --enable-smb Enable SMB sharing (default: disabled) +# --disable-net Disable network connectivity (default: enabled) + +# Parse command line arguments +WINDOWS_VERSION="11" +CLEAN_CACHE=false +CHECK_DEPS_ONLY=false +DOWNLOAD_ISO_ONLY=false +VM_NAME="windows-11" +VM_RAM="8G" +VM_CPU="6" +VM_SIZE="80G" +ENABLE_SMB=false +DISABLE_NET=false + +# Handle version argument if it's the first argument +if [[ $# -gt 0 && ! $1 =~ ^-- ]]; then + case "$1" in + "10") + WINDOWS_VERSION="10" + VM_NAME="windows-10" + ;; + "server" | "server2022") + WINDOWS_VERSION="server2022" + VM_NAME="windows-server-2022" + ;; + "server2019") + WINDOWS_VERSION="server2019" + VM_NAME="windows-server-2019" + ;; + "server2016") + WINDOWS_VERSION="server2016" + VM_NAME="windows-server-2016" + ;; + *) + echo "Unknown Windows version: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac + shift +fi + +while [[ $# -gt 0 ]]; do + case $1 in + --help) + echo "Windows VM Creation Script" + echo "Usage: $0 [VERSION] [OPTIONS]" + echo + echo "Versions:" + echo " 10 Windows 10" + echo " 11 Windows 11 (default)" + echo " server Windows Server 2022" + echo " server2019 Windows Server 2019" + echo " server2016 Windows Server 2016" + echo + echo "Options:" + echo " --help Show this help message" + echo " --clean Clean dependency cache" + echo " --check-deps Only check dependencies and exit" + echo " --download-iso Download Windows ISO and exit" + echo " --name NAME Set custom VM name" + echo " --ram SIZE Set RAM size (default: 8G)" + echo " --cpu NUM Set number of CPUs (default: 6)" + echo " --disk SIZE Set disk size (default: 80G)" + echo " --enable-smb Enable SMB sharing (default: disabled)" + echo " --disable-net Disable network connectivity (default: enabled)" + exit 0 + ;; + --clean) + CLEAN_CACHE=true + shift + ;; + --check-deps) + CHECK_DEPS_ONLY=true + shift + ;; + --download-iso) + DOWNLOAD_ISO_ONLY=true + shift + ;; + --name) + VM_NAME="$2" + shift 2 + ;; + --ram) + VM_RAM="$2" + shift 2 + ;; + --cpu) + VM_CPU="$2" + shift 2 + ;; + --disk) + VM_SIZE="$2" + shift 2 + ;; + --enable-smb) + ENABLE_SMB=true + shift + ;; + --disable-net) + DISABLE_NET=true + shift + ;; + *) + echo "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac +done + +# Set variables +HOST_DIR="$HOME/virt-new-new" +VM_DIR="$HOST_DIR/machines" +IMAGE_DIR="$HOST_DIR/images" +WIN_ISO_DIR="${IMAGE_DIR}/${VM_NAME}" # Directory for Windows ISO +SOCKET_DIR="$VM_DIR" +SHARED_DIR="${HOST_DIR}/shared" +FIRMWARE_DIR="${HOST_DIR}/firmware" +TPM_DIR="$WIN_ISO_DIR" +TPM_SOCKET="${WIN_ISO_DIR}/${VM_NAME}.swtpm-sock" +GUEST_PORT=22 +QCOW2_FILE="${VM_DIR}/${VM_NAME}.qcow2" + +# Try to find an available host port starting from 22220 +HOST_PORT_START=22220 +HOST_PORT_END=22300 + +for ((port = HOST_PORT_START; port <= HOST_PORT_END; port++)); do + if ! ss -tuln | grep -q ":$port\b"; then + HOST_PORT=$port + echo "Using available port: $HOST_PORT" + break + fi +done + +if [[ $port -gt $HOST_PORT_END ]]; then + echo "Error: No available ports found between $HOST_PORT_START and $HOST_PORT_END" >&2 + exit 1 +fi + +# Set SMP configuration +CORES=$((VM_CPU / 2)) +THREADS_PER_CORE=2 +SOCKETS=1 +SMP_CONFIG="cores=$CORES,threads=$THREADS_PER_CORE,sockets=$SOCKETS" + +# Create necessary directories +mkdir -p "${HOME}/${HOST_DIR}" +mkdir -p "$IMAGE_DIR" "$SHARED_DIR" "$FIRMWARE_DIR" +mkdir -p "$WIN_ISO_DIR" "$VM_DIR" +mkdir -p "${WIN_ISO_DIR}/unattended" + +# Define ISO paths and URLs +ISO_VIRTIO="${WIN_ISO_DIR}/virtio-win.iso" +ISO_UNATTENDED="${WIN_ISO_DIR}/unattended.iso" + +# Find Windows ISO with flexible pattern matching +find_windows_iso() { + # Check if directory exists + if [[ ! -d "$WIN_ISO_DIR" ]]; then + mkdir -p "$WIN_ISO_DIR" + fi + + # Try to find any Windows ISO using case-insensitive patterns + local found_iso + found_iso=$(find "$WIN_ISO_DIR" -maxdepth 1 -type f \( \ + -iname "*win11*.iso" -o \ + -iname "*win*11*.iso" -o \ + -iname "Win*.iso" -o \ + -iname "Win11*.iso" -o \ + -iname "Win*11*.iso" -o \ + -iname "*windows*11*.iso" -o \ + -iname "*windows11*.iso" \ + \) -exec stat --format="%Y %n" {} \; | sort -n | tail -n 1 | cut -d' ' -f2-) + + if [[ -n "$found_iso" && -f "$found_iso" ]]; then + echo "$found_iso" + return 0 + fi + + return 1 +} + +# Define download URLs +VIRTIO_ISO_URL="https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso" +SPICE_WEBDAVD_URL="https://www.spice-space.org/download/windows/spice-webdavd/spice-webdavd-x64-latest.msi" +SPICE_VDAGENT_URL="https://www.spice-space.org/download/windows/spice-vdagent/spice-vdagent-x64-latest.msi" +SPICE_VDAGENT_FALLBACK_URL="https://www.spice-space.org/download/windows/spice-vdagent/spice-vdagent-x64-0.10.0.msi" +USBDK_URL="https://www.spice-space.org/download/windows/usbdk/UsbDk_1.0.22_x64.msi" + +# Fido download URL (Windows ISO downloader) +#FIDO_URL="https://github.com/pbatard/Fido/raw/master/Fido.ps1" +#FIDO_PATH="$WIN_DIR/Fido.ps1" + +# Print colored messages +print_info() { echo -e "\033[1;34m[INFO]\033[0m $1" >&2; } +print_success() { echo -e "\033[1;32m[SUCCESS]\033[0m $1" >&2; } +print_warning() { echo -e "\033[1;33m[WARNING]\033[0m $1" >&2; } +print_error() { echo -e "\033[1;31m[ERROR]\033[0m $1" >&2; } + +# Helper: verify file integrity +verify_file() { + local file="$1" + local expected_sha256="$2" + + if [[ ! -f "$file" ]]; then + return 1 + fi + + if [[ -n "$expected_sha256" ]]; then + local actual_sha256 + actual_sha256=$(sha256sum "$file" | cut -d' ' -f1) + if [[ "$actual_sha256" != "$expected_sha256" ]]; then + print_error "File integrity check failed for $file" + return 1 + fi + fi + + return 0 +} + +# Helper: download file with verification +download_file() { + local url="$1" + local dest="$2" + local expected_sha256="$3" + local allow_failure="$4" + + # Check if file exists and is valid + if [[ -f "$dest" ]]; then + if verify_file "$dest" "$expected_sha256"; then + print_info "File $dest already exists and verified." + return 0 + else + print_warning "File $dest exists but failed verification. Redownloading..." + rm -f "$dest" + fi + fi + + print_info "Downloading $url..." + if ! curl -fL --progress-bar -o "$dest" "$url"; then + print_error "Failed to download $url." + if [[ "$allow_failure" != "true" ]]; then + return 1 + fi + else + # Verify downloaded file + if ! verify_file "$dest" "$expected_sha256"; then + print_error "Downloaded file failed verification" + rm -f "$dest" + return 1 + fi + print_success "Successfully downloaded and verified $dest" + fi + + return 0 +} + +# Download Windows 11 ISO using Microsoft's API +download_windows_iso() { + local windows_version="11" # Default to Windows 11 + local language="English (United States)" # Default language + + # Parse arguments if provided + if [[ -n "$1" ]]; then + windows_version="$1" + fi + + print_info "Attempting to download Windows $windows_version ISO from Microsoft..." + + # Set required variables + local user_agent="Mozilla/5.0 (X11; Linux x86_64; rv:100.0) Gecko/20100101 Firefox/100.0" + local session_id="$(uuidgen)" + local profile="606624d44113" + local url="https://www.microsoft.com/en-us/software-download/windows$windows_version" + + # Add ISO to URL for Windows 10 + case "$windows_version" in + 10) url="${url}ISO" ;; + esac + + # Step 1: Get download page HTML + print_info "Fetching download page: $url" + local iso_download_page_html + iso_download_page_html="$(curl --disable --silent --user-agent "$user_agent" --header "Accept:" --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url")" || { + handle_curl_error $? + print_error "Failed to fetch the download page. Please download Windows $windows_version ISO manually from $url" + return 1 + } + + # Step 2: Extract Product Edition ID + print_info "Getting Product Edition ID..." + local product_edition_id + product_edition_id="$(echo "$iso_download_page_html" | grep -Eo '<option value="[0-9]+">Windows' | cut -d '"' -f 2 | head -n 1 | tr -cd '0-9' | head -c 16)" + + if [[ -z "$product_edition_id" ]]; then + print_error "Failed to extract product edition ID." + print_error "Please download Windows $windows_version ISO manually from $url" + return 1 + fi + + print_success "Product Edition ID: $product_edition_id" + + # Step 3: Register session ID + print_info "Registering session ID: $session_id" + curl --disable --silent --output /dev/null --user-agent "$user_agent" \ + --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 \ + --http1.1 -- "https://vlscppe.microsoft.com/tags?org_id=y6jn8c31&session_id=$session_id" || { + print_error "Failed to register session ID." + return 1 + } + + # Step 4: Get language SKU ID + print_info "Getting language SKU ID..." + local language_skuid_table_json + language_skuid_table_json="$(curl --disable -s --fail --max-filesize 100K --proto =https --tlsv1.2 --http1.1 \ + "https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition?profile=${profile}&ProductEditionId=${product_edition_id}&SKU=undefined&friendlyFileName=undefined&Locale=en-US&sessionID=${session_id}")" || { + handle_curl_error $? + print_error "Failed to get language SKU information." + return 1 + } + + # Extract SKU ID for selected language + local sku_id + + # Try with jq if available (more reliable) + if command -v jq >/dev/null 2>&1; then + sku_id="$(echo "$language_skuid_table_json" | jq -r '.Skus[] | select(.LocalizedLanguage=="'"$language"'" or .Language=="'"$language"'").Id')" + else + # Fallback to grep/cut if jq not available + sku_id="$(echo "$language_skuid_table_json" | grep -o '"Id":"[^"]*","Language":"'"$language"'"' | cut -d'"' -f4)" + + if [[ -z "$sku_id" ]]; then + # Try alternative extraction method + sku_id="$(echo "$language_skuid_table_json" | grep -o '"LocalizedLanguage":"'"$language"'","Id":"[^"]*"' | cut -d'"' -f6)" + fi + fi + + if [[ -z "$sku_id" ]]; then + print_error "Failed to extract SKU ID for $language." + return 1 + fi + + print_success "SKU ID: $sku_id" + + # Step 5: Get ISO download link + print_info "Getting ISO download link..." + local iso_download_link_json + iso_download_link_json="$(curl --disable -s --fail --referer "$url" \ + "https://www.microsoft.com/software-download-connector/api/GetProductDownloadLinksBySku?profile=${profile}&productEditionId=undefined&SKU=${sku_id}&friendlyFileName=undefined&Locale=en-US&sessionID=${session_id}")" + + local failed=0 + + if [[ -z "$iso_download_link_json" ]]; then + print_error "Microsoft servers gave an empty response to the download request." + failed=1 + fi + + if echo "$iso_download_link_json" | grep -q "Sentinel marked this request as rejected."; then + print_error "Microsoft blocked the automated download request based on your IP address." + failed=1 + fi + + if [[ "$failed" -eq 1 ]]; then + print_warning "Please manually download the Windows $windows_version ISO using a web browser from: $url" + print_warning "Save the downloaded ISO to: $WIN_ISO_DIR" + return 1 + fi + + # Extract 64-bit ISO download URL + local iso_download_link + + # Try with jq if available + if command -v jq >/dev/null 2>&1; then + iso_download_link="$(echo "$iso_download_link_json" | jq -r '.ProductDownloadOptions[].Uri' | grep x64 | head -n 1)" + else + # Fallback to grep/cut if jq not available + iso_download_link="$(echo "$iso_download_link_json" | grep -o '"Uri":"[^"]*x64[^"]*"' | cut -d'"' -f4 | head -n 1)" + fi + + if [[ -z "$iso_download_link" ]]; then + print_error "Failed to extract the download link from Microsoft's response." + print_warning "Manually download the Windows $windows_version ISO using a web browser from: $url" + return 1 + fi + + print_success "Got download link: ${iso_download_link%%\?*}" + + # Extract filename from URL + local file_name="$(echo "$iso_download_link" | cut -d'?' -f1 | rev | cut -d'/' -f1 | rev)" + + # If filename couldn't be extracted, use default + if [[ -z "$file_name" || "$file_name" == "$iso_download_link" ]]; then + file_name="windows-$windows_version.iso" + fi + + # Step 6: Download the ISO + print_info "Downloading Windows $windows_version ISO to $WIN_ISO_DIR/$file_name. This may take a while..." + + # Check which download function to use + if type web_get >/dev/null 2>&1; then + web_get "$iso_download_link" "$WIN_ISO_DIR" "$file_name" + else + # Fallback to direct curl download + curl --disable --progress-bar --fail --location --proto '=https' --tlsv1.2 --http1.1 \ + --retry 3 --retry-delay 3 --connect-timeout 30 \ + --output "$WIN_ISO_DIR/$file_name" "$iso_download_link" || { + handle_curl_error $? + return 1 + } + fi + + if [[ $? -ne 0 ]]; then + print_error "Failed to download the Windows $windows_version ISO." + return 1 + fi + + print_success "Successfully downloaded Windows $windows_version ISO to $WIN_ISO_DIR/$file_name" + + # Return the downloaded filename so calling code can use it + echo "$file_name" + return 0 +} + +# Create unattended installation ISO +create_unattended_iso() { + print_info "Creating unattended installation ISO..." + + # Create basic autounattend.xml if it doesn't exist + if [ ! -f "$WIN_ISO_DIR/unattended/autounattend.xml" ]; then + print_info "Creating autounattend.xml..." + cat >"$WIN_ISO_DIR/unattended/autounattend.xml" <<'EOF' +<?xml version="1.0" encoding="utf-8"?> +<unattend xmlns="urn:schemas-microsoft-com:unattend" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"> + <settings pass="windowsPE"> + <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <SetupUILanguage> + <UILanguage>en-US</UILanguage> + </SetupUILanguage> + <InputLocale>0409:00000409</InputLocale> + <SystemLocale>en-US</SystemLocale> + <UILanguage>en-US</UILanguage> + <UserLocale>en-US</UserLocale> + </component> + <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <ImageInstall> + <OSImage> + <Compact>false</Compact> + <InstallTo> + <DiskID>0</DiskID> + <PartitionID>3</PartitionID> + </InstallTo> + </OSImage> + </ImageInstall> + <UserData> + <AcceptEula>true</AcceptEula> + </UserData> + <UseConfigurationSet>false</UseConfigurationSet> + <RunSynchronous> + <RunSynchronousCommand wcm:action="add"> + <Order>1</Order> + <Path>cmd.exe /c ">>"X:\diskpart.txt" (echo SELECT DISK=0&echo CLEAN&echo CONVERT GPT&echo CREATE PARTITION EFI SIZE=300&echo FORMAT QUICK FS=FAT32 LABEL="System"&echo CREATE PARTITION MSR SIZE=16)"</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>2</Order> + <Path>cmd.exe /c ">>"X:\diskpart.txt" (echo CREATE PARTITION PRIMARY&echo SHRINK MINIMUM=1000&echo FORMAT QUICK FS=NTFS LABEL="Windows"&echo CREATE PARTITION PRIMARY&echo FORMAT QUICK FS=NTFS LABEL="Recovery")"</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>3</Order> + <Path>cmd.exe /c ">>"X:\diskpart.txt" (echo SET ID="de94bba4-06d1-4d40-a16a-bfd50179d6ac"&echo GPT ATTRIBUTES=0x8000000000000001)"</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>4</Order> + <Path>cmd.exe /c "diskpart.exe /s "X:\diskpart.txt" >>"X:\diskpart.log" || ( type "X:\diskpart.log" & echo diskpart encountered an error. & pause & exit /b 1 )"</Path> + </RunSynchronousCommand> + </RunSynchronous> + </component> + </settings> + <settings pass="specialize"> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <ComputerName>Win11-VM</ComputerName> + <TimeZone>UTC</TimeZone> + </component> + </settings> + <settings pass="oobeSystem"> + <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <InputLocale>0409:00000409</InputLocale> + <SystemLocale>en-US</SystemLocale> + <UILanguage>en-US</UILanguage> + <UserLocale>en-US</UserLocale> + </component> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <UserAccounts> + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Name>Admin</Name> + <DisplayName></DisplayName> + <Group>Administrators</Group> + <Password> + <Value>password</Value> + <PlainText>true</PlainText> + </Password> + </LocalAccount> + <LocalAccount wcm:action="add"> + <Name>User</Name> + <DisplayName></DisplayName> + <Group>Users</Group> + <Password> + <Value>password</Value> + <PlainText>true</PlainText> + </Password> + </LocalAccount> + </LocalAccounts> + </UserAccounts> + <AutoLogon> + <Username>Admin</Username> + <Enabled>true</Enabled> + <LogonCount>1</LogonCount> + <Password> + <Value>password</Value> + <PlainText>true</PlainText> + </Password> + </AutoLogon> + <OOBE> + <ProtectYourPC>3</ProtectYourPC> + <HideEULAPage>true</HideEULAPage> + <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> + <HideOnlineAccountScreens>true</HideOnlineAccountScreens> + <HideLocalAccountScreen>true</HideLocalAccountScreen> + <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> + <SkipMachineOOBE>true</SkipMachineOOBE> + <SkipUserOOBE>true</SkipUserOOBE> + </OOBE> + </component> + </settings> +</unattend> +EOF + fi + + # Create the unattended ISO + local iso_tool="" + + if command -v genisoimage >/dev/null 2>&1; then + iso_tool="genisoimage" + elif command -v mkisofs >/dev/null 2>&1; then + iso_tool="mkisofs" + elif command -v xorriso >/dev/null 2>&1; then + iso_tool="xorriso" + fi + + if [ "$iso_tool" != "" ]; then + print_info "Creating unattended ISO using $iso_tool..." + + if [ "$iso_tool" = "xorriso" ]; then + xorriso -as genisoimage -J -r -o "$WIN_ISO_DIR/unattended.iso" "$WIN_ISO_DIR/unattended" + else + "$iso_tool" -J -r -o "$WIN_ISO_DIR/unattended.iso" "$WIN_ISO_DIR/unattended" + fi + + return $? + else + print_warning "No ISO creation tool found (genisoimage, mkisofs, or xorriso)" + print_warning "Creating empty ISO file as fallback" + touch "$WIN_ISO_DIR/unattended.iso" + return 1 + fi +} + +# Download or locate essential files +prepare_files() { + # Find Windows ISO + WINDOWS_ISO_PATH=$(find_windows_iso | tail -n 1 | xargs) + local iso_found=false + + # Check if we found a valid ISO + if [[ -n "$WINDOWS_ISO_PATH" && -f "$WINDOWS_ISO_PATH" ]]; then + print_success "Using Windows ISO: $WINDOWS_ISO_PATH" + iso_found=true + else + print_warning "Windows ISO not found, attempting to download..." + if download_windows_iso; then + # Check again after download attempt + WINDOWS_ISO_PATH=$(find_windows_iso | tail -n 1 | xargs) + if [[ -n "$WINDOWS_ISO_PATH" && -f "$WINDOWS_ISO_PATH" ]]; then + print_success "Using downloaded Windows ISO: $WINDOWS_ISO_PATH" + iso_found=true + fi + fi + fi + + # If we still don't have an ISO, exit + if [[ "$iso_found" != "true" ]]; then + print_error "Could not find or download Windows ISO" + print_info "Please place your Windows ISO file in: $WIN_ISO_DIR" + exit 1 + fi + + # Download VirtIO drivers if missing + if [[ ! -f "$ISO_VIRTIO" ]]; then + print_info "VirtIO drivers ISO not found, downloading..." + download_file "$VIRTIO_ISO_URL" "$ISO_VIRTIO" + else + print_success "VirtIO drivers ISO already exists: $ISO_VIRTIO" + fi + + # Define the directory containing the MSI files + UNATTENDED_DIR="$WIN_ISO_DIR/unattended" + mkdir -p "$UNATTENDED_DIR" + + # Find the latest Spice WebDAVD MSI + SPICE_WEBDAVD_MSI=$(find "$UNATTENDED_DIR" -type f -iname "spice-webdavd-x64*.msi" -exec stat --format="%Y %n" {} \; | sort -n | tail -n 1 | cut -d' ' -f2-) + + # Find the latest Spice VD Agent MSI + SPICE_VDAGENT_MSI=$(find "$UNATTENDED_DIR" -type f -iname "spice-vdagent-x64*.msi" -exec stat --format="%Y %n" {} \; | sort -n | tail -n 1 | cut -d' ' -f2-) + + # Find the latest UsbDk MSI + USBDK_MSI=$(find "$UNATTENDED_DIR" -type f -iname "UsbDk_*_x64.msi" -exec stat --format="%Y %n" {} \; | sort -n | tail -n 1 | cut -d' ' -f2-) + + # Spice Guest Tools + UsbDk (only if missing) + if [[ ! -f "$SPICE_WEBDAVD_MSI" ]]; then + print_info "Downloading Spice WebDAVD..." + curl -L -o "$UNATTENDED_DIR/spice-webdavd-x64-latest.msi" "$SPICE_WEBDAVD_URL" || { + print_error "Failed to download Spice WebDAVD" + return 1 + } + else + print_success "Spice WebDAVD MSI already exists: $SPICE_WEBDAVD_MSI" + fi + + if [[ ! -f "$SPICE_VDAGENT_MSI" ]]; then + print_info "Downloading Spice VD Agent..." + # Try multiple mirrors for spice-vdagent + local spice_mirrors=( + "https://www.spice-space.org/download/windows/spice-vdagent/spice-vdagent-x64-latest.msi" + "https://www.spice-space.org/download/windows/spice-vdagent/spice-vdagent-x64-0.10.0.msi" + "https://www.spice-space.org/download/windows/spice-vdagent/spice-vdagent-x64-0.9.0.msi" + "https://www.spice-space.org/download/windows/spice-vdagent/spice-vdagent-x64-0.8.0.msi" + "https://www.spice-space.org/download/windows/spice-vdagent/spice-vdagent-x64-0.7.0.msi" + ) + + local download_success=false + for mirror in "${spice_mirrors[@]}"; do + print_info "Trying mirror: $mirror" + if curl -L -o "$UNATTENDED_DIR/spice-vdagent-x64-latest.msi" "$mirror"; then + # Verify the downloaded file + if [[ -s "$UNATTENDED_DIR/spice-vdagent-x64-latest.msi" ]]; then + download_success=true + break + else + print_warning "Downloaded file is empty, trying next mirror..." + rm -f "$UNATTENDED_DIR/spice-vdagent-x64-latest.msi" + fi + fi + done + + if [[ "$download_success" == "false" ]]; then + print_error "Failed to download Spice VD Agent from all mirrors" + return 1 + fi + else + print_success "Spice VD Agent MSI already exists: $SPICE_VDAGENT_MSI" + fi + + if [[ ! -f "$USBDK_MSI" ]]; then + print_info "Downloading UsbDk..." + curl -L -o "$UNATTENDED_DIR/UsbDk_1.0.22_x64.msi" "$USBDK_URL" || { + print_error "Failed to download UsbDk" + return 1 + } + else + print_success "UsbDk MSI already exists: $USBDK_MSI" + fi + + # Create unattended ISO if it doesn't exist + if [[ ! -f "$ISO_UNATTENDED" ]]; then + create_unattended_iso + else + print_success "Unattended ISO already exists: $ISO_UNATTENDED" + fi +} + +# Locate OVMF firmware files +locate_ovmf() { + OVMF_DIRS=( + "/usr/share/OVMF" + "/usr/share/qemu" + "/usr/lib/qemu" + "/usr/share/edk2" + "/usr/lib/edk2" + "/usr/share/edk2/ovmf" + "/usr/share/edk2-ovmf" + ) + + OVMF_CODE="" + OVMF_VARS="" + + for dir in "${OVMF_DIRS[@]}"; do + [[ -z "$OVMF_CODE" ]] && OVMF_CODE=$(find "$dir" -type f -name "OVMF_CODE.fd" -o -name "edk2-x86_64-code.fd" 2>/dev/null | head -n 1) + [[ -z "$OVMF_VARS" ]] && OVMF_VARS=$(find "$dir" -type f -name "OVMF_VARS.fd" 2>/dev/null | head -n 1) + [[ -n "$OVMF_CODE" && -n "$OVMF_VARS" ]] && break + done + + # Ensure a writable copy of OVMF_VARS.fd + local original_ovmf_vars="$OVMF_VARS" + OVMF_VARS="$FIRMWARE_DIR/OVMF_VARS.fd" + + if [[ ! -f "$OVMF_VARS" && -f "$original_ovmf_vars" ]]; then + print_info "Copying OVMF_VARS.fd to $OVMF_VARS" + cp "$original_ovmf_vars" "$OVMF_VARS" 2>/dev/null || { + print_error "Failed to copy OVMF_VARS.fd!" + exit 1 + } + fi + + # Check if required files exist + if [[ -z "$OVMF_CODE" || ! -f "$OVMF_CODE" ]]; then + print_error "OVMF_CODE.fd not found!" + exit 1 + fi + if [[ ! -f "$OVMF_VARS" ]]; then + print_error "OVMF_VARS.fd not found or could not be copied!" + exit 1 + fi + #} +} + +# Create VM disk image +create_disk() { + # Check if the qcow2 image file exists; if not, create it + if [[ ! -f "$QCOW2_FILE" ]]; then + print_info "Creating $QCOW2_FILE with a size of $VM_SIZE" + qemu-img create -f qcow2 "$QCOW2_FILE" "$VM_SIZE" || { + print_error "Failed to create qcow2 image!" + exit 1 + } + else + print_success "VM disk image already exists: $QCOW2_FILE" + fi +} + +# Generate unique PID file for this instance +generate_pid_file() { + local base_pid_file="$VM_DIR/$VM_NAME.pid" + local pid_file="$base_pid_file" + local counter=1 + + # If the base PID file exists, try to find an available number + while [[ -f "$pid_file" ]]; do + pid_file="${base_pid_file%.pid}-${counter}.pid" + ((counter++)) + done + + echo "$pid_file" +} + +# Start the VM +start_vm() { + # Verify that we have a Windows ISO + if [[ -z "$WINDOWS_ISO_PATH" || ! -f "$WINDOWS_ISO_PATH" ]]; then + print_error "Windows ISO file not found. Cannot start VM." + print_info "Please download Windows 11 ISO and save it to: $WIN_ISO_DIR" + exit 1 + fi + + # Start swtpm + print_info "Starting TPM emulator..." + /sbin/swtpm socket \ + --ctrl type=unixio,path="$TPM_SOCKET" \ + --terminate \ + --tpmstate dir="$TPM_DIR" \ + --tpm2 & + + # Wait for swtpm socket + sleep 1 + + print_info "Starting Windows 11 VM..." + print_info "Using Windows ISO: $WINDOWS_ISO_PATH" + + # Build network options + local network_opts=() + if [[ "$DISABLE_NET" == "true" ]]; then + network_opts=( + "-netdev" "none,id=nic" + "-device" "virtio-net,netdev=nic" + ) + else + if [[ "$ENABLE_SMB" == "true" ]]; then + network_opts=( + "-netdev" "user,id=nic,hostname=$VM_NAME,hostfwd=tcp::$HOST_PORT-:$GUEST_PORT,smb=$SHARED_DIR" + "-device" "virtio-net,netdev=nic" + ) + else + network_opts=( + "-netdev" "user,id=nic,hostname=$VM_NAME,hostfwd=tcp::$HOST_PORT-:$GUEST_PORT" + "-device" "virtio-net,netdev=nic" + ) + fi + fi + + # Run QEMU + /sbin/qemu-system-x86_64 \ + -name "$VM_NAME",process="$VM_NAME" \ + -machine q35,hpet=off,smm=on,vmport=off,accel=kvm \ + -global kvm-pit.lost_tick_policy=discard \ + -global ICH9-LPC.disable_s3=1 \ + -cpu host,+hypervisor,+invtsc,l3-cache=on,migratable=no,hv_passthrough \ + -smp "$SMP_CONFIG" \ + -m "$VM_RAM" \ + -device virtio-balloon \ + -pidfile "$PID_FILE" \ + -rtc base=localtime,clock=host,driftfix=slew \ + -vga none \ + -device virtio-vga-gl,xres=1280,yres=800 \ + -display sdl,gl=on \ + -boot menu=on,splash-time=0,order=d,reboot-timeout=5000 \ + -device virtio-rng-pci,rng=rng0 \ + -object rng-random,id=rng0,filename=/dev/urandom \ + -device qemu-xhci,id=spicepass \ + -chardev spicevmc,id=usbredirchardev1,name=usbredir \ + -device usb-redir,chardev=usbredirchardev1,id=usbredirdev1 \ + -chardev spicevmc,id=usbredirchardev2,name=usbredir \ + -device usb-redir,chardev=usbredirchardev2,id=usbredirdev2 \ + -chardev spicevmc,id=usbredirchardev3,name=usbredir \ + -device usb-redir,chardev=usbredirchardev3,id=usbredirdev3 \ + -device pci-ohci,id=smartpass \ + -device usb-ccid \ + -chardev spicevmc,id=ccid,name=smartcard \ + -device ccid-card-passthru,chardev=ccid \ + -device usb-ehci,id=input \ + -device usb-kbd,bus=input.0 \ + -k en-us \ + -device usb-tablet,bus=input.0 \ + -audiodev pipewire,id=audio0 \ + -device intel-hda \ + -device hda-micro,audiodev=audio0 \ + "${network_opts[@]}" \ + -global driver=cfi.pflash01,property=secure,value=on \ + -drive if=pflash,format=raw,unit=0,file="$OVMF_CODE",readonly=on \ + -drive if=pflash,format=raw,unit=1,file="$OVMF_VARS" \ + -drive media=cdrom,index=1,file="$ISO_UNATTENDED" \ + -drive media=cdrom,index=0,file="$WINDOWS_ISO_PATH" \ + -drive media=cdrom,index=2,file="$ISO_VIRTIO" \ + -device virtio-blk-pci,drive=SystemDisk \ + -drive id=SystemDisk,if=none,format=qcow2,file="$QCOW2_FILE" \ + -chardev socket,id=chrtpm,path="$TPM_SOCKET" \ + -tpmdev emulator,id=tpm0,chardev=chrtpm \ + -device tpm-tis,tpmdev=tpm0 \ + -monitor unix:"$SOCKET_DIR/$VM_NAME-monitor.socket",server,nowait \ + -serial unix:"$SOCKET_DIR/$VM_NAME-serial.socket",server,nowait +} + +# Check dependencies +check_dependencies() { + # Cache file for dependency checks and VM configuration + local cache_name="windows-vm-${VM_NAME}-${VM_RAM}-${VM_CPU}-${VM_SIZE}" + local cache_file="${HOME}/.cache/${cache_name}.cache" + local cache_dir=$(dirname "$cache_file") + local cache_valid=false + local missing_deps=() + local pkg_manager="" + local pkg_install_cmd="" + local privilege_cmd="" + + # Clean cache if requested + if [[ "$CLEAN_CACHE" == "true" ]]; then + print_info "Cleaning dependency cache..." + rm -f "$cache_file" + fi + + # Create cache directory if it doesn't exist + mkdir -p "$cache_dir" + + # Check if cache is valid (less than 24 hours old) + if [[ -f "$cache_file" ]]; then + local cache_age=$(($(date +%s) - $(stat -c %Y "$cache_file"))) + if [[ $cache_age -lt 86400 ]]; then # 24 hours in seconds + # Read cached configuration + local cached_config + cached_config=$(head -n 1 "$cache_file" 2>/dev/null) + # Compare with current configuration + if [[ "$cached_config" == "${VM_NAME}-${VM_RAM}-${VM_CPU}-${VM_SIZE}" ]]; then + cache_valid=true + else + print_info "VM configuration changed, invalidating cache..." + rm -f "$cache_file" + fi + fi + fi + + # Check KVM group membership + if ! groups | grep -q -E 'kvm|qemu'; then + print_warning "User is not a member of kvm or qemu group" + print_info "You may need to add your user to the kvm group:" + print_info "sudo usermod -aG kvm $USER" + fi + + # Detect package manager and privilege command + if command -v emerge >/dev/null 2>&1; then + pkg_manager="emerge" + pkg_install_cmd="emerge --ask --noreplace" + privilege_cmd="sudo" + # Gentoo package mapping + declare -A pkg_map=( + ["qemu-system-x86_64"]="app-emulation/qemu" + ["qemu-img"]="app-emulation/qemu" + ["swtpm"]="app-crypt/swtpm" + ["genisoimage"]="app-cdr/cdrtools" + ["curl"]="net-misc/curl" + ["uuidgen"]="sys-apps/util-linux" + ["jq"]="app-misc/jq" + ["glxinfo"]="x11-apps/mesa-progs" + ["lspci"]="sys-apps/pciutils" + ["ps"]="sys-process/procps" + ["python3"]="dev-lang/python" + ["mkisofs"]="app-cdr/cdrtools" + ["lsusb"]="sys-apps/usbutils" + ["socat"]="net-misc/socat" + ["spicy"]="app-emulation/spice" + ["xrandr"]="x11-apps/xrandr" + ["zsync"]="net-misc/zsync" + ["unzip"]="app-arch/unzip" + ) + elif command -v apt-get >/dev/null 2>&1; then + pkg_manager="apt-get" + pkg_install_cmd="apt-get install -y" + privilege_cmd="sudo" + # Debian/Ubuntu package mapping + declare -A pkg_map=( + ["qemu-system-x86_64"]="qemu-system-x86" + ["qemu-img"]="qemu-utils" + ["swtpm"]="swtpm-tools" + ["genisoimage"]="genisoimage" + ["curl"]="curl" + ["uuidgen"]="uuid-runtime" + ["jq"]="jq" + ["glxinfo"]="mesa-utils" + ["lspci"]="pciutils" + ["ps"]="procps" + ["python3"]="python3" + ["mkisofs"]="genisoimage" + ["lsusb"]="usbutils" + ["socat"]="socat" + ["spicy"]="spice-client-gtk" + ["xrandr"]="x11-xserver-utils" + ["zsync"]="zsync" + ["unzip"]="unzip" + ) + elif command -v dnf >/dev/null 2>&1; then + pkg_manager="dnf" + pkg_install_cmd="dnf install -y" + privilege_cmd="sudo" + # Fedora package mapping + declare -A pkg_map=( + ["qemu-system-x86_64"]="qemu-system-x86" + ["qemu-img"]="qemu-img" + ["swtpm"]="swtpm" + ["genisoimage"]="genisoimage" + ["curl"]="curl" + ["uuidgen"]="util-linux" + ["jq"]="jq" + ["glxinfo"]="mesa-demos" + ["lspci"]="pciutils" + ["ps"]="procps-ng" + ["python3"]="python3" + ["mkisofs"]="genisoimage" + ["lsusb"]="usbutils" + ["socat"]="socat" + ["spicy"]="spice-gtk-tools" + ["xrandr"]="xorg-x11-server-utils" + ["zsync"]="zsync" + ["unzip"]="unzip" + ) + elif command -v pacman >/dev/null 2>&1; then + pkg_manager="pacman" + pkg_install_cmd="pacman -S --noconfirm" + privilege_cmd="sudo" + # Arch package mapping + declare -A pkg_map=( + ["qemu-system-x86_64"]="qemu" + ["qemu-img"]="qemu" + ["swtpm"]="swtpm" + ["genisoimage"]="cdrtools" + ["curl"]="curl" + ["uuidgen"]="util-linux" + ["jq"]="jq" + ["glxinfo"]="mesa-utils" + ["lspci"]="pciutils" + ["ps"]="procps-ng" + ["python3"]="python" + ["mkisofs"]="cdrtools" + ["lsusb"]="usbutils" + ["socat"]="socat" + ["spicy"]="spice-gtk" + ["xrandr"]="xorg-xrandr" + ["zsync"]="zsync" + ["unzip"]="unzip" + ) + fi + + # List of required commands and their package names + local deps=( + "qemu-system-x86_64" + "qemu-img" + "swtpm" + "genisoimage" + "curl" + "uuidgen" + "jq" + "glxinfo" + "lspci" + "ps" + "python3" + "mkisofs" + "lsusb" + "socat" + "spicy" + "xrandr" + "zsync" + "unzip" + ) + + # If cache is valid, read from it + if [[ "$cache_valid" == "true" ]]; then + print_info "Using cached dependency information..." + # Skip the first line (configuration) and read dependencies + tail -n +2 "$cache_file" | while IFS= read -r dep; do + if ! command -v "$dep" >/dev/null 2>&1; then + missing_deps+=("$dep") + fi + done + else + # Check each dependency and cache the results + print_info "Checking dependencies..." + + # Clear cache file and write current configuration + echo "${VM_NAME}-${VM_RAM}-${VM_CPU}-${VM_SIZE}" >"$cache_file" + + for dep in "${deps[@]}"; do + # Special case for genisoimage/mkisofs + if [[ "$dep" == "genisoimage" ]]; then + if ! command -v genisoimage >/dev/null 2>&1 && ! command -v mkisofs >/dev/null 2>&1; then + missing_deps+=("$dep") + echo "$dep" >>"$cache_file" + fi + # Special case for xdg-user-dirs + elif [[ "$dep" == "xdg-user-dirs" ]]; then + if ! pkg-config --exists xdg-user-dirs; then + missing_deps+=("$dep") + echo "$dep" >>"$cache_file" + fi + # Normal case for other dependencies + elif ! command -v "$dep" >/dev/null 2>&1; then + missing_deps+=("$dep") + echo "$dep" >>"$cache_file" + fi + done + fi + + if [[ ${#missing_deps[@]} -gt 0 ]]; then + print_warning "Missing dependencies:" + for dep in "${missing_deps[@]}"; do + if [[ -n "${pkg_map[$dep]}" ]]; then + print_warning "- $dep (package: ${pkg_map[$dep]})" + else + print_warning "- $dep" + fi + done + + if [[ -n "$pkg_manager" ]]; then + print_info "Detected package manager: $pkg_manager" + read -p "Would you like to install the missing dependencies? (y/N) " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + # Check for privilege command + if ! command -v "$privilege_cmd" >/dev/null 2>&1; then + print_error "Privilege command ($privilege_cmd) not found" + print_info "Please install the missing packages manually" + else + # Install missing packages + print_info "Installing missing dependencies..." + local pkgs_to_install=() + for dep in "${missing_deps[@]}"; do + if [[ -n "${pkg_map[$dep]}" ]]; then + pkgs_to_install+=("${pkg_map[$dep]}") + fi + done + "$privilege_cmd" "$pkg_install_cmd" "${pkgs_to_install[@]}" + + # Clear cache after installation + rm -f "$cache_file" + fi + else + print_warning "Continuing without installing dependencies. Some features may not work correctly." + fi + else + print_warning "No supported package manager found" + print_info "Please install the missing packages manually" + fi + else + print_success "All required dependencies are installed" + fi +} + +# Main execution +check_dependencies + +if [[ "$CHECK_DEPS_ONLY" == "true" ]]; then + exit 0 +fi + +if [[ "$DOWNLOAD_ISO_ONLY" == "true" ]]; then + download_windows_iso "$WINDOWS_VERSION" + exit 0 +fi + +# Generate unique PID file for this instance +PID_FILE=$(generate_pid_file) +print_info "Using PID file: $PID_FILE" + +prepare_files +locate_ovmf +create_disk +start_vm |
