mirror of
https://github.com/dockur/windows.git
synced 2026-02-03 17:27:21 +00:00
feat: Add JSON file cache for container status
- Cache container statuses in ~/.cache/winctl/status.json - Auto-refresh if cache older than 7 days or data is stale - Add 'refresh' command to force cache refresh - Dramatically faster menus (single Docker call vs 44 calls) - Bump version to 1.1.0 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
b06f53cb17
commit
978abbe7ec
@ -6,12 +6,13 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
- **winctl.sh**: Management script for Windows Docker containers
|
- **winctl.sh**: Management script for Windows Docker containers
|
||||||
- 12 commands: start, stop, restart, status, logs, shell, stats, build, rebuild, list, inspect, monitor, check
|
- 13 commands: start, stop, restart, status, logs, shell, stats, build, rebuild, list, inspect, monitor, check, refresh
|
||||||
- Interactive menus for version selection
|
- Interactive menus for version selection
|
||||||
- Prerequisites checking (Docker, Compose, KVM, TUN, memory, disk)
|
- Prerequisites checking (Docker, Compose, KVM, TUN, memory, disk)
|
||||||
- Color-coded output with professional table formatting
|
- Color-coded output with professional table formatting
|
||||||
- Safety confirmations for destructive operations
|
- Safety confirmations for destructive operations
|
||||||
- Support for all 22 Windows versions across 4 categories
|
- Support for all 22 Windows versions across 4 categories
|
||||||
|
- JSON status cache (`~/.cache/winctl/status.json`) with auto-refresh
|
||||||
- Multi-version compose structure with organized folders (`compose/`)
|
- Multi-version compose structure with organized folders (`compose/`)
|
||||||
- Environment file configuration (`.env` / `.env.example`)
|
- Environment file configuration (`.env` / `.env.example`)
|
||||||
- Two resource profiles: modern (8G RAM, 4 CPU) and legacy (2G RAM, 2 CPU)
|
- Two resource profiles: modern (8G RAM, 4 CPU) and legacy (2G RAM, 2 CPU)
|
||||||
|
|||||||
183
winctl.sh
183
winctl.sh
@ -16,6 +16,11 @@ readonly SCRIPT_NAME="winctl"
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
readonly SCRIPT_DIR
|
readonly SCRIPT_DIR
|
||||||
|
|
||||||
|
# Cache settings
|
||||||
|
readonly CACHE_DIR="${HOME}/.cache/winctl"
|
||||||
|
readonly CACHE_FILE="${CACHE_DIR}/status.json"
|
||||||
|
readonly CACHE_MAX_AGE=$((7 * 24 * 60 * 60)) # 7 days in seconds
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# COLORS & TERMINAL DETECTION
|
# COLORS & TERMINAL DETECTION
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@ -321,12 +326,131 @@ compose_cmd() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cache for container statuses (populated by refresh_status_cache)
|
# ==============================================================================
|
||||||
|
# STATUS CACHE (JSON file-based with auto-refresh)
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# In-memory cache (loaded from JSON)
|
||||||
declare -A _STATUS_CACHE=()
|
declare -A _STATUS_CACHE=()
|
||||||
_STATUS_CACHE_VALID=false
|
_STATUS_CACHE_VALID=false
|
||||||
|
_STATUS_CACHE_TIMESTAMP=0
|
||||||
|
|
||||||
# Refresh the status cache with a single docker call
|
# Ensure cache directory exists
|
||||||
|
ensure_cache_dir() {
|
||||||
|
[[ -d "$CACHE_DIR" ]] || mkdir -p "$CACHE_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get cache file age in seconds (returns large number if file doesn't exist)
|
||||||
|
get_cache_age() {
|
||||||
|
if [[ -f "$CACHE_FILE" ]]; then
|
||||||
|
local file_time current_time
|
||||||
|
file_time=$(stat -c %Y "$CACHE_FILE" 2>/dev/null || stat -f %m "$CACHE_FILE" 2>/dev/null || echo 0)
|
||||||
|
current_time=$(date +%s)
|
||||||
|
echo $((current_time - file_time))
|
||||||
|
else
|
||||||
|
echo 999999999
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if cache needs refresh (age > max age)
|
||||||
|
cache_needs_refresh() {
|
||||||
|
local age
|
||||||
|
age=$(get_cache_age)
|
||||||
|
((age > CACHE_MAX_AGE))
|
||||||
|
}
|
||||||
|
|
||||||
|
# Write status cache to JSON file
|
||||||
|
write_cache_file() {
|
||||||
|
ensure_cache_dir
|
||||||
|
local timestamp
|
||||||
|
timestamp=$(date +%s)
|
||||||
|
|
||||||
|
# Build JSON manually (no jq dependency)
|
||||||
|
{
|
||||||
|
echo "{"
|
||||||
|
echo " \"timestamp\": $timestamp,"
|
||||||
|
echo " \"containers\": {"
|
||||||
|
local first=true
|
||||||
|
for name in "${!_STATUS_CACHE[@]}"; do
|
||||||
|
if [[ "$first" == "true" ]]; then
|
||||||
|
first=false
|
||||||
|
else
|
||||||
|
echo ","
|
||||||
|
fi
|
||||||
|
printf ' "%s": "%s"' "$name" "${_STATUS_CACHE[$name]}"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
echo " }"
|
||||||
|
echo "}"
|
||||||
|
} > "$CACHE_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read status cache from JSON file
|
||||||
|
read_cache_file() {
|
||||||
|
if [[ ! -f "$CACHE_FILE" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_STATUS_CACHE=()
|
||||||
|
_STATUS_CACHE_TIMESTAMP=0
|
||||||
|
|
||||||
|
# Parse JSON manually (no jq dependency)
|
||||||
|
local in_containers=false
|
||||||
|
while IFS= read -r line; do
|
||||||
|
# Extract timestamp
|
||||||
|
if [[ "$line" =~ \"timestamp\":[[:space:]]*([0-9]+) ]]; then
|
||||||
|
_STATUS_CACHE_TIMESTAMP="${BASH_REMATCH[1]}"
|
||||||
|
fi
|
||||||
|
# Track when we're in containers section
|
||||||
|
if [[ "$line" =~ \"containers\" ]]; then
|
||||||
|
in_containers=true
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# Parse container entries
|
||||||
|
if [[ "$in_containers" == "true" && "$line" =~ \"([^\"]+)\":[[:space:]]*\"([^\"]+)\" ]]; then
|
||||||
|
local name="${BASH_REMATCH[1]}"
|
||||||
|
local state="${BASH_REMATCH[2]}"
|
||||||
|
_STATUS_CACHE["$name"]="$state"
|
||||||
|
fi
|
||||||
|
done < "$CACHE_FILE"
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate cache by spot-checking a running container still exists
|
||||||
|
validate_cache() {
|
||||||
|
# If cache shows a container as running, verify it still exists
|
||||||
|
for name in "${!_STATUS_CACHE[@]}"; do
|
||||||
|
if [[ "${_STATUS_CACHE[$name]}" == "running" ]]; then
|
||||||
|
# Quick check if this container exists
|
||||||
|
if ! docker ps -q --filter "name=^${name}$" 2>/dev/null | grep -q .; then
|
||||||
|
return 1 # Cache is stale
|
||||||
|
fi
|
||||||
|
return 0 # Found a valid running container
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 0 # No running containers to validate
|
||||||
|
}
|
||||||
|
|
||||||
|
# Refresh the status cache from Docker and save to file
|
||||||
refresh_status_cache() {
|
refresh_status_cache() {
|
||||||
|
local force="${1:-false}"
|
||||||
|
|
||||||
|
# Try to load from file cache first (unless forced)
|
||||||
|
if [[ "$force" != "true" && "$_STATUS_CACHE_VALID" != "true" ]]; then
|
||||||
|
if read_cache_file; then
|
||||||
|
# Check if cache is still valid (not too old)
|
||||||
|
if ! cache_needs_refresh; then
|
||||||
|
# Validate cache data
|
||||||
|
if validate_cache; then
|
||||||
|
_STATUS_CACHE_VALID=true
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fetch fresh data from Docker
|
||||||
_STATUS_CACHE=()
|
_STATUS_CACHE=()
|
||||||
local line
|
local line
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
@ -338,6 +462,15 @@ refresh_status_cache() {
|
|||||||
fi
|
fi
|
||||||
done < <(docker ps -a --format '{{.Names}}:{{.State}}' 2>/dev/null)
|
done < <(docker ps -a --format '{{.Names}}:{{.State}}' 2>/dev/null)
|
||||||
_STATUS_CACHE_VALID=true
|
_STATUS_CACHE_VALID=true
|
||||||
|
|
||||||
|
# Save to file
|
||||||
|
write_cache_file
|
||||||
|
}
|
||||||
|
|
||||||
|
# Force refresh the cache (called after start/stop/restart operations)
|
||||||
|
invalidate_cache() {
|
||||||
|
_STATUS_CACHE_VALID=false
|
||||||
|
refresh_status_cache true
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if a container is running
|
# Check if a container is running
|
||||||
@ -595,6 +728,9 @@ cmd_start() {
|
|||||||
echo -e " → RDP: ${CYAN}localhost:${VERSION_PORTS_RDP[$v]}${RESET}"
|
echo -e " → RDP: ${CYAN}localhost:${VERSION_PORTS_RDP[$v]}${RESET}"
|
||||||
echo ""
|
echo ""
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Refresh cache after state changes
|
||||||
|
invalidate_cache
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_stop() {
|
cmd_stop() {
|
||||||
@ -650,6 +786,9 @@ cmd_stop() {
|
|||||||
error "Failed to stop $v"
|
error "Failed to stop $v"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Refresh cache after state changes
|
||||||
|
invalidate_cache
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_restart() {
|
cmd_restart() {
|
||||||
@ -684,6 +823,9 @@ cmd_restart() {
|
|||||||
error "Failed to restart $v"
|
error "Failed to restart $v"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Refresh cache after state changes
|
||||||
|
invalidate_cache
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_status() {
|
cmd_status() {
|
||||||
@ -846,6 +988,9 @@ cmd_rebuild() {
|
|||||||
error "Failed to rebuild $v"
|
error "Failed to rebuild $v"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Refresh cache after state changes
|
||||||
|
invalidate_cache
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_list() {
|
cmd_list() {
|
||||||
@ -979,6 +1124,38 @@ cmd_check() {
|
|||||||
run_all_checks
|
run_all_checks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd_refresh() {
|
||||||
|
header "Refreshing Status Cache"
|
||||||
|
|
||||||
|
info "Fetching container statuses from Docker..."
|
||||||
|
refresh_status_cache true
|
||||||
|
|
||||||
|
local count=${#_STATUS_CACHE[@]}
|
||||||
|
success "Cache refreshed (${count} containers found)"
|
||||||
|
|
||||||
|
# Show cache info
|
||||||
|
local age
|
||||||
|
age=$(get_cache_age)
|
||||||
|
echo ""
|
||||||
|
echo -e " ${BOLD}Cache Info:${RESET}"
|
||||||
|
echo -e " → File: ${CYAN}${CACHE_FILE}${RESET}"
|
||||||
|
echo -e " → Age: ${age} seconds"
|
||||||
|
echo -e " → Max Age: ${CACHE_MAX_AGE} seconds (7 days)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Show summary
|
||||||
|
local cnt_running=0 cnt_stopped=0 cnt_other=0
|
||||||
|
for state in "${_STATUS_CACHE[@]}"; do
|
||||||
|
case "$state" in
|
||||||
|
running) ((cnt_running++)) || true ;;
|
||||||
|
exited) ((cnt_stopped++)) || true ;;
|
||||||
|
*) ((cnt_other++)) || true ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
echo -e " ${BOLD}Containers:${RESET} ${GREEN}${cnt_running} running${RESET}, ${RED}${cnt_stopped} stopped${RESET}, ${DIM}${cnt_other} other${RESET}"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
# HELP
|
# HELP
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
@ -1003,6 +1180,7 @@ show_usage() {
|
|||||||
printf ' %b <version> Show detailed container info\n' "${BOLD}inspect${RESET}"
|
printf ' %b <version> Show detailed container info\n' "${BOLD}inspect${RESET}"
|
||||||
printf ' %b [interval] Real-time dashboard (default: 5s refresh)\n' "${BOLD}monitor${RESET}"
|
printf ' %b [interval] Real-time dashboard (default: 5s refresh)\n' "${BOLD}monitor${RESET}"
|
||||||
printf ' %b Run prerequisites check\n' "${BOLD}check${RESET}"
|
printf ' %b Run prerequisites check\n' "${BOLD}check${RESET}"
|
||||||
|
printf ' %b Force refresh status cache\n' "${BOLD}refresh${RESET}"
|
||||||
printf ' %b Show this help message\n' "${BOLD}help${RESET}"
|
printf ' %b Show this help message\n' "${BOLD}help${RESET}"
|
||||||
printf '\n'
|
printf '\n'
|
||||||
printf '%b\n' "${BOLD}CATEGORIES${RESET}"
|
printf '%b\n' "${BOLD}CATEGORIES${RESET}"
|
||||||
@ -1053,6 +1231,7 @@ main() {
|
|||||||
inspect) cmd_inspect "$@" ;;
|
inspect) cmd_inspect "$@" ;;
|
||||||
monitor) cmd_monitor "$@" ;;
|
monitor) cmd_monitor "$@" ;;
|
||||||
check) cmd_check "$@" ;;
|
check) cmd_check "$@" ;;
|
||||||
|
refresh) cmd_refresh "$@" ;;
|
||||||
help|--help|-h)
|
help|--help|-h)
|
||||||
show_usage
|
show_usage
|
||||||
;;
|
;;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user