mirror of
https://github.com/LukeHagar/omarchy.git
synced 2025-12-06 04:20:23 +00:00
Add Windows VM (#2462)
* adding TUI for starting up windows VM * updating to use docker-compose * adding possibility to set windows product-key * ram and cpu settings * first time run notes * add two needed pacman packages * key and package install * Revert "add two needed pacman packages" This reverts commit 04dc96cee0d3e37103b9866d9140ba47133db7ad. * add install and remove scripts * install icon * rename scripts and menu to Windows VM * rename also launch script to windows vm * update nameing * remove quotes * fix paramter and quiting after installation * fix launch script * update * certificate acceptance and remove of desktop app * move desktop app * rename app from "Windows VM" to "Windows" * add example of sharing local share with Windows * merge 'dev' into windows-docker-vm * exchange rdesktop with freerdc * accept certificates automatically so user don't fail first try * remove certi during normal launch (handled in install process) * remove volumes and orphans to have clean state (avoids hang on login) * /cert option still needed * Simplify package installs * Fix icon + desktop to prevent creation on initial install * omarchy-windows-vm in working form * Don't need this anymore * Fix commands * Always use uwsm-app * We are generating it instead * Make omarchy-pkg-add quiet when the packages are already there * Improve the questionnaire * Show where you change resource usage * Add default Windows share --------- Co-authored-by: sspaeti <simon@ssp.sh> Co-authored-by: David Heinemeier Hansson <david@hey.com>
This commit is contained in:
BIN
applications/icons/windows.png
Normal file
BIN
applications/icons/windows.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
@@ -233,7 +233,7 @@ show_setup_security_menu() {
|
||||
}
|
||||
|
||||
show_install_menu() {
|
||||
case $(menu "Install" " Package\n AUR\n Web App\n TUI\n Service\n Style\n Development\n Editor\n Terminal\n AI\n Gaming") in
|
||||
case $(menu "Install" " Package\n AUR\n Web App\n TUI\n Service\n Style\n Development\n Editor\n Terminal\n AI\n Windows\n Gaming") in
|
||||
*Package*) terminal omarchy-pkg-install ;;
|
||||
*AUR*) terminal omarchy-pkg-aur-install ;;
|
||||
*Web*) present_terminal omarchy-webapp-install ;;
|
||||
@@ -244,6 +244,7 @@ show_install_menu() {
|
||||
*Editor*) show_install_editor_menu ;;
|
||||
*Terminal*) show_install_terminal_menu ;;
|
||||
*AI*) show_install_ai_menu ;;
|
||||
*Windows*) present_terminal "omarchy-windows-vm install" ;;
|
||||
*Gaming*) show_install_gaming_menu ;;
|
||||
*) show_main_menu ;;
|
||||
esac
|
||||
@@ -374,11 +375,12 @@ show_install_elixir_menu() {
|
||||
}
|
||||
|
||||
show_remove_menu() {
|
||||
case $(menu "Remove" " Package\n Web App\n TUI\n Theme\n Fingerprint\n Fido2") in
|
||||
case $(menu "Remove" " Package\n Web App\n TUI\n Theme\n Windows\n Fingerprint\n Fido2") in
|
||||
*Package*) terminal omarchy-pkg-remove ;;
|
||||
*Web*) present_terminal omarchy-webapp-remove ;;
|
||||
*TUI*) present_terminal omarchy-tui-remove ;;
|
||||
*Theme*) present_terminal omarchy-theme-remove ;;
|
||||
*Windows*) present_terminal "omarchy-windows-vm remove" ;;
|
||||
*Fingerprint*) present_terminal "omarchy-setup-fingerprint --remove" ;;
|
||||
*Fido2*) present_terminal "omarchy-setup-fido2 --remove" ;;
|
||||
*) show_main_menu ;;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
sudo pacman -S --noconfirm --needed "$@" || exit 1
|
||||
if omarchy-pkg-missing "$@"; then
|
||||
sudo pacman -S --noconfirm --needed "$@" || exit 1
|
||||
fi
|
||||
|
||||
for pkg in "$@"; do
|
||||
# Secondary check to handle states where pacman doesn't actually register an error
|
||||
|
||||
445
bin/omarchy-windows-vm
Executable file
445
bin/omarchy-windows-vm
Executable file
@@ -0,0 +1,445 @@
|
||||
#!/bin/bash
|
||||
COMPOSE_FILE="$HOME/.config/windows/docker-compose.yml"
|
||||
|
||||
check_prerequisites() {
|
||||
local DISK_SIZE_GB=${1:-64}
|
||||
local REQUIRED_SPACE=$((DISK_SIZE_GB + 10)) # Add 10GB for Windows ISO and overhead
|
||||
|
||||
# Check for KVM support
|
||||
if [ ! -e /dev/kvm ]; then
|
||||
gum style \
|
||||
--border normal \
|
||||
--padding "1 2" \
|
||||
--margin "1" \
|
||||
"❌ KVM virtualization not available!" \
|
||||
"" \
|
||||
"Please enable virtualization in BIOS or run:" \
|
||||
" sudo modprobe kvm-intel # for Intel CPUs" \
|
||||
" sudo modprobe kvm-amd # for AMD CPUs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check disk space
|
||||
AVAILABLE_SPACE=$(df "$HOME" | awk 'NR==2 {print int($4/1024/1024)}')
|
||||
if [ "$AVAILABLE_SPACE" -lt "$REQUIRED_SPACE" ]; then
|
||||
echo "❌ Insufficient disk space!"
|
||||
echo " Available: ${AVAILABLE_SPACE}GB"
|
||||
echo " Required: ${REQUIRED_SPACE}GB (${DISK_SIZE_GB}GB disk + 10GB for Windows image)"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
install_windows() {
|
||||
# Set up trap to handle Ctrl+C
|
||||
trap "echo ''; echo 'Installation cancelled by user'; exit 1" INT
|
||||
|
||||
check_prerequisites
|
||||
|
||||
omarchy-pkg-add freerdp openbsd-netcat gum
|
||||
|
||||
mkdir -p "$HOME/.windows"
|
||||
mkdir -p "$HOME/.config/windows"
|
||||
mkdir -p "$HOME/.local/share/applications/icons"
|
||||
|
||||
# Install Windows VM icon and desktop file
|
||||
if [ -f "$OMARCHY_PATH/applications/icons/windows.png" ]; then
|
||||
cp "$OMARCHY_PATH/applications/icons/windows.png" "$HOME/.local/share/applications/icons/windows.png"
|
||||
fi
|
||||
|
||||
cat << EOF | tee "$HOME/.local/share/applications/windows-vm.desktop" > /dev/null
|
||||
[Desktop Entry]
|
||||
Name=Windows
|
||||
Comment=Start Windows VM via Docker and connect with RDP
|
||||
Exec=uwsm app -- omarchy-windows-vm launch
|
||||
Icon=$HOME/.local/share/applications/icons/windows.png
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=System;Virtualization;
|
||||
EOF
|
||||
|
||||
# Get system resources
|
||||
TOTAL_RAM=$(free -h | grep "^Mem:" | awk '{print $2}')
|
||||
TOTAL_RAM_GB=$(free -g | grep "^Mem:" | awk '{print $2}')
|
||||
TOTAL_CORES=$(nproc)
|
||||
|
||||
echo ""
|
||||
echo "System Resources Detected:"
|
||||
echo " Total RAM: $TOTAL_RAM"
|
||||
echo " Total CPU Cores: $TOTAL_CORES"
|
||||
echo ""
|
||||
|
||||
RAM_OPTIONS=""
|
||||
for size in 2 4 8 16 32 64; do
|
||||
if [ $size -le $TOTAL_RAM_GB ]; then
|
||||
RAM_OPTIONS="$RAM_OPTIONS ${size}G"
|
||||
fi
|
||||
done
|
||||
|
||||
SELECTED_RAM=$(echo $RAM_OPTIONS | tr ' ' '\n' | gum choose --selected="4G" --header="How much RAM would you like to allocate to Windows VM?")
|
||||
|
||||
# Check if user cancelled
|
||||
if [ -z "$SELECTED_RAM" ]; then
|
||||
echo "Installation cancelled by user"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SELECTED_CORES=$(gum input --placeholder="Number of CPU cores (1-$TOTAL_CORES)" --value="2" --header="How many CPU cores would you like to allocate to Windows VM?" --char-limit=2)
|
||||
|
||||
# Check if user cancelled (Ctrl+C in gum input returns empty string)
|
||||
if [ -z "$SELECTED_CORES" ]; then
|
||||
echo "Installation cancelled by user"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [[ "$SELECTED_CORES" =~ ^[0-9]+$ ]] || [ "$SELECTED_CORES" -lt 1 ] || [ "$SELECTED_CORES" -gt "$TOTAL_CORES" ]; then
|
||||
echo "Invalid input. Using default: 2 cores"
|
||||
SELECTED_CORES=2
|
||||
fi
|
||||
|
||||
AVAILABLE_SPACE=$(df "$HOME" | awk 'NR==2 {print int($4/1024/1024)}')
|
||||
MAX_DISK_GB=$((AVAILABLE_SPACE - 10)) # Leave 10GB for Windows image
|
||||
|
||||
# Check if we have enough space for minimum
|
||||
if [ $MAX_DISK_GB -lt 32 ]; then
|
||||
echo "❌ Insufficient disk space for Windows VM!"
|
||||
echo " Available: ${AVAILABLE_SPACE}GB"
|
||||
echo " Minimum required: 42GB (32GB disk + 10GB for Windows image)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DISK_OPTIONS=""
|
||||
for size in 32 64 128 256 512; do
|
||||
if [ $size -le $MAX_DISK_GB ]; then
|
||||
DISK_OPTIONS="$DISK_OPTIONS ${size}G"
|
||||
fi
|
||||
done
|
||||
|
||||
# Default to 64G if available, otherwise 32G
|
||||
DEFAULT_DISK="64G"
|
||||
if ! echo "$DISK_OPTIONS" | grep -q "64G"; then
|
||||
DEFAULT_DISK="32G"
|
||||
fi
|
||||
|
||||
SELECTED_DISK=$(echo $DISK_OPTIONS | tr ' ' '\n' | gum choose --selected="$DEFAULT_DISK" --header="How much disk space would you like to give Windows VM? (64GB+ recommended)")
|
||||
|
||||
# Check if user cancelled
|
||||
if [ -z "$SELECTED_DISK" ]; then
|
||||
echo "Installation cancelled by user"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract just the number for prerequisite check
|
||||
DISK_SIZE_NUM=$(echo "$SELECTED_DISK" | sed 's/G//')
|
||||
|
||||
# Re-check prerequisites with selected disk size
|
||||
check_prerequisites "$DISK_SIZE_NUM"
|
||||
|
||||
# Prompt for username and password
|
||||
USERNAME=$(gum input --placeholder="Username (Press enter to use default: docker)" --header="Enter Windows username:")
|
||||
if [ -z "$USERNAME" ]; then
|
||||
USERNAME="docker"
|
||||
fi
|
||||
|
||||
PASSWORD=$(gum input --placeholder="Password (Press enter to use default: admin)" --password --header="Enter Windows password:")
|
||||
if [ -z "$PASSWORD" ]; then
|
||||
PASSWORD="admin"
|
||||
PASSWORD_DISPLAY="(default)"
|
||||
else
|
||||
PASSWORD_DISPLAY="(user-defined)"
|
||||
fi
|
||||
|
||||
# Display configuration summary
|
||||
gum style \
|
||||
--border normal \
|
||||
--padding "1 2" \
|
||||
--margin "1" \
|
||||
--align left \
|
||||
--bold \
|
||||
"Windows VM Configuration" \
|
||||
"" \
|
||||
"RAM: $SELECTED_RAM" \
|
||||
"CPU: $SELECTED_CORES cores" \
|
||||
"Disk: $SELECTED_DISK" \
|
||||
"Username: $USERNAME" \
|
||||
"Password: $PASSWORD_DISPLAY"
|
||||
|
||||
# Ask for confirmation
|
||||
echo ""
|
||||
if ! gum confirm "Proceed with this configuration?"; then
|
||||
echo "Installation cancelled by user"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p $HOME/Windows
|
||||
|
||||
# Create docker-compose.yml in user config directory
|
||||
cat << EOF | tee "$COMPOSE_FILE" > /dev/null
|
||||
services:
|
||||
windows:
|
||||
image: dockurr/windows
|
||||
container_name: omarchy-windows
|
||||
environment:
|
||||
VERSION: "11"
|
||||
RAM_SIZE: "$SELECTED_RAM"
|
||||
CPU_CORES: "$SELECTED_CORES"
|
||||
DISK_SIZE: "$SELECTED_DISK"
|
||||
USERNAME: "$USERNAME"
|
||||
PASSWORD: "$PASSWORD"
|
||||
devices:
|
||||
- /dev/kvm
|
||||
- /dev/net/tun
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
ports:
|
||||
- 8006:8006
|
||||
- 3389:3389/tcp
|
||||
- 3389:3389/udp
|
||||
volumes:
|
||||
- $HOME/.windows:/storage
|
||||
- $HOME/Windows:/shared
|
||||
restart: always
|
||||
stop_grace_period: 2m
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
echo "Starting Windows VM installation..."
|
||||
echo "This will download a Windows 11 image (may take 10-15 minutes)."
|
||||
echo ""
|
||||
echo "Monitor installation progress at: http://127.0.0.1:8006"
|
||||
echo ""
|
||||
|
||||
# Start docker-compose with user's config
|
||||
echo "Starting Windows VM with docker-compose..."
|
||||
if ! docker-compose -f "$COMPOSE_FILE" up -d 2>&1; then
|
||||
echo "❌ Failed to start Windows VM!"
|
||||
echo " Common issues:"
|
||||
echo " - Docker daemon not running: sudo systemctl start docker"
|
||||
echo " - Port already in use: check if another VM is running"
|
||||
echo " - Permission issues: make sure you're in the docker group"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Windows VM is starting up!"
|
||||
echo ""
|
||||
echo "Opening browser to monitor installation..."
|
||||
|
||||
# Open browser to monitor installation
|
||||
sleep 3
|
||||
xdg-open "http://127.0.0.1:8006"
|
||||
|
||||
echo ""
|
||||
echo "Installation is running in the background."
|
||||
echo "You can monitor progress at: http://127.0.0.1:8006"
|
||||
echo ""
|
||||
echo "Once finished, launch 'Windows' via Super + Space"
|
||||
echo ""
|
||||
echo "To stop the VM: omarchy-windows-vm stop"
|
||||
echo "To change resources: ~/.config/windows/docker-compose.yml"
|
||||
echo ""
|
||||
}
|
||||
|
||||
remove_windows() {
|
||||
echo "Removing Windows VM..."
|
||||
|
||||
docker-compose -f "$COMPOSE_FILE" down 2>/dev/null || true
|
||||
|
||||
docker rmi dockurr/windows 2>/dev/null || echo "Image already removed or not found"
|
||||
|
||||
rm "$HOME/.local/share/applications/windows-vm.desktop"
|
||||
rm -rf "$HOME/.config/windows"
|
||||
rm -rf "$HOME/.windows"
|
||||
|
||||
echo ""
|
||||
echo "Windows VM removal completed!"
|
||||
}
|
||||
|
||||
launch_windows() {
|
||||
KEEP_ALIVE=false
|
||||
if [ "$1" = "--keep-alive" ] || [ "$1" = "-k" ]; then
|
||||
KEEP_ALIVE=true
|
||||
fi
|
||||
|
||||
# Check if config exists
|
||||
if [ ! -f "$COMPOSE_FILE" ]; then
|
||||
echo "Windows VM not configured. Please run: omarchy-windows-vm install"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if container is already running
|
||||
CONTAINER_STATUS=$(docker inspect --format='{{.State.Status}}' omarchy-windows 2>/dev/null)
|
||||
|
||||
if [ "$CONTAINER_STATUS" != "running" ]; then
|
||||
echo "Starting Windows VM..."
|
||||
|
||||
# Send desktop notification
|
||||
notify-send "Windows VM" "Starting Windows VM, this may take a moment..."
|
||||
|
||||
if ! docker-compose -f "$COMPOSE_FILE" up -d 2>&1; then
|
||||
echo "❌ Failed to start Windows VM!"
|
||||
echo " Try checking: omarchy-windows-vm status"
|
||||
echo " View logs: docker logs omarchy-windows"
|
||||
notify-send -u critical "Windows VM" "Failed to start Windows VM"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Wait for RDP to be ready
|
||||
echo "Waiting for Windows VM to be ready..."
|
||||
WAIT_COUNT=0
|
||||
while ! nc -z 127.0.0.1 3389 2>/dev/null; do
|
||||
sleep 2
|
||||
WAIT_COUNT=$((WAIT_COUNT + 1))
|
||||
if [ $WAIT_COUNT -gt 60 ]; then # 2 minutes timeout
|
||||
echo "❌ Timeout waiting for RDP!"
|
||||
echo " The VM might still be installing Windows."
|
||||
echo " Check progress at: http://127.0.0.1:8006"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Give it a moment more to fully initialize
|
||||
sleep 3
|
||||
fi
|
||||
|
||||
# Extract credentials from compose file
|
||||
WIN_USER=$(grep "USERNAME:" "$COMPOSE_FILE" | sed 's/.*USERNAME: "\(.*\)"/\1/')
|
||||
WIN_PASS=$(grep "PASSWORD:" "$COMPOSE_FILE" | sed 's/.*PASSWORD: "\(.*\)"/\1/')
|
||||
|
||||
# Use defaults if not found
|
||||
[ -z "$WIN_USER" ] && WIN_USER="docker"
|
||||
[ -z "$WIN_PASS" ] && WIN_PASS="admin"
|
||||
|
||||
# Build the connection info
|
||||
if [ "$KEEP_ALIVE" = true ]; then
|
||||
LIFECYCLE="VM will keep running after RDP closes
|
||||
To stop: omarchy-windows-vm stop"
|
||||
else
|
||||
LIFECYCLE="VM will auto-stop when RDP closes"
|
||||
fi
|
||||
|
||||
gum style \
|
||||
--border normal \
|
||||
--padding "1 2" \
|
||||
--margin "1" \
|
||||
--align center \
|
||||
"Connecting to Windows VM" \
|
||||
"" \
|
||||
"$LIFECYCLE"
|
||||
|
||||
# Detect display scale from Hyprland
|
||||
HYPR_SCALE=$(hyprctl monitors -j | jq -r '.[0].scale')
|
||||
SCALE_PERCENT=$(echo "$HYPR_SCALE" | awk '{print int($1 * 100)}')
|
||||
|
||||
RDP_SCALE=""
|
||||
if [ "$SCALE_PERCENT" -ge 170 ]; then
|
||||
RDP_SCALE="/scale:180"
|
||||
elif [ "$SCALE_PERCENT" -ge 130 ]; then
|
||||
RDP_SCALE="/scale:140"
|
||||
fi
|
||||
# If scale is less than 130%, don't set any scale (use default 100)
|
||||
|
||||
# Connect with RDP in fullscreen (auto-detects resolution)
|
||||
xfreerdp3 /u:"$WIN_USER" /p:"$WIN_PASS" /v:127.0.0.1:3389 +f -grab-keyboard /cert:ignore /title:"Windows VM - Omarchy" /floatbar:sticky:off,default:visible,show:fullscreen $RDP_SCALE
|
||||
|
||||
# After RDP closes, stop the container unless --keep-alive was specified
|
||||
if [ "$KEEP_ALIVE" = false ]; then
|
||||
echo ""
|
||||
echo "RDP session closed. Stopping Windows VM..."
|
||||
docker-compose -f "$COMPOSE_FILE" down
|
||||
echo "Windows VM stopped."
|
||||
else
|
||||
echo ""
|
||||
echo "RDP session closed. Windows VM is still running."
|
||||
echo "To stop it: omarchy-windows-vm stop"
|
||||
fi
|
||||
}
|
||||
|
||||
stop_windows() {
|
||||
if [ ! -f "$COMPOSE_FILE" ]; then
|
||||
echo "Windows VM not configured."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Stopping Windows VM..."
|
||||
docker-compose -f "$COMPOSE_FILE" down
|
||||
echo "Windows VM stopped."
|
||||
}
|
||||
|
||||
status_windows() {
|
||||
if [ ! -f "$COMPOSE_FILE" ]; then
|
||||
echo "Windows VM not configured."
|
||||
echo "To set up: omarchy-windows-vm install"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CONTAINER_STATUS=$(docker inspect --format='{{.State.Status}}' omarchy-windows 2>/dev/null)
|
||||
|
||||
if [ -z "$CONTAINER_STATUS" ]; then
|
||||
echo "Windows VM container not found."
|
||||
echo "To start: omarchy-windows-vm launch"
|
||||
elif [ "$CONTAINER_STATUS" = "running" ]; then
|
||||
gum style \
|
||||
--border normal \
|
||||
--padding "1 2" \
|
||||
--margin "1" \
|
||||
--align left \
|
||||
"Windows VM Status: RUNNING" \
|
||||
"" \
|
||||
"Web interface: http://127.0.0.1:8006" \
|
||||
"RDP available: port 3389" \
|
||||
"" \
|
||||
"To connect: omarchy-windows-vm launch" \
|
||||
"To stop: omarchy-windows-vm stop"
|
||||
else
|
||||
echo "Windows VM is stopped (status: $CONTAINER_STATUS)"
|
||||
echo "To start: omarchy-windows-vm launch"
|
||||
fi
|
||||
}
|
||||
|
||||
show_usage() {
|
||||
echo "Usage: omarchy-windows-vm [command] [options]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " install Install and configure Windows VM"
|
||||
echo " remove Remove Windows VM and optionally its data"
|
||||
echo " launch [options] Start Windows VM (if needed) and connect via RDP"
|
||||
echo " Options:"
|
||||
echo " --keep-alive, -k Keep VM running after RDP closes"
|
||||
echo " stop Stop the running Windows VM"
|
||||
echo " status Show current VM status"
|
||||
echo " help Show this help message"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " omarchy-windows-vm install # Set up Windows VM for first time"
|
||||
echo " omarchy-windows-vm launch # Connect to VM (auto-stop on exit)"
|
||||
echo " omarchy-windows-vm launch -k # Connect to VM (keep running)"
|
||||
echo " omarchy-windows-vm stop # Shut down the VM"
|
||||
}
|
||||
|
||||
# Main command dispatcher
|
||||
case "$1" in
|
||||
install)
|
||||
install_windows
|
||||
;;
|
||||
remove)
|
||||
remove_windows
|
||||
;;
|
||||
launch|start)
|
||||
launch_windows "$2"
|
||||
;;
|
||||
stop|down)
|
||||
stop_windows
|
||||
;;
|
||||
status)
|
||||
status_windows
|
||||
;;
|
||||
help|--help|-h|"")
|
||||
show_usage
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command: $1" >&2
|
||||
echo "" >&2
|
||||
show_usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user