#!/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) load_functions #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) load_functions #echo "(build.func) Loaded core.func via wget" fi # This function enables error handling in the script by setting options and defining a trap for the ERR signal. catch_errors() { set -Eeo pipefail # if [ -n "$BASH_VERSION" ] && command -v shopt >/dev/null 2>&1; then # shopt -s errtrace # fi trap 'error_handler $LINENO "$BASH_COMMAND"' ERR } # This function is called when an error occurs. It receives the exit code, line number, and command that caused the error, and displays an error message. error_handler() { local exit_code="$?" local line_number="$1" local command="$2" printf "\e[?25h" local error_message="[ERROR] in line $line_number: exit code $exit_code: while executing command $command" post_update_to_api "failed" "$command" echo -e "\n$error_message\n" exit "$exit_code" if [[ -n "$CT_ID" ]]; then read -p "Remove this Container? " prompt if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then pct stop "$CT_ID" &>/dev/null pct destroy "$CT_ID" &>/dev/null msg_ok "Removed this Container" fi fi } # 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. pve_check() { local PVE_VER PVE_VER="$(pveversion | awk -F'/' '{print $2}' | awk -F'-' '{print $1}')" # 8 Version Check if [[ "$PVE_VER" =~ ^8\.([0-9]+) ]]; then local MINOR="${BASH_REMATCH[1]}" if ((MINOR < 1 || MINOR > 4)); then msg_error "This version of Proxmox Virtual Environment is not supported" echo -e "Requires Proxmox Virtual Environment Version 8.1 – 8.4" echo -e "Exiting..." sleep 2 exit 1 fi return 0 fi # 9 Beta Version Check if [[ "$PVE_VER" =~ ^9\.([0-9]+) ]]; then msg_custom "Detected Proxmox Virtual Environment $PVE_VER – Beta state, use with caution!" return 0 fi # All others (unsupported versions) msg_error "This version of Proxmox Virtual Environment is not supported" echo -e "Requires Proxmox Virtual Environment Version 8.1 – 8.4 or 9.x (Beta)" echo -e "Exiting..." sleep 2 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}" } # This function checks the system architecture and exits if it's not "amd64". arch_check() { if [ "$(dpkg --print-architecture)" != "amd64" ]; then echo -e "\n ${INFO}${YWB}This script will not work with PiMox! \n" echo -e "\n ${YWB}Visit https://github.com/asylumexp/Proxmox for ARM64 support. \n" echo -e "Exiting..." sleep 2 exit fi } # 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 } # select_storage() { # local CLASS=$1 CONTENT CONTENT_LABEL # case $CLASS in # container) # CONTENT='rootdir' # CONTENT_LABEL='Container' # ;; # template) # CONTENT='vztmpl' # CONTENT_LABEL='Template' # ;; # iso) # CONTENT='iso' # CONTENT_LABEL='ISO image' # ;; # images) # CONTENT='images' # CONTENT_LABEL='VM Disk image' # ;; # backup) # CONTENT='backup' # CONTENT_LABEL='Backup' # ;; # snippets) # CONTENT='snippets' # CONTENT_LABEL='Snippets' # ;; # *) # msg_error "Invalid storage class '$CLASS'." # exit 201 # ;; # esac # command -v whiptail >/dev/null || { # msg_error "whiptail missing." # exit 220 # } # command -v numfmt >/dev/null || { # msg_error "numfmt missing." # exit 221 # } # local -a MENU # while read -r line; do # local TAG=$(echo "$line" | awk '{print $1}') # local TYPE=$(echo "$line" | awk '{printf "%-10s", $2}') # local FREE=$(echo "$line" | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf "%9sB", $6}') # MENU+=("$TAG" "Type: $TYPE Free: $FREE" "OFF") # done < <(pvesm status -content "$CONTENT" | awk 'NR>1') # if [ ${#MENU[@]} -eq 0 ]; then # msg_error "No storage found for content type '$CONTENT'." # exit 203 # fi # if [ $((${#MENU[@]} / 3)) -eq 1 ]; then # echo "${MENU[0]}" # return # fi # local STORAGE # STORAGE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "Storage Pools" --radiolist \ # "Which storage pool for ${CONTENT_LABEL,,}?\n(Spacebar to select)" \ # 16 70 6 "${MENU[@]}" 3>&1 1>&2 2>&3) || { # msg_error "Storage selection cancelled by user." # exit 202 # } # echo "$STORAGE" # } # manage_default_storage() { # local file="/usr/local/community-scripts/default_storage" # mkdir -p /usr/local/community-scripts # local tmpl=$(select_storage template) # local cont=$(select_storage container) # cat <"$file" # TEMPLATE_STORAGE=$tmpl # CONTAINER_STORAGE=$cont # EOF # msg_ok "Default Storage set: Template=${BL}$tmpl${CL} ${GN}|${CL} Container=${BL}$cont${CL}" # whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ # --msgbox "Default Storage set:\n\nTemplate: $tmpl\nContainer: $cont" 10 58 # } 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 [ "$VERB" == "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 } # 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_AUTHORIZED_KEY="$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --inputbox "SSH Authorized key for root (leave empty for none)" 8 58 --title "SSH Key" 3>&1 1>&2 2>&3)" if [[ -z "${SSH_AUTHORIZED_KEY}" ]]; then SSH_AUTHORIZED_KEY="" fi if [[ "$PW" == -password* || -n "$SSH_AUTHORIZED_KEY" ]]; 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 echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}" else SSH="no" echo -e "${ROOTSSH}${BOLD}${DGN}Root SSH Access: ${BGN}$SSH${CL}" fi 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 </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 </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 } 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" ;; *) echo -e "\n${CROSS}${RD}Invalid PRESET value: ${PRESET}${CL}\n" exit 1 ;; esac else while true; do TMP_CHOICE=$(whiptail --backtitle "[dev] Proxmox VE Helper Scripts" \ --title "SETTINGS" \ --menu "Choose an option:" 20 60 7 \ "1" "Default Settings" \ "2" "Default Settings (with verbose)" \ "3" "Advanced Settings" \ "4" "Use Config File" \ "5" "Manage Default Storage" \ "6" "Diagnostic Settings" \ "7" "Exit" \ --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" break done 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_CROPPED}Verbose)${CL}" VERBOSE="yes" METHOD="default" base_settings "$VERBOSE" echo_default ;; 3) header_info echo -e "${ADVANCED}${BOLD}${RD}Using Advanced Settings on node $PVEHOST_NAME${CL}" METHOD="advanced" base_settings advanced_settings ;; 4) header_info echo -e "${INFO}${HOLD} ${GN}Using Config File on node $PVEHOST_NAME${CL}" METHOD="advanced" source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/config-file.func) config_file ;; 5) manage_default_storage ;; 6) if [[ $DIAGNOSTICS == "yes" ]]; then if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS SETTINGS" --yesno "Send Diagnostics of LXC Installation?\n\nCurrent setting: ${DIAGNOSTICS}" 10 58 \ --yes-button "No" --no-button "Back"; then DIAGNOSTICS="no" sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=no/' /usr/local/community-scripts/diagnostics whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS SETTINGS" --msgbox "Diagnostics settings changed to ${DIAGNOSTICS}." 8 58 fi else if whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS SETTINGS" --yesno "Send Diagnostics of LXC Installation?\n\nCurrent setting: ${DIAGNOSTICS}" 10 58 \ --yes-button "Yes" --no-button "Back"; then DIAGNOSTICS="yes" sed -i 's/^DIAGNOSTICS=.*/DIAGNOSTICS=yes/' /usr/local/community-scripts/diagnostics whiptail --backtitle "[dev] Proxmox VE Helper Scripts" --title "DIAGNOSTICS SETTINGS" --msgbox "Diagnostics settings changed to ${DIAGNOSTICS}." 8 58 fi fi ;; 7) echo -e "\n${CROSS}${RD}Script terminated. Have a great day!${CL}\n" exit 0 ;; *) echo -e "${CROSS}${RD}Invalid option, please try again.${CL}" ;; 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? " 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? " 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 } 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 " bash -c "$(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/create_lxc.sh)" 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 <>"$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" ) 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=() for bypath in /dev/dri/by-path/*-render; do dev_target=$(readlink -f "$bypath") || continue pci_addr=$(basename "$bypath" | cut -d- -f1 --complement | sed 's/-render//') pci_info=$(lspci -nn | grep "$pci_addr" || true) if [[ -z "$pci_info" ]]; then name="Unknown GPU ($pci_addr)" else name="${pci_info#*: }" fi label="$(basename "$dev_target") - $name" VAAPI_DEVICES+=("$dev_target" "$label" "OFF") done [[ -e /dev/fb0 ]] && VAAPI_DEVICES+=("/dev/fb0") # Dynamically resolve group IDs 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 : elif [[ "${#VAAPI_DEVICES[@]}" -eq 1 && "$CT_TYPE" == "0" ]]; then # Privileged + 1 device → silent passthrough device="${VAAPI_DEVICES[0]}" if ! major_minor=$(stat -c '%t:%T' "$device" 2>/dev/null | awk -F: '{ printf "%d:%d", "0x"$1, "0x"$2 }'); then msg_warn "Could not stat $device – skipping passthrough." else echo "lxc.cgroup2.devices.allow: c $major_minor rwm" >>"$LXC_CONFIG" echo "lxc.mount.entry: $device $device none bind,optional,create=file" >>"$LXC_CONFIG" fi else whiptail --title "VAAPI passthrough" --msgbox "\ VAAPI passthrough has been enabled for this container. This allows GPU hardware acceleration (e.g., video transcoding for Jellyfin, Plex, Frigate, etc.). You will now be prompted to select which VAAPI devices should be passed through." 12 72 SELECTED_DEVICES=$(whiptail --title "VAAPI Device Selection" \ --checklist "Select VAAPI device(s) / GPU(s) to passthrough:" 20 70 10 \ "${VAAPI_DEVICES[@]}" 3>&1 1>&2 2>&3) if [[ -n "$SELECTED_DEVICES" ]]; then IDX=0 for dev in $SELECTED_DEVICES; do dev=$(sed 's/"//g' <<<"$dev") if [[ "$CT_TYPE" == "0" ]]; then if ! major_minor=$(stat -c '%t:%T' "$dev" 2>/dev/null | awk -F: '{ printf "%d:%d", "0x"$1, "0x"$2 }'); then msg_warn "Could not stat $dev – skipping." continue fi echo "lxc.cgroup2.devices.allow: c $major_minor rwm" >>"$LXC_CONFIG" echo "lxc.mount.entry: $dev $dev none bind,optional,create=file" >>"$LXC_CONFIG" else GID=$([[ "$dev" =~ render ]] && echo "$GID_RENDER" || echo "$GID_VIDEO") echo "dev${IDX}: $dev,gid=${GID}" >>"$LXC_CONFIG" IDX=$((IDX + 1)) fi done else msg_warn "No VAAPI devices selected – passthrough skipped." fi fi fi # TUN device passthrough if [ "$ENABLE_TUN" == "yes" ]; then cat <>"$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 -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 "Startted 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" for i in {1..10}; do if pct exec "$CTID" -- ping -c1 -W1 deb.debian.org >/dev/null 2>&1; then msg_ok "Network in LXC is reachable" break fi if [ "$i" -lt 10 ]; then msg_warn "No network yet in LXC (try $i/10) – waiting..." sleep 3 else msg_error "No network in LXC after waiting." 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 if pct exec "$CTID" -- ping -c1 -W1 deb.debian.org >/dev/null 2>&1; then msg_ok "Network reachable after DNS fallback" else msg_error "Still no network/DNS in LXC! Aborting customization." exit 1 fi ;; *) msg_error "Aborted by user – no DNS fallback set." exit 1 ;; esac fi done fi msg_info "Customizing LXC Container" if [ "$var_os" == "alpine" ]; then sleep 3 pct exec "$CTID" -- /bin/sh -c 'cat </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 >/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 >/dev/null" || { msg_error "apt-get base packages installation failed" exit 1 } fi msg_ok "Customized LXC Container" if ! lxc-attach -n "$CTID" -- bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/install/"$var_install".sh)"; then exit $? 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 < Logo

${APP} LXC

spend Coffee

GitHub Discussions Issues 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