628 lines
22 KiB
Bash
628 lines
22 KiB
Bash
#!/usr/bin/env bash
|
||
# Copyright (c) 2021-2025 tteck
|
||
# Author: tteck (tteckster)
|
||
# Co-Author: MickLesk
|
||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# Optional verbose mode (debug tracing)
|
||
# ------------------------------------------------------------------------------
|
||
if [[ "${CREATE_LXC_VERBOSE:-no}" == "yes" ]]; then set -x; fi
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# Load core functions (msg_info/msg_ok/msg_error/…)
|
||
# ------------------------------------------------------------------------------
|
||
if command -v curl >/dev/null 2>&1; then
|
||
# Achtung: bewusst exakt diese URL-Struktur
|
||
source <(curl -fsSL https://git.community-scripts.org/community-scripts/ProxmoxVED/raw/branch/main/misc/core.func)
|
||
load_functions
|
||
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
|
||
fi
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# Strict error handling
|
||
# ------------------------------------------------------------------------------
|
||
set -Eeuo pipefail
|
||
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
|
||
trap on_exit EXIT
|
||
trap on_interrupt INT
|
||
trap on_terminate TERM
|
||
|
||
on_exit() {
|
||
local exit_code="$?"
|
||
[[ -n "${lockfile:-}" && -e "$lockfile" ]] && rm -f "$lockfile"
|
||
exit "$exit_code"
|
||
}
|
||
|
||
error_handler() {
|
||
local exit_code="$?"
|
||
local line_number="$1"
|
||
local command="$2"
|
||
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_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
|
||
}
|
||
exit_script() {
|
||
clear
|
||
printf "\e[?25h"
|
||
echo -e "\n${CROSS}${RD}User exited script${CL}\n"
|
||
kill 0
|
||
exit 1
|
||
}
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# Helpers (dynamic versioning / template parsing)
|
||
# ------------------------------------------------------------------------------
|
||
pkg_ver() { dpkg-query -W -f='${Version}\n' "$1" 2>/dev/null || echo ""; }
|
||
pkg_cand() { apt-cache policy "$1" 2>/dev/null | awk '/Candidate:/ {print $2}'; }
|
||
|
||
ver_ge() { dpkg --compare-versions "$1" ge "$2"; }
|
||
ver_gt() { dpkg --compare-versions "$1" gt "$2"; }
|
||
ver_lt() { dpkg --compare-versions "$1" lt "$2"; }
|
||
|
||
# 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"; }
|
||
|
||
# Offer upgrade for pve-container/lxc-pve if candidate > installed; optional auto-retry pct create
|
||
# Returns:
|
||
# 0 = no upgrade needed
|
||
# 1 = upgraded (and if do_retry=yes and retry succeeded, creation done)
|
||
# 2 = user declined
|
||
# 3 = upgrade attempted but failed OR retry failed
|
||
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
|
||
|
||
_pvec_i="$(pkg_ver pve-container)"
|
||
_lxcp_i="$(pkg_ver lxc-pve)"
|
||
_pvec_c="$(pkg_cand pve-container)"
|
||
_lxcp_c="$(pkg_cand lxc-pve)"
|
||
|
||
if [[ -n "$_pvec_c" && "$_pvec_c" != "none" ]]; then
|
||
ver_gt "$_pvec_c" "${_pvec_i:-0}" && need=1
|
||
fi
|
||
if [[ -n "$_lxcp_c" && "$_lxcp_c" != "none" ]]; then
|
||
ver_gt "$_lxcp_c" "${_lxcp_i:-0}" && need=1
|
||
fi
|
||
if [[ $need -eq 0 ]]; then
|
||
msg_debug "No newer candidate for pve-container/lxc-pve (installed=$_pvec_i/$_lxcp_i, cand=$_pvec_c/$_lxcp_c)"
|
||
return 0
|
||
fi
|
||
|
||
echo
|
||
echo "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
|
||
read -rp "Do you want to upgrade now? [y/N] " _ans
|
||
case "${_ans,,}" in
|
||
y | yes)
|
||
msg_info "Upgrading Proxmox LXC stack (pve-container, lxc-pve)"
|
||
if apt-get update -qq && apt-get install -y --only-upgrade pve-container lxc-pve; then
|
||
msg_ok "LXC stack upgraded."
|
||
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
|
||
msg_ok "Container created successfully after upgrade."
|
||
return 1
|
||
else
|
||
msg_error "pct create still failed after upgrade. See $LOGFILE"
|
||
return 3
|
||
fi
|
||
fi
|
||
return 1
|
||
else
|
||
msg_error "Upgrade failed. Please check APT output."
|
||
return 3
|
||
fi
|
||
;;
|
||
*) return 2 ;;
|
||
esac
|
||
}
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# Storage discovery / selection helpers
|
||
# ------------------------------------------------------------------------------
|
||
resolve_storage_preselect() {
|
||
local class="$1" preselect="$2" required_content=""
|
||
case "$class" in
|
||
template) required_content="vztmpl" ;;
|
||
container) required_content="rootdir" ;;
|
||
*) return 1 ;;
|
||
esac
|
||
[[ -z "$preselect" ]] && return 1
|
||
if ! pvesm status -content "$required_content" | awk 'NR>1{print $1}' | grep -qx -- "$preselect"; then
|
||
msg_warn "Preselected storage '${preselect}' does not support content '${required_content}' (or not found)"
|
||
return 1
|
||
fi
|
||
|
||
local line total used free
|
||
line="$(pvesm status | awk -v s="$preselect" 'NR>1 && $1==s {print $0}')"
|
||
if [[ -z "$line" ]]; then
|
||
STORAGE_INFO="n/a"
|
||
else
|
||
total="$(awk '{print $4}' <<<"$line")"
|
||
used="$(awk '{print $5}' <<<"$line")"
|
||
free="$(awk '{print $6}' <<<"$line")"
|
||
local total_h used_h free_h
|
||
if command -v numfmt >/dev/null 2>&1; then
|
||
total_h="$(numfmt --to=iec --suffix=B --format %.1f "$total" 2>/dev/null || echo "$total")"
|
||
used_h="$(numfmt --to=iec --suffix=B --format %.1f "$used" 2>/dev/null || echo "$used")"
|
||
free_h="$(numfmt --to=iec --suffix=B --format %.1f "$free" 2>/dev/null || echo "$free")"
|
||
STORAGE_INFO="Free: ${free_h} Used: ${used_h}"
|
||
else
|
||
STORAGE_INFO="Free: ${free} Used: ${used}"
|
||
fi
|
||
fi
|
||
STORAGE_RESULT="$preselect"
|
||
return 0
|
||
}
|
||
|
||
check_storage_support() {
|
||
local CONTENT="$1" VALID=0
|
||
while IFS= read -r line; do
|
||
local STORAGE_NAME
|
||
STORAGE_NAME=$(awk '{print $1}' <<<"$line")
|
||
[[ -n "$STORAGE_NAME" ]] && VALID=1
|
||
done < <(pvesm status -content "$CONTENT" 2>/dev/null | awk 'NR>1')
|
||
[[ $VALID -eq 1 ]]
|
||
}
|
||
|
||
select_storage() {
|
||
local CLASS=$1 CONTENT CONTENT_LABEL
|
||
case $CLASS in
|
||
container)
|
||
CONTENT='rootdir'
|
||
CONTENT_LABEL='Container'
|
||
;;
|
||
template)
|
||
CONTENT='vztmpl'
|
||
CONTENT_LABEL='Container 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'"
|
||
return 1
|
||
;;
|
||
esac
|
||
|
||
if [[ "$CONTENT" == "rootdir" && -n "${STORAGE:-}" ]]; then
|
||
if pvesm status -content "$CONTENT" | awk 'NR>1 {print $1}' | grep -qx "$STORAGE"; then
|
||
STORAGE_RESULT="$STORAGE"
|
||
msg_info "Using preset storage: $STORAGE_RESULT for $CONTENT_LABEL"
|
||
return 0
|
||
else
|
||
msg_error "Preset storage '$STORAGE' is not valid for content type '$CONTENT'."
|
||
return 2
|
||
fi
|
||
fi
|
||
|
||
declare -A STORAGE_MAP
|
||
local -a MENU=()
|
||
local COL_WIDTH=0
|
||
|
||
while read -r TAG TYPE _ TOTAL USED FREE _; do
|
||
[[ -n "$TAG" && -n "$TYPE" ]] || continue
|
||
local DISPLAY="${TAG} (${TYPE})"
|
||
local USED_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$USED")
|
||
local FREE_FMT=$(numfmt --to=iec --from-unit=K --format %.1f <<<"$FREE")
|
||
local INFO="Free: ${FREE_FMT}B Used: ${USED_FMT}B"
|
||
STORAGE_MAP["$DISPLAY"]="$TAG"
|
||
MENU+=("$DISPLAY" "$INFO" "OFF")
|
||
((${#DISPLAY} > COL_WIDTH)) && COL_WIDTH=${#DISPLAY}
|
||
done < <(pvesm status -content "$CONTENT" | awk 'NR>1')
|
||
|
||
if [[ ${#MENU[@]} -eq 0 ]]; then
|
||
msg_error "No storage found for content type '$CONTENT'."
|
||
return 2
|
||
fi
|
||
|
||
if [[ $((${#MENU[@]} / 3)) -eq 1 ]]; then
|
||
STORAGE_RESULT="${STORAGE_MAP[${MENU[0]}]}"
|
||
STORAGE_INFO="${MENU[1]}"
|
||
return 0
|
||
fi
|
||
|
||
local WIDTH=$((COL_WIDTH + 42))
|
||
while true; do
|
||
local DISPLAY_SELECTED
|
||
DISPLAY_SELECTED=$(whiptail --backtitle "Proxmox VE Helper Scripts" \
|
||
--title "Storage Pools" \
|
||
--radiolist "Which storage pool for ${CONTENT_LABEL,,}?\n(Spacebar to select)" \
|
||
16 "$WIDTH" 6 "${MENU[@]}" 3>&1 1>&2 2>&3) || exit_script
|
||
|
||
DISPLAY_SELECTED=$(sed 's/[[:space:]]*$//' <<<"$DISPLAY_SELECTED")
|
||
if [[ -z "$DISPLAY_SELECTED" || -z "${STORAGE_MAP[$DISPLAY_SELECTED]+_}" ]]; then
|
||
whiptail --msgbox "No valid storage selected. Please try again." 8 58
|
||
continue
|
||
fi
|
||
STORAGE_RESULT="${STORAGE_MAP[$DISPLAY_SELECTED]}"
|
||
for ((i = 0; i < ${#MENU[@]}; i += 3)); do
|
||
if [[ "${MENU[$i]}" == "$DISPLAY_SELECTED" ]]; then
|
||
STORAGE_INFO="${MENU[$i + 1]}"
|
||
break
|
||
fi
|
||
done
|
||
return 0
|
||
done
|
||
}
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# Required input variables
|
||
# ------------------------------------------------------------------------------
|
||
[[ "${CTID:-}" ]] || {
|
||
msg_error "You need to set 'CTID' variable."
|
||
exit 203
|
||
}
|
||
[[ "${PCT_OSTYPE:-}" ]] || {
|
||
msg_error "You need to set 'PCT_OSTYPE' variable."
|
||
exit 204
|
||
}
|
||
|
||
msg_debug "CTID=$CTID"
|
||
msg_debug "PCT_OSTYPE=$PCT_OSTYPE"
|
||
msg_debug "PCT_OSVERSION=${PCT_OSVERSION:-default}"
|
||
|
||
# ID checks
|
||
[[ "$CTID" -ge 100 ]] || {
|
||
msg_error "ID cannot be less than 100."
|
||
exit 205
|
||
}
|
||
if qm status "$CTID" &>/dev/null || pct status "$CTID" &>/dev/null; then
|
||
echo -e "ID '$CTID' is already in use."
|
||
unset CTID
|
||
msg_error "Cannot use ID that is already in use."
|
||
exit 206
|
||
fi
|
||
|
||
# Storage capability check
|
||
check_storage_support "rootdir" || {
|
||
msg_error "No valid storage found for 'rootdir' [Container]"
|
||
exit 1
|
||
}
|
||
check_storage_support "vztmpl" || {
|
||
msg_error "No valid storage found for 'vztmpl' [Template]"
|
||
exit 1
|
||
}
|
||
|
||
# Template storage selection
|
||
if resolve_storage_preselect template "${TEMPLATE_STORAGE:-}"; then
|
||
TEMPLATE_STORAGE="$STORAGE_RESULT"
|
||
TEMPLATE_STORAGE_INFO="$STORAGE_INFO"
|
||
msg_ok "Storage ${BL}${TEMPLATE_STORAGE}${CL} (${TEMPLATE_STORAGE_INFO}) [Template]"
|
||
else
|
||
while true; do
|
||
if select_storage template; then
|
||
TEMPLATE_STORAGE="$STORAGE_RESULT"
|
||
TEMPLATE_STORAGE_INFO="$STORAGE_INFO"
|
||
msg_ok "Storage ${BL}${TEMPLATE_STORAGE}${CL} (${TEMPLATE_STORAGE_INFO}) [Template]"
|
||
break
|
||
fi
|
||
done
|
||
fi
|
||
|
||
# Container storage selection
|
||
if resolve_storage_preselect container "${CONTAINER_STORAGE:-}"; then
|
||
CONTAINER_STORAGE="$STORAGE_RESULT"
|
||
CONTAINER_STORAGE_INFO="$STORAGE_INFO"
|
||
msg_ok "Storage ${BL}${CONTAINER_STORAGE}${CL} (${CONTAINER_STORAGE_INFO}) [Container]"
|
||
else
|
||
while true; do
|
||
if select_storage container; then
|
||
CONTAINER_STORAGE="$STORAGE_RESULT"
|
||
CONTAINER_STORAGE_INFO="$STORAGE_INFO"
|
||
msg_ok "Storage ${BL}${CONTAINER_STORAGE}${CL} (${CONTAINER_STORAGE_INFO}) [Container]"
|
||
break
|
||
fi
|
||
done
|
||
fi
|
||
|
||
# Validate content types
|
||
msg_info "Validating content types of storage '$CONTAINER_STORAGE'"
|
||
STORAGE_CONTENT=$(grep -A4 -E "^(zfspool|dir|lvmthin|lvm): $CONTAINER_STORAGE" /etc/pve/storage.cfg | grep content | awk '{$1=""; print $0}' | xargs)
|
||
msg_debug "Storage '$CONTAINER_STORAGE' has content types: $STORAGE_CONTENT"
|
||
grep -qw "rootdir" <<<"$STORAGE_CONTENT" || {
|
||
msg_error "Storage '$CONTAINER_STORAGE' does not support 'rootdir'. Cannot create LXC."
|
||
exit 217
|
||
}
|
||
msg_ok "Storage '$CONTAINER_STORAGE' supports 'rootdir'"
|
||
|
||
msg_info "Validating content types of template storage '$TEMPLATE_STORAGE'"
|
||
TEMPLATE_CONTENT=$(grep -A4 -E "^[^:]+: $TEMPLATE_STORAGE" /etc/pve/storage.cfg | grep content | awk '{$1=""; print $0}' | xargs)
|
||
msg_debug "Template storage '$TEMPLATE_STORAGE' has content types: $TEMPLATE_CONTENT"
|
||
if ! grep -qw "vztmpl" <<<"$TEMPLATE_CONTENT"; then
|
||
msg_warn "Template storage '$TEMPLATE_STORAGE' does not declare 'vztmpl'. This may cause pct create to fail."
|
||
else
|
||
msg_ok "Template storage '$TEMPLATE_STORAGE' supports 'vztmpl'"
|
||
fi
|
||
|
||
# Free space check
|
||
STORAGE_FREE=$(pvesm status | awk -v s="$CONTAINER_STORAGE" '$1 == s { print $6 }')
|
||
REQUIRED_KB=$((${PCT_DISK_SIZE:-8} * 1024 * 1024))
|
||
[[ "$STORAGE_FREE" -ge "$REQUIRED_KB" ]] || {
|
||
msg_error "Not enough space on '$CONTAINER_STORAGE'. Needed: ${PCT_DISK_SIZE:-8}G."
|
||
exit 214
|
||
}
|
||
|
||
# Cluster quorum (if cluster)
|
||
if [[ -f /etc/pve/corosync.conf ]]; then
|
||
msg_info "Checking cluster quorum"
|
||
if ! pvecm status | awk -F':' '/^Quorate/ { exit ($2 ~ /Yes/) ? 0 : 1 }'; then
|
||
msg_error "Cluster is not quorate. Start all nodes or configure quorum device (QDevice)."
|
||
exit 210
|
||
fi
|
||
msg_ok "Cluster is quorate"
|
||
fi
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# Template discovery & validation
|
||
# ------------------------------------------------------------------------------
|
||
TEMPLATE_SEARCH="${PCT_OSTYPE}-${PCT_OSVERSION:-}"
|
||
case "$PCT_OSTYPE" in
|
||
debian | ubuntu) TEMPLATE_PATTERN="-standard_" ;;
|
||
alpine | fedora | rocky | centos) TEMPLATE_PATTERN="-default_" ;;
|
||
*) TEMPLATE_PATTERN="" ;;
|
||
esac
|
||
|
||
msg_info "Searching for template '$TEMPLATE_SEARCH'"
|
||
|
||
mapfile -t LOCAL_TEMPLATES < <(
|
||
pveam list "$TEMPLATE_STORAGE" 2>/dev/null |
|
||
awk -v s="$TEMPLATE_SEARCH" -v p="$TEMPLATE_PATTERN" '$1 ~ s && $1 ~ p {print $1}' |
|
||
sed 's|.*/||' | sort -t - -k 2 -V
|
||
)
|
||
|
||
pveam update >/dev/null 2>&1 || msg_warn "Could not update template catalog (pveam update failed)."
|
||
mapfile -t ONLINE_TEMPLATES < <(
|
||
pveam available -section system 2>/dev/null |
|
||
sed -n "s/.*\($TEMPLATE_SEARCH.*$TEMPLATE_PATTERN.*\)/\1/p" |
|
||
sort -t - -k 2 -V
|
||
)
|
||
ONLINE_TEMPLATE=""
|
||
[[ ${#ONLINE_TEMPLATES[@]} -gt 0 ]] && ONLINE_TEMPLATE="${ONLINE_TEMPLATES[-1]}"
|
||
|
||
if [[ ${#LOCAL_TEMPLATES[@]} -gt 0 ]]; then
|
||
TEMPLATE="${LOCAL_TEMPLATES[-1]}"
|
||
TEMPLATE_SOURCE="local"
|
||
else
|
||
TEMPLATE="$ONLINE_TEMPLATE"
|
||
TEMPLATE_SOURCE="online"
|
||
fi
|
||
|
||
TEMPLATE_PATH="$(pvesm path $TEMPLATE_STORAGE:vztmpl/$TEMPLATE 2>/dev/null || true)"
|
||
if [[ -z "$TEMPLATE_PATH" ]]; then
|
||
TEMPLATE_BASE=$(awk -v s="$TEMPLATE_STORAGE" '$1==s {f=1} f && /path/ {print $2; exit}' /etc/pve/storage.cfg)
|
||
[[ -n "$TEMPLATE_BASE" ]] && TEMPLATE_PATH="$TEMPLATE_BASE/template/cache/$TEMPLATE"
|
||
fi
|
||
[[ -n "$TEMPLATE_PATH" ]] || {
|
||
msg_error "Unable to resolve template path for $TEMPLATE_STORAGE. Check storage type and permissions."
|
||
exit 220
|
||
}
|
||
|
||
msg_ok "Template ${BL}$TEMPLATE${CL} [$TEMPLATE_SOURCE]"
|
||
msg_debug "Resolved TEMPLATE_PATH=$TEMPLATE_PATH"
|
||
|
||
NEED_DOWNLOAD=0
|
||
if [[ ! -f "$TEMPLATE_PATH" ]]; then
|
||
msg_info "Template not present locally – will download."
|
||
NEED_DOWNLOAD=1
|
||
elif [[ ! -r "$TEMPLATE_PATH" ]]; then
|
||
msg_error "Template file exists but is not readable – check permissions."
|
||
exit 221
|
||
elif [[ "$(stat -c%s "$TEMPLATE_PATH")" -lt 1000000 ]]; then
|
||
if [[ -n "$ONLINE_TEMPLATE" ]]; then
|
||
msg_warn "Template file too small (<1MB) – re-downloading."
|
||
NEED_DOWNLOAD=1
|
||
else
|
||
msg_warn "Template looks too small, but no online version exists. Keeping local file."
|
||
fi
|
||
elif ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then
|
||
if [[ -n "$ONLINE_TEMPLATE" ]]; then
|
||
msg_warn "Template appears corrupted – re-downloading."
|
||
NEED_DOWNLOAD=1
|
||
else
|
||
msg_warn "Template appears corrupted, but no online version exists. Keeping local file."
|
||
fi
|
||
else
|
||
msg_ok "Template $TEMPLATE is present and valid."
|
||
fi
|
||
|
||
if [[ "$TEMPLATE_SOURCE" == "local" && -n "$ONLINE_TEMPLATE" && "$TEMPLATE" != "$ONLINE_TEMPLATE" ]]; then
|
||
msg_warn "Local template is outdated: $TEMPLATE (latest available: $ONLINE_TEMPLATE)"
|
||
if whiptail --yesno "A newer template is available:\n$ONLINE_TEMPLATE\n\nDo you want to download and use it instead?" 12 70; then
|
||
TEMPLATE="$ONLINE_TEMPLATE"
|
||
NEED_DOWNLOAD=1
|
||
else
|
||
msg_info "Continuing with local template $TEMPLATE"
|
||
fi
|
||
fi
|
||
|
||
if [[ "$NEED_DOWNLOAD" -eq 1 ]]; then
|
||
[[ -f "$TEMPLATE_PATH" ]] && rm -f "$TEMPLATE_PATH"
|
||
for attempt in {1..3}; do
|
||
msg_info "Attempt $attempt: Downloading template $TEMPLATE to $TEMPLATE_STORAGE"
|
||
if pveam download "$TEMPLATE_STORAGE" "$TEMPLATE" >/dev/null 2>&1; then
|
||
msg_ok "Template download successful."
|
||
break
|
||
fi
|
||
if [[ $attempt -eq 3 ]]; then
|
||
msg_error "Failed after 3 attempts. Please check network access, permissions, or manually run:\n pveam download $TEMPLATE_STORAGE $TEMPLATE"
|
||
exit 222
|
||
fi
|
||
sleep $((attempt * 5))
|
||
done
|
||
fi
|
||
|
||
if ! pveam list "$TEMPLATE_STORAGE" 2>/dev/null | grep -q "$TEMPLATE"; then
|
||
msg_error "Template $TEMPLATE not available in storage $TEMPLATE_STORAGE after download."
|
||
exit 223
|
||
fi
|
||
msg_ok "Template $TEMPLATE is ready for container creation."
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# Dynamic preflight for Debian 13.x: offer upgrade if available (no hard mins)
|
||
# ------------------------------------------------------------------------------
|
||
if [[ "$PCT_OSTYPE" == "debian" ]]; then
|
||
OSVER="$(parse_template_osver "$TEMPLATE")"
|
||
if [[ -n "$OSVER" ]]; then
|
||
# Proactive, aber ohne Abbruch – nur Angebot
|
||
offer_lxc_stack_upgrade_and_maybe_retry "no" || true
|
||
fi
|
||
fi
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# Create LXC Container
|
||
# ------------------------------------------------------------------------------
|
||
msg_info "Creating LXC container"
|
||
|
||
# Ensure subuid/subgid entries exist
|
||
grep -q "root:100000:65536" /etc/subuid || echo "root:100000:65536" >>/etc/subuid
|
||
grep -q "root:100000:65536" /etc/subgid || echo "root:100000:65536" >>/etc/subgid
|
||
|
||
# Assemble pct options
|
||
PCT_OPTIONS=(${PCT_OPTIONS[@]:-${DEFAULT_PCT_OPTIONS[@]}})
|
||
[[ " ${PCT_OPTIONS[*]} " =~ " -rootfs " ]] || PCT_OPTIONS+=(-rootfs "$CONTAINER_STORAGE:${PCT_DISK_SIZE:-8}")
|
||
|
||
# Lock by template file (avoid concurrent downloads/creates)
|
||
lockfile="/tmp/template.${TEMPLATE}.lock"
|
||
exec 9>"$lockfile" || {
|
||
msg_error "Failed to create lock file '$lockfile'."
|
||
exit 200
|
||
}
|
||
flock -w 60 9 || {
|
||
msg_error "Timeout while waiting for template lock."
|
||
exit 211
|
||
}
|
||
|
||
LOGFILE="/tmp/pct_create_${CTID}.log"
|
||
msg_debug "pct create command: pct create $CTID ${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}"
|
||
msg_debug "Logfile: $LOGFILE"
|
||
|
||
# First attempt
|
||
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >"$LOGFILE" 2>&1; then
|
||
msg_error "Container creation failed on ${TEMPLATE_STORAGE}. Checking template..."
|
||
|
||
# Validate template file
|
||
if [[ ! -s "$TEMPLATE_PATH" || "$(stat -c%s "$TEMPLATE_PATH")" -lt 1000000 ]]; then
|
||
msg_warn "Template file too small or missing – re-downloading."
|
||
rm -f "$TEMPLATE_PATH"
|
||
pveam download "$TEMPLATE_STORAGE" "$TEMPLATE"
|
||
elif ! tar -tf "$TEMPLATE_PATH" &>/dev/null; then
|
||
if [[ -n "$ONLINE_TEMPLATE" ]]; then
|
||
msg_warn "Template appears corrupted – re-downloading."
|
||
rm -f "$TEMPLATE_PATH"
|
||
pveam download "$TEMPLATE_STORAGE" "$TEMPLATE"
|
||
else
|
||
msg_warn "Template appears corrupted, but no online version exists. Skipping re-download."
|
||
fi
|
||
fi
|
||
|
||
# Retry after repair
|
||
if ! pct create "$CTID" "${TEMPLATE_STORAGE}:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >>"$LOGFILE" 2>&1; then
|
||
# Fallback to local storage
|
||
if [[ "$TEMPLATE_STORAGE" != "local" ]]; then
|
||
msg_warn "Retrying container creation with fallback to local storage..."
|
||
LOCAL_TEMPLATE_PATH="/var/lib/vz/template/cache/$TEMPLATE"
|
||
if [[ ! -f "$LOCAL_TEMPLATE_PATH" ]]; then
|
||
msg_info "Downloading template to local..."
|
||
pveam download local "$TEMPLATE" >/dev/null 2>&1
|
||
fi
|
||
if pct create "$CTID" "local:vztmpl/${TEMPLATE}" "${PCT_OPTIONS[@]}" >>"$LOGFILE" 2>&1; then
|
||
msg_ok "Container successfully created using local fallback."
|
||
else
|
||
# --- Dynamic stack upgrade + auto-retry on the well-known error pattern ---
|
||
if grep -qiE 'unsupported .* version' "$LOGFILE"; then
|
||
echo
|
||
echo "pct reported 'unsupported ... version' – your LXC stack might be too old for this template."
|
||
echo "We can try to upgrade 'pve-container' and 'lxc-pve' now and retry automatically."
|
||
if offer_lxc_stack_upgrade_and_maybe_retry "yes"; then
|
||
: # success after retry
|
||
else
|
||
rc=$?
|
||
case $rc in
|
||
2) echo "Upgrade was declined. Please update and re-run:
|
||
apt update && apt install --only-upgrade pve-container lxc-pve" ;;
|
||
3) echo "Upgrade and/or retry failed. Please inspect: $LOGFILE" ;;
|
||
esac
|
||
exit 231
|
||
fi
|
||
else
|
||
msg_error "Container creation failed even with local fallback. See $LOGFILE"
|
||
if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then
|
||
set -x
|
||
bash -x -c "pct create $CTID local:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}" 2>&1 | tee -a "$LOGFILE"
|
||
set +x
|
||
fi
|
||
exit 209
|
||
fi
|
||
fi
|
||
else
|
||
msg_error "Container creation failed on local storage. See $LOGFILE"
|
||
# --- Dynamic stack upgrade + auto-retry on the well-known error pattern ---
|
||
if grep -qiE 'unsupported .* version' "$LOGFILE"; then
|
||
echo
|
||
echo "pct reported 'unsupported ... version' – your LXC stack might be too old for this template."
|
||
echo "We can try to upgrade 'pve-container' and 'lxc-pve' now and retry automatically."
|
||
if offer_lxc_stack_upgrade_and_maybe_retry "yes"; then
|
||
: # success after retry
|
||
else
|
||
rc=$?
|
||
case $rc in
|
||
2) echo "Upgrade was declined. Please update and re-run:
|
||
apt update && apt install --only-upgrade pve-container lxc-pve" ;;
|
||
3) echo "Upgrade and/or retry failed. Please inspect: $LOGFILE" ;;
|
||
esac
|
||
exit 231
|
||
fi
|
||
else
|
||
if whiptail --yesno "pct create failed.\nDo you want to enable verbose debug mode and view detailed logs?" 12 70; then
|
||
set -x
|
||
bash -x -c "pct create $CTID local:vztmpl/${TEMPLATE} ${PCT_OPTIONS[*]}" 2>&1 | tee -a "$LOGFILE"
|
||
set +x
|
||
fi
|
||
exit 209
|
||
fi
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# Verify container exists
|
||
pct list | awk '{print $1}' | grep -qx "$CTID" || {
|
||
msg_error "Container ID $CTID not listed in 'pct list'. See $LOGFILE"
|
||
exit 215
|
||
}
|
||
|
||
# Verify config rootfs
|
||
grep -q '^rootfs:' "/etc/pve/lxc/$CTID.conf" || {
|
||
msg_error "RootFS entry missing in container config. See $LOGFILE"
|
||
exit 216
|
||
}
|
||
|
||
msg_ok "LXC Container ${BL}$CTID${CL} ${GN}was successfully created."
|