Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Sam Heinz
2025-06-22 19:40:06 +10:00
351 changed files with 5771 additions and 3060 deletions
+1 -1
View File
@@ -142,7 +142,7 @@ update_os() {
msg_info "Installing core dependencies"
$STD apk update
$STD apk add newt curl openssh nano mc ncurses
$STD apk add newt curl openssh nano mc ncurses gpg
msg_ok "Core dependencies installed"
}
+9 -3
View File
@@ -39,9 +39,11 @@ post_to_api() {
EOF
)
RESPONSE=$(curl -fsSL -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
if [[ "$DIAGNOSTICS" == "yes" ]]; then
RESPONSE=$(curl -s -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD") || true
fi
}
post_to_api_vm() {
@@ -87,9 +89,11 @@ post_to_api_vm() {
EOF
)
RESPONSE=$(curl -fsSL -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
if [[ "$DIAGNOSTICS" == "yes" ]]; then
RESPONSE=$(curl -s -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD") || true
fi
}
POST_UPDATE_DONE=false
@@ -115,9 +119,11 @@ post_update_to_api() {
EOF
)
RESPONSE=$(curl -fsSL -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
if [[ "$DIAGNOSTICS" == "yes" ]]; then
RESPONSE=$(curl -s -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD") || true
fi
POST_UPDATE_DONE=true
}
+11 -7
View File
@@ -378,13 +378,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}"
@@ -396,6 +396,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}"
@@ -409,13 +410,13 @@ 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}"
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}"
@@ -427,6 +428,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}"
@@ -794,7 +796,7 @@ 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
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"
@@ -1091,6 +1093,8 @@ build_container() {
else
export FUNCTIONS_FILE_PATH="$(curl -fsSL https://raw.githubusercontent.com/asylumexp/Proxmox/main/misc/install.func)"
fi
export DIAGNOSTICS="$DIAGNOSTICS"
export RANDOM_UUID="$RANDOM_UUID"
export CACHER="$APT_CACHER"
export CACHER_IP="$APT_CACHER_IP"
@@ -1142,7 +1146,7 @@ EOF
fi
if [ "$CT_TYPE" == "0" ]; then
if [[ "$APP" == "Channels" || "$APP" == "Emby" || "$APP" == "ErsatzTV" || "$APP" == "Frigate" || "$APP" == "Jellyfin" || "$APP" == "Plex" || "$APP" == "Scrypted" || "$APP" == "Tdarr" || "$APP" == "Unmanic" || "$APP" == "Ollama" || "$APP" == "FileFlows" ]]; then
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
cat <<EOF >>"$LXC_CONFIG"
# VAAPI hardware transcoding
lxc.cgroup2.devices.allow: c 226:0 rwm
@@ -1154,7 +1158,7 @@ lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,creat
EOF
fi
else
if [[ "$APP" == "Channels" || "$APP" == "Emby" || "$APP" == "ErsatzTV" || "$APP" == "Frigate" || "$APP" == "Jellyfin" || "$APP" == "Plex" || "$APP" == "Scrypted" || "$APP" == "Tdarr" || "$APP" == "Unmanic" || "$APP" == "Ollama" || "$APP" == "FileFlows" ]]; then
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 [[ -e "/dev/dri/card0" ]]; then
cat <<EOF >>"$LXC_CONFIG"
+18
View File
@@ -618,6 +618,24 @@ config_file() {
fi
fi
if [[ -n "$ENABLE_FUSE" ]]; then
if [[ "$ENABLE_FUSE" == "yes" ]]; then
echo -e "${FUSE}${BOLD}${DGN}Enable FUSE: ${BGN}Yes${CL}"
elif [[ "$ENABLE_FUSE" == "no" ]]; then
echo -e "${FUSE}${BOLD}${DGN}Enable FUSE: ${BGN}No${CL}"
else
msg_error "Enable FUSE needs to be 'yes' or 'no', was ${ENABLE_FUSE}"
exit
fi
else
if (whiptail --backtitle "Proxmox VE Helper Scripts" --defaultno --title "FUSE" --yesno "Enable FUSE?" 10 58); then
ENABLE_FUSE="yes"
else
ENABLE_FUSE="no"
fi
echo -e "${FUSE}${BOLD}${DGN}Enable FUSE: ${BGN}$ENABLE_FUSE${CL}"
fi
if [[ -n "${VERBOSE-}" ]]; then
if [[ "$VERBOSE" == "yes" ]]; then
echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}$VERBOSE${CL}"
+544
View File
@@ -0,0 +1,544 @@
# 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
}
+1 -1
View File
@@ -76,7 +76,7 @@ error_handler() {
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}"
echo -e "\n$error_message"
if [[ "$line_number" -eq 50 ]]; then
if [[ "$line_number" -eq 51 ]]; then
echo -e "The silent function has suppressed the error, run the script with verbose mode enabled, which will provide more detailed output.\n"
post_update_to_api "failed" "No error message, script ran in silent mode"
else
+178 -30
View File
@@ -32,6 +32,15 @@ install_node_and_modules() {
NEED_NODE_INSTALL=true
fi
if ! command -v jq &>/dev/null; then
$STD msg_info "Installing jq..."
$STD apt-get update -qq &>/dev/null
$STD apt-get install -y jq &>/dev/null || {
msg_error "Failed to install jq"
return 1
}
fi
# Install Node.js if required
if [[ "$NEED_NODE_INSTALL" == true ]]; then
$STD apt-get purge -y nodejs
@@ -113,20 +122,22 @@ install_node_and_modules() {
}
# ------------------------------------------------------------------------------
# Installs or upgrades PostgreSQL and performs data migration.
# Installs or upgrades PostgreSQL and optional extensions/modules.
#
# Description:
# - Detects existing PostgreSQL version
# - Dumps all databases before upgrade
# - Adds PGDG repo and installs specified version
# - Installs optional PG_MODULES (e.g. postgis, contrib)
# - Restores dumped data post-upgrade
#
# Variables:
# PG_VERSION - Major PostgreSQL version (e.g. 15, 16) (default: 16)
# PG_MODULES - Comma-separated list of extensions (e.g. "postgis,contrib")
# ------------------------------------------------------------------------------
install_postgresql() {
local PG_VERSION="${PG_VERSION:-16}"
local PG_MODULES="${PG_MODULES:-}"
local CURRENT_PG_VERSION=""
local DISTRO
local NEED_PG_INSTALL=false
@@ -136,10 +147,10 @@ install_postgresql() {
CURRENT_PG_VERSION="$(psql -V | awk '{print $3}' | cut -d. -f1)"
if [[ "$CURRENT_PG_VERSION" == "$PG_VERSION" ]]; then
msg_ok "PostgreSQL $PG_VERSION is already installed"
return
else
msg_info "Detected PostgreSQL $CURRENT_PG_VERSION, preparing upgrade to $PG_VERSION"
NEED_PG_INSTALL=true
fi
msg_info "Detected PostgreSQL $CURRENT_PG_VERSION, preparing upgrade to $PG_VERSION"
NEED_PG_INSTALL=true
else
msg_info "Setup PostgreSQL $PG_VERSION"
NEED_PG_INSTALL=true
@@ -170,20 +181,34 @@ install_postgresql() {
$STD apt-get install -y "postgresql-${PG_VERSION}" "postgresql-client-${PG_VERSION}"
if [[ -n "$CURRENT_PG_VERSION" ]]; then
$STD msg_info "Removing old PostgreSQL $CURRENT_PG_VERSION packages"
msg_info "Removing old PostgreSQL $CURRENT_PG_VERSION packages"
$STD apt-get purge -y "postgresql-${CURRENT_PG_VERSION}" "postgresql-client-${CURRENT_PG_VERSION}" || true
fi
$STD msg_info "Starting PostgreSQL $PG_VERSION"
msg_info "Starting PostgreSQL $PG_VERSION"
systemctl enable -q --now postgresql
if [[ -n "$CURRENT_PG_VERSION" ]]; then
$STD msg_info "Restoring dumped data"
msg_info "Restoring dumped data"
su - postgres -c "psql < /var/lib/postgresql/backup_$(date +%F)_v${CURRENT_PG_VERSION}.sql"
fi
msg_ok "PostgreSQL $PG_VERSION installed"
fi
# Install optional PostgreSQL modules
if [[ -n "$PG_MODULES" ]]; then
IFS=',' read -ra MODULES <<<"$PG_MODULES"
for module in "${MODULES[@]}"; do
local pkg="postgresql-${PG_VERSION}-${module}"
msg_info "Installing PostgreSQL module: $pkg"
$STD apt-get install -y "$pkg" || {
msg_error "Failed to install $pkg"
continue
}
done
msg_ok "All requested PostgreSQL modules installed"
fi
}
# ------------------------------------------------------------------------------
@@ -202,6 +227,7 @@ install_mariadb() {
local MARIADB_VERSION="${MARIADB_VERSION:-latest}"
local DISTRO_CODENAME
DISTRO_CODENAME="$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)"
CURRENT_OS="$(awk -F= '/^ID=/{print $2}' /etc/os-release)"
# grab dynamic latest LTS version
if [[ "$MARIADB_VERSION" == "latest" ]]; then
@@ -221,7 +247,7 @@ install_mariadb() {
local CURRENT_VERSION=""
if command -v mariadb >/dev/null; then
CURRENT_VERSION="$(mariadb --version | grep -oP 'Ver\s+\K[0-9]+\.[0-9]+')"
CURRENT_VERSION=$(mariadb --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
fi
if [[ "$CURRENT_VERSION" == "$MARIADB_VERSION" ]]; then
@@ -245,7 +271,7 @@ install_mariadb() {
curl -fsSL "https://mariadb.org/mariadb_release_signing_key.asc" |
gpg --dearmor -o /etc/apt/trusted.gpg.d/mariadb.gpg
echo "deb [signed-by=/etc/apt/trusted.gpg.d/mariadb.gpg] http://mirror.mariadb.org/repo/${MARIADB_VERSION}/debian ${DISTRO_CODENAME} main" \
echo "deb [signed-by=/etc/apt/trusted.gpg.d/mariadb.gpg] http://mirror.mariadb.org/repo/${MARIADB_VERSION}/${CURRENT_OS} ${DISTRO_CODENAME} main" \
>/etc/apt/sources.list.d/mariadb.list
$STD apt-get update
@@ -277,28 +303,34 @@ install_mysql() {
msg_info "MySQL $CURRENT_VERSION found, replacing with $MYSQL_VERSION"
NEED_INSTALL=true
else
msg_ok "MySQL $MYSQL_VERSION already installed"
# Check for patch-level updates
if apt list --upgradable 2>/dev/null | grep -q '^mysql-server/'; then
msg_info "MySQL $CURRENT_VERSION available for upgrade"
$STD apt-get update
$STD apt-get install --only-upgrade -y mysql-server
msg_ok "MySQL upgraded"
fi
return
fi
else
msg_info "MySQL not found, installing version $MYSQL_VERSION"
msg_info "Installing MySQL $MYSQL_VERSION"
NEED_INSTALL=true
fi
if [[ "$NEED_INSTALL" == true ]]; then
msg_info "Removing conflicting MySQL packages"
$STD systemctl stop mysql >/dev/null 2>&1 || true
$STD apt-get purge -y 'mysql*'
$STD apt-get purge -y "^mysql-server.*" "^mysql-client.*" "^mysql-common.*" || true
rm -f /etc/apt/sources.list.d/mysql.list /etc/apt/trusted.gpg.d/mysql.gpg
msg_info "Setting up MySQL APT Repository"
local DISTRO_CODENAME
DISTRO_CODENAME="$(awk -F= '/VERSION_CODENAME/ { print $2 }' /etc/os-release)"
curl -fsSL https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 | gpg --dearmor -o /etc/apt/trusted.gpg.d/mysql.gpg
curl -fsSL https://repo.mysql.com/RPM-GPG-KEY-mysql-2023 | gpg --dearmor -o /etc/apt/trusted.gpg.d/mysql.gpg
echo "deb [signed-by=/etc/apt/trusted.gpg.d/mysql.gpg] https://repo.mysql.com/apt/debian/ ${DISTRO_CODENAME} mysql-${MYSQL_VERSION}" \
>/etc/apt/sources.list.d/mysql.list
export DEBIAN_FRONTEND=noninteractive
$STD apt-get update
$STD apt-get install -y mysql-server
msg_ok "Installed MySQL $MYSQL_VERSION"
fi
}
@@ -355,8 +387,10 @@ install_php() {
CURRENT_PHP=""
fi
if [[ "$CURRENT_PHP" != "$PHP_VERSION" ]]; then
$STD msg_info "PHP $CURRENT_PHP detected, migrating to PHP $PHP_VERSION"
if [[ -z "$CURRENT_PHP" ]]; then
msg_info "Setup PHP $PHP_VERSION"
elif [[ "$CURRENT_PHP" != "$PHP_VERSION" ]]; then
msg_info "PHP $CURRENT_PHP detected, migrating to PHP $PHP_VERSION"
if [[ ! -f /etc/apt/sources.list.d/php.list ]]; then
$STD curl -fsSLo /tmp/debsuryorg-archive-keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb
$STD dpkg -i /tmp/debsuryorg-archive-keyring.deb
@@ -373,6 +407,9 @@ install_php() {
for mod in "${MODULES[@]}"; do
MODULE_LIST+=" php${PHP_VERSION}-${mod}"
done
if [[ "$PHP_FPM" == "YES" ]]; then
MODULE_LIST+=" php${PHP_VERSION}-fpm"
fi
if [[ "$PHP_APACHE" == "YES" ]]; then
# Optionally disable old Apache PHP module
@@ -439,7 +476,7 @@ install_composer() {
# Download and install latest composer
curl -fsSL https://getcomposer.org/installer -o /tmp/composer-setup.php
php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer &>/dev/null
php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer >/dev/null 2>&1
if [[ $? -ne 0 ]]; then
msg_error "Failed to install Composer"
@@ -447,7 +484,9 @@ install_composer() {
fi
chmod +x "$COMPOSER_BIN"
msg_ok "Installed Composer $($COMPOSER_BIN --version | awk '{print $3}')"
composer diagnose >/dev/null 2>&1
msg_ok "Setup Composer"
#msg_ok "Installed Composer $($COMPOSER_BIN --version | awk '{print $3}')"
}
# ------------------------------------------------------------------------------
@@ -580,11 +619,21 @@ install_java() {
install_mongodb() {
local MONGO_VERSION="${MONGO_VERSION:-8.0}"
local DISTRO_CODENAME
DISTRO_CODENAME=$(awk -F= '/VERSION_CODENAME/ { print $2 }' /etc/os-release)
local DISTRO_ID DISTRO_CODENAME MONGO_BASE_URL
DISTRO_ID=$(awk -F= '/^ID=/{ gsub(/"/,"",$2); print $2 }' /etc/os-release)
DISTRO_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{ print $2 }' /etc/os-release)
case "$DISTRO_ID" in
ubuntu) MONGO_BASE_URL="https://repo.mongodb.org/apt/ubuntu" ;;
debian) MONGO_BASE_URL="https://repo.mongodb.org/apt/debian" ;;
*)
msg_error "Unsupported distribution: $DISTRO_ID"
return 1
;;
esac
local REPO_LIST="/etc/apt/sources.list.d/mongodb-org-${MONGO_VERSION}.list"
# Aktuell installierte Major-Version ermitteln
local INSTALLED_VERSION=""
if command -v mongod >/dev/null; then
INSTALLED_VERSION=$(mongod --version | awk '/db version/{print $3}' | cut -d. -f1,2)
@@ -598,7 +647,6 @@ install_mongodb() {
return 0
fi
# Ältere Version entfernen (nur Packages, nicht Daten!)
if [[ -n "$INSTALLED_VERSION" ]]; then
msg_info "Replacing MongoDB $INSTALLED_VERSION with $MONGO_VERSION (data will be preserved)"
$STD systemctl stop mongod || true
@@ -609,15 +657,17 @@ install_mongodb() {
msg_info "Installing MongoDB $MONGO_VERSION"
fi
# MongoDB Repo hinzufügen
curl -fsSL "https://pgp.mongodb.com/server-${MONGO_VERSION}.asc" | gpg --dearmor -o "/etc/apt/trusted.gpg.d/mongodb-${MONGO_VERSION}.gpg"
echo "deb [signed-by=/etc/apt/trusted.gpg.d/mongodb-${MONGO_VERSION}.gpg] https://repo.mongodb.org/apt/debian ${DISTRO_CODENAME}/mongodb-org/${MONGO_VERSION} main" \
echo "deb [signed-by=/etc/apt/trusted.gpg.d/mongodb-${MONGO_VERSION}.gpg] ${MONGO_BASE_URL} ${DISTRO_CODENAME}/mongodb-org/${MONGO_VERSION} main" \
>"$REPO_LIST"
$STD apt-get update
$STD apt-get update || {
msg_error "APT update failed — invalid MongoDB repo for ${DISTRO_ID}-${DISTRO_CODENAME}?"
return 1
}
$STD apt-get install -y mongodb-org
# Sicherstellen, dass Datenverzeichnis intakt bleibt
mkdir -p /var/lib/mongodb
chown -R mongodb:mongodb /var/lib/mongodb
@@ -641,7 +691,8 @@ install_mongodb() {
fetch_and_deploy_gh_release() {
local repo="$1"
local app=${APP:-$(echo "${APPLICATION,,}" | tr -d ' ')}
local raw_app="${APP:-$APPLICATION}"
local app=$(echo "${raw_app,,}" | tr -d ' ')
local api_url="https://api.github.com/repos/$repo/releases/latest"
local header=()
local attempt=0
@@ -1203,3 +1254,100 @@ create_selfsigned_certs() {
-subj "/C=US/O=$app/OU=Domain Control Validated/CN=localhost"
$STD msg_ok "Created Self-Signed Certificate"
}
# ------------------------------------------------------------------------------
# Installs Rust toolchain and optional global crates via cargo.
#
# Description:
# - Installs rustup (if missing)
# - Installs or updates desired Rust toolchain (stable, nightly, or versioned)
# - Installs or updates specified global crates using `cargo install`
#
# Notes:
# - Skips crate install if exact version is already present
# - Updates crate if newer version or different version is requested
#
# Variables:
# RUST_TOOLCHAIN - Rust toolchain to install (default: stable)
# RUST_CRATES - Comma-separated list of crates (e.g. "cargo-edit,wasm-pack@0.12.1")
# ------------------------------------------------------------------------------
install_rust_and_crates() {
local RUST_TOOLCHAIN="${RUST_TOOLCHAIN:-stable}"
local RUST_CRATES="${RUST_CRATES:-}"
local CARGO_BIN="${HOME}/.cargo/bin"
# rustup & toolchain
if ! command -v rustup &>/dev/null; then
msg_info "Installing rustup"
curl -fsSL https://sh.rustup.rs | $STD sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN"
export PATH="$CARGO_BIN:$PATH"
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >>"$HOME/.profile"
msg_ok "Installed rustup with $RUST_TOOLCHAIN"
else
$STD rustup install "$RUST_TOOLCHAIN"
$STD rustup default "$RUST_TOOLCHAIN"
$STD rustup update "$RUST_TOOLCHAIN"
msg_ok "Rust toolchain set to $RUST_TOOLCHAIN"
fi
# install/update crates
if [[ -n "$RUST_CRATES" ]]; then
IFS=',' read -ra CRATES <<<"$RUST_CRATES"
for crate in "${CRATES[@]}"; do
local NAME VER INSTALLED_VER
if [[ "$crate" == *"@"* ]]; then
NAME="${crate%@*}"
VER="${crate##*@}"
else
NAME="$crate"
VER=""
fi
INSTALLED_VER=$(cargo install --list 2>/dev/null | awk "/^$NAME v[0-9]/ {print \$2}" | tr -d 'v')
if [[ -n "$INSTALLED_VER" ]]; then
if [[ -n "$VER" && "$VER" != "$INSTALLED_VER" ]]; then
msg_info "Updating $NAME from $INSTALLED_VER to $VER"
$STD cargo install "$NAME" --version "$VER" --force
elif [[ -z "$VER" ]]; then
msg_info "Updating $NAME to latest"
$STD cargo install "$NAME" --force
else
msg_ok "$NAME@$INSTALLED_VER already up to date"
fi
else
msg_info "Installing $NAME ${VER:+($VER)}"
$STD cargo install "$NAME" ${VER:+--version "$VER"}
fi
done
msg_ok "All requested Rust crates processed"
fi
}
# ------------------------------------------------------------------------------
# Installs Adminer (Debian/Ubuntu via APT, Alpine via direct download).
#
# Description:
# - Adds Adminer to Apache or web root
# - Supports Alpine and Debian-based systems
# ------------------------------------------------------------------------------
install_adminer() {
if grep -qi alpine /etc/os-release; then
msg_info "Installing Adminer (Alpine)"
mkdir -p /var/www/localhost/htdocs/adminer
if ! curl -fsSL https://github.com/vrana/adminer/releases/latest/download/adminer.php \
-o /var/www/localhost/htdocs/adminer/index.php; then
msg_error "Failed to download Adminer"
return 1
fi
msg_ok "Adminer available at /adminer (Alpine)"
else
msg_info "Installing Adminer (Debian/Ubuntu)"
$STD apt-get install -y adminer
$STD a2enconf adminer
$STD systemctl reload apache2
msg_ok "Adminer available at /adminer (Debian/Ubuntu)"
fi
}