diff --git a/custom.xml b/custom.xml
index d630612..de1a76b 100644
--- a/custom.xml
+++ b/custom.xml
@@ -465,6 +465,11 @@
25
+ reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v "IPAutoconfigurationEnabled" /t REG_DWORD /d 0 /f
+ Disable ip autoconfiguration for network interfaces
+
+
+ 26
cmd /C "type nul > \\host.lan\Data\prepared"
Let host known that all configuration is done
diff --git a/scripts/enable_sshd.ps1 b/scripts/enable_sshd.ps1
new file mode 100644
index 0000000..635d775
--- /dev/null
+++ b/scripts/enable_sshd.ps1
@@ -0,0 +1,62 @@
+# Define variables
+$OpenSSH_URL = "https://github.com/PowerShell/Win32-OpenSSH/releases/latest/download/OpenSSH-Win64.zip"
+$OpenSSH_Install_Path = "C:\Program Files\OpenSSH"
+$OpenSSH_Zip = "$env:TEMP\OpenSSH-Win64.zip"
+
+# Function to check if running as Administrator
+function Test-Admin {
+ $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
+ $currentPrincipal = New-Object Security.Principal.WindowsPrincipal($currentUser)
+ return $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+}
+
+if (-not (Test-Admin)) {
+ Write-Host "Please run this script as Administrator!" -ForegroundColor Red
+ exit 1
+}
+
+# Ensure the install path exists
+if (!(Test-Path $OpenSSH_Install_Path)) {
+ New-Item -ItemType Directory -Path $OpenSSH_Install_Path -Force
+}
+
+# Download OpenSSH if not already present
+Write-Host "Downloading OpenSSH..." -ForegroundColor Cyan
+Invoke-WebRequest -Uri $OpenSSH_URL -OutFile $OpenSSH_Zip
+
+# Extract OpenSSH
+Write-Host "Extracting OpenSSH..." -ForegroundColor Cyan
+Expand-Archive -Path $OpenSSH_Zip -DestinationPath $OpenSSH_Install_Path -Force
+
+# Check if install-sshd.ps1 exists
+if (!(Test-Path "$OpenSSH_Install_Path\OpenSSH-Win64\install-sshd.ps1")) {
+ Write-Host "❌ Error: install-sshd.ps1 not found in $OpenSSH_Install_Path. Extraction failed!" -ForegroundColor Red
+ exit 1
+}
+
+# Navigate to OpenSSH directory
+Push-Location -Path $OpenSSH_Install_Path\OpenSSH-Win64
+
+# Run install script
+Write-Host "Installing OpenSSH service..." -ForegroundColor Green
+powershell.exe -ExecutionPolicy Bypass -File install-sshd.ps1
+
+# Set SSHD service to start automatically
+Write-Host "Setting SSHD to start automatically..." -ForegroundColor Green
+if (Get-Service sshd -ErrorAction SilentlyContinue) {
+ Set-Service -Name sshd -StartupType Automatic
+ Start-Service sshd
+} else {
+ Write-Host "⚠ OpenSSH service was not installed correctly. Try running install-sshd.ps1 manually." -ForegroundColor Red
+ exit 1
+}
+
+# Verify installation
+$sshdStatus = Get-Service -Name sshd -ErrorAction SilentlyContinue
+if ($sshdStatus.Status -eq 'Running') {
+ Write-Host "✅ OpenSSH installation successful! You can now connect via SSH." -ForegroundColor Green
+} else {
+ Write-Host "⚠ OpenSSH installation failed. Try restarting your computer and rerun the script." -ForegroundColor Red
+}
+
+Pop-Location
diff --git a/scripts/install.bat b/scripts/install.bat
index cd0a3e2..ebc720c 100644
--- a/scripts/install.bat
+++ b/scripts/install.bat
@@ -3,5 +3,6 @@ pushd "C:/OEM"
powershell -ExecutionPolicy Bypass -File "dependencies_windows.ps1"
powershell -ExecutionPolicy Bypass -File "optimize.ps1"
powershell -ExecutionPolicy Bypass -File "disable_updates.ps1"
+powershell -ExecutionPolicy Bypass -File "enable_sshd.ps1"
popd
diff --git a/src/entry.sh b/src/entry.sh
index 6574d91..4de2f59 100755
--- a/src/entry.sh
+++ b/src/entry.sh
@@ -36,16 +36,38 @@ terminal
(
sleep 30
boot
- configure_guest_network_interface
- info "Windows started succesfully, you can now connect using RDP"
- if [[ "${NETWORK,,}" != "bridge"* ]]; then
- info "or visit http://localhost:8006/ to view the screen..."
+
+ if ! configure_guest_network_interface; then
+ error "Failed to configure guest network interfaces"
+ exit 666
fi
+
+ if [[ -n "${EXTRA_SCRIPT:-}" ]]; then
+ info "Executing extra script: $EXTRA_SCRIPT"
+ if ! "$EXTRA_SCRIPT"; then
+ error "Extra script failed"
+ exit 555
+ fi
+ fi
+
+ info "Windows started successfully, you can now connect using RDP or visit http://localhost:8006/ to view the screen..."
touch "$STORAGE/ready"
) &
+bg_pid=$!
+
tail -fn +0 "$QEMU_LOG" 2>/dev/null &
cat "$QEMU_TERM" 2>/dev/null | tee "$QEMU_PTY" &
-wait $! || :
+term_pd=$!
+
+wait $bg_pid
+exit_code=$?
+
+if [[ $exit_code -ne 0 ]]; then
+ error "A critical process failed, exiting container..."
+ exit $exit_code
+fi
+
+wait $term_pd || :
sleep 1 &
wait $!
diff --git a/src/network.sh b/src/network.sh
index 17d7f0e..60dbcf2 100755
--- a/src/network.sh
+++ b/src/network.sh
@@ -58,7 +58,7 @@ configure_guest_network_interface() {
if [ -z "$CURRENT_IP" ]; then
echo "Error: Unable to retrieve the current IP address of $HOST_INTERFACE."
- exit 1
+ return 1
fi
echo "Current Host IP: $CURRENT_IP"
@@ -84,10 +84,33 @@ configure_guest_network_interface() {
INTERFACE_NAME="Ethernet $IDX"
fi
- echo -e '{"execute": "guest-exec", "arguments": {"path": "C:\\\\Windows\\\\System32\\\\netsh.exe", "capture-output": true, "arg": ["interface", "ipv4", "set", "address", "'"$INTERFACE_NAME"'", "static", "'$CURRENT_IP'", "255.255.255.0", "'$GW'"]}}' | nc -U /tmp/qga.sock -w 5
- echo -e '{"execute": "guest-exec", "arguments": {"path": "C:\\\\Windows\\\\System32\\\\netsh.exe", "capture-output": true, "arg": ["interface", "ipv4", "add", "dnsservers", "'"$INTERFACE_NAME"'", "1.1.1.1", "index=1"]}}' | nc -U /tmp/qga.sock -w 5
+ exit_code=0
+ python3 /run/qga.py powershell -Command "Set-NetIPInterface -InterfaceAlias '$INTERFACE_NAME' -Dhcp Disabled" || exit_code=$?
+ if [[ $exit_code -ne 0 ]]; then
+ echo "Failed to disable dhcp using qga.py" >&2
+ return 2
+ fi
+
+ if [[ -f "$STORAGE/interfaces_configured" ]]; then
+ python3 /run/qga.py powershell -Command "Remove-NetIPAddress -IPAddress '$CURRENT_IP' -Confirm:\$false" || true
+ python3 /run/qga.py powershell -Command "Remove-NetRoute -InterfaceAlias '$INTERFACE_NAME' -DestinationPrefix '0.0.0.0/0' -Confirm:\$false" || true
+ fi
+
+ python3 /run/qga.py powershell -Command "New-NetIPAddress -InterfaceAlias '$INTERFACE_NAME' -IPAddress '$CURRENT_IP' -PrefixLength 24 -DefaultGateway '$GW'" || exit_code=$?
+ if [[ $exit_code -ne 0 ]]; then
+ echo "Failed to set ip address using qga.py" >&2
+ return 3
+ fi
+
+ python3 /run/qga.py powershell -Command "Set-DnsClientServerAddress -InterfaceAlias '$INTERFACE_NAME' -ServerAddresses 1.1.1.1" || exit_code=$?
+ if [[ $exit_code -ne 0 ]]; then
+ echo "Failed to set dns server using qga.py" >&2
+ return 4
+ fi
+
done
+ touch "$STORAGE/interfaces_configured"
fi
return 0
@@ -465,7 +488,10 @@ closeNetwork() {
[[ "$NETWORK" == [Nn]* ]] && return 0
exec 30<&- || true
- exec 40<&- || true
+ for ((i = 0; i < ETH_COUNT; i++)); do
+ fd=$((40 + i))
+ eval "exec $fd<&-" || true
+ done
if [[ "$DHCP" == [Yy1]* ]]; then
diff --git a/src/qga.py b/src/qga.py
new file mode 100644
index 0000000..c57f017
--- /dev/null
+++ b/src/qga.py
@@ -0,0 +1,138 @@
+import argparse
+import base64
+import json
+import socket
+import sys
+import time
+
+QGA_SOCKET = "/tmp/qga.sock" # Adjust if needed
+
+
+def send_qga_command(sock, command):
+ """Send a JSON command to the QEMU Guest Agent socket and receive the response."""
+ try:
+ cmd = (json.dumps(command) + "\n").encode()
+ sock.sendall(cmd)
+ response = sock.recv(4096)
+ return json.loads(response.decode())
+ except socket.timeout:
+ print(f"Timeout waiting for response from {QGA_SOCKET}", file=sys.stderr)
+ return None
+ except Exception as e:
+ print(f"Error communicating with socket: {e}", file=sys.stderr)
+ return None
+
+
+def decode_output(data):
+ """Try to decode output as hex or Base64, or return raw."""
+ if not data:
+ return ""
+
+ try:
+ # Try Hex decoding first
+ return bytes.fromhex(data).decode("utf-8", errors="ignore")
+ except ValueError:
+ pass
+
+ try:
+ # If hex fails, try Base64 decoding
+ return base64.b64decode(data).decode("utf-8", errors="ignore")
+ except ValueError:
+ pass
+
+ # If all decoding fails, return raw
+ return data
+
+
+def execute_command(sock, command_path, command_args):
+ """Execute a command inside the guest VM with specified path and arguments."""
+ exec_request = {
+ "execute": "guest-exec",
+ "arguments": {
+ "path": command_path,
+ "arg": command_args,
+ "capture-output": True, # Capture stdout and stderr
+ },
+ }
+ response = send_qga_command(sock, exec_request)
+
+ if response is None:
+ return None
+
+ if "return" not in response or "pid" not in response["return"]:
+ print("Error: Failed to start execution:", response, file=sys.stderr)
+ return None
+
+ pid = response["return"]["pid"]
+ print(f"Command started with PID {pid}")
+
+ # Step 2: Wait for completion
+ while True:
+ status_request = {"execute": "guest-exec-status", "arguments": {"pid": pid}}
+ status_response = send_qga_command(sock, status_request)
+
+ if status_response is None:
+ continue
+
+ if "return" in status_response:
+ status = status_response["return"]
+ if status.get("exited", False):
+ break # Command finished
+ time.sleep(0.2) # Wait before checking again
+
+ # Step 3: Get exit code and output
+ exit_code = status.get("exitcode", -1)
+ stdout_data = decode_output(status.get("out-data", ""))
+ stderr_data = decode_output(status.get("err-data", ""))
+
+ return {"exit_code": exit_code, "stdout": stdout_data, "stderr": stderr_data}
+
+
+def create_socket():
+ """Create and return a reusable socket connection to the QEMU Guest Agent."""
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ sock.settimeout(30) # 30 seconds timeout
+ try:
+ sock.connect(QGA_SOCKET)
+ return sock
+ except Exception as e:
+ print(f"Error creating socket: {e}", file=sys.stderr)
+ return None
+
+
+def parse_args():
+ """Parse command-line arguments."""
+ parser = argparse.ArgumentParser(description="Send commands to QEMU Guest Agent.")
+ parser.add_argument(
+ "command", help="Path to the command to execute inside the guest VM"
+ )
+ parser.add_argument(
+ "args", nargs=argparse.REMAINDER, help="Arguments to pass to the command"
+ )
+ return parser.parse_args()
+
+
+if __name__ == "__main__":
+ # Parse command-line arguments
+ args = parse_args()
+
+ # Create a reusable socket
+ unix_sock = create_socket()
+ if not unix_sock:
+ print("Failed to create socket.", file=sys.stderr)
+ sys.exit(1) # Exit if we can't connect to the socket
+
+ # Execute the command
+ result = execute_command(unix_sock, args.command, args.args)
+ if result:
+ print(f"Exit Code: {result['exit_code']}")
+ if result["stdout"]:
+ print("STDOUT:\n", result["stdout"])
+ if result["stderr"]:
+ print("STDERR:\n", result["stderr"])
+
+ # Close the socket once all commands are executed
+ unix_sock.close()
+
+ # Exit with the appropriate code based on command execution result
+ sys.exit(result["exit_code"] if result else 2)