diff --git a/README.md b/README.md index 2818b9c..16e6bc7 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # 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. ## 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 `libvirt` and `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`](docs/docker.md) -- [Creating a Windows VM with `virt-manager`](docs/KVM.md) (Deprecated) +- [Creating a Windows VM with `Docker` or `Podman`](docs/docker.md) +- [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. @@ -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,17 +134,17 @@ 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. -- 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. @@ -160,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 881d94f..900538c 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." @@ -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 @@ -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,10 +161,7 @@ function waLoadConfig() { MULTI_FLAG=$([[ $MULTIMON == "true" ]] && echo "/multimon" || echo "+span") # Update $RDP_SCALE. - RDP_SCALE=$(waFixScale "$RDP_SCALE") - - # Append additional flags or parameters to FreeRDP. - [[ -n $RDP_FLAGS ]] && FREERDP_COMMAND="${FREERDP_COMMAND} ${RDP_FLAGS}" + waFixScale } # Name: 'waLastRun' @@ -220,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 @@ -238,9 +245,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' @@ -249,30 +256,39 @@ function waCheckContainerRunning() { # Declare variables. local CONTAINER_STATE="" - # Determine container state. - CONTAINER_STATE=$(docker ps --filter name="windows" --format '{{.Status}}') + # Determine container state (docker). + if command -v docker &>/dev/null; then + CONTAINER_STATE=$(docker ps --filter name="WinApps" --format '{{.Status}}') + 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. # 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' @@ -284,7 +300,7 @@ function waRunCommand() { # Run option. if [ "$1" = "windows" ]; then - # Open Windows VM. + # Open Windows RDP session. dprint "WINDOWS" $FREERDP_COMMAND \ /d:"$RDP_DOMAIN" \ @@ -294,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 @@ -308,6 +325,7 @@ function waRunCommand() { +auto-reconnect \ +clipboard \ +home-drive \ + /audio-mode:1 \ -wallpaper \ +dynamic-resolution \ "$MULTI_FLAG" \ @@ -342,6 +360,7 @@ function waRunCommand() { +auto-reconnect \ +clipboard \ +home-drive \ + /audio-mode:1 \ -wallpaper \ +dynamic-resolution \ "$MULTI_FLAG" \ @@ -363,6 +382,7 @@ function waRunCommand() { +auto-reconnect \ +clipboard \ +home-drive \ + /audio-mode:1 \ -wallpaper \ +dynamic-resolution \ "$MULTI_FLAG" \ @@ -384,17 +404,22 @@ 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 waCheckGroupMembership waCheckVMRunning - waCheckVMContactable else waThrowExit "$EC_INVALID_FLAVOR" fi +waCheckPortOpen waRunCommand "$@" dprint "END" diff --git a/compose.yaml b/compose.yaml index 9599920..3e77cfb 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,23 +1,41 @@ -name: "winapps" +# For documentation, FAQ, additional configuration options and technical help, visit: https://github.com/dockur/windows +name: "winapps" # Docker Compose Project Name. volumes: + # 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 - 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..1b752fd 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -1,96 +1,87 @@ -# 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). > [!NOTE] -> We don't officially support older versions than Windows 10. However, they might still work with some additional tuning. +> Ensure `WAFLAVOR` is set to `"podman"` in `~/.config/winapps/winapps.conf`. -You can now just run: - -```shell -docker compose up -d +### Installing Windows +After navigating into the cloned WinApps repository, you can initiate the Windows installation using `podman-compose`. +```bash +podman-compose up ``` -to run the VM in the background. +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. -After this, just open http://127.0.0.1:8006 in your web browser and wait for the Windows installation to finish. +### Installing WinApps +Please follow the [`docker` instructions](#installing-winapps). -> [!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: - -```shell -docker compose stop +### 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 ``` -For starting again afterward, use: - -```shell -docker compose start -``` - -(All compose commands have to be run from the directory where the `compose.yaml` is located.) +> [!NOTE] +> The above `podman-compose` commands must be run within the same directory containing `compose.yaml`. 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 + + + + +
+ + + + + +
+ + +
+ + + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + +
+ + +
+ + + + + +
+ + + + + + + + + + + +
+ + +
+ + + + + + + + + + + +
+ +