From e51980dc0c4b5985aa7d066231908fb7d8cdbb1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gytis=20Sto=C5=A1kevi=C4=8Dius?= Date: Thu, 20 Feb 2025 14:55:14 +0200 Subject: [PATCH 01/17] add network script --- src/network.sh | 470 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 470 insertions(+) create mode 100644 src/network.sh diff --git a/src/network.sh b/src/network.sh new file mode 100644 index 0000000..166b877 --- /dev/null +++ b/src/network.sh @@ -0,0 +1,470 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +# Docker environment variables + +: "${MAC:=""}" +: "${MAC_2:=""}" +: "${DHCP:="N"}" +: "${NETWORK:="Y"}" +: "${USER_PORTS:=""}" +: "${HOST_PORTS:=""}" +: "${ADAPTER:="virtio-net-pci"}" +: "${ADAPTER_2:="virtio-net-pci"}" + +: "${VM_NET_DEV:="eth0"}" +: "${VM_NET_DEV_2:="eth1"}" +: "${VM_NET_TAP:="qemu"}" +: "${VM_NET_TAP_2:="qemu_2"}" +: "${VM_NET_MAC:="$MAC"}" +: "${VM_NET_MAC_2:="$MAC_2"}" +: "${VM_NET_HOST:="QEMU"}" +: "${VM_NET_HOST_2:="QEMU_2"}" +: "${VM_NET_IP:="20.20.20.21"}" +: "${VM_NET_IP_2:="20.20.21.21"}" + +: "${DNSMASQ_OPTS:=""}" +: "${DNSMASQ:="/usr/sbin/dnsmasq"}" +: "${DNSMASQ_CONF_DIR:="/etc/dnsmasq.d"}" + +ADD_ERR="Please add the following setting to your container:" + +# ###################################### +# Functions +# ###################################### + +configureDNS() { + # Create lease file for faster resolve + echo "0 $VM_NET_MAC $VM_NET_IP $VM_NET_HOST 01:$VM_NET_MAC" >/var/lib/misc/dnsmasq.leases + echo "0 $VM_NET_MAC_2 $VM_NET_IP_2 $VM_NET_HOST_2 01:$VM_NET_MAC_2" >>/var/lib/misc/dnsmasq.leases + chmod 644 /var/lib/misc/dnsmasq.leases + + # dnsmasq configuration: + # eth0 - Provides both DNS and Default Gateway + DNSMASQ_OPTS+=" --dhcp-range=$VM_NET_IP,$VM_NET_IP" + DNSMASQ_OPTS+=" --dhcp-host=$VM_NET_MAC,,$VM_NET_IP,$VM_NET_HOST,infinite" + DNSMASQ_OPTS+=" --dhcp-option=option:netmask,255.255.255.0" + DNSMASQ_OPTS+=" --dhcp-option=option:dns-server,${VM_NET_IP%.*}.1" + DNSMASQ_OPTS+=" --address=/host.lan/${VM_NET_IP%.*}.1" + + # eth1 - Provides only DNS, no default gateway + DNSMASQ_OPTS+=" --dhcp-range=$VM_NET_IP_2,$VM_NET_IP_2" + DNSMASQ_OPTS+=" --dhcp-host=$VM_NET_MAC_2,,$VM_NET_IP_2,$VM_NET_HOST_2,infinite" + DNSMASQ_OPTS+=" --dhcp-option=option:netmask,255.255.255.0" + DNSMASQ_OPTS+=" --dhcp-option=option:dns-server,${VM_NET_IP_2%.*}.1" + DNSMASQ_OPTS+=" --address=/host.lan/${VM_NET_IP_2%.*}.1" + + # Cleanup and start dnsmasq + DNSMASQ_OPTS=$(echo "$DNSMASQ_OPTS" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//') + + if ! $DNSMASQ ${DNSMASQ_OPTS:+ $DNSMASQ_OPTS}; then + error "Failed to start dnsmasq, reason: $?" && return 1 + fi + + return 0 +} + +getUserPorts() { + + local args="" + local list=$1 + local ssh="22" + local rdp="3389" + + [ -z "$list" ] && list="$ssh,$rdp" || list+=",$ssh,$rdp" + + list="${list//,/ }" + list="${list## }" + list="${list%% }" + + for port in $list; do + args+="hostfwd=tcp::$port-$VM_NET_IP:$port," + done + + echo "${args%?}" + return 0 +} + +getHostPorts() { + + local list=$1 + local vnc="5900" + local web="8006" + + [ -z "$list" ] && list="$web" || list+=",$web" + + if [[ "${DISPLAY,,}" == "vnc" ]] || [[ "${DISPLAY,,}" == "web" ]]; then + [ -z "$list" ] && list="$vnc" || list+=",$vnc" + fi + + [ -z "$list" ] && echo "" && return 0 + + if [[ "$list" != *","* ]]; then + echo " ! --dport $list" + else + echo " -m multiport ! --dports $list" + fi + + return 0 +} + +configureUser() { + + NET_OPTS="-netdev user,id=hostnet0,host=${VM_NET_IP%.*}.1,net=${VM_NET_IP%.*}.0/24,dhcpstart=$VM_NET_IP,hostname=$VM_NET_HOST" + + local forward + forward=$(getUserPorts "$USER_PORTS") + [ -n "$forward" ] && NET_OPTS+=",$forward" + + NET_OPTS+=" -netdev user,id=hostnet1,host=${VM_NET_IP_2%.*}.1,net=${VM_NET_IP_2%.*}.0/24,dhcpstart=$VM_NET_IP_2,hostname=$VM_NET_HOST_2" + [ -n "$forward" ] && NET_OPTS+=",$forward" + + return 0 +} + +configureNAT() { + + local tuntap="TUN device is missing. $ADD_ERR --device /dev/net/tun" + local tables="The 'ip_tables' kernel module is not loaded. Try this command: sudo modprobe ip_tables iptable_nat" + + # Create the necessary file structure for /dev/net/tun + if [ ! -c /dev/net/tun ]; then + [ ! -d /dev/net ] && mkdir -m 755 /dev/net + if mknod /dev/net/tun c 10 200; then + chmod 666 /dev/net/tun + fi + fi + + if [ ! -c /dev/net/tun ]; then + error "$tuntap" && return 1 + fi + + # Check port forwarding flag + if [[ $(/dev/null + rc=$? + } || : + if ((rc != 0)) || [[ $(/dev/null + update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy >/dev/null + + exclude=$(getHostPorts "$HOST_PORTS") + + if ! iptables -t nat -A POSTROUTING -o "$VM_NET_DEV" -j MASQUERADE; then + error "$tables" && return 1 + fi + + # shellcheck disable=SC2086 + if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p tcp${exclude} -j DNAT --to "$VM_NET_IP"; then + error "Failed to configure IP tables!" && return 1 + fi + + if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV" -d "$IP" -p udp -j DNAT --to "$VM_NET_IP"; then + error "Failed to configure IP tables!" && return 1 + fi + + if ! iptables -t nat -A POSTROUTING -o "$VM_NET_DEV_2" -j MASQUERADE; then + error "$tables" && return 1 + fi + + # shellcheck disable=SC2086 + if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV_2" -d "$IP_2" -p tcp${exclude} -j DNAT --to "$VM_NET_IP_2"; then + error "Failed to configure IP tables!" && return 1 + fi + + if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV_2" -d "$IP_2" -p udp -j DNAT --to "$VM_NET_IP_2"; then + error "Failed to configure IP tables!" && return 1 + fi + + if ((KERNEL > 4)); then + # Hack for guest VMs complaining about "bad udp checksums in 5 packets" + iptables -A POSTROUTING -t mangle -p udp --dport bootpc -j CHECKSUM --checksum-fill >/dev/null 2>&1 || true + fi + + NET_OPTS="-netdev tap,id=hostnet0,ifname=$VM_NET_TAP" + + if [ -c /dev/vhost-net ]; then + { + exec 40>>/dev/vhost-net + rc=$? + } 2>/dev/null || : + ((rc == 0)) && NET_OPTS+=",vhost=on,vhostfd=40" + fi + + NET_OPTS+=",script=no,downscript=no" + + NET_OPTS+=" -netdev tap,id=hostnet1,ifname=$VM_NET_TAP_2" + + if [ -c /dev/vhost-net ]; then + { + exec 41>>/dev/vhost-net + rc=$? + } 2>/dev/null || : + ((rc == 0)) && NET_OPTS+=",vhost=on,vhostfd=41" + fi + + NET_OPTS+=",script=no,downscript=no" + + configureDNS || return 1 + + return 0 +} + +closeNetwork() { + + # Shutdown nginx + nginx -s stop 2>/dev/null + fWait "nginx" + + [[ "$NETWORK" == [Nn]* ]] && return 0 + + exec 40<&- || true + exec 41<&- || true + + if [[ "$DHCP" == [Yy1]* ]]; then + + ip link set "$VM_NET_TAP" down || true + ip link delete "$VM_NET_TAP" || true + ip link set "$VM_NET_TAP_2" down || true + ip link delete "$VM_NET_TAP_2" || true + + else + + local pid="/var/run/dnsmasq.pid" + [ -s "$pid" ] && pKill "$(<"$pid")" + + [[ "${NETWORK,,}" == "user"* ]] && return 0 + + ip link set "$VM_NET_TAP" down promisc off || true + ip link delete "$VM_NET_TAP" || true + ip link set "$VM_NET_TAP_2" down promisc off || true + ip link delete "$VM_NET_TAP_2" || true + + ip link set dockerbridge down || true + ip link delete dockerbridge || true + ip link set dockerbridge_2 down || true + ip link delete dockerbridge_2 || true + + fi + + return 0 +} + +checkOS() { + + local name + local os="" + local if="macvlan" + name=$(uname -a) + + [[ "${name,,}" == *"darwin"* ]] && os="Docker Desktop for macOS" + [[ "${name,,}" == *"microsoft"* ]] && os="Docker Desktop for Windows" + + if [[ "$DHCP" == [Yy1]* ]]; then + if="macvtap" + [[ "${name,,}" == *"synology"* ]] && os="Synology Container Manager" + fi + + if [ -n "$os" ]; then + warn "you are using $os which does not support $if, please revert to bridge networking!" + fi + + return 0 +} + +getInfo() { + + if [ ! -d "/sys/class/net/$VM_NET_DEV" ]; then + error "Network interface '$VM_NET_DEV' does not exist inside the container!" + error "$ADD_ERR -e \"VM_NET_DEV=NAME\" to specify another interface name." && exit 27 + fi + + if [ ! -d "/sys/class/net/$VM_NET_DEV_2" ]; then + error "Network interface '$VM_NET_DEV_2' does not exist inside the container!" + error "$ADD_ERR -e \"VM_NET_DEV_2=NAME\" to specify another interface name." && exit 27 + fi + + if [ -z "$MAC" ]; then + local file="$STORAGE/$PROCESS.mac" + if [ -z "$MAC" ]; then + # Generate MAC address based on Docker container ID in hostname + MAC=$(printf '02:%02x:%02x:%02x:%02x:%02x\n' \ + $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256))) + echo "${MAC^^}" >"$file" + fi + fi + + VM_NET_MAC="${MAC^^}" + VM_NET_MAC="${VM_NET_MAC//-/:}" + + if [[ ${#VM_NET_MAC} == 12 ]]; then + m="$VM_NET_MAC" + VM_NET_MAC="${m:0:2}:${m:2:2}:${m:4:2}:${m:6:2}:${m:8:2}:${m:10:2}" + fi + + if [[ ${#VM_NET_MAC} != 17 ]]; then + error "Invalid MAC address: '$VM_NET_MAC', should be 12 or 17 digits long!" && exit 28 + fi + + if [ -z "$MAC_2" ]; then + local file="$STORAGE/$PROCESS.mac" + if [ -z "$MAC_2" ]; then + # Generate MAC address based on Docker container ID in hostname + MAC_2=$(printf '02:%02x:%02x:%02x:%02x:%02x\n' \ + $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256))) + echo "${MAC_2^^}" >>"$file" + fi + fi + + VM_NET_MAC_2="${MAC_2^^}" + VM_NET_MAC_2="${VM_NET_MAC_2//-/:}" + + if [[ ${#VM_NET_MAC_2} == 12 ]]; then + m="$VM_NET_MAC_2" + VM_NET_MAC_2="${m:0:2}:${m:2:2}:${m:4:2}:${m:6:2}:${m:8:2}:${m:10:2}" + fi + + if [[ ${#VM_NET_MAC_2} != 17 ]]; then + error "Invalid MAC address: '$VM_NET_MAC_2', should be 12 or 17 digits long!" && exit 28 + fi + + GATEWAY=$(ip route list dev "$VM_NET_DEV" | awk ' /^default/ {print $3}') + IP=$(ip address show dev "$VM_NET_DEV" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/) + + GATEWAY_2=$(ip route list dev "$VM_NET_DEV_2" | awk ' /^default/ {print $3}') + IP_2=$(ip address show dev "$VM_NET_DEV_2" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/) + + return 0 +} + +# ###################################### +# Configure Network +# ###################################### + +if [[ "$NETWORK" == [Nn]* ]]; then + NET_OPTS="" + return 0 +fi + +getInfo +html "Initializing network..." + +if [[ "$DEBUG" == [Yy1]* ]]; then + info "Host: $HOST IP: $IP Gateway: $GATEWAY Interface: $VM_NET_DEV MAC: $VM_NET_MAC" + [ -f /etc/resolv.conf ] && grep '^nameserver*' /etc/resolv.conf + echo +fi +if [[ "$DEBUG" == [Yy1]* ]]; then + info "Host: $HOST IP: $IP_2 Gateway: $GATEWAY_2 Interface: $VM_NET_DEV_2 MAC: $VM_NET_MAC_2" + [ -f /etc/resolv.conf ] && grep '^nameserver*' /etc/resolv.conf + echo +fi + +if [[ "$IP" != "172."* ]] && [[ "$IP" != "10.8"* ]] && [[ "$IP" != "10.9"* ]]; then + checkOS +fi + +if [[ "${NETWORK,,}" != "user"* ]]; then + + # Configure for tap interface + if ! configureNAT; then + + NETWORK="user" + warn "falling back to usermode networking! Performance will be bad and port mapping will not work." + + ip link set "$VM_NET_TAP" down promisc off &>null || true + ip link delete "$VM_NET_TAP" &>null || true + ip link set "$VM_NET_TAP_2" down promisc off &>null || true + ip link delete "$VM_NET_TAP_2" &>null || true + + ip link set dockerbridge down &>null || true + ip link delete dockerbridge &>null || true + ip link set dockerbridge_2 down &>null || true + ip link delete dockerbridge_2 &>null || true + + fi + +fi + +if [[ "${NETWORK,,}" == "user"* ]]; then + + # Configure for usermode networking (slirp) + configureUser || exit 24 + +fi + +NET_OPTS+=" -device $ADAPTER,romfile=,netdev=hostnet0,mac=$VM_NET_MAC,id=net0" +NET_OPTS+=" -device $ADAPTER_2,romfile=,netdev=hostnet1,mac=$VM_NET_MAC_2,id=net1" + +html "Initialized network successfully..." +return 0 From 941ebd6adfe4713b7590b2f6f9676ca286caacf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gytis=20Sto=C5=A1kevi=C4=8Dius?= Date: Tue, 25 Feb 2025 13:04:48 +0200 Subject: [PATCH 02/17] add deps and ready check --- compose.yml | 15 + custom.xml | 470 +++++++++++++++++++++++++++++++ scripts/dependencies_windows.ps1 | 130 +++++++++ scripts/disable_updates.ps1 | 52 ++++ scripts/install.bat | 9 + scripts/optimize.ps1 | 36 +++ 6 files changed, 712 insertions(+) create mode 100644 custom.xml create mode 100644 scripts/dependencies_windows.ps1 create mode 100644 scripts/disable_updates.ps1 create mode 100644 scripts/install.bat create mode 100644 scripts/optimize.ps1 diff --git a/compose.yml b/compose.yml index f305849..064332a 100644 --- a/compose.yml +++ b/compose.yml @@ -2,8 +2,19 @@ services: windows: image: dockurr/windows container_name: windows + privileged: true + healthcheck: + test: "[ -f /data/ready ] || exit 1" + interval: 60s + retries: 5 + start_period: 300s + timeout: 2s environment: VERSION: "11" + USERNAME: "bill" + PASSWORD: "gates" + DEBUG: "y" + MANUAL: "n" devices: - /dev/kvm - /dev/net/tun @@ -14,3 +25,7 @@ services: - 3389:3389/tcp - 3389:3389/udp stop_grace_period: 2m + volumes: + - ./scripts:/oem + - ./shared:/data + - ./custom.xml:/custom.xml \ No newline at end of file diff --git a/custom.xml b/custom.xml new file mode 100644 index 0000000..29729f9 --- /dev/null +++ b/custom.xml @@ -0,0 +1,470 @@ + + + + + + en-US + + 0409:00000409 + en-US + en-US + en-US + + + + + 0 + true + + + + 1 + EFI + 128 + + + + 2 + MSR + 128 + + + + 3 + Primary + true + + + + + + 1 + 1 + + FAT32 + + + + 2 + 2 + + + + 3 + 3 + + C + NTFS + + + + + + + + 0 + 3 + + false + + + + true + Never + + + false + Never + + + true + Docker + Windows for Docker + + VK7JG-NPHTM-C97JM-9MPGT-3V66T + + + false + + false + + + + 1 + reg.exe add "HKLM\SYSTEM\Setup\LabConfig" /v BypassTPMCheck /t REG_DWORD /d 1 /f + + + 2 + reg.exe add "HKLM\SYSTEM\Setup\LabConfig" /v BypassSecureBootCheck /t REG_DWORD /d 1 /f + + + 3 + reg.exe add "HKLM\SYSTEM\Setup\LabConfig" /v BypassRAMCheck /t REG_DWORD /d 1 /f + + + 4 + reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f + + + + + + + false + + + + + true + + + 1 + + + + + true + + + * + + Dockur + Windows for Docker + 24/7 + + Dockur + https://github.com/dockur/windows/issues + + Windows for Docker + + + 1 + + + true + true + https://google.com + about:blank + + + true + true + https://google.com + about:blank + + + 0 + + + 1 + + + 0409:00000409 + en-US + en-US + en-US + + + + + 1 + reg.exe add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v BypassNRO /t REG_DWORD /d 1 /f + + + 2 + reg.exe load "HKU\mount" "C:\Users\Default\NTUSER.DAT" + + + 3 + reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "ContentDeliveryAllowed" /t REG_DWORD /d 0 /f + + + 4 + reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "FeatureManagementEnabled" /t REG_DWORD /d 0 /f + + + 5 + reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "OEMPreInstalledAppsEnabled" /t REG_DWORD /d 0 /f + + + 6 + reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "PreInstalledAppsEnabled" /t REG_DWORD /d 0 /f + + + 7 + reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "PreInstalledAppsEverEnabled" /t REG_DWORD /d 0 /f + + + 8 + reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SilentInstalledAppsEnabled" /t REG_DWORD /d 0 /f + + + 9 + reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SoftLandingEnabled" /t REG_DWORD /d 0 /f + + + 10 + reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContentEnabled" /t REG_DWORD /d 0 /f + + + 11 + reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-310093Enabled" /t REG_DWORD /d 0 /f + + + 12 + reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-338387Enabled" /t REG_DWORD /d 0 /f + + + 13 + reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-338388Enabled" /t REG_DWORD /d 0 /f + + + 14 + reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-338389Enabled" /t REG_DWORD /d 0 /f + + + 15 + reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-338393Enabled" /t REG_DWORD /d 0 /f + + + 16 + reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-353698Enabled" /t REG_DWORD /d 0 /f + + + 17 + reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SystemPaneSuggestionsEnabled" /t REG_DWORD /d 0 /f + + + 18 + reg.exe add "HKU\mount\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableCloudOptimizedContent" /t REG_DWORD /d 1 /f + + + 19 + reg.exe add "HKU\mount\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsConsumerFeatures" /t REG_DWORD /d 1 /f + + + 20 + reg.exe add "HKU\mount\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableConsumerAccountStateContent" /t REG_DWORD /d 1 /f + + + 21 + reg.exe unload "HKU\mount" + + + 22 + reg.exe add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableCloudOptimizedContent" /t REG_DWORD /d 1 /f + + + 23 + reg.exe add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsConsumerFeatures" /t REG_DWORD /d 1 /f + + + 24 + reg.exe add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableConsumerAccountStateContent" /t REG_DWORD /d 1 /f + + + 25 + reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f + Set Network Location to Home + + + + + false + + + 0 + + + + + true + all + @FirewallAPI.dll,-28752 + + + + + + + + + true + + + 1 + + + + + + Docker + Administrators + + + true</PlainText> + </Password> + </LocalAccount> + </LocalAccounts> + <AdministratorPassword> + <Value>password</Value> + <PlainText>true</PlainText> + </AdministratorPassword> + </UserAccounts> + <AutoLogon> + <Username>Docker</Username> + <Enabled>true</Enabled> + <LogonCount>65432</LogonCount> + <Password> + <Value /> + <PlainText>true</PlainText> + </Password> + </AutoLogon> + <Display> + <ColorDepth>32</ColorDepth> + <HorizontalResolution>1920</HorizontalResolution> + <VerticalResolution>1080</VerticalResolution> + </Display> + <OOBE> + <HideEULAPage>true</HideEULAPage> + <HideLocalAccountScreen>true</HideLocalAccountScreen> + <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> + <HideOnlineAccountScreens>true</HideOnlineAccountScreens> + <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> + <NetworkLocation>Home</NetworkLocation> + <ProtectYourPC>3</ProtectYourPC> + <SkipUserOOBE>true</SkipUserOOBE> + <SkipMachineOOBE>true</SkipMachineOOBE> + </OOBE> + <RegisteredOrganization>Dockur</RegisteredOrganization> + <RegisteredOwner>Windows for Docker</RegisteredOwner> + <FirstLogonCommands> + <SynchronousCommand wcm:action="add"> + <Order>1</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters" /v "AllowInsecureGuestAuth" /t REG_DWORD /d 1 /f</CommandLine> + <Description>Allow guest access to network shares</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>2</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters" /v "RequireSecuritySignature" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Disable SMB signing requirement</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>3</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\Lsa" /v LimitBlankPasswordUse /t REG_DWORD /d 0 /f</CommandLine> + <Description>Allow RDP login with blank password</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>4</Order> + <CommandLine>reg.exe add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PasswordLess\Device" /v "DevicePasswordLessBuildVersion" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Enable option for passwordless sign-in</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>5</Order> + <CommandLine>cmd /C wmic useraccount where name="Docker" set PasswordExpires=false</CommandLine> + <Description>Password Never Expires</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>6</Order> + <CommandLine>cmd /C POWERCFG -H OFF</CommandLine> + <Description>Disable Hibernation</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>7</Order> + <CommandLine>cmd /C POWERCFG -X -monitor-timeout-ac 0</CommandLine> + <Description>Disable monitor blanking</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>8</Order> + <CommandLine>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "HideFirstRunExperience" /t REG_DWORD /d 1 /f</CommandLine> + <Description>Disable first-run experience in Edge</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>9</Order> + <CommandLine>reg.exe add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "HideFileExt" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Show file extensions in Explorer</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>10</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\Power" /v "HibernateFileSizePercent" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Zero Hibernation File</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>11</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\Power" /v "HibernateEnabled" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Disable Hibernation</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>12</Order> + <CommandLine>cmd /C POWERCFG -X -standby-timeout-ac 0</CommandLine> + <Description>Disable Sleep</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>13</Order> + <CommandLine>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" /v "fAllowUnlistedRemotePrograms" /t REG_DWORD /d 1 /f</CommandLine> + <Description>Enable RemoteAPP to launch unlisted programs</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>14</Order> + <CommandLine>reg.exe add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "ShowTaskViewButton" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Remove Task View from the Taskbar</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>15</Order> + <CommandLine>reg.exe add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "TaskbarDa" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Remove Widgets from the Taskbar</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>16</Order> + <CommandLine>reg.exe add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "TaskbarMn" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Remove Chat from the Taskbar</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>17</Order> + <CommandLine>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "NoAutoUpdate" /t REG_DWORD /d 1 /f</CommandLine> + <Description>Turn off Windows Update auto download</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>18</Order> + <CommandLine>netsh advfirewall firewall set rule group="@FirewallAPI.dll,-32752" new enable=Yes</CommandLine> + <Description>Enable Network Discovery</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>19</Order> + <CommandLine>netsh advfirewall firewall set rule group="@FirewallAPI.dll,-28502" new enable=Yes</CommandLine> + <Description>Enable File Sharing</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>20</Order> + <CommandLine>reg.exe add "HKCU\Control Panel\UnsupportedHardwareNotificationCache" /v SV1 /d 0 /t REG_DWORD /f</CommandLine> + <Description>Disable unsupported hardware notifications</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>21</Order> + <CommandLine>reg.exe add "HKCU\Control Panel\UnsupportedHardwareNotificationCache" /v SV2 /d 0 /t REG_DWORD /f</CommandLine> + <Description>Disable unsupported hardware notifications</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>22</Order> + <CommandLine>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</CommandLine> + <Description>Install VirtIO display driver</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>23</Order> + <CommandLine>cmd /C rd /q C:\Windows.old</CommandLine> + <Description>Remove empty Windows.old folder</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>24</Order> + <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> + <Description>Execute custom script from the OEM folder if exists</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>25</Order> + <CommandLine>cmd /C "type nul > \\host.lan\Data\ready"</CommandLine> + <Description>Let host known that all configuration is done</Description> + </SynchronousCommand> + </FirstLogonCommands> + </component> + </settings> +</unattend> diff --git a/scripts/dependencies_windows.ps1 b/scripts/dependencies_windows.ps1 new file mode 100644 index 0000000..06a9101 --- /dev/null +++ b/scripts/dependencies_windows.ps1 @@ -0,0 +1,130 @@ +$ErrorActionPreference = "Stop" + +# https://stackoverflow.com/questions/9948517/how-to-stop-a-powershell-script-on-the-first-error +function CheckStatus { + if (-not $?) + { + throw "Native Failure" + } +} + +function Validate-FileHash($filePath, $expectedHash, [Parameter(Mandatory=$false)] $algorithm) { + if ($algorithm -ne $null) { + $computedHash = Get-FileHash $filePath -Algorithm $algorithm + } else { + $computedHash = Get-FileHash $filePath + } + if ($computedHash.Hash -ne $expectedHash) { + Write-Error "incorrect hash for file: $filePath, actual: $($computedHash.Hash), expected: $expectedHash" + exit 1 + } +} + +function Install-STUN() { + $ZipPath = "stunserver_win64_1_2_16.zip" + $URL = "http://www.stunprotocol.org/$ZipPath" + $Destination = "C:\workspace\stunserver" + $Hash = "CDC8C68400E3B9ECE95F900699CEF1535CFCF4E59C34AF9A33F4679638ACA3A1" + + echo "Downloading $URL" + curl.exe -L $URL -o $ZipPath + CheckStatus + + Validate-FileHash $ZipPath $Hash + + echo "Extracting $ZipPath to $Destination" + Expand-Archive $ZipPath -DestinationPath $Destination + CheckStatus +} + +function Install-iperf() { + $ZipPath = "iperf3.17_64.zip" + $URL = "https://files.budman.pw/$ZipPath" + $Hash = "C1AB63DE610D73779D1003753F8DCD3FAAE0B6AC5BE1EAF31BBF4A1D3D2E3356" + $Destination = "C:\workspace\iperf3" + $DestinationTmp = "$Destination.tmp" + + echo "Downloading $URL" + curl.exe -L $URL -o $ZipPath + CheckStatus + + Validate-FileHash $ZipPath $Hash + + echo "Extracting $ZipPath to $DestinationTmp" + Expand-Archive $ZipPath -DestinationPath $DestinationTmp + CheckStatus + + $firstSubDir = Get-ChildItem -Path $DestinationTmp -Directory | Select-Object -First 1 + echo "Moving $DestinationTmp\$firstSubDir to $Destination" + mv $DestinationTmp\$firstSubDir $Destination + Remove-Item $DestinationTmp +} + +function Install-Python() { + $InstallerPath = "python-3.13.0-amd64.exe" + $URL = "https://www.python.org/ftp/python/3.13.0/$InstallerPath" + $Hash = "78156AD0CF0EC4123BFB5333B40F078596EBF15F2D062A10144863680AFBDEFC" + + echo "Downloading $URL" + curl.exe -L $URL -o $InstallerPath + CheckStatus + + Validate-FileHash $InstallerPath $Hash + + echo "Installing python.." + Start-Process -NoNewWindow -Wait -FilePath $PWD\$InstallerPath -ArgumentList "/quiet InstallAllUsers=1 PrependPath=1 Include_test=0 Include_doc=0 Include_dev=1 Include_launcher=0 Include_tcltk=0" + CheckStatus + + $env:Path = [System.Environment]::GetEnvironmentVariable("Path", [System.EnvironmentVariableTarget]::Machine) + + python.exe -m pip install --upgrade pip +} + +function Install-WinDump() { + $InstallerPath = "nmap-7.12-setup.exe" + $URL = "https://nmap.org/dist/$InstallerPath" + $Hash = "56580F1EEBDCCFBC5CE6D75690600225738DDBE8D991A417E56032869B0F43C7" + + echo "Downloading $URL" + curl.exe -L $URL -o $InstallerPath + CheckStatus + + Validate-FileHash $InstallerPath $Hash + + echo "Installing winpcap.." + Start-Process -NoNewWindow -Wait -FilePath $PWD\$InstallerPath -ArgumentList "/S" + CheckStatus + + sc.exe config npf start= auto + CheckStatus + + $BinaryPath = "WinDump.exe" + $URL = "https://www.winpcap.org/windump/install/bin/windump_3_9_5/$BinaryPath" + $Hash = "d59bc54721951dec855cbb4bbc000f9a71ea4d95" + + echo "Downloading $URL" + curl.exe -L $URL -o $BinaryPath + CheckStatus + + Validate-FileHash $BinaryPath $Hash SHA1 +} + +[System.IO.Directory]::CreateDirectory("C:\workspace") +CheckStatus + +cd C:\workspace +setx PATH "%PATH%;C:\workspace\uniffi" + +Install-STUN +CheckStatus + +Install-iperf +CheckStatus + +Install-Python +CheckStatus + +Install-WinDump +CheckStatus + +pip install Pyro5==5.15 diff --git a/scripts/disable_updates.ps1 b/scripts/disable_updates.ps1 new file mode 100644 index 0000000..5db31e3 --- /dev/null +++ b/scripts/disable_updates.ps1 @@ -0,0 +1,52 @@ +$ErrorActionPreference = "Stop" + +function Set-RegistryProperty { + param ( + [string]$path, + [string]$name, + [int]$value + ) + + if (-not (Test-Path $path)) { + New-Item -Path $path -Force + } + + if (-not (Test-Path "$path\$name")) { + New-ItemProperty -Path $path -Name $name -Value $value -Force + } else { + Set-ItemProperty -Path $path -Name $name -Value $value -Force + } +} + +Write-Output "Windows Update settings have been configured to disable automatic updates and notifications." + +$settings = @( + @{ Type = "registry"; Name = "NoAutoUpdate"; Value = 1; Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" }, + @{ Type = "registry"; Name = "AUOptions"; Value = 0; Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" }, + @{ Type = "registry"; Name = "ExcludeWUDriversInQualityUpdate"; Value = 1; Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" }, + @{ Type = "registry"; Name = "DisableWindowsUpdateAccess"; Value = 1; Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" }, + @{ Type = "registry"; Name = "NoAutoRebootWithLoggedOnUsers"; Value = 1; Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" }, + @{ Type = "registry"; Name = "DisableAutoReboot"; Value = 1; Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" }, + @{ Type = "registry"; Name = "UseWUServer"; Value = 0; Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" }, + @{ Type = "registry"; Name = "ExternalManaged"; Value = 1; Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" }, + @{ Type = "registry"; Name = "DODownloadMode"; Value = 0; Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\DeliveryOptimization\Config" }, + + @{ Type = "service"; Name = "wuauserv"; Value = 4; Path = "HKLM:\SYSTEM\CurrentControlSet\Services\wuauserv" }, + @{ Type = "service"; Name = "BITS"; Value = 4; Path = "HKLM:\SYSTEM\CurrentControlSet\Services\BITS" }, + @{ Type = "service"; Name = "cryptsvc"; Value = 4; Path = "HKLM:\SYSTEM\CurrentControlSet\Services\cryptsvc" }, + @{ Type = "service"; Name = "dosvc"; Value = 4; Path = "HKLM:\SYSTEM\CurrentControlSet\Services\dosvc" }, + @{ Type = "service"; Name = "usosvc"; Value = 4; Path = "HKLM:\SYSTEM\CurrentControlSet\Services\usosvc" }, + @{ Type = "service"; Name = "msiserver"; Value = 4; Path = "HKLM:\SYSTEM\CurrentControlSet\Services\msiserver" } +) + +foreach ($setting in $settings) { + if ($setting.Type -eq "registry") { + Set-RegistryProperty -path $setting.Path -name $setting.Name -value $setting.Value + Write-Output "Set $($setting.Name) to $($setting.Value) in $($setting.Path)." + } elseif ($setting.Type -eq "service") { + Set-RegistryProperty -path $setting.Path -name "Start" -value $setting.Value + Write-Output "Disabled $($setting.Name) service." + } +} + +Write-Output "All specified Windows Update services and group policies have been disabled." diff --git a/scripts/install.bat b/scripts/install.bat new file mode 100644 index 0000000..2f430c2 --- /dev/null +++ b/scripts/install.bat @@ -0,0 +1,9 @@ +pushd "C:/OEM" + +powershell -ExecutionPolicy Bypass -File "dependencies_windows.ps1" +powershell -ExecutionPolicy Bypass -File "optimize.ps1" +powershell -ExecutionPolicy Bypass -File "disable_updates.ps1" + +popd + +shutdown /f /r /t 0 diff --git a/scripts/optimize.ps1 b/scripts/optimize.ps1 new file mode 100644 index 0000000..017ccbe --- /dev/null +++ b/scripts/optimize.ps1 @@ -0,0 +1,36 @@ +$ErrorActionPreference = "Stop" + +# Set Power Plan to High Performance and disable sleep +Write-Output "Configuring Power Plan to High Performance and disabling sleep..." +slmgr /rearm +powercfg -setactive SCHEME_MIN +powercfg /x -hibernate-timeout-ac 0 +powercfg /x -hibernate-timeout-dc 0 +powercfg /x -disk-timeout-ac 0 +powercfg /x -disk-timeout-dc 0 +powercfg /x -monitor-timeout-ac 0 +powercfg /x -monitor-timeout-dc 0 +powercfg /x -standby-timeout-ac 0 +powercfg /x -standby-timeout-dc 0 + +# Disable Windows Search Indexing (optional, for minimal interruption) +Write-Output "Disabling Windows Search indexing service..." +Stop-Service -Name "WSearch" -Force -ErrorAction SilentlyContinue +Set-Service -Name "WSearch" -StartupType Disabled + +# Set Network Adapters to not enter Power Saving mode +Write-Output "Disabling Power Saving for Network Adapters..." +Get-WmiObject -Namespace root\wmi -Class MSPower_DeviceEnable -Filter "InstanceName LIKE 'PCI\\\\VEN%'" | ForEach-Object { + $_.Enable = $false + $_.Put() +} + +# Set Firewall to allow all connections (optional; adjust based on your requirements) +Write-Output "Configuring Windows Firewall to allow all connections (if necessary)..." +Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False +netsh advfirewall set allprofiles state off + +# This can't be done inside provision script, because a restart is needed for changes to take effect. +Write-Host "Enable IPv6" +reg add hklm\system\currentcontrolset\services\tcpip6\parameters /f /v DisabledComponents /t REG_DWORD /d 0 + From 9bc2f0463cc00cdcba5a5735a35f4c7b601fd58e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gytis=20Sto=C5=A1kevi=C4=8Dius?= <gytisstoskevicius@gmail.com> Date: Wed, 26 Feb 2025 10:42:38 +0200 Subject: [PATCH 03/17] build docker image when running from compose --- compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose.yml b/compose.yml index 064332a..52e0cfa 100644 --- a/compose.yml +++ b/compose.yml @@ -1,6 +1,6 @@ services: windows: - image: dockurr/windows + build: . container_name: windows privileged: true healthcheck: From 6651b5bde91ba47d61ef4fae63d02e1e4c463209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gytis=20Sto=C5=A1kevi=C4=8Dius?= <gytisstoskevicius@gmail.com> Date: Wed, 26 Feb 2025 13:23:20 +0200 Subject: [PATCH 04/17] adjust healthcheck timeout --- compose.yml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/compose.yml b/compose.yml index 52e0cfa..3735189 100644 --- a/compose.yml +++ b/compose.yml @@ -7,7 +7,7 @@ services: test: "[ -f /data/ready ] || exit 1" interval: 60s retries: 5 - start_period: 300s + start_period: 480s timeout: 2s environment: VERSION: "11" @@ -28,4 +28,23 @@ services: volumes: - ./scripts:/oem - ./shared:/data - - ./custom.xml:/custom.xml \ No newline at end of file + - ./custom.xml:/custom.xml + networks: + vlan: + ipv4_address: 192.168.0.100 + vlan-1: + ipv4_address: 192.168.1.100 + +networks: + vlan: + driver: bridge + ipam: + driver: default + config: + - subnet: 192.168.0.0/24 + vlan-1: + driver: bridge + ipam: + driver: default + config: + - subnet: 192.168.1.0/24 \ No newline at end of file From 888b19328834c36405c7fb651256c082ad62fcca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rytis=20Karpu=C5=A1ka?= <rytis.karpuska@nordsec.com> Date: Fri, 7 Mar 2025 16:34:32 +0200 Subject: [PATCH 05/17] Add support for bridged networking --- src/network.sh | 194 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 182 insertions(+), 12 deletions(-) diff --git a/src/network.sh b/src/network.sh index 166b877..2f90705 100644 --- a/src/network.sh +++ b/src/network.sh @@ -6,7 +6,7 @@ set -Eeuo pipefail : "${MAC:=""}" : "${MAC_2:=""}" : "${DHCP:="N"}" -: "${NETWORK:="Y"}" +: "${NETWORK:="bridge"}" : "${USER_PORTS:=""}" : "${HOST_PORTS:=""}" : "${ADAPTER:="virtio-net-pci"}" @@ -20,8 +20,8 @@ set -Eeuo pipefail : "${VM_NET_MAC_2:="$MAC_2"}" : "${VM_NET_HOST:="QEMU"}" : "${VM_NET_HOST_2:="QEMU_2"}" -: "${VM_NET_IP:="20.20.20.21"}" -: "${VM_NET_IP_2:="20.20.21.21"}" +: "${VM_NET_IP:="192.168.0.101"}" +: "${VM_NET_IP_2:="192.168.1.101"}" : "${DNSMASQ_OPTS:=""}" : "${DNSMASQ:="/usr/sbin/dnsmasq"}" @@ -277,7 +277,157 @@ configureNAT() { configureDNS || return 1 + return 0/ +} + + +configureBridge() { + + local tuntap="TUN device is missing. $ADD_ERR --device /dev/net/tun" + local tables="The 'ip_tables' kernel module is not loaded. Try this command: sudo modprobe ip_tables iptable_nat" + + # Create the necessary file structure for /dev/net/tun + if [ ! -c /dev/net/tun ]; then + [ ! -d /dev/net ] && mkdir -m 755 /dev/net + if mknod /dev/net/tun c 10 200; then + chmod 666 /dev/net/tun + fi + fi + + if [ ! -c /dev/net/tun ]; then + error "$tuntap" && return 1 + fi + + # Check port forwarding flag + if [[ $(</proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then + { + sysctl -w net.ipv4.ip_forward=1 >/dev/null + rc=$? + } || : + if ((rc != 0)) || [[ $(</proc/sys/net/ipv4/ip_forward) -eq 0 ]]; then + error "IP forwarding is disabled. $ADD_ERR --sysctl net.ipv4.ip_forward=1" && return 1 + fi + fi + + # Create a bridge with a static IP for the VM guest + + { + ip link add dev dockerbridge type bridge + rc=$? + } || : + + if ((rc != 0)); then + error "Failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1 + fi + + { + ip link add dev dockerbridge_2 type bridge + rc=$? + } || : + if ((rc != 0)); then + error "Failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1 + fi + + # We need freshly created bridge to have IP address of the container + # For this reason we need to migrate IP from eth0 to dockerbridge. + for addr in $(ip --json addr show dev $VM_NET_DEV | jq -c '.[0].addr_info[] | select(.family == "inet")'); do + cidr_addr=$(echo $addr | jq -r '[ .local, .prefixlen|tostring] | join("/")'); + if ! ip addr add dev dockerbridge $cidr_addr; then + error "Failed to add address for dockerbridge interface" + exit 30 + fi + done + if ! ip addr flush dev $VM_NET_DEV; then + error "Failed to clear $VM_NET_DEV interface addresses" + exit 30 + fi + + while ! ip link set dockerbridge up; do + info "Waiting for IP address to become available..." + sleep 2 + done + + # We need freshly created bridge to have IP address of the container + # For this reason we need to migrate IP from eth0 to dockerbridge. + for addr in $(ip --json addr show dev $VM_NET_DEV_2 | jq -c '.[0].addr_info[] | select(.family == "inet")'); do + cidr_addr=$(echo $addr | jq -r '[ .local, .prefixlen|tostring] | join("/")'); + if ! ip addr add dev dockerbridge_2 $cidr_addr; then + error "Failed to add address for dockerbridge_2 interface" + exit 30 + fi + done + if ! ip addr flush dev $VM_NET_DEV_2; then + error "Failed to clear $VM_NET_DEV_2 interface addresses" + exit 30 + fi + + while ! ip link set dockerbridge_2 up; do + info "Waiting for IP address to become available..." + sleep 2 + done + + # QEMU Works with taps, set tap to the bridge created + if ! ip tuntap add dev "$VM_NET_TAP" mode tap; then + error "$tuntap" && return 1 + fi + + while ! ip link set "$VM_NET_TAP" up promisc on; do + info "Waiting for TAP to become available..." + sleep 2 + done + + if ! ip link set dev "$VM_NET_TAP" master dockerbridge; then + error "Failed to set IP link!" && return 1 + fi + + if ! ip link set dev "$VM_NET_DEV" master dockerbridge; then + error "Failed to attach docker interface to bridge" + fi + + if ! ip tuntap add dev "$VM_NET_TAP_2" mode tap; then + error "$tuntap" && return 1 + fi + + while ! ip link set "$VM_NET_TAP_2" up promisc on; do + info "Waiting for TAP to become available..." + sleep 2 + done + + if ! ip link set dev "$VM_NET_TAP_2" master dockerbridge_2; then + error "Failed to set IP link!" && return 1 + fi + + # add initial default route as well + if ! ip route add default dev dockerbridge via ${VM_NET_IP%.*}.1; then + error "Failed to setup default route" && return 10 + fi + + NET_OPTS="-netdev tap,id=hostnet0,ifname=$VM_NET_TAP" + + if [ -c /dev/vhost-net ]; then + { + exec 40>>/dev/vhost-net + rc=$? + } 2>/dev/null || : + ((rc == 0)) && NET_OPTS+=",vhost=on,vhostfd=40" + fi + + NET_OPTS+=",script=no,downscript=no" + + NET_OPTS+=" -netdev tap,id=hostnet1,ifname=$VM_NET_TAP_2" + + if [ -c /dev/vhost-net ]; then + { + exec 41>>/dev/vhost-net + rc=$? + } 2>/dev/null || : + ((rc == 0)) && NET_OPTS+=",vhost=on,vhostfd=41" + fi + + NET_OPTS+=",script=no,downscript=no" + return 0 + } closeNetwork() { @@ -434,13 +584,18 @@ if [[ "$IP" != "172."* ]] && [[ "$IP" != "10.8"* ]] && [[ "$IP" != "10.9"* ]]; t checkOS fi -if [[ "${NETWORK,,}" != "user"* ]]; then +if [[ "${NETWORK,,}" == "user"* ]]; then - # Configure for tap interface - if ! configureNAT; then + # Configure for usermode networking (slirp) + configureUser || exit 24 - NETWORK="user" - warn "falling back to usermode networking! Performance will be bad and port mapping will not work." +elif [[ "${NETWORK,,}" == "bridge"* ]]; then + # CONFIGURE Bridge + html "Configuring bridged network" + + if ! configureBridge; then + + error "Failed to setup bridge networking" ip link set "$VM_NET_TAP" down promisc off &>null || true ip link delete "$VM_NET_TAP" &>null || true @@ -452,14 +607,29 @@ if [[ "${NETWORK,,}" != "user"* ]]; then ip link set dockerbridge_2 down &>null || true ip link delete dockerbridge_2 &>null || true + exit 25 fi -fi +else -if [[ "${NETWORK,,}" == "user"* ]]; then + # Configure for tap interface + if ! configureNAT; then - # Configure for usermode networking (slirp) - configureUser || exit 24 + error "Failed to setup NAT networking" + + ip link set "$VM_NET_TAP" down promisc off &>null || true + ip link delete "$VM_NET_TAP" &>null || true + ip link set "$VM_NET_TAP_2" down promisc off &>null || true + ip link delete "$VM_NET_TAP_2" &>null || true + + ip link set dockerbridge down &>null || true + ip link delete dockerbridge &>null || true + ip link set dockerbridge_2 down &>null || true + ip link delete dockerbridge_2 &>null || true + + exit 25 + + fi fi From 5e4aede455cbf38e4c1da4d32452b52173eeca08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gytis=20Sto=C5=A1kevi=C4=8Dius?= <gytis.stoskevicius@nordsec.com> Date: Mon, 31 Mar 2025 07:16:25 +0000 Subject: [PATCH 06/17] Add CI builds --- .gitlab-ci.yml | 36 +++ Dockerfile | 3 +- compose.yml | 59 ++--- custom.xml | 8 +- env.sh | 2 + prepare_image.sh | 19 ++ scripts/dependencies_windows.ps1 | 36 +++ scripts/install.bat | 2 - src/install.sh | 6 +- src/network.sh | 435 ++++++++++++++++--------------- src/power.sh | 5 + 11 files changed, 360 insertions(+), 251 deletions(-) create mode 100644 .gitlab-ci.yml create mode 100755 env.sh create mode 100755 prepare_image.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..7d8a4e9 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,36 @@ +services: + - name: docker:dind + +default: + image: docker:latest + artifacts: + expire_in: 1 week + interruptible: true + retry: + max: 2 + when: runner_system_failure + tags: + - infra-docker-dind + +variables: + DOCKER_DRIVER: overlay2 + +stages: + - build + +build: + stage: build + variables: + IMAGE_NAME: "registry.digitalarsenal.net/low-level-hacks/third-party-build/dockur_windows" + rules: + - if: $CI_COMMIT_TAG + variables: + IMAGE_VERSION: "$CI_COMMIT_TAG" + - if: $CI_COMMIT_TAG == null + variables: + IMAGE_VERSION: "$CI_COMMIT_REF_SLUG" + before_script: + - apk add --no-cache docker-compose bash kmod + script: + - docker login -u "$CI_REGISTRY_USER" -p "$REGISTRY_PUSH_ACCESS_TOKEN" "$CI_REGISTRY" + - ./prepare_image.sh diff --git a/Dockerfile b/Dockerfile index b3eedfb..ba6997b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,8 @@ RUN set -eu && \ COPY --chmod=755 ./src /run/ COPY --chmod=755 ./assets /run/assets +RUN mkdir /storage + ADD --chmod=664 https://github.com/qemus/virtiso-whql/releases/download/v1.9.44-0/virtio-win-1.9.44.tar.xz /drivers.txz FROM dockurr/windows-arm:${VERSION_ARG} AS build-arm64 @@ -37,7 +39,6 @@ FROM build-${TARGETARCH} ARG VERSION_ARG="0.00" RUN echo "$VERSION_ARG" > /run/version -VOLUME /storage EXPOSE 8006 3389 ENV VERSION="11" diff --git a/compose.yml b/compose.yml index 3735189..57ad009 100644 --- a/compose.yml +++ b/compose.yml @@ -1,20 +1,19 @@ services: - windows: + windows-build: build: . - container_name: windows + container_name: windows-build privileged: true healthcheck: - test: "[ -f /data/ready ] || exit 1" - interval: 60s - retries: 5 - start_period: 480s + test: "[ -f /data/prepared ] || exit 1" + interval: 30s + retries: 50 + start_period: 600s timeout: 2s environment: VERSION: "11" USERNAME: "bill" PASSWORD: "gates" - DEBUG: "y" - MANUAL: "n" + DISK_FMT: "qcow2" devices: - /dev/kvm - /dev/net/tun @@ -22,29 +21,31 @@ services: - NET_ADMIN ports: - 8006:8006 - - 3389:3389/tcp - - 3389:3389/udp stop_grace_period: 2m volumes: - ./scripts:/oem - - ./shared:/data - ./custom.xml:/custom.xml - networks: - vlan: - ipv4_address: 192.168.0.100 - vlan-1: - ipv4_address: 192.168.1.100 + - ./custom.iso:/custom.iso -networks: - vlan: - driver: bridge - ipam: - driver: default - config: - - subnet: 192.168.0.0/24 - vlan-1: - driver: bridge - ipam: - driver: default - config: - - subnet: 192.168.1.0/24 \ No newline at end of file + windows-installed: + image: $IMAGE_NAME:$IMAGE_VERSION + container_name: windows-installed + privileged: true + healthcheck: + test: "[ -f /storage/ready ] || exit 1" + interval: 30s + retries: 20 + start_period: 60s + timeout: 2s + environment: + VERSION: "11" + USERNAME: "bill" + PASSWORD: "gates" + devices: + - /dev/kvm + - /dev/net/tun + cap_add: + - NET_ADMIN + ports: + - 8006:8006 + stop_grace_period: 2m diff --git a/custom.xml b/custom.xml index 29729f9..d630612 100644 --- a/custom.xml +++ b/custom.xml @@ -105,6 +105,10 @@ <Order>4</Order> <Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path> </RunSynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>5</Order> + <Path>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\msiserver" /v Start /t REG_DWORD /d 2 /f</Path> + </SynchronousCommand> </RunSynchronous> </component> </settings> @@ -456,12 +460,12 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>24</Order> - <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> + <CommandLine>cmd /C if exist "C:\OEM\install.bat" cmd /C C:\OEM\install.bat</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>25</Order> - <CommandLine>cmd /C "type nul > \\host.lan\Data\ready"</CommandLine> + <CommandLine>cmd /C "type nul > \\host.lan\Data\prepared"</CommandLine> <Description>Let host known that all configuration is done</Description> </SynchronousCommand> </FirstLogonCommands> diff --git a/env.sh b/env.sh new file mode 100755 index 0000000..64c21db --- /dev/null +++ b/env.sh @@ -0,0 +1,2 @@ +export IMAGE_NAME=${IMAGE_NAME:-"dockur_windows_installed"} +export IMAGE_VERSION=${IMAGE_VERSION:-"latest"} \ No newline at end of file diff --git a/prepare_image.sh b/prepare_image.sh new file mode 100755 index 0000000..19cde37 --- /dev/null +++ b/prepare_image.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +source env.sh + +echo "start to build and install windows" +docker compose up windows-build -d --wait --build + +echo "windows installed, now stop container" +docker stop windows-build + +echo "commit all the changes" +docker commit windows-build "$IMAGE_NAME:$IMAGE_VERSION" +docker images + +docker push "$IMAGE_NAME:$IMAGE_VERSION" + +echo "start container with windows installed" +docker compose up windows-installed -d --wait diff --git a/scripts/dependencies_windows.ps1 b/scripts/dependencies_windows.ps1 index 06a9101..568330d 100644 --- a/scripts/dependencies_windows.ps1 +++ b/scripts/dependencies_windows.ps1 @@ -109,6 +109,39 @@ function Install-WinDump() { Validate-FileHash $BinaryPath $Hash SHA1 } +function Install-QGA() { + # Define QEMU Guest Agent installer URL (change version if needed) + $QGA_URL = "https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso" + $QGA_ISO = "$env:TEMP\virtio-win.iso" + + # Download QEMU Guest Agent ISO + Write-Host "Downloading QEMU Guest Agent ISO..." + curl.exe -L $QGA_URL -o $QGA_ISO + + # Mount the ISO + Write-Host "Mounting ISO..." + $mount = Mount-DiskImage -ImagePath $QGA_ISO -PassThru | Get-Volume + $QGA_DRIVE = $mount.DriveLetter + ":" + + # Define installer path + $QGA_MSI = "$QGA_DRIVE\guest-agent\qemu-ga-x86_64.msi" + + # Install QEMU Guest Agent + Write-Host "Installing QEMU Guest Agent..." + Start-Process msiexec.exe -ArgumentList "/i `"$QGA_MSI`" /quiet /norestart" -Wait -NoNewWindow + + Get-Service QEMU-GA + + # Unmount the ISO + Write-Host "Unmounting ISO..." + Dismount-DiskImage -ImagePath $QGA_ISO + + # Cleanup + Remove-Item -Path $QGA_ISO -Force + + Write-Host "QEMU Guest Agent installation complete." +} + [System.IO.Directory]::CreateDirectory("C:\workspace") CheckStatus @@ -127,4 +160,7 @@ CheckStatus Install-WinDump CheckStatus +Install-QGA +CheckStatus + pip install Pyro5==5.15 diff --git a/scripts/install.bat b/scripts/install.bat index 2f430c2..cd0a3e2 100644 --- a/scripts/install.bat +++ b/scripts/install.bat @@ -5,5 +5,3 @@ powershell -ExecutionPolicy Bypass -File "optimize.ps1" powershell -ExecutionPolicy Bypass -File "disable_updates.ps1" popd - -shutdown /f /r /t 0 diff --git a/src/install.sh b/src/install.sh index e22b99e..54eebd5 100644 --- a/src/install.sh +++ b/src/install.sh @@ -1036,13 +1036,17 @@ if ! startInstall; then exit 68 fi +if [ -e /storage/*.qcow2 ]; then + html "Windows already installed, skipping image preparation..." + return 0 +fi + if [ ! -s "$ISO" ] || [ ! -f "$ISO" ]; then if ! downloadImage "$ISO" "$VERSION" "$LANGUAGE"; then rm -f "$ISO" 2> /dev/null || true exit 61 fi fi - if ! extractImage "$ISO" "$DIR" "$VERSION"; then rm -f "$ISO" 2> /dev/null || true exit 62 diff --git a/src/network.sh b/src/network.sh index 2f90705..da15ef0 100644 --- a/src/network.sh +++ b/src/network.sh @@ -4,24 +4,17 @@ set -Eeuo pipefail # Docker environment variables : "${MAC:=""}" -: "${MAC_2:=""}" : "${DHCP:="N"}" -: "${NETWORK:="bridge"}" +: "${NETWORK:="Y"}" : "${USER_PORTS:=""}" : "${HOST_PORTS:=""}" : "${ADAPTER:="virtio-net-pci"}" -: "${ADAPTER_2:="virtio-net-pci"}" -: "${VM_NET_DEV:="eth0"}" -: "${VM_NET_DEV_2:="eth1"}" +: "${VM_NET_DEV:=""}" : "${VM_NET_TAP:="qemu"}" -: "${VM_NET_TAP_2:="qemu_2"}" : "${VM_NET_MAC:="$MAC"}" -: "${VM_NET_MAC_2:="$MAC_2"}" : "${VM_NET_HOST:="QEMU"}" -: "${VM_NET_HOST_2:="QEMU_2"}" -: "${VM_NET_IP:="192.168.0.101"}" -: "${VM_NET_IP_2:="192.168.1.101"}" +: "${VM_NET_IP:="20.20.20.21"}" : "${DNSMASQ_OPTS:=""}" : "${DNSMASQ:="/usr/sbin/dnsmasq"}" @@ -33,28 +26,106 @@ ADD_ERR="Please add the following setting to your container:" # Functions # ###################################### +configureDHCP() { + + # Create the necessary file structure for /dev/vhost-net + if [ ! -c /dev/vhost-net ]; then + if mknod /dev/vhost-net c 10 238; then + chmod 660 /dev/vhost-net + fi + fi + + # Create a macvtap network for the VM guest + { + msg=$(ip link add link "$VM_NET_DEV" name "$VM_NET_TAP" address "$VM_NET_MAC" type macvtap mode bridge 2>&1) + rc=$? + } || : + + case "$msg" in + "RTNETLINK answers: File exists"*) + while ! ip link add link "$VM_NET_DEV" name "$VM_NET_TAP" address "$VM_NET_MAC" type macvtap mode bridge; do + info "Waiting for macvtap interface to become available.." + sleep 5 + done + ;; + "RTNETLINK answers: Invalid argument"*) + error "Cannot create macvtap interface. Please make sure that the network type of the container is 'macvlan' and not 'ipvlan'." + return 1 + ;; + "RTNETLINK answers: Operation not permitted"*) + error "No permission to create macvtap interface. Please make sure that your host kernel supports it and that the NET_ADMIN capability is set." + return 1 + ;; + *) + [ -n "$msg" ] && echo "$msg" >&2 + if ((rc != 0)); then + error "Cannot create macvtap interface." + return 1 + fi + ;; + esac + + while ! ip link set "$VM_NET_TAP" up; do + info "Waiting for MAC address $VM_NET_MAC to become available..." + sleep 2 + done + + local TAP_NR TAP_PATH MAJOR MINOR + TAP_NR=$(</sys/class/net/"$VM_NET_TAP"/ifindex) + TAP_PATH="/dev/tap${TAP_NR}" + + # Create dev file (there is no udev in container: need to be done manually) + IFS=: read -r MAJOR MINOR < <(cat /sys/devices/virtual/net/"$VM_NET_TAP"/tap*/dev) + ((MAJOR < 1)) && error "Cannot find: sys/devices/virtual/net/$VM_NET_TAP" && return 1 + + [[ ! -e "$TAP_PATH" ]] && [[ -e "/dev0/${TAP_PATH##*/}" ]] && ln -s "/dev0/${TAP_PATH##*/}" "$TAP_PATH" + + if [[ ! -e "$TAP_PATH" ]]; then + { + mknod "$TAP_PATH" c "$MAJOR" "$MINOR" + rc=$? + } || : + ((rc != 0)) && error "Cannot mknod: $TAP_PATH ($rc)" && return 1 + fi + + { + exec 30>>"$TAP_PATH" + rc=$? + } 2>/dev/null || : + + if ((rc != 0)); then + error "Cannot create TAP interface ($rc). $ADD_ERR --device-cgroup-rule='c *:* rwm'" && return 1 + fi + + { + exec 40>>/dev/vhost-net + rc=$? + } 2>/dev/null || : + + if ((rc != 0)); then + error "VHOST can not be found ($rc). $ADD_ERR --device=/dev/vhost-net" && return 1 + fi + + NET_OPTS="-netdev tap,id=hostnet0,vhost=on,vhostfd=40,fd=30" + + return 0 +} + configureDNS() { - # Create lease file for faster resolve - echo "0 $VM_NET_MAC $VM_NET_IP $VM_NET_HOST 01:$VM_NET_MAC" >/var/lib/misc/dnsmasq.leases - echo "0 $VM_NET_MAC_2 $VM_NET_IP_2 $VM_NET_HOST_2 01:$VM_NET_MAC_2" >>/var/lib/misc/dnsmasq.leases - chmod 644 /var/lib/misc/dnsmasq.leases # dnsmasq configuration: - # eth0 - Provides both DNS and Default Gateway - DNSMASQ_OPTS+=" --dhcp-range=$VM_NET_IP,$VM_NET_IP" - DNSMASQ_OPTS+=" --dhcp-host=$VM_NET_MAC,,$VM_NET_IP,$VM_NET_HOST,infinite" - DNSMASQ_OPTS+=" --dhcp-option=option:netmask,255.255.255.0" - DNSMASQ_OPTS+=" --dhcp-option=option:dns-server,${VM_NET_IP%.*}.1" + DNSMASQ_OPTS+=" --dhcp-range=$VM_NET_IP,$VM_NET_IP --dhcp-host=$VM_NET_MAC,,$VM_NET_IP,$VM_NET_HOST,infinite --dhcp-option=option:netmask,255.255.255.0" + + # Create lease file for faster resolve + echo "0 $VM_NET_MAC $VM_NET_IP $VM_NET_HOST 01:$VM_NET_MAC" >/var/lib/misc/dnsmasq.leases + chmod 644 /var/lib/misc/dnsmasq.leases + + # Set DNS server and gateway + DNSMASQ_OPTS+=" --dhcp-option=option:dns-server,${VM_NET_IP%.*}.1 --dhcp-option=option:router,${VM_NET_IP%.*}.1" + + # Add DNS entry for container DNSMASQ_OPTS+=" --address=/host.lan/${VM_NET_IP%.*}.1" - # eth1 - Provides only DNS, no default gateway - DNSMASQ_OPTS+=" --dhcp-range=$VM_NET_IP_2,$VM_NET_IP_2" - DNSMASQ_OPTS+=" --dhcp-host=$VM_NET_MAC_2,,$VM_NET_IP_2,$VM_NET_HOST_2,infinite" - DNSMASQ_OPTS+=" --dhcp-option=option:netmask,255.255.255.0" - DNSMASQ_OPTS+=" --dhcp-option=option:dns-server,${VM_NET_IP_2%.*}.1" - DNSMASQ_OPTS+=" --address=/host.lan/${VM_NET_IP_2%.*}.1" - - # Cleanup and start dnsmasq DNSMASQ_OPTS=$(echo "$DNSMASQ_OPTS" | sed 's/\t/ /g' | tr -s ' ' | sed 's/^ *//') if ! $DNSMASQ ${DNSMASQ_OPTS:+ $DNSMASQ_OPTS}; then @@ -116,9 +187,6 @@ configureUser() { forward=$(getUserPorts "$USER_PORTS") [ -n "$forward" ] && NET_OPTS+=",$forward" - NET_OPTS+=" -netdev user,id=hostnet1,host=${VM_NET_IP_2%.*}.1,net=${VM_NET_IP_2%.*}.0/24,dhcpstart=$VM_NET_IP_2,hostname=$VM_NET_HOST_2" - [ -n "$forward" ] && NET_OPTS+=",$forward" - return 0 } @@ -161,14 +229,6 @@ configureNAT() { error "Failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1 fi - { - ip link add dev dockerbridge_2 type bridge - rc=$? - } || : - if ((rc != 0)); then - error "Failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1 - fi - if ! ip address add "${VM_NET_IP%.*}.1/24" broadcast "${VM_NET_IP%.*}.255" dev dockerbridge; then error "Failed to add IP address!" && return 1 fi @@ -178,15 +238,6 @@ configureNAT() { sleep 2 done - if ! ip address add "${VM_NET_IP_2%.*}.1/24" broadcast "${VM_NET_IP_2%.*}.255" dev dockerbridge_2; then - error "Failed to add IP address!" && return 1 - fi - - while ! ip link set dockerbridge_2 up; do - info "Waiting for IP address to become available..." - sleep 2 - done - # QEMU Works with taps, set tap to the bridge created if ! ip tuntap add dev "$VM_NET_TAP" mode tap; then error "$tuntap" && return 1 @@ -201,19 +252,6 @@ configureNAT() { error "Failed to set IP link!" && return 1 fi - if ! ip tuntap add dev "$VM_NET_TAP_2" mode tap; then - error "$tuntap" && return 1 - fi - - while ! ip link set "$VM_NET_TAP_2" up promisc on; do - info "Waiting for TAP to become available..." - sleep 2 - done - - if ! ip link set dev "$VM_NET_TAP_2" master dockerbridge_2; then - error "Failed to set IP link!" && return 1 - fi - # Add internet connection to the VM update-alternatives --set iptables /usr/sbin/iptables-legacy >/dev/null update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy >/dev/null @@ -233,19 +271,6 @@ configureNAT() { error "Failed to configure IP tables!" && return 1 fi - if ! iptables -t nat -A POSTROUTING -o "$VM_NET_DEV_2" -j MASQUERADE; then - error "$tables" && return 1 - fi - - # shellcheck disable=SC2086 - if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV_2" -d "$IP_2" -p tcp${exclude} -j DNAT --to "$VM_NET_IP_2"; then - error "Failed to configure IP tables!" && return 1 - fi - - if ! iptables -t nat -A PREROUTING -i "$VM_NET_DEV_2" -d "$IP_2" -p udp -j DNAT --to "$VM_NET_IP_2"; then - error "Failed to configure IP tables!" && return 1 - fi - if ((KERNEL > 4)); then # Hack for guest VMs complaining about "bad udp checksums in 5 packets" iptables -A POSTROUTING -t mangle -p udp --dport bootpc -j CHECKSUM --checksum-fill >/dev/null 2>&1 || true @@ -263,24 +288,11 @@ configureNAT() { NET_OPTS+=",script=no,downscript=no" - NET_OPTS+=" -netdev tap,id=hostnet1,ifname=$VM_NET_TAP_2" - - if [ -c /dev/vhost-net ]; then - { - exec 41>>/dev/vhost-net - rc=$? - } 2>/dev/null || : - ((rc == 0)) && NET_OPTS+=",vhost=on,vhostfd=41" - fi - - NET_OPTS+=",script=no,downscript=no" - configureDNS || return 1 - return 0/ + return 0 } - configureBridge() { local tuntap="TUN device is missing. $ADD_ERR --device /dev/net/tun" @@ -320,21 +332,21 @@ configureBridge() { error "Failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1 fi - { - ip link add dev dockerbridge_2 type bridge - rc=$? - } || : - if ((rc != 0)); then - error "Failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1 - fi + # { + # ip link add dev dockerbridge_2 type bridge + # rc=$? + # } || : + # if ((rc != 0)); then + # error "Failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1 + # fi # We need freshly created bridge to have IP address of the container # For this reason we need to migrate IP from eth0 to dockerbridge. for addr in $(ip --json addr show dev $VM_NET_DEV | jq -c '.[0].addr_info[] | select(.family == "inet")'); do - cidr_addr=$(echo $addr | jq -r '[ .local, .prefixlen|tostring] | join("/")'); + cidr_addr=$(echo $addr | jq -r '[ .local, .prefixlen|tostring] | join("/")') if ! ip addr add dev dockerbridge $cidr_addr; then - error "Failed to add address for dockerbridge interface" - exit 30 + error "Failed to add address for dockerbridge interface" + exit 30 fi done if ! ip addr flush dev $VM_NET_DEV; then @@ -347,24 +359,24 @@ configureBridge() { sleep 2 done - # We need freshly created bridge to have IP address of the container - # For this reason we need to migrate IP from eth0 to dockerbridge. - for addr in $(ip --json addr show dev $VM_NET_DEV_2 | jq -c '.[0].addr_info[] | select(.family == "inet")'); do - cidr_addr=$(echo $addr | jq -r '[ .local, .prefixlen|tostring] | join("/")'); - if ! ip addr add dev dockerbridge_2 $cidr_addr; then - error "Failed to add address for dockerbridge_2 interface" - exit 30 - fi - done - if ! ip addr flush dev $VM_NET_DEV_2; then - error "Failed to clear $VM_NET_DEV_2 interface addresses" - exit 30 - fi + # # We need freshly created bridge to have IP address of the container + # # For this reason we need to migrate IP from eth0 to dockerbridge. + # for addr in $(ip --json addr show dev $VM_NET_DEV_2 | jq -c '.[0].addr_info[] | select(.family == "inet")'); do + # cidr_addr=$(echo $addr | jq -r '[ .local, .prefixlen|tostring] | join("/")') + # if ! ip addr add dev dockerbridge_2 $cidr_addr; then + # error "Failed to add address for dockerbridge_2 interface" + # exit 30 + # fi + # done + # if ! ip addr flush dev $VM_NET_DEV_2; then + # error "Failed to clear $VM_NET_DEV_2 interface addresses" + # exit 30 + # fi - while ! ip link set dockerbridge_2 up; do - info "Waiting for IP address to become available..." - sleep 2 - done + # while ! ip link set dockerbridge_2 up; do + # info "Waiting for IP address to become available..." + # sleep 2 + # done # QEMU Works with taps, set tap to the bridge created if ! ip tuntap add dev "$VM_NET_TAP" mode tap; then @@ -384,18 +396,18 @@ configureBridge() { error "Failed to attach docker interface to bridge" fi - if ! ip tuntap add dev "$VM_NET_TAP_2" mode tap; then - error "$tuntap" && return 1 - fi + # if ! ip tuntap add dev "$VM_NET_TAP_2" mode tap; then + # error "$tuntap" && return 1 + # fi - while ! ip link set "$VM_NET_TAP_2" up promisc on; do - info "Waiting for TAP to become available..." - sleep 2 - done + # while ! ip link set "$VM_NET_TAP_2" up promisc on; do + # info "Waiting for TAP to become available..." + # sleep 2 + # done - if ! ip link set dev "$VM_NET_TAP_2" master dockerbridge_2; then - error "Failed to set IP link!" && return 1 - fi + # if ! ip link set dev "$VM_NET_TAP_2" master dockerbridge_2; then + # error "Failed to set IP link!" && return 1 + # fi # add initial default route as well if ! ip route add default dev dockerbridge via ${VM_NET_IP%.*}.1; then @@ -414,17 +426,17 @@ configureBridge() { NET_OPTS+=",script=no,downscript=no" - NET_OPTS+=" -netdev tap,id=hostnet1,ifname=$VM_NET_TAP_2" + # NET_OPTS+=" -netdev tap,id=hostnet1,ifname=$VM_NET_TAP_2" - if [ -c /dev/vhost-net ]; then - { - exec 41>>/dev/vhost-net - rc=$? - } 2>/dev/null || : - ((rc == 0)) && NET_OPTS+=",vhost=on,vhostfd=41" - fi + # if [ -c /dev/vhost-net ]; then + # { + # exec 41>>/dev/vhost-net + # rc=$? + # } 2>/dev/null || : + # ((rc == 0)) && NET_OPTS+=",vhost=on,vhostfd=41" + # fi - NET_OPTS+=",script=no,downscript=no" + # NET_OPTS+=",script=no,downscript=no" return 0 @@ -438,15 +450,13 @@ closeNetwork() { [[ "$NETWORK" == [Nn]* ]] && return 0 + exec 30<&- || true exec 40<&- || true - exec 41<&- || true if [[ "$DHCP" == [Yy1]* ]]; then ip link set "$VM_NET_TAP" down || true ip link delete "$VM_NET_TAP" || true - ip link set "$VM_NET_TAP_2" down || true - ip link delete "$VM_NET_TAP_2" || true else @@ -457,13 +467,9 @@ closeNetwork() { ip link set "$VM_NET_TAP" down promisc off || true ip link delete "$VM_NET_TAP" || true - ip link set "$VM_NET_TAP_2" down promisc off || true - ip link delete "$VM_NET_TAP_2" || true ip link set dockerbridge down || true ip link delete dockerbridge || true - ip link set dockerbridge_2 down || true - ip link delete dockerbridge_2 || true fi @@ -494,22 +500,28 @@ checkOS() { getInfo() { + if [ -z "$VM_NET_DEV" ]; then + # Give Kubernetes priority over the default interface + [ -d "/sys/class/net/net0" ] && VM_NET_DEV="net0" + [ -d "/sys/class/net/net1" ] && VM_NET_DEV="net1" + [ -d "/sys/class/net/net2" ] && VM_NET_DEV="net2" + [ -d "/sys/class/net/net3" ] && VM_NET_DEV="net3" + # Automaticly detect the default network interface + [ -z "$VM_NET_DEV" ] && VM_NET_DEV=$(awk '$2 == 00000000 { print $1 }' /proc/net/route) + [ -z "$VM_NET_DEV" ] && VM_NET_DEV="eth0" + fi + if [ ! -d "/sys/class/net/$VM_NET_DEV" ]; then error "Network interface '$VM_NET_DEV' does not exist inside the container!" error "$ADD_ERR -e \"VM_NET_DEV=NAME\" to specify another interface name." && exit 27 fi - if [ ! -d "/sys/class/net/$VM_NET_DEV_2" ]; then - error "Network interface '$VM_NET_DEV_2' does not exist inside the container!" - error "$ADD_ERR -e \"VM_NET_DEV_2=NAME\" to specify another interface name." && exit 27 - fi - if [ -z "$MAC" ]; then local file="$STORAGE/$PROCESS.mac" + [ -s "$file" ] && MAC=$(<"$file") if [ -z "$MAC" ]; then # Generate MAC address based on Docker container ID in hostname - MAC=$(printf '02:%02x:%02x:%02x:%02x:%02x\n' \ - $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256))) + MAC=$(echo "$HOST" | md5sum | sed 's/^\(..\)\(..\)\(..\)\(..\)\(..\).*$/02:\1:\2:\3:\4:\5/') echo "${MAC^^}" >"$file" fi fi @@ -526,34 +538,9 @@ getInfo() { error "Invalid MAC address: '$VM_NET_MAC', should be 12 or 17 digits long!" && exit 28 fi - if [ -z "$MAC_2" ]; then - local file="$STORAGE/$PROCESS.mac" - if [ -z "$MAC_2" ]; then - # Generate MAC address based on Docker container ID in hostname - MAC_2=$(printf '02:%02x:%02x:%02x:%02x:%02x\n' \ - $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256))) - echo "${MAC_2^^}" >>"$file" - fi - fi - - VM_NET_MAC_2="${MAC_2^^}" - VM_NET_MAC_2="${VM_NET_MAC_2//-/:}" - - if [[ ${#VM_NET_MAC_2} == 12 ]]; then - m="$VM_NET_MAC_2" - VM_NET_MAC_2="${m:0:2}:${m:2:2}:${m:4:2}:${m:6:2}:${m:8:2}:${m:10:2}" - fi - - if [[ ${#VM_NET_MAC_2} != 17 ]]; then - error "Invalid MAC address: '$VM_NET_MAC_2', should be 12 or 17 digits long!" && exit 28 - fi - GATEWAY=$(ip route list dev "$VM_NET_DEV" | awk ' /^default/ {print $3}') IP=$(ip address show dev "$VM_NET_DEV" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/) - GATEWAY_2=$(ip route list dev "$VM_NET_DEV_2" | awk ' /^default/ {print $3}') - IP_2=$(ip address show dev "$VM_NET_DEV_2" | grep inet | awk '/inet / { print $2 }' | cut -f1 -d/) - return 0 } @@ -574,67 +561,83 @@ if [[ "$DEBUG" == [Yy1]* ]]; then [ -f /etc/resolv.conf ] && grep '^nameserver*' /etc/resolv.conf echo fi -if [[ "$DEBUG" == [Yy1]* ]]; then - info "Host: $HOST IP: $IP_2 Gateway: $GATEWAY_2 Interface: $VM_NET_DEV_2 MAC: $VM_NET_MAC_2" - [ -f /etc/resolv.conf ] && grep '^nameserver*' /etc/resolv.conf - echo -fi -if [[ "$IP" != "172."* ]] && [[ "$IP" != "10.8"* ]] && [[ "$IP" != "10.9"* ]]; then +if [[ "$DHCP" == [Yy1]* ]]; then + checkOS -fi -if [[ "${NETWORK,,}" == "user"* ]]; then - - # Configure for usermode networking (slirp) - configureUser || exit 24 - -elif [[ "${NETWORK,,}" == "bridge"* ]]; then - # CONFIGURE Bridge - html "Configuring bridged network" - - if ! configureBridge; then - - error "Failed to setup bridge networking" - - ip link set "$VM_NET_TAP" down promisc off &>null || true - ip link delete "$VM_NET_TAP" &>null || true - ip link set "$VM_NET_TAP_2" down promisc off &>null || true - ip link delete "$VM_NET_TAP_2" &>null || true - - ip link set dockerbridge down &>null || true - ip link delete dockerbridge &>null || true - ip link set dockerbridge_2 down &>null || true - ip link delete dockerbridge_2 &>null || true - - exit 25 + if [[ "$IP" == "172."* ]]; then + warn "container IP starts with 172.* which is often a sign that you are not on a macvlan network (required for DHCP)!" fi + # Configure for macvtap interface + configureDHCP || exit 20 + else - # Configure for tap interface - if ! configureNAT; then + if [[ "$IP" != "172."* ]] && [[ "$IP" != "10.8"* ]] && [[ "$IP" != "10.9"* ]]; then + checkOS + fi - error "Failed to setup NAT networking" + if [[ "${NETWORK,,}" == [Yy1]* ]]; then - ip link set "$VM_NET_TAP" down promisc off &>null || true - ip link delete "$VM_NET_TAP" &>null || true - ip link set "$VM_NET_TAP_2" down promisc off &>null || true - ip link delete "$VM_NET_TAP_2" &>null || true + # Configure for tap interface + if ! configureNAT; then - ip link set dockerbridge down &>null || true - ip link delete dockerbridge &>null || true - ip link set dockerbridge_2 down &>null || true - ip link delete dockerbridge_2 &>null || true + NETWORK="user" + warn "falling back to usermode networking! Performance will be bad and port mapping will not work." - exit 25 + ip link set "$VM_NET_TAP" down promisc off &>null || true + ip link delete "$VM_NET_TAP" &>null || true + + ip link set dockerbridge down &>null || true + ip link delete dockerbridge &>null || true + + fi + + fi + + if [[ "${NETWORK,,}" == "user"* ]]; then + + # Configure for usermode networking (slirp) + configureUser || exit 24 + + fi + + if [[ "${NETWORK,,}" == "bridge"* ]]; then + + # Configure for usermode networking (slirp) + # CONFIGURE Bridge + html "Configuring bridged network" + + if ! configureBridge; then + + error "Failed to setup bridge networking" + + ip link set "$VM_NET_TAP" down promisc off &>null || true + ip link delete "$VM_NET_TAP" &>null || true + # ip link set "$VM_NET_TAP_2" down promisc off &>null || true + # ip link delete "$VM_NET_TAP_2" &>null || true + + ip link set dockerbridge down &>null || true + ip link delete dockerbridge &>null || true + # ip link set dockerbridge_2 down &>null || true + # ip link delete dockerbridge_2 &>null || true + + exit 25 + fi fi fi NET_OPTS+=" -device $ADAPTER,romfile=,netdev=hostnet0,mac=$VM_NET_MAC,id=net0" -NET_OPTS+=" -device $ADAPTER_2,romfile=,netdev=hostnet1,mac=$VM_NET_MAC_2,id=net1" +# NET_OPTS+=" -device $ADAPTER_2,romfile=,netdev=hostnet1,mac=$VM_NET_MAC_2,id=net1" + +NET_OPTS+=" -device virtio-serial-pci,id=virtserial0,bus=pcie.0,addr=0x6" +NET_OPTS+=" -chardev socket,id=qga0,path=/tmp/qga.sock,server=on,wait=off" +NET_OPTS+=" -device virtio-serial" +NET_OPTS+=" -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0" html "Initialized network successfully..." return 0 diff --git a/src/power.sh b/src/power.sh index e7ceaff..3e36701 100644 --- a/src/power.sh +++ b/src/power.sh @@ -36,6 +36,7 @@ boot() { fi if [ -z "$fail" ]; then info "Windows started succesfully, visit http://localhost:8006/ to view the screen..." + touch "$STORAGE/ready" return 0 fi fi @@ -161,6 +162,10 @@ _graceful_shutdown() { set +e + if [ -f "$STORAGE/ready" ]; then + rm $STORAGE/ready + fi + if [ -f "$QEMU_END" ]; then info "Received $1 while already shutting down..." return From 7323ae399b2dbd85ede41f1b61b95c78a69031e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gytis=20Sto=C5=A1kevi=C4=8Dius?= <gytisstoskevicius@gmail.com> Date: Mon, 31 Mar 2025 15:51:22 +0300 Subject: [PATCH 07/17] add static network interface configuration when network is bridge --- src/entry.sh | 47 +++++++++++++++++++++++-------------- src/network.sh | 63 ++++++++++++++++++++++++++++++++++++++++++++++---- src/power.sh | 15 ++++++------ 3 files changed, 95 insertions(+), 30 deletions(-) mode change 100644 => 100755 src/entry.sh mode change 100644 => 100755 src/network.sh diff --git a/src/entry.sh b/src/entry.sh old mode 100644 new mode 100755 index b037cdc..18d8e49 --- a/src/entry.sh +++ b/src/entry.sh @@ -8,32 +8,45 @@ SUPPORT="https://github.com/dockur/windows" cd /run -. reset.sh # Initialize system -. define.sh # Define versions -. mido.sh # Download code -. install.sh # Run installation -. disk.sh # Initialize disks -. display.sh # Initialize graphics -. network.sh # Initialize network -. samba.sh # Configure samba -. boot.sh # Configure boot -. proc.sh # Initialize processor -. power.sh # Configure shutdown -. config.sh # Configure arguments +. reset.sh # Initialize system +. define.sh # Define versions +. mido.sh # Download code +. install.sh # Run installation +. disk.sh # Initialize disks +. display.sh # Initialize graphics +. network.sh # Initialize network +. samba.sh # Configure samba +. boot.sh # Configure boot +. proc.sh # Initialize processor +. power.sh # Configure shutdown +. config.sh # Configure arguments trap - ERR version=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1 | awk '{ print $NF }') info "Booting ${APP}${BOOT_DESC} using QEMU v$version..." -{ qemu-system-x86_64 ${ARGS:+ $ARGS} >"$QEMU_OUT" 2>"$QEMU_LOG"; rc=$?; } || : -(( rc != 0 )) && error "$(<"$QEMU_LOG")" && exit 15 +{ + qemu-system-x86_64 ${ARGS:+ $ARGS} >"$QEMU_OUT" 2>"$QEMU_LOG" + rc=$? +} || : +((rc != 0)) && error "$(<"$QEMU_LOG")" && exit 15 terminal -( sleep 30; boot ) & +( + 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..." + fi + touch "$STORAGE/ready" +) & tail -fn +0 "$QEMU_LOG" 2>/dev/null & -cat "$QEMU_TERM" 2> /dev/null | tee "$QEMU_PTY" & +cat "$QEMU_TERM" 2>/dev/null | tee "$QEMU_PTY" & wait $! || : -sleep 1 & wait $! +sleep 1 & +wait $! [ ! -f "$QEMU_END" ] && finish 0 diff --git a/src/network.sh b/src/network.sh old mode 100644 new mode 100755 index da15ef0..13835fb --- a/src/network.sh +++ b/src/network.sh @@ -25,6 +25,64 @@ ADD_ERR="Please add the following setting to your container:" # ###################################### # Functions # ###################################### +find_free_ip() { + local current_ip="$1" + local mask="$2" + + # Get network prefix + IFS='.' read -r i1 i2 i3 i4 <<<"$current_ip" + IFS='.' read -r m1 m2 m3 m4 <<<"$(ip -o -f inet addr show | awk '/scope global/ {print $4}' | cut -d'/' -f2)" + + network_ip=$((i1 & m1)).$((i2 & m2)).$((i3 & m3)).0 + base_ip="$i1.$i2.$i3" + + # Iterate over available IPs + for i in {2..254}; do + new_ip="$base_ip.$i" + if [[ "$new_ip" != "$current_ip" ]] && ! ping -c 1 -W 1 "$new_ip" &>/dev/null; then + echo "$new_ip" + return + fi + done + + echo "No free IP found" +} + +configure_guest_network_interface() { + if [[ "${NETWORK,,}" == "bridge"* ]]; then + + HOST_INTERFACE="dockerbridge" + CURRENT_IP=$(ip addr show $HOST_INTERFACE | grep -oP 'inet \K[\d.]+') + MASK="$(ip -4 addr show $HOST_INTERFACE | awk '/inet / {print $2}' | cut -d'/' -f2)" + + if [ -z "$CURRENT_IP" ]; then + echo "Error: Unable to retrieve the current IP address of $HOST_INTERFACE." + exit 1 + fi + + echo "Current Host IP: $CURRENT_IP" + + IFS='.' read -r -a ip_parts <<<"$CURRENT_IP" + NEW_HOST_IP=$(find_free_ip "$CURRENT_IP" "$MASK") + GW="${ip_parts[0]}.${ip_parts[1]}.${ip_parts[2]}.1" + + echo "New Host IP: $NEW_HOST_IP" + + ip addr del $CURRENT_IP/$MASK dev $HOST_INTERFACE + ip addr add $NEW_HOST_IP/$MASK dev $HOST_INTERFACE + + ip link set $HOST_INTERFACE down + ip link set $HOST_INTERFACE up + + route add default gw $GW + + echo -e '{"execute": "guest-exec", "arguments": {"path": "C:\\\\Windows\\\\System32\\\\netsh.exe", "arg": ["interface", "ipv4", "set", "address", "name=\\"Ethernet\\"", "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", "arg": ["interface", "ipv4", "add", "dnsservers", "name=\\"Ethernet\\"", "address=\\"'$GW'\\"", "index=\\"1\\""]}}' | nc -U /tmp/qga.sock -w 5 + + fi + + return 0 +} configureDHCP() { @@ -409,11 +467,6 @@ configureBridge() { # error "Failed to set IP link!" && return 1 # fi - # add initial default route as well - if ! ip route add default dev dockerbridge via ${VM_NET_IP%.*}.1; then - error "Failed to setup default route" && return 10 - fi - NET_OPTS="-netdev tap,id=hostnet0,ifname=$VM_NET_TAP" if [ -c /dev/vhost-net ]; then diff --git a/src/power.sh b/src/power.sh index 3e36701..b619f5f 100644 --- a/src/power.sh +++ b/src/power.sh @@ -17,8 +17,9 @@ rm -f "$QEMU_DIR/qemu.*" touch "$QEMU_LOG" _trap() { - func="$1" ; shift - for sig ; do + func="$1" + shift + for sig; do trap "$func $sig" "$sig" done } @@ -35,8 +36,6 @@ boot() { grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && fail="y" fi if [ -z "$fail" ]; then - info "Windows started succesfully, visit http://localhost:8006/ to view the screen..." - touch "$STORAGE/ready" return 0 fi fi @@ -129,7 +128,7 @@ terminal() { if [ -n "$msg" ]; then - if [[ "${msg,,}" != "char"* || "$msg" != *"serial0)" ]]; then + if [[ "${msg,,}" != "char"* || "$msg" != *"serial0)" ]]; then echo "$msg" fi @@ -193,13 +192,13 @@ _graceful_shutdown() { fi # Send ACPI shutdown signal - echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null + echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" >/dev/null local cnt=0 while [ "$cnt" -lt "$QEMU_TIMEOUT" ]; do sleep 1 - cnt=$((cnt+1)) + cnt=$((cnt + 1)) ! isAlive "$pid" && break # Workaround for zombie pid @@ -208,7 +207,7 @@ _graceful_shutdown() { info "Waiting for Windows to shutdown... ($cnt/$QEMU_TIMEOUT)" # Send ACPI shutdown signal - echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null + echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" >/dev/null done From 725d9296829ae9bdf277526a262175cb6fd46725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gytis=20Sto=C5=A1kevi=C4=8Dius?= <gytisstoskevicius@gmail.com> Date: Mon, 31 Mar 2025 17:51:31 +0300 Subject: [PATCH 08/17] Make default network bridge --- compose.yml | 5 +---- src/network.sh | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/compose.yml b/compose.yml index 57ad009..b57b65e 100644 --- a/compose.yml +++ b/compose.yml @@ -14,13 +14,12 @@ services: USERNAME: "bill" PASSWORD: "gates" DISK_FMT: "qcow2" + NETWORK: "Y" devices: - /dev/kvm - /dev/net/tun cap_add: - NET_ADMIN - ports: - - 8006:8006 stop_grace_period: 2m volumes: - ./scripts:/oem @@ -46,6 +45,4 @@ services: - /dev/net/tun cap_add: - NET_ADMIN - ports: - - 8006:8006 stop_grace_period: 2m diff --git a/src/network.sh b/src/network.sh index 13835fb..53b61b8 100755 --- a/src/network.sh +++ b/src/network.sh @@ -5,7 +5,7 @@ set -Eeuo pipefail : "${MAC:=""}" : "${DHCP:="N"}" -: "${NETWORK:="Y"}" +: "${NETWORK:="bridge"}" : "${USER_PORTS:=""}" : "${HOST_PORTS:=""}" : "${ADAPTER:="virtio-net-pci"}" From 34cac42db588236d82b8a3d9abdb9610706b8254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gytis=20Sto=C5=A1kevi=C4=8Dius?= <gytisstoskevicius@gmail.com> Date: Mon, 31 Mar 2025 18:23:21 +0300 Subject: [PATCH 09/17] Make prepare image more verbose --- prepare_image.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prepare_image.sh b/prepare_image.sh index 19cde37..96689ce 100755 --- a/prepare_image.sh +++ b/prepare_image.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -Eeuo pipefail +set -Eeuox pipefail source env.sh From 6e08a5a2eab15fba75bed680c4dc34a67bb0fa8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gytis=20Sto=C5=A1kevi=C4=8Dius?= <gytisstoskevicius@gmail.com> Date: Mon, 31 Mar 2025 18:30:06 +0300 Subject: [PATCH 10/17] Add debug --- compose.yml | 1 + prepare_image.sh | 2 +- src/define.sh | 2 +- src/entry.sh | 2 +- src/install.sh | 2 +- src/mido.sh | 2 +- src/network.sh | 2 +- src/power.sh | 2 +- src/samba.sh | 2 +- 9 files changed, 9 insertions(+), 8 deletions(-) diff --git a/compose.yml b/compose.yml index b57b65e..ac286af 100644 --- a/compose.yml +++ b/compose.yml @@ -15,6 +15,7 @@ services: PASSWORD: "gates" DISK_FMT: "qcow2" NETWORK: "Y" + DEBUG: "Y" devices: - /dev/kvm - /dev/net/tun diff --git a/prepare_image.sh b/prepare_image.sh index 96689ce..90bfa33 100755 --- a/prepare_image.sh +++ b/prepare_image.sh @@ -4,7 +4,7 @@ set -Eeuox pipefail source env.sh echo "start to build and install windows" -docker compose up windows-build -d --wait --build +docker compose up windows-build --build echo "windows installed, now stop container" docker stop windows-build diff --git a/src/define.sh b/src/define.sh index a245248..279468b 100644 --- a/src/define.sh +++ b/src/define.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -Eeuo pipefail +set -Eeuox pipefail : "${WIDTH:=""}" : "${HEIGHT:=""}" diff --git a/src/entry.sh b/src/entry.sh index 18d8e49..6574d91 100755 --- a/src/entry.sh +++ b/src/entry.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -Eeuo pipefail +set -Eeuox pipefail : "${BOOT_MODE:="windows"}" diff --git a/src/install.sh b/src/install.sh index 54eebd5..f392c0e 100644 --- a/src/install.sh +++ b/src/install.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -Eeuo pipefail +set -Eeuox pipefail TMP="$STORAGE/tmp" DIR="$TMP/unpack" diff --git a/src/mido.sh b/src/mido.sh index 7c6112b..cd4171f 100644 --- a/src/mido.sh +++ b/src/mido.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -Eeuo pipefail +set -Eeuox pipefail handle_curl_error() { diff --git a/src/network.sh b/src/network.sh index 53b61b8..1fa4071 100755 --- a/src/network.sh +++ b/src/network.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -Eeuo pipefail +set -Eeuox pipefail # Docker environment variables diff --git a/src/power.sh b/src/power.sh index b619f5f..adc8fc6 100644 --- a/src/power.sh +++ b/src/power.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -Eeuo pipefail +set -Eeuox pipefail # Configure QEMU for graceful shutdown diff --git a/src/samba.sh b/src/samba.sh index e2c12c9..321d05d 100644 --- a/src/samba.sh +++ b/src/samba.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -Eeuo pipefail +set -Eeuox pipefail : "${SAMBA:="Y"}" From 5ed1a0c413d158ded3d83b4342f8168a30dafde6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gytis=20Sto=C5=A1kevi=C4=8Dius?= <gytisstoskevicius@gmail.com> Date: Tue, 1 Apr 2025 11:00:21 +0300 Subject: [PATCH 11/17] Restore dettached compose up --- prepare_image.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prepare_image.sh b/prepare_image.sh index 90bfa33..96689ce 100755 --- a/prepare_image.sh +++ b/prepare_image.sh @@ -4,7 +4,7 @@ set -Eeuox pipefail source env.sh echo "start to build and install windows" -docker compose up windows-build --build +docker compose up windows-build -d --wait --build echo "windows installed, now stop container" docker stop windows-build From a5c2af4742e629e73b6961bf29236864f62b6fe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gytis=20Sto=C5=A1kevi=C4=8Dius?= <gytisstoskevicius@gmail.com> Date: Tue, 1 Apr 2025 11:11:22 +0300 Subject: [PATCH 12/17] update prepare script --- prepare_image.sh | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/prepare_image.sh b/prepare_image.sh index 96689ce..93f97dc 100755 --- a/prepare_image.sh +++ b/prepare_image.sh @@ -1,10 +1,18 @@ #!/usr/bin/env bash -set -Eeuox pipefail +set -Eeuo pipefail source env.sh echo "start to build and install windows" -docker compose up windows-build -d --wait --build +docker compose up windows-build -d --build + +echo "streaming logs..." +docker logs -f windows-build | tee windows-build.log & + +echo "waiting for windows-build container to be healthy..." +while [[ "$(docker inspect --format='{{.State.Health.Status}}' windows-build 2>/dev/null)" != "healthy" ]]; do + sleep 2 +done echo "windows installed, now stop container" docker stop windows-build @@ -13,7 +21,15 @@ echo "commit all the changes" docker commit windows-build "$IMAGE_NAME:$IMAGE_VERSION" docker images -docker push "$IMAGE_NAME:$IMAGE_VERSION" - echo "start container with windows installed" -docker compose up windows-installed -d --wait +docker compose up windows-installed -d + +echo "streaming logs..." +docker logs -f windows-installed | tee windows-installed.log & + +echo "waiting for windows-installed container to be healthy..." +while [[ "$(docker inspect --format='{{.State.Health.Status}}' windows-installed 2>/dev/null)" != "healthy" ]]; do + sleep 2 +done + +docker push "$IMAGE_NAME:$IMAGE_VERSION" From 0528800424cc9b889bc08035c5fb374acd1d4cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gytis=20Sto=C5=A1kevi=C4=8Dius?= <gytisstoskevicius@gmail.com> Date: Tue, 1 Apr 2025 15:29:25 +0300 Subject: [PATCH 13/17] Add option to have multiple bridges --- src/network.sh | 236 ++++++++++++++++++++++--------------------------- 1 file changed, 105 insertions(+), 131 deletions(-) diff --git a/src/network.sh b/src/network.sh index 1fa4071..17d7f0e 100755 --- a/src/network.sh +++ b/src/network.sh @@ -20,6 +20,7 @@ set -Eeuox pipefail : "${DNSMASQ:="/usr/sbin/dnsmasq"}" : "${DNSMASQ_CONF_DIR:="/etc/dnsmasq.d"}" +ETH_COUNT=$(ls /sys/class/net | grep -E '^eth[0-9]+$' | wc -l) ADD_ERR="Please add the following setting to your container:" # ###################################### @@ -50,34 +51,42 @@ find_free_ip() { configure_guest_network_interface() { if [[ "${NETWORK,,}" == "bridge"* ]]; then + for ((i = 0; i < ETH_COUNT; i++)); do + HOST_INTERFACE="dockerbridge$i" + CURRENT_IP=$(ip addr show $HOST_INTERFACE | grep -oP 'inet \K[\d.]+') + MASK="$(ip -4 addr show $HOST_INTERFACE | awk '/inet / {print $2}' | cut -d'/' -f2)" - HOST_INTERFACE="dockerbridge" - CURRENT_IP=$(ip addr show $HOST_INTERFACE | grep -oP 'inet \K[\d.]+') - MASK="$(ip -4 addr show $HOST_INTERFACE | awk '/inet / {print $2}' | cut -d'/' -f2)" + if [ -z "$CURRENT_IP" ]; then + echo "Error: Unable to retrieve the current IP address of $HOST_INTERFACE." + exit 1 + fi - if [ -z "$CURRENT_IP" ]; then - echo "Error: Unable to retrieve the current IP address of $HOST_INTERFACE." - exit 1 - fi + echo "Current Host IP: $CURRENT_IP" - echo "Current Host IP: $CURRENT_IP" + IFS='.' read -r -a ip_parts <<<"$CURRENT_IP" + NEW_HOST_IP=$(find_free_ip "$CURRENT_IP" "$MASK") + GW="${ip_parts[0]}.${ip_parts[1]}.${ip_parts[2]}.1" - IFS='.' read -r -a ip_parts <<<"$CURRENT_IP" - NEW_HOST_IP=$(find_free_ip "$CURRENT_IP" "$MASK") - GW="${ip_parts[0]}.${ip_parts[1]}.${ip_parts[2]}.1" + echo "New Host IP: $NEW_HOST_IP" - echo "New Host IP: $NEW_HOST_IP" + ip addr del $CURRENT_IP/$MASK dev $HOST_INTERFACE + ip addr add $NEW_HOST_IP/$MASK dev $HOST_INTERFACE - ip addr del $CURRENT_IP/$MASK dev $HOST_INTERFACE - ip addr add $NEW_HOST_IP/$MASK dev $HOST_INTERFACE + ip link set $HOST_INTERFACE down + ip link set $HOST_INTERFACE up - ip link set $HOST_INTERFACE down - ip link set $HOST_INTERFACE up + route add default gw $GW - route add default gw $GW + if [ $i -eq 0 ]; then + INTERFACE_NAME="Ethernet" + else + IDX=$((1 + i)) + INTERFACE_NAME="Ethernet $IDX" + fi - echo -e '{"execute": "guest-exec", "arguments": {"path": "C:\\\\Windows\\\\System32\\\\netsh.exe", "arg": ["interface", "ipv4", "set", "address", "name=\\"Ethernet\\"", "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", "arg": ["interface", "ipv4", "add", "dnsservers", "name=\\"Ethernet\\"", "address=\\"'$GW'\\"", "index=\\"1\\""]}}' | 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", "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 + done fi @@ -379,120 +388,72 @@ configureBridge() { fi fi - # Create a bridge with a static IP for the VM guest + for ((i = 0; i < ETH_COUNT; i++)); do + DOCKER_BRIDGE="dockerbridge$i" + NET_DEV="eth$i" + NET_TAP="qemu$i" + { + ip link add dev $DOCKER_BRIDGE type bridge + rc=$? + } || : - { - ip link add dev dockerbridge type bridge - rc=$? - } || : + if ((rc != 0)); then + error "Failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1 + fi - if ((rc != 0)); then - error "Failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1 - fi + # We need freshly created bridge to have IP address of the container + # For this reason we need to migrate IP from eth0 to dockerbridge. + for addr in $(ip --json addr show dev $NET_DEV | jq -c '.[0].addr_info[] | select(.family == "inet")'); do + cidr_addr=$(echo $addr | jq -r '[ .local, .prefixlen|tostring] | join("/")') + if ! ip addr add dev $DOCKER_BRIDGE $cidr_addr; then + error "Failed to add address for $DOCKER_BRIDGE interface" + exit 30 + fi + done - # { - # ip link add dev dockerbridge_2 type bridge - # rc=$? - # } || : - # if ((rc != 0)); then - # error "Failed to create bridge. $ADD_ERR --cap-add NET_ADMIN" && return 1 - # fi - - # We need freshly created bridge to have IP address of the container - # For this reason we need to migrate IP from eth0 to dockerbridge. - for addr in $(ip --json addr show dev $VM_NET_DEV | jq -c '.[0].addr_info[] | select(.family == "inet")'); do - cidr_addr=$(echo $addr | jq -r '[ .local, .prefixlen|tostring] | join("/")') - if ! ip addr add dev dockerbridge $cidr_addr; then - error "Failed to add address for dockerbridge interface" + if ! ip addr flush dev $NET_DEV; then + error "Failed to clear $NET_DEV interface addresses" exit 30 fi - done - if ! ip addr flush dev $VM_NET_DEV; then - error "Failed to clear $VM_NET_DEV interface addresses" - exit 30 - fi - while ! ip link set dockerbridge up; do - info "Waiting for IP address to become available..." - sleep 2 - done + while ! ip link set $DOCKER_BRIDGE up; do + info "Waiting for IP address to become available..." + sleep 2 + done - # # We need freshly created bridge to have IP address of the container - # # For this reason we need to migrate IP from eth0 to dockerbridge. - # for addr in $(ip --json addr show dev $VM_NET_DEV_2 | jq -c '.[0].addr_info[] | select(.family == "inet")'); do - # cidr_addr=$(echo $addr | jq -r '[ .local, .prefixlen|tostring] | join("/")') - # if ! ip addr add dev dockerbridge_2 $cidr_addr; then - # error "Failed to add address for dockerbridge_2 interface" - # exit 30 - # fi - # done - # if ! ip addr flush dev $VM_NET_DEV_2; then - # error "Failed to clear $VM_NET_DEV_2 interface addresses" - # exit 30 - # fi + # QEMU Works with taps, set tap to the bridge created + if ! ip tuntap add dev "$NET_TAP" mode tap; then + error "$tuntap" && return 1 + fi - # while ! ip link set dockerbridge_2 up; do - # info "Waiting for IP address to become available..." - # sleep 2 - # done + while ! ip link set "$NET_TAP" up promisc on; do + info "Waiting for TAP to become available..." + sleep 2 + done - # QEMU Works with taps, set tap to the bridge created - if ! ip tuntap add dev "$VM_NET_TAP" mode tap; then - error "$tuntap" && return 1 - fi + if ! ip link set dev "$NET_TAP" master $DOCKER_BRIDGE; then + error "Failed to set IP link!" && return 1 + fi - while ! ip link set "$VM_NET_TAP" up promisc on; do - info "Waiting for TAP to become available..." - sleep 2 - done + if ! ip link set dev "$NET_DEV" master $DOCKER_BRIDGE; then + error "Failed to attach docker interface to bridge" + fi - if ! ip link set dev "$VM_NET_TAP" master dockerbridge; then - error "Failed to set IP link!" && return 1 - fi - - if ! ip link set dev "$VM_NET_DEV" master dockerbridge; then - error "Failed to attach docker interface to bridge" - fi - - # if ! ip tuntap add dev "$VM_NET_TAP_2" mode tap; then - # error "$tuntap" && return 1 - # fi - - # while ! ip link set "$VM_NET_TAP_2" up promisc on; do - # info "Waiting for TAP to become available..." - # sleep 2 - # done - - # if ! ip link set dev "$VM_NET_TAP_2" master dockerbridge_2; then - # error "Failed to set IP link!" && return 1 - # fi - - NET_OPTS="-netdev tap,id=hostnet0,ifname=$VM_NET_TAP" - - if [ -c /dev/vhost-net ]; then - { - exec 40>>/dev/vhost-net + NET_OPTS+=" -netdev tap,id=hostnet$i,ifname=$NET_TAP" + if [ -c /dev/vhost-net ]; then + fd=$((40 + i)) + eval "exec $fd>>/dev/vhost-net" rc=$? - } 2>/dev/null || : - ((rc == 0)) && NET_OPTS+=",vhost=on,vhostfd=40" - fi + if ((rc == 0)); then + NET_OPTS+=",vhost=on,vhostfd=$fd" + fi + fi - NET_OPTS+=",script=no,downscript=no" + NET_OPTS+=",script=no,downscript=no " - # NET_OPTS+=" -netdev tap,id=hostnet1,ifname=$VM_NET_TAP_2" - - # if [ -c /dev/vhost-net ]; then - # { - # exec 41>>/dev/vhost-net - # rc=$? - # } 2>/dev/null || : - # ((rc == 0)) && NET_OPTS+=",vhost=on,vhostfd=41" - # fi - - # NET_OPTS+=",script=no,downscript=no" + done return 0 - } closeNetwork() { @@ -518,11 +479,21 @@ closeNetwork() { [[ "${NETWORK,,}" == "user"* ]] && return 0 - ip link set "$VM_NET_TAP" down promisc off || true - ip link delete "$VM_NET_TAP" || true + if [[ "${NETWORK,,}" == "bridge"* ]]; then + for ((i = 0; i < ETH_COUNT; i++)); do + ip link set "qemu$i" down promisc off || true + ip link delete "qemu$i" || true - ip link set dockerbridge down || true - ip link delete dockerbridge || true + ip link set dockerbridge$i down || true + ip link delete dockerbridge$i || true + done + else + ip link set "$VM_NET_TAP" down promisc off || true + ip link delete "$VM_NET_TAP" || true + + ip link set dockerbridge down || true + ip link delete dockerbridge || true + fi fi @@ -666,16 +637,13 @@ else if ! configureBridge; then error "Failed to setup bridge networking" + for ((i = 0; i < ETH_COUNT; i++)); do + ip link set "$VM_NET_TAP$i" down promisc off &>null || true + ip link delete "$VM_NET_TAP$i" &>null || true - ip link set "$VM_NET_TAP" down promisc off &>null || true - ip link delete "$VM_NET_TAP" &>null || true - # ip link set "$VM_NET_TAP_2" down promisc off &>null || true - # ip link delete "$VM_NET_TAP_2" &>null || true - - ip link set dockerbridge down &>null || true - ip link delete dockerbridge &>null || true - # ip link set dockerbridge_2 down &>null || true - # ip link delete dockerbridge_2 &>null || true + ip link set dockerbridge$i down &>null || true + ip link delete dockerbridge$i &>null || true + done exit 25 fi @@ -685,7 +653,13 @@ else fi NET_OPTS+=" -device $ADAPTER,romfile=,netdev=hostnet0,mac=$VM_NET_MAC,id=net0" -# NET_OPTS+=" -device $ADAPTER_2,romfile=,netdev=hostnet1,mac=$VM_NET_MAC_2,id=net1" + +if [[ "${NETWORK,,}" == "bridge"* ]]; then + for ((i = 1; i < ETH_COUNT; i++)); do + MAC=$(printf "52:54:00:%02X:%02X:%02X" $((RANDOM % 256)) $((RANDOM % 256)) $((RANDOM % 256))) + NET_OPTS+=" -device $ADAPTER,romfile=,netdev=hostnet$i,mac=$MAC,id=net$i" + done +fi NET_OPTS+=" -device virtio-serial-pci,id=virtserial0,bus=pcie.0,addr=0x6" NET_OPTS+=" -chardev socket,id=qga0,path=/tmp/qga.sock,server=on,wait=off" From d04cc507f0a2b79a9b474cb5f387a21d05316079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gytis=20Sto=C5=A1kevi=C4=8Dius?= <gytis.stoskevicius@nordsec.com> Date: Fri, 4 Apr 2025 13:53:19 +0000 Subject: [PATCH 14/17] add extra_script option and qemu-guest-agen sock wrapper --- custom.xml | 5 ++ scripts/enable_sshd.ps1 | 62 ++++++++++++++++++ scripts/install.bat | 1 + src/entry.sh | 32 ++++++++-- src/network.sh | 34 ++++++++-- src/qga.py | 138 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 263 insertions(+), 9 deletions(-) create mode 100644 scripts/enable_sshd.ps1 create mode 100644 src/qga.py diff --git a/custom.xml b/custom.xml index d630612..de1a76b 100644 --- a/custom.xml +++ b/custom.xml @@ -465,6 +465,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>25</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v "IPAutoconfigurationEnabled" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Disable ip autoconfiguration for network interfaces</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>26</Order> <CommandLine>cmd /C "type nul > \\host.lan\Data\prepared"</CommandLine> <Description>Let host known that all configuration is done</Description> </SynchronousCommand> 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) From 49bd845e77d6f5fa52ce827047f6011f2ca2783f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gytis=20Sto=C5=A1kevi=C4=8Dius?= <gytisstoskevicius@gmail.com> Date: Mon, 7 Apr 2025 11:34:15 +0300 Subject: [PATCH 15/17] wait for interface to be up --- src/network.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/network.sh b/src/network.sh index 60dbcf2..e9b82c9 100755 --- a/src/network.sh +++ b/src/network.sh @@ -84,6 +84,21 @@ configure_guest_network_interface() { INTERFACE_NAME="Ethernet $IDX" fi + RETRIES=10 + for i in $(seq 1 $RETRIES); do + OUTPUT=$(python3 /run/qga.py powershell -Command "(\$(Get-NetAdapter -Name '$INTERFACE_NAME').Status)") + STATUS=$(echo "$OUTPUT" | grep -A1 'STDOUT:' | tail -n1 | tr -d '\r' | xargs) + + echo "Status: '$STATUS'" + if [[ "$STATUS" == "Up" ]]; then + echo "Interface '$INTERFACE_NAME' is up!" + break + else + echo "Waiting for interface '$INTERFACE_NAME' to be up... ($i/$RETRIES)" + sleep $SLEEP + fi + done + exit_code=0 python3 /run/qga.py powershell -Command "Set-NetIPInterface -InterfaceAlias '$INTERFACE_NAME' -Dhcp Disabled" || exit_code=$? if [[ $exit_code -ne 0 ]]; then From 0797f5899aba9cd21dbd3a88cc0a485c021605ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gytis=20Sto=C5=A1kevi=C4=8Dius?= <gytis.stoskevicius@nordsec.com> Date: Tue, 8 Apr 2025 11:50:56 +0000 Subject: [PATCH 16/17] wait for interface to be up --- .devcontainer.json | 6 + Dockerfile | 11 +- assets/win10x64-enterprise-eval.xml | 5 + assets/win10x64-enterprise.xml | 5 + assets/win10x64-iot.xml | 5 + assets/win10x64-ltsc.xml | 5 + assets/win10x64.xml | 5 + assets/win11x64-enterprise-eval.xml | 31 +- assets/win11x64-enterprise.xml | 32 +- assets/win11x64-iot.xml | 31 +- assets/win11x64-ltsc.xml | 31 +- assets/win11x64.xml | 31 +- assets/win2008r2-eval.xml | 5 + assets/win2008r2.xml | 5 + assets/win2012r2-eval.xml | 5 + assets/win2012r2.xml | 5 + assets/win2016-eval.xml | 5 + assets/win2016.xml | 5 + assets/win2019-eval.xml | 5 + custom.xml => assets/win2019-hv.xml | 204 +++--------- assets/win2019.xml | 5 + assets/win2022-eval.xml | 5 + assets/win2022.xml | 5 + assets/win2025-eval.xml | 13 +- assets/win2025.xml | 13 +- assets/win7x64-enterprise-eval.xml | 288 +++++++++++++++++ assets/win7x64-enterprise.xml | 5 + assets/win7x64-ultimate.xml | 5 + assets/win7x64.xml | 5 + assets/win7x86-enterprise.xml | 5 + assets/win7x86-ultimate.xml | 5 + assets/win7x86.xml | 5 + assets/win81x64-enterprise-eval.xml | 5 + assets/win81x64-enterprise.xml | 5 + assets/win81x64.xml | 5 + assets/winvistax64-enterprise.xml | 5 + assets/winvistax64-ultimate.xml | 5 + assets/winvistax64.xml | 5 + assets/winvistax86-enterprise.xml | 5 + assets/winvistax86-ultimate.xml | 5 + assets/winvistax86.xml | 5 + compose.yml | 16 +- kubernetes.yml | 127 +++++--- prepare_image.sh | 2 +- readme.md | 220 +++++++------ src/define.sh | 482 ++++++++++++++-------------- src/entry.sh | 14 +- src/install.sh | 221 +++++++++---- src/mido.sh | 362 +++------------------ src/network.sh | 6 +- src/samba.sh | 9 + 51 files changed, 1296 insertions(+), 1004 deletions(-) create mode 100644 .devcontainer.json rename custom.xml => assets/win2019-hv.xml (63%) create mode 100644 assets/win7x64-enterprise-eval.xml diff --git a/.devcontainer.json b/.devcontainer.json new file mode 100644 index 0000000..14d70b4 --- /dev/null +++ b/.devcontainer.json @@ -0,0 +1,6 @@ +{ + "name": "windows", + "service": "windows", + "forwardPorts": [8006], + "dockerComposeFile": "compose.yml" +} diff --git a/Dockerfile b/Dockerfile index ba6997b..384d40b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ARG VERSION_ARG="latest" FROM scratch AS build-amd64 -COPY --from=qemux/qemu-docker:6.13 / / +COPY --from=qemux/qemu:7.10 / / ARG DEBCONF_NOWARNINGS="yes" ARG DEBIAN_FRONTEND="noninteractive" @@ -10,19 +10,14 @@ ARG DEBCONF_NONINTERACTIVE_SEEN="true" RUN set -eu && \ apt-get update && \ apt-get --no-install-recommends -y install \ - bc \ - jq \ - curl \ - 7zip \ wsdd \ samba \ - xz-utils \ wimtools \ dos2unix \ cabextract \ - genisoimage \ libxml2-utils \ - libarchive-tools && \ + libarchive-tools \ + netcat-openbsd && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* diff --git a/assets/win10x64-enterprise-eval.xml b/assets/win10x64-enterprise-eval.xml index df17cbe..15cf47e 100644 --- a/assets/win10x64-enterprise-eval.xml +++ b/assets/win10x64-enterprise-eval.xml @@ -415,6 +415,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>20</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>21</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win10x64-enterprise.xml b/assets/win10x64-enterprise.xml index 5b60828..8523316 100644 --- a/assets/win10x64-enterprise.xml +++ b/assets/win10x64-enterprise.xml @@ -418,6 +418,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>20</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>21</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win10x64-iot.xml b/assets/win10x64-iot.xml index 9516684..fc80dc5 100644 --- a/assets/win10x64-iot.xml +++ b/assets/win10x64-iot.xml @@ -424,6 +424,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>20</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>21</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win10x64-ltsc.xml b/assets/win10x64-ltsc.xml index 108db1f..8cdc2de 100644 --- a/assets/win10x64-ltsc.xml +++ b/assets/win10x64-ltsc.xml @@ -421,6 +421,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>20</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>21</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win10x64.xml b/assets/win10x64.xml index 0b19f99..dbcbb51 100644 --- a/assets/win10x64.xml +++ b/assets/win10x64.xml @@ -418,6 +418,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>20</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>21</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win11x64-enterprise-eval.xml b/assets/win11x64-enterprise-eval.xml index b8c3cbf..7822f82 100644 --- a/assets/win11x64-enterprise-eval.xml +++ b/assets/win11x64-enterprise-eval.xml @@ -102,6 +102,10 @@ <Order>4</Order> <Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path> </RunSynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>5</Order> + <Path>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\msiserver" /v Start /t REG_DWORD /d 2 /f</Path> + </SynchronousCommand> </RunSynchronous> </component> </settings> @@ -264,6 +268,11 @@ <Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path> <Description>Set Network Location to Home</Description> </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>26</Order> + <Path>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</Path> + <Description>Install VirtIO display driver</Description> + </RunSynchronousCommand> </RunSynchronous> </component> <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> @@ -443,19 +452,29 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>22</Order> - <CommandLine>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</CommandLine> - <Description>Install VirtIO display driver</Description> - </SynchronousCommand> - <SynchronousCommand wcm:action="add"> - <Order>23</Order> <CommandLine>cmd /C rd /q C:\Windows.old</CommandLine> <Description>Remove empty Windows.old folder</Description> </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>23</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>24</Order> - <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> + <CommandLine>cmd /C if exist "C:\OEM\install.bat" cmd /C C:\OEM\install.bat</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>25</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v "IPAutoconfigurationEnabled" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Disable ip autoconfiguration for network interfaces</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>26</Order> + <CommandLine>cmd /C "type nul > \\host.lan\Data\prepared"</CommandLine> + <Description>Let host known that all configuration is done</Description> + </SynchronousCommand> </FirstLogonCommands> </component> </settings> diff --git a/assets/win11x64-enterprise.xml b/assets/win11x64-enterprise.xml index fc8562a..6620eb7 100644 --- a/assets/win11x64-enterprise.xml +++ b/assets/win11x64-enterprise.xml @@ -105,6 +105,10 @@ <Order>4</Order> <Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path> </RunSynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>5</Order> + <Path>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\msiserver" /v Start /t REG_DWORD /d 2 /f</Path> + </SynchronousCommand> </RunSynchronous> </component> </settings> @@ -267,6 +271,11 @@ <Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path> <Description>Set Network Location to Home</Description> </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>26</Order> + <Path>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</Path> + <Description>Install VirtIO display driver</Description> + </RunSynchronousCommand> </RunSynchronous> </component> <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> @@ -446,21 +455,30 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>22</Order> - <CommandLine>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</CommandLine> - <Description>Install VirtIO display driver</Description> - </SynchronousCommand> - <SynchronousCommand wcm:action="add"> - <Order>23</Order> <CommandLine>cmd /C rd /q C:\Windows.old</CommandLine> <Description>Remove empty Windows.old folder</Description> </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>23</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>24</Order> - <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> + <CommandLine>cmd /C if exist "C:\OEM\install.bat" cmd /C C:\OEM\install.bat</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>25</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v "IPAutoconfigurationEnabled" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Disable ip autoconfiguration for network interfaces</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>26</Order> + <CommandLine>cmd /C "type nul > \\host.lan\Data\prepared"</CommandLine> + <Description>Let host known that all configuration is done</Description> + </SynchronousCommand> </FirstLogonCommands> </component> </settings> </unattend> - diff --git a/assets/win11x64-iot.xml b/assets/win11x64-iot.xml index 0de7bb4..4707774 100644 --- a/assets/win11x64-iot.xml +++ b/assets/win11x64-iot.xml @@ -105,6 +105,10 @@ <Order>4</Order> <Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path> </RunSynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>5</Order> + <Path>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\msiserver" /v Start /t REG_DWORD /d 2 /f</Path> + </SynchronousCommand> </RunSynchronous> </component> </settings> @@ -267,6 +271,11 @@ <Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path> <Description>Set Network Location to Home</Description> </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>26</Order> + <Path>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</Path> + <Description>Install VirtIO display driver</Description> + </RunSynchronousCommand> </RunSynchronous> </component> <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> @@ -446,19 +455,29 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>22</Order> - <CommandLine>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</CommandLine> - <Description>Install VirtIO display driver</Description> - </SynchronousCommand> - <SynchronousCommand wcm:action="add"> - <Order>23</Order> <CommandLine>cmd /C rd /q C:\Windows.old</CommandLine> <Description>Remove empty Windows.old folder</Description> </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>23</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>24</Order> - <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> + <CommandLine>cmd /C if exist "C:\OEM\install.bat" cmd /C C:\OEM\install.bat</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>25</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v "IPAutoconfigurationEnabled" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Disable ip autoconfiguration for network interfaces</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>26</Order> + <CommandLine>cmd /C "type nul > \\host.lan\Data\prepared"</CommandLine> + <Description>Let host known that all configuration is done</Description> + </SynchronousCommand> </FirstLogonCommands> </component> </settings> diff --git a/assets/win11x64-ltsc.xml b/assets/win11x64-ltsc.xml index 8e42eb6..4359b1c 100644 --- a/assets/win11x64-ltsc.xml +++ b/assets/win11x64-ltsc.xml @@ -105,6 +105,10 @@ <Order>4</Order> <Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path> </RunSynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>5</Order> + <Path>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\msiserver" /v Start /t REG_DWORD /d 2 /f</Path> + </SynchronousCommand> </RunSynchronous> </component> </settings> @@ -267,6 +271,11 @@ <Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path> <Description>Set Network Location to Home</Description> </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>26</Order> + <Path>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</Path> + <Description>Install VirtIO display driver</Description> + </RunSynchronousCommand> </RunSynchronous> </component> <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> @@ -446,19 +455,29 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>22</Order> - <CommandLine>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</CommandLine> - <Description>Install VirtIO display driver</Description> - </SynchronousCommand> - <SynchronousCommand wcm:action="add"> - <Order>23</Order> <CommandLine>cmd /C rd /q C:\Windows.old</CommandLine> <Description>Remove empty Windows.old folder</Description> </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>23</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>24</Order> - <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> + <CommandLine>cmd /C if exist "C:\OEM\install.bat" cmd /C C:\OEM\install.bat</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>25</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v "IPAutoconfigurationEnabled" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Disable ip autoconfiguration for network interfaces</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>26</Order> + <CommandLine>cmd /C "type nul > \\host.lan\Data\prepared"</CommandLine> + <Description>Let host known that all configuration is done</Description> + </SynchronousCommand> </FirstLogonCommands> </component> </settings> diff --git a/assets/win11x64.xml b/assets/win11x64.xml index 3130bb4..bec96d4 100644 --- a/assets/win11x64.xml +++ b/assets/win11x64.xml @@ -105,6 +105,10 @@ <Order>4</Order> <Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path> </RunSynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>5</Order> + <Path>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\msiserver" /v Start /t REG_DWORD /d 2 /f</Path> + </SynchronousCommand> </RunSynchronous> </component> </settings> @@ -267,6 +271,11 @@ <Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path> <Description>Set Network Location to Home</Description> </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>26</Order> + <Path>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</Path> + <Description>Install VirtIO display driver</Description> + </RunSynchronousCommand> </RunSynchronous> </component> <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> @@ -446,19 +455,29 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>22</Order> - <CommandLine>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</CommandLine> - <Description>Install VirtIO display driver</Description> - </SynchronousCommand> - <SynchronousCommand wcm:action="add"> - <Order>23</Order> <CommandLine>cmd /C rd /q C:\Windows.old</CommandLine> <Description>Remove empty Windows.old folder</Description> </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>23</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>24</Order> - <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> + <CommandLine>cmd /C if exist "C:\OEM\install.bat" cmd /C C:\OEM\install.bat</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>25</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v "IPAutoconfigurationEnabled" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Disable ip autoconfiguration for network interfaces</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>26</Order> + <CommandLine>cmd /C "type nul > \\host.lan\Data\prepared"</CommandLine> + <Description>Let host known that all configuration is done</Description> + </SynchronousCommand> </FirstLogonCommands> </component> </settings> diff --git a/assets/win2008r2-eval.xml b/assets/win2008r2-eval.xml index ed35737..bd3d074 100644 --- a/assets/win2008r2-eval.xml +++ b/assets/win2008r2-eval.xml @@ -290,6 +290,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>19</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>20</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win2008r2.xml b/assets/win2008r2.xml index bd75078..f1e87d6 100644 --- a/assets/win2008r2.xml +++ b/assets/win2008r2.xml @@ -293,6 +293,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>19</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>20</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win2012r2-eval.xml b/assets/win2012r2-eval.xml index fcb7c32..b454f2a 100644 --- a/assets/win2012r2-eval.xml +++ b/assets/win2012r2-eval.xml @@ -308,6 +308,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>16</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>17</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win2012r2.xml b/assets/win2012r2.xml index dea2c37..06c8030 100644 --- a/assets/win2012r2.xml +++ b/assets/win2012r2.xml @@ -311,6 +311,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>16</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>17</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win2016-eval.xml b/assets/win2016-eval.xml index 66fc244..5a3f668 100644 --- a/assets/win2016-eval.xml +++ b/assets/win2016-eval.xml @@ -328,6 +328,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>20</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>21</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win2016.xml b/assets/win2016.xml index 1336e3f..7300fed 100644 --- a/assets/win2016.xml +++ b/assets/win2016.xml @@ -331,6 +331,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>20</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>21</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win2019-eval.xml b/assets/win2019-eval.xml index e9aebfd..6de9fd1 100644 --- a/assets/win2019-eval.xml +++ b/assets/win2019-eval.xml @@ -332,6 +332,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>20</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>21</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/custom.xml b/assets/win2019-hv.xml similarity index 63% rename from custom.xml rename to assets/win2019-hv.xml index de1a76b..d07259d 100644 --- a/custom.xml +++ b/assets/win2019-hv.xml @@ -8,6 +8,7 @@ <InputLocale>0409:00000409</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"> @@ -61,10 +62,17 @@ </DiskConfiguration> <ImageInstall> <OSImage> + <InstallFrom> + <MetaData wcm:action="add"> + <Key>/IMAGE/NAME</Key> + <Value>Hyper-V Server 2019 SERVERHYPERCORE</Value> + </MetaData> + </InstallFrom> <InstallTo> <DiskID>0</DiskID> <PartitionID>3</PartitionID> </InstallTo> + <WillShowUI>OnError</WillShowUI> <InstallToAvailablePartition>false</InstallToAvailablePartition> </OSImage> </ImageInstall> @@ -80,36 +88,11 @@ <AcceptEula>true</AcceptEula> <FullName>Docker</FullName> <Organization>Windows for Docker</Organization> - <ProductKey> - <Key>VK7JG-NPHTM-C97JM-9MPGT-3V66T</Key> - </ProductKey> </UserData> <EnableFirewall>false</EnableFirewall> <Diagnostics> <OptIn>false</OptIn> </Diagnostics> - <RunSynchronous> - <RunSynchronousCommand wcm:action="add"> - <Order>1</Order> - <Path>reg.exe add "HKLM\SYSTEM\Setup\LabConfig" /v BypassTPMCheck /t REG_DWORD /d 1 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>2</Order> - <Path>reg.exe add "HKLM\SYSTEM\Setup\LabConfig" /v BypassSecureBootCheck /t REG_DWORD /d 1 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>3</Order> - <Path>reg.exe add "HKLM\SYSTEM\Setup\LabConfig" /v BypassRAMCheck /t REG_DWORD /d 1 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>4</Order> - <Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path> - </RunSynchronousCommand> - <SynchronousCommand wcm:action="add"> - <Order>5</Order> - <Path>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\msiserver" /v Start /t REG_DWORD /d 2 /f</Path> - </SynchronousCommand> - </RunSynchronous> </component> </settings> <settings pass="offlineServicing"> @@ -166,113 +149,29 @@ <InputLocale>0409:00000409</InputLocale> <SystemLocale>en-US</SystemLocale> <UILanguage>en-US</UILanguage> + <UILanguageFallback>en-US</UILanguageFallback> <UserLocale>en-US</UserLocale> </component> <component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <RunSynchronous> <RunSynchronousCommand wcm:action="add"> <Order>1</Order> - <Path>reg.exe add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v BypassNRO /t REG_DWORD /d 1 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>2</Order> - <Path>reg.exe load "HKU\mount" "C:\Users\Default\NTUSER.DAT"</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>3</Order> - <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "ContentDeliveryAllowed" /t REG_DWORD /d 0 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>4</Order> - <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "FeatureManagementEnabled" /t REG_DWORD /d 0 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>5</Order> - <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "OEMPreInstalledAppsEnabled" /t REG_DWORD /d 0 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>6</Order> - <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "PreInstalledAppsEnabled" /t REG_DWORD /d 0 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>7</Order> - <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "PreInstalledAppsEverEnabled" /t REG_DWORD /d 0 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>8</Order> - <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SilentInstalledAppsEnabled" /t REG_DWORD /d 0 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>9</Order> - <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SoftLandingEnabled" /t REG_DWORD /d 0 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>10</Order> - <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContentEnabled" /t REG_DWORD /d 0 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>11</Order> - <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-310093Enabled" /t REG_DWORD /d 0 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>12</Order> - <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-338387Enabled" /t REG_DWORD /d 0 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>13</Order> - <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-338388Enabled" /t REG_DWORD /d 0 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>14</Order> - <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-338389Enabled" /t REG_DWORD /d 0 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>15</Order> - <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-338393Enabled" /t REG_DWORD /d 0 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>16</Order> - <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-353698Enabled" /t REG_DWORD /d 0 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>17</Order> - <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SystemPaneSuggestionsEnabled" /t REG_DWORD /d 0 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>18</Order> - <Path>reg.exe add "HKU\mount\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableCloudOptimizedContent" /t REG_DWORD /d 1 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>19</Order> - <Path>reg.exe add "HKU\mount\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsConsumerFeatures" /t REG_DWORD /d 1 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>20</Order> - <Path>reg.exe add "HKU\mount\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableConsumerAccountStateContent" /t REG_DWORD /d 1 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>21</Order> - <Path>reg.exe unload "HKU\mount"</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>22</Order> - <Path>reg.exe add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableCloudOptimizedContent" /t REG_DWORD /d 1 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>23</Order> - <Path>reg.exe add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsConsumerFeatures" /t REG_DWORD /d 1 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>24</Order> - <Path>reg.exe add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableConsumerAccountStateContent" /t REG_DWORD /d 1 /f</Path> - </RunSynchronousCommand> - <RunSynchronousCommand wcm:action="add"> - <Order>25</Order> <Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path> <Description>Set Network Location to Home</Description> </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>2</Order> + <Path>dism.exe /online /Disable-Feature /FeatureName:Microsoft-Hyper-V /NoRestart</Path> + <Description>Disable Hyper-V role</Description> + </RunSynchronousCommand> </RunSynchronous> </component> + <component name="Microsoft-Windows-ServerManager-SvrMgrNc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <DoNotOpenServerManagerAtLogon>true</DoNotOpenServerManagerAtLogon> + </component> + <component name="Microsoft-Windows-OutOfBoxExperience" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <DoNotOpenInitialConfigurationTasksAtLogon>true</DoNotOpenInitialConfigurationTasksAtLogon> + </component> <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <fDenyTSConnections>false</fDenyTSConnections> </component> @@ -289,8 +188,6 @@ </FirewallGroups> </component> </settings> - <settings pass="auditSystem" /> - <settings pass="auditUser" /> <settings pass="oobeSystem"> <component name="Microsoft-Windows-SecureStartup-FilterDriver" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <PreventDeviceEncryption>true</PreventDeviceEncryption> @@ -350,64 +247,64 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>2</Order> - <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters" /v "RequireSecuritySignature" /t REG_DWORD /d 0 /f</CommandLine> - <Description>Disable SMB signing requirement</Description> - </SynchronousCommand> - <SynchronousCommand wcm:action="add"> - <Order>3</Order> <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\Lsa" /v LimitBlankPasswordUse /t REG_DWORD /d 0 /f</CommandLine> <Description>Allow RDP login with blank password</Description> </SynchronousCommand> <SynchronousCommand wcm:action="add"> - <Order>4</Order> + <Order>3</Order> <CommandLine>reg.exe add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PasswordLess\Device" /v "DevicePasswordLessBuildVersion" /t REG_DWORD /d 0 /f</CommandLine> <Description>Enable option for passwordless sign-in</Description> </SynchronousCommand> <SynchronousCommand wcm:action="add"> - <Order>5</Order> + <Order>4</Order> <CommandLine>cmd /C wmic useraccount where name="Docker" set PasswordExpires=false</CommandLine> <Description>Password Never Expires</Description> </SynchronousCommand> <SynchronousCommand wcm:action="add"> - <Order>6</Order> + <Order>5</Order> <CommandLine>cmd /C POWERCFG -H OFF</CommandLine> <Description>Disable Hibernation</Description> </SynchronousCommand> <SynchronousCommand wcm:action="add"> - <Order>7</Order> + <Order>6</Order> <CommandLine>cmd /C POWERCFG -X -monitor-timeout-ac 0</CommandLine> <Description>Disable monitor blanking</Description> </SynchronousCommand> <SynchronousCommand wcm:action="add"> - <Order>8</Order> + <Order>7</Order> <CommandLine>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "HideFirstRunExperience" /t REG_DWORD /d 1 /f</CommandLine> <Description>Disable first-run experience in Edge</Description> </SynchronousCommand> <SynchronousCommand wcm:action="add"> - <Order>9</Order> + <Order>8</Order> <CommandLine>reg.exe add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "HideFileExt" /t REG_DWORD /d 0 /f</CommandLine> <Description>Show file extensions in Explorer</Description> </SynchronousCommand> <SynchronousCommand wcm:action="add"> - <Order>10</Order> + <Order>9</Order> <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\Power" /v "HibernateFileSizePercent" /t REG_DWORD /d 0 /f</CommandLine> <Description>Zero Hibernation File</Description> </SynchronousCommand> <SynchronousCommand wcm:action="add"> - <Order>11</Order> + <Order>10</Order> <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\Power" /v "HibernateEnabled" /t REG_DWORD /d 0 /f</CommandLine> <Description>Disable Hibernation</Description> </SynchronousCommand> <SynchronousCommand wcm:action="add"> - <Order>12</Order> + <Order>11</Order> <CommandLine>cmd /C POWERCFG -X -standby-timeout-ac 0</CommandLine> <Description>Disable Sleep</Description> </SynchronousCommand> <SynchronousCommand wcm:action="add"> - <Order>13</Order> + <Order>12</Order> <CommandLine>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" /v "fAllowUnlistedRemotePrograms" /t REG_DWORD /d 1 /f</CommandLine> <Description>Enable RemoteAPP to launch unlisted programs</Description> </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>13</Order> + <CommandLine>reg.exe add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "SearchboxTaskbarMode" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Remove Search from the Taskbar</Description> + </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>14</Order> <CommandLine>reg.exe add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "ShowTaskViewButton" /t REG_DWORD /d 0 /f</CommandLine> @@ -440,39 +337,14 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>20</Order> - <CommandLine>reg.exe add "HKCU\Control Panel\UnsupportedHardwareNotificationCache" /v SV1 /d 0 /t REG_DWORD /f</CommandLine> - <Description>Disable unsupported hardware notifications</Description> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>21</Order> - <CommandLine>reg.exe add "HKCU\Control Panel\UnsupportedHardwareNotificationCache" /v SV2 /d 0 /t REG_DWORD /f</CommandLine> - <Description>Disable unsupported hardware notifications</Description> - </SynchronousCommand> - <SynchronousCommand wcm:action="add"> - <Order>22</Order> - <CommandLine>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</CommandLine> - <Description>Install VirtIO display driver</Description> - </SynchronousCommand> - <SynchronousCommand wcm:action="add"> - <Order>23</Order> - <CommandLine>cmd /C rd /q C:\Windows.old</CommandLine> - <Description>Remove empty Windows.old folder</Description> - </SynchronousCommand> - <SynchronousCommand wcm:action="add"> - <Order>24</Order> - <CommandLine>cmd /C if exist "C:\OEM\install.bat" cmd /C C:\OEM\install.bat</CommandLine> + <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> - <SynchronousCommand wcm:action="add"> - <Order>25</Order> - <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" /v "IPAutoconfigurationEnabled" /t REG_DWORD /d 0 /f</CommandLine> - <Description>Disable ip autoconfiguration for network interfaces</Description> - </SynchronousCommand> - <SynchronousCommand wcm:action="add"> - <Order>26</Order> - <CommandLine>cmd /C "type nul > \\host.lan\Data\prepared"</CommandLine> - <Description>Let host known that all configuration is done</Description> - </SynchronousCommand> </FirstLogonCommands> </component> </settings> diff --git a/assets/win2019.xml b/assets/win2019.xml index aa43463..e07ef29 100644 --- a/assets/win2019.xml +++ b/assets/win2019.xml @@ -335,6 +335,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>20</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>21</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win2022-eval.xml b/assets/win2022-eval.xml index 782752e..9059ab5 100644 --- a/assets/win2022-eval.xml +++ b/assets/win2022-eval.xml @@ -332,6 +332,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>20</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>21</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win2022.xml b/assets/win2022.xml index 3b61f90..2cdfb86 100644 --- a/assets/win2022.xml +++ b/assets/win2022.xml @@ -335,6 +335,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>20</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>21</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win2025-eval.xml b/assets/win2025-eval.xml index 9d90c77..52303b5 100644 --- a/assets/win2025-eval.xml +++ b/assets/win2025-eval.xml @@ -159,6 +159,11 @@ <Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path> <Description>Set Network Location to Home</Description> </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>2</Order> + <Path>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</Path> + <Description>Install VirtIO display driver</Description> + </RunSynchronousCommand> </RunSynchronous> </component> <component name="Microsoft-Windows-ServerManager-SvrMgrNc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> @@ -337,13 +342,13 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>21</Order> - <CommandLine>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</CommandLine> - <Description>Install VirtIO display driver</Description> + <CommandLine>cmd /C rd /q C:\Windows.old</CommandLine> + <Description>Remove empty Windows.old folder</Description> </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>22</Order> - <CommandLine>cmd /C rd /q C:\Windows.old</CommandLine> - <Description>Remove empty Windows.old folder</Description> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>23</Order> diff --git a/assets/win2025.xml b/assets/win2025.xml index 7e543a2..bef8e3f 100644 --- a/assets/win2025.xml +++ b/assets/win2025.xml @@ -162,6 +162,11 @@ <Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path> <Description>Set Network Location to Home</Description> </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>2</Order> + <Path>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</Path> + <Description>Install VirtIO display driver</Description> + </RunSynchronousCommand> </RunSynchronous> </component> <component name="Microsoft-Windows-ServerManager-SvrMgrNc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> @@ -340,13 +345,13 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>21</Order> - <CommandLine>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</CommandLine> - <Description>Install VirtIO display driver</Description> + <CommandLine>cmd /C rd /q C:\Windows.old</CommandLine> + <Description>Remove empty Windows.old folder</Description> </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>22</Order> - <CommandLine>cmd /C rd /q C:\Windows.old</CommandLine> - <Description>Remove empty Windows.old folder</Description> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>23</Order> diff --git a/assets/win7x64-enterprise-eval.xml b/assets/win7x64-enterprise-eval.xml new file mode 100644 index 0000000..4b7ce48 --- /dev/null +++ b/assets/win7x64-enterprise-eval.xml @@ -0,0 +1,288 @@ +<?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"> + <DiskConfiguration> + <WillShowUI>OnError</WillShowUI> + <Disk wcm:action="add"> + <DiskID>0</DiskID> + <WillWipeDisk>true</WillWipeDisk> + <CreatePartitions> + <CreatePartition wcm:action="add"> + <Order>1</Order> + <Type>Primary</Type> + <Size>100</Size> + </CreatePartition> + <CreatePartition wcm:action="add"> + <Order>2</Order> + <Type>Primary</Type> + <Extend>true</Extend> + </CreatePartition> + </CreatePartitions> + <ModifyPartitions> + <ModifyPartition wcm:action="add"> + <Format>NTFS</Format> + <Label>System Reserved</Label> + <Order>1</Order> + <Active>true</Active> + <PartitionID>1</PartitionID> + <TypeID>0x27</TypeID> + </ModifyPartition> + <ModifyPartition wcm:action="add"> + <Active>true</Active> + <Format>NTFS</Format> + <Label>Windows</Label> + <Letter>C</Letter> + <Order>2</Order> + <PartitionID>2</PartitionID> + </ModifyPartition> + </ModifyPartitions> + </Disk> + </DiskConfiguration> + <ImageInstall> + <OSImage> + <InstallFrom> + <MetaData wcm:action="add"> + <Value>Windows 7 Enterprise</Value> + <Key>/IMAGE/NAME</Key> + </MetaData> + </InstallFrom> + <InstallTo> + <DiskID>0</DiskID> + <PartitionID>2</PartitionID> + </InstallTo> + <InstallToAvailablePartition>false</InstallToAvailablePartition> + </OSImage> + </ImageInstall> + <DynamicUpdate> + <Enable>true</Enable> + <WillShowUI>Never</WillShowUI> + </DynamicUpdate> + <UpgradeData> + <Upgrade>false</Upgrade> + <WillShowUI>Never</WillShowUI> + </UpgradeData> + <UserData> + <AcceptEula>true</AcceptEula> + <FullName>Docker</FullName> + <Organization>Windows for Docker</Organization> + </UserData> + <EnableFirewall>false</EnableFirewall> + <Diagnostics> + <OptIn>false</OptIn> + </Diagnostics> + </component> + </settings> + <settings pass="offlineServicing"> + <component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <EnableLUA>false</EnableLUA> + </component> + </settings> + <settings pass="generalize"> + <component name="Microsoft-Windows-PnPSysprep" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <PersistAllDeviceInstalls>true</PersistAllDeviceInstalls> + </component> + <component name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <SkipRearm>1</SkipRearm> + </component> + </settings> + <settings pass="specialize"> + <component name="Microsoft-Windows-Security-SPP-UX" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <SkipAutoActivation>true</SkipAutoActivation> + </component> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <ComputerName>*</ComputerName> + <OEMInformation> + <Manufacturer>Dockur</Manufacturer> + <Model>Windows for Docker</Model> + </OEMInformation> + </component> + <component name="Microsoft-Windows-ErrorReportingCore" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <DisableWER>1</DisableWER> + </component> + <component name="Microsoft-Windows-IE-InternetExplorer" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <DisableAccelerators>true</DisableAccelerators> + <DisableFirstRunWizard>true</DisableFirstRunWizard> + <Home_Page>https://google.com</Home_Page> + <Help_Page>about:blank</Help_Page> + </component> + <component name="Microsoft-Windows-IE-InternetExplorer" processorArchitecture="wow64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <DisableAccelerators>true</DisableAccelerators> + <DisableFirstRunWizard>true</DisableFirstRunWizard> + <Home_Page>https://google.com</Home_Page> + <Help_Page>about:blank</Help_Page> + </component> + <component name="Microsoft-Windows-SQMApi" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <CEIPEnabled>0</CEIPEnabled> + </component> + <component name="Microsoft-Windows-SystemRestore-Main" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <DisableSR>1</DisableSR> + </component> + <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-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <fDenyTSConnections>false</fDenyTSConnections> + </component> + <component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <UserAuthentication>0</UserAuthentication> + </component> + <component name="Networking-MPSSVC-Svc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <FirewallGroups> + <FirewallGroup wcm:action="add" wcm:keyValue="RemoteDesktop"> + <Active>true</Active> + <Profile>all</Profile> + <Group>@FirewallAPI.dll,-28752</Group> + </FirewallGroup> + </FirewallGroups> + </component> + </settings> + <settings pass="oobeSystem"> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <UserAccounts> + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Name>Docker</Name> + <Group>Administrators</Group> + <Password> + <Value /> + <PlainText>true</PlainText> + </Password> + </LocalAccount> + </LocalAccounts> + <AdministratorPassword> + <Value>password</Value> + <PlainText>true</PlainText> + </AdministratorPassword> + </UserAccounts> + <AutoLogon> + <Username>Docker</Username> + <Enabled>true</Enabled> + <LogonCount>65432</LogonCount> + <Password> + <Value /> + <PlainText>true</PlainText> + </Password> + </AutoLogon> + <Display> + <ColorDepth>32</ColorDepth> + <HorizontalResolution>1920</HorizontalResolution> + <VerticalResolution>1080</VerticalResolution> + </Display> + <OOBE> + <HideEULAPage>true</HideEULAPage> + <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> + <NetworkLocation>Home</NetworkLocation> + <ProtectYourPC>3</ProtectYourPC> + <SkipUserOOBE>true</SkipUserOOBE> + <SkipMachineOOBE>true</SkipMachineOOBE> + </OOBE> + <RegisteredOrganization>Dockur</RegisteredOrganization> + <RegisteredOwner>Windows for Docker</RegisteredOwner> + <FirstLogonCommands> + <SynchronousCommand wcm:action="add"> + <Order>1</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters" /v "AllowInsecureGuestAuth" /t REG_DWORD /d 1 /f</CommandLine> + <Description>Allow guest access to network shares</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>2</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\Lsa" /v LimitBlankPasswordUse /t REG_DWORD /d 0 /f</CommandLine> + <Description>Allow RDP login with blank password</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>3</Order> + <CommandLine>cmd /C wmic useraccount where name="Docker" set PasswordExpires=false</CommandLine> + <Description>Password Never Expires</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>4</Order> + <CommandLine>cmd /C POWERCFG -H OFF</CommandLine> + <Description>Disable Hibernation</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>5</Order> + <CommandLine>cmd /C POWERCFG -X -monitor-timeout-ac 0</CommandLine> + <Description>Disable monitor blanking</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>6</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\Network\NewNetworkWindowOff" /f</CommandLine> + <Description>Disable Network Discovery popup</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>7</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\Network\NetworkLocationWizard" /v "HideWizard" /t REG_DWORD /d 1 /f</CommandLine> + <Description>Disable Network Discovery popup</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>8</Order> + <CommandLine>reg.exe add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\NewNetworks" /v NetworkList /t REG_MULTI_SZ /d "" /f</CommandLine> + <Description>Disable Network Discovery popup</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>9</Order> + <CommandLine>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "HideFirstRunExperience" /t REG_DWORD /d 1 /f</CommandLine> + <Description>Disable first-run experience in Edge</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>10</Order> + <CommandLine>reg.exe add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "HideFileExt" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Show file extensions in Explorer</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>11</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\Power" /v "HibernateFileSizePercent" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Zero Hibernation File</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>12</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\Power" /v "HibernateEnabled" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Disable Hibernation</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>13</Order> + <CommandLine>cmd /C POWERCFG -X -standby-timeout-ac 0</CommandLine> + <Description>Disable Sleep</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>14</Order> + <CommandLine>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" /v "fAllowUnlistedRemotePrograms" /t REG_DWORD /d 1 /f</CommandLine> + <Description>Enable RemoteAPP to launch unlisted programs</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>15</Order> + <CommandLine>netsh advfirewall firewall set rule group="@FirewallAPI.dll,-32752" new enable=Yes</CommandLine> + <Description>Enable Network Discovery</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>16</Order> + <CommandLine>netsh advfirewall firewall set rule group="@FirewallAPI.dll,-28502" new enable=Yes</CommandLine> + <Description>Enable File Sharing</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>17</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>18</Order> + <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> + <Description>Execute custom script from the OEM folder if exists</Description> + </SynchronousCommand> + </FirstLogonCommands> + </component> + </settings> +</unattend> diff --git a/assets/win7x64-enterprise.xml b/assets/win7x64-enterprise.xml index 14c4060..f64a076 100644 --- a/assets/win7x64-enterprise.xml +++ b/assets/win7x64-enterprise.xml @@ -278,6 +278,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>17</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>18</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win7x64-ultimate.xml b/assets/win7x64-ultimate.xml index f3c1ae3..aa8117d 100644 --- a/assets/win7x64-ultimate.xml +++ b/assets/win7x64-ultimate.xml @@ -278,6 +278,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>17</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>18</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win7x64.xml b/assets/win7x64.xml index b9f2908..f0056e2 100644 --- a/assets/win7x64.xml +++ b/assets/win7x64.xml @@ -278,6 +278,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>17</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>18</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win7x86-enterprise.xml b/assets/win7x86-enterprise.xml index ef606e5..acf676f 100644 --- a/assets/win7x86-enterprise.xml +++ b/assets/win7x86-enterprise.xml @@ -278,6 +278,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>17</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>18</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win7x86-ultimate.xml b/assets/win7x86-ultimate.xml index f0748cd..c6beb01 100644 --- a/assets/win7x86-ultimate.xml +++ b/assets/win7x86-ultimate.xml @@ -278,6 +278,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>17</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>18</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win7x86.xml b/assets/win7x86.xml index c5cd323..74a9064 100644 --- a/assets/win7x86.xml +++ b/assets/win7x86.xml @@ -278,6 +278,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>17</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>18</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win81x64-enterprise-eval.xml b/assets/win81x64-enterprise-eval.xml index 2fe8c73..7aee42a 100644 --- a/assets/win81x64-enterprise-eval.xml +++ b/assets/win81x64-enterprise-eval.xml @@ -293,6 +293,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>16</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>17</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win81x64-enterprise.xml b/assets/win81x64-enterprise.xml index d6c72dc..d2d49fc 100644 --- a/assets/win81x64-enterprise.xml +++ b/assets/win81x64-enterprise.xml @@ -296,6 +296,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>16</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>17</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/win81x64.xml b/assets/win81x64.xml index e5e268e..d4b9d5c 100644 --- a/assets/win81x64.xml +++ b/assets/win81x64.xml @@ -303,6 +303,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>16</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>17</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/winvistax64-enterprise.xml b/assets/winvistax64-enterprise.xml index 50527a0..04a3f5b 100644 --- a/assets/winvistax64-enterprise.xml +++ b/assets/winvistax64-enterprise.xml @@ -252,6 +252,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>22</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>23</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/winvistax64-ultimate.xml b/assets/winvistax64-ultimate.xml index 14c627d..a391a96 100644 --- a/assets/winvistax64-ultimate.xml +++ b/assets/winvistax64-ultimate.xml @@ -252,6 +252,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>22</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>23</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/winvistax64.xml b/assets/winvistax64.xml index f753ea3..a2fda33 100644 --- a/assets/winvistax64.xml +++ b/assets/winvistax64.xml @@ -252,6 +252,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>22</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>23</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/winvistax86-enterprise.xml b/assets/winvistax86-enterprise.xml index d087355..2dc58fa 100644 --- a/assets/winvistax86-enterprise.xml +++ b/assets/winvistax86-enterprise.xml @@ -252,6 +252,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>22</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>23</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/winvistax86-ultimate.xml b/assets/winvistax86-ultimate.xml index 417722e..b7d35cc 100644 --- a/assets/winvistax86-ultimate.xml +++ b/assets/winvistax86-ultimate.xml @@ -252,6 +252,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>22</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>23</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/assets/winvistax86.xml b/assets/winvistax86.xml index 172e5dd..3bec2e6 100644 --- a/assets/winvistax86.xml +++ b/assets/winvistax86.xml @@ -252,6 +252,11 @@ </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>22</Order> + <CommandLine>cmd /C echo 20.20.20.1 host.lan >> %WINDIR%\system32\drivers\etc\hosts</CommandLine> + <Description>Add entry in hosts file</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>23</Order> <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> <Description>Execute custom script from the OEM folder if exists</Description> </SynchronousCommand> diff --git a/compose.yml b/compose.yml index ac286af..df5adc1 100644 --- a/compose.yml +++ b/compose.yml @@ -16,23 +16,28 @@ services: DISK_FMT: "qcow2" NETWORK: "Y" DEBUG: "Y" + COMMIT: "Y" devices: - /dev/kvm - /dev/net/tun cap_add: - NET_ADMIN + ports: + - 8006:8006 + - 3389:3389/tcp + - 3389:3389/udp + restart: always stop_grace_period: 2m volumes: + - ./windows:/storage - ./scripts:/oem - - ./custom.xml:/custom.xml - - ./custom.iso:/custom.iso windows-installed: image: $IMAGE_NAME:$IMAGE_VERSION container_name: windows-installed privileged: true healthcheck: - test: "[ -f /storage/ready ] || exit 1" + test: "[ -f /local/ready ] || exit 1" interval: 30s retries: 20 start_period: 60s @@ -46,4 +51,9 @@ services: - /dev/net/tun cap_add: - NET_ADMIN + ports: + - 8006:8006 + - 3389:3389/tcp + - 3389:3389/udp + restart: always stop_grace_period: 2m diff --git a/kubernetes.yml b/kubernetes.yml index 2de24a4..fa8eef0 100644 --- a/kubernetes.yml +++ b/kubernetes.yml @@ -10,68 +10,91 @@ spec: requests: storage: 64Gi --- -apiVersion: v1 -kind: Pod +apiVersion: apps/v1 +kind: Deployment metadata: name: windows labels: name: windows spec: - containers: - - name: windows - image: dockurr/windows - env: - - name: VERSION - value: "11" - - name: RAM_SIZE - value: "4G" - - name: CPU_CORES - value: "2" - - name: DISK_SIZE - value: "64G" - ports: - - containerPort: 8006 - - containerPort: 3389 - - containerPort: 3389 - protocol: UDP - securityContext: - capabilities: - add: - - NET_ADMIN - privileged: true - volumeMounts: - - mountPath: /storage - name: storage - - mountPath: /dev/kvm - name: dev-kvm - - mountPath: /dev/net/tun - name: dev-tun - terminationGracePeriodSeconds: 120 - volumes: - - name: storage - persistentVolumeClaim: - claimName: windows-pvc - - hostPath: - path: /dev/kvm - name: dev-kvm - - hostPath: - path: /dev/net/tun - type: CharDevice - name: dev-tun + replicas: 1 + selector: + matchLabels: + app: windows + template: + metadata: + labels: + app: windows + spec: + containers: + - name: windows + image: dockurr/windows + env: + - name: VERSION + value: "11" + - name: DISK_SIZE + value: "64G" + ports: + - containerPort: 8006 + name: http + protocol: TCP + - containerPort: 3389 + name: rdp + protocol: TCP + - containerPort: 3389 + name: udp + protocol: UDP + - containerPort: 5900 + name: vnc + protocol: TCP + securityContext: + capabilities: + add: + - NET_ADMIN + privileged: true + volumeMounts: + - mountPath: /storage + name: storage + - mountPath: /dev/kvm + name: dev-kvm + - mountPath: /dev/net/tun + name: dev-tun + terminationGracePeriodSeconds: 120 + volumes: + - name: storage + persistentVolumeClaim: + claimName: windows-pvc + - hostPath: + path: /dev/kvm + name: dev-kvm + - hostPath: + path: /dev/net/tun + type: CharDevice + name: dev-tun --- apiVersion: v1 kind: Service metadata: name: windows spec: + internalTrafficPolicy: Cluster ports: - - name: tcp-8006 - port: 8006 - - name: tcp-3389 - port: 3389 - - name: udp-3389 - port: 3389 - protocol: UDP + - name: http + port: 8006 + protocol: TCP + targetPort: 8006 + - name: rdp + port: 3389 + protocol: TCP + targetPort: 3389 + - name: udp + port: 3389 + protocol: UDP + targetPort: 3389 + - name: vnc + port: 5900 + protocol: TCP + targetPort: 5900 selector: - name: windows - type: NodePort + app: windows + type: ClusterIP diff --git a/prepare_image.sh b/prepare_image.sh index 93f97dc..aee267c 100755 --- a/prepare_image.sh +++ b/prepare_image.sh @@ -4,7 +4,7 @@ set -Eeuo pipefail source env.sh echo "start to build and install windows" -docker compose up windows-build -d --build +docker compose up windows-build -d --build --force-recreate echo "streaming logs..." docker logs -f windows-build | tee windows-build.log & diff --git a/readme.md b/readme.md index e63e2a9..dbbd035 100644 --- a/readme.md +++ b/readme.md @@ -26,7 +26,7 @@ Windows inside a Docker container. ## Usage 🐳 -Via Docker Compose: +##### Via Docker Compose: ```yaml services: @@ -44,30 +44,27 @@ services: - 8006:8006 - 3389:3389/tcp - 3389:3389/udp + volumes: + - ./windows:/storage + restart: always stop_grace_period: 2m ``` -Via Docker CLI: +##### Via Docker CLI: ```bash -docker run -it --rm -p 8006:8006 --device=/dev/kvm --device=/dev/net/tun --cap-add NET_ADMIN --stop-timeout 120 dockurr/windows +docker run -it --rm --name windows -p 8006:8006 --device=/dev/kvm --device=/dev/net/tun --cap-add NET_ADMIN -v ${PWD:-.}/windows:/storage --stop-timeout 120 dockurr/windows ``` -Via Kubernetes: +##### Via Kubernetes: ```shell kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/master/kubernetes.yml ``` -## Compatibility ⚙️ +##### Via Github Codespaces: -| **Product** | **Platform** | | -|---|---|---| -| Docker Engine | Linux| ✅ | -| Docker Desktop | Linux | ❌ | -| Docker Desktop | macOS | ❌ | -| Docker Desktop | Windows 11 | ✅ | -| Docker Desktop | Windows 10 | ❌ | +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/dockur/windows) ## FAQ 💬 @@ -75,7 +72,7 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas Very simple! These are the steps: - - Start the container and connect to [port 8006](http://localhost:8006) using your web browser. + - Start the container and connect to [port 8006](http://127.0.0.1:8006/) using your web browser. - Sit back and relax while the magic happens, the whole installation will be performed fully automatic. @@ -94,28 +91,29 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas Select from the values below: - | **Value** | **Version** | **Size** | + | **Value** | **Version** | **Size** | |---|---|---| - | `11` | Windows 11 Pro | 5.4 GB | - | `11l` | Windows 11 LTSC | 4.2 GB | - | `11e` | Windows 11 Enterprise | 5.8 GB | + | `11` | Windows 11 Pro | 5.4 GB | + | `11l` | Windows 11 LTSC | 4.7 GB | + | `11e` | Windows 11 Enterprise | 4.0 GB | |||| - | `10` | Windows 10 Pro | 5.7 GB | - | `10l` | Windows 10 LTSC | 4.6 GB | - | `10e` | Windows 10 Enterprise | 5.2 GB | + | `10` | Windows 10 Pro | 5.7 GB | + | `10l` | Windows 10 LTSC | 4.6 GB | + | `10e` | Windows 10 Enterprise | 5.2 GB | |||| - | `8e` | Windows 8.1 Enterprise | 3.7 GB | - | `7e` | Windows 7 Enterprise | 3.0 GB | - | `ve` | Windows Vista Enterprise | 3.0 GB | - | `xp` | Windows XP Professional | 0.6 GB | - |||| - | `2025` | Windows Server 2025 | 5.0 GB | - | `2022` | Windows Server 2022 | 4.7 GB | - | `2019` | Windows Server 2019 | 5.3 GB | - | `2016` | Windows Server 2016 | 6.5 GB | - | `2012` | Windows Server 2012 | 4.3 GB | - | `2008` | Windows Server 2008 | 3.0 GB | - | `2003` | Windows Server 2003 | 0.6 GB | + | `8e` | Windows 8.1 Enterprise | 3.7 GB | + | `7e` | Windows 7 Enterprise | 3.0 GB | + | `ve` | Windows Vista Enterprise | 3.0 GB | + | `xp` | Windows XP Professional | 0.6 GB | + | `2k` | Windows 2000 Professional | 0.4 GB | + |||| + | `2025` | Windows Server 2025 | 5.6 GB | + | `2022` | Windows Server 2022 | 4.7 GB | + | `2019` | Windows Server 2019 | 5.3 GB | + | `2016` | Windows Server 2016 | 6.5 GB | + | `2012` | Windows Server 2012 | 4.3 GB | + | `2008` | Windows Server 2008 | 3.0 GB | + | `2003` | Windows Server 2003 | 0.6 GB | > [!TIP] > To install ARM64 versions of Windows use [dockur/windows-arm](https://github.com/dockur/windows-arm/). @@ -126,10 +124,10 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas ```yaml volumes: - - /var/win:/storage + - ./windows:/storage ``` - Replace the example path `/var/win` with the desired storage folder. + Replace the example path `./windows` with the desired storage folder or named volume. ### How do I change the size of the disk? @@ -145,60 +143,20 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas ### How do I share files with the host? - Open 'File Explorer' and click on the 'Network' section, you will see a computer called `host.lan`. Double-click it and it will show a folder called `Data`, which can be bound to any folder on your host via the compose file: + Open 'File Explorer' and click on the 'Network' section, you will see a computer called `host.lan`. + + Double-click it and it will show a folder called `Data`, which can be bound to any folder on your host via the compose file: ```yaml volumes: - - /home/user/example:/data + - ./example:/data ``` - The example folder `/home/user/example` will be available as ` \\host.lan\Data`. + The example folder `./example` will be available as ` \\host.lan\Data`. > [!TIP] > You can map this path to a drive letter in Windows, for easier access. -### How do I install a custom image? - - In order to download an unsupported ISO image that is not selectable from the list above, specify the URL of that ISO in the `VERSION` environment variable, for example: - - ```yaml - environment: - VERSION: "https://example.com/win.iso" - ``` - - Alternatively, you can also skip the download and use a local file instead, by binding it in your compose file in this way: - - ```yaml - volumes: - - /home/user/example.iso:/custom.iso - ``` - - Replace the example path `/home/user/example.iso` with the filename of your desired ISO file, the value of `VERSION` will be ignored in this case. - -### How do I run a script after installation? - - To run your own script after installation, you can create a file called `install.bat` and place it in a folder together with any additional files it needs (software to be installed for example). - - Then bind that folder in your compose file like this: - - ```yaml - volumes: - - /home/user/example:/oem - ``` - - The example folder `/home/user/example` will be copied to `C:\OEM` during installation and the containing `install.bat` will be executed during the last step. - -### How do I perform a manual installation? - - It's best to stick to the automatic installation, as it adjusts various settings to prevent common issues when running Windows inside a virtual environment. - - However, if you insist on performing the installation manually, add the following environment variable to your compose file: - - ```yaml - environment: - MANUAL: "Y" - ``` - ### How do I change the amount of CPU or RAM? By default, the container will be allowed to use a maximum of 2 CPU cores and 4 GB of RAM. @@ -213,9 +171,9 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas ### How do I configure the username and password? - By default, a user called `Docker` is created during the installation, with an empty password. + By default, a user called `Docker` is created during installation and its password is `admin`. - If you want to use different credentials, you can change them in your compose file: + If you want to use different credentials, you can configure them in your compose file (only before installation): ```yaml environment: @@ -225,7 +183,9 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas ### How do I select the Windows language? - By default, the English version of Windows will be downloaded. But you can add the `LANGUAGE` environment variable to your compose file, in order to specify an alternative language: + By default, the English version of Windows will be downloaded. + + But before installation you can add the `LANGUAGE` environment variable to your compose file, in order to specify an alternative language: ```yaml environment: @@ -236,7 +196,7 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas ### How do I select the keyboard layout? - If you want to use a keyboard layout or locale that is not the default for your selected language, you can add the `KEYBOARD` and `REGION` variables with a culture code, like this: + If you want to use a keyboard layout or locale that is not the default for your selected language, you can add `KEYBOARD` and `REGION` variables like this (before installation): ```yaml environment: @@ -244,15 +204,62 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas KEYBOARD: "en-US" ``` -> [!NOTE] -> Changing these values will have no effect after the installation has been performed already. Use the control panel inside Windows in that case. -> +### How do I select the edition? + + Windows Server offers a minimalistic Core edition without a GUI. To select those non-standard editions, you can add a `EDITION` variable like this (before installation): + + ```yaml + environment: + EDITION: "core" + ``` + +### How do I install a custom image? + + In order to download an unsupported ISO image, specify its URL in the `VERSION` environment variable: + + ```yaml + environment: + VERSION: "https://example.com/win.iso" + ``` + + Alternatively, you can also skip the download and use a local file instead, by binding it in your compose file in this way: + + ```yaml + volumes: + - ./example.iso:/custom.iso + ``` + + Replace the example path `./example.iso` with the filename of your desired ISO file. The value of `VERSION` will be ignored in this case. + +### How do I run a script after installation? + + To run your own script after installation, you can create a file called `install.bat` and place it in a folder together with any additional files it needs (software to be installed for example). + + Then bind that folder in your compose file like this: + + ```yaml + volumes: + - ./example:/oem + ``` + + The example folder `./example` will be copied to `C:\OEM` and the containing `install.bat` will be executed during the last step of the automatic installation. + +### How do I perform a manual installation? + + It's recommended to stick to the automatic installation, as it adjusts various settings to prevent common issues when running Windows inside a virtual environment. + + However, if you insist on performing the installation manually at your own risk, add the following environment variable to your compose file: + + ```yaml + environment: + MANUAL: "Y" + ``` ### How do I connect using RDP? The web-viewer is mainly meant to be used during installation, as its picture quality is low, and it has no audio or clipboard for example. - So for a better experience you can connect using any Microsoft Remote Desktop client to the IP of the container, using the username `Docker` and by leaving the password empty. + So for a better experience you can connect using any Microsoft Remote Desktop client to the IP of the container, using the username `Docker` and password `admin`. There is a RDP client for [Android](https://play.google.com/store/apps/details?id=com.microsoft.rdc.androidx) available from the Play Store and one for [iOS](https://apps.apple.com/nl/app/microsoft-remote-desktop/id714464092?l=en-GB) in the Apple Store. For Linux you can use [FreeRDP](https://www.freerdp.com/) and on Windows just type `mstsc` in the search box. @@ -297,7 +304,7 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas After configuring the container for [macvlan](#how-do-i-assign-an-individual-ip-address-to-the-container), it is possible for Windows to become part of your home network by requesting an IP from your router, just like a real PC. - To enable this mode, add the following lines to your compose file: + To enable this mode, in which the container and Windows will have separate IP addresses, add the following lines to your compose file: ```yaml environment: @@ -308,9 +315,6 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas - 'c *:* rwm' ``` -> [!NOTE] -> In this mode, the container and Windows will each have their own separate IPs. - ### How do I add multiple disks? To create additional disks, modify your compose file like this: @@ -320,18 +324,18 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas DISK2_SIZE: "32G" DISK3_SIZE: "64G" volumes: - - /home/example:/storage2 - - /mnt/data/example:/storage3 + - ./example2:/storage2 + - ./example3:/storage3 ``` ### How do I pass-through a disk? - It is possible to pass-through disk devices directly by adding them to your compose file in this way: + It is possible to pass-through disk devices or partitions directly by adding them to your compose file in this way: ```yaml devices: - /dev/sdb:/disk1 - - /dev/sdc:/disk2 + - /dev/sdc1:/disk2 ``` Use `/disk1` if you want it to become your main drive (which will be formatted during installation), and use `/disk2` and higher to add them as secondary drives (which will stay untouched). @@ -347,14 +351,20 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas - /dev/bus/usb ``` -> [!IMPORTANT] -> If the device is a USB disk drive, please wait until after the installation is completed before connecting it. Otherwise the installation may fail, as the order of the disks can get rearranged. + If the device is a USB disk drive, please wait until after the installation is fully completed before connecting it. Otherwise the installation may fail, as the order of the disks can get rearranged. ### How do I verify if my system supports KVM? - Only Linux and Windows 11 support KVM virtualization, macOS and Windows 10 do not unfortunately. - - You can run the following commands in Linux to check your system: + First check if your software is compatible using this chart: + + | **Product** | **Linux** | **Win11** | **Win10** | **macOS** | + |---|---|---|---|---| + | Docker CLI | ✅ | ✅ | ❌ | ❌ | + | Docker Desktop | ❌ | ✅ | ❌ | ❌ | + | Podman CLI | ✅ | ✅ | ❌ | ❌ | + | Podman Desktop | ✅ | ✅ | ❌ | ❌ | + + After that you can run the following commands in Linux to check your system: ```bash sudo apt install cpu-checker @@ -369,16 +379,16 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas - you are not using a cloud provider, as most of them do not allow nested virtualization for their VPS's. - If you do not receive any error from `kvm-ok` but the container still complains about KVM, please check whether: - - - you are not using "Docker Desktop for Linux" as it does not support KVM, instead make use of Docker Engine directly. - - - it could help to add `privileged: true` to your compose file (or `sudo` to your `docker run` command), to rule out any permission issue. + If you did not receive any error from `kvm-ok` but the container still complains about a missing KVM device, it could help to add `privileged: true` to your compose file (or `sudo` to your `docker` command) to rule out any permission issue. ### How do I run macOS in a container? You can use [dockur/macos](https://github.com/dockur/macos) for that. It shares many of the same features, except for the automatic installation. +### How do I run a Linux desktop in a container? + + You can use [qemus/qemu](https://github.com/qemus/qemu) in that case. + ### Is this project legal? Yes, this project contains only open-source code and does not distribute any copyrighted material. Any product keys found in the code are just generic placeholders provided by Microsoft for trial purposes. So under all applicable laws, this project will be considered legal. diff --git a/src/define.sh b/src/define.sh index 279468b..69896b1 100644 --- a/src/define.sh +++ b/src/define.sh @@ -1,10 +1,12 @@ #!/usr/bin/env bash set -Eeuox pipefail +: "${KEY:=""}" : "${WIDTH:=""}" : "${HEIGHT:=""}" : "${VERIFY:=""}" : "${REGION:=""}" +: "${EDITION:=""}" : "${MANUAL:=""}" : "${REMOVE:=""}" : "${VERSION:=""}" @@ -15,7 +17,6 @@ set -Eeuox pipefail : "${PASSWORD:=""}" MIRRORS=3 -PLATFORM="x64" parseVersion() { @@ -62,7 +63,7 @@ parseVersion() { ;; "7" | "7e" | "win7" | "win7e" | "windows7" | "windows 7" ) VERSION="win7x64" - [ -z "$DETECTED" ] && DETECTED="win7x64-enterprise" + [ -z "$DETECTED" ] && DETECTED="win7x64-enterprise-eval" ;; "7u" | "win7u" | "windows7u" | "windows 7u" ) VERSION="win7x64-ultimate" @@ -88,6 +89,9 @@ parseVersion() { "xp64" | "xpx64" | "5x64" | "winxp64" | "winxpx64" | "windowsxp64" | "windowsxpx64" ) VERSION="winxpx64" ;; + "2k" | "2000" | "win2k" | "win2000" | "windows2k" | "windows2000" ) + VERSION="win2kx86" + ;; "25" | "2025" | "win25" | "win2025" | "windows2025" | "windows 2025" ) VERSION="win2025-eval" ;; @@ -100,6 +104,9 @@ parseVersion() { "16" | "2016" | "win16" | "win2016" | "windows2016" | "windows 2016" ) VERSION="win2016-eval" ;; + "hv" | "hyperv" | "hyper v" | "hyper-v" | "19hv" | "2019hv" | "win2019hv" ) + VERSION="win2019-hv" + ;; "2012" | "2012r2" | "win2012" | "win2012r2" | "windows2012" | "windows 2012" ) VERSION="win2012r2-eval" ;; @@ -416,6 +423,7 @@ printVersion() { "win95"* ) desc="Windows 95" ;; "win2k"* ) desc="Windows 2000" ;; "winvista"* ) desc="Windows Vista" ;; + "win2019-hv"* ) desc="Hyper-V Server" ;; "win2003"* ) desc="Windows Server 2003" ;; "win2008"* ) desc="Windows Server 2008" ;; "win2012"* ) desc="Windows Server 2012" ;; @@ -460,6 +468,9 @@ printEdition() { *"-education" ) edition="Education" ;; + *"-hv" ) + edition="2019" + ;; *"-iot" | *"-iot-eval" ) edition="LTSC" ;; @@ -481,11 +492,12 @@ printEdition() { "winvista"* ) edition="Business" ;; - "win2025"* | "win2022"* | "win2019"* | "win2016"* ) - edition="Standard" - ;; - "win2012"* | "win2008"* | "win2003"* ) - edition="Standard" + "win2025"* | "win2022"* | "win2019"* | "win2016"* | "win2012"* | "win2008"* | "win2003"* ) + case "${EDITION^^}" in + *"DATACENTER"* ) edition="Datacenter" ;; + "CORE" | "STANDARDCORE" ) edition="Core" ;; + * ) edition="Standard" ;; + esac ;; esac @@ -548,6 +560,9 @@ fromFile() { "tiny10"* | "tiny_10"* ) id="tiny10" ;; + *"_serverhypercore_"* ) + id="win2019${add}-hv" + ;; *"server2025"* | *"server_2025"* ) id="win2025${add}" ;; @@ -592,7 +607,9 @@ fromName() { *"windows 7"* ) id="win7${arch}" ;; *"windows 8"* ) id="win81${arch}" ;; *"windows 10"* ) id="win10${arch}" ;; + *"optimum 10"* ) id="win10${arch}" ;; *"windows 11"* ) id="win11${arch}" ;; + *"optimum 11"* ) id="win11${arch}" ;; *"windows vista"* ) id="winvista${arch}" ;; *"server 2025"* ) id="win2025${add}" ;; *"server 2022"* ) id="win2022${add}" ;; @@ -601,6 +618,7 @@ fromName() { *"server 2012"* ) id="win2012r2${add}" ;; *"server 2008"* ) id="win2008r2${add}" ;; *"server 2003"* ) id="win2003r2${add}" ;; + *"hyper-v server"* ) id="win2019${add}" ;; esac echo "$id" @@ -621,6 +639,7 @@ getVersion() { *" home"* ) id="$id-home" ;; *" starter"* ) id="$id-starter" ;; *" ultimate"* ) id="$id-ultimate" ;; + *" enterprise evaluation"* ) id="$id-enterprise-eval" ;; *" enterprise"* ) id="$id-enterprise" ;; esac ;; @@ -643,6 +662,7 @@ getVersion() { "win2025"* | "win2022"* | "win2019"* | "win2016"* | "win2012"* | "win2008"* | "win2003"* ) case "${name,,}" in *" evaluation"* ) id="$id-eval" ;; + *"hyper-v server"* ) id="$id-hv" ;; esac ;; esac @@ -665,6 +685,9 @@ switchEdition() { "win81${PLATFORM,,}-enterprise-eval" ) DETECTED="win81${PLATFORM,,}-enterprise" ;; + "win7${PLATFORM,,}" | "win7${PLATFORM,,}-enterprise-eval" ) + DETECTED="win7${PLATFORM,,}-enterprise" + ;; "win2025-eval" ) DETECTED="win2025" ;; "win2022-eval" ) DETECTED="win2022" ;; "win2019-eval" ) DETECTED="win2019" ;; @@ -681,6 +704,7 @@ getMido() { local id="$1" local lang="$2" local ret="$3" + local url="" local sum="" local size="" @@ -692,16 +716,14 @@ getMido() { sum="b56b911bf18a2ceaeb3904d87e7c770bdf92d3099599d61ac2497b91bf190b11" ;; "win11x64-enterprise-eval" ) - size=6209064960 - sum="c8dbc96b61d04c8b01faf6ce0794fdf33965c7b350eaa3eb1e6697019902945c" + size=4295096320 + sum="dad633276073f14f3e0373ef7e787569e216d54942ce522b39451c8f2d38ad43" + url="https://software-static.download.prss.microsoft.com/dbazure/888969d5-f34g-4e03-ac9d-1f9786c66749/26100.1.240331-1435.ge_release_CLIENTENTERPRISEEVAL_OEMRET_A64FRE_en-us.iso" ;; - "win11x64-enterprise-ltsc-eval" ) - size=4428627968 - sum="8abf91c9cd408368dc73aab3425d5e3c02dae74900742072eb5c750fc637c195" - ;; - "win11x64-enterprise-iot-eval" ) - size=4428627968 - sum="8abf91c9cd408368dc73aab3425d5e3c02dae74900742072eb5c750fc637c195" + "win11x64-enterprise-iot-eval" | "win11x64-enterprise-ltsc-eval" ) + size=5060020224 + sum="2cee70bd183df42b92a2e0da08cc2bb7a2a9ce3a3841955a012c0f77aeb3cb29" + url="https://software-static.download.prss.microsoft.com/dbazure/888969d5-f34g-4e03-ac9d-1f9786c66749/26100.1.240331-1435.ge_release_CLIENT_IOT_LTSC_EVAL_x64FRE_en-us.iso" ;; "win10x64" ) size=6140975104 @@ -710,45 +732,59 @@ getMido() { "win10x64-enterprise-eval" ) size=5550497792 sum="ef7312733a9f5d7d51cfa04ac497671995674ca5e1058d5164d6028f0938d668" + url="https://software-static.download.prss.microsoft.com/dbazure/988969d5-f34g-4e03-ac9d-1f9786c66750/19045.2006.220908-0225.22h2_release_svc_refresh_CLIENTENTERPRISEEVAL_OEMRET_x64FRE_en-us.iso" ;; "win10x64-enterprise-ltsc-eval" ) size=4898582528 sum="e4ab2e3535be5748252a8d5d57539a6e59be8d6726345ee10e7afd2cb89fefb5" + url="https://software-download.microsoft.com/download/pr/19044.1288.211006-0501.21h2_release_svc_refresh_CLIENT_LTSC_EVAL_x64FRE_en-us.iso" ;; "win81x64-enterprise-eval" ) size=3961473024 sum="2dedd44c45646c74efc5a028f65336027e14a56f76686a4631cf94ffe37c72f2" + url="https://download.microsoft.com/download/B/9/9/B999286E-0A47-406D-8B3D-5B5AD7373A4A/9600.17050.WINBLUE_REFRESH.140317-1640_X64FRE_ENTERPRISE_EVAL_EN-US-IR3_CENA_X64FREE_EN-US_DV9.ISO" ;; "win2025-eval" ) - size=5307996160 - sum="16442d1c0509bcbb25b715b1b322a15fb3ab724a42da0f384b9406ca1c124ed4" + size=6014152704 + sum="d0ef4502e350e3c6c53c15b1b3020d38a5ded011bf04998e950720ac8579b23d" + url="https://software-static.download.prss.microsoft.com/dbazure/888969d5-f34g-4e03-ac9d-1f9786c66749/26100.1742.240906-0331.ge_release_svc_refresh_SERVER_EVAL_x64FRE_en-us.iso" ;; "win2022-eval" ) size=5044094976 sum="3e4fa6d8507b554856fc9ca6079cc402df11a8b79344871669f0251535255325" + url="https://software-static.download.prss.microsoft.com/sg/download/888969d5-f34g-4e03-ac9d-1f9786c66749/SERVER_EVAL_x64FRE_en-us.iso" ;; "win2019-eval" ) size=5652088832 sum="6dae072e7f78f4ccab74a45341de0d6e2d45c39be25f1f5920a2ab4f51d7bcbb" + url="https://software-download.microsoft.com/download/pr/17763.737.190906-2324.rs5_release_svc_refresh_SERVER_EVAL_x64FRE_en-us_1.iso" + ;; + "win2019-hv" ) + size=3072712704 + sum="48e9b944518e5bbc80876a9a7ff99716f386f404f4be48dca47e16a66ae7872c" + url="https://software-download.microsoft.com/download/pr/17763.557.190612-0019.rs5_release_svc_refresh_SERVERHYPERCORE_OEM_x64FRE_en-us.ISO" ;; "win2016-eval" ) size=6972221440 sum="1ce702a578a3cb1ac3d14873980838590f06d5b7101c5daaccbac9d73f1fb50f" + url="https://software-download.microsoft.com/download/pr/Windows_Server_2016_Datacenter_EVAL_en-us_14393_refresh.ISO" ;; "win2012r2-eval" ) size=4542291968 sum="6612b5b1f53e845aacdf96e974bb119a3d9b4dcb5b82e65804ab7e534dc7b4d5" + url="https://download.microsoft.com/download/6/2/A/62A76ABB-9990-4EFC-A4FE-C7D698DAEB96/9600.17050.WINBLUE_REFRESH.140317-1640_X64FRE_SERVER_EVAL_EN-US-IR3_SSS_X64FREE_EN-US_DV9.ISO" ;; "win2008r2" ) size=3166840832 sum="30832ad76ccfa4ce48ccb936edefe02079d42fb1da32201bf9e3a880c8ed6312" + url="https://download.microsoft.com/download/4/1/D/41DEA7E0-B30D-4012-A1E3-F24DC03BA1BB/7601.17514.101119-1850_x64fre_server_eval_en-us-GRMSXEVAL_EN_DVD.iso" ;; esac case "${ret,,}" in "sum" ) echo "$sum" ;; "size" ) echo "$size" ;; - *) echo "";; + *) echo "$url";; esac return 0 @@ -770,25 +806,23 @@ getLink1() { case "${id,,}" in "win11x64" | "win11x64-enterprise" | "win11x64-enterprise-eval" ) - size=5946128384 - sum="5bb1459034f50766ee480d895d751af73a4af30814240ae32ebc5633546a5af7" - url="11/en-us_windows_11_23h2_x64.iso" + size=5332989952 + sum="aa1ad990f930d907b7a34ea897abbb0dfbe47552ca8acc146f92e40381839e05" + url="11/en-us_windows_11_24h2_x64.iso" ;; "win11x64-iot" | "win11x64-enterprise-iot-eval" ) - [[ "${lang,,}" != "en" ]] && [[ "${lang,,}" != "en-us" ]] && return 0 size=5144817664 sum="4f59662a96fc1da48c1b415d6c369d08af55ddd64e8f1c84e0166d9e50405d7a" url="11/X23-81951_26100.1742.240906-0331.ge_release_svc_refresh_CLIENT_ENTERPRISES_OEM_x64FRE_en-us.iso" ;; "win11x64-ltsc" | "win11x64-enterprise-ltsc-eval" ) - [[ "${lang,,}" != "en" ]] && [[ "${lang,,}" != "en-us" ]] && return 0 size=5144817664 sum="4f59662a96fc1da48c1b415d6c369d08af55ddd64e8f1c84e0166d9e50405d7a" url="11/X23-81951_26100.1742.240906-0331.ge_release_svc_refresh_CLIENT_ENTERPRISES_OEM_x64FRE_en-us.iso" ;; "win10x64" | "win10x64-enterprise" | "win10x64-enterprise-eval" ) - size=5623582720 - sum="57371545d752a79a8a8b163b209c7028915da661de83516e06ddae913290a855" + size=5535252480 + sum="557871965263d0fd0a1ea50b5d0d0d7cb04a279148ca905c1c675c9bc0d5486c" url="10/en-us_windows_10_22h2_x64.iso" ;; "win10x64-iot" | "win10x64-enterprise-iot-eval" ) @@ -841,7 +875,7 @@ getLink1() { sum="dfd9890881b7e832a927c38310fb415b7ea62ac5a896671f2ce2a111998f0df8" url="server/2008r2/en_windows_server_2008_r2_with_sp1_x64_dvd_617601-018.iso" ;; - "win7x64" | "win7x64-enterprise" ) + "win7x64" | "win7x64-enterprise" | "win7x64-enterprise-eval" ) size=3182604288 sum="ee69f3e9b86ff973f632db8e01700c5724ef78420b175d25bae6ead90f6805a7" url="7/en_windows_7_enterprise_with_sp1_x64_dvd_u_677651.iso" @@ -851,7 +885,7 @@ getLink1() { sum="0b738b55a5ea388ad016535a5c8234daf2e5715a0638488ddd8a228a836055a1" url="7/en_windows_7_with_sp1_x64.iso" ;; - "win7x86" | "win7x86-enterprise" ) + "win7x86" | "win7x86-enterprise" | "win7x86-enterprise-eval" ) size=2434502656 sum="8bdd46ff8cb8b8de9c4aba02706629c8983c45e87da110e64e13be17c8434dad" url="7/en_windows_7_enterprise_with_sp1_x86_dvd_u_677710.iso" @@ -881,6 +915,11 @@ getLink1() { sum="8fac68e1e56c64ad9a2aa0ad464560282e67fa4f4dd51d09a66f4e548eb0f2d6" url="xp/professional/en_win_xp_pro_x64_vl.iso" ;; + "win2kx86" ) + size=331701982 + sum="a93251b31f92316411bb48458a695d9051b13cdeba714c46f105012fdda45bf3" + url="2000/5.00.2195.6717_x86fre_client-professional_retail_en-us.7z" + ;; esac case "${ret,,}" in @@ -925,7 +964,7 @@ getLink2() { sum="dfd9890881b7e832a927c38310fb415b7ea62ac5a896671f2ce2a111998f0df8" url="Windows%20Server%202008%20R2/en_windows_server_2008_r2_with_sp1_x64_dvd_617601.iso" ;; - "win7x64" | "win7x64-enterprise" ) + "win7x64" | "win7x64-enterprise" | "win7x64-enterprise-eval" ) size=3182604288 sum="ee69f3e9b86ff973f632db8e01700c5724ef78420b175d25bae6ead90f6805a7" url="Windows%207/en_windows_7_enterprise_with_sp1_x64_dvd_u_677651.iso" @@ -935,7 +974,7 @@ getLink2() { sum="36f4fa2416d0982697ab106e3a72d2e120dbcdb6cc54fd3906d06120d0653808" url="Windows%207/en_windows_7_ultimate_with_sp1_x64_dvd_u_677332.iso" ;; - "win7x86" | "win7x86-enterprise" ) + "win7x86" | "win7x86-enterprise" | "win7x86-enterprise-eval" ) size=2434502656 sum="8bdd46ff8cb8b8de9c4aba02706629c8983c45e87da110e64e13be17c8434dad" url="Windows%207/en_windows_7_enterprise_with_sp1_x86_dvd_u_677710.iso" @@ -1019,11 +1058,40 @@ getLink3() { sum="a11116c0645d892d6a5a7c585ecc1fa13aa66f8c7cc6b03bf1f27bd16860cc35" url="tiny-10-23-h2/tiny10%20x64%2023h2.iso" ;; + "win7x64" | "win7x64-enterprise" | "win7x64-enterprise-eval" ) + size=3182604288 + sum="ee69f3e9b86ff973f632db8e01700c5724ef78420b175d25bae6ead90f6805a7" + url="en_windows_7_enterprise_with_sp1_x64_dvd_u_677651_202006/en_windows_7_enterprise_with_sp1_x64_dvd_u_677651.iso" + ;; + "win7x64-ultimate" ) + size=3320903680 + sum="36f4fa2416d0982697ab106e3a72d2e120dbcdb6cc54fd3906d06120d0653808" + url="win7-ult-sp1-english/Win7_Ult_SP1_English_x64.iso" + ;; + "win7x86" | "win7x86-enterprise" | "win7x86-enterprise-eval" ) + size=2434502656 + sum="8bdd46ff8cb8b8de9c4aba02706629c8983c45e87da110e64e13be17c8434dad" + url="en_windows_7_enterprise_with_sp1_x86_dvd_u_677710_202006/en_windows_7_enterprise_with_sp1_x86_dvd_u_677710.iso" + ;; + "win7x86-ultimate" ) + size=2564476928 + sum="e2c009a66d63a742941f5087acae1aa438dcbe87010bddd53884b1af6b22c940" + url="win7-ult-sp1-english/Win7_Ult_SP1_English_x32.iso" + ;; "winxpx86" ) size=617756672 sum="62b6c91563bad6cd12a352aa018627c314cfc5162d8e9f8af0756a642e602a46" url="XPPRO_SP3_ENU/en_windows_xp_professional_with_service_pack_3_x86_cd_x14-80428.iso" ;; + "winxpx64" ) + size=614166528 + sum="8fac68e1e56c64ad9a2aa0ad464560282e67fa4f4dd51d09a66f4e548eb0f2d6" + url="windows-xp-all-sp-msdn-iso-files-en-de-ru-tr-x86-x64/en_win_xp_sp1_pro_x64_vl.iso" + ;; + "win2kx86" ) + size=386859008 + sum="e3816f6e80b66ff686ead03eeafffe9daf020a5e4717b8bd4736b7c51733ba22" + url="MicrosoftWindows2000BuildCollection/5.00.2195.6717_x86fre_client-professional_retail_en-us-ZRMPFPP_EN.iso" esac case "${ret,,}" in @@ -1110,66 +1178,12 @@ isESD() { return 1 } -isMG() { - - local id="$1" - local lang="$2" - - case "${id,,}" in - "win11${PLATFORM,,}" ) - return 0 - ;; - "win11${PLATFORM,,}-enterprise" | "win11${PLATFORM,,}-enterprise-eval" ) - return 0 - ;; - "win11${PLATFORM,,}-ltsc" | "win11${PLATFORM,,}-enterprise-ltsc-eval" ) - return 0 - ;; - "win11${PLATFORM,,}-iot" | "win11${PLATFORM,,}-enterprise-iot-eval" ) - return 0 - ;; - "win10${PLATFORM,,}" ) - return 0 - ;; - "win10${PLATFORM,,}-enterprise" | "win10${PLATFORM,,}-enterprise-eval" ) - return 0 - ;; - "win10${PLATFORM,,}-ltsc" | "win10${PLATFORM,,}-enterprise-ltsc-eval" ) - return 0 - ;; - "win10${PLATFORM,,}-iot" | "win10${PLATFORM,,}-enterprise-iot-eval" ) - return 0 - ;; - "win81${PLATFORM,,}-enterprise" | "win81${PLATFORM,,}-enterprise-eval" ) - return 0 - ;; - "win2025" | "win2025-eval" | "win2022" | "win2022-eval" | "win2019" | "win2019-eval" ) - return 0 - ;; - "win2016" | "win2016-eval" | "win2012r2" | "win2012r2-eval" | "win2008r2" | "win2008r2-eval" ) - return 0 - ;; - "win7x64" | "win7x64-enterprise" | "win7x64-ultimate" | "win7x86" | "win7x86-enterprise" | "win7x86-ultimate" ) - return 0 - ;; - "winvistax64" | "winvistax64-enterprise" | "winvistax64-ultimate" | "winvistax86" | "winvistax86-enterprise" | "winvistax86-ultimate" ) - return 0 - ;; - "winxpx86" | "winxpx64" ) - return 0 - ;; - esac - - return 1 -} - validVersion() { local id="$1" local lang="$2" local url - isMG "$id" "$lang" && return 0 isESD "$id" "$lang" && return 0 isMido "$id" "$lang" && return 0 @@ -1207,118 +1221,111 @@ addFolder() { return 0 } -migrateFiles() { - - local base="$1" - local version="$2" - local file="" - - [ -f "$base" ] && return 0 - - [[ "${version,,}" == "tiny10" ]] && file="tiny10_x64_23h2.iso" - [[ "${version,,}" == "tiny11" ]] && file="tiny11_2311_x64.iso" - [[ "${version,,}" == "core11" ]] && file="tiny11_core_x64_beta_1.iso" - [[ "${version,,}" == "winxpx86" ]] && file="en_windows_xp_professional_with_service_pack_3_x86_cd_x14-80428.iso" - [[ "${version,,}" == "winvistax64" ]] && file="en_windows_vista_sp2_x64_dvd_342267.iso" - [[ "${version,,}" == "win7x64" ]] && file="en_windows_7_enterprise_with_sp1_x64_dvd_u_677651.iso" - - [ ! -f "$STORAGE/$file" ] && return 0 - mv -f "$STORAGE/$file" "$base" || return 1 - - return 0 -} - prepareInstall() { local dir="$2" local desc="$3" - local arch="$4" - local key="$5" - local driver="$6" + local driver="$4" local drivers="/tmp/drivers" - rm -rf "$drivers" - mkdir -p "$drivers" - ETFS="[BOOT]/Boot-NoEmul.img" if [ ! -f "$dir/$ETFS" ] || [ ! -s "$dir/$ETFS" ]; then error "Failed to locate file \"$ETFS\" in $desc ISO image!" && return 1 fi - local msg="Adding drivers to image..." - info "$msg" && html "$msg" - - if ! bsdtar -xf /drivers.txz -C "$drivers"; then - error "Failed to extract drivers!" && return 1 - fi - - local target + local arch target + [ -d "$dir/AMD64" ] && arch="amd64" || arch="x86" [[ "${arch,,}" == "x86" ]] && target="$dir/I386" || target="$dir/AMD64" - if [ ! -f "$drivers/viostor/$driver/$arch/viostor.sys" ]; then - error "Failed to locate required storage drivers!" && return 1 + if [ ! -d "$target" ]; then + error "Failed to locate directory \"$target\" in $desc ISO image!" && return 1 fi - cp -L "$drivers/viostor/$driver/$arch/viostor.sys" "$target" || return 1 + if [[ "${driver,,}" == "xp" ]] || [[ "${driver,,}" == "2k3" ]]; then - mkdir -p "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1 - cp -L "$drivers/viostor/$driver/$arch/viostor.cat" "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1 - cp -L "$drivers/viostor/$driver/$arch/viostor.inf" "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1 - cp -L "$drivers/viostor/$driver/$arch/viostor.sys" "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1 + local msg="Adding drivers to image..." + info "$msg" && html "$msg" + + rm -rf "$drivers" + mkdir -p "$drivers" + + if ! bsdtar -xf /drivers.txz -C "$drivers"; then + error "Failed to extract drivers!" && return 1 + fi + + if [ ! -f "$drivers/viostor/$driver/$arch/viostor.sys" ]; then + error "Failed to locate required storage drivers!" && return 1 + fi + + cp -L "$drivers/viostor/$driver/$arch/viostor.sys" "$target" || return 1 + + mkdir -p "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1 + cp -L "$drivers/viostor/$driver/$arch/viostor.cat" "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1 + cp -L "$drivers/viostor/$driver/$arch/viostor.inf" "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1 + cp -L "$drivers/viostor/$driver/$arch/viostor.sys" "$dir/\$OEM\$/\$1/Drivers/viostor" || return 1 + + if [ ! -f "$drivers/NetKVM/$driver/$arch/netkvm.sys" ]; then + error "Failed to locate required network drivers!" && return 1 + fi + + mkdir -p "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1 + cp -L "$drivers/NetKVM/$driver/$arch/netkvm.cat" "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1 + cp -L "$drivers/NetKVM/$driver/$arch/netkvm.inf" "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1 + cp -L "$drivers/NetKVM/$driver/$arch/netkvm.sys" "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1 + + if [ ! -f "$target/TXTSETUP.SIF" ]; then + error "The file TXTSETUP.SIF could not be found!" && return 1 + fi + + sed -i '/^\[SCSI.Load\]/s/$/\nviostor=viostor.sys,4/' "$target/TXTSETUP.SIF" + sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\nviostor.sys=1,,,,,,4_,4,1,,,1,4/' "$target/TXTSETUP.SIF" + sed -i '/^\[SCSI\]/s/$/\nviostor=\"Red Hat VirtIO SCSI Disk Device\"/' "$target/TXTSETUP.SIF" + sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_1AF4\&DEV_1001\&SUBSYS_00000000=\"viostor\"/' "$target/TXTSETUP.SIF" + sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_1AF4\&DEV_1001\&SUBSYS_00020000=\"viostor\"/' "$target/TXTSETUP.SIF" + sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_1AF4\&DEV_1001\&SUBSYS_00021AF4=\"viostor\"/' "$target/TXTSETUP.SIF" + sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_1AF4\&DEV_1001\&SUBSYS_00000000=\"viostor\"/' "$target/TXTSETUP.SIF" + + if [ ! -d "$drivers/sata/xp/$arch" ]; then + error "Failed to locate required SATA drivers!" && return 1 + fi + + mkdir -p "$dir/\$OEM\$/\$1/Drivers/sata" || return 1 + cp -Lr "$drivers/sata/xp/$arch/." "$dir/\$OEM\$/\$1/Drivers/sata" || return 1 + cp -Lr "$drivers/sata/xp/$arch/." "$target" || return 1 + + sed -i '/^\[SCSI.Load\]/s/$/\niaStor=iaStor.sys,4/' "$target/TXTSETUP.SIF" + sed -i '/^\[FileFlags\]/s/$/\niaStor.sys = 16/' "$target/TXTSETUP.SIF" + sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaStor.cat = 1,,,,,,,1,0,0/' "$target/TXTSETUP.SIF" + sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaStor.inf = 1,,,,,,,1,0,0/' "$target/TXTSETUP.SIF" + sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaStor.sys = 1,,,,,,4_,4,1,,,1,4/' "$target/TXTSETUP.SIF" + sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaStor.sys = 1,,,,,,,1,0,0/' "$target/TXTSETUP.SIF" + sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaahci.cat = 1,,,,,,,1,0,0/' "$target/TXTSETUP.SIF" + sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaAHCI.inf = 1,,,,,,,1,0,0/' "$target/TXTSETUP.SIF" + sed -i '/^\[SCSI\]/s/$/\niaStor=\"Intel\(R\) SATA RAID\/AHCI Controller\"/' "$target/TXTSETUP.SIF" + sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_8086\&DEV_2922\&CC_0106=\"iaStor\"/' "$target/TXTSETUP.SIF" + + rm -rf "$drivers" - if [ ! -f "$drivers/NetKVM/$driver/$arch/netkvm.sys" ]; then - error "Failed to locate required network drivers!" && return 1 fi - mkdir -p "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1 - cp -L "$drivers/NetKVM/$driver/$arch/netkvm.cat" "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1 - cp -L "$drivers/NetKVM/$driver/$arch/netkvm.inf" "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1 - cp -L "$drivers/NetKVM/$driver/$arch/netkvm.sys" "$dir/\$OEM\$/\$1/Drivers/NetKVM" || return 1 - - if [ ! -f "$target/TXTSETUP.SIF" ]; then - error "The file TXTSETUP.SIF could not be found!" && return 1 - fi - - sed -i '/^\[SCSI.Load\]/s/$/\nviostor=viostor.sys,4/' "$target/TXTSETUP.SIF" - sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\nviostor.sys=1,,,,,,4_,4,1,,,1,4/' "$target/TXTSETUP.SIF" - sed -i '/^\[SCSI\]/s/$/\nviostor=\"Red Hat VirtIO SCSI Disk Device\"/' "$target/TXTSETUP.SIF" - sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_1AF4\&DEV_1001\&SUBSYS_00000000=\"viostor\"/' "$target/TXTSETUP.SIF" - sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_1AF4\&DEV_1001\&SUBSYS_00020000=\"viostor\"/' "$target/TXTSETUP.SIF" - sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_1AF4\&DEV_1001\&SUBSYS_00021AF4=\"viostor\"/' "$target/TXTSETUP.SIF" - sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_1AF4\&DEV_1001\&SUBSYS_00000000=\"viostor\"/' "$target/TXTSETUP.SIF" - - if [ ! -d "$drivers/sata/xp/$arch" ]; then - error "Failed to locate required SATA drivers!" && return 1 - fi - - mkdir -p "$dir/\$OEM\$/\$1/Drivers/sata" || return 1 - cp -Lr "$drivers/sata/xp/$arch/." "$dir/\$OEM\$/\$1/Drivers/sata" || return 1 - cp -Lr "$drivers/sata/xp/$arch/." "$target" || return 1 - - sed -i '/^\[SCSI.Load\]/s/$/\niaStor=iaStor.sys,4/' "$target/TXTSETUP.SIF" - sed -i '/^\[FileFlags\]/s/$/\niaStor.sys = 16/' "$target/TXTSETUP.SIF" - sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaStor.cat = 1,,,,,,,1,0,0/' "$target/TXTSETUP.SIF" - sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaStor.inf = 1,,,,,,,1,0,0/' "$target/TXTSETUP.SIF" - sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaStor.sys = 1,,,,,,4_,4,1,,,1,4/' "$target/TXTSETUP.SIF" - sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaStor.sys = 1,,,,,,,1,0,0/' "$target/TXTSETUP.SIF" - sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaahci.cat = 1,,,,,,,1,0,0/' "$target/TXTSETUP.SIF" - sed -i '/^\[SourceDisksFiles.'"$arch"'\]/s/$/\niaAHCI.inf = 1,,,,,,,1,0,0/' "$target/TXTSETUP.SIF" - sed -i '/^\[SCSI\]/s/$/\niaStor=\"Intel\(R\) SATA RAID\/AHCI Controller\"/' "$target/TXTSETUP.SIF" - sed -i '/^\[HardwareIdsDatabase\]/s/$/\nPCI\\VEN_8086\&DEV_2922\&CC_0106=\"iaStor\"/' "$target/TXTSETUP.SIF" - - rm -rf "$drivers" - local pid file setup setup=$(find "$target" -maxdepth 1 -type f -iname setupp.ini | head -n 1) - pid=$(<"$setup") - pid="${pid:(-4)}" - pid="${pid:0:3}" - if [[ "$pid" == "270" ]]; then - warn "this version of $desc requires a volume license key (VLK), it will ask for one during installation." + if [ -n "$setup" ]; then + + pid=$(<"$setup") + pid="${pid:(-4)}" + pid="${pid:0:3}" + + if [[ "$pid" == "270" ]]; then + warn "this version of $desc requires a volume license key (VLK), it will ask for one during installation." + fi + fi + mkdir -p "$dir/\$OEM\$" + if ! addFolder "$dir"; then error "Failed to add OEM folder to image!" && return 1 fi @@ -1333,11 +1340,48 @@ prepareInstall() { XHEX=$(printf '%x\n' "$WIDTH") YHEX=$(printf '%x\n' "$HEIGHT") - local username="Docker" - local password="*" + local username="" + local password="" - [ -n "$PASSWORD" ] && password="$PASSWORD" [ -n "$USERNAME" ] && username=$(echo "$USERNAME" | sed 's/[^[:alnum:]@!._-]//g') + [ -z "$username" ] && username="Docker" + + [ -n "$PASSWORD" ] && password=$(echo "$PASSWORD" | sed 's/"//g') + [ -z "$password" ] && password="admin" + + local ip="20.20.20.1" + [ -n "${VM_NET_IP:-}" ] && ip="${VM_NET_IP%.*}.1" + + # These are not pirated keys, they come from the official MS documentation. + case "${driver,,}" in + "xp" ) + + if [[ "${arch,,}" == "x86" ]]; then + # Windows XP Professional x86 generic key (no activation, trial-only) + [ -z "$KEY" ] && KEY="DR8GV-C8V6J-BYXHG-7PYJR-DB66Y" + else + # Windows XP Professional x64 generic key (no activation, trial-only) + [ -z "$KEY" ] && KEY="B2RBK-7KPT9-4JP6X-QQFWM-PJD6G" + fi ;; + + "2k3" ) + + if [[ "${arch,,}" == "x86" ]]; then + # Windows Server 2003 Standard x86 generic key (no activation, trial-only) + [ -z "$KEY" ] && KEY="QKDCQ-TP2JM-G4MDG-VR6F2-P9C48" + else + # Windows Server 2003 Standard x64 generic key (no activation, trial-only) + [ -z "$KEY" ] && KEY="P4WJG-WK3W7-3HM8W-RWHCK-8JTRY" + fi ;; + + "2k" ) + + # Windows 2000 Professional x86 generic key + KEY="G74HG-XXQTJ-RTX64-QKP3F-HKHXP" ;; + + * ) error "Unknown version: \"$driver\"" && return 1 ;; + + esac find "$target" -maxdepth 1 -type f -iname winnt.sif -exec rm {} \; @@ -1376,7 +1420,7 @@ prepareInstall() { echo " FullName=\"$username\"" echo " ComputerName=\"*\"" echo " OrgName=\"Windows for Docker\"" - echo " ProductKey=$key" + echo " ProductID=$KEY" echo "" echo "[Identification]" echo " JoinWorkgroup = WORKGROUP" @@ -1435,12 +1479,14 @@ prepareInstall() { echo "[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced]" echo "\"HideFileExt\"=dword:00000000" echo "" + echo "[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer]" + echo "\"NoWelcomeScreen\"=\"1\"" + echo "" echo "[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]" - echo "\"DefaultUserName\"=\"$username\"" - echo "\"DefaultDomainName\"=\"Dockur\"" - echo "\"AltDefaultUserName\"=\"$username\"" - echo "\"AltDefaultDomainName\"=\"Dockur\"" echo "\"AutoAdminLogon\"=\"1\"" + echo "\"DefaultUserName\"=\"$username\"" + echo "\"DefaultPassword\"=\"$password\"" + echo "\"DefaultDomainName\"=\"Dockur\"" echo "" echo "[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Video\{23A77BF7-ED96-40EC-AF06-9B1F4867732A}\0000]" echo "\"DefaultSettings.BitsPerPel\"=dword:00000020" @@ -1511,6 +1557,14 @@ prepareInstall() { echo "" echo "Call Domain.MoveHere(LocalAdminADsPath, \"$username\")" echo "" + echo "With (CreateObject(\"Scripting.FileSystemObject\"))" + echo " SysRoot = WshShell.ExpandEnvironmentStrings(\"%SystemRoot%\")" + echo " Set oFile = .OpenTextFile(SysRoot & \"\system32\drivers\etc\hosts\", 8, true)" + echo " oFile.Write(\"$ip host.lan\")" + echo " oFile.Close()" + echo " Set oFile = Nothing" + echo "End With" + echo "" } | unix2dos > "$dir/\$OEM\$/admin.vbs" { echo "[COMMANDS]" @@ -1522,56 +1576,6 @@ prepareInstall() { return 0 } -prepare2k3() { - - local iso="$1" - local dir="$2" - local desc="$3" - local driver="2k3" - local arch key - - [ -d "$dir/AMD64" ] && arch="amd64" || arch="x86" - - if [[ "${arch,,}" == "x86" ]]; then - # Windows Server 2003 Standard x86 generic key (no activation, trial-only) - # This is not a pirated key, it comes from the official MS documentation. - key="QKDCQ-TP2JM-G4MDG-VR6F2-P9C48" - else - # Windows Server 2003 Standard x64 generic key (no activation, trial-only) - # This is not a pirated key, it comes from the official MS documentation. - key="P4WJG-WK3W7-3HM8W-RWHCK-8JTRY" - fi - - prepareInstall "$iso" "$dir" "$desc" "$arch" "$key" "$driver" || return 1 - - return 0 -} - -prepareXP() { - - local iso="$1" - local dir="$2" - local desc="$3" - local driver="xp" - local arch key - - [ -d "$dir/AMD64" ] && arch="amd64" || arch="x86" - - if [[ "${arch,,}" == "x86" ]]; then - # Windows XP Professional x86 generic key (no activation, trial-only) - # This is not a pirated key, it comes from the official MS documentation. - key="DR8GV-C8V6J-BYXHG-7PYJR-DB66Y" - else - # Windows XP Professional x64 generic key (no activation, trial-only) - # This is not a pirated key, it comes from the official MS documentation. - key="B2RBK-7KPT9-4JP6X-QQFWM-PJD6G" - fi - - prepareInstall "$iso" "$dir" "$desc" "$arch" "$key" "$driver" || return 1 - - return 0 -} - prepareLegacy() { local iso="$1" @@ -1656,22 +1660,34 @@ setMachine() { "win9"* ) ETFS="[BOOT]/Boot-1.44M.img" ;; "win2k"* ) - ETFS="[BOOT]/Boot-NoEmul.img" ;; + if ! prepareInstall "$iso" "$dir" "$desc" "2k"; then + error "Failed to prepare $desc ISO!" && return 1 + fi ;; "winxp"* ) - if ! prepareXP "$iso" "$dir" "$desc"; then + if ! prepareInstall "$iso" "$dir" "$desc" "xp"; then error "Failed to prepare $desc ISO!" && return 1 fi ;; "win2003"* ) - if ! prepare2k3 "$iso" "$dir" "$desc"; then + if ! prepareInstall "$iso" "$dir" "$desc" "2k3"; then error "Failed to prepare $desc ISO!" && return 1 fi ;; esac case "${id,,}" in - "win9"* | "win2k"* ) + "win9"* ) + USB="no" + VGA="cirrus" DISK_TYPE="auto" + ADAPTER="rtl8139" MACHINE="pc-i440fx-2.4" BOOT_MODE="windows_legacy" ;; + "win2k"* ) + VGA="cirrus" + MACHINE="pc" + USB="pci-ohci" + DISK_TYPE="auto" + ADAPTER="rtl8139" + BOOT_MODE="windows_legacy" ;; "winxp"* | "win2003"* ) DISK_TYPE="blk" BOOT_MODE="windows_legacy" ;; diff --git a/src/entry.sh b/src/entry.sh index 4de2f59..88b102c 100755 --- a/src/entry.sh +++ b/src/entry.sh @@ -1,16 +1,17 @@ #!/usr/bin/env bash set -Eeuox pipefail +: "${APP:="Windows"}" +: "${PLATFORM:="x64"}" : "${BOOT_MODE:="windows"}" - -APP="Windows" -SUPPORT="https://github.com/dockur/windows" +: "${SUPPORT:="https://github.com/dockur/windows"}" cd /run +. utils.sh # Load functions . reset.sh # Initialize system . define.sh # Define versions -. mido.sh # Download code +. mido.sh # Download Windows . install.sh # Run installation . disk.sh # Initialize disks . display.sh # Initialize graphics @@ -56,7 +57,10 @@ terminal bg_pid=$! tail -fn +0 "$QEMU_LOG" 2>/dev/null & -cat "$QEMU_TERM" 2>/dev/null | tee "$QEMU_PTY" & +cat "$QEMU_TERM" 2>/dev/null | tee "$QEMU_PTY" | + sed -u -e 's/\x1B\[[=0-9;]*[a-z]//gi' \ + -e 's/failed to load Boot/skipped Boot/g' \ + -e 's/0): Not Found/0)/g' & term_pd=$! wait $bg_pid diff --git a/src/install.sh b/src/install.sh index f392c0e..a37124b 100644 --- a/src/install.sh +++ b/src/install.sh @@ -10,21 +10,31 @@ EFISYS="efi/microsoft/boot/efisys_noprompt.bin" skipInstall() { local iso="$1" + local method="" local magic byte local boot="$STORAGE/windows.boot" local previous="$STORAGE/windows.base" if [ -f "$previous" ]; then previous=$(<"$previous") + previous="${previous//[![:print:]]/}" if [ -n "$previous" ]; then - previous="$STORAGE/$previous" - if [[ "${previous,,}" != "${iso,,}" ]]; then + if [[ "${STORAGE,,}/${previous,,}" != "${iso,,}" ]]; then if [ -f "$boot" ] && hasDisk; then - info "Detected that the version was changed, but ignoring this because Windows is already installed." - info "Please start with an empty /storage folder, if you want to install a different version of Windows." + if [[ "${iso,,}" == "${STORAGE,,}/windows."* ]]; then + method="your custom .iso file" + else + if [[ "${previous,,}" != "windows."* ]]; then + method="the VERSION variable" + fi + fi + if [ -n "$method" ]; then + info "Detected that $method was changed, but ignoring this because Windows is already installed." + info "Please start with an empty /storage folder, if you want to install a different version of Windows." + fi return 0 fi - [ -f "$previous" ] && rm -f "$previous" + rm -f "$STORAGE/$previous" return 1 fi fi @@ -76,8 +86,6 @@ startInstall() { BOOT="$STORAGE/$file" - ! migrateFiles "$BOOT" "$VERSION" && error "Migration failed!" && exit 57 - fi skipInstall "$BOOT" && return 1 @@ -120,6 +128,8 @@ finishInstall() { rm -f "$STORAGE/windows.old" rm -f "$STORAGE/windows.vga" + rm -f "$STORAGE/windows.net" + rm -f "$STORAGE/windows.usb" rm -f "$STORAGE/windows.args" rm -f "$STORAGE/windows.base" rm -f "$STORAGE/windows.boot" @@ -162,10 +172,22 @@ finishInstall() { echo "$ARGS" > "$STORAGE/windows.args" fi + if [ -n "${VGA:-}" ] && [[ "${VGA:-}" != "virtio"* ]]; then + echo "$VGA" > "$STORAGE/windows.vga" + fi + + if [ -n "${USB:-}" ] && [[ "${USB:-}" != "qemu-xhci"* ]]; then + echo "$USB" > "$STORAGE/windows.usb" + fi + if [ -n "${DISK_TYPE:-}" ] && [[ "${DISK_TYPE:-}" != "scsi" ]]; then echo "$DISK_TYPE" > "$STORAGE/windows.type" fi + if [ -n "${ADAPTER:-}" ] && [[ "${ADAPTER:-}" != "virtio-net-pci" ]]; then + echo "$ADAPTER" > "$STORAGE/windows.net" + fi + rm -rf "$TMP" return 0 } @@ -177,6 +199,7 @@ abortInstall() { local efi [[ "${iso,,}" == *".esd" ]] && exit 60 + [[ "${UNPACK:-}" == [Yy1]* ]] && exit 60 efi=$(find "$dir" -maxdepth 1 -type d -iname efi | head -n 1) @@ -201,11 +224,23 @@ abortInstall() { detectCustom() { - local file base + local dir file base + local fname="custom.iso" + local boot="$STORAGE/windows.boot" + CUSTOM="" - file=$(find / -maxdepth 1 -type f -iname custom.iso | head -n 1) - [ ! -s "$file" ] && file=$(find "$STORAGE" -maxdepth 1 -type f -iname custom.iso | head -n 1) + dir=$(find / -maxdepth 1 -type d -iname "$fname" | head -n 1) + [ ! -d "$dir" ] && dir=$(find "$STORAGE" -maxdepth 1 -type d -iname "$fname" | head -n 1) + + if [ -d "$dir" ]; then + if ! hasDisk || [ ! -f "$boot" ]; then + error "The bind $dir maps to a file that does not exist!" && return 1 + fi + fi + + file=$(find / -maxdepth 1 -type f -iname "$fname" | head -n 1) + [ ! -s "$file" ] && file=$(find "$STORAGE" -maxdepth 1 -type f -iname "$fname" | head -n 1) if [ ! -s "$file" ] && [[ "${VERSION,,}" != "http"* ]]; then base=$(basename "$VERSION") @@ -246,12 +281,12 @@ extractESD() { mkdir -p "$dir" size=16106127360 - size_gb=$(( (size + 1073741823)/1073741824 )) + size_gb=$(formatBytes "$size") space=$(df --output=avail -B 1 "$dir" | tail -n 1) - space_gb=$(( (space + 1073741823)/1073741824 )) + space_gb=$(formatBytes "$space") if (( size > space )); then - error "Not enough free space in $STORAGE, have $space_gb GB available but need at least $size_gb GB." && return 1 + error "Not enough free space in $STORAGE, have $space_gb available but need at least $size_gb." && return 1 fi local esdImageCount @@ -316,7 +351,7 @@ extractImage() { local dir="$2" local version="$3" local desc="local ISO" - local size size_gb space space_gb + local file size size_gb space space_gb if [ -z "$CUSTOM" ]; then desc="downloaded ISO" @@ -337,16 +372,16 @@ extractImage() { mkdir -p "$dir" size=$(stat -c%s "$iso") - size_gb=$(( (size + 1073741823)/1073741824 )) + size_gb=$(formatBytes "$size") space=$(df --output=avail -B 1 "$dir" | tail -n 1) - space_gb=$(( (space + 1073741823)/1073741824 )) + space_gb=$(formatBytes "$space") if ((size<100000000)); then error "Invalid ISO file: Size is smaller than 100 MB" && return 1 fi if (( size > space )); then - error "Not enough free space in $STORAGE, have $space_gb GB available but need at least $size_gb GB." && return 1 + error "Not enough free space in $STORAGE, have $space_gb available but need at least $size_gb." && return 1 fi rm -rf "$dir" @@ -355,7 +390,26 @@ extractImage() { error "Failed to extract ISO file: $iso" && return 1 fi - LABEL=$(isoinfo -d -i "$iso" | sed -n 's/Volume id: //p') + if [[ "${UNPACK:-}" != [Yy1]* ]]; then + + LABEL=$(isoinfo -d -i "$iso" | sed -n 's/Volume id: //p') + + else + + file=$(find "$dir" -maxdepth 1 -type f -iname "*.iso" | head -n 1) + + if [ -z "$file" ]; then + error "Failed to find any .iso file in archive!" && return 1 + fi + + if ! 7z x "$file" -o"$dir" > /dev/null; then + error "Failed to extract archive!" && return 1 + fi + + LABEL=$(isoinfo -d -i "$file" | sed -n 's/Volume id: //p') + rm -f "$file" + + fi return 0 } @@ -489,6 +543,10 @@ setXML() { local file="/custom.xml" + if [ -d "$file" ]; then + error "The bind $file maps to a file that does not exist!" && exit 67 + fi + [ ! -f "$file" ] || [ ! -s "$file" ] && file="$STORAGE/custom.xml" [ ! -f "$file" ] || [ ! -s "$file" ] && file="/run/assets/custom.xml" [ ! -f "$file" ] || [ ! -s "$file" ] && file="$1" @@ -573,6 +631,10 @@ detectImage() { info "Detected: $desc" setXML "" && return 0 + if [[ "$DETECTED" == "win81x86"* ]] || [[ "$DETECTED" == "win10x86"* ]]; then + error "The 32-bit version of $desc is not supported!" && return 1 + fi + msg="the answer file for $desc was not found ($DETECTED.xml)" local fallback="/run/assets/${DETECTED%%-*}.xml" @@ -620,6 +682,10 @@ updateXML() { local language="$2" local culture region user admin pass keyboard + if [ -n "${VM_NET_IP:-}" ]; then + sed -i "s/ 20.20.20.1 / ${VM_NET_IP%.*}.1 /g" "$asset" + fi + [ -z "$HEIGHT" ] && HEIGHT="720" [ -z "$WIDTH" ] && WIDTH="1280" @@ -657,15 +723,26 @@ updateXML() { sed -i "s/<Username>Docker<\/Username>/<Username>$user<\/Username>/g" "$asset" fi - if [ -n "$PASSWORD" ]; then - pass=$(printf '%s' "${PASSWORD}Password" | iconv -f utf-8 -t utf-16le | base64 -w 0) - admin=$(printf '%s' "${PASSWORD}AdministratorPassword" | iconv -f utf-8 -t utf-16le | base64 -w 0) - sed -i "s/<Value>password<\/Value>/<Value>$admin<\/Value>/g" "$asset" - sed -i "s/<PlainText>true<\/PlainText>/<PlainText>false<\/PlainText>/g" "$asset" - sed -z "s/<Password>...........<Value \/>/<Password>\n <Value>$pass<\/Value>/g" -i "$asset" - sed -z "s/<Password>...............<Value \/>/<Password>\n <Value>$pass<\/Value>/g" -i "$asset" - sed -z "s/<AdministratorPassword>...........<Value \/>/<AdministratorPassword>\n <Value>$admin<\/Value>/g" -i "$asset" - sed -z "s/<AdministratorPassword>...............<Value \/>/<AdministratorPassword>\n <Value>$admin<\/Value>/g" -i "$asset" + [ -n "$PASSWORD" ] && pass="$PASSWORD" || pass="admin" + + pw=$(printf '%s' "${pass}Password" | iconv -f utf-8 -t utf-16le | base64 -w 0) + admin=$(printf '%s' "${pass}AdministratorPassword" | iconv -f utf-8 -t utf-16le | base64 -w 0) + + sed -i "s/<Value>password<\/Value>/<Value>$admin<\/Value>/g" "$asset" + sed -i "s/<PlainText>true<\/PlainText>/<PlainText>false<\/PlainText>/g" "$asset" + sed -z "s/<Password>...........<Value \/>/<Password>\n <Value>$pw<\/Value>/g" -i "$asset" + sed -z "s/<Password>...............<Value \/>/<Password>\n <Value>$pw<\/Value>/g" -i "$asset" + sed -z "s/<AdministratorPassword>...........<Value \/>/<AdministratorPassword>\n <Value>$admin<\/Value>/g" -i "$asset" + sed -z "s/<AdministratorPassword>...............<Value \/>/<AdministratorPassword>\n <Value>$admin<\/Value>/g" -i "$asset" + + if [ -n "$EDITION" ]; then + [[ "${EDITION^^}" == "CORE" ]] && EDITION="STANDARDCORE" + sed -i "s/SERVERSTANDARD<\/Value>/SERVER${EDITION^^}<\/Value>/g" "$asset" + fi + + if [ -n "$KEY" ]; then + sed -i '/<ProductKey>/,/<\/ProductKey>/d' "$asset" + sed -i "s/<\/UserData>/ <ProductKey>\n <Key>${KEY}<\/Key>\n <WillShowUI>OnError<\/WillShowUI>\n <\/ProductKey>\n <\/UserData>/g" "$asset" fi return 0 @@ -677,8 +754,13 @@ addDriver() { local path="$2" local target="$3" local driver="$4" + local desc="" local folder="" + if [ -z "$id" ]; then + warn "no Windows version specified for \"$driver\" driver!" && return 0 + fi + case "${id,,}" in "win7x86"* ) folder="w7/x86" ;; "win7x64"* ) folder="w7/amd64" ;; @@ -698,7 +780,12 @@ addDriver() { esac if [ -z "$folder" ]; then - warn "no \"$driver\" driver found for \"$DETECTED\" !" && return 0 + desc=$(printVersion "$id" "$id") + if [[ "${id,,}" != *"x86"* ]]; then + warn "no \"$driver\" driver available for \"$desc\" !" && return 0 + else + warn "no \"$driver\" driver available for the 32-bit version of \"$desc\" !" && return 0 + fi fi [ ! -d "$path/$driver/$folder" ] && return 0 @@ -731,6 +818,11 @@ addDrivers() { local msg="Adding drivers to image..." info "$msg" && html "$msg" + if [ -z "$version" ]; then + version="win11x64" + warn "Windows version unknown, falling back to Windows 11 drivers..." + fi + if ! bsdtar -xf /drivers.txz -C "$drivers"; then error "Failed to extract drivers from archive!" && return 1 fi @@ -924,12 +1016,12 @@ buildImage() { fi size=$(du -h -b --max-depth=0 "$dir" | cut -f1) - size_gb=$(( (size + 1073741823)/1073741824 )) + size_gb=$(formatBytes "$size") space=$(df --output=avail -B 1 "$TMP" | tail -n 1) - space_gb=$(( (space + 1073741823)/1073741824 )) + space_gb=$(formatBytes "$space") if (( size > space )); then - error "Not enough free space in $STORAGE, have $space_gb GB available but need at least $size_gb GB." && return 1 + error "Not enough free space in $STORAGE, have $space_gb available but need at least $size_gb." && return 1 fi if [[ "${BOOT_MODE,,}" != "windows_legacy" ]]; then @@ -973,52 +1065,47 @@ bootWindows() { if [ -f "$STORAGE/windows.args" ]; then ARGS=$(<"$STORAGE/windows.args") + ARGS="${ARGS//[![:print:]]/}" ARGUMENTS="$ARGS ${ARGUMENTS:-}" fi + if [ -s "$STORAGE/windows.vga" ] && [ -f "$STORAGE/windows.vga" ]; then + if [ -z "${VGA:-}" ]; then + VGA=$(<"$STORAGE/windows.vga") + VGA="${VGA//[![:print:]]/}" + fi + fi + + if [ -s "$STORAGE/windows.usb" ] && [ -f "$STORAGE/windows.usb" ]; then + if [ -z "${USB:-}" ]; then + USB=$(<"$STORAGE/windows.usb") + USB="${USB//[![:print:]]/}" + fi + fi + + if [ -s "$STORAGE/windows.net" ] && [ -f "$STORAGE/windows.net" ]; then + if [ -z "${ADAPTER:-}" ]; then + ADAPTER=$(<"$STORAGE/windows.net") + ADAPTER="${ADAPTER//[![:print:]]/}" + fi + fi + if [ -s "$STORAGE/windows.type" ] && [ -f "$STORAGE/windows.type" ]; then - [ -z "${DISK_TYPE:-}" ] && DISK_TYPE=$(<"$STORAGE/windows.type") + if [ -z "${DISK_TYPE:-}" ]; then + DISK_TYPE=$(<"$STORAGE/windows.type") + DISK_TYPE="${DISK_TYPE//[![:print:]]/}" + fi fi if [ -s "$STORAGE/windows.mode" ] && [ -f "$STORAGE/windows.mode" ]; then BOOT_MODE=$(<"$STORAGE/windows.mode") - if [ -s "$STORAGE/windows.old" ] && [ -f "$STORAGE/windows.old" ]; then - [[ "${PLATFORM,,}" == "x64" ]] && MACHINE=$(<"$STORAGE/windows.old") - fi - return 0 + BOOT_MODE="${BOOT_MODE//[![:print:]]/}" fi - # Migrations - - [[ "${PLATFORM,,}" != "x64" ]] && return 0 - - if [ -f "$STORAGE/windows.old" ]; then - MACHINE=$(<"$STORAGE/windows.old") - [ -z "$MACHINE" ] && MACHINE="q35" - BOOT_MODE="windows_legacy" - echo "$BOOT_MODE" > "$STORAGE/windows.mode" - return 0 - fi - - local creation="1.10" - local minimal="2.14" - - if [ -f "$STORAGE/windows.ver" ]; then - creation=$(<"$STORAGE/windows.ver") - [[ "${creation}" != *"."* ]] && creation="$minimal" - fi - - # Force secure boot on installs created prior to v2.14 - if (( $(echo "$creation < $minimal" | bc -l) )); then - if [[ "${BOOT_MODE,,}" == "windows" ]]; then - BOOT_MODE="windows_secure" - echo "$BOOT_MODE" > "$STORAGE/windows.mode" - if [ -f "$STORAGE/windows.rom" ] && [ ! -f "$STORAGE/$BOOT_MODE.rom" ]; then - mv -f "$STORAGE/windows.rom" "$STORAGE/$BOOT_MODE.rom" - fi - if [ -f "$STORAGE/windows.vars" ] && [ ! -f "$STORAGE/$BOOT_MODE.vars" ]; then - mv -f "$STORAGE/windows.vars" "$STORAGE/$BOOT_MODE.vars" - fi + if [ -s "$STORAGE/windows.old" ] && [ -f "$STORAGE/windows.old" ]; then + if [[ "${PLATFORM,,}" == "x64" ]]; then + MACHINE=$(<"$STORAGE/windows.old") + MACHINE="${MACHINE//[![:print:]]/}" fi fi diff --git a/src/mido.sh b/src/mido.sh index cd4171f..5258d1b 100644 --- a/src/mido.sh +++ b/src/mido.sh @@ -93,6 +93,7 @@ download_windows() { # uuidgen: For MacOS (installed by default) and other systems (e.g. with no /proc) that don't have a kernel interface for generating random UUIDs session_id=$(cat /proc/sys/kernel/random/uuid 2> /dev/null || uuidgen --random) + session_id="${session_id//[![:print:]]/}" # Get product edition ID for latest release of given Windows version # Product edition ID: This specifies both the Windows release (e.g. 22H2) and edition ("multi-edition" is default, either Home/Pro/Edu/etc., we select "Pro" in the answer files) in one number @@ -208,6 +209,9 @@ download_windows_eval() { "win2022-eval" ) enterprise_type="server" windows_version="windows-server-2022" ;; + "win2019-hv" ) + enterprise_type="server" + windows_version="hyper-v-server-2019" ;; "win2019-eval" ) enterprise_type="server" windows_version="windows-server-2019" ;; @@ -242,10 +246,10 @@ download_windows_eval() { [[ "$DEBUG" == [Yy1]* ]] && echo "Getting download link.." - if [[ "$enterprise_type" == "iot" ]]; then - filter="https://go.microsoft.com/fwlink/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country^^}" - else - filter="https://go.microsoft.com/fwlink/p/?LinkID=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country^^}" + filter="https://go.microsoft.com/fwlink/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country,,}" + + if ! echo "$iso_download_page_html" | grep -io "$filter" > /dev/null; then + filter="https://go.microsoft.com/fwlink/p/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country,,}" fi iso_download_links=$(echo "$iso_download_page_html" | grep -io "$filter") || { @@ -310,7 +314,7 @@ getWindows() { info "$msg" && html "$msg" case "${version,,}" in - "win2008r2" | "win81${PLATFORM,,}-enterprise"* | "win11${PLATFORM,,}-enterprise-iot"* | "win11${PLATFORM,,}-enterprise-ltsc"* ) + "win2008r2" | "win81${PLATFORM,,}"* | "win11${PLATFORM,,}-enterprise-iot"* | "win11${PLATFORM,,}-enterprise-ltsc"* ) if [[ "${lang,,}" != "en" ]] && [[ "${lang,,}" != "en-"* ]]; then error "No download in the $language language available for $edition!" MIDO_URL="" && return 1 @@ -335,7 +339,7 @@ getWindows() { "win11${PLATFORM,,}-enterprise"* | "win10${PLATFORM,,}-enterprise"* ) download_windows_eval "$version" "$lang" "$edition" && return 0 ;; - "win2025-eval" | "win2022-eval" | "win2019-eval" | "win2016-eval" | "win2012r2-eval" ) + "win2025-eval" | "win2022-eval" | "win2019-eval" | "win2019-hv" | "win2016-eval" | "win2012r2-eval" ) download_windows_eval "$version" "$lang" "$edition" && return 0 ;; "win81${PLATFORM,,}-enterprise"* | "win2008r2" ) @@ -343,53 +347,10 @@ getWindows() { * ) error "Invalid VERSION specified, value \"$version\" is not recognized!" ;; esac - if [[ "${PLATFORM,,}" != "x64" ]]; then - MIDO_URL="" - return 1 - fi + MIDO_URL=$(getMido "$version" "$lang" "") + [ -z "$MIDO_URL" ] && return 1 - if [[ "${lang,,}" != "en" ]] && [[ "${lang,,}" != "en-"* ]]; then - MIDO_URL="" - return 1 - fi - - case "${version,,}" in - "win81${PLATFORM,,}-enterprise"* ) - MIDO_URL="https://download.microsoft.com/download/B/9/9/B999286E-0A47-406D-8B3D-5B5AD7373A4A/9600.17050.WINBLUE_REFRESH.140317-1640_X64FRE_ENTERPRISE_EVAL_EN-US-IR3_CENA_X64FREE_EN-US_DV9.ISO" - return 0 - ;; - "win11${PLATFORM,,}-enterprise-iot"* | "win11${PLATFORM,,}-enterprise-ltsc"* ) - MIDO_URL="https://software-static.download.prss.microsoft.com/dbazure/888969d5-f34g-4e03-ac9d-1f9786c66749/26100.1.240331-1435.ge_release_CLIENT_IOT_LTSC_EVAL_x64FRE_en-us.iso" - return 0 - ;; - "win2025-eval" ) - MIDO_URL="https://software-static.download.prss.microsoft.com/dbazure/888969d5-f34g-4e03-ac9d-1f9786c66749/26100.1.240331-1435.ge_release_SERVER_EVAL_x64FRE_en-us.iso" - return 0 - ;; - "win2022-eval" ) - MIDO_URL="https://software-static.download.prss.microsoft.com/sg/download/888969d5-f34g-4e03-ac9d-1f9786c66749/SERVER_EVAL_x64FRE_en-us.iso" - return 0 - ;; - "win2019-eval" ) - MIDO_URL="https://software-download.microsoft.com/download/pr/17763.737.190906-2324.rs5_release_svc_refresh_SERVER_EVAL_x64FRE_en-us_1.iso" - return 0 - ;; - "win2016-eval" ) - MIDO_URL="https://software-download.microsoft.com/download/pr/Windows_Server_2016_Datacenter_EVAL_en-us_14393_refresh.ISO" - return 0 - ;; - "win2012r2-eval" ) - MIDO_URL="https://download.microsoft.com/download/6/2/A/62A76ABB-9990-4EFC-A4FE-C7D698DAEB96/9600.17050.WINBLUE_REFRESH.140317-1640_X64FRE_SERVER_EVAL_EN-US-IR3_SSS_X64FREE_EN-US_DV9.ISO" - return 0 - ;; - "win2008r2" ) - MIDO_URL="https://download.microsoft.com/download/4/1/D/41DEA7E0-B30D-4012-A1E3-F24DC03BA1BB/7601.17514.101119-1850_x64fre_server_eval_en-us-GRMSXEVAL_EN_DVD.iso" - return 0 - ;; - esac - - MIDO_URL="" - return 1 + return 0 } getCatalog() { @@ -429,243 +390,6 @@ getCatalog() { return 0 } -getMG() { - - local version="$1" - local lang="$2" - local desc="$3" - - local locale="" - local culture="" - local language="" - local user_agent="" - - user_agent=$(get_agent) - language=$(getLanguage "$lang" "desc") - culture=$(getLanguage "$lang" "culture") - - local msg="Requesting download link from massgrave.dev..." - info "$msg" && html "$msg" - - local pattern="" - local locale="${culture,,}" - local platform="${PLATFORM,,}" - local url="https://massgrave.dev/" - - if [[ "${PLATFORM,,}" != "arm64" ]]; then - - case "${version,,}" in - "win11${PLATFORM,,}" ) - url+="windows_11_links" - pattern="consumer" - ;; - "win11${PLATFORM,,}-enterprise" | "win11${PLATFORM,,}-enterprise-eval" ) - url+="windows_11_links" - pattern="business" - ;; - "win11${PLATFORM,,}-ltsc" | "win11${PLATFORM,,}-enterprise-ltsc-eval" ) - url+="windows_ltsc_links" - pattern="11_enterprise_ltsc" - ;; - "win11${PLATFORM,,}-iot" | "win11${PLATFORM,,}-enterprise-iot-eval" ) - url+="windows_ltsc_links" - pattern="11_iot" - ;; - "win10${PLATFORM,,}" ) - url+="windows_10_links" - pattern="consumer" - ;; - "win10${PLATFORM,,}-enterprise" | "win10${PLATFORM,,}-enterprise-eval" ) - url+="windows_10_links" - pattern="business" - ;; - "win10${PLATFORM,,}-ltsc" | "win10${PLATFORM,,}-enterprise-ltsc-eval" ) - url+="windows_ltsc_links" - pattern="10_enterprise_ltsc" - ;; - "win10${PLATFORM,,}-iot" | "win10${PLATFORM,,}-enterprise-iot-eval" ) - url+="windows_ltsc_links" - pattern="10_iot" - ;; - "win81${PLATFORM,,}-enterprise" | "win81${PLATFORM,,}-enterprise-eval" ) - url+="windows_8.1_links" - pattern="8.1_enterprise" - locale=$(getLanguage "$lang" "code") - [[ "$locale" == "sr" ]] && locale="sr-latn" - ;; - "win2025" | "win2025-eval" ) - url+="windows_server_links" - pattern="server_2025" - ;; - "win2022" | "win2022-eval" ) - url+="windows_server_links" - pattern="server_2022" - ;; - "win2019" | "win2019-eval" ) - url+="windows_server_links" - pattern="server_2019" - ;; - "win2016" | "win2016-eval" ) - url+="windows_server_links" - pattern="server_2016" - locale=$(getLanguage "$lang" "code") - [[ "$locale" == "hk" ]] && locale="ct" - [[ "$locale" == "tw" ]] && locale="ct" - ;; - "win2012r2" | "win2012r2-eval" ) - url+="windows_server_links" - pattern="server_2012_r2" - locale=$(getLanguage "$lang" "code") - ;; - "win2008r2" | "win2008r2-eval" ) - url+="windows_server_links" - pattern="server_2008_r2" - locale=$(getLanguage "$lang" "code") - ;; - "win7x64" | "win7x64-enterprise" ) - url+="windows_7_links" - pattern="enterprise" - locale=$(getLanguage "$lang" "code") - ;; - "win7x64-ultimate" ) - url+="windows_7_links" - pattern="ultimate" - locale=$(getLanguage "$lang" "code") - ;; - "win7x86" | "win7x86-enterprise" ) - platform="x86" - url+="windows_7_links" - pattern="enterprise" - locale=$(getLanguage "$lang" "code") - ;; - "win7x86-ultimate" ) - platform="x86" - url+="windows_7_links" - pattern="ultimate" - locale=$(getLanguage "$lang" "code") - ;; - "winvistax64" | "winvistax64-enterprise" ) - url+="windows_vista_links" - pattern="enterprise" - locale=$(getLanguage "$lang" "code") - ;; - "winvistax64-ultimate" ) - url+="windows_vista_links" - pattern="sp2" - locale=$(getLanguage "$lang" "code") - ;; - "winvistax86" | "winvistax86-enterprise" ) - platform="x86" - url+="windows_vista_links" - pattern="enterprise" - locale=$(getLanguage "$lang" "code") - ;; - "winvistax86-ultimate" ) - platform="x86" - url+="windows_vista_links" - pattern="sp2" - locale=$(getLanguage "$lang" "code") - ;; - "winxpx86" ) - platform="x86" - url+="windows_xp_links" - pattern="xp" - locale=$(getLanguage "$lang" "code") - [[ "$locale" == "pt" ]] && locale="pt-br" - [[ "$locale" == "pp" ]] && locale="pt-pt" - [[ "$locale" == "cn" ]] && locale="zh-hans" - [[ "$locale" == "hk" ]] && locale="zh-hk" - [[ "$locale" == "tw" ]] && locale="zh-tw" - ;; - "winxpx64" ) - url+="windows_xp_links" - pattern="xp" - locale=$(getLanguage "$lang" "code") - ;; - esac - - else - - case "${version,,}" in - "win11${PLATFORM,,}" | "win11${PLATFORM,,}-enterprise" | "win11${PLATFORM,,}-enterprise-eval" ) - url+="windows_arm_links" - pattern="11_business" - ;; - "win11${PLATFORM,,}-ltsc" | "win11${PLATFORM,,}-enterprise-ltsc-eval" ) - url+="windows_arm_links" - pattern="11_iot_enterprise_ltsc" - ;; - "win10${PLATFORM,,}" | "win10${PLATFORM,,}-enterprise" | "win10${PLATFORM,,}-enterprise-eval" ) - url+="windows_arm_links" - pattern="Pro_10" - locale="$language" - [[ "$locale" == "Chinese" ]] && locale="ChnSimp" - [[ "$locale" == "Chinese HK" ]] && locale="ChnTrad" - [[ "$locale" == "Chinese TW" ]] && locale="ChnTrad" - ;; - "win10${PLATFORM,,}-ltsc" | "win10${PLATFORM,,}-enterprise-ltsc-eval" ) - url+="windows_arm_links" - pattern="10_iot_enterprise_ltsc" - ;; - esac - - fi - - local body="" - - [[ "$DEBUG" == [Yy1]* ]] && echo "Parsing product page: ${url}" - body=$(curl --silent --max-time 30 --user-agent "$user_agent" --location --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url") || { - handle_curl_error "$?" "Massgrave" - return $? - } - - local list="" - list=$(echo "$body" | xmllint --html --nonet --xpath "//a[contains(text(), '.iso')]" - 2>/dev/null) - - local result="" - result=$(echo "$list" | grep -i "${platform}" | grep "${pattern}" | grep -i -m 1 "${locale,,}_") - result=$(echo "$result" | sed -r 's/.*href="([^"]+).*/\1/g') - local page="$result" - - if [ -z "$page" ]; then - if [[ "${lang,,}" != "en" ]] && [[ "${lang,,}" != "en-"* ]]; then - error "No download in the $language language available for $desc!" - else - error "Failed to parse download link for $desc! Please report this at $SUPPORT/issues." - fi - return 1 - fi - - [[ "$DEBUG" == [Yy1]* ]] && echo "Parsing download page: ${page}" - result=$(curl --silent --max-time 30 --request GET --user-agent "$user_agent" --referer "$url" --head --proto =https --tlsv1.2 --http1.1 -- "$page") || { - handle_curl_error "$?" "Massgrave" - return $? - } - - if [[ "${result,,}" == *"content-type: text"* ]]; then - body=$(curl --silent --max-time 30 --user-agent "$user_agent" --referer "$url" --location --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$page") || { - handle_curl_error "$?" "Massgrave" - return $? - } - - list=$(echo "$body" | xmllint --html --nonet --xpath "//a[contains(@href, '.iso')]" - 2>/dev/null) - list=$(echo "$list" | sed -r 's/.*href="([^"]+).*/\1/g') - page=$(echo "$list" | sed 's/&amp;/\&/g;') - - if [ -z "$page" ]; then - if [[ "${lang,,}" != "en" ]] && [[ "${lang,,}" != "en-"* ]]; then - error "No download in the $language language available for $desc!" - else - error "Failed to parse download link for $desc! Please report this at $SUPPORT/issues." - fi - return 1 - fi - fi - - MG_URL="$page" - return 0 -} - getESD() { local dir="$1" @@ -748,6 +472,18 @@ getESD() { return 0 } +isCompressed() { + + local file="$1" + + case "${file,,}" in + *".7z" | *".zip" | *".rar" | *".lzma" | *".bz" | *".bz2" ) + return 0 ;; + esac + + return 1 +} + verifyFile() { local iso="$1" @@ -756,7 +492,9 @@ verifyFile() { local check="$4" if [ -n "$size" ] && [[ "$total" != "$size" ]] && [[ "$size" != "0" ]]; then - warn "The downloaded file has an unexpected size: $total bytes, while expected value was: $size bytes. Please report this at $SUPPORT/issues" + if [[ "$VERIFY" == [Yy1]* ]] || [[ "$DEBUG" == [Yy1]* ]]; then + warn "The downloaded file has a different size ( $total bytes) than expected ( $size bytes). Please report this at $SUPPORT/issues" + fi fi local hash="" @@ -779,7 +517,7 @@ verifyFile() { info "Succesfully verified ISO!" && return 0 fi - error "The downloaded file has an invalid $algo checksum: $hash , while expected value was: $check. Please report this at $SUPPORT/issues" + error "The downloaded file has an unknown $algo checksum: $hash , as the expected value was: $check. Please report this at $SUPPORT/issues" return 1 } @@ -791,14 +529,16 @@ downloadFile() { local size="$4" local lang="$5" local desc="$6" - local rc total progress domain dots space folder + local msg="Downloading $desc" + local rc total total_gb progress domain dots space folder rm -f "$iso" if [ -n "$size" ] && [[ "$size" != "0" ]]; then folder=$(dirname -- "$iso") space=$(df --output=avail -B 1 "$folder" | tail -n 1) - (( size > space )) && error "Not enough free space left to download file!" && return 1 + total_gb=$(formatBytes "$space") + (( size > space )) && error "Not enough free space to download file, only $total_gb left!" && return 1 fi # Check if running with interactive TTY or redirected to docker log @@ -808,8 +548,8 @@ downloadFile() { progress="--progress=dot:giga" fi - local msg="Downloading $desc" html "$msg..." + /run/progress.sh "$iso" "$size" "$msg ([P])..." & domain=$(echo "$url" | awk -F/ '{print $3}') dots=$(echo "$domain" | tr -cd '.' | wc -c) @@ -820,7 +560,6 @@ downloadFile() { fi info "$msg..." - /run/progress.sh "$iso" "$size" "$msg ([P])..." & { wget "$url" -O "$iso" -q --timeout=30 --no-http-keep-alive --show-progress "$progress"; rc=$?; } || : @@ -828,17 +567,19 @@ downloadFile() { if (( rc == 0 )) && [ -f "$iso" ]; then total=$(stat -c%s "$iso") + total_gb=$(formatBytes "$total") if [ "$total" -lt 100000000 ]; then - error "Invalid download link: $url (is only $total bytes?). Please report this at $SUPPORT/issues." && return 1 + error "Invalid download link: $url (is only $total_gb ?). Please report this at $SUPPORT/issues." && return 1 fi verifyFile "$iso" "$size" "$total" "$sum" || return 1 + isCompressed "$url" && UNPACK="Y" html "Download finished successfully..." && return 0 fi msg="Failed to download $url" (( rc == 3 )) && error "$msg , cannot write file (disk full?)" && return 1 (( rc == 4 )) && error "$msg , network failure!" && return 1 - (( rc == 8 )) && error "$msg , server issued an error response!" && return 1 + (( rc == 8 )) && error "$msg , server issued an error response! Please report this at $SUPPORT/issues." && return 1 error "$msg , reason: $rc" return 1 @@ -856,12 +597,14 @@ downloadImage() { local msg="Will retry after $delay seconds..." if [[ "${version,,}" == "http"* ]]; then + base=$(basename "$iso") desc=$(fromFile "$base") downloadFile "$iso" "$version" "" "" "" "$desc" && return 0 info "$msg" && html "$msg" && sleep "$delay" downloadFile "$iso" "$version" "" "" "" "$desc" && return 0 rm -f "$iso" + return 1 fi @@ -950,31 +693,6 @@ downloadImage() { done - if isMG "$version" "$lang"; then - - if [[ "$tried" != "n" ]]; then - info "Failed to download $desc, will try a diferent method now..." - fi - - tried="y" - success="n" - - if getMG "$version" "$lang" "$desc"; then - success="y" - else - info "$msg" && html "$msg" && sleep "$delay" - getMG "$version" "$lang" "$desc" && success="y" - fi - - if [[ "$success" == "y" ]]; then - downloadFile "$iso" "$MG_URL" "" "" "$lang" "$desc" && return 0 - info "$msg" && html "$msg" && sleep "$delay" - downloadFile "$iso" "$MG_URL" "" "" "$lang" "$desc" && return 0 - rm -f "$iso" - fi - - fi - return 1 } diff --git a/src/network.sh b/src/network.sh index e9b82c9..6112244 100755 --- a/src/network.sh +++ b/src/network.sh @@ -85,7 +85,7 @@ configure_guest_network_interface() { fi RETRIES=10 - for i in $(seq 1 $RETRIES); do + for j in $(seq 1 $RETRIES); do OUTPUT=$(python3 /run/qga.py powershell -Command "(\$(Get-NetAdapter -Name '$INTERFACE_NAME').Status)") STATUS=$(echo "$OUTPUT" | grep -A1 'STDOUT:' | tail -n1 | tr -d '\r' | xargs) @@ -94,8 +94,8 @@ configure_guest_network_interface() { echo "Interface '$INTERFACE_NAME' is up!" break else - echo "Waiting for interface '$INTERFACE_NAME' to be up... ($i/$RETRIES)" - sleep $SLEEP + echo "Waiting for interface '$INTERFACE_NAME' to be up... ($j/$RETRIES)" + sleep 1 fi done diff --git a/src/samba.sh b/src/samba.sh index 321d05d..4ef366c 100644 --- a/src/samba.sh +++ b/src/samba.sh @@ -14,6 +14,10 @@ if [[ "$DHCP" == [Yy1]* ]]; then interface="$VM_NET_DEV" fi +if [[ "${NETWORK,,}" == "user"* ]]; then + interface="127.0.0.1" +fi + addShare() { local dir="$1" local name="$2" @@ -99,6 +103,11 @@ for dir in "${dirs[@]}"; do addShare "$dir" "$dir_name" "Shared $dir_name" || error "Failed to create shared folder for $dir!" done +# Fix Samba permissions +[ -d /run/samba/msg.lock ] && chmod -R 0755 /run/samba/msg.lock +[ -d /var/log/samba/cores ] && chmod -R 0700 /var/log/samba/cores +[ -d /var/cache/samba/msg.lock ] && chmod -R 0755 /var/cache/samba/msg.lock + if ! smbd; then error "Samba daemon failed to start!" smbd -i --debug-stdout || true From 1e46dde82ba49f40cf4e9971550ea71ed5c7a47d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gytis=20Sto=C5=A1kevi=C4=8Dius?= <gytis.stoskevicius@nordsec.com> Date: Fri, 11 Apr 2025 10:04:20 +0000 Subject: [PATCH 17/17] improve qga.py --- assets/win11x64-enterprise-eval.xml | 3 + assets/win11x64-enterprise.xml | 3 + assets/win11x64-iot.xml | 3 + assets/win11x64-ltsc.xml | 3 + assets/win11x64.xml | 3 + src/boot.sh | 161 ++++++++++++++++++++++++++++ src/define.sh | 2 +- src/qga.py | 86 ++++++++++----- 8 files changed, 235 insertions(+), 29 deletions(-) create mode 100755 src/boot.sh diff --git a/assets/win11x64-enterprise-eval.xml b/assets/win11x64-enterprise-eval.xml index 7822f82..c41d244 100644 --- a/assets/win11x64-enterprise-eval.xml +++ b/assets/win11x64-enterprise-eval.xml @@ -9,6 +9,7 @@ <SystemLocale>en-US</SystemLocale> <UILanguage>en-US</UILanguage> <UserLocale>en-US</UserLocale> + <TimeZone>UTC</TimeZone> </component> <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <DiskConfiguration> @@ -137,6 +138,7 @@ <SupportURL>https://github.com/dockur/windows/issues</SupportURL> </OEMInformation> <OEMName>Windows for Docker</OEMName> + <TimeZone>UTC</TimeZone> </component> <component name="Microsoft-Windows-ErrorReportingCore" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <DisableWER>1</DisableWER> @@ -301,6 +303,7 @@ <TCGSecurityActivationDisabled>1</TCGSecurityActivationDisabled> </component> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <TimeZone>UTC</TimeZone> <UserAccounts> <LocalAccounts> <LocalAccount wcm:action="add"> diff --git a/assets/win11x64-enterprise.xml b/assets/win11x64-enterprise.xml index 6620eb7..c5153a4 100644 --- a/assets/win11x64-enterprise.xml +++ b/assets/win11x64-enterprise.xml @@ -9,6 +9,7 @@ <SystemLocale>en-US</SystemLocale> <UILanguage>en-US</UILanguage> <UserLocale>en-US</UserLocale> + <TimeZone>UTC</TimeZone> </component> <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <DiskConfiguration> @@ -140,6 +141,7 @@ <SupportURL>https://github.com/dockur/windows/issues</SupportURL> </OEMInformation> <OEMName>Windows for Docker</OEMName> + <TimeZone>UTC</TimeZone> </component> <component name="Microsoft-Windows-ErrorReportingCore" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <DisableWER>1</DisableWER> @@ -304,6 +306,7 @@ <TCGSecurityActivationDisabled>1</TCGSecurityActivationDisabled> </component> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <TimeZone>UTC</TimeZone> <UserAccounts> <LocalAccounts> <LocalAccount wcm:action="add"> diff --git a/assets/win11x64-iot.xml b/assets/win11x64-iot.xml index 4707774..03eabb1 100644 --- a/assets/win11x64-iot.xml +++ b/assets/win11x64-iot.xml @@ -9,6 +9,7 @@ <SystemLocale>en-US</SystemLocale> <UILanguage>en-US</UILanguage> <UserLocale>en-US</UserLocale> + <TimeZone>UTC</TimeZone> </component> <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <DiskConfiguration> @@ -140,6 +141,7 @@ <SupportURL>https://github.com/dockur/windows/issues</SupportURL> </OEMInformation> <OEMName>Windows for Docker</OEMName> + <TimeZone>UTC</TimeZone> </component> <component name="Microsoft-Windows-ErrorReportingCore" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <DisableWER>1</DisableWER> @@ -304,6 +306,7 @@ <TCGSecurityActivationDisabled>1</TCGSecurityActivationDisabled> </component> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <TimeZone>UTC</TimeZone> <UserAccounts> <LocalAccounts> <LocalAccount wcm:action="add"> diff --git a/assets/win11x64-ltsc.xml b/assets/win11x64-ltsc.xml index 4359b1c..eb63d15 100644 --- a/assets/win11x64-ltsc.xml +++ b/assets/win11x64-ltsc.xml @@ -9,6 +9,7 @@ <SystemLocale>en-US</SystemLocale> <UILanguage>en-US</UILanguage> <UserLocale>en-US</UserLocale> + <TimeZone>UTC</TimeZone> </component> <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <DiskConfiguration> @@ -140,6 +141,7 @@ <SupportURL>https://github.com/dockur/windows/issues</SupportURL> </OEMInformation> <OEMName>Windows for Docker</OEMName> + <TimeZone>UTC</TimeZone> </component> <component name="Microsoft-Windows-ErrorReportingCore" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <DisableWER>1</DisableWER> @@ -304,6 +306,7 @@ <TCGSecurityActivationDisabled>1</TCGSecurityActivationDisabled> </component> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <TimeZone>UTC</TimeZone> <UserAccounts> <LocalAccounts> <LocalAccount wcm:action="add"> diff --git a/assets/win11x64.xml b/assets/win11x64.xml index bec96d4..b8f58aa 100644 --- a/assets/win11x64.xml +++ b/assets/win11x64.xml @@ -9,6 +9,7 @@ <SystemLocale>en-US</SystemLocale> <UILanguage>en-US</UILanguage> <UserLocale>en-US</UserLocale> + <TimeZone>UTC</TimeZone> </component> <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <DiskConfiguration> @@ -140,6 +141,7 @@ <SupportURL>https://github.com/dockur/windows/issues</SupportURL> </OEMInformation> <OEMName>Windows for Docker</OEMName> + <TimeZone>UTC</TimeZone> </component> <component name="Microsoft-Windows-ErrorReportingCore" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <DisableWER>1</DisableWER> @@ -304,6 +306,7 @@ <TCGSecurityActivationDisabled>1</TCGSecurityActivationDisabled> </component> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <TimeZone>UTC</TimeZone> <UserAccounts> <LocalAccounts> <LocalAccount wcm:action="add"> diff --git a/src/boot.sh b/src/boot.sh new file mode 100755 index 0000000..63e9dec --- /dev/null +++ b/src/boot.sh @@ -0,0 +1,161 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +# Docker environment variables +: "${BIOS:=""}" # BIOS file +: "${TPM:="N"}" # Disable TPM +: "${SMM:="N"}" # Disable SMM + +BOOT_DESC="" +BOOT_OPTS="" + +SECURE="off" +[[ "$SMM" == [Yy1]* ]] && SECURE="on" +[ -n "$BIOS" ] && BOOT_MODE="custom" + +case "${BOOT_MODE,,}" in + "uefi" | "" ) + BOOT_MODE="uefi" + ROM="OVMF_CODE_4M.fd" + VARS="OVMF_VARS_4M.fd" + ;; + "secure" ) + SECURE="on" + BOOT_DESC=" securely" + ROM="OVMF_CODE_4M.secboot.fd" + VARS="OVMF_VARS_4M.secboot.fd" + ;; + "windows" | "windows_plain" ) + ROM="OVMF_CODE_4M.fd" + VARS="OVMF_VARS_4M.fd" + ;; + "windows_secure" ) + TPM="Y" + SECURE="on" + BOOT_DESC=" securely" + ROM="OVMF_CODE_4M.ms.fd" + VARS="OVMF_VARS_4M.ms.fd" + ;; + "windows_legacy" ) + HV="N" + SECURE="on" + BOOT_DESC=" (legacy)" + [ -z "${USB:-}" ] && USB="usb-ehci,id=ehci" + ;; + "legacy" ) + BOOT_DESC=" with SeaBIOS" + ;; + "custom" ) + BOOT_OPTS="-bios $BIOS" + BOOT_DESC=" with custom BIOS file" + ;; + *) + error "Unknown BOOT_MODE, value \"${BOOT_MODE}\" is not recognized!" + exit 33 + ;; +esac + +if [[ "${BOOT_MODE,,}" == "windows"* ]]; then + BOOT_OPTS+=" -rtc base=utc" + BOOT_OPTS+=" -global ICH9-LPC.disable_s3=1" + BOOT_OPTS+=" -global ICH9-LPC.disable_s4=1" +fi + +case "${BOOT_MODE,,}" in + "uefi" | "secure" | "windows" | "windows_plain" | "windows_secure" ) + + OVMF="/usr/share/OVMF" + DEST="$STORAGE/${BOOT_MODE,,}" + + if [ ! -s "$DEST.rom" ] || [ ! -f "$DEST.rom" ]; then + [ ! -s "$OVMF/$ROM" ] || [ ! -f "$OVMF/$ROM" ] && error "UEFI boot file ($OVMF/$ROM) not found!" && exit 44 + cp "$OVMF/$ROM" "$DEST.rom" + fi + + if [ ! -s "$DEST.vars" ] || [ ! -f "$DEST.vars" ]; then + [ ! -s "$OVMF/$VARS" ] || [ ! -f "$OVMF/$VARS" ]&& error "UEFI vars file ($OVMF/$VARS) not found!" && exit 45 + cp "$OVMF/$VARS" "$DEST.vars" + fi + + if [[ "${BOOT_MODE,,}" == "secure" ]] || [[ "${BOOT_MODE,,}" == "windows_secure" ]]; then + BOOT_OPTS+=" -global driver=cfi.pflash01,property=secure,value=on" + fi + + BOOT_OPTS+=" -drive file=$DEST.rom,if=pflash,unit=0,format=raw,readonly=on" + BOOT_OPTS+=" -drive file=$DEST.vars,if=pflash,unit=1,format=raw" + + ;; +esac + +MSRS="/sys/module/kvm/parameters/ignore_msrs" +if [ -e "$MSRS" ]; then + result=$(<"$MSRS") + result="${result//[![:print:]]/}" + if [[ "$result" == "0" ]] || [[ "${result^^}" == "N" ]]; then + echo 1 | tee "$MSRS" > /dev/null 2>&1 || true + fi +fi + +CLOCKSOURCE="tsc" +[[ "${ARCH,,}" == "arm64" ]] && CLOCKSOURCE="arch_sys_counter" +CLOCK="/sys/devices/system/clocksource/clocksource0/current_clocksource" + +if [ ! -f "$CLOCK" ]; then + warn "file \"$CLOCK\" cannot not found?" +else + result=$(<"$CLOCK") + result="${result//[![:print:]]/}" + case "${result,,}" in + "${CLOCKSOURCE,,}" ) ;; + "kvm-clock" ) info "Nested KVM virtualization detected.." ;; + "hyperv_clocksource_tsc_page" ) info "Nested Hyper-V virtualization detected.." ;; + "hpet" ) warn "unsupported clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;; + *) warn "unexpected clock source detected: '$result'. Please set host clock source to '$CLOCKSOURCE'." ;; + esac +fi + +SM_BIOS="" +PS="/sys/class/dmi/id/product_serial" + +if [ -s "$PS" ] && [ -r "$PS" ]; then + + BIOS_SERIAL=$(<"$PS") + BIOS_SERIAL="${BIOS_SERIAL//[![:alnum:]]/}" + + if [ -n "$BIOS_SERIAL" ]; then + SM_BIOS="-smbios type=1,serial=$BIOS_SERIAL" + fi + +fi + +if [[ "$TPM" == [Yy1]* ]]; then + + rm -f /var/run/tpm.pid + + if ! swtpm socket -t -d --tpmstate "backend-uri=file://$STORAGE/${BOOT_MODE,,}.tpm" --ctrl type=unixio,path=/run/swtpm-sock --pid file=/var/run/tpm.pid --tpm2; then + error "Failed to start TPM emulator, reason: $?" + else + + for (( i = 1; i < 20; i++ )); do + + [ -S "/run/swtpm-sock" ] && break + + if (( i % 10 == 0 )); then + echo "Waiting for TPM emulator to become available..." + fi + + sleep 0.1 + + done + + if [ ! -S "/run/swtpm-sock" ]; then + error "TPM socket not found? Disabling TPM module..." + else + BOOT_OPTS+=" -chardev socket,id=chrtpm,path=/run/swtpm-sock" + BOOT_OPTS+=" -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0" + fi + + fi +fi + +return 0 diff --git a/src/define.sh b/src/define.sh index 69896b1..d10fe5e 100644 --- a/src/define.sh +++ b/src/define.sh @@ -1412,7 +1412,7 @@ prepareInstall() { echo " OEMSkipRegional=1" echo " OemSkipWelcome=1" echo " AdminPassword=$password" - echo " TimeZone=0" + echo " TimeZone=85" echo " AutoLogon=Yes" echo " AutoLogonCount=65432" echo "" diff --git a/src/qga.py b/src/qga.py index c57f017..64b818e 100644 --- a/src/qga.py +++ b/src/qga.py @@ -29,58 +29,61 @@ def decode_output(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): +def execute_command(sock, command_path, command_args, timeout): """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 + "capture-output": True, }, } + + print(f"Executing: {command_path} {' '.join(command_args)}") 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) + if response is None or "return" not in response or "pid" not in response["return"]: + print( + "Error: Failed to start execution.", + json.dumps(response or {}, indent=2), + file=sys.stderr, + ) return None pid = response["return"]["pid"] print(f"Command started with PID {pid}") - # Step 2: Wait for completion + # Step 2: Wait for completion with timeout + start_time = time.time() + status = {} while True: + if time.time() - start_time > timeout: + print("Execution timeout reached.", file=sys.stderr) + return {"exit_code": -2, "stdout": "", "stderr": "Execution timed out."} + 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: + if status_response and "return" in status_response: status = status_response["return"] if status.get("exited", False): - break # Command finished - time.sleep(0.2) # Wait before checking again + break + + time.sleep(0.2) - # 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", "")) @@ -91,7 +94,7 @@ def execute_command(sock, command_path, command_args): 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 + sock.settimeout(30) try: sock.connect(QGA_SOCKET) return sock @@ -103,6 +106,21 @@ def create_socket(): def parse_args(): """Parse command-line arguments.""" parser = argparse.ArgumentParser(description="Send commands to QEMU Guest Agent.") + shell_group = parser.add_mutually_exclusive_group() + shell_group.add_argument( + "--cmd", action="store_true", help="Run the command through cmd.exe /c" + ) + shell_group.add_argument( + "--powershell", + action="store_true", + help="Run the command with powershell -Command", + ) + parser.add_argument( + "--timeout", type=int, default=60, help="Max execution time in seconds" + ) + parser.add_argument( + "--json", action="store_true", help="Output result in JSON format" + ) parser.add_argument( "command", help="Path to the command to execute inside the guest VM" ) @@ -113,23 +131,35 @@ def parse_args(): if __name__ == "__main__": - # Parse command-line arguments args = parse_args() - # Create a reusable socket + if args.cmd: + command_path = "cmd.exe" + command_args = ["/c", args.command] + args.args + elif args.powershell: + command_path = "powershell.exe" + full_command = " ".join([args.command] + args.args) + command_args = ["-Command", full_command] + else: + command_path = args.command + command_args = args.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) + result = execute_command(unix_sock, command_path, command_args, args.timeout) 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"]) + if args.json: + print(json.dumps(result, indent=2)) + else: + 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()