diff --git a/applications/icons/apple.png b/applications/icons/apple.png new file mode 100644 index 0000000000..b9e765b6a1 Binary files /dev/null and b/applications/icons/apple.png differ diff --git a/bin/omarchy-mac-vm b/bin/omarchy-mac-vm new file mode 100644 index 0000000000..8d351c06d0 --- /dev/null +++ b/bin/omarchy-mac-vm @@ -0,0 +1,452 @@ +#!/bin/bash +COMPOSE_FILE="$HOME/.config/mac/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_mac() { + # 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/.mac/" + mkdir -p "$HOME/.config/mac" + mkdir -p "$HOME/.local/share/applications/icons" + + # Install Windows VM icon and desktop file + if [ -f "$OMARCHY_PATH/applications/icons/apple.png" ]; then + cp "$OMARCHY_PATH/applications/icons/apple.png" "$HOME/.local/share/applications/icons/apple.png" + fi + + cat << EOF | tee "$HOME/.local/share/applications/mac-vm.desktop" > /dev/null +[Desktop Entry] +Name=MacOS +Comment=Start MacOS VM via Docker and connect with RDP +Exec=uwsm app -- omarchy-mac-vm launch +Icon=$HOME/.local/share/applications/icons/apple.png +Terminal=false +Type=Application +Categories=System;Virtualization; +EOF + + # Get system resources + TOTAL_RAM=$(free -h | awk 'NR==2 {print $2}') + TOTAL_RAM_GB=$(awk 'NR==1 {printf "%d", $2/1024/1024}' /proc/meminfo) + 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 MaxOS 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 MacOS 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 MacOS VM!" + echo " Available: ${AVAILABLE_SPACE}GB" + echo " Minimum required: 42GB (32GB disk + 10GB for MacOS 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 MacOS 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 MacOS username:") + if [ -z "$USERNAME" ]; then + USERNAME="docker" + fi + + PASSWORD=$(gum input --placeholder="Password (Press enter to use default: admin)" --password --header="Enter MacOS 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 \ + "MacOS 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: + macos: + image: dockurr/macos + container_name: omarchy-macos + environment: + VERSION: "15" + 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: + - 8007:8006 + - 5900:5900/tcp + - 5900:5900/udp + volumes: + - $HOME/.mac:/storage + - $HOME/mac:/shared + restart: always + stop_grace_period: 2m +EOF + + echo "" + echo "Starting MacOS VM installation..." + echo "This will download a MacOS Sequoia image (may take 10-15 minutes)." + echo "" + echo "Monitor installation progress at: http://127.0.0.1:8007" + echo "" + + # Start docker-compose with user's config + echo "Starting MacOS VM with docker-compose..." + if ! docker-compose -f "$COMPOSE_FILE" up -d 2>&1; then + echo "❌ Failed to start MacOS 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 "MacOS 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:8007" + + echo "" + echo "Installation is running in the background." + echo "You can monitor progress at: http://127.0.0.1:8007" + echo "" + echo "Once finished, launch 'MacOS' via Super + Space" + echo "" + echo "To stop the VM: omarchy-mac-vm stop" + echo "To change resources: ~/.config/mac/docker-compose.yml" + echo "" +} + +remove_mac() { + echo "Removing MacOS 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/mac-vm.desktop" + rm -rf "$HOME/.config/mac" + rm -rf "$HOME/.mac" + + echo "" + echo "MacOS VM removal completed!" +} + +wait_for_rdp_ready() { + local MAC_USER="$1" + local MAC_PASS="$2" + local TIMEOUT=240 + local SECONDS=0 + + echo "Waiting for MacOS VM to be ready..." + + while ! timeout 5s xfreerdp3 /auth-only /cert:ignore /u:"$MAC_USER" /p:"$MAC_PASS" /v:127.0.0.1:5900 &>/dev/null; do + sleep 2 + if [ $SECONDS -gt $TIMEOUT ]; then + echo "❌ Timeout waiting for RDP!" + echo " The VM might still be installing MacOS." + echo " Check progress at: http://127.0.0.1:8007" + return 1 + fi + done +} + +launch_mac() { + KEEP_ALIVE=false + if [ "$1" = "--keep-alive" ] || [ "$1" = "-k" ]; then + KEEP_ALIVE=true + fi + + # Check if config exists + if [ ! -f "$COMPOSE_FILE" ]; then + echo "MacOS VM not configured. Please run: omarchy-mac-vm install" + exit 1 + fi + + # Extract credentials from compose file + MAC_USER=$(grep "USERNAME:" "$COMPOSE_FILE" | sed 's/.*USERNAME: "\(.*\)"/\1/') + MAC_PASS=$(grep "PASSWORD:" "$COMPOSE_FILE" | sed 's/.*PASSWORD: "\(.*\)"/\1/') + + # Use defaults if not found + [ -z "$MAC_USER" ] && MAC_USER="docker" + [ -z "$MAC_PASS" ] && MAC_PASS="admin" + + # Check if container is already running + CONTAINER_STATUS=$(docker inspect --format='{{.State.Status}}' omarchy-macos 2>/dev/null) + + if [ "$CONTAINER_STATUS" != "running" ]; then + echo "Starting MacOS VM..." + + # Send desktop notification + notify-send " Starting MacOS VM" " This can take 15-30 seconds" -t 15000 + + if ! docker-compose -f "$COMPOSE_FILE" up -d 2>&1; then + echo "❌ Failed to start MacOS VM!" + echo " Try checking: omarchy-mac-vm status" + echo " View logs: docker logs omarchy-mac" + notify-send -u critical "MacOS VM" "Failed to start MacOS VM" + exit 1 + fi + fi + + if ! wait_for_rdp_ready "$MAC_USER" "$MAC_PASS"; then + notify-send -u critical "MacOS VM" "Did not come alive in time." + exit 1 + fi + + # Build the connection info + if [ "$KEEP_ALIVE" = true ]; then + LIFECYCLE="VM will keep running after RDP closes +To stop: omarchy-mac-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 MacOS VM" \ + "" \ + "$LIFECYCLE" + + # Detect display scale from Hyprland + HYPR_SCALE=$(hyprctl monitors -j | jq -r '.[] | select (.focused == true) | .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:"$MAC_USER" /p:"$MAC_PASS" /v:127.0.0.1:5900 -grab-keyboard /sound /microphone /cert:ignore /title:"MacOS VM - Omarchy" /dynamic-resolution /gfx:AVC444 /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 MacOS VM..." + docker-compose -f "$COMPOSE_FILE" down + echo "MacOS VM stopped." + else + echo "" + echo "RDP session closed. MacOS VM is still running." + echo "To stop it: omarchy-mac-vm stop" + fi +} + +stop_mac() { + if [ ! -f "$COMPOSE_FILE" ]; then + echo "MacOS VM not configured." + exit 1 + fi + + echo "Stopping MacOS VM..." + docker-compose -f "$COMPOSE_FILE" down + echo "MacOS VM stopped." +} + +status_mac() { + if [ ! -f "$COMPOSE_FILE" ]; then + echo "MacOS VM not configured." + echo "To set up: omarchy-mac-vm install" + exit 1 + fi + + CONTAINER_STATUS=$(docker inspect --format='{{.State.Status}}' omarchy-mac 2>/dev/null) + + if [ -z "$CONTAINER_STATUS" ]; then + echo "MacOS VM container not found." + echo "To start: omarchy-mac-vm launch" + elif [ "$CONTAINER_STATUS" = "running" ]; then + gum style \ + --border normal \ + --padding "1 2" \ + --margin "1" \ + --align left \ + "MacOS VM Status: RUNNING" \ + "" \ + "Web interface: http://127.0.0.1:8007" \ + "RDP available: port 5900" \ + "" \ + "To connect: omarchy-mac-vm launch" \ + "To stop: omarchy-mac-vm stop" + else + echo "MacOS VM is stopped (status: $CONTAINER_STATUS)" + echo "To start: omarchy-mac-vm launch" + fi +} + +show_usage() { + echo "Usage: omarchy-mac-vm [command] [options]" + echo "" + echo "Commands:" + echo " install Install and configure MacOS VM" + echo " remove Remove MacOS VM and optionally its data" + echo " launch [options] Start MacOS VM (if needed) and connect via RDP" + echo " Options:" + echo " --keep-alive, -k Keep VM running after RDP closes" + echo " stop Stop the running MacOS VM" + echo " status Show current VM status" + echo " help Show this help message" + echo "" + echo "Examples:" + echo " omarchy-mac-vm install # Set up MacOS VM for first time" + echo " omarchy-mac-vm launch # Connect to VM (auto-stop on exit)" + echo " omarchy-mac-vm launch -k # Connect to VM (keep running)" + echo " omarchy-mac-vm stop # Shut down the VM" +} + +# Main command dispatcher +case "$1" in + install) + install_mac + ;; + remove) + remove_mac + ;; + launch|start) + launch_mac "$2" + ;; + stop|down) + stop_mac + ;; + status) + status_mac + ;; + help|--help|-h|"") + show_usage + ;; + *) + echo "Unknown command: $1" >&2 + echo "" >&2 + show_usage >&2 + exit 1 + ;; +esac diff --git a/bin/omarchy-menu b/bin/omarchy-menu index 6e0e5012f2..342b4390f3 100755 --- a/bin/omarchy-menu +++ b/bin/omarchy-menu @@ -88,11 +88,10 @@ show_learn_menu() { } show_trigger_menu() { - case $(menu "Trigger" " Capture\n Share\n󰔎 Toggle\n Hardware") in + case $(menu "Trigger" " Capture\n Share\n󰔎 Toggle") in *Capture*) show_capture_menu ;; *Share*) show_share_menu ;; *Toggle*) show_toggle_menu ;; - *Hardware*) show_hardware_menu ;; *) show_main_menu ;; esac } @@ -174,13 +173,6 @@ show_toggle_menu() { esac } -show_hardware_menu() { - case $(menu "Toggle" " Hybrid GPU") in - *"Hybrid GPU"*) present_terminal omarchy-toggle-hybrid-gpu ;; - *) show_trigger_menu ;; - esac -} - show_style_menu() { case $(menu "Style" "󰸌 Theme\n Font\n Background\n Hyprland\n󱄄 Screensaver\n About") in *Theme*) show_theme_menu ;; @@ -297,6 +289,7 @@ show_install_menu() { *Terminal*) show_install_terminal_menu ;; *AI*) show_install_ai_menu ;; *Windows*) present_terminal "omarchy-windows-vm install" ;; + *MacOS*) present_terminal "omarchy-mac-vm install" ;; *Gaming*) show_install_gaming_menu ;; *) show_main_menu ;; esac @@ -438,6 +431,7 @@ show_remove_menu() { *Dictation*) present_terminal omarchy-voxtype-remove ;; *Theme*) present_terminal omarchy-theme-remove ;; *Windows*) present_terminal "omarchy-windows-vm remove" ;; + *MacOS*) present_terminal "omarchy-mac-vm remove" ;; *Fingerprint*) present_terminal "omarchy-setup-fingerprint --remove" ;; *Fido2*) present_terminal "omarchy-setup-fido2 --remove" ;; *) show_main_menu ;; @@ -445,7 +439,7 @@ show_remove_menu() { } show_remove_development_menu() { - case $(menu "Remove" "󰫏 Ruby on Rails\n JavaScript\n Go\n PHP\n Python\n Elixir\n Zig\n Rust\n Java\n .NET\n OCaml\n Clojure") in + case $(menu "Remove" "󰫏 Ruby on Rails\n JavaScript\n Go\n PHP\n Python\n Elixir\n Zig\n Rust\n Java\n .NET\n OCaml\n Clojure") in *Rails*) present_terminal "omarchy-remove-dev-env ruby" ;; *JavaScript*) show_remove_javascript_menu ;; *Go*) present_terminal "omarchy-remove-dev-env go" ;; @@ -463,7 +457,7 @@ show_remove_development_menu() { } show_remove_javascript_menu() { - case $(menu "Remove" " Node.js\n Bun\n Deno") in + case $(menu "Remove" " Node.js\n Bun\n Deno") in *Node*) present_terminal "omarchy-remove-dev-env node" ;; *Bun*) present_terminal "omarchy-remove-dev-env bun" ;; *Deno*) present_terminal "omarchy-remove-dev-env deno" ;; @@ -472,7 +466,7 @@ show_remove_javascript_menu() { } show_remove_php_menu() { - case $(menu "Remove" " PHP\n Laravel\n Symfony") in + case $(menu "Remove" " PHP\n Laravel\n Symfony") in *PHP*) present_terminal "omarchy-remove-dev-env php" ;; *Laravel*) present_terminal "omarchy-remove-dev-env laravel" ;; *Symfony*) present_terminal "omarchy-remove-dev-env symfony" ;; @@ -481,7 +475,7 @@ show_remove_php_menu() { } show_remove_elixir_menu() { - case $(menu "Remove" " Elixir\n Phoenix") in + case $(menu "Remove" " Elixir\n Phoenix") in *Elixir*) present_terminal "omarchy-remove-dev-env elixir" ;; *Phoenix*) present_terminal "omarchy-remove-dev-env phoenix" ;; *) show_remove_development_menu ;; @@ -581,7 +575,6 @@ go_to_menu() { *learn*) show_learn_menu ;; *trigger*) show_trigger_menu ;; *share*) show_share_menu ;; - *capture*) show_capture_menu ;; *style*) show_style_menu ;; *theme*) show_theme_menu ;; *screenshot*) show_screenshot_menu ;;