Merge branch 'community-scripts:main' into pr-update-lxcs-services

This commit is contained in:
Rémi Bédard-Couture
2025-07-02 14:09:12 -04:00
committed by GitHub
45 changed files with 1464 additions and 1133 deletions

View File

@@ -186,92 +186,92 @@ ssh_check() {
fi
}
select_storage() {
local CLASS=$1 CONTENT CONTENT_LABEL
case $CLASS in
container)
CONTENT='rootdir'
CONTENT_LABEL='Container'
;;
template)
CONTENT='vztmpl'
CONTENT_LABEL='Template'
;;
iso)
CONTENT='iso'
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'."
exit 201
;;
esac
# select_storage() {
# local CLASS=$1 CONTENT CONTENT_LABEL
# case $CLASS in
# container)
# CONTENT='rootdir'
# CONTENT_LABEL='Container'
# ;;
# template)
# CONTENT='vztmpl'
# CONTENT_LABEL='Template'
# ;;
# iso)
# CONTENT='iso'
# 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'."
# exit 201
# ;;
# esac
command -v whiptail >/dev/null || {
msg_error "whiptail missing."
exit 220
}
command -v numfmt >/dev/null || {
msg_error "numfmt missing."
exit 221
}
# command -v whiptail >/dev/null || {
# msg_error "whiptail missing."
# exit 220
# }
# command -v numfmt >/dev/null || {
# msg_error "numfmt missing."
# exit 221
# }
local -a MENU
while read -r line; do
local TAG=$(echo "$line" | awk '{print $1}')
local TYPE=$(echo "$line" | awk '{printf "%-10s", $2}')
local FREE=$(echo "$line" | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf "%9sB", $6}')
MENU+=("$TAG" "Type: $TYPE Free: $FREE" "OFF")
done < <(pvesm status -content "$CONTENT" | awk 'NR>1')
# local -a MENU
# while read -r line; do
# local TAG=$(echo "$line" | awk '{print $1}')
# local TYPE=$(echo "$line" | awk '{printf "%-10s", $2}')
# local FREE=$(echo "$line" | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf "%9sB", $6}')
# MENU+=("$TAG" "Type: $TYPE Free: $FREE" "OFF")
# done < <(pvesm status -content "$CONTENT" | awk 'NR>1')
if [ ${#MENU[@]} -eq 0 ]; then
msg_error "No storage found for content type '$CONTENT'."
exit 203
fi
# if [ ${#MENU[@]} -eq 0 ]; then
# msg_error "No storage found for content type '$CONTENT'."
# exit 203
# fi
if [ $((${#MENU[@]} / 3)) -eq 1 ]; then
echo "${MENU[0]}"
return
fi
# if [ $((${#MENU[@]} / 3)) -eq 1 ]; then
# echo "${MENU[0]}"
# return
# fi
local STORAGE
STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \
"Which storage pool for ${CONTENT_LABEL,,}?\n(Spacebar to select)" \
16 70 6 "${MENU[@]}" 3>&1 1>&2 2>&3) || {
msg_error "Storage selection cancelled by user."
exit 202
}
echo "$STORAGE"
}
# local STORAGE
# STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \
# "Which storage pool for ${CONTENT_LABEL,,}?\n(Spacebar to select)" \
# 16 70 6 "${MENU[@]}" 3>&1 1>&2 2>&3) || {
# msg_error "Storage selection cancelled by user."
# exit 202
# }
# echo "$STORAGE"
# }
manage_default_storage() {
local file="/usr/local/community-scripts/default_storage"
mkdir -p /usr/local/community-scripts
# manage_default_storage() {
# local file="/usr/local/community-scripts/default_storage"
# mkdir -p /usr/local/community-scripts
local tmpl=$(select_storage template)
local cont=$(select_storage container)
# local tmpl=$(select_storage template)
# local cont=$(select_storage container)
cat <<EOF >"$file"
TEMPLATE_STORAGE=$tmpl
CONTAINER_STORAGE=$cont
EOF
# cat <<EOF >"$file"
# TEMPLATE_STORAGE=$tmpl
# CONTAINER_STORAGE=$cont
# EOF
msg_ok "Default Storage set: Template=${BL}$tmpl${CL} ${GN}|${CL} Container=${BL}$cont${CL}"
whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \
--msgbox "Default Storage set:\n\nTemplate: $tmpl\nContainer: $cont" 10 58
}
# msg_ok "Default Storage set: Template=${BL}$tmpl${CL} ${GN}|${CL} Container=${BL}$cont${CL}"
# whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \
# --msgbox "Default Storage set:\n\nTemplate: $tmpl\nContainer: $cont" 10 58
# }
base_settings() {
# Default Settings
@@ -333,13 +333,13 @@ write_config() {
CT_TYPE="${CT_TYPE}"
DISK_SIZE="${DISK_SIZE}"
CORE_COUNT="${DISK_SIZE}"
CORE_COUNT="${CORE_COUNT}"
RAM_SIZE="${RAM_SIZE}"
HN="${HN}"
BRG="${BRG}"
APT_CACHER_IP="${APT_CACHER_IP:-none}"
DISABLEIP6="${DISABLEIP6}"
PW="${PW:-none}"
PW='${PW:-none}'
SSH="${SSH}"
SSH_AUTHORIZED_KEY="${SSH_AUTHORIZED_KEY}"
VERBOSE="${VERBOSE}"
@@ -351,6 +351,7 @@ SD="${SD:-none}"
MAC="${MAC:-none}"
NS="${NS:-none}"
NET="${NET}"
FUSE="${ENABLE_FUSE}"
EOF
echo -e "${INFO}${BOLD}${GN}Writing configuration to ${FILEPATH}${CL}"
@@ -364,7 +365,7 @@ EOF
CT_TYPE="${CT_TYPE}"
DISK_SIZE="${DISK_SIZE}"
CORE_COUNT="${DISK_SIZE}"
CORE_COUNT="${CORE_COUNT}"
RAM_SIZE="${RAM_SIZE}"
HN="${HN}"
BRG="${BRG}"
@@ -382,6 +383,7 @@ SD="${SD:-none}"
MAC="${MAC:-none}"
NS="${NS:-none}"
NET="${NET}"
FUSE="${ENABLE_FUSE}"
EOF
echo -e "${INFO}${BOLD}${GN}Writing configuration to ${FILEPATH}${CL}"
@@ -402,9 +404,11 @@ echo_default() {
# Output the selected values with icons
echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}${CT_ID}${CL}"
echo -e "${OS}${BOLD}${DGN}Operating System: $var_os | Version: $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 "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB | ${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT} | ${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${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 "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}"
if [ "$VERB" == "yes" ]; then
echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}Enabled${CL}"
fi
@@ -861,12 +865,12 @@ advanced_settings() {
echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}"
fi
# if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "FUSE Support" --yesno "Enable FUSE support?\nRequired for tools like rclone, mergerfs, AppImage, etc." 10 58); then
# ENABLE_FUSE="yes"
# else
# ENABLE_FUSE="no"
# fi
# echo -e "${FUSE}${BOLD}${DGN}Enable FUSE Support: ${BGN}$ENABLE_FUSE${CL}"
if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "FUSE Support" --yesno "Enable FUSE support?\nRequired for tools like rclone, mergerfs, AppImage, etc." 10 58); then
ENABLE_FUSE="yes"
else
ENABLE_FUSE="no"
fi
echo -e "${FUSE}${BOLD}${DGN}Enable FUSE Support: ${BGN}$ENABLE_FUSE${CL}"
if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "VERBOSE MODE" --yesno "Enable Verbose Mode?" 10 58); then
VERBOSE="yes"
@@ -1195,12 +1199,10 @@ build_container() {
-unprivileged $CT_TYPE
$PW
"
# This executes create_lxc.sh and creates the container and .conf file
CREATE_CMD="bash -c \"\$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/create_lxc.sh)\""
eval "$CREATE_CMD"
bash -c "$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/create_lxc.sh)"
RET=$?
if [[ $RET -ne 0 ]]; then
msg_error "in line $LINENO: exit code $RET: while executing command $CREATE_CMD"
msg_error "rny: in line $LINENO: exit code $RET: while executing create_lxc.sh"
exit $RET
fi
@@ -1273,21 +1275,24 @@ EOF'
pct exec "$CTID" -- ash -c "apk add bash newt curl openssh nano mc ncurses >/dev/null"
else
sleep 3
# Set locale and timezone before update
pct exec "$CTID" -- bash -c "sed -i '/$LANG/ s/^# //' /etc/locale.gen"
pct exec "$CTID" -- bash -c "locale_line=\$(grep -v '^#' /etc/locale.gen | grep -E '^[a-zA-Z]' | awk '{print \$1}' | head -n 1) && \
echo LANG=\$locale_line >/etc/default/locale && \
locale-gen >/dev/null && \
export LANG=\$locale_line"
pct exec "$CTID" -- bash -c "echo $tz >/etc/timezone && ln -sf /usr/share/zoneinfo/$tz /etc/localtime"
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"
else
msg_info "Skipping timezone setup zone '$tz' not found in container"
fi
# Install curl
pct exec "$CTID" -- bash -c "apt-get update >/dev/null && apt-get install -y sudo curl mc gnupg2 >/dev/null"
fi
msg_ok "Customized LXC Container"
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/install/$var_install.sh)" $?
lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/"$var_install".sh)" $?
}
# This function sets the description of the container.

View File

@@ -9,21 +9,21 @@
# }
# fi
trap 'on_error $? $LINENO' ERR
trap 'on_exit' EXIT
trap 'on_interrupt' INT
trap 'on_terminate' TERM
# 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
# if ! declare -f wait_for >/dev/null; then
# wait_for() {
# true
# }
# fi
declare -A MSG_INFO_SHOWN=()
SPINNER_PID=""
SPINNER_ACTIVE=0
SPINNER_MSG=""
# declare -A MSG_INFO_SHOWN=()
# SPINNER_PID=""
# SPINNER_ACTIVE=0
# SPINNER_MSG=""
# ------------------------------------------------------------------------------
# Loads core utility groups once (colors, formatting, icons, defaults).
@@ -47,9 +47,9 @@ load_functions() {
# Error & Signal Handling robust, universal, subshell-safe
# ============================================================================
_stop_spinner_on_error() {
[[ -n "${SPINNER_PID:-}" ]] && kill "$SPINNER_PID" 2>/dev/null && wait "$SPINNER_PID" 2>/dev/null || true
}
# _stop_spinner_on_error() {
# [[ -n "${SPINNER_PID:-}" ]] && kill "$SPINNER_PID" 2>/dev/null && wait "$SPINNER_PID" 2>/dev/null || true
# }
_tool_error_hint() {
local cmd="$1"
@@ -89,44 +89,39 @@ _tool_error_hint() {
esac
}
on_error() {
local code="$?"
local line="${BASH_LINENO[0]:-unknown}"
local cmd="${BASH_COMMAND:-unknown}"
# on_error() {
# local code="$?"
# local line="${BASH_LINENO[0]:-unknown}"
# local cmd="${BASH_COMMAND:-unknown}"
# Signalcode unterdrücken, falls INT/TERM kommt
[[ "$code" == "130" || "$code" == "143" ]] && return
# # Signalcode unterdrücken, falls INT/TERM kommt
# [[ "$code" == "130" || "$code" == "143" ]] && return
_stop_spinner_on_error
msg_error "Script failed at line $line with exit code $code: $cmd"
exit "$code"
}
# _stop_spinner_on_error
# msg_error "Script failed at line $line with exit code $code: $cmd"
# exit "$code"
# }
on_exit() {
_stop_spinner_on_error
[[ "${VERBOSE:-no}" == "yes" ]] && msg_info "Script exited cleanly"
}
# on_exit() {
# _stop_spinner_on_error
# [[ "${VERBOSE:-no}" == "yes" ]] && msg_info "Script exited cleanly"
# }
on_interrupt() {
_stop_spinner_on_error
msg_error "Interrupted by user (CTRL+C)"
exit 130
}
# on_interrupt() {
# _stop_spinner_on_error
# msg_error "Interrupted by user (CTRL+C)"
# exit 130
# }
on_terminate() {
_stop_spinner_on_error
msg_error "Terminated by signal (SIGTERM)"
exit 143
}
# on_terminate() {
# _stop_spinner_on_error
# msg_error "Terminated by signal (SIGTERM)"
# exit 143
# }
catch_errors() {
trap 'on_error' ERR
trap 'on_exit' EXIT
trap 'on_interrupt' INT
trap 'on_terminate' TERM
set -Eeuo pipefail
shopt -s inherit_errexit
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
}
# ------------------------------------------------------------------------------
@@ -143,6 +138,13 @@ color() {
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.
# ------------------------------------------------------------------------------
@@ -363,149 +365,215 @@ fatal() {
kill -INT $$
}
# Ensure POSIX compatibility across Alpine and Debian/Ubuntu
# === Spinner Start ===
# Trap cleanup on various signals
trap 'cleanup_spinner' EXIT INT TERM HUP
# # 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_frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
# === Spinner Start ===
start_spinner() {
local msg="$1"
local spin_i=0
local interval=0.1
# # === Spinner Start ===
# start_spinner() {
# local msg="$1"
# local spin_i=0
# local interval=0.1
stop_spinner
SPINNER_MSG="$msg"
SPINNER_ACTIVE=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
} &
# {
# 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
# 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
# echo -e "OS: ${var_os:-}"
# if [[ "${VERBOSE:-no}" != "no" || "${var_os:-}" == "alpine" || ! -t 2 ]]; then
# printf "\r\e[2K%s %b\n" "$HOURGLASS" "${YW}${msg}${CL}" >&2
# else
# start_spinner "$msg"
# fi
# }
spinner() {
local chars=(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏)
local i=0
while true; do
local index=$((i++ % ${#chars[@]}))
printf "\r\033[2K%s %b" "${CS_YWB}${chars[$index]}${CS_CL}" "${CS_YWB}${SPINNER_MSG:-}${CS_CL}"
sleep 0.1
done
}
# === Spinner Stop ===
stop_spinner() {
if [[ "$SPINNER_ACTIVE" -eq 1 && -n "$SPINNER_PID" ]]; then
SPINNER_ACTIVE=0
local pid="${SPINNER_PID:-}"
[[ -z "$pid" && -f /tmp/.spinner.pid ]] && pid=$(</tmp/.spinner.pid)
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
if [[ -n "$pid" && "$pid" =~ ^[0-9]+$ ]]; then
if kill "$pid" 2>/dev/null; then
sleep 0.05
kill -9 "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
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=""
rm -f /tmp/.spinner.pid
unset SPINNER_PID
fi
}
cleanup_spinner() {
stop_spinner
stty sane 2>/dev/null
#printf "\r\033[K\e[?25h\n"
}
msg_info() {
local msg="$1"
[[ -z "$msg" || -n "${MSG_INFO_SHOWN["$msg"]+x}" ]] && return
MSG_INFO_SHOWN["$msg"]=1
stop_spinner
if [[ "${VERBOSE:-no}" != "no" || "${var_os:-}" == "alpine" || ! -t 2 ]]; then
printf "\r\e[2K%s %b\n" "$HOURGLASS" "${YW}${msg}${CL}" >&2
else
start_spinner "$msg"
fi
SPINNER_MSG="$msg"
color_spinner
spinner &
SPINNER_PID=$!
echo "$SPINNER_PID" >/tmp/.spinner.pid
disown "$SPINNER_PID" 2>/dev/null || true
}
msg_ok() {
local msg="$1"
[[ -z "$msg" ]] && return
stop_spinner
printf "\r\e[2K%s %b\n" "$CM" "${GN}${msg}${CL}" >&2
if declare -p MSG_INFO_SHOWN &>/dev/null && [[ "$(declare -p MSG_INFO_SHOWN 2>/dev/null)" =~ "declare -A" ]]; then
unset MSG_INFO_SHOWN["$msg"]
fi
local msg="$1"
echo -e "${BFR:-} ${CM:-✔️} ${GN}${msg}${CL}"
}
msg_error() {
local msg="$1"
[[ -z "$msg" ]] && return
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() {
local msg="$1"
[[ -z "$msg" ]] && return
stop_spinner
printf "\r\e[2K%s %b\n" "$INFO" "${YWB}${msg}${CL}" >&2
if declare -p MSG_INFO_SHOWN &>/dev/null && [[ "$(declare -p MSG_INFO_SHOWN 2>/dev/null)" =~ "declare -A" ]]; then
unset MSG_INFO_SHOWN["$msg"]
fi
local msg="$1"
echo -e "${BFR:-} ${INFO:-} ${YWB}${msg}${CL}"
}
msg_custom() {
local symbol="${1:-"[*]"}"
local color="${2:-"\e[36m"}" # Default: Cyan
local color="${2:-"\e[36m"}"
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
stop_spinner
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="-"
# msg_ok() {
# local msg="$1"
# [[ -z "$msg" ]] && return
# stop_spinner
# printf "\r\e[2K%s %b\n" "$CM" "${GN}${msg}${CL}" >&2
# if declare -p MSG_INFO_SHOWN &>/dev/null && [[ "$(declare -p MSG_INFO_SHOWN 2>/dev/null)" =~ "declare -A" ]]; then
# unset MSG_INFO_SHOWN["$msg"]
# fi
# }
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
# msg_error() {
# local msg="$1"
# [[ -z "$msg" ]] && return
# stop_spinner
# printf "\r\e[2K%s %b\n" "$CROSS" "${RD}${msg}${CL}" >&2
# }
percent=$(((current * 100) / total))
filled=$(((current * width) / total))
empty=$((width - filled))
# msg_warn() {
# local msg="$1"
# [[ -z "$msg" ]] && return
# stop_spinner
# printf "\r\e[2K%s %b\n" "$INFO" "${YWB}${msg}${CL}" >&2
# if declare -p MSG_INFO_SHOWN &>/dev/null && [[ "$(declare -p MSG_INFO_SHOWN 2>/dev/null)" =~ "declare -A" ]]; then
# unset MSG_INFO_SHOWN["$msg"]
# fi
# }
bar=$(printf "%${filled}s" | tr ' ' "$fill_char")
bar+=$(printf "%${empty}s" | tr ' ' "$empty_char")
# msg_custom() {
# local symbol="${1:-"[*]"}"
# local color="${2:-"\e[36m"}" # Default: Cyan
# local msg="${3:-}"
printf "\r\e[2K%s [%s] %3d%% %s" "${TAB}" "$bar" "$percent" "$label" >&2
# [[ -z "$msg" ]] && return
# stop_spinner 2>/dev/null || true
# printf "\r\e[2K%s %b\n" "$symbol" "${color}${msg}${CL:-\e[0m}" >&2
# }
if [[ "$current" -eq "$total" ]]; then
printf "\n" >&2
fi
}
# 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"
@@ -555,3 +623,5 @@ check_or_create_swap() {
return 1
fi
}
trap 'stop_spinner' EXIT INT TERM

View File

@@ -27,7 +27,7 @@ trap on_terminate TERM
function on_exit() {
local exit_code="$?"
[[ -n "${lockfile:-}" ]]
[[ -n "${lockfile:-}" && -e "$lockfile" ]] && rm -f "$lockfile"
exit "$exit_code"
}
@@ -50,18 +50,30 @@ function on_terminate() {
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
msg_info "Validating Storage"
VALIDCT=$(pvesm status -content rootdir | awk 'NR>1')
if [ -z "$VALIDCT" ]; then
msg_error "Unable to detect a valid Container Storage location."
if ! check_storage_support "rootdir"; then
msg_error "No valid storage found for 'rootdir' (Container)."
exit 1
fi
VALIDTMP=$(pvesm status -content vztmpl | awk 'NR>1')
if [ -z "$VALIDTMP" ]; then
msg_error "Unable to detect a valid Template Storage location."
if ! check_storage_support "vztmpl"; then
msg_error "No valid storage found for 'vztmpl' (Template)."
exit 1
fi
msg_ok "Storage types rootdir and vztmpl are supported."
# This function is used to select the storage class and determine the corresponding storage content type and label.
function select_storage() {
@@ -107,12 +119,19 @@ function select_storage() {
}
local -a MENU
while read -r line; do
local TAG=$(echo $line | awk '{print $1}')
local TYPE=$(echo $line | awk '{printf "%-10s", $2}')
local FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( "%9sB", $6)}')
MENU+=("$TAG" "Type: $TYPE Free: $FREE " "OFF")
done < <(pvesm status -content $CONTENT | awk 'NR>1')
local -A STORAGE_MAP=()
local COL_WIDTH=0
while read -r TAG TYPE _ TOTAL USED FREE _; do
[[ -n "$TAG" && -n "$TYPE" ]] || continue
local DISPLAY="${TAG} (${TYPE})"
local USED_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$USED")
local FREE_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$FREE")
local INFO="Free: ${FREE_FMT}B Used: ${USED_FMT}B"
STORAGE_MAP["$DISPLAY"]="$TAG" # Map DISPLAY to actual TAG
MENU+=("$DISPLAY" "$INFO" "OFF")
((${#DISPLAY} > COL_WIDTH)) && COL_WIDTH=${#DISPLAY}
done < <(pvesm status -content "$CONTENT" | awk 'NR>1')
if [ ${#MENU[@]} -eq 0 ]; then
msg_error "No storage found for content type '$CONTENT'."
@@ -120,18 +139,32 @@ function select_storage() {
fi
if [ $((${#MENU[@]} / 3)) -eq 1 ]; then
printf "%s" "${MENU[0]}"
echo "${STORAGE_MAP[${MENU[0]}]}"
return
fi
local STORAGE
STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \
"Which storage pool for ${CONTENT_LABEL,,}?\n(Spacebar to select)" \
16 70 6 "${MENU[@]}" 3>&1 1>&2 2>&3) || {
msg_error "Storage selection cancelled by user."
exit 202
}
printf "%s" "$STORAGE"
local WIDTH=$((COL_WIDTH + 42))
local DISPLAY_SELECTED=""
while true; do
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) || {
msg_error "Storage selection cancelled by user."
exit 202
}
# Validierung gegen STORAGE_MAP
if [[ -z "$DISPLAY_SELECTED" || -z "${STORAGE_MAP[$DISPLAY_SELECTED]+_}" ]]; then
whiptail --backtitle "Proxmox VE Helper Scripts" --title "Invalid Selection" \
--msgbox "No valid storage selected. Please choose a storage pool to continue." 9 60
continue
fi
break
done
echo "${STORAGE_MAP["$DISPLAY_SELECTED"]}"
}
# Test if required variables are set
@@ -172,10 +205,29 @@ if [[ -f "$DEFAULT_FILE" ]]; then
msg_ok "Using ${BL}$CONTAINER_STORAGE${CL} ${GN}for Container Storage."
fi
else
TEMPLATE_STORAGE=$(select_storage template)
# TEMPLATE STORAGE SELECTION
# Template Storage
if ! TEMPLATE_STORAGE=$(select_storage template); then
[[ $? -eq 202 ]] && {
msg_error "Template Storage selection cancelled by user. Exiting."
kill -INT $$
}
msg_error "Unexpected error during template storage selection."
exit 1
fi
msg_ok "Using ${BL}$TEMPLATE_STORAGE${CL} ${GN}for Template Storage."
CONTAINER_STORAGE=$(select_storage container)
# Container Storage
if ! CONTAINER_STORAGE=$(select_storage container); then
[[ $? -eq 202 ]] && {
msg_error "Container Storage selection cancelled by user. Exiting."
kill -INT $$
}
msg_error "Unexpected error during container storage selection."
exit 1
fi
msg_ok "Using ${BL}$CONTAINER_STORAGE${CL} ${GN}for Container Storage."
fi
# Check free space on selected container storage

View File

@@ -31,7 +31,7 @@ catch_errors() {
# This function handles errors
error_handler() {
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func)
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/api.func)
printf "\e[?25h"
local exit_code="$?"
local line_number="$1"
@@ -65,7 +65,8 @@ setting_up_container() {
rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED
systemctl disable -q --now systemd-networkd-wait-online.service
msg_ok "Set up Container OS"
msg_custom "${CM}" "${GN}" "Network Connected: ${BL}$(hostname -I)"
#msg_custom "${CM}" "${GN}" "Network Connected: ${BL}$(hostname -I)"
msg_ok "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
@@ -104,24 +105,24 @@ network_check() {
fi
# DNS resolution checks for GitHub-related domains (IPv4 and/or IPv6)
GITHUB_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org")
GITHUB_STATUS="GitHub DNS:"
GIT_HOSTS=("github.com" "raw.githubusercontent.com" "api.github.com" "git.community-scripts.org")
GIT_STATUS="Git DNS:"
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)
if [[ -z "$RESOLVEDIP" ]]; then
GITHUB_STATUS+="$HOST:($DNSFAIL)"
GIT_STATUS+="$HOST:($DNSFAIL)"
DNS_FAILED=true
else
GITHUB_STATUS+=" $HOST:($DNSOK)"
GIT_STATUS+=" $HOST:($DNSOK)"
fi
done
if [[ "$DNS_FAILED" == true ]]; then
fatal "$GITHUB_STATUS"
fatal "$GIT_STATUS"
else
msg_ok "$GITHUB_STATUS"
msg_ok "$GIT_STATUS"
fi
set -e

View File

@@ -238,7 +238,6 @@ setup_mariadb() {
msg_info "Setting up MariaDB $MARIADB_VERSION"
# grab dynamic latest LTS version
if [[ "$MARIADB_VERSION" == "latest" ]]; then
$STD msg_info "Resolving latest GA MariaDB version"
MARIADB_VERSION=$(curl -fsSL http://mirror.mariadb.org/repo/ |
grep -Eo '[0-9]+\.[0-9]+\.[0-9]+/' |
grep -vE 'rc/|rolling/' |
@@ -249,7 +248,6 @@ setup_mariadb() {
msg_error "Could not determine latest GA MariaDB version"
return 1
fi
$STD msg_ok "Latest GA MariaDB version is $MARIADB_VERSION"
fi
local CURRENT_VERSION=""
@@ -274,7 +272,6 @@ setup_mariadb() {
$STD msg_info "Setup MariaDB $MARIADB_VERSION"
fi
$STD msg_info "Setting up MariaDB Repository"
curl -fsSL "https://mariadb.org/mariadb_release_signing_key.asc" |
gpg --dearmor -o /etc/apt/trusted.gpg.d/mariadb.gpg
@@ -824,19 +821,30 @@ function fetch_and_deploy_gh_release() {
elif [[ "$mode" == "binary" ]]; then
local arch
arch=$(dpkg --print-architecture 2>/dev/null || uname -m)
[[ "$arch" == "x86_64" ]] && arch="x86_64"
[[ "$arch" == "x86_64" ]] && arch="amd64"
[[ "$arch" == "aarch64" ]] && arch="arm64"
local assets url_match=""
assets=$(echo "$json" | jq -r '.assets[].browser_download_url')
for u in $assets; do
if [[ "$u" =~ ($arch|amd64|x86_64|aarch64|arm64).*\.deb$ ]]; then
url_match="$u"
break
fi
done
# If explicit filename pattern is provided (param $6), match that first
if [[ -n "$6" ]]; then
for u in $assets; do
[[ "$u" =~ $6 || "$u" == *"$6" ]] && url_match="$u" && break
done
fi
# If no match via explicit pattern, fall back to architecture heuristic
if [[ -z "$url_match" ]]; then
for u in $assets; do
if [[ "$u" =~ ($arch|amd64|x86_64|aarch64|arm64).*\.deb$ ]]; then
url_match="$u"
break
fi
done
fi
# Fallback: any .deb file
if [[ -z "$url_match" ]]; then
for u in $assets; do
[[ "$u" =~ \.deb$ ]] && url_match="$u" && break
@@ -1611,3 +1619,142 @@ function setup_imagemagick() {
ensure_usr_local_bin_persist
msg_ok "Setup ImageMagick $VERSION"
}
# ------------------------------------------------------------------------------
# Installs FFmpeg from source or prebuilt binary (Debian/Ubuntu only).
#
# Description:
# - Downloads and builds FFmpeg from GitHub (https://github.com/FFmpeg/FFmpeg)
# - Supports specific version override via FFMPEG_VERSION (e.g. n7.1.1)
# - Supports build profile via FFMPEG_TYPE:
# - minimal : x264, vpx, mp3 only
# - medium : adds subtitles, fonts, opus, vorbis
# - full : adds dav1d, svt-av1, zlib, numa
# - binary : downloads static build (johnvansickle.com)
# - Defaults to latest stable version and full feature set
#
# Notes:
# - Requires: curl, jq, build-essential, and matching codec libraries
# - Result is installed to /usr/local/bin/ffmpeg
# ------------------------------------------------------------------------------
function setup_ffmpeg() {
local TMP_DIR
TMP_DIR=$(mktemp -d)
local GITHUB_REPO="FFmpeg/FFmpeg"
local VERSION="${FFMPEG_VERSION:-}"
local TYPE="${FFMPEG_TYPE:-full}"
local BIN_PATH="/usr/local/bin/ffmpeg"
# Binary fallback mode
if [[ "$TYPE" == "binary" ]]; then
msg_info "Installing FFmpeg (static binary)"
curl -fsSL https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz -o "$TMP_DIR/ffmpeg.tar.xz"
tar -xf "$TMP_DIR/ffmpeg.tar.xz" -C "$TMP_DIR"
local EXTRACTED_DIR
EXTRACTED_DIR=$(find "$TMP_DIR" -maxdepth 1 -type d -name "ffmpeg-*")
cp "$EXTRACTED_DIR/ffmpeg" "$BIN_PATH"
cp "$EXTRACTED_DIR/ffprobe" /usr/local/bin/ffprobe
chmod +x "$BIN_PATH" /usr/local/bin/ffprobe
rm -rf "$TMP_DIR"
msg_ok "Installed FFmpeg binary ($($BIN_PATH -version | head -n1))"
return
fi
# Auto-detect latest stable version if none specified
if [[ -z "$VERSION" ]]; then
msg_info "Fetching latest stable FFmpeg tag"
VERSION=$(curl -fsSL "https://api.github.com/repos/${GITHUB_REPO}/tags" |
jq -r '.[].name' |
grep -E '^n[0-9]+\.[0-9]+\.[0-9]+$' | # stable only
sort -V | tail -n1)
fi
if [[ -z "$VERSION" ]]; then
msg_error "Could not determine FFmpeg version"
rm -rf "$TMP_DIR"
return 1
fi
msg_info "Installing FFmpeg ${VERSION} ($TYPE)"
# Dependency selection
local DEPS=(build-essential yasm nasm pkg-config)
case "$TYPE" in
minimal)
DEPS+=(libx264-dev libvpx-dev libmp3lame-dev)
;;
medium)
DEPS+=(libx264-dev libvpx-dev libmp3lame-dev libfreetype6-dev libass-dev libopus-dev libvorbis-dev)
;;
full)
DEPS+=(
libx264-dev libx265-dev libvpx-dev libmp3lame-dev
libfreetype6-dev libass-dev libopus-dev libvorbis-dev
libdav1d-dev libsvtav1-dev zlib1g-dev libnuma-dev
)
;;
*)
msg_error "Invalid FFMPEG_TYPE: $TYPE"
rm -rf "$TMP_DIR"
return 1
;;
esac
$STD apt-get update
$STD apt-get install -y "${DEPS[@]}"
curl -fsSL "https://github.com/${GITHUB_REPO}/archive/refs/tags/${VERSION}.tar.gz" -o "$TMP_DIR/ffmpeg.tar.gz"
tar -xzf "$TMP_DIR/ffmpeg.tar.gz" -C "$TMP_DIR"
cd "$TMP_DIR/FFmpeg-"* || {
msg_error "Source extraction failed"
rm -rf "$TMP_DIR"
return 1
}
local args=(
--enable-gpl
--enable-shared
--enable-nonfree
--disable-static
--enable-libx264
--enable-libvpx
--enable-libmp3lame
)
if [[ "$TYPE" != "minimal" ]]; then
args+=(--enable-libfreetype --enable-libass --enable-libopus --enable-libvorbis)
fi
if [[ "$TYPE" == "full" ]]; then
args+=(--enable-libx265 --enable-libdav1d --enable-zlib)
fi
if [[ ${#args[@]} -eq 0 ]]; then
msg_error "FFmpeg configure args array is empty aborting."
rm -rf "$TMP_DIR"
return 1
fi
./configure "${args[@]}" >"$TMP_DIR/configure.log" 2>&1 || {
msg_error "FFmpeg ./configure failed (see $TMP_DIR/configure.log)"
cat "$TMP_DIR/configure.log" | tail -n 20
rm -rf "$TMP_DIR"
return 1
}
$STD make -j"$(nproc)"
$STD make install
if ! command -v ffmpeg &>/dev/null; then
msg_error "FFmpeg installation failed"
rm -rf "$TMP_DIR"
return 1
fi
local FINAL_VERSION
FINAL_VERSION=$(ffmpeg -version | head -n1 | awk '{print $3}')
rm -rf "$TMP_DIR"
ensure_usr_local_bin_persist
msg_ok "Setup FFmpeg $FINAL_VERSION"
}