Improve validation and robustness in container scripts

Enhances timezone handling by mapping 'Etc/*' zones to 'host', adds stricter password validation (removing leading dashes and enforcing minimum length), and improves container ID validation. Adds storage space validation before container creation and when selecting storage. Implements retry logic and stale lock cleanup for template lock files to avoid stuck processes. Improves GitHub release fetching by adding a fallback to codeload.github.com for complex tag names.
This commit is contained in:
CanbiZ (MickLesk) 2026-01-21 15:48:22 +01:00
parent eb4c45c9fe
commit c2b890baa6
2 changed files with 121 additions and 29 deletions

View File

@ -1789,7 +1789,10 @@ advanced_settings() {
elif [ -f /etc/timezone ]; then
_host_timezone=$(cat /etc/timezone 2>/dev/null || echo "")
fi
# pct doesn't accept Etc/* zones - map to 'host' instead
[[ "${_host_timezone:-}" == Etc/* ]] && _host_timezone="host"
local _ct_timezone="${var_timezone:-$_host_timezone}"
[[ "${_ct_timezone:-}" == Etc/* ]] && _ct_timezone="host"
# Helper to show current progress
show_progress() {
@ -1882,8 +1885,16 @@ advanced_settings() {
((STEP++))
elif [[ "$PW1" == *" "* ]]; then
whiptail --msgbox "Password cannot contain spaces." 8 58
elif ((${#PW1} < 5)); then
whiptail --msgbox "Password must be at least 5 characters." 8 58
else
# Clean up leading dashes from password
local _pw1_clean="$PW1"
while [[ "$_pw1_clean" == -* ]]; do
_pw1_clean="${_pw1_clean#-}"
done
if [[ -z "$_pw1_clean" ]]; then
whiptail --msgbox "Password cannot be only '-' characters." 8 58
elif ((${#_pw1_clean} < 5)); then
whiptail --msgbox "Password must be at least 5 characters (after removing leading '-')." 8 70
else
# Verify password
if PW2=$(whiptail --backtitle "Proxmox VE Helper Scripts [Step $STEP/$MAX_STEP]" \
@ -1892,7 +1903,7 @@ advanced_settings() {
--passwordbox "\nVerify Root Password" 10 58 \
3>&1 1>&2 2>&3); then
if [[ "$PW1" == "$PW2" ]]; then
_pw="-password $PW1"
_pw="--password $_pw1_clean"
_pw_display="********"
((STEP++))
else
@ -1902,6 +1913,7 @@ advanced_settings() {
((STEP--))
fi
fi
fi
else
((STEP--))
fi
@ -1916,8 +1928,23 @@ advanced_settings() {
--ok-button "Next" --cancel-button "Back" \
--inputbox "\nSet Container ID" 10 58 "$_ct_id" \
3>&1 1>&2 2>&3); then
_ct_id="${result:-$NEXTID}"
local input_id="${result:-$NEXTID}"
# Validate container ID is numeric
if ! [[ "$input_id" =~ ^[0-9]+$ ]]; then
whiptail --msgbox "Container ID must be numeric." 8 58
continue
fi
# Validate container ID is available
if ! validate_container_id "$input_id"; then
if whiptail --yesno "Container/VM ID $input_id is already in use.\n\nWould you like to use the next available ID: $(get_valid_container_id "$input_id")?" 10 58; then
_ct_id=$(get_valid_container_id "$input_id")
((STEP++))
fi
# else stay on this step
else
_ct_id="$input_id"
((STEP++))
fi
else
((STEP--))
fi
@ -2803,6 +2830,8 @@ install_script() {
else
timezone="UTC"
fi
# pct doesn't accept Etc/* zones - map to 'host' instead
[[ "${timezone:-}" == Etc/* ]] && timezone="host"
# Show APP Header
header_info
@ -3374,10 +3403,37 @@ build_container() {
export DEV_MODE_LOGS="${DEV_MODE_LOGS:-false}"
export DEV_MODE_DRYRUN="${DEV_MODE_DRYRUN:-false}"
# Validate storage space before container creation
if [[ -n "$CONTAINER_STORAGE" ]]; then
msg_info "Validating storage space"
if ! validate_storage_space "$CONTAINER_STORAGE" "$DISK_SIZE" "no"; then
local free_space
free_space=$(pvesm status 2>/dev/null | awk -v s="$CONTAINER_STORAGE" '$1 == s { print $6 }')
local free_fmt
free_fmt=$(numfmt --to=iec --from-unit=1024 --suffix=B --format %.1f "$free_space" 2>/dev/null || echo "${free_space}KB")
msg_error "Not enough space on '$CONTAINER_STORAGE'. Required: ${DISK_SIZE}GB, Available: ${free_fmt}"
exit 214
fi
msg_ok "Storage space validated"
fi
# Build PCT_OPTIONS as multi-line string
PCT_OPTIONS_STRING=" -features $FEATURES
-hostname $HN
PCT_OPTIONS_STRING=""
# Add features if set
if [ -n "$FEATURES" ]; then
PCT_OPTIONS_STRING=" -features $FEATURES"
fi
# Add hostname
PCT_OPTIONS_STRING="$PCT_OPTIONS_STRING
-hostname $HN"
# Add tags if set
if [ -n "$TAGS" ]; then
PCT_OPTIONS_STRING="$PCT_OPTIONS_STRING
-tags $TAGS"
fi
# Add storage if specified
if [ -n "$SD" ]; then
@ -4234,6 +4290,11 @@ select_storage() {
if [[ $((${#MENU[@]} / 3)) -eq 1 ]]; then
STORAGE_RESULT="${STORAGE_MAP[${MENU[0]}]}"
STORAGE_INFO="${MENU[1]}"
# Validate storage space for auto-picked container storage
if [[ "$CLASS" == "container" && -n "${DISK_SIZE:-}" ]]; then
validate_storage_space "$STORAGE_RESULT" "$DISK_SIZE" "yes"
fi
return 0
fi
@ -4257,6 +4318,11 @@ select_storage() {
break
fi
done
# Validate storage space for container storage
if [[ "$CLASS" == "container" && -n "${DISK_SIZE:-}" ]]; then
validate_storage_space "$STORAGE_RESULT" "$DISK_SIZE" "yes"
fi
return 0
done
}
@ -4848,14 +4914,35 @@ create_lxc_container() {
# Lock by template file (avoid concurrent downloads/creates)
lockfile="/tmp/template.${TEMPLATE}.lock"
# Cleanup stale lock files (older than 1 hour - likely from crashed processes)
if [[ -f "$lockfile" ]]; then
local lock_age=$(($(date +%s) - $(stat -c %Y "$lockfile" 2>/dev/null || echo 0)))
if [[ $lock_age -gt 3600 ]]; then
msg_warn "Removing stale template lock file (age: ${lock_age}s)"
rm -f "$lockfile"
fi
fi
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."
# Retry logic for template lock (another container creation may be running)
local lock_attempts=0
local max_lock_attempts=10
local lock_wait_time=30
while ! flock -w "$lock_wait_time" 9; do
lock_attempts=$((lock_attempts + 1))
if [[ $lock_attempts -ge $max_lock_attempts ]]; then
msg_error "Timeout while waiting for template lock after ${max_lock_attempts} attempts."
msg_custom "💡" "${YW}" "Another container creation may be stuck. Check running processes or remove: $lockfile"
exit 211
}
fi
msg_custom "⏳" "${YW}" "Another container is being created with this template. Waiting... (attempt ${lock_attempts}/${max_lock_attempts})"
done
LOGFILE="/tmp/pct_create_${CTID}_$(date +%Y%m%d_%H%M%S)_${SESSION_ID}.log"

View File

@ -1776,11 +1776,16 @@ function fetch_and_deploy_gh_release() {
local direct_tarball_url="https://github.com/$repo/archive/refs/tags/$encoded_tag_name.tar.gz"
filename="${app_lc}-${version}.tar.gz"
curl $download_timeout -fsSL -o "$tmpdir/$filename" "$direct_tarball_url" || {
msg_error "Download failed: $direct_tarball_url"
# Try primary URL first, fallback to codeload.github.com for complex tag names
if ! curl $download_timeout -fsSL -o "$tmpdir/$filename" "$direct_tarball_url" 2>/dev/null; then
# Fallback: codeload.github.com handles special chars like @scope/package@version better
local codeload_url="https://codeload.github.com/$repo/tar.gz/refs/tags/$encoded_tag_name"
curl $download_timeout -fsSL -o "$tmpdir/$filename" "$codeload_url" || {
msg_error "Download failed: $direct_tarball_url (and fallback $codeload_url)"
rm -rf "$tmpdir"
return 1
}
fi
mkdir -p "$target"
if [[ "${CLEAN_INSTALL:-0}" == "1" ]]; then