mirror of
https://github.com/winapps-org/winapps.git
synced 2025-06-22 06:03:02 +02:00
Merge pull request #175 from KernelGhost/main
Added ability to automatically pause and resume Windows.
This commit is contained in:
commit
89759b2a38
104
README.md
104
README.md
@ -299,16 +299,104 @@ sudo flatpak override --filesystem=home com.freerdp.FreeRDP # To use `+home-driv
|
||||
### Step 3: Create a WinApps Configuration File
|
||||
Create a configuration file at `~/.config/winapps/winapps.conf` containing the following:
|
||||
```bash
|
||||
##################################
|
||||
# WINAPPS CONFIGURATION FILE #
|
||||
##################################
|
||||
|
||||
# INSTRUCTIONS
|
||||
# - Leading and trailing whitespace are ignored.
|
||||
# - Empty lines are ignored.
|
||||
# - Lines starting with '#' are ignored.
|
||||
# - All characters following a '#' are ignored.
|
||||
|
||||
# [WINDOWS USERNAME]
|
||||
RDP_USER="MyWindowsUser"
|
||||
|
||||
# [WINDOWS PASSWORD]
|
||||
RDP_PASS="MyWindowsPassword"
|
||||
#RDP_DOMAIN="MYDOMAIN"
|
||||
#RDP_IP="192.168.123.111"
|
||||
#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"
|
||||
#FREERDP_COMMAND="xfreerdp"
|
||||
|
||||
# [WINDOWS DOMAIN]
|
||||
# DEFAULT VALUE: '' (BLANK)
|
||||
RDP_DOMAIN=""
|
||||
|
||||
# [WINDOWS IPV4 ADDRESS]
|
||||
# NOTES:
|
||||
# - If using 'libvirt', 'RDP_IP' will be determined by WinApps at runtime if left unspecified.
|
||||
# DEFAULT VALUE:
|
||||
# - 'docker': '127.0.0.1'
|
||||
# - 'podman': '127.0.0.1'
|
||||
# - 'libvirt': '' (BLANK)
|
||||
RDP_IP=""
|
||||
|
||||
# [WINAPPS BACKEND]
|
||||
# DEFAULT VALUE: 'docker'
|
||||
# VALID VALUES:
|
||||
# - 'docker'
|
||||
# - 'podman'
|
||||
# - 'libvirt'
|
||||
WAFLAVOR="docker"
|
||||
|
||||
# [DISPLAY SCALING FACTOR]
|
||||
# NOTES:
|
||||
# - If an unsupported value is specified, a warning will be displayed.
|
||||
# - If an unsupported value is specified, WinApps will use the closest supported value.
|
||||
# DEFAULT VALUE: '100'
|
||||
# VALID VALUES:
|
||||
# - '100'
|
||||
# - '140'
|
||||
# - '180'
|
||||
RDP_SCALE="100"
|
||||
|
||||
# [ADDITIONAL FREERDP FLAGS & ARGUMENTS]
|
||||
# DEFAULT VALUE: '' (BLANK)
|
||||
# VALID VALUES: See https://github.com/awakecoding/FreeRDP-Manuals/blob/master/User/FreeRDP-User-Manual.markdown
|
||||
RDP_FLAGS=""
|
||||
|
||||
# [MULTIPLE MONITORS]
|
||||
# NOTES:
|
||||
# - If enabled, a FreeRDP bug *might* produce a black screen.
|
||||
# DEFAULT VALUE: 'false'
|
||||
# VALID VALUES:
|
||||
# - 'true'
|
||||
# - 'false'
|
||||
MULTIMON="false"
|
||||
|
||||
# [DEBUG WINAPPS]
|
||||
# NOTES:
|
||||
# - Creates and appends to ~/.local/share/winapps/winapps.log when running WinApps.
|
||||
# DEFAULT VALUE: 'true'
|
||||
# VALID VALUES:
|
||||
# - 'true'
|
||||
# - 'false'
|
||||
DEBUG="true"
|
||||
|
||||
# [AUTOMATICALLY PAUSE WINDOWS]
|
||||
# NOTES:
|
||||
# - This is currently INCOMPATIBLE with 'docker'.
|
||||
# - See https://github.com/dockur/windows/issues/674
|
||||
# DEFAULT VALUE: 'off'
|
||||
# VALID VALUES:
|
||||
# - 'on'
|
||||
# - 'off'
|
||||
AUTOPAUSE="off"
|
||||
|
||||
# [AUTOMATICALLY PAUSE WINDOWS TIMEOUT]
|
||||
# NOTES:
|
||||
# - This setting determines the duration of inactivity to tolerate before Windows is automatically paused.
|
||||
# - This setting is ignored if 'AUTOPAUSE' is set to 'off'.
|
||||
# - The value must be specified in seconds (to the nearest 10 seconds e.g., '30', '40', '50', etc.).
|
||||
# - For RemoteApp RDP sessions, there is a mandatory 20-second delay, so the minimum value that can be specified here is '20'.
|
||||
# - Source: https://techcommunity.microsoft.com/t5/security-compliance-and-identity/terminal-services-remoteapp-8482-session-termination-logic/ba-p/246566
|
||||
# DEFAULT VALUE: '300'
|
||||
# VALID VALUES: >=20
|
||||
AUTOPAUSE_TIME="300"
|
||||
|
||||
# [FREERDP COMMAND]
|
||||
# NOTES:
|
||||
# - WinApps will attempt to automatically detect the correct command to use for your system.
|
||||
# DEFAULT VALUE: '' (BLANK)
|
||||
# VALID VALUES: The command required to run FreeRDPv3 on your system (e.g., 'xfreerdp', 'xfreerdp3', etc.).
|
||||
FREERDP_COMMAND=""
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
|
359
bin/winapps
359
bin/winapps
@ -1,19 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
### GLOBAL CONSTANTS ###
|
||||
# ANSI ESCAPE SEQUENCES
|
||||
readonly ERROR_TEXT="\033[1;41;37m" # Bold + White + Red Background
|
||||
readonly CLEAR_TEXT="\033[0m" # Clear
|
||||
|
||||
# ERROR CODES
|
||||
readonly EC_MISSING_CONFIG=1
|
||||
readonly EC_MISSING_FREERDP=2
|
||||
readonly EC_NOT_IN_GROUP=3
|
||||
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
|
||||
readonly EC_FAIL_START=4
|
||||
readonly EC_FAIL_RESUME=5
|
||||
readonly EC_FAIL_DESTROY=6
|
||||
readonly EC_SD_TIMEOUT=7
|
||||
readonly EC_DIE_TIMEOUT=8
|
||||
readonly EC_RESTART_TIMEOUT=9
|
||||
readonly EC_NOT_EXIST=10
|
||||
readonly EC_UNKNOWN=11
|
||||
readonly EC_NO_IP=12
|
||||
readonly EC_BAD_PORT=13
|
||||
readonly EC_UNSUPPORTED_APP=14
|
||||
readonly EC_INVALID_FLAVOR=15
|
||||
|
||||
# PATHS
|
||||
readonly APPDATA_PATH="${HOME}/.local/share/winapps"
|
||||
@ -21,11 +24,13 @@ readonly SYS_APP_PATH="/usr/local/share/winapps"
|
||||
readonly LASTRUN_PATH="${APPDATA_PATH}/lastrun"
|
||||
readonly LOG_PATH="${APPDATA_PATH}/winapps.log"
|
||||
readonly CONFIG_PATH="${HOME}/.config/winapps/winapps.conf"
|
||||
readonly COMPOSE_PATH="${HOME}/.config/winapps/compose.yaml"
|
||||
# shellcheck disable=SC2155 # Silence warnings regarding masking return values through simultaneous declaration and assignment.
|
||||
readonly SCRIPT_DIR_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
|
||||
|
||||
# OTHER
|
||||
readonly VM_NAME="RDPWindows" # FOR 'libvirt' ONLY
|
||||
readonly CONTAINER_NAME="WinApps" # FOR 'docker' AND 'podman' 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.
|
||||
@ -41,11 +46,33 @@ WAFLAVOR="docker"
|
||||
RDP_FLAGS=""
|
||||
FREERDP_COMMAND=""
|
||||
RDP_SCALE=100
|
||||
AUTOPAUSE="off"
|
||||
AUTOPAUSE_TIME="300"
|
||||
MULTIMON="false"
|
||||
DEBUG="true"
|
||||
MULTI_FLAG=""
|
||||
|
||||
# OTHER
|
||||
FREERDP_PID=-1
|
||||
|
||||
### TRAPS ###
|
||||
# Catch SIGINT (CTRL+C) to call 'waCleanUp'.
|
||||
trap waCleanUp SIGINT
|
||||
|
||||
### FUNCTIONS ###
|
||||
# Name: 'waCleanUp'
|
||||
# Role: Clean up remains prior to exit.
|
||||
waCleanUp() {
|
||||
# Kill FreeRDP.
|
||||
[ "$FREERDP_PID" -gt 0 ] && kill -9 "$FREERDP_PID" &>/dev/null
|
||||
|
||||
# Remove '.cproc' file.
|
||||
[ -f "${APPDATA_PATH}/FreeRDP_Process_${FREERDP_PID}.cproc" ] && rm "${APPDATA_PATH}/FreeRDP_Process_${FREERDP_PID}.cproc" &>/dev/null
|
||||
|
||||
# Terminate script.
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Name: 'waThrowExit'
|
||||
# Role: Throw an error message and exit the script.
|
||||
function waThrowExit() {
|
||||
@ -57,52 +84,70 @@ function waThrowExit() {
|
||||
"$EC_MISSING_CONFIG")
|
||||
# Missing WinApps configuration file.
|
||||
dprint "ERROR: MISSING WINAPPS CONFIGURATION FILE. EXITING."
|
||||
echo -e "${ERROR_TEXT}ERROR: MISSING WINAPPS CONFIGURATION FILE.${CLEAR_TEXT}"
|
||||
echo "Please create a WinApps configuration file at '${CONFIG_PATH}'".
|
||||
notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "The WinApps configuration file is missing.\nPlease create a WinApps configuration file at '${CONFIG_PATH}'."
|
||||
;;
|
||||
"$EC_MISSING_FREERDP")
|
||||
dprint "ERROR: FREERDP VERSION 3 IS NOT INSTALLED. EXITING."
|
||||
echo -e "${ERROR_TEXT}ERROR: FREERDP VERSION 3 IS NOT INSTALLED.${CLEAR_TEXT}"
|
||||
notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "FreeRDP version 3 is not installed."
|
||||
;;
|
||||
"$EC_NOT_IN_GROUP")
|
||||
dprint "ERROR: USER NOT PART OF REQUIRED GROUPS. EXITING."
|
||||
echo -e "${ERROR_TEXT}ERROR: USER NOT PART OF REQUIRED GROUPS.${CLEAR_TEXT}"
|
||||
echo "Please run:"
|
||||
echo " sudo usermod -a -G libvirt $(whoami)"
|
||||
echo " sudo usermod -a -G kvm $(whoami)"
|
||||
notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "The user $(whoami) is not part of the required groups.
|
||||
Please run:
|
||||
sudo usermod -a -G libvirt $(whoami)
|
||||
sudo usermod -a -G kvm $(whoami)"
|
||||
;;
|
||||
"$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_FAIL_START")
|
||||
dprint "ERROR: WINDOWS FAILED TO START. EXITING."
|
||||
notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Windows failed to start."
|
||||
;;
|
||||
"$EC_FAIL_RESUME")
|
||||
dprint "ERROR: WINDOWS FAILED TO RESUME. EXITING."
|
||||
notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Windows failed to resume."
|
||||
;;
|
||||
"$EC_FAIL_DESTROY")
|
||||
dprint "ERROR: WINDOWS FAILED TO IMMEDIATELY UNGRACEFULLY SHUT DOWN WINDOWS. EXITING."
|
||||
notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Failed to ungracefully shut down Windows."
|
||||
;;
|
||||
"$EC_SD_TIMEOUT")
|
||||
dprint "ERROR: WINDOWS TOOK TOO LONG TO SHUT DOWN. EXITING."
|
||||
notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Windows took too long to shut down."
|
||||
;;
|
||||
"$EC_DIE_TIMEOUT")
|
||||
dprint "ERROR: WINDOWS TOOK TOO LONG TO SHUT DOWN. EXITING."
|
||||
notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Windows took too long to die."
|
||||
;;
|
||||
"$EC_RESTART_TIMEOUT")
|
||||
dprint "ERROR: WINDOWS TOOK TOO LONG TO RESTART. EXITING."
|
||||
notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Windows took too long to restart."
|
||||
;;
|
||||
"$EC_NOT_EXIST")
|
||||
dprint "ERROR: WINDOWS NONEXISTENT. EXITING."
|
||||
notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Windows does not exist."
|
||||
;;
|
||||
"$EC_UNKNOWN")
|
||||
dprint "ERROR: UNKNOWN CONTAINER ERROR. EXITING."
|
||||
notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Unknown Windows container error."
|
||||
;;
|
||||
"$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."
|
||||
notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Windows is unreachable.\nPlease ensure Windows is assigned an IP address."
|
||||
;;
|
||||
"$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 Windows."
|
||||
notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "The Windows RDP port '${RDP_PORT}' is closed.\nPlease ensure Remote Desktop is correctly configured on Windows."
|
||||
;;
|
||||
"$EC_UNSUPPORTED_APP")
|
||||
dprint "ERROR: APPLICATION NOT FOUND. EXITING."
|
||||
echo -e "${ERROR_TEXT}ERROR: APPLICATION NOT FOUND.${CLEAR_TEXT}"
|
||||
echo "Please ensure the program is correctly configured as an officially supported application."
|
||||
notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Application not found.\nPlease ensure the program is correctly configured as an officially supported application."
|
||||
;;
|
||||
"$EC_INVALID_FLAVOR")
|
||||
dprint "ERROR: INVALID FLAVOR. EXITING."
|
||||
echo -e "${ERROR_TEXT}ERROR: INVALID FLAVOR.${CLEAR_TEXT}"
|
||||
echo "Please ensure 'docker', 'podman' or 'libvirt' are specified as the flavor in the WinApps configuration file."
|
||||
notify-send --expire-time=8000 --icon="dialog-error" --app-name="WinApps" --urgency="low" "WinApps" "Invalid WinApps flavor.\nPlease ensure 'docker', 'podman' or 'libvirt' are specified as the flavor in the WinApps configuration file."
|
||||
;;
|
||||
esac
|
||||
|
||||
# Provide generic advice.
|
||||
echo "Check the WinApps project README for more information."
|
||||
|
||||
# Terminate the script.
|
||||
echo "Exiting with status '${ERR_CODE}'."
|
||||
exit "$ERR_CODE"
|
||||
}
|
||||
|
||||
@ -142,7 +187,7 @@ function waFixScale() {
|
||||
|
||||
# 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}'."
|
||||
notify-send --expire-time=4000 --icon="dialog-warning" --app-name="WinApps" --urgency="low" "WinApps" "Unsupported RDP_SCALE value '${OLD_SCALE}'.\nDefaulting to '${RDP_SCALE}'."
|
||||
fi
|
||||
}
|
||||
|
||||
@ -162,6 +207,13 @@ function waLoadConfig() {
|
||||
|
||||
# Update $RDP_SCALE.
|
||||
waFixScale
|
||||
|
||||
# Update $AUTOPAUSE_TIME.
|
||||
# RemoteApp RDP sessions take, at minimum, 20 seconds to be terminated by the Windows server.
|
||||
# Hence, subtract 20 from the timeout specified by the user, as a 'built in' timeout of 20 seconds will occur.
|
||||
# Source: https://techcommunity.microsoft.com/t5/security-compliance-and-identity/terminal-services-remoteapp-8482-session-termination-logic/ba-p/246566
|
||||
AUTOPAUSE_TIME=$((AUTOPAUSE_TIME - 20))
|
||||
AUTOPAUSE_TIME=$((AUTOPAUSE_TIME < 0 ? 0 : AUTOPAUSE_TIME))
|
||||
}
|
||||
|
||||
# Name: 'waLastRun'
|
||||
@ -245,32 +297,163 @@ function waCheckGroupMembership() {
|
||||
}
|
||||
|
||||
# Name: 'waCheckVMRunning'
|
||||
# Role: Throw an error if the Windows 'libvirt' VM is not running.
|
||||
# Role: Check if the Windows 'libvirt' VM is running, and attempt to start it if it is not.
|
||||
function waCheckVMRunning() {
|
||||
! virsh list --state-running --name | grep -q "^${VM_NAME}$" && waThrowExit "$EC_NOT_RUNNING"
|
||||
# Declare exit status variable.
|
||||
local EXIT_STATUS=0
|
||||
|
||||
# Declare timer variables.
|
||||
local TIME_ELAPSED=0
|
||||
local TIME_LIMIT=60
|
||||
local TIME_INTERVAL=5
|
||||
|
||||
# Attempt to run the Windows virtual machine.
|
||||
# Note: States 'running' and 'idle' do not require intervention, and are not checked for.
|
||||
if (virsh list --all --name | xargs | grep -wq "$VM_NAME"); then
|
||||
if (virsh list --state-shutoff --name | xargs | grep -wq "$VM_NAME"); then
|
||||
dprint "WINDOWS SHUT OFF. BOOTING WINDOWS."
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Booting Windows."
|
||||
virsh start "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_START
|
||||
if (virsh list --state-paused --name | xargs | grep -wq "$VM_NAME"); then
|
||||
dprint "WINDOWS PAUSED. RESUMING WINDOWS."
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Resuming Windows."
|
||||
virsh resume "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_RESUME
|
||||
fi
|
||||
elif (virsh list --state-paused --name | xargs | grep -wq "$VM_NAME"); then
|
||||
dprint "WINDOWS PAUSED. RESUMING WINDOWS."
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Resuming Windows."
|
||||
virsh resume "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_RESUME
|
||||
elif (virsh list --state-other --name | xargs | grep -wq "$VM_NAME"); then
|
||||
if (virsh domstate "$VM_NAME" | xargs | grep -wq "shutdown"); then
|
||||
dprint "WINDOWS SHUTTING DOWN. WAITING."
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Windows is currently shutting down.\nIt will automatically restart once the shutdown process is complete."
|
||||
EXIT_STATUS=$EC_SD_TIMEOUT
|
||||
while (( TIME_ELAPSED < TIME_LIMIT )); do
|
||||
if (virsh list --state-shutoff --name | xargs | grep -wq "$VM_NAME"); then
|
||||
EXIT_STATUS=0
|
||||
dprint "WINDOWS SHUT OFF. BOOTING WINDOWS."
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Booting Windows."
|
||||
virsh start "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_START
|
||||
break
|
||||
fi
|
||||
sleep $TIME_INTERVAL
|
||||
TIME_ELAPSED=$((TIME_ELAPSED + TIME_INTERVAL))
|
||||
done
|
||||
elif (virsh domstate "$VM_NAME" | xargs | grep -wq "crashed"); then
|
||||
dprint "WINDOWS CRASHED. DESTROYING WINDOWS."
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Windows experienced an unexpected crash.\nAttempting to restart Windows."
|
||||
virsh destroy "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_DESTROY
|
||||
if [ "$EXIT_STATUS" -eq 0 ]; then
|
||||
dprint "WINDOWS DESTROYED. BOOTING WINDOWS."
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Booting Windows."
|
||||
virsh start "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_START
|
||||
fi
|
||||
elif (virsh domstate "$VM_NAME" | xargs | grep -wq "dying"); then
|
||||
dprint "WINDOWS DYING. WAITING."
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Windows is currently shutting down unexpectedly.\nIt will try to restart once the shutdown process finishes."
|
||||
EXIT_STATUS=$EC_DIE_TIMEOUT
|
||||
while (( TIME_ELAPSED < TIME_LIMIT )); do
|
||||
if (virsh domstate "$VM_NAME" | xargs | grep -wq "crashed"); then
|
||||
EXIT_STATUS=0
|
||||
dprint "WINDOWS CRASHED. DESTROYING WINDOWS."
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Windows experienced an unexpected crash.\nAttempting to restart Windows."
|
||||
virsh destroy "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_DESTROY
|
||||
if [ "$EXIT_STATUS" -eq 0 ]; then
|
||||
dprint "WINDOWS DESTROYED. BOOTING WINDOWS."
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Booting Windows."
|
||||
virsh start "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_START
|
||||
fi
|
||||
break
|
||||
elif (virsh list --state-shutoff --name | xargs | grep -wq "$VM_NAME"); then
|
||||
EXIT_STATUS=0
|
||||
dprint "WINDOWS SHUT OFF. BOOTING WINDOWS."
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Booting Windows."
|
||||
virsh start "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_START
|
||||
break
|
||||
fi
|
||||
sleep $TIME_INTERVAL
|
||||
TIME_ELAPSED=$((TIME_ELAPSED + TIME_INTERVAL))
|
||||
done
|
||||
elif (virsh domstate "$VM_NAME" | xargs | grep -wq "pmsuspended" ); then
|
||||
dprint "WINDOWS SUSPENDED. RESUMING WINDOWS."
|
||||
virsh resume "$VM_NAME" &>/dev/null || EXIT_STATUS=$EC_FAIL_RESUME
|
||||
fi
|
||||
fi
|
||||
else
|
||||
EXIT_STATUS=$EC_NOT_EXIST
|
||||
fi
|
||||
|
||||
# Handle non-zero exit statuses.
|
||||
[ "$EXIT_STATUS" -ne 0 ] && waThrowExit "$EXIT_STATUS"
|
||||
}
|
||||
|
||||
# Name: 'waCheckContainerRunning'
|
||||
# Role: Throw an error if the Docker container is not running.
|
||||
function waCheckContainerRunning() {
|
||||
# Declare variables.
|
||||
local EXIT_STATUS=0
|
||||
local CONTAINER_STATE=""
|
||||
local COMPOSE_COMMAND=""
|
||||
local TIME_ELAPSED=0
|
||||
local TIME_LIMIT=60
|
||||
local TIME_INTERVAL=5
|
||||
|
||||
# Determine container state (docker).
|
||||
if command -v docker &>/dev/null; then
|
||||
CONTAINER_STATE=$(docker ps --filter name="WinApps" --format '{{.Status}}')
|
||||
fi
|
||||
# Determine the state of the container.
|
||||
CONTAINER_STATE=$("$WAFLAVOR" inspect --format='{{.State.Status}}' "$CONTAINER_NAME")
|
||||
|
||||
# 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.
|
||||
# Determine the compose command.
|
||||
case "$WAFLAVOR" in
|
||||
"docker") COMPOSE_COMMAND="docker compose" ;;
|
||||
"podman") COMPOSE_COMMAND="podman-compose" ;;
|
||||
esac
|
||||
|
||||
# Check container state.
|
||||
[[ "$CONTAINER_STATE" != "up" ]] && waThrowExit "$EC_NOT_RUNNING"
|
||||
# Note: Errors DO NOT result in non-zero exit statuses.
|
||||
# Docker: 'created', 'restarting', 'running', 'removing', 'paused', 'exited' or 'dead'.
|
||||
# Podman: 'created', 'running', 'paused', 'exited' or 'unknown'.
|
||||
case "$CONTAINER_STATE" in
|
||||
"created")
|
||||
dprint "WINDOWS CREATED. BOOTING WINDOWS."
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Booting Windows."
|
||||
$COMPOSE_COMMAND --file "$COMPOSE_PATH" start &>/dev/null
|
||||
;;
|
||||
"restarting")
|
||||
dprint "WINDOWS RESTARTING. WAITING."
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Windows is currently restarting. Please wait."
|
||||
EXIT_STATUS=$EC_RESTART_TIMEOUT
|
||||
while (( TIME_ELAPSED < TIME_LIMIT )); do
|
||||
if [[ $("$WAFLAVOR" inspect --format='{{.State.Status}}' "$CONTAINER_NAME") == "running" ]]; then
|
||||
EXIT_STATUS=0
|
||||
dprint "WINDOWS RESTARTED."
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Restarted Windows."
|
||||
break
|
||||
fi
|
||||
sleep $TIME_INTERVAL
|
||||
TIME_ELAPSED=$((TIME_ELAPSED + TIME_INTERVAL))
|
||||
done
|
||||
;;
|
||||
"paused")
|
||||
dprint "WINDOWS PAUSED. RESUMING WINDOWS."
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Resuming Windows."
|
||||
$COMPOSE_COMMAND --file "$COMPOSE_PATH" unpause &>/dev/null
|
||||
;;
|
||||
"exited")
|
||||
dprint "WINDOWS SHUT OFF. BOOTING WINDOWS."
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Booting Windows."
|
||||
$COMPOSE_COMMAND --file "$COMPOSE_PATH" start &>/dev/null
|
||||
;;
|
||||
"dead")
|
||||
dprint "WINDOWS DEAD. RECREATING WINDOWS CONTAINER."
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Re-creating and booting Windows."
|
||||
$COMPOSE_COMMAND --file "$COMPOSE_PATH" down &>/dev/null && $COMPOSE_COMMAND --file "$COMPOSE_PATH" up -d &>/dev/null
|
||||
;;
|
||||
"unknown")
|
||||
EXIT_STATUS=$EC_UNKNOWN
|
||||
;;
|
||||
esac
|
||||
|
||||
# Handle non-zero exit statuses.
|
||||
[ "$EXIT_STATUS" -ne 0 ] && waThrowExit "$EXIT_STATUS"
|
||||
}
|
||||
|
||||
# Name: 'waCheckPortOpen'
|
||||
@ -278,17 +461,30 @@ function waCheckContainerRunning() {
|
||||
function waCheckPortOpen() {
|
||||
# Declare variables.
|
||||
local VM_MAC="" # Stores the MAC address of the Windows VM.
|
||||
local TIME_ELAPSED=0
|
||||
local TIME_LIMIT=30
|
||||
local TIME_INTERVAL=5
|
||||
|
||||
# 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.
|
||||
|
||||
while (( TIME_ELAPSED < TIME_LIMIT )); do
|
||||
if [ "$TIME_ELAPSED" -eq "$TIME_INTERVAL" ]; then
|
||||
notify-send --expire-time=4000 --icon="dialog-info" --app-name="WinApps" --urgency="low" "WinApps" "Requesting Windows IP address..."
|
||||
fi
|
||||
RDP_IP=$(ip neigh show | grep "$VM_MAC" | grep -oE "([0-9]{1,3}\.){3}[0-9]{1,3}") # VM IP address.
|
||||
[ -n "$RDP_IP" ] && break
|
||||
sleep $TIME_INTERVAL
|
||||
TIME_ELAPSED=$((TIME_ELAPSED + TIME_INTERVAL))
|
||||
done
|
||||
|
||||
[ -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_BAD_PORT"
|
||||
timeout 10 nc -z "$RDP_IP" "$RDP_PORT" &>/dev/null || waThrowExit "$EC_BAD_PORT"
|
||||
}
|
||||
|
||||
# Name: 'waRunCommand'
|
||||
@ -300,6 +496,9 @@ function waRunCommand() {
|
||||
|
||||
# Run option.
|
||||
if [ "$1" = "windows" ]; then
|
||||
# Update timeout (since there is no 'in-built' 20 second delay for full RDP sessions post-logout).
|
||||
AUTOPAUSE_TIME=$((AUTOPAUSE_TIME + 20))
|
||||
|
||||
# Open Windows RDP session.
|
||||
dprint "WINDOWS"
|
||||
$FREERDP_COMMAND \
|
||||
@ -314,6 +513,9 @@ function waRunCommand() {
|
||||
/wm-class:"Microsoft Windows" \
|
||||
/t:"Windows RDP Session [$RDP_IP]" \
|
||||
/v:"$RDP_IP" &>/dev/null &
|
||||
|
||||
# Capture the process ID.
|
||||
FREERDP_PID=$!
|
||||
elif [ "$1" = "manual" ]; then
|
||||
# Open specified application.
|
||||
dprint "MANUAL: ${2}"
|
||||
@ -332,6 +534,9 @@ function waRunCommand() {
|
||||
"$MULTI_FLAG" \
|
||||
/app:program:"$2" \
|
||||
/v:"$RDP_IP" &>/dev/null &
|
||||
|
||||
# Capture the process ID.
|
||||
FREERDP_PID=$!
|
||||
else
|
||||
# Script summoned from right-click menu with officially supported application name plus/minus a file path.
|
||||
if [ -e "${SCRIPT_DIR_PATH}/../apps/${1}/info" ]; then
|
||||
@ -368,6 +573,9 @@ function waRunCommand() {
|
||||
/wm-class:"$FULL_NAME" \
|
||||
/app:program:"$WIN_EXECUTABLE",icon:"$ICON",name:"$FULL_NAME" \
|
||||
/v:"$RDP_IP" &>/dev/null &
|
||||
|
||||
# Capture the process ID.
|
||||
FREERDP_PID=$!
|
||||
else
|
||||
# Convert path from UNIX to Windows style.
|
||||
FILE_PATH=$(echo "$2" | sed \
|
||||
@ -392,6 +600,55 @@ function waRunCommand() {
|
||||
/wm-class:"$FULL_NAME" \
|
||||
/app:program:"$WIN_EXECUTABLE",icon:"$ICON",name:$"FULL_NAME",cmd:\""$FILE_PATH"\" \
|
||||
/v:"$RDP_IP" &>/dev/null &
|
||||
|
||||
# Capture the process ID.
|
||||
FREERDP_PID=$!
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$FREERDP_PID" -ne -1 ]; then
|
||||
# Create a file with the process ID.
|
||||
touch "${APPDATA_PATH}/FreeRDP_Process_${FREERDP_PID}.cproc"
|
||||
|
||||
# Wait for the process to terminate.
|
||||
wait $FREERDP_PID
|
||||
|
||||
# Remove the file with the process ID.
|
||||
rm "${APPDATA_PATH}/FreeRDP_Process_${FREERDP_PID}.cproc" &>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# Name: 'waCheckIdle'
|
||||
# Role: Suspend Windows if idle.
|
||||
function waCheckIdle() {
|
||||
# Declare variables
|
||||
local TIME_INTERVAL=10
|
||||
local TIME_ELAPSED=0
|
||||
local SUSPEND_WINDOWS=0
|
||||
|
||||
# Check if there are no WinApps-related FreeRDP processes running.
|
||||
if ! ls "$APPDATA_PATH"/FreeRDP_Process_*.cproc &>/dev/null; then
|
||||
SUSPEND_WINDOWS=1
|
||||
while (( TIME_ELAPSED < AUTOPAUSE_TIME )); do
|
||||
if ls "$APPDATA_PATH"/FreeRDP_Process_*.cproc &>/dev/null; then
|
||||
SUSPEND_WINDOWS=0
|
||||
break
|
||||
fi
|
||||
sleep $TIME_INTERVAL
|
||||
TIME_ELAPSED=$((TIME_ELAPSED + TIME_INTERVAL))
|
||||
done
|
||||
fi
|
||||
|
||||
# Hibernate/Pause Windows.
|
||||
if [ "$SUSPEND_WINDOWS" -eq 1 ]; then
|
||||
dprint "IDLE FOR ${AUTOPAUSE_TIME} SECONDS. SUSPENDING WINDOWS."
|
||||
notify-send --expire-time=8000 --icon="info" --app-name="WinApps" --urgency="low" "WinApps" "Pausing Windows due to inactivity."
|
||||
if [ "$WAFLAVOR" = "docker" ]; then
|
||||
docker compose --file "$COMPOSE_PATH" pause &>/dev/null
|
||||
elif [ "$WAFLAVOR" = "podman" ]; then
|
||||
podman-compose --file "$COMPOSE_PATH" pause &>/dev/null
|
||||
elif [ "$WAFLAVOR" = "libvirt" ]; then
|
||||
virsh suspend "$VM_NAME" &>/dev/null
|
||||
fi
|
||||
fi
|
||||
}
|
||||
@ -425,4 +682,8 @@ fi
|
||||
waCheckPortOpen
|
||||
waRunCommand "$@"
|
||||
|
||||
if [[ "$AUTOPAUSE" == "on" ]]; then
|
||||
waCheckIdle
|
||||
fi
|
||||
|
||||
dprint "END"
|
||||
|
@ -26,7 +26,7 @@ Together, these components form a powerful and flexible virtualization stack, wi
|
||||
sudo emerge app-emulation/virt-manager # Gentoo Linux
|
||||
```
|
||||
|
||||
3. Configure `libvirt` to use the 'system' URI by adding the line `LIBVIRT_DEFAULT_URI="qemu:///system"` to your preferred shell profile file.
|
||||
3. Configure `libvirt` to use the 'system' URI by adding the line `LIBVIRT_DEFAULT_URI="qemu:///system"` to your preferred shell profile file (e.g., `.bashrc`, `.zshrc`, etc.).
|
||||
```bash
|
||||
echo "export LIBVIRT_DEFAULT_URI=\"qemu:///system\"" >> ~/.bashrc
|
||||
```
|
||||
|
45
installer.sh
45
installer.sh
@ -72,17 +72,6 @@ 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 WINAPPS_CONFIG="\
|
||||
RDP_USER=\"MyWindowsUser\"
|
||||
RDP_PASS=\"MyWindowsPassword\"
|
||||
#RDP_DOMAIN=\"MYDOMAIN\"
|
||||
#RDP_IP=\"192.168.123.111\"
|
||||
#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\"
|
||||
#FREERDP_COMMAND=\"xfreerdp\""
|
||||
|
||||
### GLOBAL VARIABLES ###
|
||||
# USER INPUT
|
||||
@ -100,7 +89,7 @@ WAFLAVOR="docker" # Imported variable.
|
||||
RDP_SCALE=100 # Imported variable.
|
||||
RDP_FLAGS="" # Imported variable.
|
||||
MULTIMON="false" # Imported variable.
|
||||
DEBUG="false" # Imported variable.
|
||||
DEBUG="true" # Imported variable.
|
||||
FREERDP_COMMAND="" # Imported variable.
|
||||
MULTI_FLAG="" # Set based on value of $MULTIMON.
|
||||
|
||||
@ -443,10 +432,7 @@ function waLoadConfig() {
|
||||
# Display the suggested action(s).
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
echo -e "Please create a configuration file at ${COMMAND_TEXT}${CONFIG_PATH}${CLEAR_TEXT}."
|
||||
echo -e "\nThe configuration file should contain the following:"
|
||||
echo -e "\n${COMMAND_TEXT}${WINAPPS_CONFIG}${CLEAR_TEXT}"
|
||||
echo -e "\nThe ${COMMAND_TEXT}RDP_USER${CLEAR_TEXT} and ${COMMAND_TEXT}RDP_PASS${CLEAR_TEXT} fields should contain the Windows user's account name and password."
|
||||
echo -e "Note that the Windows user's PIN combination CANNOT be used to populate ${COMMAND_TEXT}RDP_PASS${CLEAR_TEXT}."
|
||||
echo -e "See https://github.com/winapps-org/winapps?tab=readme-ov-file#step-3-create-a-winapps-configuration-file"
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
|
||||
# Terminate the script.
|
||||
@ -504,6 +490,33 @@ function waCheckInstallDependencies() {
|
||||
# Print feedback.
|
||||
echo -n "Checking whether dependencies are installed... "
|
||||
|
||||
# 'libnotify'
|
||||
if ! command -v notify-send &>/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 'libnotify' to proceed.${CLEAR_TEXT}"
|
||||
|
||||
# Display the suggested action(s).
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
echo "Debian/Ubuntu-based systems:"
|
||||
echo -e " ${COMMAND_TEXT}sudo apt install libnotify-bin${CLEAR_TEXT}"
|
||||
echo "Red Hat/Fedora-based systems:"
|
||||
echo -e " ${COMMAND_TEXT}sudo dnf install libnotify${CLEAR_TEXT}"
|
||||
echo "Arch Linux systems:"
|
||||
echo -e " ${COMMAND_TEXT}sudo pacman -S libnotify${CLEAR_TEXT}"
|
||||
echo "Gentoo Linux systems:"
|
||||
echo -e " ${COMMAND_TEXT}sudo emerge --ask x11-libs/libnotify${CLEAR_TEXT}"
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
|
||||
# Terminate the script.
|
||||
return "$EC_MISSING_DEPS"
|
||||
fi
|
||||
|
||||
# 'Netcat'
|
||||
if ! command -v nc &>/dev/null; then
|
||||
# Complete the previous line.
|
||||
|
Loading…
x
Reference in New Issue
Block a user