From b0b3c01b9efc736592b3993a098500dbb6df6e82 Mon Sep 17 00:00:00 2001 From: Rohan Barar Date: Thu, 18 Jul 2024 07:19:18 +1000 Subject: [PATCH 1/8] Added warnings when changing RDP_SCALE automatically to prevent user confusion. --- bin/winapps | 40 +++++++++++++++++++++++----------------- installer.sh | 41 ++++++++++++++++++++++++----------------- 2 files changed, 47 insertions(+), 34 deletions(-) diff --git a/bin/winapps b/bin/winapps index 881d94f..a1399f1 100755 --- a/bin/winapps +++ b/bin/winapps @@ -116,28 +116,34 @@ function dprint() { # Role: Since FreeRDP only supports '/scale' values of 100, 140 or 180, find the closest supported argument to the user's configuration. function waFixScale() { # Define variables. - local USER_CONFIG_SCALE="$1" - local CLOSEST_SCALE=100 + local OLD_SCALE=100 local VALID_SCALE_1=100 local VALID_SCALE_2=140 local VALID_SCALE_3=180 - # Calculate the absolute differences. - local DIFF_1=$(( USER_CONFIG_SCALE > VALID_SCALE_1 ? USER_CONFIG_SCALE - VALID_SCALE_1 : VALID_SCALE_1 - USER_CONFIG_SCALE )) - local DIFF_2=$(( USER_CONFIG_SCALE > VALID_SCALE_2 ? USER_CONFIG_SCALE - VALID_SCALE_2 : VALID_SCALE_2 - USER_CONFIG_SCALE )) - local DIFF_3=$(( USER_CONFIG_SCALE > VALID_SCALE_3 ? USER_CONFIG_SCALE - VALID_SCALE_3 : VALID_SCALE_3 - USER_CONFIG_SCALE )) + # Check for an unsupported value. + if [ "$RDP_SCALE" != "$VALID_SCALE_1" ] && [ "$RDP_SCALE" != "$VALID_SCALE_2" ] && [ "$RDP_SCALE" != "$VALID_SCALE_3" ]; then + # Save the unsupported scale. + OLD_SCALE="$RDP_SCALE" - # Set the final scale to the valid scale value with the smallest absolute difference. - if (( DIFF_1 <= DIFF_2 && DIFF_1 <= DIFF_3 )); then - CLOSEST_SCALE="$VALID_SCALE_1" - elif (( DIFF_2 <= DIFF_1 && DIFF_2 <= DIFF_3 )); then - CLOSEST_SCALE="$VALID_SCALE_2" - else - CLOSEST_SCALE="$VALID_SCALE_3" + # Calculate the absolute differences. + local DIFF_1=$(( RDP_SCALE > VALID_SCALE_1 ? RDP_SCALE - VALID_SCALE_1 : VALID_SCALE_1 - RDP_SCALE )) + local DIFF_2=$(( RDP_SCALE > VALID_SCALE_2 ? RDP_SCALE - VALID_SCALE_2 : VALID_SCALE_2 - RDP_SCALE )) + local DIFF_3=$(( RDP_SCALE > VALID_SCALE_3 ? RDP_SCALE - VALID_SCALE_3 : VALID_SCALE_3 - RDP_SCALE )) + + # Set the final scale to the valid scale value with the smallest absolute difference. + if (( DIFF_1 <= DIFF_2 && DIFF_1 <= DIFF_3 )); then + RDP_SCALE="$VALID_SCALE_1" + elif (( DIFF_2 <= DIFF_1 && DIFF_2 <= DIFF_3 )); then + RDP_SCALE="$VALID_SCALE_2" + else + RDP_SCALE="$VALID_SCALE_3" + fi + + # Print feedback. + dprint "WARNING: Unsupported RDP_SCALE value '${OLD_SCALE}'. Defaulting to '${RDP_SCALE}'." + echo "WARNING: Unsupported RDP_SCALE value '${OLD_SCALE}' detected. Defaulting to '${RDP_SCALE}'." fi - - # Return the final scale value. - echo "$CLOSEST_SCALE" } # Name: 'waLoadConfig' @@ -155,7 +161,7 @@ function waLoadConfig() { MULTI_FLAG=$([[ $MULTIMON == "true" ]] && echo "/multimon" || echo "+span") # Update $RDP_SCALE. - RDP_SCALE=$(waFixScale "$RDP_SCALE") + waFixScale # Append additional flags or parameters to FreeRDP. [[ -n $RDP_FLAGS ]] && FREERDP_COMMAND="${FREERDP_COMMAND} ${RDP_FLAGS}" diff --git a/installer.sh b/installer.sh index 5d5a623..1f39a6b 100755 --- a/installer.sh +++ b/installer.sh @@ -12,6 +12,7 @@ readonly EXIT_TEXT="\033[1;41;37m" # Bold + White + Red Background readonly FAIL_TEXT="\033[0;91m" # Bright Red readonly INFO_TEXT="\033[0;33m" # Orange/Yellow readonly SUCCESS_TEXT="\033[1;42;37m" # Bold + White + Green Background +readonly WARNING_TEXT="\033[1;33m" # Bold + Orange/Yellow # ERROR CODES readonly EC_FAILED_CD="1" # Failed to change directory to location of script. @@ -392,28 +393,33 @@ function waCheckExistingInstall() { # Role: Since FreeRDP only supports '/scale' values of 100, 140 or 180, find the closest supported argument to the user's configuration. function waFixScale() { # Define variables. - local USER_CONFIG_SCALE="$1" - local CLOSEST_SCALE=100 + local OLD_SCALE=100 local VALID_SCALE_1=100 local VALID_SCALE_2=140 local VALID_SCALE_3=180 - # Calculate the absolute differences. - local DIFF_1=$(( USER_CONFIG_SCALE > VALID_SCALE_1 ? USER_CONFIG_SCALE - VALID_SCALE_1 : VALID_SCALE_1 - USER_CONFIG_SCALE )) - local DIFF_2=$(( USER_CONFIG_SCALE > VALID_SCALE_2 ? USER_CONFIG_SCALE - VALID_SCALE_2 : VALID_SCALE_2 - USER_CONFIG_SCALE )) - local DIFF_3=$(( USER_CONFIG_SCALE > VALID_SCALE_3 ? USER_CONFIG_SCALE - VALID_SCALE_3 : VALID_SCALE_3 - USER_CONFIG_SCALE )) + # Check for an unsupported value. + if [ "$RDP_SCALE" != "$VALID_SCALE_1" ] && [ "$RDP_SCALE" != "$VALID_SCALE_2" ] && [ "$RDP_SCALE" != "$VALID_SCALE_3" ]; then + # Save the unsupported scale. + OLD_SCALE="$RDP_SCALE" - # Set the final scale to the valid scale value with the smallest absolute difference. - if (( DIFF_1 <= DIFF_2 && DIFF_1 <= DIFF_3 )); then - CLOSEST_SCALE="$VALID_SCALE_1" - elif (( DIFF_2 <= DIFF_1 && DIFF_2 <= DIFF_3 )); then - CLOSEST_SCALE="$VALID_SCALE_2" - else - CLOSEST_SCALE="$VALID_SCALE_3" + # Calculate the absolute differences. + local DIFF_1=$(( RDP_SCALE > VALID_SCALE_1 ? RDP_SCALE - VALID_SCALE_1 : VALID_SCALE_1 - RDP_SCALE )) + local DIFF_2=$(( RDP_SCALE > VALID_SCALE_2 ? RDP_SCALE - VALID_SCALE_2 : VALID_SCALE_2 - RDP_SCALE )) + local DIFF_3=$(( RDP_SCALE > VALID_SCALE_3 ? RDP_SCALE - VALID_SCALE_3 : VALID_SCALE_3 - RDP_SCALE )) + + # Set the final scale to the valid scale value with the smallest absolute difference. + if (( DIFF_1 <= DIFF_2 && DIFF_1 <= DIFF_3 )); then + RDP_SCALE="$VALID_SCALE_1" + elif (( DIFF_2 <= DIFF_1 && DIFF_2 <= DIFF_3 )); then + RDP_SCALE="$VALID_SCALE_2" + else + RDP_SCALE="$VALID_SCALE_3" + fi + + # Print feedback. + echo -e "${WARNING_TEXT}[WARNING]${CLEAR_TEXT} Unsupported RDP_SCALE value '${OLD_SCALE}' detected. Defaulting to '${RDP_SCALE}'." fi - - # Return the final scale value. - echo "$CLOSEST_SCALE" } # Name: 'waLoadConfig' @@ -892,6 +898,7 @@ function waCheckRDPAccess() { echo " - If using 'libvirt', ensure the Windows VM is correctly named as specified within the README." echo " - If using 'libvirt', ensure 'Remote Desktop' is enabled within the Windows VM." echo " - If using 'libvirt', ensure you have merged 'RDPApps.reg' into the Windows VM's registry." + echo " - If using 'libvirt', try logging into and back out of the Windows VM within 'virt-manager' prior to initiating the WinApps installation." echo "--------------------------------------------------------------------------------" # Terminate the script. @@ -1361,7 +1368,7 @@ function waInstall() { fi # Update $RDP_SCALE. - RDP_SCALE=$(waFixScale "$RDP_SCALE") + waFixScale # Append additional FreeRDP flags if required. if [[ -n $RDP_FLAGS ]]; then From 9d3191cbf1177009b47c183c564cf3df877824d4 Mon Sep 17 00:00:00 2001 From: Rohan Barar Date: Thu, 18 Jul 2024 18:27:07 +1000 Subject: [PATCH 2/8] Various changes to streamline Docker experience. --- bin/winapps | 55 ++++++++++----------- compose.yaml | 45 ++++++++++++------ docs/docker.md | 5 +- installer.sh | 127 +++++++++++++++++++++++++------------------------ 4 files changed, 123 insertions(+), 109 deletions(-) diff --git a/bin/winapps b/bin/winapps index a1399f1..90a9c5e 100755 --- a/bin/winapps +++ b/bin/winapps @@ -9,9 +9,9 @@ readonly CLEAR_TEXT="\033[0m" # Clear readonly EC_MISSING_CONFIG=1 readonly EC_MISSING_FREERDP=2 readonly EC_NOT_IN_GROUP=3 -readonly EC_VM_NOT_RUNNING=4 -readonly EC_VM_NO_IP=5 -readonly EC_VM_BAD_PORT=6 +readonly EC_NOT_RUNNING=4 +readonly EC_NO_IP=5 +readonly EC_BAD_PORT=6 readonly EC_UNSUPPORTED_APP=7 readonly EC_INVALID_FLAVOR=8 @@ -25,7 +25,7 @@ readonly CONFIG_PATH="${HOME}/.config/winapps/winapps.conf" readonly SCRIPT_DIR_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" # OTHER -readonly VM_NAME="RDPWindows" +readonly VM_NAME="RDPWindows" # FOR 'libvirt' ONLY readonly RDP_PORT=3389 readonly DOCKER_IP="127.0.0.1" # shellcheck disable=SC2155 # Silence warnings regarding masking return values through simultaneous declaration and assignment. @@ -71,20 +71,20 @@ function waThrowExit() { echo " sudo usermod -a -G libvirt $(whoami)" echo " sudo usermod -a -G kvm $(whoami)" ;; - "$EC_VM_NOT_RUNNING") - dprint "ERROR: VM NOT RUNNING. EXITING." - echo -e "${ERROR_TEXT}ERROR: VM NOT RUNNING.${CLEAR_TEXT}" - echo "Please ensure the Windows container/virtual machine is running." + "$EC_NOT_RUNNING") + dprint "ERROR: WINDOWS NOT RUNNING. EXITING." + echo -e "${ERROR_TEXT}ERROR: WINDOWS NOT RUNNING.${CLEAR_TEXT}" + echo "Please ensure Windows is running." ;; - "$EC_VM_NO_IP") - dprint "ERROR: VM UNREACHABLE. EXITING." - echo -e "${ERROR_TEXT}ERROR: VM UNREACHABLE.${CLEAR_TEXT}" - echo "Please ensure the Windows virtual machine is assigned an IP address." + "$EC_NO_IP") + dprint "ERROR: WINDOWS UNREACHABLE. EXITING." + echo -e "${ERROR_TEXT}ERROR: WINDOWS UNREACHABLE.${CLEAR_TEXT}" + echo "Please ensure Windows is assigned an IP address." ;; - "$EC_VM_BAD_PORT") + "$EC_BAD_PORT") dprint "ERROR: RDP PORT CLOSED. EXITING." echo -e "${ERROR_TEXT}ERROR: RDP PORT CLOSED.${CLEAR_TEXT}" - echo "Please ensure Remote Desktop is correctly configured on the Windows virtual machine." + echo "Please ensure Remote Desktop is correctly configured on Windows." ;; "$EC_UNSUPPORTED_APP") dprint "ERROR: APPLICATION NOT FOUND. EXITING." @@ -244,9 +244,9 @@ function waCheckGroupMembership() { } # Name: 'waCheckVMRunning' -# Role: Throw an error if the Windows VM is not running. +# Role: Throw an error if the Windows 'libvirt' VM is not running. function waCheckVMRunning() { - ! virsh list --state-running --name | grep -q "^${VM_NAME}$" && waThrowExit "$EC_VM_NOT_RUNNING" + ! virsh list --state-running --name | grep -q "^${VM_NAME}$" && waThrowExit "$EC_NOT_RUNNING" } # Name: 'waCheckContainerRunning' @@ -256,29 +256,30 @@ function waCheckContainerRunning() { local CONTAINER_STATE="" # Determine container state. - CONTAINER_STATE=$(docker ps --filter name="windows" --format '{{.Status}}') + CONTAINER_STATE=$(docker ps --filter name="WinApps" --format '{{.Status}}') CONTAINER_STATE=${CONTAINER_STATE,,} # Convert the string to lowercase. CONTAINER_STATE=${CONTAINER_STATE%% *} # Extract the first word. # Check container state. - [[ "$CONTAINER_STATE" != "up" ]] && waThrowExit "$EC_VM_NOT_RUNNING" + [[ "$CONTAINER_STATE" != "up" ]] && waThrowExit "$EC_NOT_RUNNING" } -# Name: 'waCheckVMContactable' -# Role: Assesses whether the Windows VM can be contacted. -function waCheckVMContactable() { +# Name: 'waCheckPortOpen' +# Role: Assesses whether the RDP port on Windows is open. +function waCheckPortOpen() { # Declare variables. local VM_MAC="" # Stores the MAC address of the Windows VM. - # Obtain Windows VM IP Address - if [ -z "$RDP_IP" ]; then + # Obtain Windows VM IP Address ('libvirt' ONLY) + # Note: 'RDP_IP' should not be empty if 'WAFLAVOR' is 'docker', since it is set to localhost before this function is called. + if [ -z "$RDP_IP" ] && [ "$WAFLAVOR" = "libvirt" ]; then VM_MAC=$(virsh domiflist "$VM_NAME" | grep -oE "([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})") # VM MAC address. RDP_IP=$(arp -n | grep "$VM_MAC" | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}") # VM IP address. - [ -z "$RDP_IP" ] && waThrowExit "$EC_VM_NO_IP" + [ -z "$RDP_IP" ] && waThrowExit "$EC_NO_IP" fi # Check for an open RDP port. - timeout 5 nc -z "$RDP_IP" "$RDP_PORT" &>/dev/null || waThrowExit "$EC_VM_BAD_PORT" + timeout 5 nc -z "$RDP_IP" "$RDP_PORT" &>/dev/null || waThrowExit "$EC_BAD_PORT" } # Name: 'waRunCommand' @@ -290,7 +291,7 @@ function waRunCommand() { # Run option. if [ "$1" = "windows" ]; then - # Open Windows VM. + # Open Windows RDP session. dprint "WINDOWS" $FREERDP_COMMAND \ /d:"$RDP_DOMAIN" \ @@ -396,11 +397,11 @@ if [ "$WAFLAVOR" = "docker" ]; then elif [ "$WAFLAVOR" = "libvirt" ]; then waCheckGroupMembership waCheckVMRunning - waCheckVMContactable else waThrowExit "$EC_INVALID_FLAVOR" fi +waCheckPortOpen waRunCommand "$@" dprint "END" diff --git a/compose.yaml b/compose.yaml index 9599920..efe769a 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,23 +1,38 @@ -name: "winapps" +# For documentation, FAQ, additional configuration options and technical help, visit: https://github.com/dockur/windows +name: "winapps" # Docker Compose Project Name. volumes: - data: - + data: # Create Volume 'data' @ '/var/lib/docker/volumes/winapps_data/_data'. services: windows: - image: dockurr/windows - container_name: windows + image: dockurr/windows # https://hub.docker.com/r/dockurr/windows + container_name: WinApps # Created Docker VM Name. environment: + # Version of Windows to configure. For valid options, visit: + # https://github.com/dockur/windows?tab=readme-ov-file#how-do-i-select-the-windows-version + # https://github.com/dockur/windows?tab=readme-ov-file#how-do-i-install-a-custom-image VERSION: "tiny11" - RAM_SIZE: "4G" - CPU_CORES: "4" - privileged: true + RAM_SIZE: "4G" # RAM allocated to the Windows VM. + CPU_CORES: "4" # CPU cores allocated to the Windows VM. + DISK_SIZE: "64G" # Size of the primary hard disk. + #DISK2_SIZE: "32G" # Uncomment to add an additional hard disk to the Windows VM. Ensure it is mounted as a volume below. + #USERNAME: "Docker" # Uncomment to set a custom Windows username. The default is 'Docker'. + #PASSWORD: "" # Uncomment to set a password for the Windows user. There is no default password. + HOME: "${HOME}" # Set path to Linux user home folder. + privileged: true # Grant the Windows VM extended privileges. ports: - - 8006:8006 - - 3389:3389/tcp - - 3389:3389/udp - stop_grace_period: 2m - restart: on-failure + - 8006:8006 # Map '8006' on Linux host to '8006' on Windows VM --> For VNC Web Interface @ http://127.0.0.1:8006. + - 3389:3389/tcp # Map '3389' on Linux host to '3389' on Windows VM --> For Remote Desktop Protocol (RDP). + - 3389:3389/udp # Map '3389' on Linux host to '3389' on Windows VM --> For Remote Desktop Protocol (RDP). + stop_grace_period: 120s # Wait 120 seconds before sending SIGTERM when attempting to shut down the Windows VM. + restart: on-failure # Restart the Windows VM if the exit code indicates an error. volumes: - - data:/storage - - ./oem:/oem + - data:/storage # Mount volume 'data' to use as Windows 'C:' drive. + - ${HOME}:/shared # Mount Linux user home directory @ '\\host.lan\Data'. + #- /path/to/second/hard/disk:/storage2 # Uncomment to mount the second hard disk within the Windows VM. Ensure 'DISK2_SIZE' is specified above. + - ./oem:/oem # Enables automatic post-install execution of 'oem/install.bat', applying Windows registry modifications contained within 'oem/RDPApps.reg'. + #- /path/to/windows/install/media.iso:/custom.iso # Uncomment to use a custom Windows ISO. If specified, 'VERSION' (e.g. 'tiny11') will be ignored. + devices: + - /dev/kvm # Enable KVM. + #- /dev/sdX:/disk1 # Uncomment to mount a disk directly within the Windows VM (Note: 'disk1' will be mounted as the main drive). + #- /dev/sdY:/disk2 # Uncomment to mount a disk directly within the Windows VM (Note: 'disk2' and higher will be mounted as secondary drives). diff --git a/docs/docker.md b/docs/docker.md index 901c967..a8077ae 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -76,9 +76,6 @@ to run the VM in the background. After this, just open http://127.0.0.1:8006 in your web browser and wait for the Windows installation to finish. -> [!WARNING] -> Make sure to change the `RDP_IP` in your WinApps config to `127.0.0.1`. - Now you should be ready to go and try to connect to your VM with WinApps. For stopping the VM, just use: @@ -93,4 +90,4 @@ For starting again afterward, use: docker compose start ``` -(All compose commands have to be run from the directory where the `compose.yaml` is located.) +(All compose commands have to be run from the directory where `compose.yaml` is located.) diff --git a/installer.sh b/installer.sh index 1f39a6b..29a3f78 100755 --- a/installer.sh +++ b/installer.sh @@ -22,14 +22,14 @@ readonly EC_NO_CONFIG="4" # Absence of a valid WinApps configuration file readonly EC_MISSING_DEPS="5" # Missing dependencies. readonly EC_NO_SUDO="6" # Insufficient privilages to invoke superuser access. readonly EC_NOT_IN_GROUP="7" # Current user not in group 'libvirt' and/or 'kvm'. -readonly EC_VM_OFF="8" # Windows VM powered off. -readonly EC_VM_PAUSED="9" # Windows VM paused. -readonly EC_VM_ABSENT="10" # Windows VM does not exist. -readonly EC_CONTAINER_OFF="11" # Docker container is not running. -readonly EC_VM_NO_IP="12" # Windows VM does not have an IP address. -readonly EC_VM_BAD_PORT="13" # Windows VM is unreachable via RDP_PORT. -readonly EC_RDP_FAIL="14" # FreeRDP failed to establish a connection with the Windows VM. -readonly EC_APPQUERY_FAIL="15" # Failed to query the Windows VM for installed applications. +readonly EC_VM_OFF="8" # Windows 'libvirt' VM powered off. +readonly EC_VM_PAUSED="9" # Windows 'libvirt' VM paused. +readonly EC_VM_ABSENT="10" # Windows 'libvirt' VM does not exist. +readonly EC_CONTAINER_OFF="11" # Windows Docker container is not running. +readonly EC_NO_IP="12" # Windows does not have an IP address. +readonly EC_BAD_PORT="13" # Windows is unreachable via RDP_PORT. +readonly EC_RDP_FAIL="14" # FreeRDP failed to establish a connection with Windows. +readonly EC_APPQUERY_FAIL="15" # Failed to query Windows for installed applications. # PATHS # 'BIN' @@ -45,8 +45,8 @@ readonly SYS_APPDATA_PATH="/usr/local/share/winapps" # UNIX pat readonly USER_APPDATA_PATH="${HOME}/.local/share/winapps" # UNIX path to 'application data' directory for a '--user' WinApps installation. readonly USER_APPDATA_PATH_WIN='\\tsclient\home\.local\share\winapps' # WINDOWS path to 'application data' directory for a '--user' WinApps installation. # 'Installed Batch Script' -readonly BATCH_SCRIPT_PATH="${USER_APPDATA_PATH}/installed.bat" # UNIX path to a batch script used to search the Windows VM for applications. -readonly BATCH_SCRIPT_PATH_WIN="${USER_APPDATA_PATH_WIN}\\installed.bat" # WINDOWS path to a batch script used to search the Windows VM for applications. +readonly BATCH_SCRIPT_PATH="${USER_APPDATA_PATH}/installed.bat" # UNIX path to a batch script used to search Windows for applications. +readonly BATCH_SCRIPT_PATH_WIN="${USER_APPDATA_PATH_WIN}\\installed.bat" # WINDOWS path to a batch script used to search Windows for applications. # 'Installed File' readonly TMP_INST_FILE_PATH="${USER_APPDATA_PATH}/installed.tmp" # UNIX path to a temporary file containing the names of detected officially supported applications. readonly TMP_INST_FILE_PATH_WIN="${USER_APPDATA_PATH_WIN}\\installed.tmp" # WINDOWS path to a temporary file containing the names of detected officially supported applications. @@ -54,8 +54,8 @@ readonly INST_FILE_PATH="${USER_APPDATA_PATH}/installed" # UNIX readonly INST_FILE_PATH_WIN="${USER_APPDATA_PATH_WIN}\\installed" # WINDOWS path to a file containing the names of detected officially supported applications. # 'PowerShell Script' readonly PS_SCRIPT_PATH="./install/ExtractPrograms.ps1" # UNIX path to a PowerShell script used to store the names, executable paths and icons (base64) of detected applications. -readonly PS_SCRIPT_HOME_PATH="${USER_APPDATA_PATH}/ExtractPrograms.ps1" # UNIX path to a copy of the PowerShell script within the user's home directory to enable access by Windows VM. -readonly PS_SCRIPT_HOME_PATH_WIN="${USER_APPDATA_PATH_WIN}\\ExtractPrograms.ps1" # WINDOWS path to a copy of the PowerShell script within the user's home directory to enable access by Windows VM. +readonly PS_SCRIPT_HOME_PATH="${USER_APPDATA_PATH}/ExtractPrograms.ps1" # UNIX path to a copy of the PowerShell script within the user's home directory to enable access by Windows. +readonly PS_SCRIPT_HOME_PATH_WIN="${USER_APPDATA_PATH_WIN}\\ExtractPrograms.ps1" # WINDOWS path to a copy of the PowerShell script within the user's home directory to enable access by Windows. # 'Detected File' readonly DETECTED_FILE_PATH="${USER_APPDATA_PATH}/detected" # UNIX path to a file containing the output generated by the PowerShell script, formatted to define bash arrays. readonly DETECTED_FILE_PATH_WIN="${USER_APPDATA_PATH_WIN}\\detected" # WINDOWS path to a file containing the output generated by the PowerShell script, formatted to define bash arrays. @@ -68,11 +68,11 @@ readonly CONFIG_PATH="${HOME}/.config/winapps/winapps.conf" # UNIX path to the W readonly INQUIRER_PATH="./install/inquirer.sh" # UNIX path to the 'inquirer' script, which is used to produce selection menus. # REMOTE DESKTOP CONFIGURATION -readonly VM_NAME="RDPWindows" # Name of the Windows VM. -readonly RDP_PORT=3389 # Port used for RDP on the Windows VM. +readonly VM_NAME="RDPWindows" # Name of the Windows VM (FOR 'libvirt' ONLY). +readonly RDP_PORT=3389 # Port used for RDP on Windows. readonly DOCKER_IP="127.0.0.1" # Localhost. -readonly WINAPPS_CONFIG='RDP_USER="MyWindowsUser" -RDP_PASS="MyWindowsPassword" +readonly RDP_USER="" +RDP_PASS="" #RDP_DOMAIN="MYDOMAIN" #RDP_IP="192.168.123.111" #WAFLAVOR="docker" @@ -594,11 +594,11 @@ function waCheckDependencies() { echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}" # Display the error details. - echo -e "${INFO_TEXT}Please install 'Docker Desktop on Linux' to proceed.${CLEAR_TEXT}" + echo -e "${INFO_TEXT}Please install 'Docker Engine' to proceed.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" - echo "Please visit https://docs.docker.com/desktop/install/linux-install/ for more information." + echo "Please visit https://docs.docker.com/engine/install/ for more information." echo "--------------------------------------------------------------------------------" # Terminate the script. @@ -648,7 +648,7 @@ function waCheckGroupMembership() { } # Name: 'waCheckVMRunning' -# Role: Checks the state of the Windows VM to ensure it is running. +# Role: Checks the state of the Windows 'libvirt' VM to ensure it is running. function waCheckVMRunning() { # Print feedback. echo -n "Checking the status of the Windows VM... " @@ -722,13 +722,13 @@ function waCheckVMRunning() { # Role: Throw an error if the Docker container is not running. function waCheckContainerRunning() { # Print feedback. - echo -n "Checking the status of the Windows Docker container/virtual machine... " + echo -n "Checking container status... " # Declare variables. local CONTAINER_STATE="" # Determine container state. - CONTAINER_STATE=$(docker ps --filter name="windows" --format '{{.Status}}') + CONTAINER_STATE=$(docker ps --filter name="WinApps" --format '{{.Status}}') CONTAINER_STATE=${CONTAINER_STATE,,} # Convert the string to lowercase. CONTAINER_STATE=${CONTAINER_STATE%% *} # Extract the first word. @@ -738,14 +738,14 @@ function waCheckContainerRunning() { echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" # Display the error type. - echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}DOCKER VM NOT RUNNING.${CLEAR_TEXT}" + echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}DOCKER CONTAINER NOT RUNNING.${CLEAR_TEXT}" # Display the error details. - echo -e "${INFO_TEXT}The Windows Docker container/virtual machine is not running.${CLEAR_TEXT}" + echo -e "${INFO_TEXT}Windows is not running.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" - echo "Please ensure the Windows Docker container/virtual machine is powered on:" + echo "Please ensure Windows is powered on:" echo -e "${COMMAND_TEXT}docker compose start${CLEAR_TEXT}" echo "--------------------------------------------------------------------------------" @@ -757,17 +757,18 @@ function waCheckContainerRunning() { echo -e "${DONE_TEXT}Done!${CLEAR_TEXT}" } -# Name: 'waCheckVMContactable' -# Role: Assesses whether the Windows VM can be contacted (prior to attempting a remote desktop connection). -function waCheckVMContactable() { +# Name: 'waCheckPortOpen' +# Role: Assesses whether the RDP port on Windows is open. +function waCheckPortOpen() { # Print feedback. - echo -n "Attempting to contact the Windows VM... " + echo -n "Checking for an open RDP Port on Windows... " # Declare variables. local VM_MAC="" # Stores the MAC address of the Windows VM. - # Obtain Windows VM IP Address - if [ -z "$RDP_IP" ]; then + # Obtain Windows VM IP Address (FOR 'libvirt' ONLY) + # Note: 'RDP_IP' should not be empty if 'WAFLAVOR' is 'docker', since it is set to localhost before this function is called. + if [ -z "$RDP_IP" ] && [ "$WAFLAVOR" = "libvirt" ]; then VM_MAC=$(virsh domiflist "$VM_NAME" | grep -oE "([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})") # VM MAC address. RDP_IP=$(arp -n | grep "$VM_MAC" | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}") # VM IP address. @@ -787,7 +788,7 @@ function waCheckVMContactable() { echo "--------------------------------------------------------------------------------" # Terminate the script. - return "$EC_VM_NO_IP" + return "$EC_NO_IP" fi fi @@ -800,15 +801,15 @@ function waCheckVMContactable() { echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}NETWORK CONFIGURATION ERROR.${CLEAR_TEXT}" # Display the error details. - echo -e "${INFO_TEXT}Failed to establish a connection with the Windows VM '${VM_NAME}' at '${RDP_IP}:${RDP_PORT}'.${CLEAR_TEXT}" + echo -e "${INFO_TEXT}Failed to establish a connection with Windows at '${RDP_IP}:${RDP_PORT}'.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" - echo "Please ensure Remote Desktop is configured on the Windows VM as per the WinApps README." + echo "Please ensure Remote Desktop is configured on Windows as per the WinApps README." echo "--------------------------------------------------------------------------------" # Terminate the script. - return "$EC_VM_BAD_PORT" + return "$EC_BAD_PORT" fi # Print feedback. @@ -816,10 +817,10 @@ function waCheckVMContactable() { } # Name: 'waCheckRDPAccess' -# Role: Tests if the Windows VM is accessible via remote desktop. +# Role: Tests if Windows is accessible via RDP. function waCheckRDPAccess() { # Print feedback. - echo -n "Attempting to establish a Remote Desktop connection with the Windows VM... " + echo -n "Attempting to establish a Remote Desktop connection with Windows... " # Declare variables. local FREERDP_LOG="" # Stores the path of the FreeRDP log file. @@ -836,7 +837,7 @@ function waCheckRDPAccess() { rm -f "$TEST_PATH" # This command should create a file on the host filesystem before terminating the RDP session. This command is silently executed as a background process. - # If the file is created, it means the Windows VM received the command via FreeRDP successfully and can read and write to the Linux home folder. + # If the file is created, it means Windows received the command via FreeRDP successfully and can read and write to the Linux home folder. # Note: The following final line is expected within the log, indicating successful execution of the 'tsdiscon' command and termination of the RDP session. # [INFO][com.freerdp.core] - [rdp_print_errinfo]: ERRINFO_LOGOFF_BY_USER (0x0000000C):The disconnection was initiated by the user logging off their session on the server. # shellcheck disable=SC2140,SC2027 # Disable warnings regarding unquoted strings. @@ -886,13 +887,13 @@ function waCheckRDPAccess() { echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}REMOTE DESKTOP PROTOCOL FAILURE.${CLEAR_TEXT}" # Display the error details. - echo -e "${INFO_TEXT}FreeRDP failed to establish a connection with the Windows VM.${CLEAR_TEXT}" + echo -e "${INFO_TEXT}FreeRDP failed to establish a connection with Windows.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo -e "Please view the log at ${COMMAND_TEXT}${FREERDP_LOG}${CLEAR_TEXT}." echo "Troubleshooting Tips:" - echo " - Ensure the user is logged out of the Windows VM prior to initiating the WinApps installation." + echo " - Ensure the user is logged out of Windows prior to initiating the WinApps installation." echo " - Ensure the credentials within the WinApps configuration file are correct." echo -e " - Utilise a new certificate by removing relevant certificate(s) in ${COMMAND_TEXT}${HOME}/.config/freerdp/server${CLEAR_TEXT}." echo " - If using 'libvirt', ensure the Windows VM is correctly named as specified within the README." @@ -913,7 +914,7 @@ function waCheckRDPAccess() { } # Name: 'waFindInstalled' -# Role: Identifies installed applications on the Windows VM. +# Role: Identifies installed applications on Windows. function waFindInstalled() { # Print feedback. echo -n "Checking for installed Windows applications... " @@ -933,7 +934,7 @@ function waFindInstalled() { rm -f "$BATCH_SCRIPT_PATH" "$TMP_INST_FILE_PATH" "$INST_FILE_PATH" "$PS_SCRIPT_HOME_PATH" "$DETECTED_FILE_PATH" # Copy PowerShell script to a directory within the user's home folder. - # This will enable the PowerShell script to be accessed and executed by the Windows VM. + # This will enable the PowerShell script to be accessed and executed by Windows. cp "$PS_SCRIPT_PATH" "$PS_SCRIPT_HOME_PATH" # Enumerate over each officially supported application. @@ -965,7 +966,7 @@ function waFindInstalled() { # Append a command to the batch script to terminate the remote desktop session once all previous commands are complete. echo "tsdiscon" >>"$BATCH_SCRIPT_PATH" - # Silently execute the batch script within the Windows VM in the background (Log Output To File) + # Silently execute the batch script within Windows in the background (Log Output To File) # Note: The following final line is expected within the log, indicating successful execution of the 'tsdiscon' command and termination of the RDP session. # [INFO][com.freerdp.core] - [rdp_print_errinfo]: ERRINFO_LOGOFF_BY_USER (0x0000000C):The disconnection was initiated by the user logging off their session on the server. # shellcheck disable=SC2140,SC2027 # Disable warnings regarding unquoted strings. @@ -1015,7 +1016,7 @@ function waFindInstalled() { echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}APPLICATION QUERY FAILURE.${CLEAR_TEXT}" # Display the error details. - echo -e "${INFO_TEXT}Failed to query Windows VM for installed applications.${CLEAR_TEXT}" + echo -e "${INFO_TEXT}Failed to query Windows for installed applications.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" @@ -1031,14 +1032,14 @@ function waFindInstalled() { } # Name: 'waConfigureWindows' -# Role: Create an application entry for launching Windows VM via Remote Desktop. +# Role: Create an application entry for launching Windows via Remote Desktop. function waConfigureWindows() { # Print feedback. - echo -n "Creating an application entry for Windows VM... " + echo -n "Creating an application entry for Windows... " # Declare variables. - local WIN_BASH="" # Stores the bash script to launch the Windows VM. - local WIN_DESKTOP="" # Stores the '.desktop' file to launch the Windows VM. + local WIN_BASH="" # Stores the bash script to launch a Windows RDP session. + local WIN_DESKTOP="" # Stores the '.desktop' file to launch a Windows RDP session. # Populate variables. WIN_BASH="\ @@ -1052,7 +1053,7 @@ Terminal=false Type=Application Icon=${APPDATA_PATH}/icons/windows.svg StartupWMClass=Microsoft Windows -Comment=Microsoft Windows VM" +Comment=Microsoft Windows RDP Session" # Copy the 'Windows' icon. $SUDO cp "./icons/windows.svg" "${APPDATA_PATH}/icons/windows.svg" @@ -1071,7 +1072,7 @@ Comment=Microsoft Windows VM" } # Name: 'waConfigureApp' -# Role: Create application entries for a given application installed on the Windows VM. +# Role: Create application entries for a given application installed on Windows. function waConfigureApp() { # Declare variables. local APP_ICON="" # Stores the path to the application icon. @@ -1120,12 +1121,12 @@ MimeType=${MIME_TYPES}" } # Name: 'waConfigureOfficiallySupported' -# Role: Create application entries for officially supported applications installed on the Windows VM. +# Role: Create application entries for officially supported applications installed on Windows. function waConfigureOfficiallySupported() { # Declare variables. - local OSA_LIST=() # Stores a list of all officially supported applications installed on the Windows VM. + local OSA_LIST=() # Stores a list of all officially supported applications installed on Windows. - # Read the list of officially supported applications that are installed on the Windows VM into an array, returning an empty array if no such files exist. + # Read the list of officially supported applications that are installed on Windows into an array, returning an empty array if no such files exist. # This will remove leading and trailing whitespace characters as well as ignore empty lines. readarray -t OSA_LIST < <(grep -v '^[[:space:]]*$' "$INST_FILE_PATH" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' 2>/dev/null || true) @@ -1152,18 +1153,18 @@ function waConfigureOfficiallySupported() { # Role: Allow the user to select which officially supported applications to configure. function waConfigureApps() { # Declare variables. - local OSA_LIST=() # Stores a list of all officially supported applications installed on the Windows VM. + local OSA_LIST=() # Stores a list of all officially supported applications installed on Windows. local APPS=() # Stores a list of both the simplified and full names of each installed officially supported application. local OPTIONS=() # Stores a list of options presented to the user. local APP_INSTALL="" # Stores the option selected by the user. local SELECTED_APPS=() # Stores the officially supported applications selected by the user. local TEMP_ARRAY=() # Temporary array used for sorting elements of an array. - # Read the list of officially supported applications that are installed on the Windows VM into an array, returning an empty array if no such files exist. + # Read the list of officially supported applications that are installed on Windows into an array, returning an empty array if no such files exist. # This will remove leading and trailing whitespace characters as well as ignore empty lines. readarray -t OSA_LIST < <(grep -v '^[[:space:]]*$' "$INST_FILE_PATH" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' 2>/dev/null || true) - # Loop over each officially supported application installed on the Windows VM. + # Loop over each officially supported application installed on Windows. for OSA in "${OSA_LIST[@]}"; do # Source 'Info' File Containing: # - The Application Name (FULL_NAME) @@ -1245,7 +1246,7 @@ function waConfigureDetectedApps() { if [ -f "$DETECTED_FILE_PATH" ]; then # On UNIX systems, lines are terminated with a newline character (\n). # On WINDOWS systems, lines are terminated with both a carriage return (\r) and a newline (\n) character. - # Remove all carriage returns (\r) within the 'detected' file, as the file was written by the Windows VM. + # Remove all carriage returns (\r) within the 'detected' file, as the file was written by Windows. sed -i 's/\r//g' "$DETECTED_FILE_PATH" # Import the detected application information: @@ -1321,7 +1322,7 @@ function waConfigureDetectedApps() { NAME=\"${PROGRAM_NAME}\" # Used for Descriptions and Window Class FULL_NAME=\"${PROGRAM_NAME}\" -# Executable within Windows VM +# Path to executable inside Windows WIN_EXECUTABLE=\"${EXES[$INDEX]}\" # GNOME Categories CATEGORIES=\"WinApps\" @@ -1379,7 +1380,7 @@ function waInstall() { # Set RDP_IP to localhost. RDP_IP="$DOCKER_IP" - # Check if the Windows Docker container/virtual machine is powered on. + # Check if Windows is powered on. waCheckContainerRunning elif [ "$WAFLAVOR" = "libvirt" ]; then # Check the group membership of the current user. @@ -1387,14 +1388,14 @@ function waInstall() { # Check if the Windows VM is powered on. waCheckVMRunning - - # Check if the Windows VM is contactable. - waCheckVMContactable else waThrowExit "$EC_INVALID_FLAVOR" fi - # Test RDP access to the Windows VM. + # Check if the RDP port on Windows is open. + waCheckPortOpen + + # Test RDP access to Windows. waCheckRDPAccess # Create required directories. @@ -1409,7 +1410,7 @@ function waInstall() { # Install the WinApps bash script. $SUDO cp "./bin/winapps" "${BIN_PATH}/winapps" - # Configure the Windows VM application launcher. + # Configure the Windows RDP session application launcher. waConfigureWindows if [ "$OPT_AOSA" -eq 1 ]; then From c62391abff3809f48ed3a8b9e50f307f504b905b Mon Sep 17 00:00:00 2001 From: Rohan Barar Date: Thu, 18 Jul 2024 18:45:57 +1000 Subject: [PATCH 3/8] Corrected RDP_USER variable declaration. --- installer.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer.sh b/installer.sh index 29a3f78..cde2588 100755 --- a/installer.sh +++ b/installer.sh @@ -71,7 +71,7 @@ readonly INQUIRER_PATH="./install/inquirer.sh" # UNIX path to the 'inquirer' scr readonly VM_NAME="RDPWindows" # Name of the Windows VM (FOR 'libvirt' ONLY). readonly RDP_PORT=3389 # Port used for RDP on Windows. readonly DOCKER_IP="127.0.0.1" # Localhost. -readonly RDP_USER="" +RDP_USER="" RDP_PASS="" #RDP_DOMAIN="MYDOMAIN" #RDP_IP="192.168.123.111" From 8fbf074307568f79369f33b49ae059d39c52dd8d Mon Sep 17 00:00:00 2001 From: Rohan Barar Date: Fri, 19 Jul 2024 08:06:53 +1000 Subject: [PATCH 4/8] Added instructions for Podman. --- bin/winapps | 6 ++- docs/docker.md | 143 +++++++++++++++++++++++-------------------------- installer.sh | 12 +++-- 3 files changed, 81 insertions(+), 80 deletions(-) diff --git a/bin/winapps b/bin/winapps index 90a9c5e..5433ad4 100755 --- a/bin/winapps +++ b/bin/winapps @@ -256,7 +256,11 @@ function waCheckContainerRunning() { local CONTAINER_STATE="" # Determine container state. - CONTAINER_STATE=$(docker ps --filter name="WinApps" --format '{{.Status}}') + if command -v docker &>/dev/null; then + CONTAINER_STATE=$(docker ps --filter name="WinApps" --format '{{.Status}}') + else + CONTAINER_STATE=$(podman ps --filter name="WinApps" --format '{{.Status}}') + fi CONTAINER_STATE=${CONTAINER_STATE,,} # Convert the string to lowercase. CONTAINER_STATE=${CONTAINER_STATE%% *} # Extract the first word. diff --git a/docs/docker.md b/docs/docker.md index a8077ae..4b56eef 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -1,93 +1,84 @@ -# Creating a Virtual Machine in Docker +# Creating a Windows VM in `Docker` or `Podman` +Although WinApps supports using `QEMU+KVM+libvirt` as a backend for running Windows virtual machines, it is recommended to use `Docker` or `Podman`. These backends automate the setup process, eliminating the need for manual configuration and optimisation of the Windows virtual machine. -## Why Docker? +> [!IMPORTANT] +Running a Windows virtual machine using `Docker` or `Podman` as a backend is only possible on GNU/Linux systems. This is due to the necessity of kernel interfaces, such as the KVM hypervisor, for achieving acceptable performance. The performance of the virtual machine can vary based on the version of the Linux kernel, with newer releases generally offering better performance. -While working with `virsh` is completely fine for WinApps, you have to set up and optimize your VM manually. -Docker, on the other hand, sets up most of the stuff automatically and makes the VM highly portable between Linux distros. +> [!IMPORTANT] +> WinApps does NOT officially support versions of Windows prior to Windows 10. Despite this, it may be possible to achieve a successful installation with some additional experimentation. If you find a way to achieve this, please share your solution through a pull request for the benefit of other users. -# Requirements +## `Docker` +### Installation +You can find a guide for installing `Docker Engine` [here](https://docs.docker.com/engine/install/). -Since Docker manages the dependencies of the container automatically, you only need to install Docker itself. +### Setup `Docker` Container +WinApps utilises `docker compose` to configure Windows VMs. A suitable [`compose.yaml`](https://github.com/winapps-org/winapps/blob/main/compose.yaml) file is included in the root directory of the WinApps repository. -You can try using Podman too because of their faster container startup times, -but note that Podman and Docker aren't always fully interchangeable. In case you want to follow this guide using Podman, -you will have to install the `docker` CLI to be able to run `docker compose` commands. -You will also have to enable the Podman socket. Refer to the Podman docs for how to do that. +Prior to initiating the installation, you can modify the RAM and number of CPU cores available to the Windows VM by changing `RAM_SIZE` and `CPU_CORES` within `compose.yaml`. -See: - -- [Podman installation docs](https://podman.io/docs/installation) -- [Docker installation docs](https://docs.docker.com/engine/install) -- [Using `docker compose` with Podman](https://www.redhat.com/sysadmin/podman-docker-compose) (slightly outdated) +It is also possible to specify the version of Windows you wish to install within `compose.yaml` by modifying `VERSION`. > [!NOTE] -> This will only work on Linux systems since the VM needs some kernel interfaces (like KVM). Because of this, -> performance can vary depending on kernel version (newer will likely perform better). +> WinApps uses a stripped-down Windows installation by default. Although this is recommended, you can request a stock Windows installation by changing `VERSION` to one of the versions listed in the README of the [original GitHub repository](https://github.com/dockur/windows). -# Setup Docker Container +Please refer to the [original GitHub repository](https://github.com/dockur/windows) for more information on additional configuration options. -The easiest way to set up a Windows VM is by using docker compose. A compose file that looks like this is already shipped with WinApps: - -```yaml -name: "winapps" - -volumes: - data: - -services: - windows: - image: dockurr/windows - container_name: windows - environment: - VERSION: "tiny11" - RAM_SIZE: "4G" - CPU_CORES: "4" - privileged: true - ports: - - 8006:8006 - - 3389:3389/tcp - - 3389:3389/udp - stop_grace_period: 2m - restart: on-failure - volumes: - - data:/storage +### Installing Windows +After navigating into the cloned WinApps repository, you can initiate the Windows installation using `docker compose`. +```bash +docker compose up ``` -Now you can tune the RAM/usage by changing `RAM_SIZE` & `CPU_CORES`. You can also specify -the Windows versions you want to use. You might also want to take a look at the [repo of the Docker image](https://github.com/dockur/windows) for further information. +You can then access the Windows virtual machine via a VNC connection to complete the Windows setup by navigating to http://127.0.0.1:8006 in your web browser. -This compose file uses Windows 11 by default. You can use Windows 10 by changing the `VERSION` to `tiny10`. +### Installing WinApps +`Docker` simplifies the WinApps installation process by eliminating the need for any additional configuration of the Windows virtual machine. Once the Windows virtual machine is up and running, you can directly launch the WinApps installer, which should automatically detect and interface with Windows. + +```bash +./installer.sh +``` + +### Subsequent Use +```bash +docker compose start # Power on the Windows VM +docker compose pause # Pause the Windows VM +docker compose unpause # Resume the Windows VM +docker compose restart # Restart the Windows VM +docker compose stop # Gracefully shut down the Windows VM +docker compose kill # Force shut down the Windows VM +``` > [!NOTE] -> We use a stripped-down Windows installation by default. This is recommended, -> but you can still opt for stock Windows by changing the version to one of the versions listed in -> the README of the images repository linked above. +> The above `docker compose` commands must be run within the same directory containing `compose.yaml`. + +## `Podman` +### Installation +1. Install `Podman` using [this guide](https://podman.io/docs/installation). +2. Install `podman-compose` using [this guide](https://github.com/containers/podman-compose?tab=readme-ov-file#installation). + +### Setup `Podman` Container +Please follow the [`docker` instructions](#setup-docker-container). + +### Installing Windows +After navigating into the cloned WinApps repository, you can initiate the Windows installation using `podman-compose`. +```bash +podman-compose up +``` + +You can then access the Windows virtual machine via a VNC connection to complete the Windows setup by navigating to http://127.0.0.1:8006 in your web browser. + +### Installing WinApps +Please follow the [`docker` instructions](#installing-winapps). + +### Subsequent Use +```bash +podman-compose start # Power on the Windows VM +podman-compose pause # Pause the Windows VM +podman-compose unpause # Resume the Windows VM +podman-compose restart # Restart the Windows VM +podman-compose stop # Gracefully shut down the Windows VM +podman-compose kill # Force shut down the Windows VM +``` > [!NOTE] -> We don't officially support older versions than Windows 10. However, they might still work with some additional tuning. - -You can now just run: - -```shell -docker compose up -d -``` - -to run the VM in the background. - -After this, just open http://127.0.0.1:8006 in your web browser and wait for the Windows installation to finish. - -Now you should be ready to go and try to connect to your VM with WinApps. - -For stopping the VM, just use: - -```shell -docker compose stop -``` - -For starting again afterward, use: - -```shell -docker compose start -``` - -(All compose commands have to be run from the directory where `compose.yaml` is located.) +> The above `podman-compose` commands must be run within the same directory containing `compose.yaml`. diff --git a/installer.sh b/installer.sh index cde2588..33d1b9a 100755 --- a/installer.sh +++ b/installer.sh @@ -586,7 +586,7 @@ function waCheckDependencies() { return "$EC_MISSING_DEPS" fi elif [ "$WAFLAVOR" = "docker" ]; then - if ! command -v docker &>/dev/null; then + if ! command -v docker &>/dev/null && ! command -v podman-compose &>/dev/null; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" @@ -594,11 +594,13 @@ function waCheckDependencies() { echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}" # Display the error details. - echo -e "${INFO_TEXT}Please install 'Docker Engine' to proceed.${CLEAR_TEXT}" + echo -e "${INFO_TEXT}Please install 'Docker Engine' OR 'podman' and 'podman-compose' to proceed.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Please visit https://docs.docker.com/engine/install/ for more information." + echo "Please visit https://podman.io/docs/installation for more information." + echo "Please visit https://github.com/containers/podman-compose for more information." echo "--------------------------------------------------------------------------------" # Terminate the script. @@ -728,7 +730,11 @@ function waCheckContainerRunning() { local CONTAINER_STATE="" # Determine container state. - CONTAINER_STATE=$(docker ps --filter name="WinApps" --format '{{.Status}}') + if command -v docker &>/dev/null; then + CONTAINER_STATE=$(docker ps --filter name="WinApps" --format '{{.Status}}') + else + CONTAINER_STATE=$(podman ps --filter name="WinApps" --format '{{.Status}}') + fi CONTAINER_STATE=${CONTAINER_STATE,,} # Convert the string to lowercase. CONTAINER_STATE=${CONTAINER_STATE%% *} # Extract the first word. From ff2b258ae39460369f95cdf7670077bb9a3e501e Mon Sep 17 00:00:00 2001 From: Rohan Barar Date: Fri, 19 Jul 2024 13:13:02 +1000 Subject: [PATCH 5/8] Added official support for Podman. --- README.md | 15 +++++------ bin/winapps | 17 ++++++++++--- docs/docker.md | 3 +++ installer.sh | 68 +++++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 80 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 2818b9c..e93da75 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # WinApps *The WinApps project, forked from Fmstrat's [original repository](https://github.com/Fmstrat/winapps).* -Run Windows applications (including [Microsoft 365](https://www.microsoft365.com/) and [Adobe Creative Cloud](https://www.adobe.com/creativecloud.html)) on GNU+Linux with `KDE` or `GNOME`, integrated seamlessly as if they were native to the OS. +Run Windows applications (including [Microsoft 365](https://www.microsoft365.com/) and [Adobe Creative Cloud](https://www.adobe.com/creativecloud.html)) on GNU+Linux with `KDE`, `GNOME` or `XFCE`, integrated seamlessly as if they were native to the OS. WinApps Demonstration Animation. @@ -75,10 +75,10 @@ Contributing to the list of supported applications is encouraged through submiss ## Installation ### Step 1: Configure a Windows VM -The optimal choice for running a Windows VM as a subsystem for WinApps is `Docker`. `Docker` facilitates automated installation processes while leveraging a `KVM/QEMU` backend. Despite continuing to provide documentation for configuring a Windows VM using `libvirt` and `virt-manager`, this method is now considered deprecated. +The optimal choice for running a Windows VM as a subsystem for WinApps is `Docker`. `Docker` facilitates automated installation processes while leveraging a `KVM/QEMU` backend. Despite continuing to provide documentation for configuring a Windows VM using `virt-manager`, this method is now considered deprecated. The following guides are available: -- [Creating a Windows VM with `Docker`](docs/docker.md) +- [Creating a Windows VM with `Docker` or `Podman`](docs/docker.md) - [Creating a Windows VM with `virt-manager`](docs/KVM.md) (Deprecated) If you already have a Windows VM or server you wish to use with WinApps, you will need to merge `install/RDPApps.reg` into the Windows Registry. @@ -107,7 +107,8 @@ If you already have a Windows VM or server you wish to use with WinApps, you wil sudo emerge --ask=n sys-libs/dialog net-misc/freerdp:3 ``` -Please note that WinApps requires `FreeRDP` version 3 or later. If not available for your distribution through your package manager, you can install the [Flatpak](https://flathub.org/apps/com.freerdp.FreeRDP). +> [!NOTE] +> WinApps requires `FreeRDP` version 3 or later. If not available for your distribution through your package manager, you can install the [Flatpak](https://flathub.org/apps/com.freerdp.FreeRDP). ```bash flatpak install flathub com.freerdp.FreeRDP @@ -121,8 +122,8 @@ RDP_USER="MyWindowsUser" RDP_PASS="MyWindowsPassword" #RDP_DOMAIN="MYDOMAIN" #RDP_IP="192.168.123.111" -#WAFLAVOR="docker" -#RDP_SCALE=100 +#WAFLAVOR="docker" # Acceptable values are 'docker', 'podman' and 'libvirt'. +#RDP_SCALE=100 # Acceptable values are 100, 140, and 180. #RDP_FLAGS="" #MULTIMON="true" #DEBUG="true" @@ -133,7 +134,7 @@ RDP_PASS="MyWindowsPassword" > `RDP_USER` and `RDP_PASS` must correspond to a complete Windows user account and password, such as those created during Windows setup or for a domain user. User/PIN combinations are not valid for RDP access. > [!NOTE] -> If you wish to use the older (deprecated) `virt-manager` method, uncomment and change `WAFLAVOR="docker"` to `WAFLAVOR="libvirt"`. +> If you wish to use an alternative WinApps backend (other than `Docker`), uncomment and change `WAFLAVOR="docker"` to `WAFLAVOR="podman"` or `WAFLAVOR="libvirt"`. #### Configuration Options Explained - When using a pre-existing non-KVM RDP server, you must use `RDP_IP` to specify the location of the Windows server. diff --git a/bin/winapps b/bin/winapps index 5433ad4..6c52ca1 100755 --- a/bin/winapps +++ b/bin/winapps @@ -94,7 +94,7 @@ function waThrowExit() { "$EC_INVALID_FLAVOR") dprint "ERROR: INVALID FLAVOR. EXITING." echo -e "${ERROR_TEXT}ERROR: INVALID FLAVOR.${CLEAR_TEXT}" - echo "Please ensure either 'docker' or 'libvirt' are specified as the flavor in the WinApps configuration file." + echo "Please ensure 'docker', 'podman' or 'libvirt' are specified as the flavor in the WinApps configuration file." ;; esac @@ -255,12 +255,16 @@ function waCheckContainerRunning() { # Declare variables. local CONTAINER_STATE="" - # Determine container state. + # Determine container state (docker). if command -v docker &>/dev/null; then CONTAINER_STATE=$(docker ps --filter name="WinApps" --format '{{.Status}}') - else + fi + + # Determine container state (podman). + if [ -z "$CONTAINER_STATE" ]; then CONTAINER_STATE=$(podman ps --filter name="WinApps" --format '{{.Status}}') fi + CONTAINER_STATE=${CONTAINER_STATE,,} # Convert the string to lowercase. CONTAINER_STATE=${CONTAINER_STATE%% *} # Extract the first word. @@ -395,7 +399,12 @@ waLastRun waLoadConfig waGetFreeRDPCommand -if [ "$WAFLAVOR" = "docker" ]; then +# If using podman backend, modify the FreeRDP command to enter a new namespace. +if [ "$WAFLAVOR" = "podman" ]; then + FREERDP_COMMAND="podman unshare --rootless-netns ${FREERDP_COMMAND}" +fi + +if [ "$WAFLAVOR" = "docker" ] || [ "$WAFLAVOR" = "podman" ]; then RDP_IP="$DOCKER_IP" waCheckContainerRunning elif [ "$WAFLAVOR" = "libvirt" ]; then diff --git a/docs/docker.md b/docs/docker.md index 4b56eef..1b752fd 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -59,6 +59,9 @@ docker compose kill # Force shut down the Windows VM ### Setup `Podman` Container Please follow the [`docker` instructions](#setup-docker-container). +> [!NOTE] +> Ensure `WAFLAVOR` is set to `"podman"` in `~/.config/winapps/winapps.conf`. + ### Installing Windows After navigating into the cloned WinApps repository, you can initiate the Windows installation using `podman-compose`. ```bash diff --git a/installer.sh b/installer.sh index 33d1b9a..9816134 100755 --- a/installer.sh +++ b/installer.sh @@ -30,6 +30,7 @@ readonly EC_NO_IP="12" # Windows does not have an IP address. readonly EC_BAD_PORT="13" # Windows is unreachable via RDP_PORT. readonly EC_RDP_FAIL="14" # FreeRDP failed to establish a connection with Windows. readonly EC_APPQUERY_FAIL="15" # Failed to query Windows for installed applications. +readonly EC_INVALID_FLAVOR="16" # Backend specified is not 'libvirt', 'docker' or 'podman'. # PATHS # 'BIN' @@ -586,7 +587,7 @@ function waCheckDependencies() { return "$EC_MISSING_DEPS" fi elif [ "$WAFLAVOR" = "docker" ]; then - if ! command -v docker &>/dev/null && ! command -v podman-compose &>/dev/null; then + if ! command -v docker &>/dev/null; then # Complete the previous line. echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" @@ -594,11 +595,29 @@ function waCheckDependencies() { echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}" # Display the error details. - echo -e "${INFO_TEXT}Please install 'Docker Engine' OR 'podman' and 'podman-compose' to proceed.${CLEAR_TEXT}" + echo -e "${INFO_TEXT}Please install 'Docker Engine' to proceed.${CLEAR_TEXT}" # Display the suggested action(s). echo "--------------------------------------------------------------------------------" echo "Please visit https://docs.docker.com/engine/install/ for more information." + echo "--------------------------------------------------------------------------------" + + # Terminate the script. + return "$EC_MISSING_DEPS" + fi + elif [ "$WAFLAVOR" = "podman" ]; then + if ! command -v podman-compose &>/dev/null || ! command -v podman &>/dev/null; then + # Complete the previous line. + echo -e "${FAIL_TEXT}Failed!${CLEAR_TEXT}\n" + + # Display the error type. + echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}MISSING DEPENDENCIES.${CLEAR_TEXT}" + + # Display the error details. + echo -e "${INFO_TEXT}Please install 'podman' and 'podman-compose' to proceed.${CLEAR_TEXT}" + + # Display the suggested action(s). + echo "--------------------------------------------------------------------------------" echo "Please visit https://podman.io/docs/installation for more information." echo "Please visit https://github.com/containers/podman-compose for more information." echo "--------------------------------------------------------------------------------" @@ -729,12 +748,16 @@ function waCheckContainerRunning() { # Declare variables. local CONTAINER_STATE="" - # Determine container state. + # Determine container state (docker). if command -v docker &>/dev/null; then CONTAINER_STATE=$(docker ps --filter name="WinApps" --format '{{.Status}}') - else + fi + + # Determine container state (podman). + if [ -z "$CONTAINER_STATE" ]; then CONTAINER_STATE=$(podman ps --filter name="WinApps" --format '{{.Status}}') fi + CONTAINER_STATE=${CONTAINER_STATE,,} # Convert the string to lowercase. CONTAINER_STATE=${CONTAINER_STATE%% *} # Extract the first word. @@ -881,7 +904,7 @@ function waCheckRDPAccess() { # Check if FreeRDP process is not complete. if ps -p "$FREERDP_PROC" &>/dev/null; then # SIGKILL FreeRDP. - kill -9 "$FREERDP_PROC" + kill -9 "$FREERDP_PROC" &>/dev/null fi # Check if test file does not exist. @@ -1010,7 +1033,7 @@ function waFindInstalled() { # Check if the FreeRDP process is not complete. if ps -p "$FREERDP_PROC" &>/dev/null; then # SIGKILL FreeRDP. - kill -9 "$FREERDP_PROC" + kill -9 "$FREERDP_PROC" &>/dev/null fi # Check if test file does not exist. @@ -1299,7 +1322,9 @@ function waConfigureDetectedApps() { if [[ $APP_INSTALL == "Select which applications to set up" ]]; then inqChkBx "Which other applications would you like to set up?" APPS SELECTED_APPS elif [[ $APP_INSTALL == "Set up all detected applications" ]]; then - readarray -t SELECTED_APPS <<<"${APPS[@]}" + for APP in "${APPS[@]}"; do + SELECTED_APPS+=("$APP") + done fi for SELECTED_APP in "${SELECTED_APPS[@]}"; do @@ -1382,20 +1407,39 @@ function waInstall() { FREERDP_COMMAND="${FREERDP_COMMAND} ${RDP_FLAGS}" fi - if [ "$WAFLAVOR" = "docker" ]; then - # Set RDP_IP to localhost. + # If using 'docker' or 'podman', set RDP_IP to localhost. + if [ "$WAFLAVOR" = "docker" ] || [ "$WAFLAVOR" = "podman" ]; then RDP_IP="$DOCKER_IP" + fi + # If using podman backend, modify the FreeRDP command to enter a new namespace. + if [ "$WAFLAVOR" = "podman" ]; then + FREERDP_COMMAND="podman unshare --rootless-netns ${FREERDP_COMMAND}" + fi + + if [ "$WAFLAVOR" = "docker" ] || [ "$WAFLAVOR" = "podman" ]; then # Check if Windows is powered on. waCheckContainerRunning elif [ "$WAFLAVOR" = "libvirt" ]; then - # Check the group membership of the current user. - waCheckGroupMembership + # Verify the current user's group membership. + waCheckGroupMembership # Check membership # Check if the Windows VM is powered on. waCheckVMRunning else - waThrowExit "$EC_INVALID_FLAVOR" + # Display the error type. + echo -e "${ERROR_TEXT}ERROR:${CLEAR_TEXT} ${BOLD_TEXT}INVALID WINAPPS BACKEND.${CLEAR_TEXT}" + + # Display the error details. + echo -e "${INFO_TEXT}An invalid WinApps backend '${WAFLAVOR}' was specified.${CLEAR_TEXT}" + + # Display the suggested action(s). + echo "--------------------------------------------------------------------------------" + echo -e "Please ensure 'WAFLAVOR' is set to 'docker', 'podman' or 'libvirt' in ${COMMAND_TEXT}${CONFIG_PATH}${CLEAR_TEXT}." + echo "--------------------------------------------------------------------------------" + + # Terminate the script. + return "$EC_INVALID_FLAVOR" fi # Check if the RDP port on Windows is open. From 701ab587f423cea730844ab9c2ae5a48a3a3c448 Mon Sep 17 00:00:00 2001 From: Rohan Barar Date: Fri, 19 Jul 2024 13:32:43 +1000 Subject: [PATCH 6/8] Minor changes to compose.yaml --- compose.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compose.yaml b/compose.yaml index efe769a..3e77cfb 100644 --- a/compose.yaml +++ b/compose.yaml @@ -2,7 +2,10 @@ name: "winapps" # Docker Compose Project Name. volumes: - data: # Create Volume 'data' @ '/var/lib/docker/volumes/winapps_data/_data'. + # Create Volume 'data'. + # Located @ '/var/lib/docker/volumes/winapps_data/_data' (Docker). + # Located @ '/var/lib/containers/storage/volumes/winapps_data/_data' or '~/.local/share/containers/storage/volumes/winapps_data/_data' (Podman). + data: services: windows: image: dockurr/windows # https://hub.docker.com/r/dockurr/windows From 2dfda007a83c37be46e542bab5ea2d939902e525 Mon Sep 17 00:00:00 2001 From: Rohan Barar Date: Fri, 19 Jul 2024 13:54:12 +1000 Subject: [PATCH 7/8] Fix #91. --- bin/winapps | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bin/winapps b/bin/winapps index 6c52ca1..a927654 100755 --- a/bin/winapps +++ b/bin/winapps @@ -162,9 +162,6 @@ function waLoadConfig() { # Update $RDP_SCALE. waFixScale - - # Append additional flags or parameters to FreeRDP. - [[ -n $RDP_FLAGS ]] && FREERDP_COMMAND="${FREERDP_COMMAND} ${RDP_FLAGS}" } # Name: 'waLastRun' @@ -226,6 +223,10 @@ function waGetFreeRDPCommand() { if command -v "$FREERDP_COMMAND" &>/dev/null || [ "$FREERDP_COMMAND" = "flatpak run --command=xfreerdp com.freerdp.FreeRDP" ]; then dprint "Using FreeRDP command '${FREERDP_COMMAND}'." + + # Append additional flags or parameters to FreeRDP. + # These additional flags are loaded prior in 'waLoadConfig'. + [[ -n $RDP_FLAGS ]] && FREERDP_COMMAND="${FREERDP_COMMAND} ${RDP_FLAGS}" else waThrowExit "$EC_MISSING_FREERDP" fi From daecdbe35231ccbb63015269255989229f68b666 Mon Sep 17 00:00:00 2001 From: Rohan Barar Date: Sat, 20 Jul 2024 01:30:13 +1000 Subject: [PATCH 8/8] Added sound and microphone support by default + Updated libvirt documentation. --- README.md | 22 +- bin/winapps | 4 + docs/libvirt.md | 555 +++++++++++++++++++ docs/libvirt_images/00.png | Bin 0 -> 103096 bytes docs/libvirt_images/01.png | Bin 0 -> 21102 bytes docs/libvirt_images/02.png | Bin 0 -> 14811 bytes docs/libvirt_images/03.png | Bin 0 -> 36656 bytes docs/libvirt_images/04_1.png | Bin 0 -> 35930 bytes docs/libvirt_images/04_2.png | Bin 0 -> 38914 bytes docs/libvirt_images/05.png | Bin 0 -> 25365 bytes docs/libvirt_images/06.png | Bin 0 -> 30936 bytes docs/libvirt_images/07.png | Bin 0 -> 43307 bytes docs/libvirt_images/08.png | Bin 0 -> 76725 bytes docs/libvirt_images/09.png | Bin 0 -> 150332 bytes docs/libvirt_images/10.png | Bin 0 -> 73746 bytes docs/libvirt_images/11.png | Bin 0 -> 79057 bytes docs/libvirt_images/12.png | Bin 0 -> 89898 bytes docs/libvirt_images/13.png | Bin 0 -> 82387 bytes docs/libvirt_images/14.png | Bin 0 -> 74397 bytes docs/libvirt_images/15.png | Bin 0 -> 98328 bytes docs/libvirt_images/16.png | Bin 0 -> 37341 bytes docs/libvirt_images/17.png | Bin 0 -> 43961 bytes docs/libvirt_images/18.png | Bin 0 -> 54473 bytes docs/libvirt_images/19.png | Bin 0 -> 50057 bytes docs/libvirt_images/20.png | Bin 0 -> 44324 bytes docs/libvirt_images/21.png | Bin 0 -> 35255 bytes docs/libvirt_images/22.png | Bin 0 -> 30656 bytes docs/libvirt_images/23.png | Bin 0 -> 72561 bytes docs/libvirt_images/24.png | Bin 0 -> 203431 bytes docs/libvirt_images/25.png | Bin 0 -> 230002 bytes docs/libvirt_images/26.png | Bin 0 -> 202861 bytes docs/libvirt_images/27.png | Bin 0 -> 172329 bytes docs/libvirt_images/28.png | Bin 0 -> 182136 bytes docs/libvirt_images/29.png | Bin 0 -> 175252 bytes docs/libvirt_images/30.png | Bin 0 -> 160743 bytes docs/libvirt_images/31.png | Bin 0 -> 164177 bytes docs/libvirt_images/Virtualisation_Stack.svg | 180 ++++++ installer.sh | 4 +- 38 files changed, 752 insertions(+), 13 deletions(-) create mode 100644 docs/libvirt.md create mode 100644 docs/libvirt_images/00.png create mode 100644 docs/libvirt_images/01.png create mode 100644 docs/libvirt_images/02.png create mode 100644 docs/libvirt_images/03.png create mode 100644 docs/libvirt_images/04_1.png create mode 100644 docs/libvirt_images/04_2.png create mode 100644 docs/libvirt_images/05.png create mode 100644 docs/libvirt_images/06.png create mode 100644 docs/libvirt_images/07.png create mode 100644 docs/libvirt_images/08.png create mode 100644 docs/libvirt_images/09.png create mode 100644 docs/libvirt_images/10.png create mode 100644 docs/libvirt_images/11.png create mode 100644 docs/libvirt_images/12.png create mode 100644 docs/libvirt_images/13.png create mode 100644 docs/libvirt_images/14.png create mode 100644 docs/libvirt_images/15.png create mode 100644 docs/libvirt_images/16.png create mode 100644 docs/libvirt_images/17.png create mode 100644 docs/libvirt_images/18.png create mode 100644 docs/libvirt_images/19.png create mode 100644 docs/libvirt_images/20.png create mode 100644 docs/libvirt_images/21.png create mode 100644 docs/libvirt_images/22.png create mode 100644 docs/libvirt_images/23.png create mode 100644 docs/libvirt_images/24.png create mode 100644 docs/libvirt_images/25.png create mode 100644 docs/libvirt_images/26.png create mode 100644 docs/libvirt_images/27.png create mode 100644 docs/libvirt_images/28.png create mode 100644 docs/libvirt_images/29.png create mode 100644 docs/libvirt_images/30.png create mode 100644 docs/libvirt_images/31.png create mode 100644 docs/libvirt_images/Virtualisation_Stack.svg diff --git a/README.md b/README.md index e93da75..16e6bc7 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Run Windows applications (including [Microsoft 365](https://www.microsoft365.com ## Underlying Mechanism WinApps works by: -1. Running Windows in a `Docker` or `libvirt + KVM/QEMU` virtual machine (deprecated). +1. Running Windows in a `Docker`, `Podman` or `libvirt` virtual machine. 2. Querying Windows for all installed applications. 3. Creating shortcuts to selected Windows applications on the host GNU/Linux OS. 4. Using [`FreeRDP`](https://www.freerdp.com/) as a backend to seamlessly render Windows applications alongside GNU/Linux applications. @@ -75,13 +75,13 @@ Contributing to the list of supported applications is encouraged through submiss ## Installation ### Step 1: Configure a Windows VM -The optimal choice for running a Windows VM as a subsystem for WinApps is `Docker`. `Docker` facilitates automated installation processes while leveraging a `KVM/QEMU` backend. Despite continuing to provide documentation for configuring a Windows VM using `virt-manager`, this method is now considered deprecated. +Both `Docker` and `Podman` are recommended backends for running the Windows virtual machine, as they facilitate an automated Windows installation process. WinApps is also compatible with `libvirt`. While this method requires considerably more manual configuration, it also provides greater virtual machine customisation options. All three methods leverage the `KVM` hypervisor, ensuring excellent virtual machine performance. Ultimately, the choice of backend depends on your specific use case. The following guides are available: - [Creating a Windows VM with `Docker` or `Podman`](docs/docker.md) -- [Creating a Windows VM with `virt-manager`](docs/KVM.md) (Deprecated) +- [Creating a Windows VM with `libvirt`](docs/libvirt.md) -If you already have a Windows VM or server you wish to use with WinApps, you will need to merge `install/RDPApps.reg` into the Windows Registry. +If you already have a Windows VM or server you wish to use with WinApps, you will need to merge `install/RDPApps.reg` into the Windows Registry manually. ### Step 2: Clone WinApps Repository and Dependencies 1. Clone the WinApps GitHub repository. @@ -137,14 +137,14 @@ RDP_PASS="MyWindowsPassword" > If you wish to use an alternative WinApps backend (other than `Docker`), uncomment and change `WAFLAVOR="docker"` to `WAFLAVOR="podman"` or `WAFLAVOR="libvirt"`. #### Configuration Options Explained -- When using a pre-existing non-KVM RDP server, you must use `RDP_IP` to specify the location of the Windows server. -- If running a Windows VM in KVM with NAT enabled, leave `RDP_IP` commented out and WinApps will auto-detect the local IP address for the VM. +- If using a pre-existing Windows RDP server on your LAN, you must use `RDP_IP` to specify the location of the Windows server. You may also wish to configure a static IP address for this server. +- If running a Windows VM using `libvirt` with NAT enabled, leave `RDP_IP` commented out and WinApps will auto-detect the local IP address for the VM. - For domain users, you can uncomment and change `RDP_DOMAIN`. -- On high-resolution (UHD) displays, you can set `RDP_SCALE` to the scale you would like to use [100|140|160|180]. -- To add flags to the FreeRDP call, such as `/audio-mode:1` to pass in a microphone, uncomment and use the `RDP_FLAGS` configuration option. +- On high-resolution (UHD) displays, you can set `RDP_SCALE` to the scale you would like to use (100, 140 or 180). +- To add additional flags to the FreeRDP call (e.g. `/prevent-session-lock 120`), uncomment and use the `RDP_FLAGS` configuration option. - For multi-monitor setups, you can try enabling `MULTIMON`. A FreeRDP bug may result in a black screen however, in which case you should revert this change. -- If you enable `DEBUG`, a log will be created on each application start in `~/.local/share/winapps/winapps.log` -- If using a system on which the FreeRDP command is not `xfreerdp`, the correct command can be specified using `FREERDP_COMMAND`. +- If you enable `DEBUG`, a log will be created on each application start in `~/.local/share/winapps/winapps.log`. +- If using a system on which the FreeRDP command is not `xfreerdp` or `xfreerdp3`, the correct command can be specified using `FREERDP_COMMAND`. ### Step 4: Run the WinApps Installer Run the WinApps installer. @@ -161,7 +161,7 @@ Adding your own applications with custom icons and MIME types to the installer i 1. Modify the name and variables to reflect the appropriate/desired values for your application. 2. Replace `icon.svg` with an SVG for your application (ensuring the icon is appropriately licensed). 3. Remove and reinstall WinApps. -4. (Optional, but strongly encouraged) Submit a pull request to add your application to WinApps as an officially supported application once you have tested your configuration files to verify functionality. +4. Submit a pull request to add your application to WinApps as an officially supported application once you have tested and verified your configuration (optional, but encouraged). ## Running Applications Manually WinApps offers a manual mode for running applications that were not configured by the WinApps installer. This is completed with the `manual` flag. Executables that are in the Windows PATH do not require full path definition. diff --git a/bin/winapps b/bin/winapps index a927654..900538c 100755 --- a/bin/winapps +++ b/bin/winapps @@ -310,6 +310,7 @@ function waRunCommand() { +dynamic-resolution \ +auto-reconnect \ +home-drive \ + /audio-mode:1 \ /wm-class:"Microsoft Windows" \ /v:"$RDP_IP" &>/dev/null & elif [ "$1" = "manual" ]; then @@ -324,6 +325,7 @@ function waRunCommand() { +auto-reconnect \ +clipboard \ +home-drive \ + /audio-mode:1 \ -wallpaper \ +dynamic-resolution \ "$MULTI_FLAG" \ @@ -358,6 +360,7 @@ function waRunCommand() { +auto-reconnect \ +clipboard \ +home-drive \ + /audio-mode:1 \ -wallpaper \ +dynamic-resolution \ "$MULTI_FLAG" \ @@ -379,6 +382,7 @@ function waRunCommand() { +auto-reconnect \ +clipboard \ +home-drive \ + /audio-mode:1 \ -wallpaper \ +dynamic-resolution \ "$MULTI_FLAG" \ diff --git a/docs/libvirt.md b/docs/libvirt.md new file mode 100644 index 0000000..cb82dfb --- /dev/null +++ b/docs/libvirt.md @@ -0,0 +1,555 @@ +# Creating a `libvirt` Windows VM +## Understanding The Virtualisation Stack +This method of configuring a Windows virtual machine for use with WinApps is significantly more involved than utilising `Docker` or `Podman`. Nevertheless, expert users may prefer this method due to its greater flexibility and wider range of customisation options. + +Before beginning, it is important to have a basic understanding of the various components involved in this particular method. + +1. `QEMU` is a FOSS emulator that performs hardware virtualisation, enabling operating systems and applications designed for one architecture (e.g., aarch64) to run on systems with differing architectures (e.g., amd64). When used in conjunction with `KVM`, it can run virtual machines at near-native speed (provided the guest virtual machine matches the host architecture) by utilising hardware extensions like Intel VT-x or AMD-V. +2. `KVM` is a Linux kernel module that enables the kernel to function as a type-1 hypervisor. `KVM` runs directly on the underlying hardware (as opposed to on top of the GNU/Linux host OS). For many workloads, the performance overhead is minimal, often in the range of 2-5%. `KVM` requires a CPU with hardware virtualisation extensions. +3. `libvirt` is an open-source API, daemon, and management tool for orchestrating platform virtualisation. It provides a consistent and stable interface for managing various virtualisation technologies, including `KVM` and `QEMU` (as well as others). `libvirt` offers a wide range of functionality to control the lifecycle of virtual machines, storage, networks, and interfaces, making it easier to interact with virtualisation capabilities programmatically or via command-line tools. +4. `virt-manager` (Virtual Machine Manager) is a GUI desktop application that provides an easy-to-use interface for creating, configuring and controlling virtual machines. `virt-manager` utilises `libvirt` as a backend. + +Together, these components form a powerful and flexible virtualization stack, with `KVM` providing low-level kernel-based virtualisation capabilities, `QEMU` providing high-level userspace-based virtualisation functionality, `libvirt` managing the resources and `virt-manager` offering an intuitive graphical management interface. + +

+ +

+ +## Prerequisites +1. Ensure your CPU supports hardware virtualisation extensions by [reading this article](https://wiki.archlinux.org/title/KVM). + +2. Install all dependencies by installing `virt-manager`. This will ensure that your package manager automatically installs all the necessary components. + ```bash + sudo apt install virt-manager # Debian/Ubuntu + sudo dnf install virt-manager # Fedora/RHEL + sudo pacman -S virt-manager # Arch Linux + sudo emerge app-emulation/virt-manager # Gentoo Linux + ``` + +3. Download a [Windows 10](https://www.microsoft.com/software-download/windows10ISO) or [Windows 11](https://www.microsoft.com/software-download/windows11) installation `.ISO` image. + +> [!IMPORTANT] +> 'Professional', 'Enterprise' or 'Server' editions of Windows are required to run RDP applications. Windows 'Home' will NOT suffice. + +4. Download [VirtIO drivers](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso) for the Windows virtual machine. + +> [!NOTE] +> VirtIO drivers enhance system performance and minimize overhead by enabling the Windows virtual machine to use specialised network and disk device drivers. These drivers are aware that they are operating inside a virtual machine, and cooperate with the hypervisor. This approach eliminates the need for the hypervisor to emulate physical hardware devices, which is a computationally expensive process. This setup allows guests to achieve high-performance network and disk operations, leveraging the benefits of paravirtualisation. +> You can read more about `VirtIO` [here](https://wiki.libvirt.org/Virtio.html). + +## Creating a Windows VM +> [!NOTE] If you are an expert user, you may wish to: +> - [Define a Windows virtual machine from an existing `.XML` file](#defining-windows-vm-from-xml) +> - [Configure Rootless `libvirt`](#configuring-rootless-libvirt) + +1. Open `virt-manager`. + +> [!NOTE] +> The name given to the application can vary between GNU/Linux distributions (e.g., 'Virtual Machines', 'Virtual Machine Manager', etc.) + +

+ +

+ +2. Navigate to `Edit`→`Preferences`. Ensure `Enable XML editing` is enabled, then click the `Close` button. + +

+ +

+ +3. Create a new virtual machine by clicking the `+` button. + +

+ Creating a new virtual machine in 'virt-manager' +

+ +4. Choose `Local install media` and click `Forward`. + +

+ +

+ +5. Select the location of your Windows 10 or 11 `.ISO` by clicking `Browse...` and `Browse Local`. Ensure `Automatically detect from the installation media / source` is enabled. + +

+ + +

+ +5. Configure the RAM and CPU cores allocated to the Windows virtual machine. We recommend `2` CPUs and `4096MB` of RAM. We will use the `VirtIO` Memory Ballooning service, which means the virtual machine can use up to `4096MB` of memory, but it will only consume this amount if necessary. + +

+ +

+ +6. Configure the virtual disk by setting its maximum size. While this size represents the largest it can grow to, the disk will only use this space as needed. + +

+ +

+ +7. Name your virtual machine `RDPWindows` to ensure it is recognized by WinApps, and select the option to `Customize configuration before installation`. + +

+ +

+ +8. After clicking `Finish`, select `Copy host CPU configuration` under 'CPUs', and then click `Apply`. + +> [!NOTE] +> Sometimes this feature gets disabled after installing Windows. Make sure to check and re-enable this option after the installation is complete. + +

+ +

+ +9. (Optional) Configure 'CPU pinning' by following [this excellent guide](https://wiki.archlinux.org/title/PCI_passthrough_via_OVMF#CPU_pinning). + +> [!NOTE] +> CPU pinning involves assigning specific physical CPU cores to a virtual machine. This can improve performance by reducing context switching and ensuring that the VM's workload consistently uses the same cores, leading to better CPU cache utilisation. + +5. Navigate to the `XML` tab, and edit the `` section to disable all timers except for the hypervclock, thereby drastically reducing idle CPU usage. Once changed, click `Apply`. + ```xml + + + + + + + + ``` + +

+ +

+ +6. Enable Hyper-V enlightenments by adding the following to the `` section. Once changed, click `Apply`. + + ```xml + + + + + + + + + + + + + + + + ``` + +> [!NOTE] +> Hyper-V enlightenments make Windows (and other Hyper-V guests) think they are running on top of a Hyper-V compatible hypervisor. This enables use of Hyper-V specific features, allowing `KVM` to implement paravirtualised interfaces for improved virtual machine performance. + +7. In the 'Memory' section, set the `Current allocation` to the minimum amount of memory you want the virtual machine to use, with a recommended value of `1024MB`. + +

+ +

+ +8. (Optional) Under `Boot Options`, enable `Start virtual machine on host boot up`. + +

+ +

+ +9. Navigate to 'SATA Disk 1' and set the `Disk bus` type to `VirtIO`. This allows disk access to be paravirtualised, improving virtual machine performance. + +

+ +

+ +10. Navigate to 'NIC' and set the `Device model` type to `virtio` to enable paravirtualised networking. + +

+ +

+ +11. Click the `Add Hardware` button in the lower left, and choose `Storage`. For `Device type`, select `CDROM device` and choose the VirtIO driver `.ISO` you downloaded earlier. Click `Finish` to add the new CD-ROM device. + +> [!IMPORTANT] +> If you skip this step, the Windows installer will fail to recognise and list the virtual hard drive you created earlier. + +

+ +

+ +12. Click `Begin Installation` in the top left. + +

+ +

+ +### Example `.XML` File +Below is an example `.XML` file that describes a Windows 11 virtual machine. + +```xml + + RDPWindows + 4d76e36e-c632-43e0-83c0-dc9f36c2823a + + + + + + 8388608 + 8388608 + 4 + + + + + + + + hvm + + + + + /usr/share/edk2/ovmf/OVMF_CODE_4M.secboot.qcow2 + /var/lib/libvirt/qemu/nvram/RDPWindows_VARS.qcow2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + destroy + restart + destroy + + + + + + /usr/bin/qemu-system-x86_64 + + + + +
+ + + + + +
+ + +
+ + + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + +
+ + +
+ + + + + +
+ + + + + + + + + + + +
+ + +
+ + + + + + + + + + + +
+ +