mirror of
				https://github.com/community-scripts/ProxmoxVE.git
				synced 2025-11-04 02:12:49 +00:00 
			
		
		
		
	
		
			
	
	
		
			545 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
		
		
			
		
	
	
			545 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
| 
								 | 
							
								# Copyright (c) 2021-2025 community-scripts ORG
							 | 
						||
| 
								 | 
							
								# License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/LICENSE
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# if ! declare -f wait_for >/dev/null; then
							 | 
						||
| 
								 | 
							
								#   echo "[DEBUG] Undefined function 'wait_for' used from: ${BASH_SOURCE[*]}" >&2
							 | 
						||
| 
								 | 
							
								#   wait_for() {
							 | 
						||
| 
								 | 
							
								#     echo "[DEBUG] Fallback: wait_for called with: $*" >&2
							 | 
						||
| 
								 | 
							
								#     true
							 | 
						||
| 
								 | 
							
								#   }
							 | 
						||
| 
								 | 
							
								# fi
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								trap 'on_error $? $LINENO' ERR
							 | 
						||
| 
								 | 
							
								trap 'on_exit' EXIT
							 | 
						||
| 
								 | 
							
								trap 'on_interrupt' INT
							 | 
						||
| 
								 | 
							
								trap 'on_terminate' TERM
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if ! declare -f wait_for >/dev/null; then
							 | 
						||
| 
								 | 
							
								  wait_for() {
							 | 
						||
| 
								 | 
							
								    true
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								fi
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								declare -A MSG_INFO_SHOWN=()
							 | 
						||
| 
								 | 
							
								SPINNER_PID=""
							 | 
						||
| 
								 | 
							
								SPINNER_ACTIVE=0
							 | 
						||
| 
								 | 
							
								SPINNER_MSG=""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# ------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Loads core utility groups once (colors, formatting, icons, defaults).
							 | 
						||
| 
								 | 
							
								# ------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								[[ -n "${_CORE_FUNC_LOADED:-}" ]] && return
							 | 
						||
| 
								 | 
							
								_CORE_FUNC_LOADED=1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								load_functions() {
							 | 
						||
| 
								 | 
							
								  [[ -n "${__FUNCTIONS_LOADED:-}" ]] && return
							 | 
						||
| 
								 | 
							
								  __FUNCTIONS_LOADED=1
							 | 
						||
| 
								 | 
							
								  color
							 | 
						||
| 
								 | 
							
								  formatting
							 | 
						||
| 
								 | 
							
								  icons
							 | 
						||
| 
								 | 
							
								  default_vars
							 | 
						||
| 
								 | 
							
								  set_std_mode
							 | 
						||
| 
								 | 
							
								  # add more
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								on_error() {
							 | 
						||
| 
								 | 
							
								  local exit_code="$1"
							 | 
						||
| 
								 | 
							
								  local lineno="$2"
							 | 
						||
| 
								 | 
							
								  msg_error "Script failed at line $lineno with exit code $exit_code"
							 | 
						||
| 
								 | 
							
								  # Optionally log to your API or file here
							 | 
						||
| 
								 | 
							
								  exit "$exit_code"
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								on_exit() {
							 | 
						||
| 
								 | 
							
								  # Always called on script exit, success or failure
							 | 
						||
| 
								 | 
							
								  cleanup_temp_files || true
							 | 
						||
| 
								 | 
							
								  msg_info "Script exited"
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								on_interrupt() {
							 | 
						||
| 
								 | 
							
								  msg_error "Interrupted by user (CTRL+C)"
							 | 
						||
| 
								 | 
							
								  exit 130
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								on_terminate() {
							 | 
						||
| 
								 | 
							
								  msg_error "Terminated by signal (TERM)"
							 | 
						||
| 
								 | 
							
								  exit 143
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								setup_trap_abort_handling() {
							 | 
						||
| 
								 | 
							
								  trap '__handle_signal_abort SIGINT' SIGINT
							 | 
						||
| 
								 | 
							
								  trap '__handle_signal_abort SIGTERM' SIGTERM
							 | 
						||
| 
								 | 
							
								  trap '__handle_unexpected_error $?' ERR
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								__handle_signal_abort() {
							 | 
						||
| 
								 | 
							
								  local signal="$1"
							 | 
						||
| 
								 | 
							
								  echo
							 | 
						||
| 
								 | 
							
								  [ -n "${SPINNER_PID:-}" ] && kill "$SPINNER_PID" 2>/dev/null && wait "$SPINNER_PID" 2>/dev/null
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  case "$signal" in
							 | 
						||
| 
								 | 
							
								  SIGINT)
							 | 
						||
| 
								 | 
							
								    msg_error "Script aborted by user (CTRL+C)"
							 | 
						||
| 
								 | 
							
								    exit 130
							 | 
						||
| 
								 | 
							
								    ;;
							 | 
						||
| 
								 | 
							
								  SIGTERM)
							 | 
						||
| 
								 | 
							
								    msg_error "Script terminated (SIGTERM)"
							 | 
						||
| 
								 | 
							
								    exit 143
							 | 
						||
| 
								 | 
							
								    ;;
							 | 
						||
| 
								 | 
							
								  *)
							 | 
						||
| 
								 | 
							
								    msg_error "Script interrupted (unknown signal: $signal)"
							 | 
						||
| 
								 | 
							
								    exit 1
							 | 
						||
| 
								 | 
							
								    ;;
							 | 
						||
| 
								 | 
							
								  esac
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								__handle_unexpected_error() {
							 | 
						||
| 
								 | 
							
								  local exit_code="$1"
							 | 
						||
| 
								 | 
							
								  echo
							 | 
						||
| 
								 | 
							
								  [ -n "${SPINNER_PID:-}" ] && kill "$SPINNER_PID" 2>/dev/null && wait "$SPINNER_PID" 2>/dev/null
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  case "$exit_code" in
							 | 
						||
| 
								 | 
							
								  1)
							 | 
						||
| 
								 | 
							
								    msg_error "Generic error occurred (exit code 1)"
							 | 
						||
| 
								 | 
							
								    ;;
							 | 
						||
| 
								 | 
							
								  2)
							 | 
						||
| 
								 | 
							
								    msg_error "Misuse of shell builtins (exit code 2)"
							 | 
						||
| 
								 | 
							
								    ;;
							 | 
						||
| 
								 | 
							
								  126)
							 | 
						||
| 
								 | 
							
								    msg_error "Command invoked cannot execute (exit code 126)"
							 | 
						||
| 
								 | 
							
								    ;;
							 | 
						||
| 
								 | 
							
								  127)
							 | 
						||
| 
								 | 
							
								    msg_error "Command not found (exit code 127)"
							 | 
						||
| 
								 | 
							
								    ;;
							 | 
						||
| 
								 | 
							
								  128)
							 | 
						||
| 
								 | 
							
								    msg_error "Invalid exit argument (exit code 128)"
							 | 
						||
| 
								 | 
							
								    ;;
							 | 
						||
| 
								 | 
							
								  130)
							 | 
						||
| 
								 | 
							
								    msg_error "Script aborted by user (CTRL+C)"
							 | 
						||
| 
								 | 
							
								    ;;
							 | 
						||
| 
								 | 
							
								  143)
							 | 
						||
| 
								 | 
							
								    msg_error "Script terminated by SIGTERM"
							 | 
						||
| 
								 | 
							
								    ;;
							 | 
						||
| 
								 | 
							
								  *)
							 | 
						||
| 
								 | 
							
								    msg_error "Unexpected error occurred (exit code $exit_code)"
							 | 
						||
| 
								 | 
							
								    ;;
							 | 
						||
| 
								 | 
							
								  esac
							 | 
						||
| 
								 | 
							
								  exit "$exit_code"
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# ------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Sets ANSI color codes used for styled terminal output.
							 | 
						||
| 
								 | 
							
								# ------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								color() {
							 | 
						||
| 
								 | 
							
								  YW=$(echo "\033[33m")
							 | 
						||
| 
								 | 
							
								  YWB=$'\e[93m'
							 | 
						||
| 
								 | 
							
								  BL=$(echo "\033[36m")
							 | 
						||
| 
								 | 
							
								  RD=$(echo "\033[01;31m")
							 | 
						||
| 
								 | 
							
								  BGN=$(echo "\033[4;92m")
							 | 
						||
| 
								 | 
							
								  GN=$(echo "\033[1;92m")
							 | 
						||
| 
								 | 
							
								  DGN=$(echo "\033[32m")
							 | 
						||
| 
								 | 
							
								  CL=$(echo "\033[m")
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# ------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Defines formatting helpers like tab, bold, and line reset sequences.
							 | 
						||
| 
								 | 
							
								# ------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								formatting() {
							 | 
						||
| 
								 | 
							
								  BFR="\\r\\033[K"
							 | 
						||
| 
								 | 
							
								  BOLD=$(echo "\033[1m")
							 | 
						||
| 
								 | 
							
								  HOLD=" "
							 | 
						||
| 
								 | 
							
								  TAB="  "
							 | 
						||
| 
								 | 
							
								  TAB3="      "
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# ------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Sets symbolic icons used throughout user feedback and prompts.
							 | 
						||
| 
								 | 
							
								# ------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								icons() {
							 | 
						||
| 
								 | 
							
								  CM="${TAB}✔️${TAB}"
							 | 
						||
| 
								 | 
							
								  CROSS="${TAB}✖️${TAB}"
							 | 
						||
| 
								 | 
							
								  DNSOK="✔️ "
							 | 
						||
| 
								 | 
							
								  DNSFAIL="${TAB}✖️${TAB}"
							 | 
						||
| 
								 | 
							
								  INFO="${TAB}💡${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  OS="${TAB}🖥️${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  OSVERSION="${TAB}🌟${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  CONTAINERTYPE="${TAB}📦${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  DISKSIZE="${TAB}💾${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  CPUCORE="${TAB}🧠${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  RAMSIZE="${TAB}🛠️${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  SEARCH="${TAB}🔍${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  VERBOSE_CROPPED="🔍${TAB}"
							 | 
						||
| 
								 | 
							
								  VERIFYPW="${TAB}🔐${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  CONTAINERID="${TAB}🆔${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  HOSTNAME="${TAB}🏠${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  BRIDGE="${TAB}🌉${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  NETWORK="${TAB}📡${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  GATEWAY="${TAB}🌐${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  DISABLEIPV6="${TAB}🚫${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  DEFAULT="${TAB}⚙️${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  MACADDRESS="${TAB}🔗${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  VLANTAG="${TAB}🏷️${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  ROOTSSH="${TAB}🔑${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  CREATING="${TAB}🚀${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  ADVANCED="${TAB}🧩${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								  FUSE="${TAB}🗂️${TAB}${CL}"
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# ------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Sets default retry and wait variables used for system actions.
							 | 
						||
| 
								 | 
							
								# ------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								default_vars() {
							 | 
						||
| 
								 | 
							
								  RETRY_NUM=10
							 | 
						||
| 
								 | 
							
								  RETRY_EVERY=3
							 | 
						||
| 
								 | 
							
								  i=$RETRY_NUM
							 | 
						||
| 
								 | 
							
								  #[[ "${VAR_OS:-}" == "unknown" ]]
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# ------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Sets default verbose mode for script and os execution.
							 | 
						||
| 
								 | 
							
								# ------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								set_std_mode() {
							 | 
						||
| 
								 | 
							
								  if [ "${VERBOSE:-no}" = "yes" ]; then
							 | 
						||
| 
								 | 
							
								    STD=""
							 | 
						||
| 
								 | 
							
								  else
							 | 
						||
| 
								 | 
							
								    STD="silent"
							 | 
						||
| 
								 | 
							
								  fi
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# Silent execution function
							 | 
						||
| 
								 | 
							
								silent() {
							 | 
						||
| 
								 | 
							
								  "$@" >/dev/null 2>&1
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# Function to download & save header files
							 | 
						||
| 
								 | 
							
								get_header() {
							 | 
						||
| 
								 | 
							
								  local app_name=$(echo "${APP,,}" | tr -d ' ')
							 | 
						||
| 
								 | 
							
								  local app_type=${APP_TYPE:-ct} # Default 'ct'
							 | 
						||
| 
								 | 
							
								  local header_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/${app_type}/headers/${app_name}"
							 | 
						||
| 
								 | 
							
								  local local_header_path="/usr/local/community-scripts/headers/${app_type}/${app_name}"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  mkdir -p "$(dirname "$local_header_path")"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if [ ! -s "$local_header_path" ]; then
							 | 
						||
| 
								 | 
							
								    if ! curl -fsSL "$header_url" -o "$local_header_path"; then
							 | 
						||
| 
								 | 
							
								      return 1
							 | 
						||
| 
								 | 
							
								    fi
							 | 
						||
| 
								 | 
							
								  fi
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  cat "$local_header_path" 2>/dev/null || true
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								header_info() {
							 | 
						||
| 
								 | 
							
								  local app_name=$(echo "${APP,,}" | tr -d ' ')
							 | 
						||
| 
								 | 
							
								  local header_content
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  header_content=$(get_header "$app_name") || header_content=""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  clear
							 | 
						||
| 
								 | 
							
								  local term_width
							 | 
						||
| 
								 | 
							
								  term_width=$(tput cols 2>/dev/null || echo 120)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if [ -n "$header_content" ]; then
							 | 
						||
| 
								 | 
							
								    echo "$header_content"
							 | 
						||
| 
								 | 
							
								  fi
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# ------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Performs a curl request with retry logic and inline feedback.
							 | 
						||
| 
								 | 
							
								# ------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								run_curl() {
							 | 
						||
| 
								 | 
							
								  if [ "$VERBOSE" = "no" ]; then
							 | 
						||
| 
								 | 
							
								    $STD curl "$@"
							 | 
						||
| 
								 | 
							
								  else
							 | 
						||
| 
								 | 
							
								    curl "$@"
							 | 
						||
| 
								 | 
							
								  fi
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								curl_handler() {
							 | 
						||
| 
								 | 
							
								  set +e
							 | 
						||
| 
								 | 
							
								  trap 'set -e' RETURN
							 | 
						||
| 
								 | 
							
								  local args=()
							 | 
						||
| 
								 | 
							
								  local url=""
							 | 
						||
| 
								 | 
							
								  local max_retries=3
							 | 
						||
| 
								 | 
							
								  local delay=2
							 | 
						||
| 
								 | 
							
								  local attempt=1
							 | 
						||
| 
								 | 
							
								  local exit_code
							 | 
						||
| 
								 | 
							
								  local has_output_file=false
							 | 
						||
| 
								 | 
							
								  local result=""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  # Parse arguments
							 | 
						||
| 
								 | 
							
								  for arg in "$@"; do
							 | 
						||
| 
								 | 
							
								    if [[ "$arg" != -* && -z "$url" ]]; then
							 | 
						||
| 
								 | 
							
								      url="$arg"
							 | 
						||
| 
								 | 
							
								    fi
							 | 
						||
| 
								 | 
							
								    [[ "$arg" == "-o" || "$arg" == --output ]] && has_output_file=true
							 | 
						||
| 
								 | 
							
								    args+=("$arg")
							 | 
						||
| 
								 | 
							
								  done
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if [[ -z "$url" ]]; then
							 | 
						||
| 
								 | 
							
								    msg_error "No valid URL or option entered for curl_handler"
							 | 
						||
| 
								 | 
							
								    return 1
							 | 
						||
| 
								 | 
							
								  fi
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  $STD msg_info "Fetching: $url"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  while [[ $attempt -le $max_retries ]]; do
							 | 
						||
| 
								 | 
							
								    if $has_output_file; then
							 | 
						||
| 
								 | 
							
								      $STD run_curl "${args[@]}"
							 | 
						||
| 
								 | 
							
								      exit_code=$?
							 | 
						||
| 
								 | 
							
								    else
							 | 
						||
| 
								 | 
							
								      result=$(run_curl "${args[@]}")
							 | 
						||
| 
								 | 
							
								      exit_code=$?
							 | 
						||
| 
								 | 
							
								    fi
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if [[ $exit_code -eq 0 ]]; then
							 | 
						||
| 
								 | 
							
								      $STD msg_ok "Fetched: $url"
							 | 
						||
| 
								 | 
							
								      $has_output_file || printf '%s' "$result"
							 | 
						||
| 
								 | 
							
								      return 0
							 | 
						||
| 
								 | 
							
								    fi
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if ((attempt >= max_retries)); then
							 | 
						||
| 
								 | 
							
								      # Read error log if it exists
							 | 
						||
| 
								 | 
							
								      if [ -s /tmp/curl_error.log ]; then
							 | 
						||
| 
								 | 
							
								        local curl_stderr
							 | 
						||
| 
								 | 
							
								        curl_stderr=$(</tmp/curl_error.log)
							 | 
						||
| 
								 | 
							
								        rm -f /tmp/curl_error.log
							 | 
						||
| 
								 | 
							
								      fi
							 | 
						||
| 
								 | 
							
								      __curl_err_handler "$exit_code" "$url" "${curl_stderr:-}"
							 | 
						||
| 
								 | 
							
								      exit
							 | 
						||
| 
								 | 
							
								    fi
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    $STD printf "\r\033[K${INFO}${YW}Retry $attempt/$max_retries in ${delay}s...${CL}" >&2
							 | 
						||
| 
								 | 
							
								    sleep "$delay"
							 | 
						||
| 
								 | 
							
								    ((attempt++))
							 | 
						||
| 
								 | 
							
								  done
							 | 
						||
| 
								 | 
							
								  set -e
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# ------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								# Handles specific curl error codes and displays descriptive messages.
							 | 
						||
| 
								 | 
							
								# ------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								__curl_err_handler() {
							 | 
						||
| 
								 | 
							
								  local exit_code="$1"
							 | 
						||
| 
								 | 
							
								  local target="$2"
							 | 
						||
| 
								 | 
							
								  local curl_msg="$3"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  case $exit_code in
							 | 
						||
| 
								 | 
							
								  1) msg_error "Unsupported protocol: $target" ;;
							 | 
						||
| 
								 | 
							
								  2) msg_error "Curl init failed: $target" ;;
							 | 
						||
| 
								 | 
							
								  3) msg_error "Malformed URL: $target" ;;
							 | 
						||
| 
								 | 
							
								  5) msg_error "Proxy resolution failed: $target" ;;
							 | 
						||
| 
								 | 
							
								  6) msg_error "Host resolution failed: $target" ;;
							 | 
						||
| 
								 | 
							
								  7) msg_error "Connection failed: $target" ;;
							 | 
						||
| 
								 | 
							
								  9) msg_error "Access denied: $target" ;;
							 | 
						||
| 
								 | 
							
								  18) msg_error "Partial file transfer: $target" ;;
							 | 
						||
| 
								 | 
							
								  22) msg_error "HTTP error (e.g. 400/404): $target" ;;
							 | 
						||
| 
								 | 
							
								  23) msg_error "Write error on local system: $target" ;;
							 | 
						||
| 
								 | 
							
								  26) msg_error "Read error from local file: $target" ;;
							 | 
						||
| 
								 | 
							
								  28) msg_error "Timeout: $target" ;;
							 | 
						||
| 
								 | 
							
								  35) msg_error "SSL connect error: $target" ;;
							 | 
						||
| 
								 | 
							
								  47) msg_error "Too many redirects: $target" ;;
							 | 
						||
| 
								 | 
							
								  51) msg_error "SSL cert verify failed: $target" ;;
							 | 
						||
| 
								 | 
							
								  52) msg_error "Empty server response: $target" ;;
							 | 
						||
| 
								 | 
							
								  55) msg_error "Send error: $target" ;;
							 | 
						||
| 
								 | 
							
								  56) msg_error "Receive error: $target" ;;
							 | 
						||
| 
								 | 
							
								  60) msg_error "SSL CA not trusted: $target" ;;
							 | 
						||
| 
								 | 
							
								  67) msg_error "Login denied by server: $target" ;;
							 | 
						||
| 
								 | 
							
								  78) msg_error "Remote file not found (404): $target" ;;
							 | 
						||
| 
								 | 
							
								  *) msg_error "Curl failed with code $exit_code: $target" ;;
							 | 
						||
| 
								 | 
							
								  esac
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  [[ -n "$curl_msg" ]] && printf "%s\n" "$curl_msg" >&2
							 | 
						||
| 
								 | 
							
								  exit 1
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								fatal() {
							 | 
						||
| 
								 | 
							
								  msg_error "$1"
							 | 
						||
| 
								 | 
							
								  kill -INT $$
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# Ensure POSIX compatibility across Alpine and Debian/Ubuntu
							 | 
						||
| 
								 | 
							
								# === Spinner Start ===
							 | 
						||
| 
								 | 
							
								# Trap cleanup on various signals
							 | 
						||
| 
								 | 
							
								trap 'cleanup_spinner' EXIT INT TERM HUP
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								spinner_frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# === Spinner Start ===
							 | 
						||
| 
								 | 
							
								start_spinner() {
							 | 
						||
| 
								 | 
							
								  local msg="$1"
							 | 
						||
| 
								 | 
							
								  local spin_i=0
							 | 
						||
| 
								 | 
							
								  local interval=0.1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  stop_spinner
							 | 
						||
| 
								 | 
							
								  SPINNER_MSG="$msg"
							 | 
						||
| 
								 | 
							
								  SPINNER_ACTIVE=1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  {
							 | 
						||
| 
								 | 
							
								    while [[ "$SPINNER_ACTIVE" -eq 1 ]]; do
							 | 
						||
| 
								 | 
							
								      if [[ -t 2 ]]; then
							 | 
						||
| 
								 | 
							
								        printf "\r\e[2K%s %b" "${TAB}${spinner_frames[spin_i]}${TAB}" "${YW}${SPINNER_MSG}${CL}" >&2
							 | 
						||
| 
								 | 
							
								      else
							 | 
						||
| 
								 | 
							
								        printf "%s...\n" "$SPINNER_MSG" >&2
							 | 
						||
| 
								 | 
							
								        break
							 | 
						||
| 
								 | 
							
								      fi
							 | 
						||
| 
								 | 
							
								      spin_i=$(((spin_i + 1) % ${#spinner_frames[@]}))
							 | 
						||
| 
								 | 
							
								      sleep "$interval"
							 | 
						||
| 
								 | 
							
								    done
							 | 
						||
| 
								 | 
							
								  } &
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  local pid=$!
							 | 
						||
| 
								 | 
							
								  if ps -p "$pid" >/dev/null 2>&1; then
							 | 
						||
| 
								 | 
							
								    SPINNER_PID="$pid"
							 | 
						||
| 
								 | 
							
								  else
							 | 
						||
| 
								 | 
							
								    SPINNER_ACTIVE=0
							 | 
						||
| 
								 | 
							
								    SPINNER_PID=""
							 | 
						||
| 
								 | 
							
								  fi
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# === Spinner Stop ===
							 | 
						||
| 
								 | 
							
								stop_spinner() {
							 | 
						||
| 
								 | 
							
								  if [[ "$SPINNER_ACTIVE" -eq 1 && -n "$SPINNER_PID" ]]; then
							 | 
						||
| 
								 | 
							
								    SPINNER_ACTIVE=0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if kill -0 "$SPINNER_PID" 2>/dev/null; then
							 | 
						||
| 
								 | 
							
								      kill "$SPINNER_PID" 2>/dev/null || true
							 | 
						||
| 
								 | 
							
								      for _ in $(seq 1 10); do
							 | 
						||
| 
								 | 
							
								        sleep 0.05
							 | 
						||
| 
								 | 
							
								        kill -0 "$SPINNER_PID" 2>/dev/null || break
							 | 
						||
| 
								 | 
							
								      done
							 | 
						||
| 
								 | 
							
								    fi
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if [[ "$SPINNER_PID" =~ ^[0-9]+$ ]]; then
							 | 
						||
| 
								 | 
							
								      ps -p "$SPINNER_PID" -o pid= >/dev/null 2>&1 && wait "$SPINNER_PID" 2>/dev/null || true
							 | 
						||
| 
								 | 
							
								    fi
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    printf "\r\e[2K" >&2
							 | 
						||
| 
								 | 
							
								    SPINNER_PID=""
							 | 
						||
| 
								 | 
							
								  fi
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								cleanup_spinner() {
							 | 
						||
| 
								 | 
							
								  stop_spinner
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								msg_info() {
							 | 
						||
| 
								 | 
							
								  local msg="$1"
							 | 
						||
| 
								 | 
							
								  [[ -z "$msg" || -n "${MSG_INFO_SHOWN["$msg"]+x}" ]] && return
							 | 
						||
| 
								 | 
							
								  MSG_INFO_SHOWN["$msg"]=1
							 | 
						||
| 
								 | 
							
								  stop_spinner
							 | 
						||
| 
								 | 
							
								  start_spinner "$msg"
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								msg_ok() {
							 | 
						||
| 
								 | 
							
								  local msg="$1"
							 | 
						||
| 
								 | 
							
								  [[ -z "$msg" ]] && return
							 | 
						||
| 
								 | 
							
								  stop_spinner
							 | 
						||
| 
								 | 
							
								  printf "\r\e[2K%s %b\n" "$CM" "${GN}${msg}${CL}" >&2
							 | 
						||
| 
								 | 
							
								  unset MSG_INFO_SHOWN["$msg"]
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								msg_error() {
							 | 
						||
| 
								 | 
							
								  local msg="$1"
							 | 
						||
| 
								 | 
							
								  [[ -z "$msg" ]] && return
							 | 
						||
| 
								 | 
							
								  stop_spinner
							 | 
						||
| 
								 | 
							
								  printf "\r\e[2K%s %b\n" "$CROSS" "${RD}${msg}${CL}" >&2
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								msg_warn() {
							 | 
						||
| 
								 | 
							
								  local msg="$1"
							 | 
						||
| 
								 | 
							
								  [[ -z "$msg" ]] && return
							 | 
						||
| 
								 | 
							
								  stop_spinner
							 | 
						||
| 
								 | 
							
								  printf "\r\e[2K%s %b\n" "$INFO" "${YWB}${msg}${CL}" >&2
							 | 
						||
| 
								 | 
							
								  unset MSG_INFO_SHOWN["$msg"]
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								msg_custom() {
							 | 
						||
| 
								 | 
							
								  local symbol="${1:-"[*]"}"
							 | 
						||
| 
								 | 
							
								  local color="${2:-"\e[36m"}" # Default: Cyan
							 | 
						||
| 
								 | 
							
								  local msg="${3:-}"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  [[ -z "$msg" ]] && return
							 | 
						||
| 
								 | 
							
								  stop_spinner 2>/dev/null || true
							 | 
						||
| 
								 | 
							
								  printf "\r\e[2K%s %b\n" "$symbol" "${color}${msg}${CL:-\e[0m}" >&2
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								msg_progress() {
							 | 
						||
| 
								 | 
							
								  local current="$1"
							 | 
						||
| 
								 | 
							
								  local total="$2"
							 | 
						||
| 
								 | 
							
								  local label="$3"
							 | 
						||
| 
								 | 
							
								  local width=40
							 | 
						||
| 
								 | 
							
								  local filled percent bar empty
							 | 
						||
| 
								 | 
							
								  local fill_char="#"
							 | 
						||
| 
								 | 
							
								  local empty_char="-"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if ! [[ "$current" =~ ^[0-9]+$ ]] || ! [[ "$total" =~ ^[0-9]+$ ]] || [[ "$total" -eq 0 ]]; then
							 | 
						||
| 
								 | 
							
								    printf "\r\e[2K%s %b\n" "$CROSS" "${RD}Invalid progress input${CL}" >&2
							 | 
						||
| 
								 | 
							
								    return
							 | 
						||
| 
								 | 
							
								  fi
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  percent=$(((current * 100) / total))
							 | 
						||
| 
								 | 
							
								  filled=$(((current * width) / total))
							 | 
						||
| 
								 | 
							
								  empty=$((width - filled))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  bar=$(printf "%${filled}s" | tr ' ' "$fill_char")
							 | 
						||
| 
								 | 
							
								  bar+=$(printf "%${empty}s" | tr ' ' "$empty_char")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  printf "\r\e[2K%s [%s] %3d%% %s" "${TAB}" "$bar" "$percent" "$label" >&2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if [[ "$current" -eq "$total" ]]; then
							 | 
						||
| 
								 | 
							
								    printf "\n" >&2
							 | 
						||
| 
								 | 
							
								  fi
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								run_container_safe() {
							 | 
						||
| 
								 | 
							
								  local ct="$1"
							 | 
						||
| 
								 | 
							
								  shift
							 | 
						||
| 
								 | 
							
								  local cmd="$*"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  lxc-attach -n "$ct" -- bash -euo pipefail -c "
							 | 
						||
| 
								 | 
							
								    trap 'echo Aborted in container; exit 130' SIGINT SIGTERM
							 | 
						||
| 
								 | 
							
								    $cmd
							 | 
						||
| 
								 | 
							
								  " || __handle_general_error "lxc-attach to CT $ct"
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								check_or_create_swap() {
							 | 
						||
| 
								 | 
							
								  msg_info "Checking for active swap"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if swapon --noheadings --show | grep -q 'swap'; then
							 | 
						||
| 
								 | 
							
								    msg_ok "Swap is active"
							 | 
						||
| 
								 | 
							
								    return 0
							 | 
						||
| 
								 | 
							
								  fi
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  msg_error "No active swap detected"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  read -p "Do you want to create a swap file? [y/N]: " create_swap
							 | 
						||
| 
								 | 
							
								  create_swap="${create_swap,,}" # to lowercase
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if [[ "$create_swap" != "y" && "$create_swap" != "yes" ]]; then
							 | 
						||
| 
								 | 
							
								    msg_info "Skipping swap file creation"
							 | 
						||
| 
								 | 
							
								    return 1
							 | 
						||
| 
								 | 
							
								  fi
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  read -p "Enter swap size in MB (e.g., 2048 for 2GB): " swap_size_mb
							 | 
						||
| 
								 | 
							
								  if ! [[ "$swap_size_mb" =~ ^[0-9]+$ ]]; then
							 | 
						||
| 
								 | 
							
								    msg_error "Invalid size input. Aborting."
							 | 
						||
| 
								 | 
							
								    return 1
							 | 
						||
| 
								 | 
							
								  fi
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  local swap_file="/swapfile"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  msg_info "Creating ${swap_size_mb}MB swap file at $swap_file"
							 | 
						||
| 
								 | 
							
								  if dd if=/dev/zero of="$swap_file" bs=1M count="$swap_size_mb" status=progress &&
							 | 
						||
| 
								 | 
							
								    chmod 600 "$swap_file" &&
							 | 
						||
| 
								 | 
							
								    mkswap "$swap_file" &&
							 | 
						||
| 
								 | 
							
								    swapon "$swap_file"; then
							 | 
						||
| 
								 | 
							
								    msg_ok "Swap file created and activated successfully"
							 | 
						||
| 
								 | 
							
								  else
							 | 
						||
| 
								 | 
							
								    msg_error "Failed to create or activate swap"
							 | 
						||
| 
								 | 
							
								    return 1
							 | 
						||
| 
								 | 
							
								  fi
							 | 
						||
| 
								 | 
							
								}
							 |