Compare commits

..

3 Commits

Author SHA1 Message Date
MickLesk fe01d670f6 Don't destroy container on error_handler recovery-prompt read failure
Same bug as build.func's install-recovery menu: this "Remove broken
container?" prompt treated a hard read failure (broken/closed stdin,
I/O error) identically to a real 60s timeout, defaulting to pct
destroy either way. Only auto-remove on an actual timeout (read_rc >
128); on a genuine read failure, keep the container instead of
guessing and destroying it.
2026-07-21 23:32:19 +02:00
MickLesk e0df8a80bf Don't destroy container on install-recovery menu read failure
read -t returns >128 on a real timeout, but a lower/plain error code
on a genuine read failure (e.g. broken/closed stdin, I/O error) -
these were treated identically as "no response", so a user whose
keystroke failed to be captured (e.g. a transient stdin I/O error)
got their container silently destroyed instead of kept. Only
auto-remove on an actual timeout; on a hard read failure, keep the
container (reversible) and tell the user how to remove it manually.
2026-07-21 23:27:22 +02:00
MickLesk 3f7283ab66 Clear RETURN traps after temp cleanup
Updates temp-file and temp-dir cleanup traps in `misc/tools.func` to unset the `RETURN` trap after running cleanup. This prevents stale RETURN traps from leaking into later function returns and avoids repeated or unintended cleanup behavior in nested helper flows.
2026-07-21 22:49:58 +02:00
3 changed files with 38 additions and 88 deletions
+12 -77
View File
@@ -3831,78 +3831,6 @@ runtime_script_status_guard() {
return 0
}
# ------------------------------------------------------------------------------
# check_container_os_guard()
#
# - Compares the container OS (/etc/os-release) with the script's recommended
# var_os/var_version before running update_script
# - On mismatch: interactive runs ask whether to continue (default: no);
# headless runs (PHS_SILENT=1 / no tty) abort instead of updating on an
# unsupported base and leaving the app broken mid-update
# - Bypass via var_ignore_os_mismatch=1|yes|true|on (still warns, but continues)
# ------------------------------------------------------------------------------
check_container_os_guard() {
local rec_os="${var_os:-}" rec_ver="${var_version:-}"
rec_os="${rec_os,,}"
[[ -z "$rec_os" || -z "$rec_ver" ]] && return 0
[[ -r /etc/os-release ]] || return 0
local cur_os cur_ver
cur_os="$(. /etc/os-release 2>/dev/null; echo "${ID:-}")"
cur_ver="$(. /etc/os-release 2>/dev/null; echo "${VERSION_ID:-}")"
cur_os="${cur_os,,}"
[[ -z "$cur_os" || -z "$cur_ver" ]] && return 0
# Exact version or prefix on a dot boundary (e.g. alpine 3.22 matches 3.22.1)
if [[ "$cur_os" == "$rec_os" ]] && [[ "$cur_ver" == "$rec_ver" || "$cur_ver" == "$rec_ver".* ]]; then
return 0
fi
case "${var_ignore_os_mismatch:-}" in
1 | yes | true | on)
msg_warn "Container OS is ${cur_os} ${cur_ver} but the script recommends ${rec_os} ${rec_ver} — continuing via var_ignore_os_mismatch (may break, no support)."
return 0
;;
esac
# Persistent opt-out: stores the ignored target version, so the question
# comes back once the script targets a newer OS again
local ignore_file="/usr/local/community-scripts/ignore-os-mismatch"
if [[ -f "$ignore_file" && "$(cat "$ignore_file" 2>/dev/null)" == "${rec_os} ${rec_ver}" ]]; then
msg_warn "Container OS is ${cur_os} ${cur_ver} but the script recommends ${rec_os} ${rec_ver} — continuing (previously ignored via ${ignore_file}, may break, no support)."
return 0
fi
if [[ "${PHS_SILENT:-0}" != "1" ]] && command -v whiptail &>/dev/null && [ -t 0 ] && [[ "$TERM" != "dumb" ]]; then
local choice
choice=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "OS VERSION MISMATCH" --menu \
"This container runs ${cur_os} ${cur_ver}, but this script now targets ${rec_os} ${rec_ver}.\n\nUpdating on the older base OS may fail or leave ${APP:-the application} broken (e.g. required runtime versions are not available).\n\nRecommended: upgrade the container OS to ${rec_os} ${rec_ver} first, then run this update again.\n\nIf you continue anyway, it may break — no support is provided in that case.\n\nContinue anyway?" \
20 70 3 \
"1" "No (cancel update)" \
"2" "Yes (continue this time)" \
"3" "Yes (continue and don't ask again)" \
--nocancel --default-item "1" 3>&1 1>&2 2>&3)
case "$choice" in
2)
msg_warn "Continuing update on ${cur_os} ${cur_ver} despite recommended ${rec_os} ${rec_ver} — may break, no support."
return 0
;;
3)
mkdir -p "${ignore_file%/*}"
echo "${rec_os} ${rec_ver}" >"$ignore_file"
msg_warn "Continuing update on ${cur_os} ${cur_ver}; OS check for ${rec_os} ${rec_ver} disabled via ${ignore_file} — may break, no support."
return 0
;;
esac
msg_error "Update cancelled: container OS ${cur_os} ${cur_ver} does not match the recommended ${rec_os} ${rec_ver}."
return 1
fi
msg_error "Container OS ${cur_os} ${cur_ver} does not match the recommended ${rec_os} ${rec_ver} — skipping update."
msg_error "Upgrade the container OS to ${rec_os} ${rec_ver} first, then run this update again — or bypass this check (may break, no support) with: echo \"${rec_os} ${rec_ver}\" > ${ignore_file}"
return 1
}
# ------------------------------------------------------------------------------
# start()
#
@@ -3924,7 +3852,6 @@ start() {
ensure_profile_loaded
get_lxc_ip
runtime_script_status_guard update || return 0
check_container_os_guard || return 0
update_script
run_addon_updates
update_motd_ip
@@ -3936,7 +3863,6 @@ start() {
ensure_profile_loaded
get_lxc_ip
runtime_script_status_guard update || return 0
check_container_os_guard || return 0
update_script
run_addon_updates
update_motd_ip
@@ -3967,7 +3893,6 @@ start() {
ensure_profile_loaded
get_lxc_ip
runtime_script_status_guard update || return 0
check_container_os_guard || return 0
update_script
run_addon_updates
update_motd_ip
@@ -5215,7 +5140,10 @@ EOF
echo -en "${YW}Select option [1-${max_option}] (default: 1, auto-remove in 60s): ${CL}"
local response=""
if read -t 60 -r response; then
local read_rc
read -t 60 -r response
read_rc=$?
if [[ $read_rc -eq 0 ]]; then
case "${response:-1}" in
1)
# Remove container
@@ -5434,13 +5362,20 @@ EOF
fi
;;
esac
else
elif [[ $read_rc -gt 128 ]]; then
# Timeout - auto-remove
echo ""
msg_info "No response - removing container ${CTID}"
pct stop "$CTID" &>/dev/null || true
pct destroy "$CTID" &>/dev/null || true
msg_ok "Container ${CTID} removed"
else
# read itself failed (e.g. broken/closed stdin, I/O error) rather than
# timing out - don't guess and destroy the container on a read we
# couldn't actually capture; keep it since that's the reversible choice.
echo ""
msg_error "Could not read your response (stdin error) - keeping container ${CTID} for safety."
msg_error "Remove it manually if not needed: pct destroy ${CTID}"
fi
# Force one final status update attempt after cleanup
+17 -2
View File
@@ -497,7 +497,10 @@ error_handler() {
fi
local response=""
if read -t 60 -r response; then
local read_rc
read -t 60 -r response
read_rc=$?
if [[ $read_rc -eq 0 ]]; then
if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then
echo ""
if declare -f msg_info >/dev/null 2>&1; then
@@ -520,7 +523,7 @@ error_handler() {
echo -e "${YW}Container ${CTID} kept for debugging${CL}"
fi
fi
else
elif [[ $read_rc -gt 128 ]]; then
# Timeout - auto-remove
echo ""
if declare -f msg_info >/dev/null 2>&1; then
@@ -535,6 +538,18 @@ error_handler() {
else
echo -e "${GN}${CL} Container ${CTID} removed"
fi
else
# read itself failed (e.g. broken/closed stdin, I/O error) rather than
# timing out - don't guess and destroy the container on a read we
# couldn't actually capture; keep it since that's the reversible choice.
echo ""
if declare -f msg_error >/dev/null 2>&1; then
msg_error "Could not read your response (stdin error) - keeping container ${CTID} for safety."
msg_error "Remove it manually if not needed: pct destroy ${CTID}"
else
echo -e "${YW}Could not read your response (stdin error) - keeping container ${CTID} for safety.${CL}"
echo -e "${YW}Remove it manually if not needed: pct destroy ${CTID}${CL}"
fi
fi
fi
fi
+9 -9
View File
@@ -2689,7 +2689,7 @@ fetch_and_deploy_gh_tag() {
local tmpdir
tmpdir=$(mktemp -d) || return 1
trap 'rm -rf "$tmpdir"' RETURN
trap 'rm -rf "$tmpdir"; trap - RETURN' RETURN
local tarball_url="https://github.com/${repo}/archive/refs/tags/${version}.tar.gz"
local filename="${app_lc}-${version}.tar.gz"
@@ -2866,7 +2866,7 @@ fetch_and_deploy_gl_tag() {
local tmpdir
tmpdir=$(mktemp -d) || return 1
trap 'rm -rf "$tmpdir"' RETURN
trap 'rm -rf "$tmpdir"; trap - RETURN' RETURN
local filename="${app_lc}-${version_safe}.tar.gz"
msg_info "Fetching GitLab tag: ${app} (${resolved_tag})"
@@ -2974,7 +2974,7 @@ check_for_gh_release() {
local gh_check_json=""
gh_check_json=$(mktemp /tmp/tools-gh-check-XXXXXX) || return 73
trap 'rm -f "${gh_check_json:-}"' RETURN
trap 'rm -f "${gh_check_json:-}"; trap - RETURN' RETURN
# Build auth header if token is available
local header_args=()
@@ -3574,7 +3574,7 @@ fetch_and_deploy_codeberg_release() {
local tmpdir
tmpdir=$(mktemp -d) || return 252
trap 'rm -rf "$tmpdir"' RETURN
trap 'rm -rf "$tmpdir"; trap - RETURN' RETURN
msg_info "Fetching Codeberg tag: $app ($tag_name)"
@@ -3616,7 +3616,7 @@ fetch_and_deploy_codeberg_release() {
local codeberg_rel_json
codeberg_rel_json=$(mktemp /tmp/tools-codeberg-rel-XXXXXX) || return 73
trap 'rm -f "$codeberg_rel_json"; rm -rf "${tmpdir:-}" "${unpack_tmp:-}"' RETURN
trap 'rm -f "$codeberg_rel_json"; rm -rf "${tmpdir:-}" "${unpack_tmp:-}"; trap - RETURN' RETURN
local attempt=0 success=false resp http_code
@@ -4113,7 +4113,7 @@ fetch_and_deploy_gh_release() {
local tmpdir
tmpdir=$(mktemp -d) || return 1
trap 'rm -rf "$tmpdir" "${unpack_tmp:-}"' RETURN
trap 'rm -rf "$tmpdir" "${unpack_tmp:-}"; trap - RETURN' RETURN
local filename="" url=""
msg_info "Fetching GitHub release: $app ($version)"
@@ -9218,7 +9218,7 @@ fetch_and_deploy_from_url() {
msg_error "Failed to create temporary directory"
return 252
}
trap 'rm -rf "$tmpdir" "${unpack_tmp:-}"' RETURN
trap 'rm -rf "$tmpdir" "${unpack_tmp:-}"; trap - RETURN' RETURN
curl -fsSL -o "$tmpdir/$filename" "$url" || {
msg_error "Download failed: $url"
@@ -9434,7 +9434,7 @@ check_for_gl_release() {
local gl_check_json
gl_check_json=$(mktemp /tmp/tools-gl-check-XXXXXX) || return 73
trap 'rm -f "$gl_check_json"' RETURN
trap 'rm -f "$gl_check_json"; trap - RETURN' RETURN
local repo_encoded
repo_encoded=$(printf '%s' "$source" | sed 's|/|%2F|g')
@@ -9724,7 +9724,7 @@ fetch_and_deploy_gl_release() {
local gl_rel_json
gl_rel_json=$(mktemp /tmp/tools-gl-rel-XXXXXX) || return 73
trap 'rm -f "$gl_rel_json"; rm -rf "${tmpdir:-}" "${unpack_tmp:-}"' RETURN
trap 'rm -f "$gl_rel_json"; rm -rf "${tmpdir:-}" "${unpack_tmp:-}"; trap - RETURN' RETURN
local repo_encoded
repo_encoded=$(printf '%s' "$repo" | sed 's|/|%2F|g')