
Refactored case statements and conditional checks to use more idiomatic and consistent Bash syntax. Replaced German comments with English, improved readability by splitting long lines, and standardized variable assignments. Enhanced maintainability by using case/esac and if/then constructs, and replaced double-bracketed conditionals with POSIX-compliant single brackets where appropriate.
2274 lines
75 KiB
Bash
2274 lines
75 KiB
Bash
#!/usr/bin/env bash
|
||
# Copyright (c) 2021-2025 tteck
|
||
# Author: tteck (tteckster)
|
||
# Co-Author: MickLesk
|
||
# Co-Author: michelroegl-brunner
|
||
# License: MIT | https://github.com/community-scripts/ProxmoxVED/raw/main/LICENSE
|
||
|
||
variables() {
|
||
NSAPP=$(echo "${APP,,}" | tr -d ' ') # This function sets the NSAPP variable by converting the value of the APP variable to lowercase and removing any spaces.
|
||
var_install="${NSAPP}-install" # sets the var_install variable by appending "-install" to the value of NSAPP.
|
||
INTEGER='^[0-9]+([.][0-9]+)?$' # it defines the INTEGER regular expression pattern.
|
||
PVEHOST_NAME=$(hostname) # gets the Proxmox Hostname and sets it to Uppercase
|
||
DIAGNOSTICS="yes" # sets the DIAGNOSTICS variable to "yes", used for the API call.
|
||
METHOD="default" # sets the METHOD variable to "default", used for the API call.
|
||
RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" # generates a random UUID and sets it to the RANDOM_UUID variable.
|
||
#CT_TYPE=${var_unprivileged:-$CT_TYPE}
|
||
}
|
||
|
||
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/api.func)
|
||
|
||
if command -v curl >/dev/null 2>&1; then
|
||
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func)
|
||
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func)
|
||
load_functions
|
||
init_error_traps
|
||
#echo "(build.func) Loaded core.func via curl"
|
||
elif command -v wget >/dev/null 2>&1; then
|
||
source <(wget -qO- https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func)
|
||
source <(wget -qO- https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/error_handler.func)
|
||
load_functions
|
||
init_error_traps
|
||
#echo "(build.func) Loaded core.func via wget"
|
||
fi
|
||
|
||
# set -Eeuo pipefail
|
||
# trap 'error_handler $? $LINENO "$BASH_COMMAND"' ERR
|
||
# trap on_exit EXIT
|
||
# trap on_interrupt INT
|
||
# trap on_terminate TERM
|
||
|
||
# error_handler() {
|
||
# local exit_code="$1"
|
||
# local line_number="$2"
|
||
# local command="${3:-}"
|
||
|
||
# if [[ "$exit_code" -eq 0 ]]; then
|
||
# return 0
|
||
# fi
|
||
|
||
# printf "\e[?25h"
|
||
# 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 "$exit_code"
|
||
# }
|
||
|
||
# on_exit() {
|
||
# local exit_code="$?"
|
||
# [[ -n "${lockfile:-}" && -e "$lockfile" ]] && rm -f "$lockfile"
|
||
# exit "$exit_code"
|
||
# }
|
||
|
||
# on_interrupt() {
|
||
# echo -e "\n${RD}Interrupted by user (SIGINT)${CL}"
|
||
# exit 130
|
||
# }
|
||
|
||
# on_terminate() {
|
||
# echo -e "\n${RD}Terminated by signal (SIGTERM)${CL}"
|
||
# exit 143
|
||
# }
|
||
|
||
# # Check if the shell is using bash
|
||
# shell_check() {
|
||
# if [[ "$(basename "$SHELL")" != "bash" ]]; then
|
||
# clear
|
||
# msg_error "Your default shell is currently not set to Bash. To use these scripts, please switch to the Bash shell."
|
||
# echo -e "\nExiting..."
|
||
# sleep 2
|
||
# exit
|
||
# fi
|
||
# }
|
||
|
||
# # Run as root only
|
||
# root_check() {
|
||
# if [[ "$(id -u)" -ne 0 || $(ps -o comm= -p $PPID) == "sudo" ]]; then
|
||
# clear
|
||
# msg_error "Please run this script as root."
|
||
# echo -e "\nExiting..."
|
||
# sleep 2
|
||
# exit
|
||
# fi
|
||
# }
|
||
|
||
# # This function checks the version of Proxmox Virtual Environment (PVE) and exits if the version is not supported.
|
||
# # Supported: Proxmox VE 8.0.x – 8.9.x and 9.0 (NOT 9.1+)
|
||
# pve_check() {
|
||
# local PVE_VER
|
||
# PVE_VER="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')"
|
||
|
||
# # Check for Proxmox VE 8.x: allow 8.0–8.9
|
||
# if [[ "$PVE_VER" =~ ^8\.([0-9]+) ]]; then
|
||
# local MINOR="${BASH_REMATCH[1]}"
|
||
# if ((MINOR < 0 || MINOR > 9)); then
|
||
# msg_error "This version of Proxmox VE is not supported."
|
||
# msg_error "Supported: Proxmox VE version 8.0 – 8.9"
|
||
# exit 1
|
||
# fi
|
||
# return 0
|
||
# fi
|
||
|
||
# # Check for Proxmox VE 9.x: allow ONLY 9.0
|
||
# if [[ "$PVE_VER" =~ ^9\.([0-9]+) ]]; then
|
||
# local MINOR="${BASH_REMATCH[1]}"
|
||
# if ((MINOR != 0)); then
|
||
# msg_error "This version of Proxmox VE is not yet supported."
|
||
# msg_error "Supported: Proxmox VE version 9.0"
|
||
# exit 1
|
||
# fi
|
||
# return 0
|
||
# fi
|
||
|
||
# # All other unsupported versions
|
||
# msg_error "This version of Proxmox VE is not supported."
|
||
# msg_error "Supported versions: Proxmox VE 8.0 – 8.x or 9.0"
|
||
# exit 1
|
||
# }
|
||
|
||
# When a node is running tens of containers, it's possible to exceed the kernel's cryptographic key storage allocations.
|
||
# These are tuneable, so verify if the currently deployment is approaching the limits, advise the user on how to tune the limits, and exit the script.
|
||
# https://cleveruptime.com/docs/files/proc-key-users | https://docs.kernel.org/security/keys/core.html
|
||
maxkeys_check() {
|
||
# Read kernel parameters
|
||
per_user_maxkeys=$(cat /proc/sys/kernel/keys/maxkeys 2>/dev/null || echo 0)
|
||
per_user_maxbytes=$(cat /proc/sys/kernel/keys/maxbytes 2>/dev/null || echo 0)
|
||
|
||
# Exit if kernel parameters are unavailable
|
||
if [[ "$per_user_maxkeys" -eq 0 || "$per_user_maxbytes" -eq 0 ]]; then
|
||
echo -e "${CROSS}${RD} Error: Unable to read kernel parameters. Ensure proper permissions.${CL}"
|
||
exit 1
|
||
fi
|
||
|
||
# Fetch key usage for user ID 100000 (typical for containers)
|
||
used_lxc_keys=$(awk '/100000:/ {print $2}' /proc/key-users 2>/dev/null || echo 0)
|
||
used_lxc_bytes=$(awk '/100000:/ {split($5, a, "/"); print a[1]}' /proc/key-users 2>/dev/null || echo 0)
|
||
|
||
# Calculate thresholds and suggested new limits
|
||
threshold_keys=$((per_user_maxkeys - 100))
|
||
threshold_bytes=$((per_user_maxbytes - 1000))
|
||
new_limit_keys=$((per_user_maxkeys * 2))
|
||
new_limit_bytes=$((per_user_maxbytes * 2))
|
||
|
||
# Check if key or byte usage is near limits
|
||
failure=0
|
||
if [[ "$used_lxc_keys" -gt "$threshold_keys" ]]; then
|
||
echo -e "${CROSS}${RD} Warning: Key usage is near the limit (${used_lxc_keys}/${per_user_maxkeys}).${CL}"
|
||
echo -e "${INFO} Suggested action: Set ${GN}kernel.keys.maxkeys=${new_limit_keys}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}."
|
||
failure=1
|
||
fi
|
||
if [[ "$used_lxc_bytes" -gt "$threshold_bytes" ]]; then
|
||
echo -e "${CROSS}${RD} Warning: Key byte usage is near the limit (${used_lxc_bytes}/${per_user_maxbytes}).${CL}"
|
||
echo -e "${INFO} Suggested action: Set ${GN}kernel.keys.maxbytes=${new_limit_bytes}${CL} in ${BOLD}/etc/sysctl.d/98-community-scripts.conf${CL}."
|
||
failure=1
|
||
fi
|
||
|
||
# Provide next steps if issues are detected
|
||
if [[ "$failure" -eq 1 ]]; then
|
||
echo -e "${INFO} To apply changes, run: ${BOLD}service procps force-reload${CL}"
|
||
exit 1
|
||
fi
|
||
|
||
echo -e "${CM}${GN} All kernel key limits are within safe thresholds.${CL}"
|
||
}
|
||
|
||
# Function to get the current IP address based on the distribution
|
||
get_current_ip() {
|
||
if [ -f /etc/os-release ]; then
|
||
# Check for Debian/Ubuntu (uses hostname -I)
|
||
if grep -qE 'ID=debian|ID=ubuntu' /etc/os-release; then
|
||
CURRENT_IP=$(hostname -I | awk '{print $1}')
|
||
# Check for Alpine (uses ip command)
|
||
elif grep -q 'ID=alpine' /etc/os-release; then
|
||
CURRENT_IP=$(ip -4 addr show eth0 | awk '/inet / {print $2}' | cut -d/ -f1 | head -n 1)
|
||
else
|
||
CURRENT_IP="Unknown"
|
||
fi
|
||
fi
|
||
echo "$CURRENT_IP"
|
||
}
|
||
|
||
# Function to update the IP address in the MOTD file
|
||
update_motd_ip() {
|
||
MOTD_FILE="/etc/motd"
|
||
|
||
if [ -f "$MOTD_FILE" ]; then
|
||
# Remove existing IP Address lines to prevent duplication
|
||
sed -i '/IP Address:/d' "$MOTD_FILE"
|
||
|
||
IP=$(get_current_ip)
|
||
# Add the new IP address
|
||
echo -e "${TAB}${NETWORK}${YW} IP Address: ${GN}${IP}${CL}" >>"$MOTD_FILE"
|
||
fi
|
||
}
|
||
|
||
# This function checks if the script is running through SSH and prompts the user to confirm if they want to proceed or exit.
|
||
ssh_check() {
|
||
if [ -n "${SSH_CLIENT:+x}" ]; then
|
||
if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "SSH DETECTED" --yesno "It's advisable to utilize the Proxmox shell rather than SSH, as there may be potential complications with variable retrieval. Proceed using SSH?" 10 72; then
|
||
whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox --title "Proceed using SSH" "You've chosen to proceed using SSH. If any issues arise, please run the script in the Proxmox shell before creating a repository issue." 10 72
|
||
else
|
||
clear
|
||
echo "Exiting due to SSH usage. Please consider using the Proxmox shell."
|
||
exit
|
||
fi
|
||
fi
|
||
}
|
||
|
||
install_ssh_keys_into_ct() {
|
||
[[ "$SSH" != "yes" ]] && return 0
|
||
|
||
if [[ -n "$SSH_KEYS_FILE" && -s "$SSH_KEYS_FILE" ]]; then
|
||
msg_info "Installing selected SSH keys into CT ${CTID}"
|
||
pct exec "$CTID" -- sh -c 'mkdir -p /root/.ssh && chmod 700 /root/.ssh' || {
|
||
msg_error "prepare /root/.ssh failed"
|
||
return 1
|
||
}
|
||
pct push "$CTID" "$SSH_KEYS_FILE" /root/.ssh/authorized_keys >/dev/null 2>&1 ||
|
||
pct exec "$CTID" -- sh -c "cat > /root/.ssh/authorized_keys" <"$SSH_KEYS_FILE" || {
|
||
msg_error "write authorized_keys failed"
|
||
return 1
|
||
}
|
||
pct exec "$CTID" -- sh -c 'chmod 600 /root/.ssh/authorized_keys' || true
|
||
msg_ok "Installed SSH keys into CT ${CTID}"
|
||
return 0
|
||
fi
|
||
|
||
# Fallback: nichts ausgewählt
|
||
msg_warn "No SSH keys to install (skipping)."
|
||
return 0
|
||
}
|
||
|
||
base_settings() {
|
||
# Default Settings
|
||
CT_TYPE=${var_unprivileged:-"1"}
|
||
DISK_SIZE=${var_disk:-"4"}
|
||
CORE_COUNT=${var_cpu:-"1"}
|
||
RAM_SIZE=${var_ram:-"1024"}
|
||
VERBOSE=${var_verbose:-"${1:-no}"}
|
||
PW=${var_pw:-""}
|
||
CT_ID=${var_ctid:-$NEXTID}
|
||
HN=${var_hostname:-$NSAPP}
|
||
BRG=${var_brg:-"vmbr0"}
|
||
NET=${var_net:-"dhcp"}
|
||
IPV6_METHOD=${var_ipv6_method:-"none"}
|
||
IPV6_STATIC=${var_ipv6_static:-""}
|
||
GATE=${var_gateway:-""}
|
||
APT_CACHER=${var_apt_cacher:-""}
|
||
APT_CACHER_IP=${var_apt_cacher_ip:-""}
|
||
MTU=${var_mtu:-""}
|
||
SD=${var_storage:-""}
|
||
NS=${var_ns:-""}
|
||
MAC=${var_mac:-""}
|
||
VLAN=${var_vlan:-""}
|
||
SSH=${var_ssh:-"no"}
|
||
SSH_AUTHORIZED_KEY=${var_ssh_authorized_key:-""}
|
||
UDHCPC_FIX=${var_udhcpc_fix:-""}
|
||
TAGS="community-script,${var_tags:-}"
|
||
ENABLE_FUSE=${var_fuse:-"${1:-no}"}
|
||
ENABLE_TUN=${var_tun:-"${1:-no}"}
|
||
|
||
# Since these 2 are only defined outside of default_settings function, we add a temporary fallback. TODO: To align everything, we should add these as constant variables (e.g. OSTYPE and OSVERSION), but that would currently require updating the default_settings function for all existing scripts
|
||
if [ -z "$var_os" ]; then
|
||
var_os="debian"
|
||
fi
|
||
if [ -z "$var_version" ]; then
|
||
var_version="12"
|
||
fi
|
||
}
|
||
|
||
# This function displays the default values for various settings.
|
||
echo_default() {
|
||
# Convert CT_TYPE to description
|
||
CT_TYPE_DESC="Unprivileged"
|
||
if [ "$CT_TYPE" -eq 0 ]; then
|
||
CT_TYPE_DESC="Privileged"
|
||
fi
|
||
|
||
# Output the selected values with icons
|
||
echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}${CT_ID}${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${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 [ "$VERBOSE" == "yes" ]; then
|
||
echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}Enabled${CL}"
|
||
fi
|
||
echo -e "${CREATING}${BOLD}${BL}Creating a ${APP} LXC using the above default settings${CL}"
|
||
echo -e " "
|
||
}
|
||
|
||
# This function is called when the user decides to exit the script. It clears the screen and displays an exit message.
|
||
exit_script() {
|
||
clear
|
||
echo -e "\n${CROSS}${RD}User exited script${CL}\n"
|
||
exit
|
||
}
|
||
|
||
find_host_ssh_keys() {
|
||
local re='(ssh-(rsa|ed25519)|ecdsa-sha2-nistp256|sk-(ssh-ed25519|ecdsa-sha2-nistp256))'
|
||
local -a files=() cand=()
|
||
local g="${var_ssh_import_glob:-}"
|
||
local total=0 f base c
|
||
|
||
shopt -s nullglob
|
||
if [[ -n "$g" ]]; then
|
||
for pat in $g; do cand+=($pat); done
|
||
else
|
||
cand+=(/root/.ssh/authorized_keys /root/.ssh/authorized_keys2)
|
||
cand+=(/root/.ssh/*.pub)
|
||
cand+=(/etc/ssh/authorized_keys /etc/ssh/authorized_keys.d/*)
|
||
fi
|
||
shopt -u nullglob
|
||
|
||
for f in "${cand[@]}"; do
|
||
[[ -f "$f" && -r "$f" ]] || continue
|
||
base="$(basename -- "$f")"
|
||
case "$base" in
|
||
known_hosts | known_hosts.* | config) continue ;;
|
||
id_*) [[ "$f" != *.pub ]] && continue ;;
|
||
esac
|
||
|
||
# CRLF safe check for host keys
|
||
c=$(tr -d '\r' <"$f" | awk '
|
||
/^[[:space:]]*#/ {next}
|
||
/^[[:space:]]*$/ {next}
|
||
{print}
|
||
' | grep -E -c '"$re"' || true)
|
||
|
||
if ((c > 0)); then
|
||
files+=("$f")
|
||
total=$((total + c))
|
||
fi
|
||
done
|
||
|
||
# Fallback to /root/.ssh/authorized_keys
|
||
if ((${#files[@]} == 0)) && [[ -r /root/.ssh/authorized_keys ]]; then
|
||
if grep -E -q "$re" /root/.ssh/authorized_keys; then
|
||
files+=(/root/.ssh/authorized_keys)
|
||
total=$((total + $(grep -E -c "$re" /root/.ssh/authorized_keys || echo 0)))
|
||
fi
|
||
fi
|
||
|
||
FOUND_HOST_KEY_COUNT="$total"
|
||
(
|
||
IFS=:
|
||
echo "${files[*]}"
|
||
)
|
||
}
|
||
|
||
# This function allows the user to configure advanced settings for the script.
|
||
advanced_settings() {
|
||
whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox --title "Here is an instructional tip:" "To make a selection, use the Spacebar." 8 58
|
||
# Setting Default Tag for Advanced Settings
|
||
TAGS="community-script;${var_tags:-}"
|
||
CT_DEFAULT_TYPE="${CT_TYPE}"
|
||
CT_TYPE=""
|
||
while [ -z "$CT_TYPE" ]; do
|
||
if [ "$CT_DEFAULT_TYPE" == "1" ]; then
|
||
if CT_TYPE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "CONTAINER TYPE" --radiolist "Choose Type" 10 58 2 \
|
||
"1" "Unprivileged" ON \
|
||
"0" "Privileged" OFF \
|
||
3>&1 1>&2 2>&3); then
|
||
if [ -n "$CT_TYPE" ]; then
|
||
CT_TYPE_DESC="Unprivileged"
|
||
if [ "$CT_TYPE" -eq 0 ]; then
|
||
CT_TYPE_DESC="Privileged"
|
||
fi
|
||
echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os | ${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}"
|
||
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}"
|
||
fi
|
||
else
|
||
exit_script
|
||
fi
|
||
fi
|
||
if [ "$CT_DEFAULT_TYPE" == "0" ]; then
|
||
if CT_TYPE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "CONTAINER TYPE" --radiolist "Choose Type" 10 58 2 \
|
||
"1" "Unprivileged" OFF \
|
||
"0" "Privileged" ON \
|
||
3>&1 1>&2 2>&3); then
|
||
if [ -n "$CT_TYPE" ]; then
|
||
CT_TYPE_DESC="Unprivileged"
|
||
if [ "$CT_TYPE" -eq 0 ]; then
|
||
CT_TYPE_DESC="Privileged"
|
||
fi
|
||
echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}$var_os${CL}"
|
||
echo -e "${OSVERSION}${BOLD}${DGN}Version: ${BGN}$var_version${CL}"
|
||
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Container Type: ${BGN}$CT_TYPE_DESC${CL}"
|
||
fi
|
||
else
|
||
exit_script
|
||
fi
|
||
fi
|
||
done
|
||
|
||
while true; do
|
||
if PW1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --passwordbox "\nSet Root Password (needed for root ssh access)" 9 58 --title "PASSWORD (leave blank for automatic login)" 3>&1 1>&2 2>&3); then
|
||
# Empty = Autologin
|
||
if [[ -z "$PW1" ]]; then
|
||
PW=""
|
||
PW1="Automatic Login"
|
||
echo -e "${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}$PW1${CL}"
|
||
break
|
||
fi
|
||
|
||
# Invalid: contains spaces
|
||
if [[ "$PW1" == *" "* ]]; then
|
||
whiptail --msgbox "Password cannot contain spaces." 8 58
|
||
continue
|
||
fi
|
||
|
||
# Invalid: too short
|
||
if ((${#PW1} < 5)); then
|
||
whiptail --msgbox "Password must be at least 5 characters." 8 58
|
||
continue
|
||
fi
|
||
|
||
# Confirm password
|
||
if PW2=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --passwordbox "\nVerify Root Password" 9 58 --title "PASSWORD VERIFICATION" 3>&1 1>&2 2>&3); then
|
||
if [[ "$PW1" == "$PW2" ]]; then
|
||
PW="-password $PW1"
|
||
echo -e "${VERIFYPW}${BOLD}${DGN}Root Password: ${BGN}********${CL}"
|
||
break
|
||
else
|
||
whiptail --msgbox "Passwords do not match. Please try again." 8 58
|
||
fi
|
||
else
|
||
exit_script
|
||
fi
|
||
else
|
||
exit_script
|
||
fi
|
||
done
|
||
|
||
if CT_ID=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set Container ID" 8 58 "$NEXTID" --title "CONTAINER ID" 3>&1 1>&2 2>&3); then
|
||
if [ -z "$CT_ID" ]; then
|
||
CT_ID="$NEXTID"
|
||
echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}"
|
||
else
|
||
echo -e "${CONTAINERID}${BOLD}${DGN}Container ID: ${BGN}$CT_ID${CL}"
|
||
fi
|
||
else
|
||
exit_script
|
||
fi
|
||
|
||
while true; do
|
||
if CT_NAME=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set Hostname" 8 58 "$NSAPP" --title "HOSTNAME" 3>&1 1>&2 2>&3); then
|
||
if [ -z "$CT_NAME" ]; then
|
||
HN="$NSAPP"
|
||
else
|
||
HN=$(echo "${CT_NAME,,}" | tr -d ' ')
|
||
fi
|
||
# Hostname validate (RFC 1123)
|
||
if [[ "$HN" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ ]]; then
|
||
echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}$HN${CL}"
|
||
break
|
||
else
|
||
whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \
|
||
--msgbox "❌ Invalid hostname: '$HN'\n\nOnly lowercase letters, digits and hyphens (-) are allowed.\nUnderscores (_) or other characters are not permitted!" 10 70
|
||
fi
|
||
else
|
||
exit_script
|
||
fi
|
||
done
|
||
|
||
while true; do
|
||
DISK_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Disk Size in GB" 8 58 "$var_disk" --title "DISK SIZE" 3>&1 1>&2 2>&3) || exit_script
|
||
|
||
if [ -z "$DISK_SIZE" ]; then
|
||
DISK_SIZE="$var_disk"
|
||
fi
|
||
|
||
if [[ "$DISK_SIZE" =~ ^[1-9][0-9]*$ ]]; then
|
||
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE} GB${CL}"
|
||
break
|
||
else
|
||
whiptail --msgbox "Disk size must be a positive integer!" 8 58
|
||
fi
|
||
done
|
||
|
||
while true; do
|
||
CORE_COUNT=$(whiptail --backtitle "Proxmox VE Helper Scripts" \
|
||
--inputbox "Allocate CPU Cores" 8 58 "$var_cpu" --title "CORE COUNT" 3>&1 1>&2 2>&3) || exit_script
|
||
|
||
if [ -z "$CORE_COUNT" ]; then
|
||
CORE_COUNT="$var_cpu"
|
||
fi
|
||
|
||
if [[ "$CORE_COUNT" =~ ^[1-9][0-9]*$ ]]; then
|
||
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}$CORE_COUNT${CL}"
|
||
break
|
||
else
|
||
whiptail --msgbox "CPU core count must be a positive integer!" 8 58
|
||
fi
|
||
done
|
||
|
||
while true; do
|
||
RAM_SIZE=$(whiptail --backtitle "Proxmox VE Helper Scripts" \
|
||
--inputbox "Allocate RAM in MiB" 8 58 "$var_ram" --title "RAM" 3>&1 1>&2 2>&3) || exit_script
|
||
|
||
if [ -z "$RAM_SIZE" ]; then
|
||
RAM_SIZE="$var_ram"
|
||
fi
|
||
|
||
if [[ "$RAM_SIZE" =~ ^[1-9][0-9]*$ ]]; then
|
||
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE} MiB${CL}"
|
||
break
|
||
else
|
||
whiptail --msgbox "RAM size must be a positive integer!" 8 58
|
||
fi
|
||
done
|
||
|
||
IFACE_FILEPATH_LIST="/etc/network/interfaces"$'\n'$(find "/etc/network/interfaces.d/" -type f)
|
||
BRIDGES=""
|
||
OLD_IFS=$IFS
|
||
IFS=$'\n'
|
||
for iface_filepath in ${IFACE_FILEPATH_LIST}; do
|
||
|
||
iface_indexes_tmpfile=$(mktemp -q -u '.iface-XXXX')
|
||
(grep -Pn '^\s*iface' "${iface_filepath}" | cut -d':' -f1 && wc -l "${iface_filepath}" | cut -d' ' -f1) | awk 'FNR==1 {line=$0; next} {print line":"$0-1; line=$0}' >"${iface_indexes_tmpfile}" || true
|
||
|
||
if [ -f "${iface_indexes_tmpfile}" ]; then
|
||
|
||
while read -r pair; do
|
||
start=$(echo "${pair}" | cut -d':' -f1)
|
||
end=$(echo "${pair}" | cut -d':' -f2)
|
||
|
||
if awk "NR >= ${start} && NR <= ${end}" "${iface_filepath}" | grep -qP '^\s*(bridge[-_](ports|stp|fd|vlan-aware|vids)|ovs_type\s+OVSBridge)\b'; then
|
||
iface_name=$(sed "${start}q;d" "${iface_filepath}" | awk '{print $2}')
|
||
BRIDGES="${iface_name}"$'\n'"${BRIDGES}"
|
||
fi
|
||
|
||
done <"${iface_indexes_tmpfile}"
|
||
rm -f "${iface_indexes_tmpfile}"
|
||
fi
|
||
|
||
done
|
||
IFS=$OLD_IFS
|
||
BRIDGES=$(echo "$BRIDGES" | grep -v '^\s*$' | sort | uniq)
|
||
if [[ -z "$BRIDGES" ]]; then
|
||
BRG="vmbr0"
|
||
echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}"
|
||
else
|
||
BRG=$(whiptail --backtitle "Proxmox VE Helper Scripts" --menu "Select network bridge:" 15 40 6 $(echo "$BRIDGES" | awk '{print $0, "Bridge"}') 3>&1 1>&2 2>&3)
|
||
if [[ -z "$BRG" ]]; then
|
||
exit_script
|
||
else
|
||
echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}$BRG${CL}"
|
||
fi
|
||
fi
|
||
|
||
# IPv4 methods: dhcp, static, none
|
||
while true; do
|
||
IPV4_METHOD=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \
|
||
--title "IPv4 Address Management" \
|
||
--menu "Select IPv4 Address Assignment Method:" 12 60 2 \
|
||
"dhcp" "Automatic (DHCP, recommended)" \
|
||
"static" "Static (manual entry)" \
|
||
3>&1 1>&2 2>&3)
|
||
|
||
exit_status=$?
|
||
if [ $exit_status -ne 0 ]; then
|
||
exit_script
|
||
fi
|
||
|
||
case "$IPV4_METHOD" in
|
||
dhcp)
|
||
NET="dhcp"
|
||
GATE=""
|
||
echo -e "${NETWORK}${BOLD}${DGN}IPv4: DHCP${CL}"
|
||
break
|
||
;;
|
||
static)
|
||
# Static: call and validate CIDR address
|
||
while true; do
|
||
NET=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \
|
||
--inputbox "Enter Static IPv4 CIDR Address (e.g. 192.168.100.50/24)" 8 58 "" \
|
||
--title "IPv4 ADDRESS" 3>&1 1>&2 2>&3)
|
||
if [ -z "$NET" ]; then
|
||
whiptail --msgbox "IPv4 address must not be empty." 8 58
|
||
continue
|
||
elif [[ "$NET" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]]; then
|
||
echo -e "${NETWORK}${BOLD}${DGN}IPv4 Address: ${BGN}$NET${CL}"
|
||
break
|
||
else
|
||
whiptail --msgbox "$NET is not a valid IPv4 CIDR address. Please enter a correct value!" 8 58
|
||
fi
|
||
done
|
||
|
||
# call and validate Gateway
|
||
while true; do
|
||
GATE1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \
|
||
--inputbox "Enter Gateway IP address for static IPv4" 8 58 "" \
|
||
--title "Gateway IP" 3>&1 1>&2 2>&3)
|
||
if [ -z "$GATE1" ]; then
|
||
whiptail --msgbox "Gateway IP address cannot be empty." 8 58
|
||
elif [[ ! "$GATE1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
|
||
whiptail --msgbox "Invalid Gateway IP address format." 8 58
|
||
else
|
||
GATE=",gw=$GATE1"
|
||
echo -e "${GATEWAY}${BOLD}${DGN}Gateway IP Address: ${BGN}$GATE1${CL}"
|
||
break
|
||
fi
|
||
done
|
||
break
|
||
;;
|
||
esac
|
||
done
|
||
|
||
# IPv6 Address Management selection
|
||
while true; do
|
||
IPV6_METHOD=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --menu \
|
||
"Select IPv6 Address Management Type:" 15 58 4 \
|
||
"auto" "SLAAC/AUTO (recommended, default)" \
|
||
"dhcp" "DHCPv6" \
|
||
"static" "Static (manual entry)" \
|
||
"none" "Disabled" \
|
||
--default-item "auto" 3>&1 1>&2 2>&3)
|
||
[ $? -ne 0 ] && exit_script
|
||
|
||
case "$IPV6_METHOD" in
|
||
auto)
|
||
echo -e "${NETWORK}${BOLD}${DGN}IPv6: ${BGN}SLAAC/AUTO${CL}"
|
||
IPV6_ADDR=""
|
||
IPV6_GATE=""
|
||
break
|
||
;;
|
||
dhcp)
|
||
echo -e "${NETWORK}${BOLD}${DGN}IPv6: ${BGN}DHCPv6${CL}"
|
||
IPV6_ADDR="dhcp"
|
||
IPV6_GATE=""
|
||
break
|
||
;;
|
||
static)
|
||
# Ask for static IPv6 address (CIDR notation, e.g., 2001:db8::1234/64)
|
||
while true; do
|
||
IPV6_ADDR=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox \
|
||
"Set a static IPv6 CIDR address (e.g., 2001:db8::1234/64)" 8 58 "" \
|
||
--title "IPv6 STATIC ADDRESS" 3>&1 1>&2 2>&3) || exit_script
|
||
if [[ "$IPV6_ADDR" =~ ^([0-9a-fA-F:]+:+)+[0-9a-fA-F]+(/[0-9]{1,3})$ ]]; then
|
||
echo -e "${NETWORK}${BOLD}${DGN}IPv6 Address: ${BGN}$IPV6_ADDR${CL}"
|
||
break
|
||
else
|
||
whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox \
|
||
"$IPV6_ADDR is an invalid IPv6 CIDR address. Please enter a valid IPv6 CIDR address (e.g., 2001:db8::1234/64)" 8 58
|
||
fi
|
||
done
|
||
# Optional: ask for IPv6 gateway for static config
|
||
while true; do
|
||
IPV6_GATE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox \
|
||
"Enter IPv6 gateway address (optional, leave blank for none)" 8 58 "" --title "IPv6 GATEWAY" 3>&1 1>&2 2>&3)
|
||
if [ -z "$IPV6_GATE" ]; then
|
||
IPV6_GATE=""
|
||
break
|
||
elif [[ "$IPV6_GATE" =~ ^([0-9a-fA-F:]+:+)+[0-9a-fA-F]+$ ]]; then
|
||
break
|
||
else
|
||
whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox \
|
||
"Invalid IPv6 gateway format." 8 58
|
||
fi
|
||
done
|
||
break
|
||
;;
|
||
none)
|
||
echo -e "${NETWORK}${BOLD}${DGN}IPv6: ${BGN}Disabled${CL}"
|
||
IPV6_ADDR="none"
|
||
IPV6_GATE=""
|
||
break
|
||
;;
|
||
*)
|
||
exit_script
|
||
;;
|
||
esac
|
||
done
|
||
|
||
if [ "$var_os" == "alpine" ]; then
|
||
APT_CACHER=""
|
||
APT_CACHER_IP=""
|
||
else
|
||
if APT_CACHER_IP=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set APT-Cacher IP (leave blank for none)" 8 58 --title "APT-Cacher IP" 3>&1 1>&2 2>&3); then
|
||
APT_CACHER="${APT_CACHER_IP:+yes}"
|
||
echo -e "${NETWORK}${BOLD}${DGN}APT-Cacher IP Address: ${BGN}${APT_CACHER_IP:-Default}${CL}"
|
||
else
|
||
exit_script
|
||
fi
|
||
fi
|
||
|
||
# if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "IPv6" --yesno "Disable IPv6?" 10 58); then
|
||
# DISABLEIP6="yes"
|
||
# else
|
||
# DISABLEIP6="no"
|
||
# fi
|
||
# echo -e "${DISABLEIPV6}${BOLD}${DGN}Disable IPv6: ${BGN}$DISABLEIP6${CL}"
|
||
|
||
if MTU1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set Interface MTU Size (leave blank for default [The MTU of your selected vmbr, default is 1500])" 8 58 --title "MTU SIZE" 3>&1 1>&2 2>&3); then
|
||
if [ -z "$MTU1" ]; then
|
||
MTU1="Default"
|
||
MTU=""
|
||
else
|
||
MTU=",mtu=$MTU1"
|
||
fi
|
||
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}$MTU1${CL}"
|
||
else
|
||
exit_script
|
||
fi
|
||
|
||
if SD=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a DNS Search Domain (leave blank for HOST)" 8 58 --title "DNS Search Domain" 3>&1 1>&2 2>&3); then
|
||
if [ -z "$SD" ]; then
|
||
SX=Host
|
||
SD=""
|
||
else
|
||
SX=$SD
|
||
SD="-searchdomain=$SD"
|
||
fi
|
||
echo -e "${SEARCH}${BOLD}${DGN}DNS Search Domain: ${BGN}$SX${CL}"
|
||
else
|
||
exit_script
|
||
fi
|
||
|
||
if NX=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a DNS Server IP (leave blank for HOST)" 8 58 --title "DNS SERVER IP" 3>&1 1>&2 2>&3); then
|
||
if [ -z "$NX" ]; then
|
||
NX=Host
|
||
NS=""
|
||
else
|
||
NS="-nameserver=$NX"
|
||
fi
|
||
echo -e "${NETWORK}${BOLD}${DGN}DNS Server IP Address: ${BGN}$NX${CL}"
|
||
else
|
||
exit_script
|
||
fi
|
||
|
||
if [ "$var_os" == "alpine" ] && [ "$NET" == "dhcp" ] && [ "$NX" != "Host" ]; then
|
||
UDHCPC_FIX="yes"
|
||
else
|
||
UDHCPC_FIX="no"
|
||
fi
|
||
export UDHCPC_FIX
|
||
|
||
if MAC1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a MAC Address(leave blank for generated MAC)" 8 58 --title "MAC ADDRESS" 3>&1 1>&2 2>&3); then
|
||
if [ -z "$MAC1" ]; then
|
||
MAC1="Default"
|
||
MAC=""
|
||
else
|
||
MAC=",hwaddr=$MAC1"
|
||
echo -e "${MACADDRESS}${BOLD}${DGN}MAC Address: ${BGN}$MAC1${CL}"
|
||
fi
|
||
else
|
||
exit_script
|
||
fi
|
||
|
||
if VLAN1=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set a Vlan(leave blank for no VLAN)" 8 58 --title "VLAN" 3>&1 1>&2 2>&3); then
|
||
if [ -z "$VLAN1" ]; then
|
||
VLAN1="Default"
|
||
VLAN=""
|
||
else
|
||
VLAN=",tag=$VLAN1"
|
||
fi
|
||
echo -e "${VLANTAG}${BOLD}${DGN}Vlan: ${BGN}$VLAN1${CL}"
|
||
else
|
||
exit_script
|
||
fi
|
||
|
||
if ADV_TAGS=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "Set Custom Tags?[If you remove all, there will be no tags!]" 8 58 "${TAGS}" --title "Advanced Tags" 3>&1 1>&2 2>&3); then
|
||
if [ -n "${ADV_TAGS}" ]; then
|
||
ADV_TAGS=$(echo "$ADV_TAGS" | tr -d '[:space:]')
|
||
TAGS="${ADV_TAGS}"
|
||
else
|
||
TAGS=";"
|
||
fi
|
||
echo -e "${NETWORK}${BOLD}${DGN}Tags: ${BGN}$TAGS${CL}"
|
||
else
|
||
exit_script
|
||
fi
|
||
|
||
# --- SSH key provisioning (one dialog) ---
|
||
SSH_KEYS_FILE="$(mktemp)"
|
||
: >"$SSH_KEYS_FILE"
|
||
|
||
IFS=$'\0' read -r -d '' -a _def_files < <(ssh_discover_default_files && printf '\0')
|
||
ssh_build_choices_from_files "${_def_files[@]}"
|
||
DEF_KEYS_COUNT="$COUNT"
|
||
|
||
if [[ "$DEF_KEYS_COUNT" -gt 0 ]]; then
|
||
SSH_KEY_MODE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "SSH KEY SOURCE" --menu \
|
||
"Provision SSH keys for root:" 14 72 4 \
|
||
"found" "Select from detected keys (${DEF_KEYS_COUNT})" \
|
||
"manual" "Paste a single public key" \
|
||
"folder" "Scan another folder (path or glob)" \
|
||
"none" "No keys" 3>&1 1>&2 2>&3) || exit_script
|
||
else
|
||
SSH_KEY_MODE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "SSH KEY SOURCE" --menu \
|
||
"No host keys detected; choose manual/none:" 12 72 2 \
|
||
"manual" "Paste a single public key" \
|
||
"none" "No keys" 3>&1 1>&2 2>&3) || exit_script
|
||
fi
|
||
|
||
case "$SSH_KEY_MODE" in
|
||
found)
|
||
SEL=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "SELECT HOST KEYS" \
|
||
--checklist "Select one or more keys to import:" 20 20 10 "${CHOICES[@]}" 3>&1 1>&2 2>&3) || exit_script
|
||
for tag in $SEL; do
|
||
tag="${tag%\"}"
|
||
tag="${tag#\"}"
|
||
line=$(grep -E "^${tag}\|" "$MAPFILE" | head -n1 | cut -d'|' -f2-)
|
||
[[ -n "$line" ]] && printf '%s\n' "$line" >>"$SSH_KEYS_FILE"
|
||
done
|
||
;;
|
||
manual)
|
||
SSH_AUTHORIZED_KEY="$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \
|
||
--inputbox "Paste one SSH public key line (ssh-ed25519/ssh-rsa/...)" 10 72 --title "SSH Public Key" 3>&1 1>&2 2>&3)"
|
||
[[ -n "$SSH_AUTHORIZED_KEY" ]] && printf '%s\n' "$SSH_AUTHORIZED_KEY" >>"$SSH_KEYS_FILE"
|
||
;;
|
||
folder)
|
||
GLOB_PATH="$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \
|
||
--inputbox "Enter a folder or glob to scan (e.g. /root/.ssh/*.pub)" 10 72 --title "Scan Folder/Glob" 3>&1 1>&2 2>&3)"
|
||
if [[ -n "$GLOB_PATH" ]]; then
|
||
shopt -s nullglob
|
||
read -r -a _scan_files <<<"$GLOB_PATH"
|
||
shopt -u nullglob
|
||
if [[ "${#_scan_files[@]}" -gt 0 ]]; then
|
||
ssh_build_choices_from_files "${_scan_files[@]}"
|
||
if [[ "$COUNT" -gt 0 ]]; then
|
||
SEL=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "SELECT FOLDER KEYS" \
|
||
--checklist "Select key(s) to import:" 20 78 10 "${CHOICES[@]}" 3>&1 1>&2 2>&3) || exit_script
|
||
for tag in $SEL; do
|
||
tag="${tag%\"}"
|
||
tag="${tag#\"}"
|
||
line=$(grep -E "^${tag}\|" "$MAPFILE" | head -n1 | cut -d'|' -f2-)
|
||
[[ -n "$line" ]] && printf '%s\n' "$line" >>"$SSH_KEYS_FILE"
|
||
done
|
||
else
|
||
whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox "No keys found in: $GLOB_PATH" 8 60
|
||
fi
|
||
else
|
||
whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --msgbox "Path/glob returned no files." 8 60
|
||
fi
|
||
fi
|
||
;;
|
||
none) : ;;
|
||
esac
|
||
|
||
# Dedupe + clean EOF
|
||
if [[ -s "$SSH_KEYS_FILE" ]]; then
|
||
sort -u -o "$SSH_KEYS_FILE" "$SSH_KEYS_FILE"
|
||
printf '\n' >>"$SSH_KEYS_FILE"
|
||
fi
|
||
|
||
# SSH activate, if keys found or password set
|
||
if [[ -s "$SSH_KEYS_FILE" || "$PW" == -password* ]]; then
|
||
if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --defaultno --title "SSH ACCESS" --yesno "Enable root SSH access?" 10 58); then
|
||
SSH="yes"
|
||
else
|
||
SSH="no"
|
||
fi
|
||
else
|
||
SSH="no"
|
||
fi
|
||
echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}"
|
||
|
||
export SSH_KEYS_FILE
|
||
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"
|
||
else
|
||
VERBOSE="no"
|
||
fi
|
||
echo -e "${SEARCH}${BOLD}${DGN}Verbose Mode: ${BGN}$VERBOSE${CL}"
|
||
|
||
if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "ADVANCED SETTINGS COMPLETE" --yesno "Ready to create ${APP} LXC?" 10 58); then
|
||
echo -e "${CREATING}${BOLD}${RD}Creating a ${APP} LXC using the above advanced settings${CL}"
|
||
else
|
||
clear
|
||
header_info
|
||
echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings on node $PVEHOST_NAME${CL}"
|
||
advanced_settings
|
||
fi
|
||
}
|
||
|
||
diagnostics_check() {
|
||
if ! [ -d "/usr/local/community-scripts" ]; then
|
||
mkdir -p /usr/local/community-scripts
|
||
fi
|
||
|
||
if ! [ -f "/usr/local/community-scripts/diagnostics" ]; then
|
||
if (whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS" --yesno "Send Diagnostics of LXC Installation?\n\n(This only transmits data without user data, just RAM, CPU, LXC name, ...)" 10 58); then
|
||
cat <<EOF >/usr/local/community-scripts/diagnostics
|
||
DIAGNOSTICS=yes
|
||
|
||
#This file is used to store the diagnostics settings for the Community-Scripts API.
|
||
#https://github.com/community-scripts/ProxmoxVED/discussions/1836
|
||
#Your diagnostics will be sent to the Community-Scripts API for troubleshooting/statistical purposes.
|
||
#You can review the data at https://community-scripts.github.io/ProxmoxVE/data
|
||
#If you do not wish to send diagnostics, please set the variable 'DIAGNOSTICS' to "no" in /usr/local/community-scripts/diagnostics, or use the menue.
|
||
#This will disable the diagnostics feature.
|
||
#To send diagnostics, set the variable 'DIAGNOSTICS' to "yes" in /usr/local/community-scripts/diagnostics, or use the menue.
|
||
#This will enable the diagnostics feature.
|
||
#The following information will be sent:
|
||
#"ct_type"
|
||
#"disk_size"
|
||
#"core_count"
|
||
#"ram_size"
|
||
#"os_type"
|
||
#"os_version"
|
||
#"disableip6"
|
||
#"nsapp"
|
||
#"method"
|
||
#"pve_version"
|
||
#"status"
|
||
#If you have any concerns, please review the source code at /misc/build.func
|
||
EOF
|
||
DIAGNOSTICS="yes"
|
||
else
|
||
cat <<EOF >/usr/local/community-scripts/diagnostics
|
||
DIAGNOSTICS=no
|
||
|
||
#This file is used to store the diagnostics settings for the Community-Scripts API.
|
||
#https://github.com/community-scripts/ProxmoxVED/discussions/1836
|
||
#Your diagnostics will be sent to the Community-Scripts API for troubleshooting/statistical purposes.
|
||
#You can review the data at https://community-scripts.github.io/ProxmoxVE/data
|
||
#If you do not wish to send diagnostics, please set the variable 'DIAGNOSTICS' to "no" in /usr/local/community-scripts/diagnostics, or use the menue.
|
||
#This will disable the diagnostics feature.
|
||
#To send diagnostics, set the variable 'DIAGNOSTICS' to "yes" in /usr/local/community-scripts/diagnostics, or use the menue.
|
||
#This will enable the diagnostics feature.
|
||
#The following information will be sent:
|
||
#"ct_type"
|
||
#"disk_size"
|
||
#"core_count"
|
||
#"ram_size"
|
||
#"os_type"
|
||
#"os_version"
|
||
#"disableip6"
|
||
#"nsapp"
|
||
#"method"
|
||
#"pve_version"
|
||
#"status"
|
||
#If you have any concerns, please review the source code at /misc/build.func
|
||
EOF
|
||
DIAGNOSTICS="no"
|
||
fi
|
||
else
|
||
DIAGNOSTICS=$(awk -F '=' '/^DIAGNOSTICS/ {print $2}' /usr/local/community-scripts/diagnostics)
|
||
|
||
fi
|
||
|
||
}
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# default_var_settings
|
||
#
|
||
# - Ensures /usr/local/community-scripts/default.vars exists (creates if missing)
|
||
# - Loads var_* values from default.vars (safe parser, no source/eval)
|
||
# - Precedence: ENV var_* > default.vars > built-in defaults
|
||
# - Maps var_verbose → VERBOSE
|
||
# - Calls base_settings "$VERBOSE" and echo_default
|
||
# ------------------------------------------------------------------------------
|
||
default_var_settings() {
|
||
# Allowed var_* keys (alphabetically sorted)
|
||
local VAR_WHITELIST=(
|
||
var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_ctid var_disk var_fuse
|
||
var_gateway var_hostname var_ipv6_method var_ipv6_static var_mac var_mtu
|
||
var_net var_ns var_pw var_ram var_tags var_tun var_unprivileged
|
||
var_verbose var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage
|
||
)
|
||
|
||
# Snapshot: environment variables (highest precedence)
|
||
declare -A _HARD_ENV=()
|
||
local _k
|
||
for _k in "${VAR_WHITELIST[@]}"; do
|
||
if printenv "$_k" >/dev/null 2>&1; then _HARD_ENV["$_k"]=1; fi
|
||
done
|
||
|
||
# Find default.vars (first valid path wins)
|
||
local _find_default_vars
|
||
_find_default_vars() {
|
||
local f
|
||
for f in \
|
||
/usr/local/community-scripts/default.vars \
|
||
"$HOME/.config/community-scripts/default.vars" \
|
||
"./default.vars"; do
|
||
[ -f "$f" ] && {
|
||
echo "$f"
|
||
return 0
|
||
}
|
||
done
|
||
return 1
|
||
}
|
||
|
||
# Ensure default.vars exists, create with sane defaults if missing
|
||
local _ensure_default_vars
|
||
_ensure_default_vars() {
|
||
_find_default_vars >/dev/null 2>&1 && return 0
|
||
local canonical="/usr/local/community-scripts/default.vars"
|
||
msg_info "No default.vars found. Creating ${canonical}"
|
||
mkdir -p /usr/local/community-scripts
|
||
cat >"$canonical" <<'EOF'
|
||
# Community-Scripts defaults (var_* only). Lines starting with # are comments.
|
||
# Precedence: ENV var_* > default.vars > built-ins.
|
||
# Keep keys alphabetically sorted.
|
||
|
||
# Container type
|
||
var_unprivileged=1
|
||
|
||
# Storage
|
||
# Example: "local", "docker", ...
|
||
# var_template_storage=local
|
||
# var_container_storage=local
|
||
|
||
# Resources
|
||
var_cpu=1
|
||
var_disk=4
|
||
var_ram=1024
|
||
|
||
# Network
|
||
var_brg=vmbr0
|
||
var_net=dhcp
|
||
var_ipv6_method=none
|
||
# var_gateway=
|
||
# var_ipv6_static=
|
||
# var_vlan=
|
||
# var_mtu=
|
||
# var_mac=
|
||
# var_ns=
|
||
|
||
# SSH
|
||
var_ssh=no
|
||
# var_ssh_authorized_key=
|
||
|
||
# APT cacher (optional)
|
||
# var_apt_cacher=yes
|
||
# var_apt_cacher_ip=192.168.1.10
|
||
|
||
# Features/Tags/verbosity
|
||
var_fuse=no
|
||
var_tun=no
|
||
var_tags=community-script
|
||
var_verbose=no
|
||
|
||
# Security (root PW) – empty => autologin
|
||
# var_pw=
|
||
|
||
# Optional fixed CTID/hostname – empty => auto
|
||
# var_ctid=
|
||
# var_hostname=
|
||
EOF
|
||
chmod 0644 "$canonical"
|
||
msg_ok "Created ${canonical}"
|
||
}
|
||
|
||
# Whitelist check
|
||
local _is_whitelisted_key
|
||
_is_whitelisted_key() {
|
||
local k="$1"
|
||
local w
|
||
for w in "${VAR_WHITELIST[@]}"; do [ "$k" = "$w" ] && return 0; done
|
||
return 1
|
||
}
|
||
|
||
# Safe parser for KEY=VALUE lines
|
||
local _load_vars_file
|
||
_load_vars_file() {
|
||
local file="$1"
|
||
[ -f "$file" ] || return 0
|
||
msg_info "Loading defaults from ${file}"
|
||
local line key val
|
||
while IFS= read -r line || [ -n "$line" ]; do
|
||
line="${line#"${line%%[![:space:]]*}"}"
|
||
line="${line%"${line##*[![:space:]]}"}"
|
||
[[ -z "$line" || "$line" == \#* ]] && continue
|
||
if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]]; then
|
||
local var_key="${BASH_REMATCH[1]}"
|
||
local var_val="${BASH_REMATCH[2]}"
|
||
|
||
[[ "$var_key" != var_* ]] && continue
|
||
_is_whitelisted_key "$var_key" || {
|
||
msg_debug "Ignore non-whitelisted ${var_key}"
|
||
continue
|
||
}
|
||
|
||
# Strip quotes
|
||
if [[ "$var_val" =~ ^\"(.*)\"$ ]]; then
|
||
var_val="${BASH_REMATCH[1]}"
|
||
elif [[ "$var_val" =~ ^\'(.*)\'$ ]]; then
|
||
var_val="${BASH_REMATCH[1]}"
|
||
fi
|
||
|
||
# Unsafe check without regex (formatter-friendly)
|
||
local _unsafe=""
|
||
case "$var_val" in
|
||
*'$('*) _unsafe=1 ;;
|
||
*'`'*) _unsafe=1 ;;
|
||
*';'*) _unsafe=1 ;;
|
||
*'&'*) _unsafe=1 ;;
|
||
*'<('*) _unsafe=1 ;;
|
||
esac
|
||
if [[ -n "$_unsafe" ]]; then
|
||
msg_warn "Ignoring ${var_key} from ${file}: unsafe characters"
|
||
continue
|
||
fi
|
||
|
||
# Hard env wins
|
||
if [[ -n "${_HARD_ENV[$var_key]:-}" ]]; then
|
||
continue
|
||
fi
|
||
|
||
# Set only if not already exported
|
||
if [[ -z "${!var_key+x}" ]]; then
|
||
export "${var_key}=${var_val}"
|
||
fi
|
||
|
||
else
|
||
msg_warn "Malformed line in ${file}: ${line}"
|
||
fi
|
||
|
||
done <"$file"
|
||
msg_ok "Loaded ${file}"
|
||
}
|
||
|
||
# 1) Ensure file exists
|
||
_ensure_default_vars
|
||
|
||
# 2) Load file
|
||
local dv
|
||
dv="$(_find_default_vars)" || {
|
||
msg_error "default.vars not found after ensure step"
|
||
return 1
|
||
}
|
||
_load_vars_file "$dv"
|
||
|
||
# 3) Map var_verbose → VERBOSE
|
||
if [[ -n "${var_verbose:-}" ]]; then
|
||
case "${var_verbose,,}" in
|
||
1 | yes | true | on) VERBOSE="yes" ;;
|
||
0 | no | false | off) VERBOSE="no" ;;
|
||
*) VERBOSE="${var_verbose}" ;;
|
||
esac
|
||
else
|
||
VERBOSE="no"
|
||
fi
|
||
|
||
# 4) Apply base settings and show summary
|
||
METHOD="mydefaults-global"
|
||
base_settings "$VERBOSE"
|
||
header_info
|
||
echo -e "${DEFAULT}${BOLD}${BL}Using My Defaults (default.vars) on node $PVEHOST_NAME${CL}"
|
||
echo_default
|
||
}
|
||
|
||
get_app_defaults_path() {
|
||
local n="${NSAPP:-${APP,,}}"
|
||
echo "/usr/local/community-scripts/defaults/${n}.vars"
|
||
}
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# maybe_offer_save_app_defaults
|
||
#
|
||
# - Called after advanced_settings returned with fully chosen values.
|
||
# - If no <nsapp>.vars exists, offers to persist current advanced settings
|
||
# into /usr/local/community-scripts/defaults/<nsapp>.vars
|
||
# - Only writes whitelisted var_* keys.
|
||
# - Extracts raw values from flags like ",gw=..." ",mtu=..." etc.
|
||
# ------------------------------------------------------------------------------
|
||
if ! declare -p VAR_WHITELIST >/dev/null 2>&1; then
|
||
declare -ag VAR_WHITELIST=(
|
||
var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_ctid var_disk var_fuse
|
||
var_gateway var_hostname var_ipv6_method var_ipv6_static var_mac var_mtu
|
||
var_net var_ns var_pw var_ram var_tags var_tun var_unprivileged
|
||
var_verbose var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage
|
||
)
|
||
fi
|
||
|
||
_is_whitelisted_key() {
|
||
local k="$1"
|
||
local w
|
||
for w in "${VAR_WHITELIST[@]}"; do [[ "$k" == "$w" ]] && return 0; done
|
||
return 1
|
||
}
|
||
|
||
_sanitize_value() {
|
||
# Disallow Command-Substitution / Shell-Meta
|
||
case "$1" in
|
||
*'$('* | *'`'* | *';'* | *'&'* | *'<('*)
|
||
echo ""
|
||
return 0
|
||
;;
|
||
esac
|
||
echo "$1"
|
||
}
|
||
|
||
# Map-Parser: liest var_* aus Datei in eine assoziative Map (Name hart: _VARS_IN)
|
||
declare -A _VARS_IN
|
||
_load_vars_file_to_map() {
|
||
local file="$1"
|
||
_VARS_IN=()
|
||
[[ -f "$file" ]] || return 0
|
||
local line
|
||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||
line="${line#"${line%%[![:space:]]*}"}"
|
||
line="${line%"${line##*[![:space:]]}"}"
|
||
[[ -z "$line" || "$line" == \#* ]] && continue
|
||
if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]]; then
|
||
local k="${BASH_REMATCH[1]}"
|
||
local v="${BASH_REMATCH[2]}"
|
||
[[ "$k" == var_* ]] || continue
|
||
_is_whitelisted_key "$k" || continue
|
||
# Quotes strippen
|
||
if [[ "$v" =~ ^\"(.*)\"$ ]]; then v="${BASH_REMATCH[1]}"; fi
|
||
if [[ "$v" =~ ^\'(.*)\'$ ]]; then v="${BASH_REMATCH[1]}"; fi
|
||
_VARS_IN["$k"]="$v"
|
||
fi
|
||
done <"$file"
|
||
}
|
||
|
||
# Diff zweier Dateien mit var_* → gibt in $1 (old) vs $2 (new) eine menschenlesbare Diff-Liste aus
|
||
_build_vars_diff() {
|
||
local oldf="$1" newf="$2"
|
||
local k
|
||
local -A OLD=() NEW=()
|
||
_load_vars_file_to_map "$oldf"
|
||
for k in "${!_VARS_IN[@]}"; do OLD["$k"]="${_VARS_IN[$k]}"; done
|
||
_load_vars_file_to_map "$newf"
|
||
for k in "${!_VARS_IN[@]}"; do NEW["$k"]="${_VARS_IN[$k]}"; done
|
||
|
||
local out
|
||
out+="# Diff for ${APP} (${NSAPP})\n"
|
||
out+="# Old: ${oldf}\n# New: ${newf}\n\n"
|
||
|
||
local found_change=0
|
||
|
||
# Changed & Removed
|
||
for k in "${!OLD[@]}"; do
|
||
if [[ -v NEW["$k"] ]]; then
|
||
if [[ "${OLD[$k]}" != "${NEW[$k]}" ]]; then
|
||
out+="~ ${k}\n - old: ${OLD[$k]}\n + new: ${NEW[$k]}\n"
|
||
found_change=1
|
||
fi
|
||
else
|
||
out+="- ${k}\n - old: ${OLD[$k]}\n"
|
||
found_change=1
|
||
fi
|
||
done
|
||
|
||
# Added
|
||
for k in "${!NEW[@]}"; do
|
||
if [[ ! -v OLD["$k"] ]]; then
|
||
out+="+ ${k}\n + new: ${NEW[$k]}\n"
|
||
found_change=1
|
||
fi
|
||
done
|
||
|
||
if [[ $found_change -eq 0 ]]; then
|
||
out+="(No differences)\n"
|
||
fi
|
||
|
||
printf "%b" "$out"
|
||
}
|
||
|
||
# Build a temporary <app>.vars file from current advanced settings
|
||
_build_current_app_vars_tmp() {
|
||
tmpf="$(mktemp /tmp/${NSAPP:-app}.vars.new.XXXXXX)"
|
||
|
||
# NET/GW
|
||
_net="${NET:-}"
|
||
_gate=""
|
||
case "${GATE:-}" in
|
||
,gw=*) _gate=$(echo "$GATE" | sed 's/^,gw=//') ;;
|
||
esac
|
||
|
||
# IPv6
|
||
_ipv6_method="${IPV6_METHOD:-auto}"
|
||
_ipv6_static=""
|
||
_ipv6_gateway=""
|
||
if [ "$_ipv6_method" = "static" ]; then
|
||
_ipv6_static="${IPV6_ADDR:-}"
|
||
_ipv6_gateway="${IPV6_GATE:-}"
|
||
fi
|
||
|
||
# MTU/VLAN/MAC
|
||
_mtu=""
|
||
_vlan=""
|
||
_mac=""
|
||
case "${MTU:-}" in
|
||
,mtu=*) _mtu=$(echo "$MTU" | sed 's/^,mtu=//') ;;
|
||
esac
|
||
case "${VLAN:-}" in
|
||
,tag=*) _vlan=$(echo "$VLAN" | sed 's/^,tag=//') ;;
|
||
esac
|
||
case "${MAC:-}" in
|
||
,hwaddr=*) _mac=$(echo "$MAC" | sed 's/^,hwaddr=//') ;;
|
||
esac
|
||
|
||
# DNS / Searchdomain
|
||
_ns=""
|
||
_searchdomain=""
|
||
case "${NS:-}" in
|
||
-nameserver=*) _ns=$(echo "$NS" | sed 's/^-nameserver=//') ;;
|
||
esac
|
||
case "${SD:-}" in
|
||
-searchdomain=*) _searchdomain=$(echo "$SD" | sed 's/^-searchdomain=//') ;;
|
||
esac
|
||
|
||
# SSH / APT / Features
|
||
_ssh="${SSH:-no}"
|
||
_ssh_auth="${SSH_AUTHORIZED_KEY:-}"
|
||
_apt_cacher="${APT_CACHER:-}"
|
||
_apt_cacher_ip="${APT_CACHER_IP:-}"
|
||
_fuse="${ENABLE_FUSE:-no}"
|
||
_tun="${ENABLE_TUN:-no}"
|
||
_tags="${TAGS:-}"
|
||
_verbose="${VERBOSE:-no}"
|
||
|
||
# Type / Resources / Identity
|
||
_unpriv="${CT_TYPE:-1}"
|
||
_cpu="${CORE_COUNT:-1}"
|
||
_ram="${RAM_SIZE:-1024}"
|
||
_disk="${DISK_SIZE:-4}"
|
||
_hostname="${HN:-$NSAPP}"
|
||
|
||
# Storage
|
||
_tpl_storage="${TEMPLATE_STORAGE:-}"
|
||
_ct_storage="${CONTAINER_STORAGE:-}"
|
||
|
||
{
|
||
echo "# App-specific defaults for ${APP} (${NSAPP})"
|
||
echo "# Generated on $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||
echo
|
||
|
||
echo "var_unprivileged=$(_sanitize_value "$_unpriv")"
|
||
echo "var_cpu=$(_sanitize_value "$_cpu")"
|
||
echo "var_ram=$(_sanitize_value "$_ram")"
|
||
echo "var_disk=$(_sanitize_value "$_disk")"
|
||
|
||
[ -n "${BRG:-}" ] && echo "var_brg=$(_sanitize_value "$BRG")"
|
||
[ -n "$_net" ] && echo "var_net=$(_sanitize_value "$_net")"
|
||
[ -n "$_gate" ] && echo "var_gateway=$(_sanitize_value "$_gate")"
|
||
[ -n "$_mtu" ] && echo "var_mtu=$(_sanitize_value "$_mtu")"
|
||
[ -n "$_vlan" ] && echo "var_vlan=$(_sanitize_value "$_vlan")"
|
||
[ -n "$_mac" ] && echo "var_mac=$(_sanitize_value "$_mac")"
|
||
[ -n "$_ns" ] && echo "var_ns=$(_sanitize_value "$_ns")"
|
||
|
||
[ -n "$_ipv6_method" ] && echo "var_ipv6_method=$(_sanitize_value "$_ipv6_method")"
|
||
[ -n "$_ipv6_static" ] && echo "var_ipv6_static=$(_sanitize_value "$_ipv6_static")"
|
||
|
||
[ -n "$_ssh" ] && echo "var_ssh=$(_sanitize_value "$_ssh")"
|
||
[ -n "$_ssh_auth" ] && echo "var_ssh_authorized_key=$(_sanitize_value "$_ssh_auth")"
|
||
|
||
[ -n "$_apt_cacher" ] && echo "var_apt_cacher=$(_sanitize_value "$_apt_cacher")"
|
||
[ -n "$_apt_cacher_ip" ] && echo "var_apt_cacher_ip=$(_sanitize_value "$_apt_cacher_ip")"
|
||
|
||
[ -n "$_fuse" ] && echo "var_fuse=$(_sanitize_value "$_fuse")"
|
||
[ -n "$_tun" ] && echo "var_tun=$(_sanitize_value "$_tun")"
|
||
[ -n "$_tags" ] && echo "var_tags=$(_sanitize_value "$_tags")"
|
||
[ -n "$_verbose" ] && echo "var_verbose=$(_sanitize_value "$_verbose")"
|
||
|
||
[ -n "$_hostname" ] && echo "var_hostname=$(_sanitize_value "$_hostname")"
|
||
[ -n "$_searchdomain" ] && echo "var_searchdomain=$(_sanitize_value "$_searchdomain")"
|
||
|
||
[ -n "$_tpl_storage" ] && echo "var_template_storage=$(_sanitize_value "$_tpl_storage")"
|
||
[ -n "$_ct_storage" ] && echo "var_container_storage=$(_sanitize_value "$_ct_storage")"
|
||
} >"$tmpf"
|
||
|
||
echo "$tmpf"
|
||
}
|
||
|
||
# -----------------------------------------------
|
||
# maybe_offer_save_app_defaults (Create/Update)
|
||
# - UPDATE-Pfad mit Diff & Menü (Update = Default)
|
||
# -----------------------------------------------
|
||
maybe_offer_save_app_defaults() {
|
||
local app_vars_path
|
||
app_vars_path="$(get_app_defaults_path)"
|
||
|
||
# Immer Kandidat aus aktueller Auswahl bauen
|
||
local new_tmp diff_tmp
|
||
new_tmp="$(_build_current_app_vars_tmp)"
|
||
diff_tmp="$(mktemp -p /tmp "${NSAPP:-app}.vars.diff.XXXXXX")"
|
||
|
||
# 1) Wenn Datei noch nicht existiert → Erstellen wie bisher
|
||
if [[ ! -f "$app_vars_path" ]]; then
|
||
if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \
|
||
--yesno "Save these advanced settings as defaults for ${APP}?\n\nThis will create:\n${app_vars_path}" 12 72; then
|
||
mkdir -p "$(dirname "$app_vars_path")"
|
||
install -m 0644 "$new_tmp" "$app_vars_path"
|
||
msg_ok "Saved app defaults: ${app_vars_path}"
|
||
fi
|
||
rm -f "$new_tmp" "$diff_tmp"
|
||
return 0
|
||
fi
|
||
|
||
# 2) Datei existiert → Diff bauen
|
||
_build_vars_diff "$app_vars_path" "$new_tmp" >"$diff_tmp"
|
||
|
||
# Wenn kein Unterschied → nichts tun
|
||
if grep -q "^(No differences)$" "$diff_tmp"; then
|
||
rm -f "$new_tmp" "$diff_tmp"
|
||
return 0
|
||
fi
|
||
|
||
# 3) Menü mit Default-Auswahl "Update Defaults"
|
||
while true; do
|
||
local sel
|
||
sel="$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \
|
||
--title "APP DEFAULTS – ${APP}" \
|
||
--menu "Differences detected. What do you want to do?" 20 78 10 \
|
||
"Update Defaults" "Write new values to ${app_vars_path}" \
|
||
"Keep Current" "Keep existing defaults (no changes)" \
|
||
"View Diff" "Show a detailed diff" \
|
||
"Cancel" "Abort without changes" \
|
||
--default-item "Update Defaults" \
|
||
3>&1 1>&2 2>&3)" || { sel="Cancel"; }
|
||
|
||
case "$sel" in
|
||
"Update Defaults")
|
||
install -m 0644 "$new_tmp" "$app_vars_path"
|
||
msg_ok "Updated app defaults: ${app_vars_path}"
|
||
break
|
||
;;
|
||
"Keep Current")
|
||
msg_info "Keeping current app defaults: ${app_vars_path}"
|
||
break
|
||
;;
|
||
"View Diff")
|
||
whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \
|
||
--title "Diff – ${APP}" \
|
||
--scrolltext --textbox "$diff_tmp" 25 100
|
||
;;
|
||
"Cancel" | *)
|
||
msg_info "Canceled. No changes to app defaults."
|
||
break
|
||
;;
|
||
esac
|
||
done
|
||
|
||
rm -f "$new_tmp" "$diff_tmp"
|
||
}
|
||
|
||
install_script() {
|
||
pve_check
|
||
shell_check
|
||
root_check
|
||
arch_check
|
||
ssh_check
|
||
maxkeys_check
|
||
diagnostics_check
|
||
|
||
if systemctl is-active -q ping-instances.service; then
|
||
systemctl -q stop ping-instances.service
|
||
fi
|
||
|
||
NEXTID=$(pvesh get /cluster/nextid)
|
||
timezone=$(cat /etc/timezone)
|
||
header_info
|
||
|
||
# --- PRESET support ---
|
||
if [ -n "${PRESET:-}" ]; then
|
||
case "$PRESET" in
|
||
DEFAULT | default | 1)
|
||
CHOICE="1"
|
||
;;
|
||
VERBOSE | verbose | 2)
|
||
CHOICE="2"
|
||
;;
|
||
ADVANCED | advanced | 3)
|
||
CHOICE="3"
|
||
;;
|
||
MYDEFAULTS | mydefaults | 4)
|
||
CHOICE="4"
|
||
;;
|
||
APPDEFAULTS | appdefaults | 5)
|
||
CHOICE="5"
|
||
;;
|
||
DIAGNOSTICS | diagnostics | 6)
|
||
CHOICE="6"
|
||
;;
|
||
STORAGE | storage | 7)
|
||
CHOICE="7"
|
||
;;
|
||
*)
|
||
echo -e "\n${CROSS}${RD}Invalid PRESET value: ${PRESET}${CL}\n"
|
||
exit 1
|
||
;;
|
||
esac
|
||
else
|
||
# Build dynamic menu
|
||
local menu_items=(
|
||
"1" "Default Settings"
|
||
"2" "Default Settings (Verbose)"
|
||
"3" "Advanced Install"
|
||
"4" "My Defaults"
|
||
)
|
||
if [ -f "$(get_app_defaults_path)" ]; then
|
||
menu_items+=("5" "App Defaults for ${APP}")
|
||
fi
|
||
menu_items+=(
|
||
"6" "Diagnostic Settings"
|
||
"7" "Storage Settings"
|
||
"8" "Exit"
|
||
)
|
||
|
||
TMP_CHOICE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \
|
||
--title "SETTINGS" \
|
||
--menu "Choose an option:" 20 60 9 \
|
||
"${menu_items[@]}" \
|
||
--default-item "1" 3>&1 1>&2 2>&3) || true
|
||
|
||
if [ -z "$TMP_CHOICE" ]; then
|
||
echo -e "\n${CROSS}${RD}Menu canceled. Exiting script.${CL}\n"
|
||
exit 0
|
||
fi
|
||
CHOICE="$TMP_CHOICE"
|
||
fi
|
||
|
||
case $CHOICE in
|
||
1)
|
||
header_info
|
||
echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings on node $PVEHOST_NAME${CL}"
|
||
VERBOSE="no"
|
||
METHOD="default"
|
||
base_settings "$VERBOSE"
|
||
echo_default
|
||
;;
|
||
2)
|
||
header_info
|
||
echo -e "${DEFAULT}${BOLD}${BL}Using Default Settings on node $PVEHOST_NAME (Verbose)${CL}"
|
||
VERBOSE="yes"
|
||
METHOD="default"
|
||
base_settings "$VERBOSE"
|
||
echo_default
|
||
;;
|
||
3)
|
||
header_info
|
||
echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Install on node $PVEHOST_NAME${CL}"
|
||
METHOD="advanced"
|
||
base_settings
|
||
advanced_settings
|
||
maybe_offer_save_app_defaults
|
||
;;
|
||
4)
|
||
default_var_settings || {
|
||
msg_error "Failed to apply default.vars"
|
||
exit 1
|
||
}
|
||
;;
|
||
5)
|
||
# App Defaults
|
||
if [ -f "$(get_app_defaults_path)" ]; then
|
||
header_info
|
||
echo -e "${DEFAULT}${BOLD}${BL}Using App Defaults for ${APP} on node $PVEHOST_NAME${CL}"
|
||
METHOD="appdefaults"
|
||
base_settings
|
||
_load_vars_file "$(get_app_defaults_path)"
|
||
echo_default
|
||
else
|
||
msg_error "No App Defaults available for ${APP}"
|
||
exit 1
|
||
fi
|
||
;;
|
||
6)
|
||
if [[ $DIAGNOSTICS == "yes" ]]; then
|
||
if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTIC SETTINGS" --yesno "Send Diagnostics?\n\nCurrent: ${DIAGNOSTICS}" 10 58 \
|
||
--yes-button "No" --no-button "Back"; then
|
||
DIAGNOSTICS="no"
|
||
sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=no/' /usr/local/community-scripts/diagnostics
|
||
whiptail --msgbox "Diagnostics set to ${DIAGNOSTICS}." 8 58
|
||
fi
|
||
else
|
||
if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTIC SETTINGS" --yesno "Send Diagnostics?\n\nCurrent: ${DIAGNOSTICS}" 10 58 \
|
||
--yes-button "Yes" --no-button "Back"; then
|
||
DIAGNOSTICS="yes"
|
||
sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=yes/' /usr/local/community-scripts/diagnostics
|
||
whiptail --msgbox "Diagnostics set to ${DIAGNOSTICS}." 8 58
|
||
fi
|
||
fi
|
||
;;
|
||
7)
|
||
storage_settings_menu
|
||
;;
|
||
8)
|
||
echo -e "\n${CROSS}${RD}Script terminated.${CL}\n"
|
||
exit 0
|
||
;;
|
||
*)
|
||
echo -e "${CROSS}${RD}Invalid option, please try again.${CL}"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# Check and prompt for storage if missing or invalid
|
||
check_storage_or_prompt() {
|
||
local vars_file="$1"
|
||
local changed=0
|
||
|
||
if [[ ! -f "$vars_file" ]]; then
|
||
msg_warn "No vars file found at $vars_file"
|
||
return 1
|
||
fi
|
||
|
||
# Helper: validate storage
|
||
_validate_storage() {
|
||
local s="$1"
|
||
[[ -n "$s" ]] || return 1
|
||
pvesm status -content images | awk 'NR>1 {print $1}' | grep -qx "$s"
|
||
}
|
||
|
||
# Load current values
|
||
local ct_store tpl_store
|
||
ct_store="$(grep -E '^var_container_storage=' "$vars_file" | cut -d= -f2-)"
|
||
tpl_store="$(grep -E '^var_template_storage=' "$vars_file" | cut -d= -f2-)"
|
||
|
||
# Container storage
|
||
if ! _validate_storage "$ct_store"; then
|
||
local new_ct
|
||
new_ct=$(pvesm status -content images | awk 'NR>1 {print $1" "$2" "$6}')
|
||
new_ct=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \
|
||
--title "Select Container Storage" \
|
||
--menu "Choose container storage:" 20 60 10 $new_ct 3>&1 1>&2 2>&3) || return 1
|
||
if [[ -n "$new_ct" ]]; then
|
||
sed -i "/^var_container_storage=/d" "$vars_file"
|
||
echo "var_container_storage=$new_ct" >>"$vars_file"
|
||
changed=1
|
||
msg_ok "Updated container storage in $vars_file → $new_ct"
|
||
fi
|
||
fi
|
||
|
||
# Template storage
|
||
if ! _validate_storage "$tpl_store"; then
|
||
local new_tpl
|
||
new_tpl=$(pvesm status -content vztmpl | awk 'NR>1 {print $1" "$2" "$6}')
|
||
new_tpl=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \
|
||
--title "Select Template Storage" \
|
||
--menu "Choose template storage:" 20 60 10 $new_tpl 3>&1 1>&2 2>&3) || return 1
|
||
if [[ -n "$new_tpl" ]]; then
|
||
sed -i "/^var_template_storage=/d" "$vars_file"
|
||
echo "var_template_storage=$new_tpl" >>"$vars_file"
|
||
changed=1
|
||
msg_ok "Updated template storage in $vars_file → $new_tpl"
|
||
fi
|
||
fi
|
||
|
||
return $changed
|
||
}
|
||
|
||
# Storage Settings menu
|
||
storage_settings_menu() {
|
||
local menu_items=(
|
||
"1" "Check & update My Defaults (default.vars)"
|
||
)
|
||
if [ -f "$(get_app_defaults_path)" ]; then
|
||
menu_items+=("2" "Check & update App Defaults for ${APP}")
|
||
fi
|
||
menu_items+=("3" "Back")
|
||
|
||
local choice
|
||
choice=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \
|
||
--title "STORAGE SETTINGS" \
|
||
--menu "Select what to update:" 15 60 5 \
|
||
"${menu_items[@]}" 3>&1 1>&2 2>&3) || return 0
|
||
|
||
case "$choice" in
|
||
1)
|
||
check_storage_or_prompt "/usr/local/community-scripts/default.vars"
|
||
;;
|
||
2)
|
||
if [ -f "$(get_app_defaults_path)" ]; then
|
||
check_storage_or_prompt "$(get_app_defaults_path)"
|
||
fi
|
||
;;
|
||
3)
|
||
return 0
|
||
;;
|
||
esac
|
||
}
|
||
|
||
check_container_resources() {
|
||
# Check actual RAM & Cores
|
||
current_ram=$(free -m | awk 'NR==2{print $2}')
|
||
current_cpu=$(nproc)
|
||
|
||
# Check whether the current RAM is less than the required RAM or the CPU cores are less than required
|
||
if [[ "$current_ram" -lt "$var_ram" ]] || [[ "$current_cpu" -lt "$var_cpu" ]]; then
|
||
echo -e "\n${INFO}${HOLD} ${GN}Required: ${var_cpu} CPU, ${var_ram}MB RAM ${CL}| ${RD}Current: ${current_cpu} CPU, ${current_ram}MB RAM${CL}"
|
||
echo -e "${YWB}Please ensure that the ${APP} LXC is configured with at least ${var_cpu} vCPU and ${var_ram} MB RAM for the build process.${CL}\n"
|
||
echo -ne "${INFO}${HOLD} May cause data loss! ${INFO} Continue update with under-provisioned LXC? <yes/No> "
|
||
read -r prompt
|
||
# Check if the input is 'yes', otherwise exit with status 1
|
||
if [[ ! ${prompt,,} =~ ^(yes)$ ]]; then
|
||
echo -e "${CROSS}${HOLD} ${YWB}Exiting based on user input.${CL}"
|
||
exit 1
|
||
fi
|
||
else
|
||
echo -e ""
|
||
fi
|
||
}
|
||
|
||
check_container_storage() {
|
||
# Check if the /boot partition is more than 80% full
|
||
total_size=$(df /boot --output=size | tail -n 1)
|
||
local used_size=$(df /boot --output=used | tail -n 1)
|
||
usage=$((100 * used_size / total_size))
|
||
if ((usage > 80)); then
|
||
# Prompt the user for confirmation to continue
|
||
echo -e "${INFO}${HOLD} ${YWB}Warning: Storage is dangerously low (${usage}%).${CL}"
|
||
echo -ne "Continue anyway? <y/N> "
|
||
read -r prompt
|
||
# Check if the input is 'y' or 'yes', otherwise exit with status 1
|
||
if [[ ! ${prompt,,} =~ ^(y|yes)$ ]]; then
|
||
echo -e "${CROSS}${HOLD}${YWB}Exiting based on user input.${CL}"
|
||
exit 1
|
||
fi
|
||
fi
|
||
}
|
||
|
||
ssh_extract_keys_from_file() {
|
||
local f="$1"
|
||
[[ -r "$f" ]] || return 0
|
||
tr -d '\r' <"$f" | awk '
|
||
/^[[:space:]]*#/ {next}
|
||
/^[[:space:]]*$/ {next}
|
||
# nackt: typ base64 [comment]
|
||
/^(ssh-(rsa|ed25519)|ecdsa-sha2-nistp256|sk-(ssh-ed25519|ecdsa-sha2-nistp256))[[:space:]]+/ {print; next}
|
||
# mit Optionen: finde ab erstem Key-Typ
|
||
{
|
||
match($0, /(ssh-(rsa|ed25519)|ecdsa-sha2-nistp256|sk-(ssh-ed25519|ecdsa-sha2-nistp256))[[:space:]]+/)
|
||
if (RSTART>0) { print substr($0, RSTART) }
|
||
}
|
||
'
|
||
}
|
||
|
||
ssh_build_choices_from_files() {
|
||
local -a files=("$@")
|
||
CHOICES=()
|
||
COUNT=0
|
||
MAPFILE="$(mktemp)"
|
||
local id key typ fp cmt base ln=0
|
||
|
||
for f in "${files[@]}"; do
|
||
[[ -f "$f" && -r "$f" ]] || continue
|
||
base="$(basename -- "$f")"
|
||
case "$base" in
|
||
known_hosts | known_hosts.* | config) continue ;;
|
||
id_*) [[ "$f" != *.pub ]] && continue ;;
|
||
esac
|
||
|
||
# jede Key-Zeile mappen -> K<N>|<key>
|
||
while IFS= read -r key; do
|
||
[[ -n "$key" ]] || continue
|
||
|
||
# Fingerprint/Type/Comment hübsch machen (best effort)
|
||
typ=""
|
||
fp=""
|
||
cmt=""
|
||
# Nur der pure Key-Teil (ohne Optionen) ist schon in 'key' enthalten
|
||
read -r _typ _b64 _cmt <<<"$key"
|
||
typ="${_typ:-key}"
|
||
cmt="${_cmt:-}"
|
||
# Fingerprint via ssh-keygen (falls verfügbar)
|
||
if command -v ssh-keygen >/dev/null 2>&1; then
|
||
fp="$(printf '%s\n' "$key" | ssh-keygen -lf - 2>/dev/null | awk '{print $2}')"
|
||
fi
|
||
# Label kürzen
|
||
[[ ${#cmt} -gt 40 ]] && cmt="${cmt:0:37}..."
|
||
|
||
ln=$((ln + 1))
|
||
COUNT=$((COUNT + 1))
|
||
id="K${COUNT}"
|
||
echo "${id}|${key}" >>"$MAPFILE"
|
||
CHOICES+=("$id" "[$typ] ${fp:+$fp }${cmt:+$cmt }— ${base}" "OFF")
|
||
done < <(ssh_extract_keys_from_file "$f")
|
||
done
|
||
}
|
||
|
||
# Sucht Standard-Quellen (authorized_keys, *.pub, /etc/ssh/authorized_keys.d/*)
|
||
ssh_discover_default_files() {
|
||
local -a cand=()
|
||
shopt -s nullglob
|
||
cand+=(/root/.ssh/authorized_keys /root/.ssh/authorized_keys2)
|
||
cand+=(/root/.ssh/*.pub)
|
||
cand+=(/etc/ssh/authorized_keys /etc/ssh/authorized_keys.d/*)
|
||
shopt -u nullglob
|
||
printf '%s\0' "${cand[@]}"
|
||
}
|
||
|
||
start() {
|
||
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/tools.func)
|
||
if command -v pveversion >/dev/null 2>&1; then
|
||
install_script
|
||
elif [ ! -z ${PHS_SILENT+x} ] && [[ "${PHS_SILENT}" == "1" ]]; then
|
||
VERBOSE="no"
|
||
set_std_mode
|
||
update_script
|
||
else
|
||
CHOICE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "${APP} LXC Update/Setting" --menu \
|
||
"Support/Update functions for ${APP} LXC. Choose an option:" \
|
||
12 60 3 \
|
||
"1" "YES (Silent Mode)" \
|
||
"2" "YES (Verbose Mode)" \
|
||
"3" "NO (Cancel Update)" --nocancel --default-item "1" 3>&1 1>&2 2>&3)
|
||
|
||
case "$CHOICE" in
|
||
1)
|
||
VERBOSE="no"
|
||
set_std_mode
|
||
;;
|
||
2)
|
||
VERBOSE="yes"
|
||
set_std_mode
|
||
;;
|
||
3)
|
||
clear
|
||
exit_script
|
||
exit
|
||
;;
|
||
esac
|
||
update_script
|
||
fi
|
||
}
|
||
|
||
# This function collects user settings and integrates all the collected information.
|
||
build_container() {
|
||
# if [ "$VERBOSE" == "yes" ]; then set -x; fi
|
||
|
||
NET_STRING="-net0 name=eth0,bridge=$BRG$MAC,ip=$NET$GATE$VLAN$MTU"
|
||
case "$IPV6_METHOD" in
|
||
auto) NET_STRING="$NET_STRING,ip6=auto" ;;
|
||
dhcp) NET_STRING="$NET_STRING,ip6=dhcp" ;;
|
||
static)
|
||
NET_STRING="$NET_STRING,ip6=$IPV6_ADDR"
|
||
[ -n "$IPV6_GATE" ] && NET_STRING="$NET_STRING,gw6=$IPV6_GATE"
|
||
;;
|
||
none) ;;
|
||
esac
|
||
|
||
if [ "$CT_TYPE" == "1" ]; then
|
||
FEATURES="keyctl=1,nesting=1"
|
||
else
|
||
FEATURES="nesting=1"
|
||
fi
|
||
|
||
if [ "$ENABLE_FUSE" == "yes" ]; then
|
||
FEATURES="$FEATURES,fuse=1"
|
||
fi
|
||
|
||
#if [[ $DIAGNOSTICS == "yes" ]]; then
|
||
# post_to_api
|
||
#fi
|
||
|
||
TEMP_DIR=$(mktemp -d)
|
||
pushd "$TEMP_DIR" >/dev/null
|
||
if [ "$var_os" == "alpine" ]; then
|
||
export FUNCTIONS_FILE_PATH="$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/alpine-install.func)"
|
||
else
|
||
export FUNCTIONS_FILE_PATH="$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/install.func)"
|
||
fi
|
||
export DIAGNOSTICS="$DIAGNOSTICS"
|
||
export RANDOM_UUID="$RANDOM_UUID"
|
||
export CACHER="$APT_CACHER"
|
||
export CACHER_IP="$APT_CACHER_IP"
|
||
export tz="$timezone"
|
||
#export DISABLEIPV6="$DISABLEIP6"
|
||
export APPLICATION="$APP"
|
||
export app="$NSAPP"
|
||
export PASSWORD="$PW"
|
||
export VERBOSE="$VERBOSE"
|
||
export SSH_ROOT="${SSH}"
|
||
export SSH_AUTHORIZED_KEY
|
||
export CTID="$CT_ID"
|
||
export CTTYPE="$CT_TYPE"
|
||
export ENABLE_FUSE="$ENABLE_FUSE"
|
||
export ENABLE_TUN="$ENABLE_TUN"
|
||
export PCT_OSTYPE="$var_os"
|
||
export PCT_OSVERSION="${var_version%%.*}"
|
||
export PCT_DISK_SIZE="$DISK_SIZE"
|
||
export PCT_OPTIONS="
|
||
-features $FEATURES
|
||
-hostname $HN
|
||
-tags $TAGS
|
||
$SD
|
||
$NS
|
||
$NET_STRING
|
||
-onboot 1
|
||
-cores $CORE_COUNT
|
||
-memory $RAM_SIZE
|
||
-unprivileged $CT_TYPE
|
||
$PW
|
||
"
|
||
export TEMPLATE_STORAGE="${var_template_storage:-}"
|
||
export CONTAINER_STORAGE="${var_container_storage:-}"
|
||
bash -c "$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/create_lxc.sh)" || exit
|
||
if [ $? -ne 0 ]; then
|
||
exit 200
|
||
fi
|
||
|
||
LXC_CONFIG="/etc/pve/lxc/${CTID}.conf"
|
||
|
||
# USB passthrough for privileged LXC (CT_TYPE=0)
|
||
if [ "$CT_TYPE" == "0" ]; then
|
||
cat <<EOF >>"$LXC_CONFIG"
|
||
# USB passthrough
|
||
lxc.cgroup2.devices.allow: a
|
||
lxc.cap.drop:
|
||
lxc.cgroup2.devices.allow: c 188:* rwm
|
||
lxc.cgroup2.devices.allow: c 189:* rwm
|
||
lxc.mount.entry: /dev/serial/by-id dev/serial/by-id none bind,optional,create=dir
|
||
lxc.mount.entry: /dev/ttyUSB0 dev/ttyUSB0 none bind,optional,create=file
|
||
lxc.mount.entry: /dev/ttyUSB1 dev/ttyUSB1 none bind,optional,create=file
|
||
lxc.mount.entry: /dev/ttyACM0 dev/ttyACM0 none bind,optional,create=file
|
||
lxc.mount.entry: /dev/ttyACM1 dev/ttyACM1 none bind,optional,create=file
|
||
EOF
|
||
fi
|
||
|
||
# VAAPI passthrough for privileged containers or known apps
|
||
VAAPI_APPS=(
|
||
"immich" "Channels" "Emby" "ErsatzTV" "Frigate" "Jellyfin"
|
||
"Plex" "Scrypted" "Tdarr" "Unmanic" "Ollama" "FileFlows"
|
||
"Open WebUI" "Debian" "Tunarr"
|
||
)
|
||
|
||
is_vaapi_app=false
|
||
for vaapi_app in "${VAAPI_APPS[@]}"; do
|
||
if [[ "$APP" == "$vaapi_app" ]]; then
|
||
is_vaapi_app=true
|
||
break
|
||
fi
|
||
done
|
||
|
||
if [[ "$CT_TYPE" == "0" || "$is_vaapi_app" == "true" ]]; then
|
||
VAAPI_DEVICES=()
|
||
SINGLE_VAAPI_DEVICE=""
|
||
seen_ids=()
|
||
|
||
for bypath in /dev/dri/by-path/*-render /dev/dri/renderD*; do
|
||
[[ -e "$bypath" ]] || continue
|
||
dev_render=$(readlink -f "$bypath") || continue
|
||
id=$(basename "$dev_render")
|
||
[[ " ${seen_ids[*]} " == *" $id "* ]] && continue
|
||
seen_ids+=("$id")
|
||
|
||
card_index="${id#renderD}"
|
||
card="/dev/dri/card${card_index}"
|
||
combo_devices=("$dev_render")
|
||
if [[ "${#combo_devices[@]}" -eq 1 && -e /dev/dri/card0 ]]; then
|
||
combo_devices+=("/dev/dri/card0")
|
||
fi
|
||
#echo "combo_devices=${combo_devices[*]}"
|
||
|
||
pci_addr=$(basename "$bypath" | cut -d- -f1 --complement | sed 's/-render//' || true)
|
||
pci_info=$(lspci -nn | grep "${pci_addr#0000:}" || true)
|
||
name="${pci_info#*: }"
|
||
[[ -z "$name" ]] && name="Unknown GPU ($pci_addr)"
|
||
|
||
label="$(basename "$dev_render")"
|
||
[[ -e "$card" ]] && label+=" + $(basename "$card")"
|
||
label+=" – $name"
|
||
msg_debug "[VAAPI DEBUG] Adding VAAPI combo: ${combo_devices[*]} ($label)"
|
||
|
||
VAAPI_DEVICES+=("$(
|
||
IFS=:
|
||
echo "${combo_devices[*]}"
|
||
)" "$label" "OFF")
|
||
done
|
||
|
||
[[ -e /dev/fb0 ]] && VAAPI_DEVICES+=("/dev/fb0" "fb0 - Framebuffer" "OFF")
|
||
|
||
GID_VIDEO=$(getent group video | cut -d: -f3)
|
||
GID_RENDER=$(getent group render | cut -d: -f3)
|
||
[[ -z "$GID_VIDEO" ]] && GID_VIDEO=44 && msg_warn "'video' group not found, falling back to GID 44"
|
||
[[ -z "$GID_RENDER" ]] && GID_RENDER=104 && msg_warn "'render' group not found, falling back to GID 104"
|
||
|
||
if [[ "${#VAAPI_DEVICES[@]}" -eq 0 ]]; then
|
||
msg_warn "No VAAPI-compatible devices found."
|
||
elif [[ "${#VAAPI_DEVICES[@]}" -eq 3 ]]; then
|
||
#msg_info "Only one VAAPI-compatible device found – enabling passthrough."
|
||
|
||
IFS=":" read -ra devices <<<"${VAAPI_DEVICES[0]//\"/}"
|
||
IDX=0
|
||
DID_MOUNT_DRI=0
|
||
|
||
for d in "${devices[@]}"; do
|
||
if [[ "$CT_TYPE" == "0" ]]; then
|
||
if [[ "$DID_MOUNT_DRI" -eq 0 && -d /dev/dri ]]; then
|
||
echo "lxc.mount.entry: /dev/dri /dev/dri none bind,optional,create=dir" >>"$LXC_CONFIG"
|
||
DID_MOUNT_DRI=1
|
||
fi
|
||
if ! major_minor=$(stat -c '%t:%T' "$d" 2>/dev/null | awk -F: '{ printf "%d:%d", "0x"$1, "0x"$2 }'); then
|
||
msg_warn "Could not stat $d – skipping."
|
||
continue
|
||
fi
|
||
echo "lxc.cgroup2.devices.allow: c $major_minor rwm" >>"$LXC_CONFIG"
|
||
echo "lxc.mount.entry: $d $d none bind,optional,create=file" >>"$LXC_CONFIG"
|
||
else
|
||
GID=$([[ "$d" =~ render ]] && echo "$GID_RENDER" || echo "$GID_VIDEO")
|
||
echo "dev${IDX}: $d,gid=${GID}" >>"$LXC_CONFIG"
|
||
IDX=$((IDX + 1))
|
||
fi
|
||
done
|
||
|
||
else
|
||
if [[ "$CT_TYPE" == "0" ]]; then
|
||
whiptail --title "VAAPI passthrough" --msgbox "\
|
||
✅ VAAPI passthrough has been enabled
|
||
|
||
GPU hardware acceleration will be available inside the container
|
||
(e.g., for Jellyfin, Plex, Frigate, etc.)
|
||
|
||
ℹ️ Note: You may need to install drivers manually inside the container,
|
||
such as 'intel-media-driver', 'libva2', or 'vainfo'." 15 74
|
||
else
|
||
whiptail --title "VAAPI passthrough (limited)" --msgbox "\
|
||
⚠️ VAAPI passthrough in unprivileged containers may be limited
|
||
|
||
Some drivers (e.g., iHD) require privileged access to DRM subsystems.
|
||
If VAAPI fails, consider switching to a privileged container.
|
||
|
||
ℹ️ Note: You may need to install drivers manually inside the container,
|
||
such as 'intel-media-driver', 'libva2', or 'vainfo'." 15 74
|
||
fi
|
||
|
||
SELECTED_DEVICES=$(whiptail --title "VAAPI Device Selection" \
|
||
--checklist "Select VAAPI device(s) / GPU(s) to passthrough:" 20 100 6 \
|
||
"${VAAPI_DEVICES[@]}" 3>&1 1>&2 2>&3)
|
||
|
||
if [[ $? -ne 0 ]]; then
|
||
exit_script
|
||
msg_error "VAAPI passthrough selection cancelled by user."
|
||
fi
|
||
|
||
if [[ -n "$SELECTED_DEVICES" ]]; then
|
||
IDX=0
|
||
DID_MOUNT_DRI=0
|
||
for dev in $SELECTED_DEVICES; do
|
||
dev="${dev%\"}"
|
||
dev="${dev#\"}" # strip quotes
|
||
IFS=":" read -ra devices <<<"$dev"
|
||
msg_debug "[VAAPI DEBUG] Autopassthrough for devices: ${devices[*]}"
|
||
for d in "${devices[@]}"; do
|
||
if [[ "$CT_TYPE" == "0" ]]; then
|
||
if [[ "$DID_MOUNT_DRI" -eq 0 && -d /dev/dri ]]; then
|
||
echo "lxc.mount.entry: /dev/dri /dev/dri none bind,optional,create=dir" >>"$LXC_CONFIG"
|
||
DID_MOUNT_DRI=1
|
||
fi
|
||
if ! major_minor=$(stat -c '%t:%T' "$d" 2>/dev/null | awk -F: '{ printf "%d:%d", "0x"$1, "0x"$2 }'); then
|
||
msg_warn "Could not stat $d – skipping."
|
||
continue
|
||
fi
|
||
echo "lxc.cgroup2.devices.allow: c $major_minor rwm" >>"$LXC_CONFIG"
|
||
echo "lxc.mount.entry: $d $d none bind,optional,create=file" >>"$LXC_CONFIG"
|
||
else
|
||
GID=$([[ "$d" =~ render ]] && echo "$GID_RENDER" || echo "$GID_VIDEO")
|
||
|
||
echo "dev${IDX}: $d,gid=${GID}" >>"$LXC_CONFIG"
|
||
IDX=$((IDX + 1))
|
||
fi
|
||
done
|
||
done
|
||
else
|
||
msg_warn "No VAAPI devices selected – passthrough skipped."
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# TUN device passthrough
|
||
if [ "$ENABLE_TUN" == "yes" ]; then
|
||
cat <<EOF >>"$LXC_CONFIG"
|
||
lxc.cgroup2.devices.allow: c 10:200 rwm
|
||
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file
|
||
EOF
|
||
fi
|
||
|
||
# This starts the container and executes <app>-install.sh
|
||
msg_info "Starting LXC Container"
|
||
pct start "$CTID"
|
||
|
||
# wait for status 'running'
|
||
for i in {1..10}; do
|
||
if pct status "$CTID" | grep -q "status: running"; then
|
||
msg_ok "Started LXC Container"
|
||
break
|
||
fi
|
||
sleep 1
|
||
if [ "$i" -eq 10 ]; then
|
||
msg_error "LXC Container did not reach running state"
|
||
exit 1
|
||
fi
|
||
done
|
||
|
||
if [ "$var_os" != "alpine" ]; then
|
||
msg_info "Waiting for network in LXC container"
|
||
sleep 2
|
||
for i in {1..10}; do
|
||
# 1. Primary check: ICMP ping (fastest, but may be blocked by ISP/firewall)
|
||
if pct exec "$CTID" -- ping -c1 -W1 deb.debian.org >/dev/null 2>&1; then
|
||
msg_ok "Network in LXC is reachable (ping)"
|
||
break
|
||
fi
|
||
# Wait and retry if not reachable yet
|
||
if [ "$i" -lt 10 ]; then
|
||
if [ "$i" -le 3 ]; then
|
||
sleep 2
|
||
else
|
||
msg_warn "No network in LXC yet (try $i/10) – waiting..."
|
||
sleep 3
|
||
fi
|
||
else
|
||
# After 10 unsuccessful ping attempts, try HTTP connectivity via wget as fallback
|
||
msg_warn "Ping failed 10 times. Trying HTTP connectivity check (wget) as fallback..."
|
||
if pct exec "$CTID" -- wget -q --spider http://deb.debian.org; then
|
||
msg_ok "Network in LXC is reachable (wget fallback)"
|
||
else
|
||
msg_error "No network in LXC after all checks."
|
||
read -r -p "Set fallback DNS (1.1.1.1/8.8.8.8)? [y/N]: " choice
|
||
case "$choice" in
|
||
[yY]*)
|
||
pct set "$CTID" --nameserver 1.1.1.1
|
||
pct set "$CTID" --nameserver 8.8.8.8
|
||
# Final attempt with wget after DNS change
|
||
if pct exec "$CTID" -- wget -q --spider http://deb.debian.org; then
|
||
msg_ok "Network reachable after DNS fallback"
|
||
else
|
||
msg_error "Still no network/DNS in LXC! Aborting customization."
|
||
exit_script
|
||
fi
|
||
;;
|
||
*)
|
||
msg_error "Aborted by user – no DNS fallback set."
|
||
exit_script
|
||
;;
|
||
esac
|
||
fi
|
||
break
|
||
fi
|
||
done
|
||
fi
|
||
|
||
msg_info "Customizing LXC Container"
|
||
if [ "$var_os" == "alpine" ]; then
|
||
sleep 3
|
||
pct exec "$CTID" -- /bin/sh -c 'cat <<EOF >/etc/apk/repositories
|
||
http://dl-cdn.alpinelinux.org/alpine/latest-stable/main
|
||
http://dl-cdn.alpinelinux.org/alpine/latest-stable/community
|
||
EOF'
|
||
pct exec "$CTID" -- ash -c "apk add bash newt curl openssh nano mc ncurses jq >/dev/null"
|
||
else
|
||
sleep 3
|
||
|
||
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"
|
||
|
||
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
|
||
pct exec "$CTID" -- bash -c "tz='$tz'; echo \"\$tz\" >/etc/timezone && ln -sf \"/usr/share/zoneinfo/\$tz\" /etc/localtime"
|
||
else
|
||
msg_warn "Skipping timezone setup – zone '$tz' not found in container"
|
||
fi
|
||
|
||
pct exec "$CTID" -- bash -c "apt-get update >/dev/null && apt-get install -y sudo curl mc gnupg2 jq >/dev/null" || {
|
||
msg_error "apt-get base packages installation failed"
|
||
exit 1
|
||
}
|
||
fi
|
||
msg_ok "Customized LXC Container"
|
||
install_ssh_keys_into_ct
|
||
if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/${var_install}.sh)"; then
|
||
exit $?
|
||
fi
|
||
}
|
||
|
||
destroy_lxc() {
|
||
if [[ -n "$CT_ID" ]]; then
|
||
read -p "Remove this Container? <y/N> " prompt
|
||
if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then
|
||
pct stop "$CT_ID" &>/dev/null
|
||
pct destroy "$CT_ID" &>/dev/null
|
||
msg_ok "Removed this Container"
|
||
else
|
||
msg_info "Container was not removed."
|
||
fi
|
||
else
|
||
msg_error "No CT_ID found. Nothing to remove."
|
||
fi
|
||
}
|
||
|
||
# This function sets the description of the container.
|
||
description() {
|
||
IP=$(pct exec "$CTID" ip a s dev eth0 | awk '/inet / {print $2}' | cut -d/ -f1)
|
||
|
||
# Generate LXC Description
|
||
DESCRIPTION=$(
|
||
cat <<EOF
|
||
<div align='center'>
|
||
<a href='https://Helper-Scripts.com' target='_blank' rel='noopener noreferrer'>
|
||
<img src='https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/images/logo-81x112.png' alt='Logo' style='width:81px;height:112px;'/>
|
||
</a>
|
||
|
||
<h2 style='font-size: 24px; margin: 20px 0;'>${APP} LXC</h2>
|
||
|
||
<p style='margin: 16px 0;'>
|
||
<a href='https://ko-fi.com/community_scripts' target='_blank' rel='noopener noreferrer'>
|
||
<img src='https://img.shields.io/badge/☕-Buy us a coffee-blue' alt='spend Coffee' />
|
||
</a>
|
||
</p>
|
||
|
||
<span style='margin: 0 10px;'>
|
||
<i class="fa fa-github fa-fw" style="color: #f5f5f5;"></i>
|
||
<a href='https://github.com/community-scripts/ProxmoxVED' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>GitHub</a>
|
||
</span>
|
||
<span style='margin: 0 10px;'>
|
||
<i class="fa fa-comments fa-fw" style="color: #f5f5f5;"></i>
|
||
<a href='https://github.com/community-scripts/ProxmoxVED/discussions' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Discussions</a>
|
||
</span>
|
||
<span style='margin: 0 10px;'>
|
||
<i class="fa fa-exclamation-circle fa-fw" style="color: #f5f5f5;"></i>
|
||
<a href='https://github.com/community-scripts/ProxmoxVED/issues' target='_blank' rel='noopener noreferrer' style='text-decoration: none; color: #00617f;'>Issues</a>
|
||
</span>
|
||
</div>
|
||
EOF
|
||
)
|
||
|
||
# Set Description in LXC
|
||
pct set "$CTID" -description "$DESCRIPTION"
|
||
|
||
if [[ -f /etc/systemd/system/ping-instances.service ]]; then
|
||
systemctl start ping-instances.service
|
||
fi
|
||
|
||
post_update_to_api "done" "none"
|
||
}
|
||
|
||
api_exit_script() {
|
||
exit_code=$?
|
||
if [ $exit_code -ne 0 ]; then
|
||
case $exit_code in
|
||
100) post_update_to_api "failed" "100: Unexpected error in create_lxc.sh" ;;
|
||
101) post_update_to_api "failed" "101: No network connection detected in create_lxc.sh" ;;
|
||
200) post_update_to_api "failed" "200: LXC creation failed in create_lxc.sh" ;;
|
||
201) post_update_to_api "failed" "201: Invalid Storage class in create_lxc.sh" ;;
|
||
202) post_update_to_api "failed" "202: User aborted menu in create_lxc.sh" ;;
|
||
203) post_update_to_api "failed" "203: CTID not set in create_lxc.sh" ;;
|
||
204) post_update_to_api "failed" "204: PCT_OSTYPE not set in create_lxc.sh" ;;
|
||
205) post_update_to_api "failed" "205: CTID cannot be less than 100 in create_lxc.sh" ;;
|
||
206) post_update_to_api "failed" "206: CTID already in use in create_lxc.sh" ;;
|
||
207) post_update_to_api "failed" "207: Template not found in create_lxc.sh" ;;
|
||
208) post_update_to_api "failed" "208: Error downloading template in create_lxc.sh" ;;
|
||
209) post_update_to_api "failed" "209: Container creation failed, but template is intact in create_lxc.sh" ;;
|
||
*) post_update_to_api "failed" "Unknown error, exit code: $exit_code in create_lxc.sh" ;;
|
||
esac
|
||
fi
|
||
}
|
||
|
||
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" "INTERRUPTED"' SIGINT
|
||
trap 'post_update_to_api "failed" "TERMINATED"' SIGTERM
|