docker-windows/winctl.sh

1017 lines
29 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
#
# winctl.sh - Windows Docker Container Management Script
# Manage Windows Docker containers with ease
#
# Usage: ./winctl.sh <command> [options]
#
set -Eeuo pipefail
# ==============================================================================
# METADATA
# ==============================================================================
readonly SCRIPT_VERSION="1.0.0"
readonly SCRIPT_NAME="winctl"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_DIR
# ==============================================================================
# COLORS & TERMINAL DETECTION
# ==============================================================================
if [[ -t 1 ]] && [[ "${TERM:-}" != "dumb" ]]; then
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[0;33m'
readonly BLUE='\033[0;34m'
readonly MAGENTA='\033[0;35m'
readonly CYAN='\033[0;36m'
readonly WHITE='\033[0;37m'
readonly BOLD='\033[1m'
readonly DIM='\033[2m'
readonly RESET='\033[0m'
else
readonly RED=''
readonly GREEN=''
readonly YELLOW=''
readonly BLUE=''
readonly MAGENTA=''
readonly CYAN=''
readonly WHITE=''
readonly BOLD=''
readonly DIM=''
readonly RESET=''
fi
# ==============================================================================
# VERSION DATA
# ==============================================================================
# All supported versions
readonly ALL_VERSIONS=(
win11 win11e win11l win10 win10e win10l
win81 win81e win7 win7e
vista winxp win2k
win2025 win2022 win2019 win2016 win2012 win2008 win2003
tiny11 tiny10
)
# Port mappings (web)
declare -A VERSION_PORTS_WEB=(
["win11"]=8011 ["win11e"]=8012 ["win11l"]=8013
["win10"]=8010 ["win10e"]=8014 ["win10l"]=8015
["win81"]=8008 ["win81e"]=8081
["win7"]=8007 ["win7e"]=8071
["vista"]=8006 ["winxp"]=8005 ["win2k"]=8000
["win2025"]=8025 ["win2022"]=8022 ["win2019"]=8019 ["win2016"]=8016
["win2012"]=8112 ["win2008"]=8108 ["win2003"]=8003
["tiny11"]=8111 ["tiny10"]=8110
)
# Port mappings (RDP)
declare -A VERSION_PORTS_RDP=(
["win11"]=3311 ["win11e"]=3312 ["win11l"]=3313
["win10"]=3310 ["win10e"]=3314 ["win10l"]=3315
["win81"]=3308 ["win81e"]=3381
["win7"]=3307 ["win7e"]=3371
["vista"]=3306 ["winxp"]=3305 ["win2k"]=3300
["win2025"]=3325 ["win2022"]=3322 ["win2019"]=3319 ["win2016"]=3316
["win2012"]=3212 ["win2008"]=3208 ["win2003"]=3303
["tiny11"]=3111 ["tiny10"]=3110
)
# Categories
declare -A VERSION_CATEGORIES=(
["win11"]="desktop" ["win11e"]="desktop" ["win11l"]="desktop"
["win10"]="desktop" ["win10e"]="desktop" ["win10l"]="desktop"
["win81"]="desktop" ["win81e"]="desktop"
["win7"]="desktop" ["win7e"]="desktop"
["vista"]="legacy" ["winxp"]="legacy" ["win2k"]="legacy"
["win2025"]="server" ["win2022"]="server" ["win2019"]="server" ["win2016"]="server"
["win2012"]="server" ["win2008"]="server" ["win2003"]="server"
["tiny11"]="tiny" ["tiny10"]="tiny"
)
# Compose files
declare -A VERSION_COMPOSE_FILES=(
["win11"]="compose/desktop/win11.yml" ["win11e"]="compose/desktop/win11.yml" ["win11l"]="compose/desktop/win11.yml"
["win10"]="compose/desktop/win10.yml" ["win10e"]="compose/desktop/win10.yml" ["win10l"]="compose/desktop/win10.yml"
["win81"]="compose/desktop/win8.yml" ["win81e"]="compose/desktop/win8.yml"
["win7"]="compose/desktop/win7.yml" ["win7e"]="compose/desktop/win7.yml"
["vista"]="compose/legacy/vista.yml" ["winxp"]="compose/legacy/winxp.yml" ["win2k"]="compose/legacy/win2k.yml"
["win2025"]="compose/server/win2025.yml" ["win2022"]="compose/server/win2022.yml"
["win2019"]="compose/server/win2019.yml" ["win2016"]="compose/server/win2016.yml"
["win2012"]="compose/server/win2012.yml" ["win2008"]="compose/server/win2008.yml" ["win2003"]="compose/server/win2003.yml"
["tiny11"]="compose/tiny/tiny11.yml" ["tiny10"]="compose/tiny/tiny10.yml"
)
# Display names
declare -A VERSION_DISPLAY_NAMES=(
["win11"]="Windows 11 Pro" ["win11e"]="Windows 11 Enterprise" ["win11l"]="Windows 11 LTSC"
["win10"]="Windows 10 Pro" ["win10e"]="Windows 10 Enterprise" ["win10l"]="Windows 10 LTSC"
["win81"]="Windows 8.1 Pro" ["win81e"]="Windows 8.1 Enterprise"
["win7"]="Windows 7 Ultimate" ["win7e"]="Windows 7 Enterprise"
["vista"]="Windows Vista Ultimate" ["winxp"]="Windows XP Professional" ["win2k"]="Windows 2000 Professional"
["win2025"]="Windows Server 2025" ["win2022"]="Windows Server 2022"
["win2019"]="Windows Server 2019" ["win2016"]="Windows Server 2016"
["win2012"]="Windows Server 2012 R2" ["win2008"]="Windows Server 2008 R2" ["win2003"]="Windows Server 2003"
["tiny11"]="Tiny11" ["tiny10"]="Tiny10"
)
# Resource type (modern = high resources, legacy = low resources)
declare -A VERSION_RESOURCE_TYPE=(
["win11"]="modern" ["win11e"]="modern" ["win11l"]="modern"
["win10"]="modern" ["win10e"]="modern" ["win10l"]="modern"
["win81"]="legacy" ["win81e"]="legacy"
["win7"]="legacy" ["win7e"]="legacy"
["vista"]="legacy" ["winxp"]="legacy" ["win2k"]="legacy"
["win2025"]="modern" ["win2022"]="modern" ["win2019"]="modern" ["win2016"]="modern"
["win2012"]="legacy" ["win2008"]="legacy" ["win2003"]="legacy"
["tiny11"]="legacy" ["tiny10"]="legacy"
)
# Resource requirements
readonly MODERN_RAM_GB=8
readonly MODERN_DISK_GB=128
readonly LEGACY_RAM_GB=2
readonly LEGACY_DISK_GB=32
# ==============================================================================
# OUTPUT HELPERS
# ==============================================================================
info() {
echo -e "${BLUE}[INFO]${RESET} $*"
}
success() {
echo -e "${GREEN}[OK]${RESET} $*"
}
warn() {
echo -e "${YELLOW}[WARN]${RESET} $*"
}
error() {
echo -e "${RED}[ERROR]${RESET} $*" >&2
}
die() {
error "$@"
exit 1
}
header() {
echo ""
echo -e "${BOLD}${CYAN}$*${RESET}"
echo -e "${DIM}$(printf '─%.0s' {1..60})${RESET}"
}
# Print a formatted table row
table_row() {
local version="$1"
local name="$2"
local status="$3"
local web="$4"
local rdp="$5"
local status_color
case "$status" in
running) status_color="${GREEN}" ;;
stopped|exited) status_color="${RED}" ;;
*) status_color="${YELLOW}" ;;
esac
printf " ${BOLD}%-12s${RESET} %-26s ${status_color}%-10s${RESET} %-8s %-8s\n" \
"$version" "$name" "$status" "$web" "$rdp"
}
table_header() {
echo ""
printf " ${BOLD}${DIM}%-12s %-26s %-10s %-8s %-8s${RESET}\n" \
"VERSION" "NAME" "STATUS" "WEB" "RDP"
echo -e " ${DIM}$(printf '─%.0s' {1..66})${RESET}"
}
# ==============================================================================
# PREREQUISITES CHECKS
# ==============================================================================
check_docker() {
if ! command -v docker &>/dev/null; then
error "Docker is not installed"
echo " Install Docker: https://docs.docker.com/get-docker/"
return 1
fi
if ! docker info &>/dev/null; then
error "Docker daemon is not running"
echo " Start Docker: sudo systemctl start docker"
return 1
fi
success "Docker is available"
return 0
}
check_compose() {
if docker compose version &>/dev/null; then
success "Docker Compose plugin is available"
return 0
elif command -v docker-compose &>/dev/null; then
success "Docker Compose standalone is available"
return 0
else
error "Docker Compose is not installed"
echo " Install: https://docs.docker.com/compose/install/"
return 1
fi
}
check_kvm() {
if [[ ! -e /dev/kvm ]]; then
error "KVM device not found (/dev/kvm)"
echo " Enable virtualization in BIOS or check nested virtualization"
return 1
fi
if [[ ! -r /dev/kvm ]] || [[ ! -w /dev/kvm ]]; then
error "KVM device not accessible"
echo " Fix: sudo usermod -aG kvm \$USER && newgrp kvm"
return 1
fi
success "KVM is available"
return 0
}
check_tun() {
if [[ ! -e /dev/net/tun ]]; then
warn "TUN device not found (/dev/net/tun) - networking may be limited"
return 1
fi
success "TUN device is available"
return 0
}
check_memory() {
local required_gb="${1:-$MODERN_RAM_GB}"
local available_kb
available_kb=$(grep MemAvailable /proc/meminfo | awk '{print $2}')
local available_gb=$((available_kb / 1024 / 1024))
if ((available_gb < required_gb)); then
warn "Low memory: ${available_gb}GB available (${required_gb}GB recommended)"
return 1
fi
success "Memory OK: ${available_gb}GB available (${required_gb}GB needed)"
return 0
}
check_disk() {
local required_gb="${1:-$MODERN_DISK_GB}"
local available_kb
available_kb=$(df "$SCRIPT_DIR" | tail -1 | awk '{print $4}')
local available_gb=$((available_kb / 1024 / 1024))
if ((available_gb < required_gb)); then
warn "Low disk space: ${available_gb}GB available (${required_gb}GB recommended)"
return 1
fi
success "Disk space OK: ${available_gb}GB available (${required_gb}GB needed)"
return 0
}
run_all_checks() {
header "Prerequisites Check"
local failed=0
check_docker || ((failed++))
check_compose || ((failed++))
check_kvm || ((failed++))
check_tun || true # Warning only
check_memory || true # Warning only
check_disk || true # Warning only
echo ""
if ((failed > 0)); then
error "Some critical checks failed. Please fix the issues above."
return 1
else
success "All critical prerequisites passed!"
return 0
fi
}
# ==============================================================================
# DOCKER HELPERS
# ==============================================================================
# Get the compose command (plugin vs standalone)
compose_cmd() {
if docker compose version &>/dev/null; then
echo "docker compose"
else
echo "docker-compose"
fi
}
# Check if a container is running
is_running() {
local version="$1"
docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${version}$"
}
# Check if a container exists (running or stopped)
container_exists() {
local version="$1"
docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^${version}$"
}
# Get container status
get_status() {
local version="$1"
local status
status=$(docker ps -a --filter "name=^${version}$" --format '{{.State}}' 2>/dev/null)
echo "${status:-not created}"
}
# Get compose file path for version
get_compose_file() {
local version="$1"
local file="${VERSION_COMPOSE_FILES[$version]:-}"
if [[ -z "$file" ]]; then
die "Unknown version: $version"
fi
echo "$SCRIPT_DIR/$file"
}
# Validate version
validate_version() {
local version="$1"
if [[ -z "${VERSION_COMPOSE_FILES[$version]:-}" ]]; then
error "Unknown version: $version"
echo " Run '${SCRIPT_NAME} list' to see available versions"
return 1
fi
return 0
}
# Run compose command for a version
run_compose() {
local version="$1"
shift
local compose_file
compose_file=$(get_compose_file "$version")
cd "$SCRIPT_DIR"
$(compose_cmd) -f "$compose_file" "$@"
}
# ==============================================================================
# INTERACTIVE MENU
# ==============================================================================
# Get versions by category
get_versions_by_category() {
local category="$1"
local versions=()
for v in "${ALL_VERSIONS[@]}"; do
if [[ "${VERSION_CATEGORIES[$v]}" == "$category" ]]; then
versions+=("$v")
fi
done
echo "${versions[*]}"
}
# Show category menu
select_category() {
header "Select Category"
echo ""
echo " ${BOLD}1${RESET}) Desktop (Win 11, 10, 8.1, 7)"
echo " ${BOLD}2${RESET}) Legacy (Vista, XP, 2000)"
echo " ${BOLD}3${RESET}) Server (2025, 2022, 2019, 2016, 2012, 2008, 2003)"
echo " ${BOLD}4${RESET}) Tiny (Tiny11, Tiny10)"
echo " ${BOLD}5${RESET}) All versions"
echo " ${BOLD}6${RESET}) Select individual versions"
echo ""
echo -n " Select [1-6]: "
local choice
read -r choice
case "$choice" in
1) echo "desktop" ;;
2) echo "legacy" ;;
3) echo "server" ;;
4) echo "tiny" ;;
5) echo "all" ;;
6) echo "individual" ;;
*) echo "" ;;
esac
}
# Show version selection menu
select_versions() {
local category="$1"
local versions=()
if [[ "$category" == "all" ]]; then
versions=("${ALL_VERSIONS[@]}")
elif [[ "$category" == "individual" ]]; then
versions=("${ALL_VERSIONS[@]}")
else
IFS=' ' read -ra versions <<< "$(get_versions_by_category "$category")"
fi
if [[ ${#versions[@]} -eq 0 ]]; then
die "No versions found for category: $category"
fi
header "Select Version(s)"
echo ""
local i=1
for v in "${versions[@]}"; do
local status=""
if is_running "$v"; then
status="${GREEN}[running]${RESET}"
elif container_exists "$v"; then
status="${YELLOW}[stopped]${RESET}"
fi
printf " ${BOLD}%2d${RESET}) %-10s %-28s %s\n" "$i" "$v" "${VERSION_DISPLAY_NAMES[$v]}" "$status"
((i++))
done
echo ""
echo " ${BOLD} a${RESET}) Select all"
echo " ${BOLD} q${RESET}) Cancel"
echo ""
echo -n " Select (numbers separated by spaces, or 'a' for all): "
local input
read -r input
if [[ "$input" == "q" ]] || [[ -z "$input" ]]; then
return 1
fi
if [[ "$input" == "a" ]]; then
echo "${versions[*]}"
return 0
fi
local selected=()
for num in $input; do
if [[ "$num" =~ ^[0-9]+$ ]] && ((num >= 1 && num <= ${#versions[@]})); then
selected+=("${versions[$((num-1))]}")
fi
done
if [[ ${#selected[@]} -eq 0 ]]; then
return 1
fi
echo "${selected[*]}"
}
# Interactive version selection
interactive_select() {
local category
category=$(select_category)
if [[ -z "$category" ]]; then
die "Invalid selection"
fi
local selected
if ! selected=$(select_versions "$category"); then
die "No versions selected"
fi
echo "$selected"
}
# ==============================================================================
# COMMANDS
# ==============================================================================
cmd_start() {
local versions=("$@")
# Interactive selection if no versions specified
if [[ ${#versions[@]} -eq 0 ]]; then
IFS=' ' read -ra versions <<< "$(interactive_select)"
fi
# Validate all versions first
for v in "${versions[@]}"; do
validate_version "$v" || exit 1
done
# Run prerequisite checks
check_docker || exit 1
check_kvm || exit 1
for v in "${versions[@]}"; do
header "Starting ${VERSION_DISPLAY_NAMES[$v]} ($v)"
# Check resources
local resource_type="${VERSION_RESOURCE_TYPE[$v]}"
if [[ "$resource_type" == "modern" ]]; then
check_memory "$MODERN_RAM_GB" || true
check_disk "$MODERN_DISK_GB" || true
else
check_memory "$LEGACY_RAM_GB" || true
check_disk "$LEGACY_DISK_GB" || true
fi
if is_running "$v"; then
info "$v is already running"
else
info "Starting $v..."
if run_compose "$v" up -d "$v"; then
success "$v started successfully"
else
error "Failed to start $v"
continue
fi
fi
# Show connection info
echo ""
echo -e " ${BOLD}Connection Details:${RESET}"
echo -e " → Web Viewer: ${CYAN}http://localhost:${VERSION_PORTS_WEB[$v]}${RESET}"
echo -e " → RDP: ${CYAN}localhost:${VERSION_PORTS_RDP[$v]}${RESET}"
echo ""
done
}
cmd_stop() {
local versions=("$@")
# Interactive selection if no versions specified
if [[ ${#versions[@]} -eq 0 ]]; then
IFS=' ' read -ra versions <<< "$(interactive_select)"
fi
# Validate all versions first
for v in "${versions[@]}"; do
validate_version "$v" || exit 1
done
# Show confirmation
header "Stopping Containers"
echo ""
echo " The following containers will be stopped:"
for v in "${versions[@]}"; do
local status
if is_running "$v"; then
status="${GREEN}running${RESET}"
else
status="${YELLOW}not running${RESET}"
fi
echo -e "$v (${VERSION_DISPLAY_NAMES[$v]}) - $status"
done
echo ""
echo -n " Continue? [y/N]: "
local confirm
read -r confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
info "Cancelled"
return 0
fi
for v in "${versions[@]}"; do
if ! is_running "$v" && ! container_exists "$v"; then
info "$v is not running"
continue
fi
info "Stopping $v (grace period: 2 minutes)..."
if run_compose "$v" stop "$v"; then
success "$v stopped"
else
error "Failed to stop $v"
fi
done
}
cmd_restart() {
local versions=("$@")
# Interactive selection if no versions specified
if [[ ${#versions[@]} -eq 0 ]]; then
IFS=' ' read -ra versions <<< "$(interactive_select)"
fi
# Validate all versions first
for v in "${versions[@]}"; do
validate_version "$v" || exit 1
done
for v in "${versions[@]}"; do
header "Restarting ${VERSION_DISPLAY_NAMES[$v]} ($v)"
info "Restarting $v..."
if run_compose "$v" restart "$v"; then
success "$v restarted"
echo ""
echo -e " ${BOLD}Connection Details:${RESET}"
echo -e " → Web Viewer: ${CYAN}http://localhost:${VERSION_PORTS_WEB[$v]}${RESET}"
echo -e " → RDP: ${CYAN}localhost:${VERSION_PORTS_RDP[$v]}${RESET}"
echo ""
else
error "Failed to restart $v"
fi
done
}
cmd_status() {
local versions=("$@")
# Show all if no versions specified
if [[ ${#versions[@]} -eq 0 ]]; then
versions=("${ALL_VERSIONS[@]}")
fi
table_header
for v in "${versions[@]}"; do
if ! validate_version "$v" 2>/dev/null; then
continue
fi
local status
status=$(get_status "$v")
table_row "$v" "${VERSION_DISPLAY_NAMES[$v]}" "$status" "${VERSION_PORTS_WEB[$v]}" "${VERSION_PORTS_RDP[$v]}"
done
echo ""
}
cmd_logs() {
local version="${1:-}"
local follow="${2:-}"
if [[ -z "$version" ]]; then
die "Usage: ${SCRIPT_NAME} logs <version> [-f]"
fi
validate_version "$version" || exit 1
local args=()
if [[ "$follow" == "-f" ]]; then
args+=("--follow")
fi
info "Showing logs for $version..."
run_compose "$version" logs "${args[@]}" "$version"
}
cmd_shell() {
local version="${1:-}"
if [[ -z "$version" ]]; then
die "Usage: ${SCRIPT_NAME} shell <version>"
fi
validate_version "$version" || exit 1
if ! is_running "$version"; then
die "$version is not running"
fi
info "Opening shell in $version..."
docker exec -it "$version" /bin/bash
}
cmd_stats() {
local versions=("$@")
# Get running containers if no versions specified
if [[ ${#versions[@]} -eq 0 ]]; then
local running=()
for v in "${ALL_VERSIONS[@]}"; do
if is_running "$v"; then
running+=("$v")
fi
done
if [[ ${#running[@]} -eq 0 ]]; then
die "No containers are running"
fi
versions=("${running[@]}")
fi
# Validate versions
local valid_running=()
for v in "${versions[@]}"; do
if validate_version "$v" 2>/dev/null && is_running "$v"; then
valid_running+=("$v")
fi
done
if [[ ${#valid_running[@]} -eq 0 ]]; then
die "None of the specified containers are running"
fi
info "Showing stats for: ${valid_running[*]}"
docker stats "${valid_running[@]}"
}
cmd_build() {
header "Building Docker Image"
check_docker || exit 1
info "Building dockurr/windows image locally..."
cd "$SCRIPT_DIR"
if docker build -t dockurr/windows .; then
success "Image built successfully"
else
die "Build failed"
fi
}
cmd_rebuild() {
local versions=("$@")
# Interactive selection if no versions specified
if [[ ${#versions[@]} -eq 0 ]]; then
IFS=' ' read -ra versions <<< "$(interactive_select)"
fi
# Validate all versions first
for v in "${versions[@]}"; do
validate_version "$v" || exit 1
done
# Show warning
header "⚠️ Rebuild Containers"
echo ""
echo -e " ${RED}${BOLD}WARNING: This will destroy and recreate the following containers.${RESET}"
echo -e " ${RED}Data in /storage volumes will be preserved.${RESET}"
echo ""
for v in "${versions[@]}"; do
echo "$v (${VERSION_DISPLAY_NAMES[$v]})"
done
echo ""
echo -n " Type 'yes' to confirm: "
local confirm
read -r confirm
if [[ "$confirm" != "yes" ]]; then
info "Cancelled"
return 0
fi
for v in "${versions[@]}"; do
header "Rebuilding $v"
info "Stopping and removing $v..."
run_compose "$v" down "$v" 2>/dev/null || true
info "Recreating $v..."
if run_compose "$v" up -d "$v"; then
success "$v rebuilt successfully"
echo ""
echo -e " ${BOLD}Connection Details:${RESET}"
echo -e " → Web Viewer: ${CYAN}http://localhost:${VERSION_PORTS_WEB[$v]}${RESET}"
echo -e " → RDP: ${CYAN}localhost:${VERSION_PORTS_RDP[$v]}${RESET}"
echo ""
else
error "Failed to rebuild $v"
fi
done
}
cmd_list() {
local category="${1:-all}"
header "Available Windows Versions"
local categories=()
case "$category" in
desktop) categories=("desktop") ;;
legacy) categories=("legacy") ;;
server) categories=("server") ;;
tiny) categories=("tiny") ;;
all) categories=("desktop" "legacy" "server" "tiny") ;;
*)
die "Unknown category: $category. Use: desktop, legacy, server, tiny, or all"
;;
esac
for cat in "${categories[@]}"; do
echo ""
local cat_upper
cat_upper=$(echo "$cat" | tr '[:lower:]' '[:upper:]')
echo -e " ${BOLD}${cat_upper}${RESET}"
echo -e " ${DIM}$(printf '─%.0s' {1..50})${RESET}"
for v in "${ALL_VERSIONS[@]}"; do
if [[ "${VERSION_CATEGORIES[$v]}" == "$cat" ]]; then
local status=""
if is_running "$v"; then
status="${GREEN}[running]${RESET}"
elif container_exists "$v"; then
status="${YELLOW}[stopped]${RESET}"
fi
local resource_tag
if [[ "${VERSION_RESOURCE_TYPE[$v]}" == "modern" ]]; then
resource_tag="${CYAN}(8G RAM)${RESET}"
else
resource_tag="${DIM}(2G RAM)${RESET}"
fi
printf " %-10s %-28s %s %s\n" "$v" "${VERSION_DISPLAY_NAMES[$v]}" "$resource_tag" "$status"
fi
done
done
echo ""
}
cmd_inspect() {
local version="${1:-}"
if [[ -z "$version" ]]; then
die "Usage: ${SCRIPT_NAME} inspect <version>"
fi
validate_version "$version" || exit 1
header "Container Details: $version"
echo ""
echo -e " ${BOLD}Version:${RESET} $version"
echo -e " ${BOLD}Name:${RESET} ${VERSION_DISPLAY_NAMES[$version]}"
echo -e " ${BOLD}Category:${RESET} ${VERSION_CATEGORIES[$version]}"
echo -e " ${BOLD}Status:${RESET} $(get_status "$version")"
echo -e " ${BOLD}Web Port:${RESET} ${VERSION_PORTS_WEB[$version]}"
echo -e " ${BOLD}RDP Port:${RESET} ${VERSION_PORTS_RDP[$version]}"
echo -e " ${BOLD}Resources:${RESET} ${VERSION_RESOURCE_TYPE[$version]}"
echo -e " ${BOLD}Compose:${RESET} ${VERSION_COMPOSE_FILES[$version]}"
echo ""
if container_exists "$version"; then
echo -e " ${BOLD}Docker Info:${RESET}"
docker inspect "$version" --format '
Image: {{.Config.Image}}
Created: {{.Created}}
IP Address: {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}
Mounts: {{range .Mounts}}{{.Source}} -> {{.Destination}}
{{end}}' 2>/dev/null || true
fi
echo ""
}
cmd_monitor() {
local interval="${1:-5}"
if ! [[ "$interval" =~ ^[0-9]+$ ]]; then
die "Interval must be a number (seconds)"
fi
header "Real-time Monitor (refresh: ${interval}s)"
echo " Press Ctrl+C to exit"
echo ""
while true; do
clear
echo -e "${BOLD}${CYAN}Windows Container Monitor${RESET} - $(date '+%Y-%m-%d %H:%M:%S')"
echo -e "${DIM}$(printf '─%.0s' {1..70})${RESET}"
local running_count=0
local stopped_count=0
local total_count=0
table_header
for v in "${ALL_VERSIONS[@]}"; do
local status
status=$(get_status "$v")
if [[ "$status" != "not created" ]]; then
((total_count++))
if [[ "$status" == "running" ]]; then
((running_count++))
else
((stopped_count++))
fi
table_row "$v" "${VERSION_DISPLAY_NAMES[$v]}" "$status" "${VERSION_PORTS_WEB[$v]}" "${VERSION_PORTS_RDP[$v]}"
fi
done
if [[ $total_count -eq 0 ]]; then
echo -e " ${DIM}No containers found${RESET}"
fi
echo ""
echo -e " ${BOLD}Summary:${RESET} ${GREEN}$running_count running${RESET}, ${RED}$stopped_count stopped${RESET}, $total_count total"
echo ""
echo -e " ${DIM}Refreshing in ${interval}s... (Ctrl+C to exit)${RESET}"
sleep "$interval"
done
}
cmd_check() {
run_all_checks
}
# ==============================================================================
# HELP
# ==============================================================================
show_usage() {
cat << EOF
${BOLD}${SCRIPT_NAME}${RESET} v${SCRIPT_VERSION} - Windows Docker Container Management
${BOLD}USAGE${RESET}
${SCRIPT_NAME} <command> [options]
${BOLD}COMMANDS${RESET}
${BOLD}start${RESET} [version...] Start container(s), interactive if no version
${BOLD}stop${RESET} [version...] Stop container(s) with 2-min grace period
${BOLD}restart${RESET} [version...] Restart container(s)
${BOLD}status${RESET} [version...] Show status of container(s)
${BOLD}logs${RESET} <version> [-f] View container logs (-f to follow)
${BOLD}shell${RESET} <version> Open bash shell in container
${BOLD}stats${RESET} [version...] Show real-time resource usage
${BOLD}build${RESET} Build Docker image locally
${BOLD}rebuild${RESET} [version...] Destroy and recreate container(s)
${BOLD}list${RESET} [category] List versions (desktop/legacy/server/tiny/all)
${BOLD}inspect${RESET} <version> Show detailed container info
${BOLD}monitor${RESET} [interval] Real-time dashboard (default: 5s refresh)
${BOLD}check${RESET} Run prerequisites check
${BOLD}help${RESET} Show this help message
${BOLD}CATEGORIES${RESET}
desktop Win 11/10/8.1/7 (Pro, Enterprise, LTSC variants)
legacy Vista, XP, 2000
server Server 2025/2022/2019/2016/2012/2008/2003
tiny Tiny11, Tiny10
${BOLD}EXAMPLES${RESET}
${SCRIPT_NAME} start # Interactive menu
${SCRIPT_NAME} start win11 # Start Windows 11
${SCRIPT_NAME} start win11 win10 # Start multiple
${SCRIPT_NAME} stop win11 # Stop with confirmation
${SCRIPT_NAME} status # Show all containers
${SCRIPT_NAME} logs win11 -f # Follow logs
${SCRIPT_NAME} list desktop # List desktop versions
${SCRIPT_NAME} monitor 10 # Dashboard with 10s refresh
${SCRIPT_NAME} rebuild win11 # Recreate container
${BOLD}PORTS${RESET}
Each version has unique ports for Web UI and RDP access.
Run '${SCRIPT_NAME} list' to see port mappings.
EOF
}
# ==============================================================================
# MAIN
# ==============================================================================
main() {
# Change to script directory
cd "$SCRIPT_DIR"
local command="${1:-}"
shift || true
case "$command" in
start) cmd_start "$@" ;;
stop) cmd_stop "$@" ;;
restart) cmd_restart "$@" ;;
status) cmd_status "$@" ;;
logs) cmd_logs "$@" ;;
shell) cmd_shell "$@" ;;
stats) cmd_stats "$@" ;;
build) cmd_build "$@" ;;
rebuild) cmd_rebuild "$@" ;;
list) cmd_list "$@" ;;
inspect) cmd_inspect "$@" ;;
monitor) cmd_monitor "$@" ;;
check) cmd_check "$@" ;;
help|--help|-h)
show_usage
;;
"")
show_usage
exit 1
;;
*)
error "Unknown command: $command"
echo "Run '${SCRIPT_NAME} help' for usage information"
exit 1
;;
esac
}
main "$@"