update build.func

This commit is contained in:
MickLesk
2026-05-27 14:21:34 +02:00
parent 7586aea4cd
commit 4bf59a5fd6

View File

@@ -217,22 +217,16 @@ update_motd_ip() {
# Only update if file exists and is from community-scripts
if [ -f "$PROFILE_FILE" ] && grep -q "community-scripts" "$PROFILE_FILE" 2>/dev/null; then
# Get current values
local current_os="$(grep ^NAME /etc/os-release | cut -d= -f2 | tr -d '"') - Version: $(grep ^VERSION_ID /etc/os-release | cut -d= -f2 | tr -d '"')"
local current_hostname="$(hostname)"
local current_ip="$(hostname -I | awk '{print $1}')"
# Escape sed special chars in replacement strings (& \ |)
current_os="${current_os//\\/\\\\}"
current_os="${current_os//&/\\&}"
current_hostname="${current_hostname//\\/\\\\}"
current_hostname="${current_hostname//&/\\&}"
current_ip="${current_ip//\\/\\\\}"
current_ip="${current_ip//&/\\&}"
# Update only if values actually changed
if ! grep -q "OS:.*$current_os" "$PROFILE_FILE" 2>/dev/null; then
sed -i "s|OS:.*|OS: \${GN}$current_os\${CL}\\\"|" "$PROFILE_FILE"
fi
if ! grep -q "Hostname:.*$current_hostname" "$PROFILE_FILE" 2>/dev/null; then
sed -i "s|Hostname:.*|Hostname: \${GN}$current_hostname\${CL}\\\"|" "$PROFILE_FILE"
fi
@@ -513,7 +507,7 @@ validate_bridge() {
[[ -z "$bridge" ]] && return 1
# Check if bridge interface exists
if ! ip link show "$bridge" &>/dev/null; then
if ! ip link show dev "$bridge" &>/dev/null; then
return 1
fi
@@ -979,7 +973,6 @@ base_settings() {
fi
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:-""}
@@ -1015,8 +1008,12 @@ base_settings() {
VLAN=${var_vlan:-""}
SSH=${var_ssh:-"no"}
SSH_AUTHORIZED_KEY=${var_ssh_authorized_key:-""}
UDHCPC_FIX=${var_udhcpc_fix:-""}
TAGS="community-script,${var_tags:-}"
# Build TAGS: ensure community-script prefix, use semicolons (pct format), no duplicates
if [[ "${var_tags:-}" == *community-script* ]]; then
TAGS="${var_tags:-community-script}"
else
TAGS="community-script${var_tags:+;${var_tags}}"
fi
ENABLE_FUSE=${var_fuse:-"${1:-no}"}
ENABLE_TUN=${var_tun:-"${1:-no}"}
@@ -1025,6 +1022,7 @@ base_settings() {
ENABLE_NESTING=${var_nesting:-"1"}
ENABLE_KEYCTL=${var_keyctl:-"0"}
ENABLE_MKNOD=${var_mknod:-"0"}
ALLOW_MOUNT_FS=${var_mount_fs:-""}
PROTECT_CT=${var_protection:-"no"}
CT_TIMEZONE=${var_timezone:-"$timezone"}
[[ "${CT_TIMEZONE:-}" == Etc/* ]] && CT_TIMEZONE="host" # pct doesn't accept Etc/* zones
@@ -1058,6 +1056,7 @@ load_vars_file() {
var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu
var_net var_nesting var_ns var_os var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged
var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage var_searchdomain
var_post_install
)
# Whitelist check helper
@@ -1203,6 +1202,22 @@ load_vars_file() {
continue
fi
;;
var_mknod)
if [[ "$var_val" != "0" && "$var_val" != "1" ]]; then
msg_warn "Invalid mknod value '$var_val' in $file (must be 0 or 1), ignoring"
continue
fi
;;
var_mount_fs)
# Normalize: strip spaces, trailing commas
var_val="${var_val// /}"
var_val="${var_val%%,}"
var_val="${var_val##,}"
if [[ -n "$var_val" ]] && [[ ! "$var_val" =~ ^[a-zA-Z0-9]+(,[a-zA-Z0-9]+)*$ ]]; then
msg_warn "Invalid mount_fs value '$var_val' in $file (comma-separated fs names only, e.g. nfs,cifs), ignoring"
continue
fi
;;
var_ipv6_method)
if [[ "$var_val" != "auto" && "$var_val" != "dhcp" && "$var_val" != "static" && "$var_val" != "none" ]]; then
msg_warn "Invalid IPv6 method '$var_val' in $file (must be auto/dhcp/static/none), ignoring"
@@ -1259,6 +1274,7 @@ default_var_settings() {
var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu
var_net var_nesting var_ns var_os var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged
var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage
var_post_install
)
# Snapshot: environment variables (highest precedence)
@@ -1354,6 +1370,11 @@ var_verbose=no
# GitHub Personal Access Token (optional avoids API rate limits during installs)
# Create at https://github.com/settings/tokens read-only public access is sufficient
# var_github_token=ghp_your_token_here
# Optional post-install script (host-side path to a *.sh on the Proxmox host)
# Runs ON THE HOST after the container is fully provisioned.
# Available env vars: APP, NSAPP, CTID, IP, HN, STORAGE, BRG
# var_post_install=/opt/post-install/myhook.sh
EOF
# Now choose storages (always prompt unless just one exists)
@@ -1428,10 +1449,11 @@ get_app_defaults_path() {
if ! declare -p VAR_WHITELIST >/dev/null 2>&1; then
# Note: Removed var_ctid (can only exist once), var_ipv6_static (static IPs are unique)
declare -ag VAR_WHITELIST=(
var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_github_token var_gpu
var_gateway var_hostname var_ipv6_method var_mac var_mtu
var_net var_ns var_os var_pw var_ram var_tags var_tun var_unprivileged
var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage
var_apt_cacher var_apt_cacher_ip var_brg var_cpu var_disk var_fuse var_github_token var_gpu var_keyctl
var_gateway var_hostname var_ipv6_method var_mac var_mknod var_mount_fs var_mtu
var_net var_nesting var_ns var_os var_protection var_pw var_ram var_tags var_timezone var_tun var_unprivileged
var_verbose var_version var_vlan var_ssh var_ssh_authorized_key var_container_storage var_template_storage var_searchdomain
var_post_install
)
fi
@@ -1644,6 +1666,7 @@ _build_current_app_vars_tmp() {
[ -n "$_tpl_storage" ] && echo "var_template_storage=$(_sanitize_value "$_tpl_storage")"
[ -n "$_ct_storage" ] && echo "var_container_storage=$(_sanitize_value "$_ct_storage")"
[ -n "${var_post_install:-}" ] && echo "var_post_install=$(_sanitize_value "${var_post_install}")"
} >"$tmpf"
echo "$tmpf"
@@ -1781,9 +1804,14 @@ advanced_settings() {
trap 'tput rmcup 2>/dev/null || true' RETURN
# Initialize defaults
TAGS="community-script;${var_tags:-}"
# Build TAGS: ensure community-script prefix, use semicolons (pct format), no duplicates
if [[ "${var_tags:-}" == *community-script* ]]; then
TAGS="${var_tags:-community-script}"
else
TAGS="community-script${var_tags:+;${var_tags}}"
fi
local STEP=1
local MAX_STEP=28
local MAX_STEP=29
# Store values for back navigation - inherit from var_* app defaults
local _ct_type="${var_unprivileged:-1}"
@@ -1817,6 +1845,7 @@ advanced_settings() {
local _enable_mknod="${var_mknod:-0}"
local _mount_fs="${var_mount_fs:-}"
local _protect_ct="${var_protection:-no}"
local _post_install="${var_post_install:-}"
# Detect host timezone for default (if not set via var_timezone)
local _host_timezone=""
@@ -2518,6 +2547,13 @@ advanced_settings() {
# STEP 22: Keyctl Support (Docker/systemd)
# ═══════════════════════════════════════════════════════════════════════════
22)
# Keyctl is always required for unprivileged containers — skip dialog
if [[ "$_ct_type" == "1" ]]; then
_enable_keyctl="1"
((STEP++))
continue
fi
local keyctl_default_flag="--defaultno"
[[ "$_enable_keyctl" == "1" ]] && keyctl_default_flag=""
@@ -2525,7 +2561,7 @@ advanced_settings() {
--title "KEYCTL SUPPORT" \
--ok-button "Next" --cancel-button "Back" \
$keyctl_default_flag \
--yesno "\nEnable Keyctl support?\n\nRequired for: Docker containers, systemd-networkd,\nand kernel keyring operations.\n\nNote: Automatically enabled for unprivileged containers.\n\n(App default: ${var_keyctl:-0})" 16 62; then
--yesno "\nEnable Keyctl support?\n\nRequired for: Docker containers, systemd-networkd,\nand kernel keyring operations.\n\n(App default: ${var_keyctl:-0})" 14 62; then
_enable_keyctl="1"
else
if [ $? -eq 1 ]; then
@@ -2655,6 +2691,10 @@ advanced_settings() {
--ok-button "Next" --cancel-button "Back" \
--inputbox "\nAllow specific filesystem mounts.\n\nComma-separated list: nfs, cifs, fuse, ext4, etc.\nLeave empty for defaults (none).\n\nCurrent: $mount_hint" 14 62 "$_mount_fs" \
3>&1 1>&2 2>&3); then
# Normalize: strip spaces and trailing/leading commas
result="${result// /}"
result="${result%%,}"
result="${result##,}"
_mount_fs="$result"
((STEP++))
else
@@ -2663,9 +2703,61 @@ advanced_settings() {
;;
# ═══════════════════════════════════════════════════════════════════════════
# STEP 28: Verbose Mode & Confirmation
# STEP 28: Optional host-side post-install hook (path on the Proxmox HOST)
# ═══════════════════════════════════════════════════════════════════════════
28)
local _hook_prompt="Optional: absolute path to a *.sh file ON THE PROXMOX HOST.
It runs as root on the HOST (NOT in the LXC) after the container
is fully provisioned and started.
Available env vars: APP, NSAPP, CTID, IP, HN, STORAGE, BRG.
Leave empty to skip."
while true; do
if result=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
--title "POST-INSTALL HOOK (HOST)" \
--ok-button "Next" --cancel-button "Back" \
--inputbox "$_hook_prompt" 16 70 "${_post_install}" \
3>&1 1>&2 2>&3); then
# Normalize: strip surrounding whitespace
result="$(printf '%s' "$result" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
if [[ -z "$result" ]]; then
_post_install=""
((STEP++))
break
fi
# Reject obvious shell-meta sneaking through
if [[ "$result" == *';'* || "$result" == *'$('* || "$result" == *'`'* || "$result" == *'&&'* || "$result" == *'||'* ]]; then
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID PATH" \
--msgbox "Path contains shell metacharacters. Please provide a plain absolute file path." 10 70
continue
fi
if [[ "$result" != /* ]]; then
whiptail --backtitle "Proxmox VE Helper Scripts" --title "INVALID PATH" \
--msgbox "Path must be absolute (start with /).\n\nGot: $result" 10 70
continue
fi
if [[ ! -f "$result" ]]; then
if ! whiptail --backtitle "Proxmox VE Helper Scripts" --title "FILE NOT FOUND" \
--yesno "File does not exist on host:\n\n$result\n\nKeep this path anyway?" 12 70; then
continue
fi
fi
_post_install="$result"
((STEP++))
break
else
((STEP--))
break
fi
done
;;
# ═══════════════════════════════════════════════════════════════════════════
# STEP 29: Verbose Mode & Confirmation
# ═══════════════════════════════════════════════════════════════════════════
29)
local verbose_default_flag="--defaultno"
[[ "$_verbose" == "yes" ]] && verbose_default_flag=""
@@ -2694,6 +2786,11 @@ advanced_settings() {
local apt_display="${_apt_cacher:-no}"
[[ "$_apt_cacher" == "yes" && -n "$_apt_cacher_ip" ]] && apt_display="$_apt_cacher_ip"
local post_install_display="${_post_install:-(none)}"
local post_install_warn=""
[[ -n "$_post_install" ]] && post_install_warn="
⚠ Hook runs as root on Proxmox HOST (not in LXC)"
local summary="Container Type: $ct_type_desc
Container ID: $_ct_id
Hostname: $_hostname
@@ -2711,12 +2808,14 @@ Network:
Features:
FUSE: $_enable_fuse | TUN: $_enable_tun
Nesting: $nesting_desc | Keyctl: $keyctl_desc
Mknod: $([ "$_enable_mknod" == "1" ] && echo Enabled || echo Disabled) | Mount FS: ${_mount_fs:-(none)}
GPU: $_enable_gpu | Protection: $protect_desc
Advanced:
Timezone: $tz_display
APT Cacher: $apt_display
Verbose: $_verbose"
Verbose: $_verbose
Post-Install Script: ${post_install_display}${post_install_warn}"
if whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
--title "CONFIRM SETTINGS" \
@@ -2759,6 +2858,7 @@ Advanced:
APT_CACHER="$_apt_cacher"
APT_CACHER_IP="$_apt_cacher_ip"
VERBOSE="$_verbose"
var_post_install="$_post_install"
# Update var_* based on user choice (for functions that check these)
var_gpu="$_enable_gpu"
@@ -2780,13 +2880,6 @@ Advanced:
[[ -n "$_mac" ]] && MAC=",hwaddr=$_mac" || MAC=""
[[ -n "$_vlan" ]] && VLAN=",tag=$_vlan" || VLAN=""
# Alpine UDHCPC fix
if [ "$var_os" == "alpine" ] && [ "$NET" == "dhcp" ] && [ -n "$_ns" ]; then
UDHCPC_FIX="yes"
else
UDHCPC_FIX="no"
fi
export UDHCPC_FIX
export SSH_KEYS_FILE
# Exit alternate screen buffer before showing summary (so output remains visible)
@@ -2811,6 +2904,8 @@ Advanced:
echo -e "${CONTAINERTYPE}${BOLD}${DGN}Nesting: ${BGN}$([ "${ENABLE_NESTING:-1}" == "1" ] && echo "Enabled" || echo "Disabled")${CL}"
[[ "${ENABLE_KEYCTL:-0}" == "1" ]] && echo -e "${CONTAINERTYPE}${BOLD}${DGN}Keyctl: ${BGN}Enabled${CL}"
echo -e "${GPU}${BOLD}${DGN}GPU Passthrough: ${BGN}${ENABLE_GPU:-no}${CL}"
[[ "${ENABLE_MKNOD:-0}" == "1" ]] && echo -e "${CONTAINERTYPE}${BOLD}${DGN}Mknod: ${BGN}Enabled${CL}"
[[ -n "${ALLOW_MOUNT_FS:-}" ]] && echo -e "${CONTAINERTYPE}${BOLD}${DGN}Mount FS: ${BGN}${ALLOW_MOUNT_FS}${CL}"
[[ "${PROTECT_CT:-no}" == "yes" || "${PROTECT_CT:-no}" == "1" ]] && echo -e "${CONTAINERTYPE}${BOLD}${DGN}Protection: ${BGN}Enabled${CL}"
[[ -n "${CT_TIMEZONE:-}" ]] && echo -e "${INFO}${BOLD}${DGN}Timezone: ${BGN}$CT_TIMEZONE${CL}"
[[ "$APT_CACHER" == "yes" ]] && echo -e "${INFO}${BOLD}${DGN}APT Cacher: ${BGN}$APT_CACHER_IP${CL}"
@@ -2833,6 +2928,8 @@ Advanced:
log_msg "IPv6: $IPV6_METHOD"
log_msg "FUSE Support: ${ENABLE_FUSE:-no}"
log_msg "Nesting: $([ "${ENABLE_NESTING:-1}" == "1" ] && echo "Enabled" || echo "Disabled")"
log_msg "Mknod: $([ "${ENABLE_MKNOD:-0}" == "1" ] && echo "Enabled" || echo "Disabled")"
[[ -n "${ALLOW_MOUNT_FS:-}" ]] && log_msg "Mount FS: ${ALLOW_MOUNT_FS}"
log_msg "GPU Passthrough: ${ENABLE_GPU:-no}"
log_msg "Verbose Mode: $VERBOSE"
log_msg "Session ID: ${SESSION_ID}"
@@ -3196,6 +3293,10 @@ check_container_resources() {
if [[ "$current_ram" -lt "$var_ram" ]] || [[ "$current_cpu" -lt "$var_cpu" ]]; then
msg_warn "Under-provisioned: Required ${var_cpu} CPU/${var_ram}MB RAM, Current ${current_cpu} CPU/${current_ram}MB RAM"
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"
if is_unattended; then
msg_error "Aborted: under-provisioned LXC in unattended mode (${current_cpu} CPU/${current_ram}MB RAM < ${var_cpu} CPU/${var_ram}MB RAM)"
exit 113
fi
echo -ne "${INFO}${HOLD} May cause data loss! ${INFO} Continue update with under-provisioned LXC? <yes/No> "
read -r prompt </dev/tty
if [[ ! ${prompt,,} =~ ^(yes)$ ]]; then
@@ -3219,6 +3320,10 @@ check_container_storage() {
usage=$((100 * used_size / total_size))
if ((usage > 80)); then
msg_warn "Storage is dangerously low (${usage}% used on /boot)"
if is_unattended; then
msg_error "Aborted: storage too low in unattended mode (${usage}% used on /boot)"
exit 114
fi
echo -ne "Continue anyway? <y/N> "
read -r prompt </dev/tty
if [[ ! ${prompt,,} =~ ^(y|yes)$ ]]; then
@@ -3470,6 +3575,52 @@ msg_menu() {
return 0
}
# ------------------------------------------------------------------------------
# run_addon_updates()
#
# - Scans /usr/local/bin/update_* for addon update scripts installed alongside
# the main application (e.g. by tools/addon/*.sh)
# - For each found addon, prompts the user (60s timeout, default no) whether
# it should be updated as well
# - Skipped entirely when PHS_SILENT=1 to keep unattended updates predictable
# ------------------------------------------------------------------------------
run_addon_updates() {
shopt -s nullglob
local addons=(/usr/local/bin/update_*)
shopt -u nullglob
((${#addons[@]} == 0)) && return 0
if [[ "${PHS_SILENT:-0}" == "1" ]]; then
msg_info "Detected ${#addons[@]} addon update script(s) - skipping (PHS_SILENT)"
return 0
fi
echo
echo -e "${INFO}${YW} Detected installed addon update script(s):${CL}"
local a name
for a in "${addons[@]}"; do
echo -e "${TAB}- ${a##*/update_}"
done
echo
local ans
for a in "${addons[@]}"; do
name="${a##*/update_}"
printf 'Do you also want to update addon "%s"? (y/N) [60s]: ' "$name"
ans=""
if read -r -t 60 ans; then :; else echo; fi
case "${ans,,}" in
y | yes)
bash "$a" || msg_warn "Addon update for $name failed (rc=$?)"
;;
*)
msg_info "Skipped addon: $name"
;;
esac
done
}
# ------------------------------------------------------------------------------
# start()
#
@@ -3479,7 +3630,7 @@ msg_menu() {
# - Otherwise: shows update/setting menu and runs update_script with cleanup
# ------------------------------------------------------------------------------
start() {
source "$(dirname "${BASH_SOURCE[0]}")/tools.func"
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/tools.func)
if command -v pveversion >/dev/null 2>&1; then
install_script || return 0
return 0
@@ -3489,6 +3640,17 @@ start() {
ensure_profile_loaded
get_lxc_ip
update_script
run_addon_updates
update_motd_ip
cleanup_lxc
elif ! command -v whiptail &>/dev/null || ! [ -t 0 ] || [[ "$TERM" == "dumb" ]]; then
msg_info "No interactive terminal detected defaulting to silent update mode"
VERBOSE="no"
set_std_mode
ensure_profile_loaded
get_lxc_ip
update_script
run_addon_updates
update_motd_ip
cleanup_lxc
else
@@ -3517,6 +3679,7 @@ start() {
ensure_profile_loaded
get_lxc_ip
update_script
run_addon_updates
update_motd_ip
cleanup_lxc
fi
@@ -3553,13 +3716,7 @@ build_container() {
fi
# IP (always required, default dhcp)
# Guard against the literal word "static" being passed as the IP value
# that happens when var_net=static is exported without an actual address.
if [[ "$NET" == "static" || -z "$NET" ]]; then
msg_warn "NET value '${NET:-empty}' is not a valid IP/CIDR falling back to dhcp"
NET="dhcp"
fi
NET_STRING+=",ip=${NET}"
NET_STRING+=",ip=${NET:-dhcp}"
# Gateway
if [[ -n "$GATE" ]]; then
@@ -3618,6 +3775,26 @@ build_container() {
FEATURES="${FEATURES}fuse=1"
fi
# Mknod support (user configurable via advanced settings)
if [ "${ENABLE_MKNOD:-0}" == "1" ]; then
[ -n "$FEATURES" ] && FEATURES="$FEATURES,"
FEATURES="${FEATURES}mknod=1"
fi
# Mount filesystem types (user configurable via advanced settings)
if [ -n "${ALLOW_MOUNT_FS:-}" ]; then
# Sanitize: strip spaces, trailing/leading commas, then convert commas to semicolons
local _mount_clean="${ALLOW_MOUNT_FS// /}"
_mount_clean="${_mount_clean%%,}"
_mount_clean="${_mount_clean##,}"
_mount_clean="${_mount_clean%%;}"
_mount_clean="${_mount_clean//,/;}"
if [ -n "$_mount_clean" ]; then
[ -n "$FEATURES" ] && FEATURES="$FEATURES,"
FEATURES="${FEATURES}mount=${_mount_clean}"
fi
fi
# Build PCT_OPTIONS as string for export
local _func_file
local SCRIPT_DIR
@@ -3649,7 +3826,7 @@ build_container() {
export FUNCTIONS_FILE_PATH="$(curl -fsSL "$_func_url")"
fi
if [[ -z "$FUNCTIONS_FILE_PATH" || ${#FUNCTIONS_FILE_PATH} -lt 100 ]]; then
msg_error "Failed to load install functions"
msg_error "Failed to download install functions from: $_func_url"
exit 115
fi
@@ -5234,15 +5411,185 @@ create_lxc_container() {
# Extract Debian OS minor from template name: debian-13-standard_13.1-1_amd64.tar.zst => "13.1"
parse_template_osver() { sed -n 's/.*_\([0-9][0-9]*\(\.[0-9]\+\)\?\)-.*/\1/p' <<<"$1"; }
# Switch to the previous OS major version template and retry pct create
# Determines the fallback version automatically based on available templates.
# Returns: 0 = success, 1 = failed
fallback_to_previous_os_version() {
local old_template="$TEMPLATE"
local os_type="${PCT_OSTYPE:-}"
local current_ver="${PCT_OSVERSION:-}"
# Determine template search pattern based on OS type
local tpl_pattern=""
case "$os_type" in
debian | ubuntu) tpl_pattern="-standard_" ;;
alpine | fedora | rocky | centos) tpl_pattern="-default_" ;;
*) tpl_pattern="" ;;
esac
msg_info "Searching for an older $os_type template (current: $os_type $current_ver)"
# Collect all available versions for this OS type (local + online)
local -a all_versions=()
# Local templates
mapfile -t _local_vers < <(
pveam list "$TEMPLATE_STORAGE" 2>/dev/null |
awk -v os="$os_type" -v pat="$tpl_pattern" '$1 ~ ("^"os"|/"os) && $1 ~ pat {print $1}' |
sed 's|.*/||' |
sed -E "s/^${os_type}-([0-9]+(\.[0-9]+)?).*/\1/" |
sort -u -V
)
all_versions+=("${_local_vers[@]}")
# Online templates (only if needed)
if command -v timeout &>/dev/null; then
timeout 30 pveam update >/dev/null 2>&1 || true
else
pveam update >/dev/null 2>&1 || true
fi
mapfile -t _online_vers < <(
pveam available -section system 2>/dev/null |
awk '{print $2}' |
grep -E "^${os_type}-[0-9]" |
{ [[ -n "$tpl_pattern" ]] && grep "$tpl_pattern" || cat; } |
sed -E "s/^${os_type}-([0-9]+(\.[0-9]+)?).*/\1/" |
sort -u -V 2>/dev/null || true
)
all_versions+=("${_online_vers[@]}")
# Deduplicate and sort, find the highest version below current
local fallback_ver=""
fallback_ver=$(printf '%s\n' "${all_versions[@]}" | sort -u -V | awk -v cur="$current_ver" '{
# Compare major versions: extract major part
split($0, a, ".")
split(cur, b, ".")
if (a[1]+0 < b[1]+0) ver=$0
} END { if (ver) print ver }')
if [[ -z "$fallback_ver" ]]; then
msg_error "No older $os_type template version found."
return 1
fi
msg_ok "Fallback version: $os_type $fallback_ver"
# Find the actual template file for this version
local fallback_search="${os_type}-${fallback_ver}"
local fallback_template=""
# Check local first
mapfile -t _fb_local < <(
pveam list "$TEMPLATE_STORAGE" 2>/dev/null |
awk -v search="$fallback_search" -v pat="$tpl_pattern" '$1 ~ search && $1 ~ pat {print $1}' |
sed 's|.*/||' | sort -t - -k 2 -V
)
if [[ ${#_fb_local[@]} -gt 0 ]]; then
fallback_template="${_fb_local[-1]}"
else
# Check online
mapfile -t _fb_online < <(
pveam available -section system 2>/dev/null |
awk '{print $2}' |
grep -E "^${fallback_search}.*${tpl_pattern}" |
sort -t - -k 2 -V 2>/dev/null || true
)
[[ ${#_fb_online[@]} -gt 0 ]] && fallback_template="${_fb_online[-1]}"
fi
if [[ -z "$fallback_template" ]]; then
msg_error "No template found for $os_type $fallback_ver."
return 1
fi
msg_ok "Found template: $fallback_template"
# Download if needed
local fallback_path
fallback_path="$(pvesm path "$TEMPLATE_STORAGE:vztmpl/$fallback_template" 2>/dev/null || true)"
[[ -z "$fallback_path" ]] && fallback_path="/var/lib/vz/template/cache/$fallback_template"
if [[ ! -f "$fallback_path" ]]; then
msg_info "Downloading $os_type $fallback_ver template"
if ! pveam download "$TEMPLATE_STORAGE" "$fallback_template" >>"${BUILD_LOG:-/dev/null}" 2>&1; then
msg_error "Failed to download $os_type $fallback_ver template."
return 1
fi
msg_ok "Template downloaded"
fi
# Update variables
TEMPLATE="$fallback_template"
TEMPLATE_PATH="$fallback_path"
PCT_OSVERSION="$fallback_ver"
export PCT_OSVERSION
# Retry pct create
msg_info "Retrying container creation with $os_type $fallback_ver"
if pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then
msg_ok "Container created successfully with $os_type $fallback_ver (fallback from $old_template)."
return 0
else
msg_error "Container creation with $os_type $fallback_ver also failed. See $LOGFILE"
return 1
fi
}
# Offer upgrade for pve-container/lxc-pve if candidate > installed; optional auto-retry pct create
# Returns:
# 0 = no upgrade needed
# 0 = no upgrade needed / container created after upgrade or explicit fallback
# 1 = upgraded (and if do_retry=yes and retry succeeded, creation done)
# 2 = user declined
# 2 = user chose ignore
# 3 = upgrade attempted but failed OR retry failed
# 4 = user cancelled
offer_lxc_stack_upgrade_and_maybe_retry() {
local do_retry="${1:-no}" # yes|no
local _pvec_i _pvec_c _lxcp_i _lxcp_c need=0
local _ans
has_previous_os_version_template() {
local os_type="${PCT_OSTYPE:-}"
local current_ver="${PCT_OSVERSION:-}"
local tpl_pattern="${TEMPLATE_PATTERN:-${TEMPLATE:-}}"
local -a all_versions=()
[[ -n "$os_type" && -n "$current_ver" ]] || return 1
mapfile -t _local_vers < <(
pveam list "$TEMPLATE_STORAGE" 2>/dev/null |
awk '{print $1}' |
sed 's|.*/||' |
grep -E "^${os_type}-[0-9]" |
{ [[ -n "$tpl_pattern" ]] && grep "$tpl_pattern" || cat; } |
sed -E "s/^${os_type}-([0-9]+(\.[0-9]+)?).*/\1/" |
sort -u -V
)
all_versions+=("${_local_vers[@]}")
if command -v timeout &>/dev/null; then
timeout 30 pveam update >/dev/null 2>&1 || true
else
pveam update >/dev/null 2>&1 || true
fi
mapfile -t _online_vers < <(
pveam available -section system 2>/dev/null |
awk '{print $2}' |
grep -E "^${os_type}-[0-9]" |
{ [[ -n "$tpl_pattern" ]] && grep "$tpl_pattern" || cat; } |
sed -E "s/^${os_type}-([0-9]+(\.[0-9]+)?).*/\1/" |
sort -u -V 2>/dev/null || true
)
all_versions+=("${_online_vers[@]}")
printf '%s\n' "${all_versions[@]}" | sort -u -V | awk -v cur="$current_ver" '
{
split($0, a, ".")
split(cur, b, ".")
if (a[1]+0 < b[1]+0) found=1
}
END { exit found ? 0 : 1 }
'
}
_pvec_i="$(pkg_ver pve-container)"
_lxcp_i="$(pkg_ver lxc-pve)"
@@ -5263,14 +5610,78 @@ create_lxc_container() {
msg_warn "An update for the Proxmox LXC stack is available"
echo " pve-container: installed=${_pvec_i:-n/a} candidate=${_pvec_c:-n/a}"
echo " lxc-pve : installed=${_lxcp_i:-n/a} candidate=${_lxcp_c:-n/a}"
echo " note : option 1 runs host apt update + apt upgrade"
echo
read -rp "Do you want to upgrade now? [y/N] " _ans </dev/tty
# Offer older OS version fallback when template version might be too new for LXC stack
local _has_fallback_option=false
if [[ "$do_retry" == "yes" ]] && has_previous_os_version_template; then
_has_fallback_option=true
echo " [1] Run host upgrade now (recommended). WARNING: this runs apt upgrade and updates all Packages on your host!"
echo " [2] Use an older ${PCT_OSTYPE} template instead (may not work with all scripts)"
echo " [3] Ignore"
echo " [4] Cancel"
echo
read -rp "Select option [1/2/3/4]: " _ans </dev/tty
else
echo " [1] Run host upgrade now (recommended). WARNING: this runs apt upgrade and updates all Packages on your host!"
echo " [2] Ignore"
echo " [3] Cancel"
echo
read -rp "Select option [1/2/3]: " _ans </dev/tty
fi
if [[ "$_has_fallback_option" == true ]]; then
case "$_ans" in
1)
_ans="y"
;;
2)
if fallback_to_previous_os_version; then
return 0
else
return 3
fi
;;
3)
return 2
;;
4)
return 4
;;
*)
return 4
;;
esac
else
case "$_ans" in
1)
_ans="y"
;;
2)
return 2
;;
*)
return 4
;;
esac
fi
case "${_ans,,}" in
y | yes)
msg_info "Upgrading Proxmox LXC stack (pve-container, lxc-pve)"
msg_info "Running host upgrade for LXC stack compatibility"
apt_update_safe
if $STD apt-get install -y --only-upgrade pve-container lxc-pve; then
if $STD apt-get upgrade -y; then
msg_ok "LXC stack upgraded."
# Verify pct binary still works after upgrade (partial upgrades can break Perl modules)
if ! pct list &>/dev/null; then
msg_error "LXC stack upgrade caused PVE tool breakage (likely Perl module incompatibility)."
msg_custom "⚠️" "${YW}" "A partial package upgrade has left the PVE stack in an inconsistent state."
msg_custom "🔧" "${YW}" "Please run the following on the Proxmox host, then retry:"
echo -e "${TAB} apt update && apt upgrade -y"
echo -e "${TAB} reboot"
return 3
fi
if [[ "$do_retry" == "yes" ]]; then
msg_info "Retrying container creation after upgrade"
if pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >>"$LOGFILE" 2>&1; then
@@ -5369,10 +5780,35 @@ create_lxc_container() {
fi
msg_info "Validating storage '$CONTAINER_STORAGE'"
STORAGE_TYPE=$(grep -E "^[^:]+: $CONTAINER_STORAGE$" /etc/pve/storage.cfg | cut -d: -f1 | head -1 || true)
# Check if storage.cfg is accessible (pmxcfs must be mounted)
if [[ ! -f /etc/pve/storage.cfg ]]; then
if ! mountpoint -q /etc/pve 2>/dev/null; then
msg_error "Proxmox cluster filesystem (pmxcfs) is not mounted at /etc/pve."
msg_custom "🔧" "${YW}" "Try: systemctl restart pve-cluster"
else
msg_error "/etc/pve/storage.cfg does not exist."
msg_custom "🔧" "${YW}" "Check Proxmox cluster filesystem integrity: pvecm status"
fi
exit 213
fi
STORAGE_TYPE=$(grep -E "^[^:]+:[[:space:]]*$CONTAINER_STORAGE[[:space:]]*$" /etc/pve/storage.cfg | cut -d: -f1 | head -1 || true)
# Fallback: use pvesm status to determine storage type
if [[ -z "$STORAGE_TYPE" ]]; then
STORAGE_TYPE=$(pvesm status -storage "$CONTAINER_STORAGE" 2>/dev/null | awk 'NR>1{print $2}')
fi
if [[ -z "$STORAGE_TYPE" ]]; then
msg_error "Storage '$CONTAINER_STORAGE' not found in /etc/pve/storage.cfg"
msg_custom "📋" "${YW}" "Available storages: $(pvesm status 2>/dev/null | awk 'NR>1{printf "%s (%s) ", $1, $2}' || echo 'n/a')"
if [[ -r /etc/pve/storage.cfg ]]; then
msg_custom "📋" "${YW}" "Storage definitions found in config:"
grep -E '^[a-z]+:' /etc/pve/storage.cfg 2>/dev/null | while IFS= read -r _line; do
echo "${TAB} $_line"
done
fi
msg_custom "📖" "${YW}" "See https://pve.proxmox.com/wiki/Storage for storage configuration details."
exit 213
fi
@@ -5403,6 +5839,10 @@ create_lxc_container() {
if ! pvesm status -content rootdir 2>/dev/null | awk 'NR>1{print $1}' | grep -qx "$CONTAINER_STORAGE"; then
msg_error "Storage '$CONTAINER_STORAGE' ($STORAGE_TYPE) does not support 'rootdir' content."
msg_custom "💡" "${YW}" "Enable 'Disk image' (rootdir) for storage '${CONTAINER_STORAGE}' in:"
msg_custom " " "${YW}" "Datacenter → Storage → ${CONTAINER_STORAGE} → Edit → Content"
msg_custom "📖" "${YW}" "See: https://pve.proxmox.com/wiki/Storage"
msg_custom "🔗" "${YW}" "Help: https://github.com/community-scripts/ProxmoxVE/discussions"
exit 213
fi
msg_ok "Storage '$CONTAINER_STORAGE' ($STORAGE_TYPE) validated"
@@ -5789,9 +6229,23 @@ create_lxc_container() {
msg_debug "Logfile: $LOGFILE"
# First attempt (PCT_OPTIONS is a multi-line string, use it directly)
# Disable globbing: unquoted $PCT_OPTIONS needs word-splitting but must not glob-expand
# (e.g. passwords containing * or ? would match filenames otherwise)
set -f
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" $PCT_OPTIONS >"$LOGFILE" 2>&1; then
msg_debug "Container creation failed on ${TEMPLATE_STORAGE}. Checking error..."
# Check for Perl module breakage (partial PVE upgrade)
if grep -qiE 'Compilation failed|Bareword.*not allowed' "$LOGFILE"; then
msg_error "Container creation failed due to broken Perl modules on the PVE host."
msg_custom "⚠️" "${YW}" "This usually happens after a partial PVE package upgrade."
msg_custom "🔧" "${YW}" "Please run the following on the Proxmox host, then retry:"
echo -e "${TAB} apt update && apt dist-upgrade -y"
echo -e "${TAB} reboot"
_flush_pct_log
exit 232
fi
# Check if CTID collision (race condition: ID claimed between validation and creation)
if grep -qiE 'already exists|already in use' "$LOGFILE"; then
local old_ctid="$CTID"
@@ -5838,12 +6292,17 @@ create_lxc_container() {
case $rc in
0) : ;; # success - container created, continue
2)
msg_error "Upgrade declined. Please update and re-run: apt update && apt install --only-upgrade pve-container lxc-pve"
msg_error "LXC stack upgrade ignored. Please inspect: $LOGFILE"
_flush_pct_log
exit 231
;;
3)
msg_error "Upgrade and/or retry failed. Please inspect: $LOGFILE"
msg_error "LXC stack upgrade failed. Please inspect: $LOGFILE"
_flush_pct_log
exit 231
;;
4)
msg_error "Cancelled by user."
_flush_pct_log
exit 231
;;
@@ -5870,12 +6329,17 @@ create_lxc_container() {
case $rc in
0) : ;; # success - container created, continue
2)
msg_error "Upgrade declined. Please update and re-run: apt update && apt install --only-upgrade pve-container lxc-pve"
msg_error "LXC stack upgrade ignored. Please inspect: $LOGFILE"
_flush_pct_log
exit 231
;;
3)
msg_error "Upgrade and/or retry failed. Please inspect: $LOGFILE"
msg_error "LXC stack upgrade failed. Please inspect: $LOGFILE"
_flush_pct_log
exit 231
;;
4)
msg_error "Cancelled by user."
_flush_pct_log
exit 231
;;
@@ -5896,6 +6360,7 @@ create_lxc_container() {
fi
fi # close CTID collision else-branch
fi
set +f # re-enable globbing after pct create block
# Verify container exists (allow up to 10s for pmxcfs sync in clusters)
local _pct_visible=false
@@ -6001,6 +6466,40 @@ EOF
systemctl start ping-instances.service
fi
# Optional host-side post-install hook
# Path comes from var_post_install (default.vars / app.vars / advanced settings).
# Runs ON THE PROXMOX HOST after the container is up and configured.
# Exposed env vars: APP, NSAPP, CTID, IP, HN, STORAGE, BRG.
# Output (stdout/stderr) is captured to /var/log/community-scripts/post-install-<CTID>.log
if [[ -n "${var_post_install:-}" ]]; then
local _hook_log_dir="/var/log/community-scripts"
local _hook_log="${_hook_log_dir}/post-install-${CTID}.log"
mkdir -p "$_hook_log_dir" 2>/dev/null || true
if [[ ! -f "${var_post_install}" ]]; then
msg_error "Post-install hook not found on host: ${var_post_install}"
whiptail --backtitle "Proxmox VE Helper Scripts" \
--title "POST-INSTALL HOOK FAILED" \
--msgbox "The configured post-install hook was not found on the Proxmox host:\n\n${var_post_install}\n\nThe LXC was created successfully, but the hook did NOT run." 14 72 || true
else
msg_info "Running post-install hook: ${var_post_install}"
local _hook_rc=0
APP="$APP" NSAPP="${NSAPP:-}" CTID="$CTID" IP="$IP" HN="${HN:-}" \
STORAGE="${STORAGE:-}" BRG="${BRG:-}" \
bash "${var_post_install}" >"${_hook_log}" 2>&1 || _hook_rc=$?
if [[ $_hook_rc -eq 0 ]]; then
msg_ok "Post-install hook completed (log: ${_hook_log})"
else
msg_error "Post-install hook failed (rc=${_hook_rc}) see ${_hook_log}"
local _hook_tail=""
_hook_tail="$(tail -n 15 "${_hook_log}" 2>/dev/null || true)"
whiptail --backtitle "Proxmox VE Helper Scripts" \
--title "POST-INSTALL HOOK FAILED" \
--msgbox "Hook exited with code ${_hook_rc}.\n\nScript: ${var_post_install}\nLog: ${_hook_log}\n\n--- Last log lines ---\n${_hook_tail}\n\nThe LXC itself was created successfully." 22 78 || true
fi
fi
fi
INSTALL_COMPLETE=true
post_update_to_api "done" "none"
}