Core layer refactor: centralized error traps and msg_* consistency (#5705)
This commit is contained in:
parent
a95be13c95
commit
7561e26c0a
@ -83,11 +83,6 @@ update_os() {
|
|||||||
msg_info "Updating Container OS"
|
msg_info "Updating Container OS"
|
||||||
$STD apk -U upgrade
|
$STD apk -U upgrade
|
||||||
msg_ok "Updated Container OS"
|
msg_ok "Updated Container OS"
|
||||||
|
|
||||||
msg_info "Installing core dependencies"
|
|
||||||
$STD apk update
|
|
||||||
$STD apk add newt curl openssh nano mc ncurses gpg
|
|
||||||
msg_ok "Core dependencies installed"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# This function modifies the message of the day (motd) and SSH settings
|
# This function modifies the message of the day (motd) and SSH settings
|
||||||
|
132
misc/build.func
132
misc/build.func
@ -304,13 +304,12 @@ echo_default() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Output the selected values with icons
|
# Output the selected values with icons
|
||||||
echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os${CL}"
|
echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}${CT_ID}${CL}"
|
||||||
echo -e "${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}"
|
echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os ($var_version)${CL}"
|
||||||
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}"
|
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}"
|
||||||
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}"
|
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}"
|
||||||
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}"
|
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}"
|
||||||
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}"
|
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}"
|
||||||
echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}${CT_ID}${CL}"
|
|
||||||
if [ "$VERB" == "yes" ]; then
|
if [ "$VERB" == "yes" ]; then
|
||||||
echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}Enabled${CL}"
|
echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}Enabled${CL}"
|
||||||
fi
|
fi
|
||||||
@ -1095,7 +1094,9 @@ build_container() {
|
|||||||
# This executes create_lxc.sh and creates the container and .conf file
|
# This executes create_lxc.sh and creates the container and .conf file
|
||||||
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/create_lxc.sh)" $?
|
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/create_lxc.sh)" $?
|
||||||
|
|
||||||
LXC_CONFIG=/etc/pve/lxc/${CTID}.conf
|
LXC_CONFIG="/etc/pve/lxc/${CTID}.conf"
|
||||||
|
|
||||||
|
# USB passthrough for privileged LXC (CT_TYPE=0)
|
||||||
if [ "$CT_TYPE" == "0" ]; then
|
if [ "$CT_TYPE" == "0" ]; then
|
||||||
cat <<EOF >>"$LXC_CONFIG"
|
cat <<EOF >>"$LXC_CONFIG"
|
||||||
# USB passthrough
|
# USB passthrough
|
||||||
@ -1111,38 +1112,98 @@ lxc.mount.entry: /dev/ttyACM1 dev/ttyACM1 none bind,optional,create=
|
|||||||
EOF
|
EOF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$CT_TYPE" == "0" ]; then
|
# VAAPI passthrough for privileged containers or known apps
|
||||||
if [[ "$APP" == "Channels" || "$APP" == "Emby" || "$APP" == "ErsatzTV" || "$APP" == "Frigate" || "$APP" == "Jellyfin" || "$APP" == "Plex" || "$APP" == "immich" || "$APP" == "Tdarr" || "$APP" == "Open WebUI" || "$APP" == "Unmanic" || "$APP" == "Ollama" || "$APP" == "FileFlows" ]]; then
|
VAAPI_APPS=(
|
||||||
cat <<EOF >>"$LXC_CONFIG"
|
"immich"
|
||||||
# VAAPI hardware transcoding
|
"Channels"
|
||||||
lxc.cgroup2.devices.allow: c 226:0 rwm
|
"Emby"
|
||||||
lxc.cgroup2.devices.allow: c 226:128 rwm
|
"ErsatzTV"
|
||||||
lxc.cgroup2.devices.allow: c 29:0 rwm
|
"Frigate"
|
||||||
lxc.mount.entry: /dev/fb0 dev/fb0 none bind,optional,create=file
|
"Jellyfin"
|
||||||
lxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir
|
"Plex"
|
||||||
lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file
|
"Scrypted"
|
||||||
EOF
|
"Tdarr"
|
||||||
|
"Unmanic"
|
||||||
|
"Ollama"
|
||||||
|
"FileFlows"
|
||||||
|
"Open WebUI"
|
||||||
|
)
|
||||||
|
|
||||||
|
is_vaapi_app=false
|
||||||
|
for vaapi_app in "${VAAPI_APPS[@]}"; do
|
||||||
|
if [[ "$APP" == "$vaapi_app" ]]; then
|
||||||
|
is_vaapi_app=true
|
||||||
|
break
|
||||||
fi
|
fi
|
||||||
else
|
done
|
||||||
if [[ "$APP" == "Channels" || "$APP" == "Emby" || "$APP" == "ErsatzTV" || "$APP" == "Frigate" || "$APP" == "Jellyfin" || "$APP" == "Plex" || "$APP" == "immich" || "$APP" == "Tdarr" || "$APP" == "Open WebUI" || "$APP" == "Unmanic" || "$APP" == "Ollama" || "$APP" == "FileFlows" ]]; then
|
|
||||||
if [[ -e "/dev/dri/renderD128" ]]; then
|
if ([ "$CT_TYPE" == "0" ] || [ "$is_vaapi_app" == "true" ]) &&
|
||||||
if [[ -e "/dev/dri/card0" ]]; then
|
([[ -e /dev/dri/renderD128 ]] || [[ -e /dev/dri/card0 ]] || [[ -e /dev/fb0 ]]); then
|
||||||
cat <<EOF >>"$LXC_CONFIG"
|
|
||||||
# VAAPI hardware transcoding
|
echo ""
|
||||||
dev0: /dev/dri/card0,gid=44
|
msg_custom "⚙️ " "\e[96m" "Configuring VAAPI passthrough for LXC container"
|
||||||
dev1: /dev/dri/renderD128,gid=104
|
|
||||||
EOF
|
if [ "$CT_TYPE" != "0" ]; then
|
||||||
else
|
msg_custom "⚠️ " "\e[33m" "Container is unprivileged – VAAPI passthrough may not work without additional host configuration (e.g., idmap)."
|
||||||
cat <<EOF >>"$LXC_CONFIG"
|
fi
|
||||||
# VAAPI hardware transcoding
|
|
||||||
dev0: /dev/dri/card1,gid=44
|
msg_custom "ℹ️ " "\e[96m" "VAAPI enables GPU hardware acceleration (e.g., for video transcoding in Jellyfin or Plex)."
|
||||||
dev1: /dev/dri/renderD128,gid=104
|
|
||||||
EOF
|
echo ""
|
||||||
|
read -rp "➤ Automatically mount all available VAAPI devices? [Y/n]: " VAAPI_ALL
|
||||||
|
|
||||||
|
if [[ "$VAAPI_ALL" =~ ^[Yy]$|^$ ]]; then
|
||||||
|
# Mount all devices automatically
|
||||||
|
if [[ -e /dev/dri/renderD128 ]]; then
|
||||||
|
echo "lxc.cgroup2.devices.allow: c 226:128 rwm" >>"$LXC_CONFIG"
|
||||||
|
echo "lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file" >>"$LXC_CONFIG"
|
||||||
|
fi
|
||||||
|
if [[ -e /dev/dri/card0 ]]; then
|
||||||
|
echo "lxc.cgroup2.devices.allow: c 226:0 rwm" >>"$LXC_CONFIG"
|
||||||
|
|
||||||
|
echo "lxc.mount.entry: /dev/dri/card0 dev/dri/card0 none bind,optional,create=file" >>"$LXC_CONFIG"
|
||||||
|
fi
|
||||||
|
if [[ -e /dev/fb0 ]]; then
|
||||||
|
echo "lxc.cgroup2.devices.allow: c 29:0 rwm" >>"$LXC_CONFIG"
|
||||||
|
echo "lxc.mount.entry: /dev/fb0 dev/fb0 none bind,optional,create=file" >>"$LXC_CONFIG"
|
||||||
|
fi
|
||||||
|
if [[ -d /dev/dri ]]; then
|
||||||
|
echo "lxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir" >>"$LXC_CONFIG"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Manual selection per device
|
||||||
|
if [[ -e /dev/dri/renderD128 ]]; then
|
||||||
|
read -rp "➤ Mount /dev/dri/renderD128 (GPU rendering)? [y/N]: " MOUNT_D128
|
||||||
|
if [[ "$MOUNT_D128" =~ ^[Yy]$ ]]; then
|
||||||
|
echo "lxc.cgroup2.devices.allow: c 226:128 rwm" >>"$LXC_CONFIG"
|
||||||
|
echo "lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file" >>"$LXC_CONFIG"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ -e /dev/dri/card0 ]]; then
|
||||||
|
read -rp "➤ Mount /dev/dri/card0 (GPU hardware interface)? [y/N]: " MOUNT_CARD0
|
||||||
|
if [[ "$MOUNT_CARD0" =~ ^[Yy]$ ]]; then
|
||||||
|
echo "lxc.cgroup2.devices.allow: c 226:0 rwm" >>"$LXC_CONFIG"
|
||||||
|
echo "lxc.mount.entry: /dev/dri/card0 dev/dri/card0 none bind,optional,create=file" >>"$LXC_CONFIG"
|
||||||
|
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -e /dev/fb0 ]]; then
|
||||||
|
read -rp "➤ Mount /dev/fb0 (Framebuffer, GUI)? [y/N]: " MOUNT_FB0
|
||||||
|
if [[ "$MOUNT_FB0" =~ ^[Yy]$ ]]; then
|
||||||
|
echo "lxc.cgroup2.devices.allow: c 29:0 rwm" >>"$LXC_CONFIG"
|
||||||
|
echo "lxc.mount.entry: /dev/fb0 dev/fb0 none bind,optional,create=file" >>"$LXC_CONFIG"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d /dev/dri ]]; then
|
||||||
|
echo "lxc.mount.entry: /dev/dri dev/dri none bind,optional,create=dir" >>"$LXC_CONFIG"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# TUN device passthrough
|
||||||
if [ "$ENABLE_TUN" == "yes" ]; then
|
if [ "$ENABLE_TUN" == "yes" ]; then
|
||||||
cat <<EOF >>"$LXC_CONFIG"
|
cat <<EOF >>"$LXC_CONFIG"
|
||||||
lxc.cgroup2.devices.allow: c 10:200 rwm
|
lxc.cgroup2.devices.allow: c 10:200 rwm
|
||||||
@ -1172,10 +1233,13 @@ EOF'
|
|||||||
locale-gen >/dev/null && \
|
locale-gen >/dev/null && \
|
||||||
export LANG=\$locale_line"
|
export LANG=\$locale_line"
|
||||||
|
|
||||||
|
if [[ -z "${tz:-}" ]]; then
|
||||||
|
tz=$(timedatectl show --property=Timezone --value 2>/dev/null || echo "Etc/UTC")
|
||||||
|
fi
|
||||||
if pct exec "$CTID" -- test -e "/usr/share/zoneinfo/$tz"; then
|
if pct exec "$CTID" -- test -e "/usr/share/zoneinfo/$tz"; then
|
||||||
pct exec "$CTID" -- bash -c "echo $tz >/etc/timezone && ln -sf /usr/share/zoneinfo/$tz /etc/localtime"
|
pct exec "$CTID" -- bash -c "tz='$tz'; echo \"\$tz\" >/etc/timezone && ln -sf \"/usr/share/zoneinfo/\$tz\" /etc/localtime"
|
||||||
else
|
else
|
||||||
msg_info "Skipping timezone setup – zone '$tz' not found in container"
|
msg_warn "Skipping timezone setup – zone '$tz' not found in container"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
pct exec "$CTID" -- bash -c "apt-get update >/dev/null && apt-get install -y sudo curl mc gnupg2 >/dev/null"
|
pct exec "$CTID" -- bash -c "apt-get update >/dev/null && apt-get install -y sudo curl mc gnupg2 >/dev/null"
|
||||||
@ -1255,7 +1319,9 @@ api_exit_script() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
trap 'api_exit_script' EXIT
|
if command -v pveversion >/dev/null 2>&1; then
|
||||||
|
trap 'api_exit_script' EXIT
|
||||||
|
fi
|
||||||
trap 'post_update_to_api "failed" "$BASH_COMMAND"' ERR
|
trap 'post_update_to_api "failed" "$BASH_COMMAND"' ERR
|
||||||
trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
|
trap 'post_update_to_api "failed" "INTERRUPTED"' SIGINT
|
||||||
trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
|
trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
|
||||||
|
400
misc/core.func
400
misc/core.func
@ -1,30 +1,6 @@
|
|||||||
# Copyright (c) 2021-2025 community-scripts ORG
|
# Copyright (c) 2021-2025 community-scripts ORG
|
||||||
# License: MIT | https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/LICENSE
|
# 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).
|
# Loads core utility groups once (colors, formatting, icons, defaults).
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@ -43,100 +19,51 @@ load_functions() {
|
|||||||
# add more
|
# add more
|
||||||
}
|
}
|
||||||
|
|
||||||
on_error() {
|
# ============================================================================
|
||||||
local exit_code="$1"
|
# Error & Signal Handling – robust, universal, subshell-safe
|
||||||
local lineno="$2"
|
# ============================================================================
|
||||||
|
|
||||||
stop_spinner
|
_tool_error_hint() {
|
||||||
|
local cmd="$1"
|
||||||
case "$exit_code" in
|
local code="$2"
|
||||||
1) msg_error "Generic error occurred (line $lineno)" ;;
|
case "$cmd" in
|
||||||
2) msg_error "Shell misuse (line $lineno)" ;;
|
curl)
|
||||||
126) msg_error "Command cannot execute (line $lineno)" ;;
|
case "$code" in
|
||||||
127) msg_error "Command not found (line $lineno)" ;;
|
6) echo "Curl: Could not resolve host (DNS problem)" ;;
|
||||||
128) msg_error "Invalid exit argument (line $lineno)" ;;
|
7) echo "Curl: Failed to connect to host (connection refused)" ;;
|
||||||
130) msg_error "Script aborted by user (CTRL+C)" ;;
|
22) echo "Curl: HTTP error (404/403 etc)" ;;
|
||||||
143) msg_error "Script terminated by SIGTERM" ;;
|
28) echo "Curl: Operation timeout" ;;
|
||||||
*) msg_error "Script failed at line $lineno with exit code $exit_code" ;;
|
*) echo "Curl: Unknown error ($code)" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
exit "$exit_code"
|
|
||||||
}
|
|
||||||
|
|
||||||
on_exit() {
|
|
||||||
cleanup_spinner || true
|
|
||||||
[[ "${VERBOSE:-no}" == "yes" ]] && 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)
|
wget)
|
||||||
msg_error "Script terminated (SIGTERM)"
|
echo "Wget failed – URL unreachable or permission denied"
|
||||||
exit 143
|
|
||||||
;;
|
;;
|
||||||
*)
|
systemctl)
|
||||||
msg_error "Script interrupted (unknown signal: $signal)"
|
echo "Systemd unit failure – check service name and permissions"
|
||||||
exit 1
|
|
||||||
;;
|
;;
|
||||||
|
jq)
|
||||||
|
echo "jq parse error – malformed JSON or missing key"
|
||||||
|
;;
|
||||||
|
mariadb | mysql)
|
||||||
|
echo "MySQL/MariaDB command failed – check credentials or DB"
|
||||||
|
;;
|
||||||
|
unzip)
|
||||||
|
echo "unzip failed – corrupt file or missing permission"
|
||||||
|
;;
|
||||||
|
tar)
|
||||||
|
echo "tar failed – invalid format or missing binary"
|
||||||
|
;;
|
||||||
|
node | npm | pnpm | yarn)
|
||||||
|
echo "Node tool failed – check version compatibility or package.json"
|
||||||
|
;;
|
||||||
|
*) echo "" ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
__handle_unexpected_error() {
|
catch_errors() {
|
||||||
local exit_code="$1"
|
set -Eeuo pipefail
|
||||||
echo
|
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
|
||||||
[ -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"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@ -153,6 +80,13 @@ color() {
|
|||||||
CL=$(echo "\033[m")
|
CL=$(echo "\033[m")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Special for spinner and colorized output via printf
|
||||||
|
color_spinner() {
|
||||||
|
CS_YW=$'\033[33m'
|
||||||
|
CS_YWB=$'\033[93m'
|
||||||
|
CS_CL=$'\033[m'
|
||||||
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Defines formatting helpers like tab, bold, and line reset sequences.
|
# Defines formatting helpers like tab, bold, and line reset sequences.
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@ -196,6 +130,7 @@ icons() {
|
|||||||
ADVANCED="${TAB}🧩${TAB}${CL}"
|
ADVANCED="${TAB}🧩${TAB}${CL}"
|
||||||
FUSE="${TAB}🗂️${TAB}${CL}"
|
FUSE="${TAB}🗂️${TAB}${CL}"
|
||||||
HOURGLASS="${TAB}⏳${TAB}"
|
HOURGLASS="${TAB}⏳${TAB}"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@ -227,7 +162,7 @@ silent() {
|
|||||||
# Function to download & save header files
|
# Function to download & save header files
|
||||||
get_header() {
|
get_header() {
|
||||||
local app_name=$(echo "${APP,,}" | tr -d ' ')
|
local app_name=$(echo "${APP,,}" | tr -d ' ')
|
||||||
local app_type=${APP_TYPE:-ct} # Default 'ct'
|
local app_type=${APP_TYPE:-ct}
|
||||||
local header_url="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/${app_type}/headers/${app_name}"
|
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}"
|
local local_header_path="/usr/local/community-scripts/headers/${app_type}/${app_name}"
|
||||||
|
|
||||||
@ -257,77 +192,39 @@ header_info() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
ensure_tput() {
|
||||||
# Performs a curl request with retry logic and inline feedback.
|
if ! command -v tput >/dev/null 2>&1; then
|
||||||
# ------------------------------------------------------------------------------
|
if grep -qi 'alpine' /etc/os-release; then
|
||||||
|
apk add --no-cache ncurses >/dev/null 2>&1
|
||||||
run_curl() {
|
elif command -v apt-get >/dev/null 2>&1; then
|
||||||
if [ "$VERBOSE" = "no" ]; then
|
apt-get update -qq >/dev/null
|
||||||
$STD curl "$@"
|
apt-get install -y -qq ncurses-bin >/dev/null 2>&1
|
||||||
else
|
fi
|
||||||
curl "$@"
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
curl_handler() {
|
is_alpine() {
|
||||||
set +e
|
local os_id="${var_os:-${PCT_OSTYPE:-}}"
|
||||||
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
|
if [[ -z "$os_id" && -f /etc/os-release ]]; then
|
||||||
for arg in "$@"; do
|
os_id="$(
|
||||||
if [[ "$arg" != -* && -z "$url" ]]; then
|
. /etc/os-release 2>/dev/null
|
||||||
url="$arg"
|
echo "${ID:-}"
|
||||||
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
|
fi
|
||||||
|
|
||||||
$STD msg_info "Fetching: $url"
|
[[ "$os_id" == "alpine" ]]
|
||||||
|
}
|
||||||
|
|
||||||
while [[ $attempt -le $max_retries ]]; do
|
is_verbose_mode() {
|
||||||
if $has_output_file; then
|
local verbose="${VERBOSE:-${var_verbose:-no}}"
|
||||||
$STD run_curl "${args[@]}"
|
local tty_status
|
||||||
exit_code=$?
|
if [[ -t 2 ]]; then
|
||||||
else
|
tty_status="interactive"
|
||||||
result=$(run_curl "${args[@]}")
|
else
|
||||||
exit_code=$?
|
tty_status="not-a-tty"
|
||||||
fi
|
fi
|
||||||
|
[[ "$verbose" != "no" || ! -t 2 ]]
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@ -372,144 +269,93 @@ fatal() {
|
|||||||
kill -INT $$
|
kill -INT $$
|
||||||
}
|
}
|
||||||
|
|
||||||
# Ensure POSIX compatibility across Alpine and Debian/Ubuntu
|
spinner() {
|
||||||
# === Spinner Start ===
|
local chars=(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏)
|
||||||
# Trap cleanup on various signals
|
local i=0
|
||||||
trap 'cleanup_spinner' EXIT INT TERM HUP
|
while true; do
|
||||||
|
local index=$((i++ % ${#chars[@]}))
|
||||||
spinner_frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
|
printf "\r\033[2K%s %b" "${CS_YWB}${chars[$index]}${CS_CL}" "${CS_YWB}${SPINNER_MSG:-}${CS_CL}"
|
||||||
|
sleep 0.1
|
||||||
# === Spinner Start ===
|
done
|
||||||
start_spinner() {
|
}
|
||||||
local msg="$1"
|
|
||||||
local spin_i=0
|
clear_line() {
|
||||||
local interval=0.1
|
tput cr 2>/dev/null || echo -en "\r"
|
||||||
|
tput el 2>/dev/null || echo -en "\033[K"
|
||||||
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() {
|
stop_spinner() {
|
||||||
if [[ "$SPINNER_ACTIVE" -eq 1 && -n "$SPINNER_PID" ]]; then
|
local pid="${SPINNER_PID:-}"
|
||||||
SPINNER_ACTIVE=0
|
[[ -z "$pid" && -f /tmp/.spinner.pid ]] && pid=$(</tmp/.spinner.pid)
|
||||||
|
|
||||||
if kill -0 "$SPINNER_PID" 2>/dev/null; then
|
if [[ -n "$pid" && "$pid" =~ ^[0-9]+$ ]]; then
|
||||||
kill "$SPINNER_PID" 2>/dev/null || true
|
if kill "$pid" 2>/dev/null; then
|
||||||
for _ in $(seq 1 10); do
|
sleep 0.05
|
||||||
sleep 0.05
|
kill -9 "$pid" 2>/dev/null || true
|
||||||
kill -0 "$SPINNER_PID" 2>/dev/null || break
|
wait "$pid" 2>/dev/null || true
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
|
rm -f /tmp/.spinner.pid
|
||||||
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
|
fi
|
||||||
}
|
|
||||||
|
|
||||||
cleanup_spinner() {
|
unset SPINNER_PID SPINNER_MSG
|
||||||
stop_spinner
|
stty sane 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
msg_info() {
|
msg_info() {
|
||||||
local msg="$1"
|
local msg="$1"
|
||||||
[[ -z "$msg" || -n "${MSG_INFO_SHOWN["$msg"]+x}" ]] && return
|
[[ -z "$msg" ]] && return
|
||||||
|
|
||||||
|
if ! declare -p MSG_INFO_SHOWN &>/dev/null || ! declare -A MSG_INFO_SHOWN &>/dev/null; then
|
||||||
|
declare -gA MSG_INFO_SHOWN=()
|
||||||
|
fi
|
||||||
|
[[ -n "${MSG_INFO_SHOWN["$msg"]+x}" ]] && return
|
||||||
MSG_INFO_SHOWN["$msg"]=1
|
MSG_INFO_SHOWN["$msg"]=1
|
||||||
|
|
||||||
stop_spinner
|
stop_spinner
|
||||||
|
SPINNER_MSG="$msg"
|
||||||
|
|
||||||
if [[ "${VERBOSE:-no}" == "no" && -t 2 ]]; then
|
if is_verbose_mode || is_alpine; then
|
||||||
start_spinner "$msg"
|
local HOURGLASS="${TAB}⏳${TAB}"
|
||||||
else
|
|
||||||
printf "\r\e[2K%s %b" "$HOURGLASS" "${YW}${msg}${CL}" >&2
|
printf "\r\e[2K%s %b" "$HOURGLASS" "${YW}${msg}${CL}" >&2
|
||||||
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
color_spinner
|
||||||
|
spinner &
|
||||||
|
SPINNER_PID=$!
|
||||||
|
echo "$SPINNER_PID" >/tmp/.spinner.pid
|
||||||
|
disown "$SPINNER_PID" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
msg_ok() {
|
msg_ok() {
|
||||||
local msg="$1"
|
local msg="$1"
|
||||||
[[ -z "$msg" ]] && return
|
[[ -z "$msg" ]] && return
|
||||||
stop_spinner
|
stop_spinner
|
||||||
printf "\r\e[2K%s %b\n" "$CM" "${GN}${msg}${CL}" >&2
|
clear_line
|
||||||
|
printf "%s %b\n" "$CM" "${GN}${msg}${CL}" >&2
|
||||||
unset MSG_INFO_SHOWN["$msg"]
|
unset MSG_INFO_SHOWN["$msg"]
|
||||||
}
|
}
|
||||||
|
|
||||||
msg_error() {
|
msg_error() {
|
||||||
local msg="$1"
|
|
||||||
[[ -z "$msg" ]] && return
|
|
||||||
stop_spinner
|
stop_spinner
|
||||||
printf "\r\e[2K%s %b\n" "$CROSS" "${RD}${msg}${CL}" >&2
|
local msg="$1"
|
||||||
|
echo -e "${BFR:-} ${CROSS:-✖️} ${RD}${msg}${CL}"
|
||||||
}
|
}
|
||||||
|
|
||||||
msg_warn() {
|
msg_warn() {
|
||||||
local msg="$1"
|
|
||||||
[[ -z "$msg" ]] && return
|
|
||||||
stop_spinner
|
stop_spinner
|
||||||
printf "\r\e[2K%s %b\n" "$INFO" "${YWB}${msg}${CL}" >&2
|
local msg="$1"
|
||||||
unset MSG_INFO_SHOWN["$msg"]
|
echo -e "${BFR:-} ${INFO:-ℹ️} ${YWB}${msg}${CL}"
|
||||||
}
|
}
|
||||||
|
|
||||||
msg_custom() {
|
msg_custom() {
|
||||||
local symbol="${1:-"[*]"}"
|
local symbol="${1:-"[*]"}"
|
||||||
local color="${2:-"\e[36m"}" # Default: Cyan
|
local color="${2:-"\e[36m"}"
|
||||||
local msg="${3:-}"
|
local msg="${3:-}"
|
||||||
|
|
||||||
[[ -z "$msg" ]] && return
|
[[ -z "$msg" ]] && return
|
||||||
stop_spinner 2>/dev/null || true
|
stop_spinner
|
||||||
printf "\r\e[2K%s %b\n" "$symbol" "${color}${msg}${CL:-\e[0m}" >&2
|
echo -e "${BFR:-} ${symbol} ${color}${msg}${CL:-\e[0m}"
|
||||||
}
|
printf "\r\033[K\e[?25h\n"
|
||||||
|
|
||||||
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() {
|
run_container_safe() {
|
||||||
@ -560,3 +406,5 @@ check_or_create_swap() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trap 'stop_spinner' EXIT INT TERM
|
||||||
|
@ -21,36 +21,67 @@ fi
|
|||||||
# This sets error handling options and defines the error_handler function to handle errors
|
# This sets error handling options and defines the error_handler function to handle errors
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
|
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
|
||||||
|
trap on_exit EXIT
|
||||||
|
trap on_interrupt INT
|
||||||
|
trap on_terminate TERM
|
||||||
|
|
||||||
|
function on_exit() {
|
||||||
|
local exit_code="$?"
|
||||||
|
[[ -n "${lockfile:-}" && -e "$lockfile" ]] && rm -f "$lockfile"
|
||||||
|
exit "$exit_code"
|
||||||
|
}
|
||||||
|
|
||||||
# This function handles errors
|
|
||||||
function error_handler() {
|
function error_handler() {
|
||||||
printf "\e[?25h"
|
|
||||||
local exit_code="$?"
|
local exit_code="$?"
|
||||||
local line_number="$1"
|
local line_number="$1"
|
||||||
local command="$2"
|
local command="$2"
|
||||||
local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
|
printf "\e[?25h"
|
||||||
echo -e "\n$error_message\n"
|
echo -e "\n${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}\n"
|
||||||
exit 200
|
exit "$exit_code"
|
||||||
|
}
|
||||||
|
|
||||||
|
function on_interrupt() {
|
||||||
|
echo -e "\n${RD}Interrupted by user (SIGINT)${CL}"
|
||||||
|
exit 130
|
||||||
|
}
|
||||||
|
|
||||||
|
function on_terminate() {
|
||||||
|
echo -e "\n${RD}Terminated by signal (SIGTERM)${CL}"
|
||||||
|
exit 143
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_storage_support() {
|
||||||
|
local CONTENT="$1"
|
||||||
|
local -a VALID_STORAGES=()
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
local STORAGE=$(awk '{print $1}' <<<"$line")
|
||||||
|
[[ "$STORAGE" == "storage" || -z "$STORAGE" ]] && continue
|
||||||
|
VALID_STORAGES+=("$STORAGE")
|
||||||
|
done < <(pvesm status -content "$CONTENT" 2>/dev/null | awk 'NR>1')
|
||||||
|
|
||||||
|
[[ ${#VALID_STORAGES[@]} -gt 0 ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
# This checks for the presence of valid Container Storage and Template Storage locations
|
# This checks for the presence of valid Container Storage and Template Storage locations
|
||||||
msg_info "Validating Storage"
|
msg_info "Validating Storage"
|
||||||
VALIDCT=$(pvesm status -content rootdir | awk 'NR>1')
|
if ! check_storage_support "rootdir"; then
|
||||||
if [ -z "$VALIDCT" ]; then
|
|
||||||
msg_error "Unable to detect a valid Container Storage location."
|
msg_error "No valid storage found for 'rootdir' (Container)."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
VALIDTMP=$(pvesm status -content vztmpl | awk 'NR>1')
|
if ! check_storage_support "vztmpl"; then
|
||||||
if [ -z "$VALIDTMP" ]; then
|
|
||||||
msg_error "Unable to detect a valid Template Storage location."
|
msg_error "No valid storage found for 'vztmpl' (Template)."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
msg_ok "Validated Storage (rootdir / vztmpl)."
|
||||||
|
|
||||||
# This function is used to select the storage class and determine the corresponding storage content type and label.
|
# This function is used to select the storage class and determine the corresponding storage content type and label.
|
||||||
function select_storage() {
|
function select_storage() {
|
||||||
local CLASS=$1
|
local CLASS=$1 CONTENT CONTENT_LABEL
|
||||||
local CONTENT
|
|
||||||
local CONTENT_LABEL
|
|
||||||
case $CLASS in
|
case $CLASS in
|
||||||
container)
|
container)
|
||||||
CONTENT='rootdir'
|
CONTENT='rootdir'
|
||||||
@ -60,50 +91,74 @@ function select_storage() {
|
|||||||
CONTENT='vztmpl'
|
CONTENT='vztmpl'
|
||||||
CONTENT_LABEL='Container template'
|
CONTENT_LABEL='Container template'
|
||||||
;;
|
;;
|
||||||
*) false || {
|
iso)
|
||||||
msg_error "Invalid storage class."
|
CONTENT='iso'
|
||||||
exit 201
|
CONTENT_LABEL='ISO image'
|
||||||
} ;;
|
;;
|
||||||
|
images)
|
||||||
|
CONTENT='images'
|
||||||
|
CONTENT_LABEL='VM Disk image'
|
||||||
|
;;
|
||||||
|
backup)
|
||||||
|
CONTENT='backup'
|
||||||
|
CONTENT_LABEL='Backup'
|
||||||
|
;;
|
||||||
|
snippets)
|
||||||
|
CONTENT='snippets'
|
||||||
|
CONTENT_LABEL='Snippets'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
msg_error "Invalid storage class '$CLASS'"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Collect storage options
|
|
||||||
local -a MENU
|
local -a MENU
|
||||||
local MSG_MAX_LENGTH=0
|
local -A STORAGE_MAP
|
||||||
|
local COL_WIDTH=0
|
||||||
while read -r TAG TYPE _ _ _ FREE _; do
|
while read -r TAG TYPE _ TOTAL USED FREE _; do
|
||||||
local TYPE_PADDED
|
[[ -n "$TAG" && -n "$TYPE" ]] || continue
|
||||||
local FREE_FMT
|
local DISPLAY="${TAG} (${TYPE})"
|
||||||
|
local USED_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$USED")
|
||||||
TYPE_PADDED=$(printf "%-10s" "$TYPE")
|
local FREE_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$FREE")
|
||||||
FREE_FMT=$(numfmt --to=iec --from-unit=K --format %.2f <<<"$FREE")B
|
local INFO="Free: ${FREE_FMT}B Used: ${USED_FMT}B"
|
||||||
local ITEM="Type: $TYPE_PADDED Free: $FREE_FMT"
|
STORAGE_MAP["$DISPLAY"]="$TAG"
|
||||||
|
MENU+=("$DISPLAY" "$INFO" "OFF")
|
||||||
((${#ITEM} + 2 > MSG_MAX_LENGTH)) && MSG_MAX_LENGTH=$((${#ITEM} + 2))
|
((${#DISPLAY} > COL_WIDTH)) && COL_WIDTH=${#DISPLAY}
|
||||||
|
|
||||||
MENU+=("$TAG" "$ITEM" "OFF")
|
|
||||||
done < <(pvesm status -content "$CONTENT" | awk 'NR>1')
|
done < <(pvesm status -content "$CONTENT" | awk 'NR>1')
|
||||||
|
|
||||||
local OPTION_COUNT=$((${#MENU[@]} / 3))
|
if [ ${#MENU[@]} -eq 0 ]; then
|
||||||
|
msg_error "No storage found for content type '$CONTENT'."
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $((${#MENU[@]} / 3)) -eq 1 ]; then
|
||||||
|
STORAGE_RESULT="${STORAGE_MAP[${MENU[0]}]}"
|
||||||
|
|
||||||
# Auto-select if only one option available
|
|
||||||
if [[ "$OPTION_COUNT" -eq 1 ]]; then
|
|
||||||
echo "${MENU[0]}"
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Display selection menu
|
local WIDTH=$((COL_WIDTH + 42))
|
||||||
local STORAGE
|
|
||||||
while [[ -z "${STORAGE:+x}" ]]; do
|
|
||||||
STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \
|
|
||||||
"Select the storage pool to use for the ${CONTENT_LABEL,,}.\nUse the spacebar to make a selection.\n" \
|
|
||||||
16 $((MSG_MAX_LENGTH + 23)) 6 \
|
|
||||||
"${MENU[@]}" 3>&1 1>&2 2>&3) || {
|
|
||||||
msg_error "Storage selection cancelled."
|
|
||||||
exit 202
|
|
||||||
}
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "$STORAGE"
|
while true; do
|
||||||
|
local DISPLAY_SELECTED=$(
|
||||||
|
whiptail --backtitle "Proxmox VE Helper Scripts" \
|
||||||
|
--title "Storage Pools" \
|
||||||
|
--radiolist "Which storage pool for ${CONTENT_LABEL,,}?\n(Spacebar to select)"
|
||||||
|
|
||||||
|
16 "$WIDTH" 6 "${MENU[@]}" 3>&1 1>&2 2>&3
|
||||||
|
)
|
||||||
|
|
||||||
|
[[ $? -ne 0 ]] && return 3
|
||||||
|
|
||||||
|
if [[ -z "$DISPLAY_SELECTED" || -z "${STORAGE_MAP[$DISPLAY_SELECTED]+_}" ]]; then
|
||||||
|
whiptail --msgbox "No valid storage selected. Please try again." 8 58
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
STORAGE_RESULT="${STORAGE_MAP[$DISPLAY_SELECTED]}"
|
||||||
|
return 0
|
||||||
|
done
|
||||||
}
|
}
|
||||||
# Test if required variables are set
|
# Test if required variables are set
|
||||||
[[ "${CTID:-}" ]] || {
|
[[ "${CTID:-}" ]] || {
|
||||||
@ -137,6 +192,19 @@ msg_ok "Using ${BL}$TEMPLATE_STORAGE${CL} ${GN}for Template Storage."
|
|||||||
CONTAINER_STORAGE=$(select_storage container)
|
CONTAINER_STORAGE=$(select_storage container)
|
||||||
msg_ok "Using ${BL}$CONTAINER_STORAGE${CL} ${GN}for Container Storage."
|
msg_ok "Using ${BL}$CONTAINER_STORAGE${CL} ${GN}for Container Storage."
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if select_storage template; then
|
||||||
|
TEMPLATE_STORAGE="$STORAGE_RESULT"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if select_storage container; then
|
||||||
|
CONTAINER_STORAGE="$STORAGE_RESULT"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
# Check free space on selected container storage
|
# Check free space on selected container storage
|
||||||
STORAGE_FREE=$(pvesm status | awk -v s="$CONTAINER_STORAGE" '$1 == s { print $6 }')
|
STORAGE_FREE=$(pvesm status | awk -v s="$CONTAINER_STORAGE" '$1 == s { print $6 }')
|
||||||
REQUIRED_KB=$((${PCT_DISK_SIZE:-8} * 1024 * 1024))
|
REQUIRED_KB=$((${PCT_DISK_SIZE:-8} * 1024 * 1024))
|
||||||
@ -205,6 +273,8 @@ if ! pveam list "$TEMPLATE_STORAGE" | grep -q "$TEMPLATE" || ! zstdcat "$TEMPLAT
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
msg_ok "LXC Template '$TEMPLATE' is ready to use."
|
msg_ok "LXC Template '$TEMPLATE' is ready to use."
|
||||||
|
|
||||||
|
msg_info "Creating LXC Container"
|
||||||
# Check and fix subuid/subgid
|
# Check and fix subuid/subgid
|
||||||
grep -q "root:100000:65536" /etc/subuid || echo "root:100000:65536" >>/etc/subuid
|
grep -q "root:100000:65536" /etc/subuid || echo "root:100000:65536" >>/etc/subuid
|
||||||
grep -q "root:100000:65536" /etc/subgid || echo "root:100000:65536" >>/etc/subgid
|
grep -q "root:100000:65536" /etc/subgid || echo "root:100000:65536" >>/etc/subgid
|
||||||
@ -215,12 +285,15 @@ PCT_OPTIONS=(${PCT_OPTIONS[@]:-${DEFAULT_PCT_OPTIONS[@]}})
|
|||||||
|
|
||||||
# Secure creation of the LXC container with lock and template check
|
# Secure creation of the LXC container with lock and template check
|
||||||
lockfile="/tmp/template.${TEMPLATE}.lock"
|
lockfile="/tmp/template.${TEMPLATE}.lock"
|
||||||
exec 9>"$lockfile"
|
exec 9>"$lockfile" >/dev/null 2>&1 || {
|
||||||
|
msg_error "Failed to create lock file '$lockfile'."
|
||||||
|
exit 200
|
||||||
|
}
|
||||||
flock -w 60 9 || {
|
flock -w 60 9 || {
|
||||||
msg_error "Timeout while waiting for template lock"
|
msg_error "Timeout while waiting for template lock"
|
||||||
exit 211
|
exit 211
|
||||||
}
|
}
|
||||||
msg_info "Creating LXC Container"
|
|
||||||
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" &>/dev/null; then
|
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" &>/dev/null; then
|
||||||
msg_error "Container creation failed. Checking if template is corrupted or incomplete."
|
msg_error "Container creation failed. Checking if template is corrupted or incomplete."
|
||||||
|
|
||||||
@ -252,16 +325,23 @@ if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[
|
|||||||
sleep 1 # I/O-Sync-Delay
|
sleep 1 # I/O-Sync-Delay
|
||||||
|
|
||||||
msg_ok "Re-downloaded LXC Template"
|
msg_ok "Re-downloaded LXC Template"
|
||||||
|
fi
|
||||||
|
|
||||||
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" &>/dev/null; then
|
if ! pct list | awk '{print $1}' | grep -qx "$CTID"; then
|
||||||
msg_error "Container creation failed after re-downloading template."
|
msg_error "Container ID $CTID not listed in 'pct list' – unexpected failure."
|
||||||
exit 200
|
exit 215
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -q '^rootfs:' "/etc/pve/lxc/$CTID.conf"; then
|
||||||
|
msg_error "RootFS entry missing in container config – storage not correctly assigned."
|
||||||
|
exit 216
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q '^hostname:' "/etc/pve/lxc/$CTID.conf"; then
|
||||||
|
CT_HOSTNAME=$(grep '^hostname:' "/etc/pve/lxc/$CTID.conf" | awk '{print $2}')
|
||||||
|
if [[ ! "$CT_HOSTNAME" =~ ^[a-z0-9-]+$ ]]; then
|
||||||
|
msg_warn "Hostname '$CT_HOSTNAME' contains invalid characters – may cause issues with networking or DNS."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! pct status "$CTID" &>/dev/null; then
|
|
||||||
msg_error "Container not found after pct create – assuming failure."
|
|
||||||
exit 210
|
|
||||||
fi
|
|
||||||
|
|
||||||
msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created."
|
msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created."
|
||||||
|
@ -61,8 +61,7 @@ setting_up_container() {
|
|||||||
fi
|
fi
|
||||||
rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED
|
rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED
|
||||||
systemctl disable -q --now systemd-networkd-wait-online.service
|
systemctl disable -q --now systemd-networkd-wait-online.service
|
||||||
msg_ok "Set up Container OS"
|
msg_ok "Set up Container OS | Network Connected: ${BL}$(hostname -I"
|
||||||
msg_custom "${CM}" "${GN}" "Network Connected: ${BL}$(hostname -I)"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected
|
# This function checks the network connection by pinging a known IP address and prompts the user to continue if the internet is not connected
|
||||||
@ -100,25 +99,26 @@ network_check() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# DNS resolution checks for GitHub-related domains (IPv4 and/or IPv6)
|
# DNS resolution checks for GitHub-related domains (IPv4 and/or IPv6)
|
||||||
GITHUB_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com")
|
GIT_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org")
|
||||||
GITHUB_STATUS="GitHub DNS:"
|
GIT_STATUS="Git DNS:"
|
||||||
DNS_FAILED=false
|
DNS_FAILED=false
|
||||||
|
|
||||||
for HOST in "${GITHUB_HOSTS[@]}"; do
|
for HOST in "${GIT_HOSTS[@]}"; do
|
||||||
RESOLVEDIP=$(getent hosts "$HOST" | awk '{ print $1 }' | grep -E '(^([0-9]{1,3}\.){3}[0-9]{1,3}$)|(^[a-fA-F0-9:]+$)' | head -n1)
|
RESOLVEDIP=$(getent hosts "$HOST" | awk '{ print $1 }' | grep -E '(^([0-9]{1,3}\.){3}[0-9]{1,3}$)|(^[a-fA-F0-9:]+$)' | head -n1)
|
||||||
if [[ -z "$RESOLVEDIP" ]]; then
|
if [[ -z "$RESOLVEDIP" ]]; then
|
||||||
GITHUB_STATUS+="$HOST:($DNSFAIL)"
|
GIT_STATUS+="$HOST:($DNSFAIL)"
|
||||||
DNS_FAILED=true
|
DNS_FAILED=true
|
||||||
else
|
else
|
||||||
GITHUB_STATUS+=" $HOST:($DNSOK)"
|
GIT_STATUS+=" $HOST:($DNSOK)"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ "$DNS_FAILED" == true ]]; then
|
if [[ "$DNS_FAILED" == true ]]; then
|
||||||
fatal "$GITHUB_STATUS"
|
fatal "$GIT_STATUS"
|
||||||
else
|
else
|
||||||
msg_ok "$GITHUB_STATUS"
|
msg_ok "$GIT_STATUS"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
|
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
|
||||||
}
|
}
|
||||||
|
@ -239,10 +239,14 @@ setup_mariadb() {
|
|||||||
DISTRO_CODENAME="$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)"
|
DISTRO_CODENAME="$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)"
|
||||||
CURRENT_OS="$(awk -F= '/^ID=/{print $2}' /etc/os-release)"
|
CURRENT_OS="$(awk -F= '/^ID=/{print $2}' /etc/os-release)"
|
||||||
|
|
||||||
|
if ! curl -fsI http://mirror.mariadb.org/repo/ >/dev/null; then
|
||||||
|
msg_error "MariaDB mirror not reachable"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
msg_info "Setting up MariaDB $MARIADB_VERSION"
|
msg_info "Setting up MariaDB $MARIADB_VERSION"
|
||||||
# grab dynamic latest LTS version
|
# grab dynamic latest LTS version
|
||||||
if [[ "$MARIADB_VERSION" == "latest" ]]; then
|
if [[ "$MARIADB_VERSION" == "latest" ]]; then
|
||||||
$STD msg_info "Resolving latest GA MariaDB version"
|
|
||||||
MARIADB_VERSION=$(curl -fsSL http://mirror.mariadb.org/repo/ |
|
MARIADB_VERSION=$(curl -fsSL http://mirror.mariadb.org/repo/ |
|
||||||
grep -Eo '[0-9]+\.[0-9]+\.[0-9]+/' |
|
grep -Eo '[0-9]+\.[0-9]+\.[0-9]+/' |
|
||||||
grep -vE 'rc/|rolling/' |
|
grep -vE 'rc/|rolling/' |
|
||||||
@ -253,7 +257,6 @@ setup_mariadb() {
|
|||||||
msg_error "Could not determine latest GA MariaDB version"
|
msg_error "Could not determine latest GA MariaDB version"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
$STD msg_ok "Latest GA MariaDB version is $MARIADB_VERSION"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local CURRENT_VERSION=""
|
local CURRENT_VERSION=""
|
||||||
@ -278,7 +281,6 @@ setup_mariadb() {
|
|||||||
$STD msg_info "Setup MariaDB $MARIADB_VERSION"
|
$STD msg_info "Setup MariaDB $MARIADB_VERSION"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
$STD msg_info "Setting up MariaDB Repository"
|
|
||||||
curl -fsSL "https://mariadb.org/mariadb_release_signing_key.asc" |
|
curl -fsSL "https://mariadb.org/mariadb_release_signing_key.asc" |
|
||||||
gpg --dearmor -o /etc/apt/trusted.gpg.d/mariadb.gpg
|
gpg --dearmor -o /etc/apt/trusted.gpg.d/mariadb.gpg
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user