Compare commits

..

6 Commits

Author SHA1 Message Date
MickLesk 036d7ac99c tools.func: complete a stale/partial backup manifest instead of trusting it as-is 2026-08-04 11:51:32 +02:00
community-scripts-pr-app[bot] cf942fb445 Update CHANGELOG.md (#16245)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-03 21:24:22 +00:00
jsspen 0c24310e90 Correction to link displayed when old data structure is detected during an attempted update (#16241) 2026-08-03 23:23:59 +02:00
CanbiZ (MickLesk) 336cdda47d termix: add env backup 2026-08-03 22:56:27 +02:00
community-scripts-pr-app[bot] 268a6957a7 Update CHANGELOG.md (#16236)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-03 14:58:21 +00:00
CanbiZ (MickLesk) e21a11c190 misc: read all interactive prompts from /dev/tty, drain leftover input on read failure (#16235) 2026-08-03 16:57:55 +02:00
10 changed files with 46 additions and 37 deletions
+7
View File
@@ -524,8 +524,15 @@ Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit
- #### 🐞 Bug Fixes - #### 🐞 Bug Fixes
- Correction to link in Paperless-NGX update script [@jsspen](https://github.com/jsspen) ([#16241](https://github.com/community-scripts/ProxmoxVE/pull/16241))
- fix restore env in split pro [@johanngrobe](https://github.com/johanngrobe) ([#16228](https://github.com/community-scripts/ProxmoxVE/pull/16228)) - fix restore env in split pro [@johanngrobe](https://github.com/johanngrobe) ([#16228](https://github.com/community-scripts/ProxmoxVE/pull/16228))
### 💾 Core
- #### 🔧 Refactor
- core: read all interactive prompts from /dev/tty, drain leftover input on read failure (hotfix) [@MickLesk](https://github.com/MickLesk) ([#16235](https://github.com/community-scripts/ProxmoxVE/pull/16235))
## 2026-08-02 ## 2026-08-02
### 🆕 New Scripts ### 🆕 New Scripts
+1 -1
View File
@@ -42,7 +42,7 @@ function update_script() {
echo -e "" echo -e ""
msg_custom "🔄" "Migration required to new data structure (/opt/paperless_data/)" msg_custom "🔄" "Migration required to new data structure (/opt/paperless_data/)"
msg_custom "📖" "Please follow the migration guide:" msg_custom "📖" "Please follow the migration guide:"
echo -e "${GATEWAY}${BGN}https://github.com/community-scripts/ProxmoxVE/discussions/9223${CL}" echo -e "${GATEWAY}${BGN}https://github.com/community-scripts/ProxmoxVE/pull/9223${CL}"
echo -e "" echo -e ""
msg_custom "⚠️" "Update aborted. Please migrate your data first." msg_custom "⚠️" "Update aborted. Please migrate your data first."
exit 253 exit 253
+1 -1
View File
@@ -143,7 +143,7 @@ EOF
fi fi
msg_ok "Migrated Configuration" msg_ok "Migrated Configuration"
create_backup /opt/termix/data /opt/termix/uploads create_backup /opt/termix/data /opt/termix/uploads /opt/termix/.env
CLEAN_INSTALL=1 fetch_and_deploy_gh_release "termix" "Termix-SSH/Termix" "tarball" CLEAN_INSTALL=1 fetch_and_deploy_gh_release "termix" "Termix-SSH/Termix" "tarball"
+1 -1
View File
@@ -26,7 +26,7 @@ create_self_signed_cert
setup_deb822_repo \ setup_deb822_repo \
"passbolt" \ "passbolt" \
"https://download.passbolt.com/pub.key" \ "https://keys.openpgp.org/pks/lookup?op=get&options=mr&search=0x3D1A0346C8E1802F774AEF21DE8B853FC155581D" \
"https://download.passbolt.com/ce/debian" \ "https://download.passbolt.com/ce/debian" \
"buster" \ "buster" \
"stable" "stable"
+1 -1
View File
@@ -126,7 +126,7 @@ network_check() {
fi fi
if [[ $ipv4_connected == false ]]; then if [[ $ipv4_connected == false ]]; then
read -r -p "No Internet detected, would you like to continue anyway? <y/N> " prompt read -r -p "No Internet detected, would you like to continue anyway? <y/N> " prompt </dev/tty
if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then
echo -e "${INFO}${RD}Expect Issues Without Internet${CL}" echo -e "${INFO}${RD}Expect Issues Without Internet${CL}"
else else
+2 -1
View File
@@ -3917,7 +3917,7 @@ run_addon_updates() {
name="${a##*/update_}" name="${a##*/update_}"
printf 'Do you also want to update addon "%s"? (y/N) [60s]: ' "$name" printf 'Do you also want to update addon "%s"? (y/N) [60s]: ' "$name"
ans="" ans=""
if read -r -t 60 ans; then :; else echo; fi if read -r -t 60 ans </dev/tty; then :; else echo; fi
case "${ans,,}" in case "${ans,,}" in
y | yes) y | yes)
bash "$a" || msg_warn "Addon update for $name failed (rc=$?)" bash "$a" || msg_warn "Addon update for $name failed (rc=$?)"
@@ -5729,6 +5729,7 @@ EOF
# read itself failed (e.g. broken/closed stdin, I/O error) rather than # 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 # 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. # couldn't actually capture; keep it since that's the reversible choice.
while read -r -t 0.1 -n 1 _ </dev/tty 2>/dev/null; do :; done
echo "" echo ""
msg_error "Could not read your response (stdin error) - keeping container ${CTID} for safety." 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}" msg_error "Remove it manually if not needed: pct destroy ${CTID}"
+7 -7
View File
@@ -754,7 +754,7 @@ msg_info() {
# Pause mode: Wait for Enter after each step # Pause mode: Wait for Enter after each step
if [[ "${DEV_MODE_PAUSE:-false}" == "true" ]]; then if [[ "${DEV_MODE_PAUSE:-false}" == "true" ]]; then
echo -en "\n${YWB}[PAUSE]${CL} Press Enter to continue..." >&2 echo -en "\n${YWB}[PAUSE]${CL} Press Enter to continue..." >&2
read -r read -r </dev/tty
fi fi
return return
fi fi
@@ -769,7 +769,7 @@ msg_info() {
if [[ "${DEV_MODE_PAUSE:-false}" == "true" ]]; then if [[ "${DEV_MODE_PAUSE:-false}" == "true" ]]; then
stop_spinner stop_spinner
echo -en "\n${YWB}[PAUSE]${CL} Press Enter to continue..." >&2 echo -en "\n${YWB}[PAUSE]${CL} Press Enter to continue..." >&2
read -r read -r </dev/tty
fi fi
} }
@@ -1217,7 +1217,7 @@ prompt_confirm() {
# Interactive prompt with timeout # Interactive prompt with timeout
echo -en "${YW}${message} ${hint} (auto-${default} in ${timeout}s): ${CL}" echo -en "${YW}${message} ${hint} (auto-${default} in ${timeout}s): ${CL}"
if read -t "$timeout" -r response; then if read -t "$timeout" -r response </dev/tty; then
# User provided input # User provided input
response="${response,,}" # lowercase response="${response,,}" # lowercase
case "$response" in case "$response" in
@@ -1310,7 +1310,7 @@ prompt_input() {
# Interactive prompt with timeout # Interactive prompt with timeout
echo -en "${YW}${message}${hint} (auto-default in ${timeout}s): ${CL}" >&2 echo -en "${YW}${message}${hint} (auto-default in ${timeout}s): ${CL}" >&2
if read -t "$timeout" -r response; then if read -t "$timeout" -r response </dev/tty; then
# User provided input (or pressed Enter for empty) # User provided input (or pressed Enter for empty)
if [[ -n "$response" ]]; then if [[ -n "$response" ]]; then
echo "$response" echo "$response"
@@ -1412,7 +1412,7 @@ prompt_input_required() {
echo -en "${YW}${message} (required, timeout ${timeout}s): ${CL}" >&2 echo -en "${YW}${message} (required, timeout ${timeout}s): ${CL}" >&2
if read -t "$timeout" -r response; then if read -t "$timeout" -r response </dev/tty; then
if [[ -z "$response" ]]; then if [[ -z "$response" ]]; then
echo -e "${YW}This field is required. Please enter a value. (attempt ${attempts}/3)${CL}" >&2 echo -e "${YW}This field is required. Please enter a value. (attempt ${attempts}/3)${CL}" >&2
fi fi
@@ -1507,7 +1507,7 @@ prompt_select() {
echo -en "${YW}Select [1-${num_options}] (auto-select ${default} in ${timeout}s): ${CL}" >&2 echo -en "${YW}Select [1-${num_options}] (auto-select ${default} in ${timeout}s): ${CL}" >&2
local response local response
if read -t "$timeout" -r response; then if read -t "$timeout" -r response </dev/tty; then
if [[ -z "$response" ]]; then if [[ -z "$response" ]]; then
# Empty response, use default # Empty response, use default
echo "${options[$((default - 1))]}" echo "${options[$((default - 1))]}"
@@ -1590,7 +1590,7 @@ prompt_password() {
# Interactive prompt with timeout (silent input) # Interactive prompt with timeout (silent input)
echo -en "${YW}${message}${hint} (timeout ${timeout}s): ${CL}" >&2 echo -en "${YW}${message}${hint} (timeout ${timeout}s): ${CL}" >&2
if read -t "$timeout" -rs response; then if read -t "$timeout" -rs response </dev/tty; then
echo "" >&2 # Newline after hidden input echo "" >&2 # Newline after hidden input
if [[ -n "$response" ]]; then if [[ -n "$response" ]]; then
# Validate minimum length # Validate minimum length
+2 -1
View File
@@ -499,7 +499,7 @@ error_handler() {
local response="" local response=""
local read_rc local read_rc
read -t 60 -r response read -t 60 -r response </dev/tty
read_rc=$? read_rc=$?
if [[ $read_rc -eq 0 ]]; then if [[ $read_rc -eq 0 ]]; then
if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then
@@ -543,6 +543,7 @@ error_handler() {
# read itself failed (e.g. broken/closed stdin, I/O error) rather than # 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 # 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. # couldn't actually capture; keep it since that's the reversible choice.
while read -r -t 0.1 -n 1 _ </dev/tty 2>/dev/null; do :; done
echo "" echo ""
if declare -f msg_error >/dev/null 2>&1; then 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 "Could not read your response (stdin error) - keeping container ${CTID} for safety."
+1 -1
View File
@@ -201,7 +201,7 @@ network_check() {
# If both IPv4 and IPv6 checks fail, prompt the user # If both IPv4 and IPv6 checks fail, prompt the user
if [[ $ipv4_connected == false && $ipv6_connected == false ]]; then if [[ $ipv4_connected == false && $ipv6_connected == false ]]; then
read -r -p "No Internet detected, would you like to continue anyway? <y/N> " prompt read -r -p "No Internet detected, would you like to continue anyway? <y/N> " prompt </dev/tty
if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then if [[ "${prompt,,}" =~ ^(y|yes)$ ]]; then
echo -e "${INFO}${RD}Expect Issues Without Internet${CL}" echo -e "${INFO}${RD}Expect Issues Without Internet${CL}"
else else
+23 -23
View File
@@ -1292,14 +1292,17 @@ create_temp_dir() {
# - Copies each given file/directory into a persistent store at # - Copies each given file/directory into a persistent store at
# /opt/<NSAPP>.backup, mirroring its absolute path inside the store, and # /opt/<NSAPP>.backup, mirroring its absolute path inside the store, and
# records it in a manifest so restore_backup needs no arguments. # records it in a manifest so restore_backup needs no arguments.
# - Idempotent: if a store from a previous (failed) run already exists, it is # - Idempotent per path: a path already recorded in the manifest (from this
# left untouched and no new backup is taken. This keeps the last-known-good # run or a previous failed one) is left untouched, keeping the
# data instead of overwriting it with now-partially-updated data on retry. # last-known-good copy instead of overwriting it with now-partially-updated
# data on retry. Any requested path NOT yet in the manifest is backed up
# now and appended - so a manifest left incomplete by an interrupted prior
# run gets completed instead of silently missing paths on restore.
# - Missing source paths are skipped with a warning (not fatal). # - Missing source paths are skipped with a warning (not fatal).
# - Aborts the update on copy failure: if any file/dir cannot be backed up, # - Aborts the update on copy failure: if any file/dir cannot be backed up,
# the half-written store is removed and the script exits, so the update # the script exits before the update runs against unprotected data (the
# never runs against unprotected data (and a retry re-attempts a clean # paths already recorded are left in place, so a retry only redoes the one
# backup rather than skipping it). # that failed).
# #
# restore_backup # restore_backup
# - Copies every path recorded in the manifest back to its origin (replacing # - Copies every path recorded in the manifest back to its origin (replacing
@@ -1318,19 +1321,17 @@ create_backup() {
return 0 return 0
} }
if [[ -f "$manifest" ]]; then if ! mkdir -p "$store" || ! touch "$manifest"; then
msg_ok "Existing backup found at ${store}, skipping backup" msg_error "Backup failed: could not create store at ${store} - aborting update"
return 0 exit 1
fi fi
msg_info "Backing up data" msg_info "Backing up data"
if ! mkdir -p "$store" || ! : >"$manifest"; then
msg_error "Backup failed: could not create store at ${store} - aborting update"
rm -rf "$store"
exit 1
fi
for path in "$@"; do for path in "$@"; do
path="${path%/}" path="${path%/}"
if grep -qxF "$path" "$manifest" 2>/dev/null; then
continue
fi
if [[ ! -e "$path" ]]; then if [[ ! -e "$path" ]]; then
msg_warn "Skipping backup of '${path}' (not found)" msg_warn "Skipping backup of '${path}' (not found)"
continue continue
@@ -1338,7 +1339,6 @@ create_backup() {
dest="${store}/files${path}" dest="${store}/files${path}"
if ! mkdir -p "$(dirname "$dest")" || ! cp -a "$path" "$dest"; then if ! mkdir -p "$(dirname "$dest")" || ! cp -a "$path" "$dest"; then
msg_error "Backup of '${path}' failed - aborting update" msg_error "Backup of '${path}' failed - aborting update"
rm -rf "$store"
exit 1 exit 1
fi fi
echo "$path" >>"$manifest" echo "$path" >>"$manifest"
@@ -1466,7 +1466,7 @@ prompt_for_github_token() {
fi fi
local reply local reply
read -rp "${TAB}Would you like to enter a GitHub Personal Access Token (PAT)? [y/N]: " reply read -rp "${TAB}Would you like to enter a GitHub Personal Access Token (PAT)? [y/N]: " reply </dev/tty
reply="${reply:-n}" reply="${reply:-n}"
if [[ ! "${reply,,}" =~ ^(y|yes)$ ]]; then if [[ ! "${reply,,}" =~ ^(y|yes)$ ]]; then
@@ -1475,7 +1475,7 @@ prompt_for_github_token() {
local token local token
while true; do while true; do
read -rp "${TAB}Enter your GitHub PAT: " token read -rp "${TAB}Enter your GitHub PAT: " token </dev/tty
# Trim leading/trailing whitespace # Trim leading/trailing whitespace
token="$(echo "$token" | xargs)" token="$(echo "$token" | xargs)"
if [[ -z "$token" ]]; then if [[ -z "$token" ]]; then
@@ -3973,7 +3973,7 @@ _gh_scan_older_releases() {
local use_fallback="y" local use_fallback="y"
if [[ -t 0 ]]; then if [[ -t 0 ]]; then
msg_warn "Release ${skip_tag} has no matching asset. Previous release ${rel_tag} has a compatible asset." msg_warn "Release ${skip_tag} has no matching asset. Previous release ${rel_tag} has a compatible asset."
read -rp "Use version ${rel_tag} instead? [Y/n] (auto-yes in 60s): " -t 60 use_fallback || use_fallback="y" read -rp "Use version ${rel_tag} instead? [Y/n] (auto-yes in 60s): " -t 60 use_fallback </dev/tty || use_fallback="y"
use_fallback="${use_fallback:-y}" use_fallback="${use_fallback:-y}"
fi fi
@@ -4796,7 +4796,7 @@ EOF
for entry in "${compose_updates[@]}"; do for entry in "${compose_updates[@]}"; do
IFS='|' read -r name image compose_workdir compose_service <<<"$entry" IFS='|' read -r name image compose_workdir compose_service <<<"$entry"
reply="" reply=""
if read -r -t 60 -p "${TAB3}Update ${name} (${image}) via Compose? <y/N> (auto-no in 60s): " reply; then if read -r -t 60 -p "${TAB3}Update ${name} (${image}) via Compose? <y/N> (auto-no in 60s): " reply </dev/tty; then
echo "" echo ""
else else
echo "" echo ""
@@ -4814,7 +4814,7 @@ EOF
for entry in "${standalone_updates[@]}"; do for entry in "${standalone_updates[@]}"; do
IFS='|' read -r name image <<<"$entry" IFS='|' read -r name image <<<"$entry"
reply="" reply=""
if read -r -t 60 -p "${TAB3}Pull new image for ${name} (${image})? <y/N> (auto-no in 60s): " reply; then if read -r -t 60 -p "${TAB3}Pull new image for ${name} (${image})? <y/N> (auto-no in 60s): " reply </dev/tty; then
echo "" echo ""
else else
echo "" echo ""
@@ -5409,7 +5409,7 @@ setup_hwaccel() {
# Read with 60 second timeout # Read with 60 second timeout
local selection="" local selection=""
echo -n "Select GPU(s) to configure (1-${gpu_count}, A=all) [timeout 60s, default=all]: " echo -n "Select GPU(s) to configure (1-${gpu_count}, A=all) [timeout 60s, default=all]: "
if read -r -t 60 selection; then if read -r -t 60 selection </dev/tty; then
selection="${selection^^}" # uppercase selection="${selection^^}" # uppercase
else else
echo "" echo ""
@@ -5457,7 +5457,7 @@ setup_hwaccel() {
echo "" echo ""
msg_custom "🎮" "${GN}" "NVIDIA GPU passthrough detected" msg_custom "🎮" "${GN}" "NVIDIA GPU passthrough detected"
local nvidia_reply="" local nvidia_reply=""
read -r -t 60 -p "${TAB3}⚙️ Install NVIDIA driver libraries in the container? [Y/n] (auto-yes in 60s): " nvidia_reply || nvidia_reply="" read -r -t 60 -p "${TAB3}⚙️ Install NVIDIA driver libraries in the container? [Y/n] (auto-yes in 60s): " nvidia_reply </dev/tty || nvidia_reply=""
case "${nvidia_reply,,}" in case "${nvidia_reply,,}" in
n | no) install_nvidia_drivers="no" ;; n | no) install_nvidia_drivers="no" ;;
*) install_nvidia_drivers="yes" ;; *) install_nvidia_drivers="yes" ;;
@@ -9755,7 +9755,7 @@ _gl_scan_older_releases() {
local use_fallback="y" local use_fallback="y"
if [[ -t 0 ]]; then if [[ -t 0 ]]; then
msg_warn "Release ${skip_tag} has no matching asset. Previous release ${rel_tag} has a compatible asset." msg_warn "Release ${skip_tag} has no matching asset. Previous release ${rel_tag} has a compatible asset."
read -rp "Use version ${rel_tag} instead? [Y/n] (auto-yes in 60s): " -t 60 use_fallback || use_fallback="y" read -rp "Use version ${rel_tag} instead? [Y/n] (auto-yes in 60s): " -t 60 use_fallback </dev/tty || use_fallback="y"
use_fallback="${use_fallback:-y}" use_fallback="${use_fallback:-y}"
fi fi