747 lines
22 KiB
Bash
Executable File
747 lines
22 KiB
Bash
Executable File
#!/bin/bash
|
|
# Docker Stack Manager Script
|
|
# Combines stack management and update functionality
|
|
|
|
set -euo pipefail
|
|
|
|
# Version
|
|
VERSION="2.6.2"
|
|
|
|
# Resolve script directory and source shared library
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# shellcheck source=lib/common.sh
|
|
source "$SCRIPT_DIR/lib/common.sh"
|
|
|
|
# Enable debug trace after sourcing common.sh (so DEBUG variable is available)
|
|
enable_debug_trace
|
|
|
|
# Default configuration
|
|
LOG_FILE="${LOG_FILE:-/tmp/docwell/docker-manager.log}"
|
|
|
|
# CLI flags
|
|
LIST_ONLY=false
|
|
START_STACK=""
|
|
STOP_STACK=""
|
|
RESTART_STACK=""
|
|
STATUS_STACK=""
|
|
LOGS_STACK=""
|
|
CHECK_UPDATES=false
|
|
UPDATE_ALL=false
|
|
UPDATE_STACK=""
|
|
AUTO_UPDATE=false
|
|
|
|
# Load config file overrides
|
|
load_config
|
|
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--version)
|
|
echo "docker-manager.sh v$VERSION"
|
|
exit 0
|
|
;;
|
|
--quiet|-q)
|
|
QUIET=true
|
|
shift
|
|
;;
|
|
--yes|-y)
|
|
YES=true
|
|
shift
|
|
;;
|
|
--list|-l)
|
|
LIST_ONLY=true
|
|
shift
|
|
;;
|
|
--start)
|
|
START_STACK="$2"
|
|
shift 2
|
|
;;
|
|
--stop)
|
|
STOP_STACK="$2"
|
|
shift 2
|
|
;;
|
|
--restart)
|
|
RESTART_STACK="$2"
|
|
shift 2
|
|
;;
|
|
--status)
|
|
STATUS_STACK="$2"
|
|
shift 2
|
|
;;
|
|
--logs)
|
|
LOGS_STACK="$2"
|
|
shift 2
|
|
;;
|
|
--check)
|
|
CHECK_UPDATES=true
|
|
shift
|
|
;;
|
|
--update-all|-a)
|
|
UPDATE_ALL=true
|
|
shift
|
|
;;
|
|
--update|-u)
|
|
UPDATE_STACK="$2"
|
|
shift 2
|
|
;;
|
|
--auto)
|
|
AUTO_UPDATE=true
|
|
shift
|
|
;;
|
|
--install-deps)
|
|
AUTO_INSTALL=true
|
|
shift
|
|
;;
|
|
--log)
|
|
LOG_FILE="$2"
|
|
shift 2
|
|
;;
|
|
--dry-run)
|
|
DRY_RUN=true
|
|
shift
|
|
;;
|
|
--debug|-d)
|
|
DEBUG=true
|
|
shift
|
|
;;
|
|
--verbose|-v)
|
|
VERBOSE=true
|
|
shift
|
|
;;
|
|
--help|-h)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo -e "${RED}Unknown option: $1${NC}" >&2
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
show_help() {
|
|
cat << EOF
|
|
Docker Stack Manager Script v$VERSION
|
|
|
|
Combines stack lifecycle management and update functionality.
|
|
|
|
Usage: $0 [OPTIONS]
|
|
|
|
Stack Management:
|
|
--list, -l List all stacks and their status
|
|
--start STACK Start a specific stack
|
|
--stop STACK Stop a specific stack
|
|
--restart STACK Restart a specific stack
|
|
--status STACK Get status of a specific stack
|
|
--logs STACK View logs for a specific stack
|
|
|
|
Update Operations:
|
|
--check Check for available image updates
|
|
--update-all, -a Update all stacks
|
|
--update STACK, -u Update a specific stack
|
|
--auto Auto-update mode with zero-downtime
|
|
|
|
General Options:
|
|
--quiet, -q Suppress non-error output
|
|
--yes, -y Auto-confirm all prompts
|
|
--dry-run Show what would be done without actually doing it
|
|
--debug, -d Enable debug mode (verbose output + xtrace)
|
|
--verbose, -v Enable verbose logging
|
|
--install-deps Auto-install missing dependencies (requires root)
|
|
--log FILE Log file path (default: /tmp/docwell/docker-manager.log)
|
|
--version Show version information
|
|
--help, -h Show this help message
|
|
|
|
Examples:
|
|
$0 --list # List all stacks
|
|
$0 --start myapp # Start a stack
|
|
$0 --check # Check for updates
|
|
$0 --update-all # Update all stacks
|
|
$0 --update myapp # Update specific stack
|
|
$0 --auto --yes # Auto-update all stacks
|
|
$0 --update-all --dry-run # Preview update without executing
|
|
EOF
|
|
}
|
|
|
|
# ─── Stack Management Functions ──────────────────────────────────────────────
|
|
|
|
list_stacks_display() {
|
|
show_spinner "Loading stacks..."
|
|
local stacks
|
|
mapfile -t stacks < <(get_stacks)
|
|
hide_spinner
|
|
|
|
if [[ ${#stacks[@]} -eq 0 ]]; then
|
|
log_info "No stacks found"
|
|
return 1
|
|
fi
|
|
|
|
for stack in "${stacks[@]}"; do
|
|
local status="stopped"
|
|
if is_stack_running "$stack"; then
|
|
status="running"
|
|
fi
|
|
echo -e "$stack\t$status"
|
|
done
|
|
}
|
|
|
|
start_stack_cmd() {
|
|
local stack="$1"
|
|
local stack_path="$STACKS_DIR/$stack"
|
|
local compose_file
|
|
compose_file=$(get_compose_file "$stack_path") || {
|
|
log_error "No compose file found"
|
|
return 1
|
|
}
|
|
|
|
if run_or_dry "start $stack" docker compose -f "$stack_path/$compose_file" up -d >/dev/null 2>&1; then
|
|
log_success "Started $stack"
|
|
return 0
|
|
else
|
|
log_error "Failed to start $stack"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
stop_stack_cmd() {
|
|
local stack="$1"
|
|
local stack_path="$STACKS_DIR/$stack"
|
|
local compose_file
|
|
compose_file=$(get_compose_file "$stack_path") || {
|
|
log_error "No compose file found"
|
|
return 1
|
|
}
|
|
|
|
if run_or_dry "stop $stack" docker compose -f "$stack_path/$compose_file" down >/dev/null 2>&1; then
|
|
log_success "Stopped $stack"
|
|
return 0
|
|
else
|
|
log_error "Failed to stop $stack"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
restart_stack_cmd() {
|
|
local stack="$1"
|
|
local stack_path="$STACKS_DIR/$stack"
|
|
local compose_file
|
|
compose_file=$(get_compose_file "$stack_path") || {
|
|
log_error "No compose file found"
|
|
return 1
|
|
}
|
|
|
|
if run_or_dry "restart $stack" docker compose -f "$stack_path/$compose_file" restart >/dev/null 2>&1; then
|
|
log_success "Restarted $stack"
|
|
return 0
|
|
else
|
|
log_error "Failed to restart $stack"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
get_stack_status_cmd() {
|
|
local stack="$1"
|
|
if is_stack_running "$stack"; then
|
|
echo "running"
|
|
else
|
|
echo "stopped"
|
|
fi
|
|
}
|
|
|
|
view_logs() {
|
|
local stack="$1"
|
|
local stack_path="$STACKS_DIR/$stack"
|
|
local compose_file
|
|
compose_file=$(get_compose_file "$stack_path") || {
|
|
log_error "No compose file found"
|
|
return 1
|
|
}
|
|
|
|
docker compose -f "$stack_path/$compose_file" logs -f
|
|
}
|
|
|
|
# ─── Update Functions ────────────────────────────────────────────────────────
|
|
|
|
get_stack_images() {
|
|
local stack_path="$1"
|
|
local compose_file
|
|
compose_file=$(get_compose_file "$stack_path") || return 1
|
|
|
|
docker compose -f "$stack_path/$compose_file" config --images 2>/dev/null | grep -v '^$' || true
|
|
}
|
|
|
|
get_image_digest() {
|
|
local image="$1"
|
|
docker inspect --format='{{.Id}}' "$image" 2>/dev/null || echo ""
|
|
}
|
|
|
|
get_remote_digest() {
|
|
local image="$1"
|
|
|
|
local manifest_output
|
|
manifest_output=$(docker manifest inspect "$image" 2>/dev/null) || true
|
|
|
|
if [[ -n "$manifest_output" ]]; then
|
|
# Check for manifest list (multi-platform)
|
|
if echo "$manifest_output" | grep -q '"mediaType"[[:space:]]*:[[:space:]]*"application/vnd.docker.distribution.manifest.list.v2+json"'; then
|
|
local platform_digest
|
|
platform_digest=$(echo "$manifest_output" | \
|
|
grep -A 20 '"platform"' | \
|
|
grep -B 5 -E '"architecture"[[:space:]]*:[[:space:]]*"(amd64|x86_64)"' | \
|
|
grep '"digest"' | head -1 | cut -d'"' -f4)
|
|
|
|
if [[ -z "$platform_digest" ]]; then
|
|
platform_digest=$(echo "$manifest_output" | \
|
|
grep -A 10 '"platform"' | \
|
|
grep '"digest"' | head -1 | cut -d'"' -f4)
|
|
fi
|
|
|
|
if [[ -n "$platform_digest" ]]; then
|
|
local platform_manifest
|
|
platform_manifest=$(docker manifest inspect "$image@$platform_digest" 2>/dev/null)
|
|
|
|
if [[ -n "$platform_manifest" ]]; then
|
|
local config_digest
|
|
config_digest=$(echo "$platform_manifest" | \
|
|
grep -A 5 '"config"' | \
|
|
grep '"digest"' | head -1 | cut -d'"' -f4)
|
|
|
|
if [[ -n "$config_digest" ]]; then
|
|
echo "$config_digest"
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
else
|
|
local config_digest
|
|
config_digest=$(echo "$manifest_output" | \
|
|
grep -A 5 '"config"' | \
|
|
grep '"digest"' | head -1 | cut -d'"' -f4)
|
|
|
|
if [[ -n "$config_digest" ]]; then
|
|
echo "$config_digest"
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
return 1
|
|
}
|
|
|
|
check_stack_updates_display() {
|
|
local stack_path="$1"
|
|
local stack_name="$2"
|
|
local images
|
|
mapfile -t images < <(get_stack_images "$stack_path")
|
|
local has_updates=false
|
|
|
|
local temp_dir
|
|
temp_dir=$(mktemp -d /tmp/docwell_update_check.XXXXXX)
|
|
register_cleanup "rm -rf '$temp_dir'"
|
|
|
|
local pids=()
|
|
local image_index=0
|
|
|
|
for image in "${images[@]}"; do
|
|
[[ -z "$image" ]] && continue
|
|
|
|
(
|
|
local output_file="$temp_dir/image_${image_index}.out"
|
|
local status_file="$temp_dir/image_${image_index}.status"
|
|
|
|
local local_digest
|
|
local_digest=$(get_image_digest "$image")
|
|
|
|
if [[ -z "$local_digest" ]]; then
|
|
echo -e " ${YELLOW}?${NC} $image ${GRAY}[not present locally]${NC}" > "$output_file"
|
|
echo "has_update" > "$status_file"
|
|
exit 0
|
|
fi
|
|
|
|
local remote_digest
|
|
remote_digest=$(get_remote_digest "$image") || true
|
|
local used_manifest=true
|
|
|
|
if [[ -z "$remote_digest" ]]; then
|
|
used_manifest=false
|
|
if ! docker pull "$image" >/dev/null 2>&1; then
|
|
echo -e " ${RED}✗${NC} $image ${GRAY}[check failed]${NC}" > "$output_file"
|
|
exit 0
|
|
fi
|
|
remote_digest=$(get_image_digest "$image")
|
|
fi
|
|
|
|
if [[ -n "$local_digest" && -n "$remote_digest" && "$local_digest" != "$remote_digest" ]]; then
|
|
echo -e " ${YELLOW}↑${NC} $image ${GRAY}[update available]${NC}" > "$output_file"
|
|
echo "has_update" > "$status_file"
|
|
else
|
|
local method_str=""
|
|
[[ "$used_manifest" == "true" ]] && method_str=" [manifest]"
|
|
echo -e " ${GREEN}✓${NC} $image ${GRAY}[up to date${method_str}]${NC}" > "$output_file"
|
|
fi
|
|
) &
|
|
pids+=($!)
|
|
((image_index++))
|
|
done
|
|
|
|
for pid in "${pids[@]}"; do
|
|
wait "$pid" 2>/dev/null || true
|
|
done
|
|
|
|
for ((i=0; i<image_index; i++)); do
|
|
local output_file="$temp_dir/image_${i}.out"
|
|
local status_file="$temp_dir/image_${i}.status"
|
|
|
|
[[ -f "$output_file" ]] && cat "$output_file"
|
|
|
|
if [[ -f "$status_file" ]] && [[ "$(cat "$status_file")" == "has_update" ]]; then
|
|
has_updates=true
|
|
fi
|
|
done
|
|
|
|
[[ "$has_updates" == "true" ]]
|
|
}
|
|
|
|
check_for_updates() {
|
|
show_spinner "Scanning stacks for available updates..."
|
|
local stacks
|
|
mapfile -t stacks < <(get_stacks)
|
|
hide_spinner
|
|
|
|
local updates_available=()
|
|
local total=${#stacks[@]}
|
|
local idx=0
|
|
|
|
for stack in "${stacks[@]}"; do
|
|
((idx++))
|
|
local stack_path="$STACKS_DIR/$stack"
|
|
if ! has_compose_file "$stack_path"; then
|
|
continue
|
|
fi
|
|
|
|
echo -e "${CYAN}[$idx/$total]${NC} ${BOLD}$stack:${NC}"
|
|
if check_stack_updates_display "$stack_path" "$stack"; then
|
|
updates_available+=("$stack")
|
|
fi
|
|
done
|
|
|
|
echo
|
|
if [[ ${#updates_available[@]} -gt 0 ]]; then
|
|
log_warn "${#updates_available[@]} stack(s) have updates available:"
|
|
for stack in "${updates_available[@]}"; do
|
|
echo " - $stack"
|
|
done
|
|
else
|
|
log_success "All stacks are up to date"
|
|
fi
|
|
}
|
|
|
|
update_stack_cmd() {
|
|
local stack="$1"
|
|
local stack_path="$STACKS_DIR/$stack"
|
|
|
|
if ! has_compose_file "$stack_path"; then
|
|
log_warn "No compose file found for $stack"
|
|
return 1
|
|
fi
|
|
|
|
local compose_file
|
|
compose_file=$(get_compose_file "$stack_path")
|
|
local was_running=false
|
|
|
|
if is_stack_running "$stack"; then
|
|
was_running=true
|
|
fi
|
|
|
|
log_info "Updating $stack..."
|
|
|
|
if ! run_or_dry "pull images for $stack" docker compose -f "$stack_path/$compose_file" pull >/dev/null 2>&1; then
|
|
log_warn "Failed to pull images for $stack"
|
|
return 1
|
|
fi
|
|
|
|
if [[ "$was_running" == "true" ]]; then
|
|
if ! run_or_dry "recreate $stack" docker compose -f "$stack_path/$compose_file" up -d --force-recreate >/dev/null 2>&1; then
|
|
log_error "Failed to recreate containers for $stack"
|
|
log_warn "Images pulled but containers not recreated"
|
|
return 1
|
|
fi
|
|
log_success "Updated $stack"
|
|
else
|
|
log_info "Stack was stopped, not starting $stack"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
update_all_stacks() {
|
|
log_warn "This will pull latest images and recreate all containers"
|
|
if ! confirm "Continue?"; then
|
|
return 1
|
|
fi
|
|
|
|
local stacks
|
|
mapfile -t stacks < <(get_stacks)
|
|
local failed_stacks=()
|
|
local total=${#stacks[@]}
|
|
local idx=0
|
|
local start_time=$SECONDS
|
|
|
|
for stack in "${stacks[@]}"; do
|
|
((idx++))
|
|
echo -e "${CYAN}[$idx/$total]${NC} Processing $stack..."
|
|
if ! update_stack_cmd "$stack"; then
|
|
failed_stacks+=("$stack")
|
|
fi
|
|
done
|
|
|
|
local elapsed=$(( SECONDS - start_time ))
|
|
if [[ ${#failed_stacks[@]} -gt 0 ]]; then
|
|
log_warn "Failed to update ${#failed_stacks[@]} stack(s): ${failed_stacks[*]}"
|
|
else
|
|
log_success "All stacks updated"
|
|
fi
|
|
log_info "Completed in $(format_elapsed "$elapsed")"
|
|
}
|
|
|
|
auto_update_mode() {
|
|
log_warn "This mode will:"
|
|
echo " 1. Check for updates"
|
|
echo " 2. Update stacks with available updates"
|
|
echo
|
|
|
|
if ! confirm "Enable auto-update mode?"; then
|
|
return 1
|
|
fi
|
|
|
|
local stacks
|
|
mapfile -t stacks < <(get_stacks)
|
|
local total=${#stacks[@]}
|
|
local idx=0
|
|
local start_time=$SECONDS
|
|
|
|
for stack in "${stacks[@]}"; do
|
|
((idx++))
|
|
local stack_path="$STACKS_DIR/$stack"
|
|
if ! has_compose_file "$stack_path"; then
|
|
continue
|
|
fi
|
|
|
|
echo -e "${CYAN}[$idx/$total]${NC} Processing $stack..."
|
|
|
|
local images
|
|
mapfile -t images < <(get_stack_images "$stack_path")
|
|
local needs_update=false
|
|
|
|
for image in "${images[@]}"; do
|
|
[[ -z "$image" ]] && continue
|
|
local local_digest
|
|
local_digest=$(get_image_digest "$image")
|
|
[[ -z "$local_digest" ]] && continue
|
|
|
|
local remote_digest
|
|
remote_digest=$(get_remote_digest "$image") || true
|
|
|
|
if [[ -z "$remote_digest" ]]; then
|
|
docker pull "$image" >/dev/null 2>&1 || continue
|
|
remote_digest=$(get_image_digest "$image")
|
|
fi
|
|
|
|
if [[ -n "$local_digest" && -n "$remote_digest" && "$local_digest" != "$remote_digest" ]]; then
|
|
needs_update=true
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ "$needs_update" == "false" ]]; then
|
|
log_success "$stack already up to date"
|
|
continue
|
|
fi
|
|
|
|
log_warn "Updates available for $stack, updating..."
|
|
if update_stack_cmd "$stack"; then
|
|
sleep 3
|
|
if is_stack_running "$stack"; then
|
|
log_success "Update successful"
|
|
else
|
|
log_error "Update may have failed, check logs"
|
|
fi
|
|
fi
|
|
echo
|
|
done
|
|
|
|
local elapsed=$(( SECONDS - start_time ))
|
|
log_success "Auto-update complete in $(format_elapsed "$elapsed")"
|
|
}
|
|
|
|
# ─── Interactive UI Functions ────────────────────────────────────────────────
|
|
|
|
interactive_stack_menu() {
|
|
local selected_stack="$1"
|
|
|
|
while true; do
|
|
clear_screen
|
|
log_header "Manage: $selected_stack"
|
|
echo -e "${GRAY}DocWell Manager v$VERSION${NC}"
|
|
echo
|
|
|
|
local status_str="${GRAY}●${NC} Stopped"
|
|
if is_stack_running "$selected_stack"; then
|
|
status_str="${GREEN}●${NC} Running"
|
|
fi
|
|
echo "Status: $status_str"
|
|
echo
|
|
echo "Stack Management:"
|
|
echo " 1) Start"
|
|
echo " 2) Stop"
|
|
echo " 3) Restart"
|
|
echo
|
|
echo "Updates:"
|
|
echo " 4) Update images"
|
|
echo " 5) Check for updates"
|
|
echo
|
|
echo "Other:"
|
|
echo " 6) View logs"
|
|
echo " 7) View compose file"
|
|
echo " 0) Back"
|
|
echo
|
|
|
|
read -rp "Select action: " action
|
|
|
|
case $action in
|
|
1) start_stack_cmd "$selected_stack" ;;
|
|
2) stop_stack_cmd "$selected_stack" ;;
|
|
3) restart_stack_cmd "$selected_stack" ;;
|
|
4) update_stack_cmd "$selected_stack" ;;
|
|
5)
|
|
local stack_path="$STACKS_DIR/$selected_stack"
|
|
if has_compose_file "$stack_path"; then
|
|
echo -e "${BOLD}$selected_stack:${NC}"
|
|
check_stack_updates_display "$stack_path" "$selected_stack" || true
|
|
fi
|
|
;;
|
|
6) view_logs "$selected_stack" ;;
|
|
7)
|
|
local stack_path="$STACKS_DIR/$selected_stack"
|
|
local compose_file
|
|
compose_file=$(get_compose_file "$stack_path") && \
|
|
less "$stack_path/$compose_file" || \
|
|
log_error "No compose file found"
|
|
;;
|
|
0) break ;;
|
|
*) log_error "Invalid selection" ;;
|
|
esac
|
|
|
|
echo
|
|
read -rp "Press Enter to continue..."
|
|
done
|
|
}
|
|
|
|
interactive_main_menu() {
|
|
while true; do
|
|
clear_screen
|
|
log_header "Docker Stack Manager"
|
|
echo -e "${GRAY}DocWell Manager v$VERSION${NC}"
|
|
echo
|
|
|
|
show_spinner "Loading stacks..."
|
|
local stacks
|
|
mapfile -t stacks < <(get_stacks)
|
|
hide_spinner
|
|
|
|
if [[ ${#stacks[@]} -eq 0 ]]; then
|
|
log_error "No stacks found"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Available stacks:"
|
|
for i in "${!stacks[@]}"; do
|
|
local stack="${stacks[$i]}"
|
|
local status_str="${GRAY}[stopped]${NC}"
|
|
if is_stack_running "$stack"; then
|
|
status_str="${GREEN}[running]${NC}"
|
|
fi
|
|
echo -e " ${BOLD}$((i+1))${NC}) ${stack:0:30} $status_str"
|
|
done
|
|
echo
|
|
log_separator
|
|
echo -e " ${BOLD}r${NC}) Restart all"
|
|
echo -e " ${BOLD}s${NC}) Stop all"
|
|
echo -e " ${BOLD}u${NC}) Update all"
|
|
echo -e " ${BOLD}c${NC}) Check for updates"
|
|
echo -e " ${BOLD}0${NC}) Exit"
|
|
echo
|
|
|
|
read -rp "Select stack number or action: " choice
|
|
|
|
if [[ "$choice" == "0" ]] || [[ "$choice" == "q" ]] || [[ "$choice" == "quit" ]]; then
|
|
exit 0
|
|
fi
|
|
|
|
case "$choice" in
|
|
r)
|
|
for stack in "${stacks[@]}"; do
|
|
restart_stack_cmd "$stack" || true
|
|
done
|
|
read -rp "Press Enter to continue..."
|
|
;;
|
|
s)
|
|
for stack in "${stacks[@]}"; do
|
|
stop_stack_cmd "$stack" || true
|
|
done
|
|
read -rp "Press Enter to continue..."
|
|
;;
|
|
u)
|
|
update_all_stacks
|
|
read -rp "Press Enter to continue..."
|
|
;;
|
|
c)
|
|
check_for_updates
|
|
read -rp "Press Enter to continue..."
|
|
;;
|
|
*)
|
|
if [[ "$choice" =~ ^[0-9]+$ ]] && [[ "$choice" -ge 1 ]] && [[ "$choice" -le "${#stacks[@]}" ]]; then
|
|
local idx=$((choice-1))
|
|
interactive_stack_menu "${stacks[$idx]}"
|
|
else
|
|
log_error "Invalid selection"
|
|
read -rp "Press Enter to continue..."
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# ─── Main ────────────────────────────────────────────────────────────────────
|
|
|
|
main() {
|
|
parse_args "$@"
|
|
|
|
if ! check_docker; then
|
|
exit 1
|
|
fi
|
|
|
|
# Handle list only
|
|
[[ "$LIST_ONLY" == "true" ]] && { list_stacks_display; exit 0; }
|
|
|
|
# Handle stack management CLI operations
|
|
[[ -n "$START_STACK" ]] && { start_stack_cmd "$START_STACK"; exit $?; }
|
|
[[ -n "$STOP_STACK" ]] && { stop_stack_cmd "$STOP_STACK"; exit $?; }
|
|
[[ -n "$RESTART_STACK" ]] && { restart_stack_cmd "$RESTART_STACK"; exit $?; }
|
|
[[ -n "$STATUS_STACK" ]] && { get_stack_status_cmd "$STATUS_STACK"; exit 0; }
|
|
[[ -n "$LOGS_STACK" ]] && { view_logs "$LOGS_STACK"; exit $?; }
|
|
|
|
# Handle update CLI operations
|
|
[[ "$CHECK_UPDATES" == "true" ]] && { check_for_updates; exit 0; }
|
|
[[ "$UPDATE_ALL" == "true" ]] && { update_all_stacks; exit 0; }
|
|
[[ -n "$UPDATE_STACK" ]] && { update_stack_cmd "$UPDATE_STACK"; exit $?; }
|
|
[[ "$AUTO_UPDATE" == "true" ]] && { auto_update_mode; exit 0; }
|
|
|
|
# Interactive mode
|
|
interactive_main_menu
|
|
}
|
|
|
|
main "$@"
|