feat: Add auto-cache, fix cache restore, refactor help system

- Add AUTO_CACHE=Y/N setting in .env to auto-cache ISOs on stop
- Fix cache restore to copy metadata files (windows.base, windows.ver,
  windows.mode, windows.type, windows.args) alongside ISOs so the
  container recognizes them as already processed and skips re-download
- Refactor show_usage() into topic-based help with interactive menu
  (commands, instances, cache, examples, config) and aligned columns
- Fix clean --data to unregister instances and remove compose files
- Update WINCTL_GUIDE.md and readme.md with all changes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Michel Abboud 2026-01-30 12:04:18 +00:00
parent 40b5466a77
commit 04d909acd7
4 changed files with 361 additions and 71 deletions

View File

@ -24,3 +24,7 @@
# RESTART_POLICY=on-failure # Restart policy (no, on-failure, always, unless-stopped) # RESTART_POLICY=on-failure # Restart policy (no, on-failure, always, unless-stopped)
# DEBUG=N # Debug mode (Y/N) # DEBUG=N # Debug mode (Y/N)
# WINDOWS_IMAGE=dockurr/windows # Docker image (dockurr/windows-arm for ARM64) # WINDOWS_IMAGE=dockurr/windows # Docker image (dockurr/windows-arm for ARM64)
#
# winctl.sh settings (place in .env file):
#
# AUTO_CACHE=N # Auto-cache ISOs on stop (Y/N)

View File

@ -532,7 +532,7 @@ Remove stopped containers and optionally purge their data directories.
./winctl.sh clean --data ./winctl.sh clean --data
``` ```
Requires typing `yes` to confirm. Shows freed disk space on completion. Requires typing `yes` to confirm. Shows freed disk space on completion. Stopped instances are automatically unregistered and their compose files removed.
--- ---
@ -706,7 +706,7 @@ Windows ISOs are large (3-6 GB) and re-downloaded every time a new container is
2. Cache the ISO: `./winctl.sh cache save winxp` 2. Cache the ISO: `./winctl.sh cache save winxp`
3. Create new instances — cached ISOs are auto-restored: `./winctl.sh start winxp --new` 3. Create new instances — cached ISOs are auto-restored: `./winctl.sh start winxp --new`
When creating a new instance with `--new`, winctl checks `cache/<version>/` for ISOs and copies them into the new instance's data directory before starting the container. The container sees the ISO on startup and skips the download. When creating a new instance with `--new`, winctl checks `cache/<version>/` for ISOs and metadata files, then copies them into the new instance's data directory before starting the container. The container recognizes the ISO as already processed and boots directly — no download or re-extraction needed.
### Caching an ISO ### Caching an ISO
@ -716,7 +716,7 @@ When creating a new instance with `--new`, winctl checks `cache/<version>/` for
./winctl.sh cache save win11 ./winctl.sh cache save win11
``` ```
The ISOs are copied from `data/<name>/` to `cache/<base-version>/`. The ISOs and metadata files (`windows.base`, `windows.ver`, `windows.mode`, `windows.type`, `windows.args`) are copied from `data/<name>/` to `cache/<base-version>/`.
### Listing Cached ISOs ### Listing Cached ISOs
@ -743,25 +743,43 @@ Both commands require typing `yes` to confirm.
When creating a new instance with `--new` (without `--clone`), winctl automatically checks the cache: When creating a new instance with `--new` (without `--clone`), winctl automatically checks the cache:
```bash ```bash
# If cache/winxp/ has ISOs, they are copied to data/winxp-1/ before start # If cache/winxp/ has ISOs + metadata, they are copied to data/winxp-1/ before start
./winctl.sh start winxp --new ./winctl.sh start winxp --new
``` ```
This is skipped when using `--clone`, since cloning copies all data from the base version including any ISOs. Both the ISO and metadata files are restored so the container recognizes the ISO as already processed. This is skipped when using `--clone`, since cloning copies all data from the base version including any ISOs.
### Auto-Cache on Stop
To automatically cache ISOs whenever a container is stopped, add `AUTO_CACHE=Y` to your `.env` file:
```bash
# .env
AUTO_CACHE=Y
```
When enabled, `winctl.sh stop` will silently cache any ISOs found in the stopped container's data directory. ISOs that are already cached are skipped. This removes the need to manually run `cache save` after the first download.
### Cache Directory Structure ### Cache Directory Structure
``` ```
cache/ cache/
├── winxp/ ├── winxp/
│ └── custom.iso │ ├── winxpx86.iso
│ ├── windows.base
│ ├── windows.ver
│ ├── windows.mode
│ ├── windows.type
│ └── windows.args
├── win11/ ├── win11/
│ └── win11x64.iso │ ├── win11x64.iso
│ └── ...
└── win10/ └── win10/
└── win10x64.iso ├── win10x64.iso
└── ...
``` ```
> **Note:** The cache stores processed ISOs from the container's data directory, not raw downloads. > **Note:** The cache stores processed ISOs and their metadata from the container's data directory, not raw downloads. Metadata files tell the container the ISO is already processed, so it boots directly without re-extracting or re-downloading.
--- ---
@ -823,6 +841,7 @@ DEBUG=N
| `RESTART_POLICY` | Container restart policy | on-failure | | `RESTART_POLICY` | Container restart policy | on-failure |
| `DEBUG` | Debug mode | N | | `DEBUG` | Debug mode | N |
| `WINDOWS_IMAGE` | Docker image | dockurr/windows | | `WINDOWS_IMAGE` | Docker image | dockurr/windows |
| `AUTO_CACHE` | Auto-cache ISOs on stop (in `.env`) | N |
### Restart Policy Options ### Restart Policy Options
@ -1229,9 +1248,17 @@ For servers, you can start VMs and access only via RDP:
## Getting Help ## Getting Help
```bash ```bash
# Show all commands # Show commands + interactive topic menu
./winctl.sh help ./winctl.sh help
# Jump to a specific topic
./winctl.sh help commands # Full command reference
./winctl.sh help instances # Multi-instance support
./winctl.sh help cache # ISO cache management
./winctl.sh help examples # Usage examples
./winctl.sh help config # Environment settings
./winctl.sh help all # Show everything
# Check system requirements # Check system requirements
./winctl.sh check ./winctl.sh check
@ -1239,4 +1266,6 @@ For servers, you can start VMs and access only via RDP:
./winctl.sh list ./winctl.sh list
``` ```
When run interactively, `help` shows a numbered menu to browse topics. When piped or run in a script, it prints the command summary only.
For issues, visit: https://github.com/dockur/windows/issues For issues, visit: https://github.com/dockur/windows/issues

View File

@ -134,6 +134,7 @@ Use `winctl.sh` for easy container management:
./winctl.sh cache list # Show cached ISOs ./winctl.sh cache list # Show cached ISOs
./winctl.sh cache rm winxp # Remove cached winxp ISO ./winctl.sh cache rm winxp # Remove cached winxp ISO
./winctl.sh cache flush # Clear all cached ISOs ./winctl.sh cache flush # Clear all cached ISOs
# Or set AUTO_CACHE=Y in .env to cache ISOs automatically on stop
# Full help (includes ARM64 info) # Full help (includes ARM64 info)
./winctl.sh help ./winctl.sh help

378
winctl.sh
View File

@ -182,6 +182,14 @@ readonly MODERN_DISK_GB=128
readonly LEGACY_RAM_GB=2 readonly LEGACY_RAM_GB=2
readonly LEGACY_DISK_GB=32 readonly LEGACY_DISK_GB=32
# Winctl settings (loaded from .env)
AUTO_CACHE="N"
if [[ -f "$SCRIPT_DIR/.env" ]]; then
_val=$(grep -E '^AUTO_CACHE=' "$SCRIPT_DIR/.env" 2>/dev/null | tail -1 | cut -d'=' -f2- || true)
[[ -n "$_val" ]] && AUTO_CACHE="$_val"
unset _val
fi
# ============================================================================== # ==============================================================================
# OUTPUT HELPERS # OUTPUT HELPERS
# ============================================================================== # ==============================================================================
@ -1081,16 +1089,19 @@ cmd_start() {
if [[ -z "$existing_isos" ]]; then if [[ -z "$existing_isos" ]]; then
local cache_src="$ISO_CACHE_DIR/$RESOLVED_BASE" local cache_src="$ISO_CACHE_DIR/$RESOLVED_BASE"
if [[ -d "$cache_src" ]]; then if [[ -d "$cache_src" ]]; then
local cached_isos local cached_iso
cached_isos=$(find "$cache_src" -maxdepth 1 -name '*.iso' -type f 2>/dev/null || true) cached_iso=$(find "$cache_src" -maxdepth 1 -name '*.iso' -type f -print -quit 2>/dev/null || true)
if [[ -n "$cached_isos" ]]; then if [[ -n "$cached_iso" ]]; then
info "Restoring cached ISO for $RESOLVED_BASE..." # Keep original filename — the container's parseVersion
cp "$cache_src"/*.iso "$data_dir/" # determines the expected name (e.g. winxpx86.iso, not xp.iso)
# Reset magic byte so the container re-processes the ISO local iso_name
# (installs from it) instead of trying to boot a missing disk iso_name=$(basename "$cached_iso")
local restored_iso info "Restoring cached ISO: $iso_name..."
for restored_iso in "$data_dir"/*.iso; do cp "$cached_iso" "$data_dir/$iso_name"
printf '\x00' | dd of="$restored_iso" bs=1 seek=0 count=1 conv=notrunc status=none 2>/dev/null || true # Restore metadata so the container recognizes the ISO as processed
local _meta
for _meta in windows.base windows.ver windows.mode windows.type windows.args; do
[[ -f "$cache_src/$_meta" ]] && cp "$cache_src/$_meta" "$data_dir/$_meta"
done done
success "ISO restored from cache (skipping download)" success "ISO restored from cache (skipping download)"
fi fi
@ -1217,6 +1228,13 @@ cmd_stop() {
fi fi
done done
# Auto-cache ISOs if enabled
if [[ "${AUTO_CACHE^^}" == "Y" ]]; then
for v in "${versions[@]}"; do
auto_cache_save "$v"
done
fi
# Refresh cache after state changes # Refresh cache after state changes
invalidate_cache invalidate_cache
} }
@ -2222,6 +2240,12 @@ cmd_clean() {
rm -rf "${SCRIPT_DIR:?}/data/$v" rm -rf "${SCRIPT_DIR:?}/data/$v"
info "Deleted data/$v/" info "Deleted data/$v/"
fi fi
# Unregister instances and remove their compose files
if is_instance "$v"; then
rm -f "$INSTANCE_DIR/${v}.yml"
unregister_instance "$v"
info "Unregistered instance $v"
fi
done done
local freed_after local freed_after
@ -2370,15 +2394,19 @@ cmd_new() {
# Pre-populate from ISO cache if available (skip if cloning) # Pre-populate from ISO cache if available (skip if cloning)
local cache_src="$ISO_CACHE_DIR/$version" local cache_src="$ISO_CACHE_DIR/$version"
if [[ -d "$cache_src" ]]; then if [[ -d "$cache_src" ]]; then
local iso_files local cached_iso
iso_files=$(find "$cache_src" -maxdepth 1 -name '*.iso' -type f 2>/dev/null || true) cached_iso=$(find "$cache_src" -maxdepth 1 -name '*.iso' -type f -print -quit 2>/dev/null || true)
if [[ -n "$iso_files" ]]; then if [[ -n "$cached_iso" ]]; then
info "Restoring cached ISO for $version..." # Keep original filename — the container's parseVersion
cp "$cache_src"/*.iso "$data_dir/" # determines the expected name (e.g. winxpx86.iso, not xp.iso)
# Reset magic byte so the container re-processes the ISO local iso_name
local restored_iso iso_name=$(basename "$cached_iso")
for restored_iso in "$data_dir"/*.iso; do info "Restoring cached ISO: $iso_name..."
printf '\x00' | dd of="$restored_iso" bs=1 seek=0 count=1 conv=notrunc status=none 2>/dev/null || true cp "$cached_iso" "$data_dir/$iso_name"
# Restore metadata so the container recognizes the ISO as processed
local _meta
for _meta in windows.base windows.ver windows.mode windows.type windows.args; do
[[ -f "$cache_src/$_meta" ]] && cp "$cache_src/$_meta" "$data_dir/$_meta"
done done
success "ISO restored from cache (skipping download)" success "ISO restored from cache (skipping download)"
fi fi
@ -2569,6 +2597,38 @@ cmd_instances() {
# ISO CACHE # ISO CACHE
# ============================================================================== # ==============================================================================
# Non-interactive cache save — silently skips if no ISOs or already cached.
# Used by cmd_stop() when AUTO_CACHE=Y.
auto_cache_save() {
local target="$1"
resolve_target "$target" || return 0
local data_dir="$SCRIPT_DIR/data/$RESOLVED_NAME"
local iso_files
iso_files=$(find "$data_dir" -maxdepth 1 -name '*.iso' -type f 2>/dev/null || true)
[[ -z "$iso_files" ]] && return 0
local cache_dest="$ISO_CACHE_DIR/$RESOLVED_BASE"
mkdir -p "$cache_dest"
while IFS= read -r iso; do
local filename
filename=$(basename "$iso")
if [[ ! -f "$cache_dest/$filename" ]]; then
cp "$iso" "$cache_dest/$filename"
info "Auto-cached ISO: $filename"
fi
done <<< "$iso_files"
# Cache metadata files so the container recognizes the ISO as processed
local meta
for meta in windows.base windows.ver windows.mode windows.type windows.args; do
if [[ -f "$data_dir/$meta" ]] && [[ ! -f "$cache_dest/$meta" ]]; then
cp "$data_dir/$meta" "$cache_dest/$meta"
fi
done
}
cmd_cache() { cmd_cache() {
local subcmd="${1:-}" local subcmd="${1:-}"
shift || true shift || true
@ -2637,6 +2697,14 @@ cmd_cache_save() {
((count++)) ((count++))
done <<< "$iso_files" done <<< "$iso_files"
# Cache metadata files so the container recognizes the ISO as processed
local meta
for meta in windows.base windows.ver windows.mode windows.type windows.args; do
if [[ -f "$data_dir/$meta" ]] && [[ ! -f "$cache_dest/$meta" ]]; then
cp "$data_dir/$meta" "$cache_dest/$meta"
fi
done
printf '\n' printf '\n'
success "Cached $count ISO file(s) to cache/$RESOLVED_BASE/" success "Cached $count ISO file(s) to cache/$RESOLVED_BASE/"
printf '\n' printf '\n'
@ -2782,42 +2850,143 @@ cmd_cache_flush() {
# HELP # HELP
# ============================================================================== # ==============================================================================
# Print a help row: _help_row "cmd args" "description"
# Uses fixed-width column so descriptions align regardless of ANSI bold codes.
_help_row() {
local cmd="$1" desc="$2"
printf ' %b%-24s%b%s\n' "${BOLD}" "$cmd" "${RESET}" "$desc"
}
show_usage() { show_usage() {
local topic="${1:-}"
case "$topic" in
commands) _help_topic_commands ;;
instances) _help_topic_instances ;;
cache) _help_topic_cache ;;
examples) _help_topic_examples ;;
config) _help_topic_config ;;
all) _help_all ;;
"") _help_summary ;;
*)
error "Unknown help topic: $topic"
printf '%s\n' " Available topics: commands, instances, cache, examples, config, all"
exit 1
;;
esac
}
_help_summary() {
printf '%b\n' "${BOLD}${SCRIPT_NAME}${RESET} v${SCRIPT_VERSION} - Windows Docker Container Management" printf '%b\n' "${BOLD}${SCRIPT_NAME}${RESET} v${SCRIPT_VERSION} - Windows Docker Container Management"
printf '\n' printf '\n'
printf '%b\n' "${BOLD}USAGE${RESET}" printf '%b\n' "${BOLD}USAGE${RESET}"
printf ' %s <command> [options]\n' "${SCRIPT_NAME}" printf ' %s <command> [options]\n' "${SCRIPT_NAME}"
printf '\n' printf '\n'
printf '%b\n' "${BOLD}COMMANDS${RESET}" printf '%b\n' "${BOLD}COMMANDS${RESET}"
printf ' %b [version...] Start container(s), interactive if no version\n' "${BOLD}start${RESET}" _help_row "start [version...]" "Start container(s), interactive if no version"
printf ' %b [version...|all] Stop container(s) or all running\n' "${BOLD}stop${RESET}" _help_row "stop [version...|all]" "Stop container(s) or all running"
printf ' %b [version...] Restart container(s)\n' "${BOLD}restart${RESET}" _help_row "restart [version...]" "Restart container(s)"
printf ' %b [version...] Show status of container(s)\n' "${BOLD}status${RESET}" _help_row "status [version...]" "Show status of container(s)"
printf ' %b <version> [-f] View container logs (-f to follow)\n' "${BOLD}logs${RESET}" _help_row "logs <version> [-f]" "View container logs (-f to follow)"
printf ' %b <version> Open bash shell in container\n' "${BOLD}shell${RESET}" _help_row "shell <version>" "Open bash shell in container"
printf ' %b [version...] Show real-time resource usage\n' "${BOLD}stats${RESET}" _help_row "stats [version...]" "Show real-time resource usage"
printf ' %b Build Docker image locally\n' "${BOLD}build${RESET}" _help_row "build" "Build Docker image locally"
printf ' %b [version...] Destroy and recreate container(s)\n' "${BOLD}rebuild${RESET}" _help_row "rebuild [version...]" "Destroy and recreate container(s)"
printf ' %b [category] List versions (desktop/legacy/server/tiny/all)\n' "${BOLD}list${RESET}" _help_row "list [category]" "List versions (desktop/legacy/server/tiny/all)"
printf ' %b <version> Show detailed container info\n' "${BOLD}inspect${RESET}" _help_row "inspect <version>" "Show detailed container info"
printf ' %b [interval] Real-time dashboard (default: 5s refresh)\n' "${BOLD}monitor${RESET}" _help_row "monitor [interval]" "Real-time dashboard (default: 5s refresh)"
printf ' %b Run prerequisites check\n' "${BOLD}check${RESET}" _help_row "check" "Run prerequisites check"
printf ' %b Force refresh status cache\n' "${BOLD}refresh${RESET}" _help_row "refresh" "Force refresh status cache"
printf ' %b <version> Open web viewer in browser\n' "${BOLD}open${RESET}" _help_row "open <version>" "Open web viewer in browser"
printf ' %b Pull latest Docker image\n' "${BOLD}pull${RESET}" _help_row "pull" "Pull latest Docker image"
printf ' %b [version...] Show disk usage per VM\n' "${BOLD}disk${RESET}" _help_row "disk [version...]" "Show disk usage per VM"
printf ' %b <ver> [name] Back up VM data directory\n' "${BOLD}snapshot${RESET}" _help_row "snapshot <ver> [name]" "Back up VM data directory"
printf ' %b <ver> [name] Restore VM data from snapshot\n' "${BOLD}restore${RESET}" _help_row "restore <ver> [name]" "Restore VM data from snapshot"
printf ' %b [--data] Remove stopped containers\n' "${BOLD}clean${RESET}" _help_row "clean [--data]" "Remove stopped containers"
printf ' %b <instance> Permanently remove an instance\n' "${BOLD}destroy${RESET}" _help_row "destroy <instance>" "Permanently remove an instance"
printf ' %b [base] List all registered instances\n' "${BOLD}instances${RESET}" _help_row "instances [base]" "List all registered instances"
printf ' %b <sub> Manage ISO cache (save/list/rm/flush)\n' "${BOLD}cache${RESET}" _help_row "cache <sub>" "Manage ISO cache (save/list/rm/flush)"
printf ' %b Show this help message\n' "${BOLD}help${RESET}" _help_row "help [topic]" "Show help (topics below, or 'all')"
printf '\n' printf '\n'
printf '%b\n' "${BOLD}INSTANCE FLAGS (used with start)${RESET}" printf '%b\n' "${BOLD}QUICK START${RESET}"
printf ' %b Create a new instance of a version\n' "${BOLD}--new${RESET}" printf ' %s start win11 # Start Windows 11\n' "${SCRIPT_NAME}"
printf ' %b [name] Name the instance (default: auto-numbered)\n' "${BOLD}--new${RESET}" printf ' %s status # Show all containers\n' "${SCRIPT_NAME}"
printf ' %b Clone data from base version to new instance\n' "${BOLD}--clone${RESET}" printf ' %s stop win11 # Stop with confirmation\n' "${SCRIPT_NAME}"
printf '\n'
# Interactive menu only when running directly in a terminal
if _is_interactive; then
_help_interactive_menu
else
printf ' Topics: commands, instances, cache, examples, config, all\n'
printf '\n'
fi
}
# Check if the script is running interactively (not piped, not inside
# another script, not in a CI/batch environment).
_is_interactive() {
[[ -t 0 ]] && [[ -t 1 ]] || return 1
[[ "${TERM:-dumb}" != "dumb" ]] || return 1
[[ -z "${CI:-}" ]] && [[ -z "${BATCH:-}" ]] && [[ -z "${NONINTERACTIVE:-}" ]] || return 1
return 0
}
_help_interactive_menu() {
while true; do
printf '%b\n' "${BOLD}MORE HELP${RESET}"
printf ' 1) Commands Full command reference\n'
printf ' 2) Instances Multi-instance support\n'
printf ' 3) Cache ISO cache management\n'
printf ' 4) Examples Usage examples\n'
printf ' 5) Config Environment settings\n'
printf ' 6) All Show everything\n'
printf '\n'
printf '%s' " Select [1-6] or Enter to exit: "
local choice
read -r choice
case "$choice" in
1) printf '\n'; _help_topic_commands ;;
2) printf '\n'; _help_topic_instances ;;
3) printf '\n'; _help_topic_cache ;;
4) printf '\n'; _help_topic_examples ;;
5) printf '\n'; _help_topic_config ;;
6) printf '\n'; _help_all; return 0 ;;
"") return 0 ;;
*) warn "Invalid choice: $choice"; printf '\n' ;;
esac
done
}
_help_topic_commands() {
printf '%b\n' "${BOLD}COMMANDS${RESET}"
printf '\n'
_help_row "start [version...]" "Start container(s), interactive if no version"
_help_row "stop [version...|all]" "Stop container(s) or all running"
_help_row "restart [version...]" "Restart container(s)"
_help_row "status [version...]" "Show status of container(s)"
_help_row "logs <version> [-f]" "View container logs (-f to follow)"
_help_row "shell <version>" "Open bash shell in container"
_help_row "stats [version...]" "Show real-time resource usage"
_help_row "build" "Build Docker image locally"
_help_row "rebuild [version...]" "Destroy and recreate container(s)"
_help_row "list [category]" "List versions (desktop/legacy/server/tiny/all)"
_help_row "inspect <version>" "Show detailed container info"
_help_row "monitor [interval]" "Real-time dashboard (default: 5s refresh)"
_help_row "check" "Run prerequisites check"
_help_row "refresh" "Force refresh status cache"
_help_row "open <version>" "Open web viewer in browser"
_help_row "pull" "Pull latest Docker image"
_help_row "disk [version...]" "Show disk usage per VM"
_help_row "snapshot <ver> [name]" "Back up VM data directory"
_help_row "restore <ver> [name]" "Restore VM data from snapshot"
_help_row "clean [--data]" "Remove stopped containers"
_help_row "destroy <instance>" "Permanently remove an instance"
_help_row "instances [base]" "List all registered instances"
_help_row "cache <sub>" "Manage ISO cache (save/list/rm/flush)"
_help_row "help [topic]" "Show help (topics: commands, instances, cache, examples, config, all)"
printf '\n' printf '\n'
printf '%b\n' "${BOLD}CATEGORIES${RESET}" printf '%b\n' "${BOLD}CATEGORIES${RESET}"
printf ' desktop Win 11/10/8.1/7 (Pro, Enterprise, LTSC variants)\n' printf ' desktop Win 11/10/8.1/7 (Pro, Enterprise, LTSC variants)\n'
@ -2825,7 +2994,61 @@ show_usage() {
printf ' server Server 2025/2022/2019/2016/2012/2008/2003\n' printf ' server Server 2025/2022/2019/2016/2012/2008/2003\n'
printf ' tiny Tiny11, Tiny10\n' printf ' tiny Tiny11, Tiny10\n'
printf '\n' printf '\n'
printf '%b\n' "${BOLD}PORTS${RESET}"
printf ' Each version has unique ports for Web UI and RDP access.\n'
printf ' Instances auto-allocate ports from 9000+ (web) and 4000+ (RDP).\n'
printf " Run '%s list' to see port mappings.\n" "${SCRIPT_NAME}"
printf '\n'
printf '%b\n' "${BOLD}ARM64 SUPPORT${RESET}"
printf ' Auto-detected via uname. Only Win 10/11 variants supported on ARM64.\n'
printf ' Set WINDOWS_IMAGE=dockurr/windows-arm in .env.modern or .env.legacy.\n'
printf " Run '%s check' to see detected architecture.\n" "${SCRIPT_NAME}"
printf '\n'
}
_help_topic_instances() {
printf '%b\n' "${BOLD}INSTANCE FLAGS (used with start)${RESET}"
printf '\n'
_help_row "--new" "Create a new instance of a version"
_help_row "--new [name]" "Name the instance (default: auto-numbered)"
_help_row "--clone" "Clone data from base version to new instance"
printf '\n'
printf '%b\n' "${BOLD}INSTANCE EXAMPLES${RESET}"
printf '\n'
printf ' %s start winxp --new # Create winxp-1 with auto ports\n' "${SCRIPT_NAME}"
printf ' %s start winxp --new lab # Create winxp-lab\n' "${SCRIPT_NAME}"
printf ' %s start winxp --new lab --clone # Clone base data\n' "${SCRIPT_NAME}"
printf ' %s stop winxp-lab # Stop instance\n' "${SCRIPT_NAME}"
printf ' %s instances # List all instances\n' "${SCRIPT_NAME}"
printf ' %s destroy winxp-lab # Remove instance\n' "${SCRIPT_NAME}"
printf '\n'
}
_help_topic_cache() {
printf '%b\n' "${BOLD}CACHE COMMANDS${RESET}"
printf '\n'
_help_row "cache save <version>" "Cache ISOs from a VM data directory"
_help_row "cache list" "Show all cached ISOs with sizes"
_help_row "cache rm <version>" "Remove cached ISOs for a version"
_help_row "cache flush" "Clear all cached ISOs"
printf '\n'
printf '%b\n' "${BOLD}CACHE EXAMPLES${RESET}"
printf '\n'
printf ' %s cache save winxp # Cache ISO after first download\n' "${SCRIPT_NAME}"
printf ' %s cache list # Show cached ISOs\n' "${SCRIPT_NAME}"
printf ' %s cache rm winxp # Remove cached winxp ISO\n' "${SCRIPT_NAME}"
printf ' %s cache flush # Clear all cached ISOs\n' "${SCRIPT_NAME}"
printf '\n'
printf '%b\n' "${BOLD}AUTO-CACHE${RESET}"
printf '\n'
printf ' Set AUTO_CACHE=Y in .env to automatically cache ISOs on stop.\n'
printf ' Cached ISOs are auto-restored when creating new instances with --new.\n'
printf '\n'
}
_help_topic_examples() {
printf '%b\n' "${BOLD}EXAMPLES${RESET}" printf '%b\n' "${BOLD}EXAMPLES${RESET}"
printf '\n'
printf ' %s start # Interactive menu\n' "${SCRIPT_NAME}" printf ' %s start # Interactive menu\n' "${SCRIPT_NAME}"
printf ' %s start win11 # Start Windows 11\n' "${SCRIPT_NAME}" printf ' %s start win11 # Start Windows 11\n' "${SCRIPT_NAME}"
printf ' %s start win11 win10 # Start multiple\n' "${SCRIPT_NAME}" printf ' %s start win11 win10 # Start multiple\n' "${SCRIPT_NAME}"
@ -2844,29 +3067,62 @@ show_usage() {
printf ' %s clean # Remove stopped containers\n' "${SCRIPT_NAME}" printf ' %s clean # Remove stopped containers\n' "${SCRIPT_NAME}"
printf '\n' printf '\n'
printf '%b\n' "${BOLD}INSTANCE EXAMPLES${RESET}" printf '%b\n' "${BOLD}INSTANCE EXAMPLES${RESET}"
printf ' %s start winxp --new # Create winxp-1 with auto ports\n' "${SCRIPT_NAME}" printf '\n'
printf ' %s start winxp --new lab # Create winxp-lab\n' "${SCRIPT_NAME}" printf ' %s start winxp --new # Create winxp-1 with auto ports\n' "${SCRIPT_NAME}"
printf ' %s start winxp --new lab # Create winxp-lab\n' "${SCRIPT_NAME}"
printf ' %s start winxp --new lab --clone # Clone base data\n' "${SCRIPT_NAME}" printf ' %s start winxp --new lab --clone # Clone base data\n' "${SCRIPT_NAME}"
printf ' %s stop winxp-lab # Stop instance\n' "${SCRIPT_NAME}" printf ' %s stop winxp-lab # Stop instance\n' "${SCRIPT_NAME}"
printf ' %s instances # List all instances\n' "${SCRIPT_NAME}" printf ' %s instances # List all instances\n' "${SCRIPT_NAME}"
printf ' %s destroy winxp-lab # Remove instance\n' "${SCRIPT_NAME}" printf ' %s destroy winxp-lab # Remove instance\n' "${SCRIPT_NAME}"
printf '\n' printf '\n'
printf '%b\n' "${BOLD}CACHE EXAMPLES${RESET}" printf '%b\n' "${BOLD}CACHE EXAMPLES${RESET}"
printf '\n'
printf ' %s cache save winxp # Cache ISO after first download\n' "${SCRIPT_NAME}" printf ' %s cache save winxp # Cache ISO after first download\n' "${SCRIPT_NAME}"
printf ' %s cache list # Show cached ISOs\n' "${SCRIPT_NAME}" printf ' %s cache list # Show cached ISOs\n' "${SCRIPT_NAME}"
printf ' %s cache rm winxp # Remove cached winxp ISO\n' "${SCRIPT_NAME}" printf ' %s cache rm winxp # Remove cached winxp ISO\n' "${SCRIPT_NAME}"
printf ' %s cache flush # Clear all cached ISOs\n' "${SCRIPT_NAME}" printf ' %s cache flush # Clear all cached ISOs\n' "${SCRIPT_NAME}"
printf '\n' printf '\n'
printf '%b\n' "${BOLD}PORTS${RESET}" }
printf ' Each version has unique ports for Web UI and RDP access.\n'
printf ' Instances auto-allocate ports from 9000+ (web) and 4000+ (RDP).\n' _help_topic_config() {
printf " Run '%s list' to see port mappings.\n" "${SCRIPT_NAME}" printf '%b\n' "${BOLD}CONFIGURATION${RESET}"
printf '\n' printf '\n'
printf '%b\n' "${BOLD}ARM64 SUPPORT${RESET}" printf ' Two env files control per-VM resources (used by compose files):\n'
printf ' Auto-detected via uname. Only Win 10/11 variants supported on ARM64.\n'
printf ' Set WINDOWS_IMAGE=dockurr/windows-arm in .env.modern or .env.legacy.\n'
printf " Run '%s check' to see detected architecture.\n" "${SCRIPT_NAME}"
printf '\n' printf '\n'
_help_row ".env.modern" "8G RAM, 4 CPU, 128G disk — Win 10/11, Server 2016+"
_help_row ".env.legacy" "2G RAM, 2 CPU, 32G disk — Win 7/8, Vista, XP, 2000, Tiny"
printf '\n'
printf ' Global winctl settings (in .env):\n'
printf '\n'
_help_row "AUTO_CACHE=Y|N" "Auto-cache ISOs on stop (default: N)"
printf '\n'
printf '%b\n' "${BOLD}VM SETTINGS (in .env.modern / .env.legacy)${RESET}"
printf '\n'
_help_row "RAM_SIZE" "Memory allocation (e.g. 8G)"
_help_row "CPU_CORES" "CPU cores (e.g. 4)"
_help_row "DISK_SIZE" "Virtual disk size (e.g. 128G)"
_help_row "USERNAME" "Windows username (default: Docker)"
_help_row "PASSWORD" "Windows password (default: admin)"
_help_row "LANGUAGE" "Installation language (default: en)"
_help_row "REGION" "Region setting (default: en-US)"
_help_row "KEYBOARD" "Keyboard layout (default: en-US)"
_help_row "WIDTH" "Display width (default: 1280)"
_help_row "HEIGHT" "Display height (default: 720)"
_help_row "DHCP" "Use DHCP networking (default: N)"
_help_row "SAMBA" "Enable file sharing (default: Y)"
_help_row "RESTART_POLICY" "Container restart policy (default: on-failure)"
_help_row "DEBUG" "Debug mode (default: N)"
_help_row "WINDOWS_IMAGE" "Docker image (default: dockurr/windows)"
printf '\n'
}
_help_all() {
_help_summary
_help_topic_commands
_help_topic_instances
_help_topic_cache
_help_topic_examples
_help_topic_config
} }
# ============================================================================== # ==============================================================================
@ -2905,7 +3161,7 @@ main() {
instances) cmd_instances "$@" ;; instances) cmd_instances "$@" ;;
cache) cmd_cache "$@" ;; cache) cmd_cache "$@" ;;
help|--help|-h) help|--help|-h)
show_usage show_usage "$@"
;; ;;
"") "")
show_usage show_usage