diff --git a/AppImage/ProxMenux-1.2.2.1-beta.AppImage b/AppImage/ProxMenux-1.2.2.2-beta.AppImage similarity index 52% rename from AppImage/ProxMenux-1.2.2.1-beta.AppImage rename to AppImage/ProxMenux-1.2.2.2-beta.AppImage index cb1930ea..1c2eeccb 100755 Binary files a/AppImage/ProxMenux-1.2.2.1-beta.AppImage and b/AppImage/ProxMenux-1.2.2.2-beta.AppImage differ diff --git a/AppImage/ProxMenux-Monitor.AppImage.sha256 b/AppImage/ProxMenux-Monitor.AppImage.sha256 index b332e90d..5bb8485c 100644 --- a/AppImage/ProxMenux-Monitor.AppImage.sha256 +++ b/AppImage/ProxMenux-Monitor.AppImage.sha256 @@ -1 +1 @@ -aa53e689c13d7184ebd7cb46cc0f24af9628804fcaa223a833364a5a09e382ed ProxMenux-1.2.2.1-beta.AppImage +ad6a863fc2e344e5fc5ffeb66b09de8339bfc6b8d3dfcaa8020b4fe89abb4d2e ProxMenux-1.2.2.2-beta.AppImage diff --git a/AppImage/package-lock.json b/AppImage/package-lock.json index 09c4a51f..704683e5 100644 --- a/AppImage/package-lock.json +++ b/AppImage/package-lock.json @@ -1,12 +1,12 @@ { "name": "ProxMenux-Monitor", - "version": "1.2.2", + "version": "1.2.2.2-beta", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ProxMenux-Monitor", - "version": "1.2.2", + "version": "1.2.2.2-beta", "dependencies": { "@hookform/resolvers": "^3.10.0", "@radix-ui/react-accordion": "1.2.2", diff --git a/AppImage/package.json b/AppImage/package.json index 88150fad..99372b07 100644 --- a/AppImage/package.json +++ b/AppImage/package.json @@ -1,6 +1,6 @@ { "name": "ProxMenux-Monitor", - "version": "1.2.2.1-beta", + "version": "1.2.2.2-beta", "description": "Proxmox System Monitoring Dashboard", "private": true, "scripts": { diff --git a/AppImage/tsconfig.json b/AppImage/tsconfig.json index 946cd624..93a4405a 100644 --- a/AppImage/tsconfig.json +++ b/AppImage/tsconfig.json @@ -1,6 +1,10 @@ { "compilerOptions": { - "lib": ["dom", "dom.iterable", "es6"], + "lib": [ + "dom", + "dom.iterable", + "es6" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -19,9 +23,19 @@ ], "baseUrl": ".", "paths": { - "@/*": ["./*"] - } + "@/*": [ + "./*" + ] + }, + "target": "ES2017" }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] } diff --git a/scripts/backup_restore/backup_host.sh b/scripts/backup_restore/backup_host.sh index 93dbbf8d..8fd55fc9 100644 --- a/scripts/backup_restore/backup_host.sh +++ b/scripts/backup_restore/backup_host.sh @@ -830,7 +830,7 @@ _rs_extract_borg() { local -a archive_lines=() mapfile -t archive_lines < <( "$borg_bin" list "$repo" \ - --format '{start:%Y-%m-%d %H:%M:%S}|{archive}{NL}' 2>/dev/null \ + --format '{start:%Y-%m-%d %H:%M:%S}|{archive}{NL}' /dev/null \ | sort -r ) if [[ ${#archive_lines[@]} -eq 0 ]]; then @@ -1433,9 +1433,11 @@ _rs_show_plan_summary() { # dialog --colors only fires inside --msgbox / --yesno / --infobox, not # --textbox, so we build the body as a string. Color codes match the - # complete-restore confirm dialog for visual consistency. + # complete-restore confirm dialog for visual consistency. Leading blank + # line matches the convention used in info-style dialogs (compat report, + # etc.) — keeps the title from feeling glued to the first text line. local body - body="\Zb═══ $(translate "Restore plan summary") ═══\ZB"$'\n\n' + body=$'\n'"\Zb═══ $(translate "Restore plan summary") ═══\ZB"$'\n\n' if [[ -f "$meta/run_info.env" ]]; then body+="\Zb$(translate "Backup origin metadata:")\ZB"$'\n' @@ -2223,17 +2225,12 @@ _rs_run_complete_extras() { rm -f "$cur_pkgs_file" if [[ ${#missing[@]} -gt 0 ]]; then echo - msg_info "$(translate "Installing") ${#missing[@]} $(translate "user-installed packages from backup...")" - stop_spinner - apt-get update -qq 2>&1 | sed -e 's/^/ /' | tail -2 - - # Pre-filter to packages apt actually knows about — - # otherwise a single typo or repo-renamed pkg in - # packages.manual.list (e.g. `lifnet-subnet-perl` from - # a hand-typo'd apt-mark) makes `apt-get install` exit - # with E_UNRESOLVABLE and the entire batch is skipped. - # We do this in two passes: first split into installable - # vs unknown, then run install on the installable set. + # Pre-filter to packages apt actually knows about — otherwise a + # single typo or repo-renamed pkg in packages.manual.list (e.g. + # `lifnet-subnet-perl` from a hand-typo'd apt-mark) makes + # `apt-get install` exit with E_UNRESOLVABLE and the entire + # batch is skipped. Do this BEFORE the "installing N packages" + # message so the count is honest about what we'll actually try. local -a installable=() unknown=() local pkg for pkg in "${missing[@]}"; do @@ -2245,6 +2242,28 @@ _rs_run_complete_extras() { done if (( ${#installable[@]} > 0 )); then + # Preview so the operator can see (and ^C if surprised by) + # what's about to land. First six is enough — anyone needing + # the full list goes to the apt log we write below. + local _preview="${installable[*]:0:6}" + (( ${#installable[@]} > 6 )) && _preview+=" … (+ $((${#installable[@]} - 6)) more)" + echo -e "${TAB}${BGN}$(translate "Packages from backup to install:")${CL} ${BL}${_preview}${CL}" + echo + + local apt_log="/var/log/proxmenux/restore-apt-$(date +%Y%m%d_%H%M%S).log" + mkdir -p "$(dirname "$apt_log")" 2>/dev/null + + msg_info "$(translate "Refreshing apt cache...")" + apt-get update -qq >"$apt_log" 2>&1 || true + msg_ok "$(translate "apt cache refreshed.")" + + msg_info "$(translate "Installing") ${#installable[@]} $(translate "packages (this may take a few minutes)...")" + # Silent install — full output goes to $apt_log so the + # spinner keeps turning and the operator sees ongoing + # activity. Without this redirect, dpkg's "Setting up..." + # spew would buffer and dump at the very end after a long + # silent stall, looking like a hang followed by a wall of + # text appearing from nowhere. # DEBIAN_FRONTEND=noninteractive + --force-conf prevents # apt from blocking on `*** log2ram.conf (Y/I/N/O/D/Z) ?` # type prompts (which would leave the package in @@ -2257,15 +2276,12 @@ _rs_run_complete_extras() { apt-get install -y \ -o Dpkg::Options::="--force-confdef" \ -o Dpkg::Options::="--force-confold" \ - "${installable[@]}" 2>&1 | sed -e 's/^/ /' | tail -10 - # PIPESTATUS[0] is the real exit code from apt-get; - # without this, the pipeline always reports tee's - # 0 and we'd lie about success. - local apt_rc=${PIPESTATUS[0]} + "${installable[@]}" >>"$apt_log" 2>&1 + local apt_rc=$? if (( apt_rc == 0 )); then - msg_ok "$(translate "Installed:") ${#installable[@]} $(translate "packages")" + msg_ok "$(translate "Installed:") ${#installable[@]} $(translate "packages.")" else - msg_warn "$(translate "apt-get exited") ${apt_rc} — $(translate "some packages may have failed; see output above")" + msg_warn "$(translate "apt-get exited") ${apt_rc} — $(translate "see log:") $apt_log" fi fi if (( ${#unknown[@]} > 0 )); then diff --git a/scripts/backup_restore/lib_host_backup_common.sh b/scripts/backup_restore/lib_host_backup_common.sh index 4f9dfb78..8e0526d7 100644 --- a/scripts/backup_restore/lib_host_backup_common.sh +++ b/scripts/backup_restore/lib_host_backup_common.sh @@ -1165,11 +1165,17 @@ hb_ensure_borg() { hb_borg_init_if_needed() { local borg_bin="$1" repo="$2" encrypt_mode="$3" - "$borg_bin" list "$repo" >/dev/null 2>&1 && return 0 - if "$borg_bin" help repo-create >/dev/null 2>&1; then - "$borg_bin" repo-create -e "$encrypt_mode" "$repo" + # Borg reads passphrase prompts from /dev/tty even when stdout/stderr + # are redirected — so `>/dev/null 2>&1` is not enough to silence it. + # Close stdin (`/dev/null 2>&1; then + return 0 + fi + if "$borg_bin" help repo-create /dev/null 2>&1; then + "$borg_bin" repo-create -e "$encrypt_mode" "$repo" &1 1>&2 2>&3) || return 1 + sel_pass=$(dialog --backtitle "ProxMenux" --colors --insecure \ + --title "$(hb_translate "Borg repository passphrase")" \ + --passwordbox "\n$(hb_translate "Enter the BORG REPOKEY passphrase for target:") \Zb${HB_BORG_SELECTED_NAME}\ZB" \ + 10 78 "" 3>&1 1>&2 2>&3) || return 1 mkdir -p "$HB_STATE_DIR" printf '%s' "$sel_pass" > "$sel_pass_file" chmod 600 "$sel_pass_file" @@ -1790,6 +1802,17 @@ hb_select_borg_repo() { if [[ "$choice" == "$add_idx" ]]; then hb_configure_borg_manual _borg_repo_ref || return 1 + # The new target is saved under HB_BORG_LAST_SAVED_NAME. Promote + # it to SELECTED so hb_prepare_borg_passphrase enters the saved- + # target branch (one passphrase prompt, then persist), instead + # of the brand-new branch (the "¿Cifrar?" yes/no flow that also + # races with borg picking up the repokey prompt on the TTY). + if [[ -n "${HB_BORG_LAST_SAVED_NAME:-}" ]]; then + HB_BORG_SELECTED_NAME="$HB_BORG_LAST_SAVED_NAME" + HB_BORG_SELECTED_PASS="" + fi + export BORG_RELOCATED_REPO_ACCESS_IS_OK=yes + export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes return 0 fi @@ -1825,6 +1848,13 @@ hb_select_borg_repo() { else unset BORG_RSH fi + # Saved URL may differ from the repo's recorded location by one + # trailing slash (e.g. ssh://host//path vs ssh://host/path). Borg + # prompts y/N to confirm — without a TTY the answer is empty and + # borg aborts with a red one-liner, dropping the user back to the + # menu. Trust the saved target. + export BORG_RELOCATED_REPO_ACCESS_IS_OK=yes + export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes HB_BORG_SELECTED_NAME="${HB_BORG_NAMES[$sel]}" HB_BORG_SELECTED_PASS="${HB_BORG_PASSES[$sel]}" } @@ -2567,8 +2597,11 @@ hb_show_compat_report() { local tmpfile tmpfile=$(mktemp) + # Leading blank line before the summary — dialog's --textbox draws the + # title flush to the top border, and stacking the summary directly under + # it looks cramped. One empty line gives the eye a break before the data. { - printf '%s\n' "$summary" + printf '\n%s\n' "$summary" printf '%s\n\n' "────────────────────────────────────────────────────────────" printf '%s\n' "$report" } > "$tmpfile" diff --git a/scripts/backup_restore/run_scheduled_backup.sh b/scripts/backup_restore/run_scheduled_backup.sh index 9fcf7ddf..e63f7b9e 100644 --- a/scripts/backup_restore/run_scheduled_backup.sh +++ b/scripts/backup_restore/run_scheduled_backup.sh @@ -110,6 +110,8 @@ _sb_run_borg() { # call is also `>/dev/null 2>&1`). export BORG_PASSPHRASE="$passphrase" [[ -n "${BORG_RSH:-}" ]] && export BORG_RSH + export BORG_RELOCATED_REPO_ACCESS_IS_OK=yes + export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes if ! hb_borg_init_if_needed "$borg_bin" "$repo" "${BORG_ENCRYPT_MODE:-none}" >/dev/null 2>&1; then return 1