511 lines
16 KiB
Bash
Executable File
511 lines
16 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Define color variables for formatted output
|
|
COLOR_DEBUG="\033[0;36m" # Cyan
|
|
COLOR_INFO="\033[0;32m" # Green
|
|
COLOR_WARNING="\033[0;33m" # Yellow
|
|
COLOR_ERROR="\033[0;31m" # Red
|
|
COLOR_DRY_RUN="\033[0;35m" # Purple
|
|
COLOR_RESET="\033[0m" # Reset to default
|
|
|
|
# Color Codes for all colors
|
|
COLOR_BLACK="\033[0;30m"
|
|
COLOR_RED="\033[0;31m"
|
|
COLOR_GREEN="\033[0;32m"
|
|
COLOR_YELLOW="\033[0;33m"
|
|
COLOR_BLUE="\033[0;34m"
|
|
COLOR_PURPLE="\033[0;35m"
|
|
COLOR_CYAN="\033[0;36m"
|
|
COLOR_WHITE="\033[0;37m"
|
|
|
|
# Color Codes for all colors with background
|
|
COLOR_BLACK_BG="\033[0;40m"
|
|
COLOR_RED_BG="\033[0;41m"
|
|
COLOR_GREEN_BG="\033[0;42m"
|
|
COLOR_YELLOW_BG="\033[0;43m"
|
|
COLOR_BLUE_BG="\033[0;44m"
|
|
COLOR_PURPLE_BG="\033[0;45m"
|
|
COLOR_CYAN_BG="\033[0;46m"
|
|
COLOR_WHITE_BG="\033[0;47m"
|
|
|
|
# Formatting Codes
|
|
BOLD="\033[1m"
|
|
DIM="\033[2m"
|
|
UNDERLINED="\033[4m"
|
|
BLINK="\033[5m"
|
|
INVERTED="\033[7m"
|
|
HIDDEN="\033[8m"
|
|
|
|
# Combined Formatting Codes
|
|
BOLD_BLACK="\033[1;30m"
|
|
BOLD_RED="\033[1;31m"
|
|
BOLD_GREEN="\033[1;32m"
|
|
BOLD_YELLOW="\033[1;33m"
|
|
BOLD_BLUE="\033[1;34m"
|
|
BOLD_PURPLE="\033[1;35m"
|
|
BOLD_CYAN="\033[1;36m"
|
|
BOLD_WHITE="\033[1;37m"
|
|
|
|
DIM_BLACK="\033[2;30m"
|
|
DIM_RED="\033[2;31m"
|
|
DIM_GREEN="\033[2;32m"
|
|
DIM_YELLOW="\033[2;33m"
|
|
DIM_BLUE="\033[2;34m"
|
|
DIM_PURPLE="\033[2;35m"
|
|
DIM_CYAN="\033[2;36m"
|
|
DIM_WHITE="\033[2;37m"
|
|
|
|
UNDERLINED_BLACK="\033[4;30m"
|
|
UNDERLINED_RED="\033[4;31m"
|
|
UNDERLINED_GREEN="\033[4;32m"
|
|
UNDERLINED_YELLOW="\033[4;33m"
|
|
UNDERLINED_BLUE="\033[4;34m"
|
|
UNDERLINED_PURPLE="\033[4;35m"
|
|
UNDERLINED_CYAN="\033[4;36m"
|
|
UNDERLINED_WHITE="\033[4;37m"
|
|
|
|
BLINK_BLACK="\033[5;30m"
|
|
BLINK_RED="\033[5;31m"
|
|
BLINK_GREEN="\033[5;32m"
|
|
BLINK_YELLOW="\033[5;33m"
|
|
BLINK_BLUE="\033[5;34m"
|
|
BLINK_PURPLE="\033[5;35m"
|
|
BLINK_CYAN="\033[5;36m"
|
|
BLINK_WHITE="\033[5;37m"
|
|
|
|
INVERTED_BLACK="\033[7;30m"
|
|
INVERTED_RED="\033[7;31m"
|
|
INVERTED_GREEN="\033[7;32m"
|
|
INVERTED_YELLOW="\033[7;33m"
|
|
INVERTED_BLUE="\033[7;34m"
|
|
INVERTED_PURPLE="\033[7;35m"
|
|
INVERTED_CYAN="\033[7;36m"
|
|
INVERTED_WHITE="\033[7;37m"
|
|
|
|
HIDDEN_BLACK="\033[8;30m"
|
|
HIDDEN_RED="\033[8;31m"
|
|
HIDDEN_GREEN="\033[8;32m"
|
|
HIDDEN_YELLOW="\033[8;33m"
|
|
HIDDEN_BLUE="\033[8;34m"
|
|
HIDDEN_PURPLE="\033[8;35m"
|
|
HIDDEN_CYAN="\033[8;36m"
|
|
HIDDEN_WHITE="\033[8;37m"
|
|
|
|
BOLD_DIM_BLACK="\033[1;2;30m"
|
|
BOLD_DIM_RED="\033[1;2;31m"
|
|
BOLD_DIM_GREEN="\033[1;2;32m"
|
|
BOLD_DIM_YELLOW="\033[1;2;33m"
|
|
BOLD_DIM_BLUE="\033[1;2;34m"
|
|
BOLD_DIM_PURPLE="\033[1;2;35m"
|
|
BOLD_DIM_CYAN="\033[1;2;36m"
|
|
BOLD_DIM_WHITE="\033[1;2;37m"
|
|
|
|
BOLD_UNDERLINED_BLACK="\033[1;4;30m"
|
|
BOLD_UNDERLINED_RED="\033[1;4;31m"
|
|
BOLD_UNDERLINED_GREEN="\033[1;4;32m"
|
|
BOLD_UNDERLINED_YELLOW="\033[1;4;33m"
|
|
BOLD_UNDERLINED_BLUE="\033[1;4;34m"
|
|
BOLD_UNDERLINED_PURPLE="\033[1;4;35m"
|
|
BOLD_UNDERLINED_CYAN="\033[1;4;36m"
|
|
BOLD_UNDERLINED_WHITE="\033[1;4;37m"
|
|
|
|
BOLD_BLINK_BLACK="\033[1;5;30m"
|
|
BOLD_BLINK_RED="\033[1;5;31m"
|
|
BOLD_BLINK_GREEN="\033[1;5;32m"
|
|
BOLD_BLINK_YELLOW="\033[1;5;33m"
|
|
BOLD_BLINK_BLUE="\033[1;5;34m"
|
|
BOLD_BLINK_PURPLE="\033[1;5;35m"
|
|
BOLD_BLINK_CYAN="\033[1;5;36m"
|
|
BOLD_BLINK_WHITE="\033[1;5;37m"
|
|
|
|
DIM_UNDERLINED_BLACK="\033[2;4;30m"
|
|
DIM_UNDERLINED_RED="\033[2;4;31m"
|
|
DIM_UNDERLINED_GREEN="\033[2;4;32m"
|
|
DIM_UNDERLINED_YELLOW="\033[2;4;33m"
|
|
DIM_UNDERLINED_BLUE="\033[2;4;34m"
|
|
DIM_UNDERLINED_PURPLE="\033[2;4;35m"
|
|
DIM_UNDERLINED_CYAN="\033[2;4;36m"
|
|
DIM_UNDERLINED_WHITE="\033[2;4;37m"
|
|
|
|
DIM_BLINK_BLACK="\033[2;5;30m"
|
|
DIM_BLINK_RED="\033[2;5;31m"
|
|
DIM_BLINK_GREEN="\033[2;5;32m"
|
|
DIM_BLINK_YELLOW="\033[2;5;33m"
|
|
DIM_BLINK_BLUE="\033[2;5;34m"
|
|
DIM_BLINK_PURPLE="\033[2;5;35m"
|
|
DIM_BLINK_CYAN="\033[2;5;36m"
|
|
DIM_BLINK_WHITE="\033[2;5;37m"
|
|
|
|
BOLD_DIM_UNDERLINED_BLACK="\033[1;2;4;30m"
|
|
BOLD_DIM_UNDERLINED_RED="\033[1;2;4;31m"
|
|
BOLD_DIM_UNDERLINED_GREEN="\033[1;2;4;32m"
|
|
BOLD_DIM_UNDERLINED_YELLOW="\033[1;2;4;33m"
|
|
BOLD_DIM_UNDERLINED_BLUE="\033[1;2;4;34m"
|
|
BOLD_DIM_UNDERLINED_PURPLE="\033[1;2;4;35m"
|
|
BOLD_DIM_UNDERLINED_CYAN="\033[1;2;4;36m"
|
|
BOLD_DIM_UNDERLINED_WHITE="\033[1;2;4;37m"
|
|
|
|
# Function to print help
|
|
print_help() {
|
|
echo "Usage: $0 [options]"
|
|
echo "Options:"
|
|
echo " -s Get the package status of each container"
|
|
echo " -u Update the package cache, but do not upgrade"
|
|
echo " -U Perform upgrades on all containers"
|
|
echo " -x <ID> Exclude a container by its ID (can be used multiple times)"
|
|
echo " -i <ID> Include only specific containers by their IDs (can be used multiple times)"
|
|
echo " -y Assume 'yes' for all operations (asks once)"
|
|
echo " -Y Assume 'yes' for all operations without asking (for automation)"
|
|
echo " -b Boot non-running containers before performing actions"
|
|
echo " -B <ID> Boot a specific container by its ID (can be used multiple times)"
|
|
echo " -d Dry-run mode (simulate actions without making changes)"
|
|
echo " -v Enable verbose mode"
|
|
echo " -h Print this help and exit"
|
|
}
|
|
|
|
# Logging functions
|
|
log_dbg() { [ "$VERBOSE" = true ] && echo -e "${COLOR_DEBUG}[$(date '+%Y-%m-%d %H:%M:%S')][DEBUG] $1${COLOR_RESET}"; }
|
|
log_inf() { echo -e "${COLOR_INFO}[$(date '+%Y-%m-%d %H:%M:%S')][INFO] $1${COLOR_RESET}"; }
|
|
log_wrn() { echo -e "${COLOR_WARNING}[$(date '+%Y-%m-%d %H:%M:%S')][WARNING] $1${COLOR_RESET}"; }
|
|
log_err() { echo -e "${COLOR_ERROR}[$(date '+%Y-%m-%d %H:%M:%S')][ERROR] $1${COLOR_RESET}"; }
|
|
log_dry_run() { echo -e "${COLOR_DRY_RUN}[DRY-RUN] $1${COLOR_RESET}"; }
|
|
|
|
# Check for required commands
|
|
for cmd in awk grep pct; do
|
|
if ! command -v $cmd &> /dev/null; then
|
|
log_err "Required command $cmd is not available."
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# Warn if running as root
|
|
if [ "$(id -u)" -eq 0 ]; then
|
|
log_wrn "Running as root. Ensure this is necessary for your operations."
|
|
fi
|
|
|
|
# Initialize variables
|
|
EXCLUDE_IDS=()
|
|
INCLUDE_IDS=()
|
|
BOOT_IDS=()
|
|
ACTION=""
|
|
ASSUME_YES=false
|
|
FORCE_YES=false
|
|
BOOT_CONTAINERS=false
|
|
DRY_RUN=false
|
|
VERBOSE=false
|
|
|
|
# Internal variables
|
|
STATUS_SUMMARIES=()
|
|
|
|
# Parse arguments
|
|
while getopts ":suUx:i:hyYbdvB:" opt; do
|
|
case ${opt} in
|
|
s|u|U)
|
|
if [[ -n "$ACTION" && "$ACTION" != "$opt" ]]; then
|
|
log_err "Multiple actions specified. Please specify only one of -s, -u, or -U."
|
|
exit 1
|
|
fi
|
|
ACTION="$opt"
|
|
;;
|
|
x) EXCLUDE_IDS+=("$OPTARG") ;;
|
|
i) INCLUDE_IDS+=("$OPTARG") ;;
|
|
y) ASSUME_YES=true ;;
|
|
Y) FORCE_YES=true ;;
|
|
b) BOOT_CONTAINERS=true ;;
|
|
B) BOOT_IDS+=("$OPTARG") ;;
|
|
d) DRY_RUN=true ;;
|
|
v) VERBOSE=true ;;
|
|
h) print_help; exit 0 ;;
|
|
\?) log_err "Invalid option: -$OPTARG"; exit 1 ;;
|
|
:) log_err "Option -$OPTARG requires an argument."; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
# Check for conflicts between -b and -B
|
|
if [ "$BOOT_CONTAINERS" = true ] && [ ${#BOOT_IDS[@]} -gt 0 ]; then
|
|
log_err "Conflict: Both -b and -B options are set. Use only one. -b will boot all containers, which may not be intended."
|
|
exit 1
|
|
fi
|
|
|
|
# Check if an action is specified
|
|
if [ -z "$ACTION" ]; then
|
|
log_err "No action specified. Use -h for help."
|
|
exit 1
|
|
fi
|
|
|
|
# Check for conflicts between include and exclude lists
|
|
for id in "${INCLUDE_IDS[@]}"; do
|
|
if [[ " ${EXCLUDE_IDS[@]} " =~ " $id " ]]; then
|
|
log_err "Container ID $id is both included and excluded. This is not allowed."
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# Get list of all containers
|
|
log_inf "Retrieving list of containers..."
|
|
CONTAINERS=$(pct list | awk 'NR>1 {print $1}' | grep -E '^[0-9]+$')
|
|
log_dbg "Containers found: $CONTAINERS"
|
|
|
|
# Function to check if a container ID is in the exclude list
|
|
is_excluded() {
|
|
local id="$1"
|
|
for exclude_id in "${EXCLUDE_IDS[@]}"; do
|
|
if [ "$exclude_id" == "$id" ]; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Function to check if a container ID is in the include list
|
|
is_included() {
|
|
local id="$1"
|
|
if [ ${#INCLUDE_IDS[@]} -eq 0 ]; then
|
|
return 0
|
|
fi
|
|
for include_id in "${INCLUDE_IDS[@]}"; do
|
|
if [ "$include_id" == "$id" ]; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Function to check if a container ID is in the boot list
|
|
is_booted() {
|
|
local id="$1"
|
|
for boot_id in "${BOOT_IDS[@]}"; do
|
|
if [ "$boot_id" == "$id" ]; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Function to confirm actions
|
|
confirm_action() {
|
|
local message="$1"
|
|
if $FORCE_YES; then
|
|
return 0
|
|
elif $ASSUME_YES; then
|
|
log_wrn "Assuming 'yes' for all operations. Proceed with caution."
|
|
return 0
|
|
fi
|
|
read -p "$message (y/n): " choice
|
|
case "$choice" in
|
|
y|Y ) return 0 ;;
|
|
* ) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
# Function to validate container ID
|
|
validate_container_id() {
|
|
local id="$1"
|
|
if ! [[ "$id" =~ ^[0-9]+$ ]]; then
|
|
log_err "Invalid container ID: $id"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Function to check if a container ID exists
|
|
container_exists() {
|
|
local id="$1"
|
|
if pct list | awk 'NR>1 {print $1}' | grep -q "^$id$"; then
|
|
return 0
|
|
else
|
|
log_err "Container ID $id does not exist."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Function to check if there are enough resources to boot a container
|
|
can_boot_container() {
|
|
local id="$1"
|
|
# Example check: Ensure there is enough free memory (this is a placeholder and should be replaced with actual checks)
|
|
local required_memory=$(pct config "$id" | grep -i "memory" | awk '{print $2}')
|
|
local free_memory=$(free -m | awk '/^Mem:/{print $7}')
|
|
if [ "$free_memory" -lt "$required_memory" ]; then
|
|
log_err "Not enough memory to boot container $id. Required: $required_memory MB, Available: $free_memory MB."
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
color() {
|
|
local color="$1"
|
|
local message="$2"
|
|
local previous_color="${3:-$COLOR_RESET}"
|
|
|
|
return "${color}$message${previous_color}"
|
|
}
|
|
|
|
# Filter containers based on include/exclude lists
|
|
FILTERED_CONTAINERS=()
|
|
for CTID in $CONTAINERS; do
|
|
if ! validate_container_id "$CTID"; then
|
|
continue
|
|
fi
|
|
if ! container_exists "$CTID"; then
|
|
continue
|
|
fi
|
|
if is_excluded "$CTID"; then
|
|
continue
|
|
fi
|
|
if is_included "$CTID"; then
|
|
FILTERED_CONTAINERS+=("$CTID")
|
|
fi
|
|
done
|
|
|
|
# Check if there are any containers to process
|
|
if [ ${#FILTERED_CONTAINERS[@]} -eq 0 ]; then
|
|
log_wrn "No containers match the specified criteria."
|
|
exit 0
|
|
fi
|
|
|
|
# Iterate over each filtered container
|
|
for CTID in "${FILTERED_CONTAINERS[@]}"; do
|
|
log_dbg "Processing container ID: $CTID"
|
|
|
|
# Check if the container is running
|
|
STATUS=$(pct status "$CTID" 2>/dev/null | awk '{print $2}')
|
|
if [ $? -ne 0 ]; then
|
|
log_err "Failed to retrieve status for container $CTID."
|
|
continue
|
|
fi
|
|
log_dbg "Container $CTID status: $STATUS"
|
|
if [ "$STATUS" != "running" ]; then
|
|
if $BOOT_CONTAINERS || is_booted "$CTID"; then
|
|
log_inf "Container $CTID is not running."
|
|
if $DRY_RUN; then
|
|
log_dry_run "Would boot container $CTID."
|
|
else
|
|
if can_boot_container "$CTID"; then
|
|
if confirm_action "Boot container $CTID?"; then
|
|
if pct start "$CTID"; then
|
|
log_inf "Container $CTID started."
|
|
else
|
|
log_err "Failed to start container $CTID."
|
|
continue
|
|
fi
|
|
else
|
|
log_wrn "Boot skipped for container $CTID."
|
|
continue
|
|
fi
|
|
else
|
|
log_wrn "Insufficient resources to boot container $CTID."
|
|
continue
|
|
fi
|
|
fi
|
|
else
|
|
log_wrn "Container $CTID is not running and will be skipped."
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
# Get the OS of the container
|
|
OS=$(pct config "$CTID" | grep -i "ostype" | awk '{print $2}' | tr '[:upper:]' '[:lower:]')
|
|
CTNAME=$(pct config "$CTID" | grep -i "hostname" | awk '{print $2}')
|
|
log_dbg "Container $CTID OS: $OS, Hostname: $CTNAME"
|
|
|
|
# Determine package manager and commands based on OS
|
|
case "$OS" in
|
|
debian|ubuntu)
|
|
PKG_STATUS_CMD="apt list --upgradable 2>/dev/null | grep -v 'Listing' | wc -l"
|
|
UPDATE_CMD="apt update"
|
|
UPGRADE_CMD="apt upgrade -y"
|
|
;;
|
|
alpine)
|
|
PKG_STATUS_CMD="apk version -l '<' | wc -l"
|
|
UPDATE_CMD="apk update"
|
|
UPGRADE_CMD="apk upgrade -y"
|
|
;;
|
|
centos|fedora|rhel)
|
|
if pct exec "$CTID" -- /bin/sh -c "command -v dnf" &> /dev/null; then
|
|
PKG_STATUS_CMD="dnf check-update | wc -l"
|
|
UPDATE_CMD="dnf makecache"
|
|
UPGRADE_CMD="dnf upgrade -y"
|
|
else
|
|
PKG_STATUS_CMD="yum check-update | wc -l"
|
|
UPDATE_CMD="yum makecache"
|
|
UPGRADE_CMD="yum upgrade -y"
|
|
fi
|
|
;;
|
|
*)
|
|
log_err "Unsupported OS for container $CTID: $OS"
|
|
continue
|
|
;;
|
|
esac
|
|
|
|
log_dbg "Package status command: $PKG_STATUS_CMD"
|
|
log_dbg "Update command: $UPDATE_CMD"
|
|
log_dbg "Upgrade command: $UPGRADE_CMD"
|
|
|
|
# Execute the appropriate action
|
|
case "$ACTION" in
|
|
s)
|
|
log_inf "Checking package status for container $CTNAME ($CTID)..."
|
|
if $DRY_RUN; then
|
|
log_dry_run "Would check package status for container $CTNAME ($CTID)."
|
|
else
|
|
UPDATES=$(pct exec "$CTID" -- /bin/sh -c "$PKG_STATUS_CMD")
|
|
if [ $? -ne 0 ]; then
|
|
log_err "Failed to check package status for container $CTNAME ($CTID)."
|
|
continue
|
|
fi
|
|
log_dbg "Updates available for container $CTNAME ($CTID): $UPDATES"
|
|
if [ "$UPDATES" -gt 0 ]; then
|
|
log_inf "Container $CTNAME ($CTID) STATUS: \033[1mHas Updates ($UPDATES)\033[0m"
|
|
else
|
|
log_dbg "Container $CTNAME ($CTID) STATUS: No Updates available"
|
|
fi
|
|
|
|
# Save the summary for later
|
|
STATUS_SUMMARIES+=("$CTNAME ($CTID): $UPDATES")
|
|
fi
|
|
;;
|
|
u)
|
|
log_inf "Preparing to update package cache for container $CTNAME ($CTID)..."
|
|
if $DRY_RUN; then
|
|
log_dry_run "Would execute update on $CTNAME ($CTID) with command: $UPDATE_CMD"
|
|
else
|
|
if confirm_action "Execute update on $CTNAME ($CTID) with command: $UPDATE_CMD"; then
|
|
if pct exec "$CTID" -- /bin/sh -c "$UPDATE_CMD"; then
|
|
log_inf "Update completed for $CTNAME ($CTID)"
|
|
else
|
|
log_err "Update failed for $CTNAME ($CTID)"
|
|
continue
|
|
fi
|
|
else
|
|
log_wrn "Update skipped for $CTNAME ($CTID)"
|
|
fi
|
|
fi
|
|
;;
|
|
U)
|
|
log_inf "Preparing to upgrade packages for container $CTNAME ($CTID)..."
|
|
if $DRY_RUN; then
|
|
log_dry_run "Would execute upgrade on $CTNAME ($CTID) with command: $UPDATE_CMD && $UPGRADE_CMD"
|
|
else
|
|
if confirm_action "Execute upgrade on $CTNAME ($CTID) with command: $UPDATE_CMD && $UPGRADE_CMD"; then
|
|
if pct exec "$CTID" -- /bin/sh -c "$UPDATE_CMD && $UPGRADE_CMD"; then
|
|
log_inf "Upgrade completed for $CTNAME ($CTID)"
|
|
else
|
|
log_err "Upgrade failed for $CTNAME ($CTID)"
|
|
continue
|
|
fi
|
|
else
|
|
log_wrn "Upgrade skipped for $CTNAME ($CTID)"
|
|
fi
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Print summary of statuses
|
|
if [ "$ACTION" == "s" ] && [ ${#STATUS_SUMMARIES[@]} -gt 0 ]; then
|
|
log_inf "Summary of container statuses:"
|
|
for summary in "${STATUS_SUMMARIES[@]}"; do
|
|
log_inf "$summary"
|
|
done
|
|
fi
|