/ de-omarchy / bin / omarchy-windows-vm
swadhinbiswas Initial de-omarchy: Omarchy UI layer for Arch-based distros · 8/22/2026
9f393b2
471 lines · 14.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
#!/bin/bash

# omarchy:summary=Install, launch, stop, inspect, or remove the Windows VM
# omarchy:args=<install|remove|launch|stop|status> [options]
# omarchy:requires-sudo=true

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 < 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"

  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=windows
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 <= 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 < 1 )) || (( SELECTED_CORES > 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 < 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 <= 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"
      TZ: "$(timedatectl show -p Timezone --value 2>/dev/null || echo UTC)"
      ARGUMENTS: "-rtc base=localtime,clock=host,driftfix=slew"
    devices:
      - /dev/kvm
      - /dev/net/tun
    cap_add:
      - NET_ADMIN
    ports:
      - 127.0.0.1:8006:8006
      - 127.0.0.1:3389:3389/tcp
      - 127.0.0.1:3389:3389/udp
    volumes:
      - $HOME/.windows:/storage
      - $HOME/Windows:/shared
    restart: "no"
    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() {
  if ! gum confirm --default=false "Remove Windows VM and delete all associated data?"; then
    echo "Removal cancelled by user"
    exit 1
  fi

  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

  # 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"

  # 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..."

    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"
      omarchy-notification-send -u critical "Windows VM" "Failed to start Windows VM"
      exit 1
    fi
  fi

  # docker logs keeps output across stop/start and is only cleared when the
  # container is removed, so an unanchored grep matches "started successfully"
  # from an earlier boot and returns immediately. Anchor the scan to the current
  # container start, and run it even when the container was already up: the
  # image restarts the guest in place whenever Windows reboots.
  windows_started() {
    local started_at
    started_at=$(docker inspect --format='{{.State.StartedAt}}' omarchy-windows 2>/dev/null)
    # An empty --since would drop the filter and match a stale boot again
    [[ -n $started_at ]] || return 1
    docker logs --since "$started_at" omarchy-windows 2>&1 | grep -qi "windows started successfully"
  }

  echo "Waiting for Windows VM to start..."
  WAIT_COUNT=0
  until windows_started; do
    sleep 2
    WAIT_COUNT=$((WAIT_COUNT + 1))
    if (( WAIT_COUNT > 60 )); then  # 2 minutes timeout
      echo ""
      echo "❌ Timeout: Windows VM failed to start within 2 minutes"
      echo "   Check logs: docker logs omarchy-windows"
      exit 1
    fi
  done

  # 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"

  # FreeRDP 3 tries Kerberos before NTLM for NLA, and krb5 ships /etc/krb5.conf
  # as the MIT sample with default_realm = ATHENA.MIT.EDU. Every connect then
  # goes looking for MIT's KDC: with internet up it fails fast, but off the
  # network each attempt blocks ~23s and no RDP window is ever drawn. The VM
  # authenticates against a local Windows account, so point FreeRDP at a
  # realm-less config and let it fall straight through to NTLM.
  KRB5_CONF="$(dirname "$COMPOSE_FILE")/krb5.conf"
  if [[ ! -f $KRB5_CONF ]]; then
    printf '[libdefaults]\n  dns_lookup_kdc = false\n  dns_lookup_realm = false\n' >"$KRB5_CONF"
  fi
  export KRB5_CONFIG="$KRB5_CONF"

  # 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 >= 170 )); then
    RDP_SCALE="/scale:180"
  elif (( SCALE_PERCENT >= 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 -grab-keyboard /sound /microphone /clipboard /cert:ignore /title:"Windows 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 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