aboutsummaryrefslogtreecommitdiff
path: root/common/scripts/virt
diff options
context:
space:
mode:
Diffstat (limited to 'common/scripts/virt')
-rwxr-xr-xcommon/scripts/virt/checksum.sh36
-rwxr-xr-xcommon/scripts/virt/dos.sh998
-rwxr-xr-xcommon/scripts/virt/server.sh128
-rwxr-xr-xcommon/scripts/virt/ubuntu222
-rwxr-xr-xcommon/scripts/virt/windows.sh1156
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 "&gt;&gt;"X:\diskpart.txt" (echo SELECT DISK=0&amp;echo CLEAN&amp;echo CONVERT GPT&amp;echo CREATE PARTITION EFI SIZE=300&amp;echo FORMAT QUICK FS=FAT32 LABEL="System"&amp;echo CREATE PARTITION MSR SIZE=16)"</Path>
+ </RunSynchronousCommand>
+ <RunSynchronousCommand wcm:action="add">
+ <Order>2</Order>
+ <Path>cmd.exe /c "&gt;&gt;"X:\diskpart.txt" (echo CREATE PARTITION PRIMARY&amp;echo SHRINK MINIMUM=1000&amp;echo FORMAT QUICK FS=NTFS LABEL="Windows"&amp;echo CREATE PARTITION PRIMARY&amp;echo FORMAT QUICK FS=NTFS LABEL="Recovery")"</Path>
+ </RunSynchronousCommand>
+ <RunSynchronousCommand wcm:action="add">
+ <Order>3</Order>
+ <Path>cmd.exe /c "&gt;&gt;"X:\diskpart.txt" (echo SET ID="de94bba4-06d1-4d40-a16a-bfd50179d6ac"&amp;echo GPT ATTRIBUTES=0x8000000000000001)"</Path>
+ </RunSynchronousCommand>
+ <RunSynchronousCommand wcm:action="add">
+ <Order>4</Order>
+ <Path>cmd.exe /c "diskpart.exe /s "X:\diskpart.txt" &gt;&gt;"X:\diskpart.log" || ( type "X:\diskpart.log" &amp; echo diskpart encountered an error. &amp; pause &amp; 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