update 1.2.2.2 beta

This commit is contained in:
MacRimi
2026-06-21 22:41:54 +02:00
parent 8e92df5bd7
commit 09a67bf4e6
30 changed files with 1495 additions and 473 deletions

View File

@@ -0,0 +1,144 @@
#!/usr/bin/env bash
# ==========================================================
# ProxMenux — compute rollback plan (read-only)
# ==========================================================
# Compares a staged restore vs the current host state and emits
# a JSON document describing what a "rollback to backup state"
# would do:
#
# {
# "backup_time": "...",
# "vms_in_backup": [100, 101],
# "vms_in_host": [100, 101, 105],
# "vms_to_remove": [105], // exist now, not in backup
# "vms_to_restore": [100, 101], // present in both → reapplied
# "lxcs_in_backup": [201],
# "lxcs_in_host": [201, 202],
# "lxcs_to_remove": [202],
# "lxcs_to_restore": [201],
# "components_to_uninstall": ["coral_driver"], // installed now, missing in backup
# "components_to_reinstall": ["nvidia_driver"] // installed in backup
# }
#
# No side effects whatsoever — pure inspection. The actual
# rollback is applied later by apply_cluster_postboot.sh after
# the operator confirms in the UI.
#
# Usage:
# compute_rollback_plan.sh <staging-directory>
#
# The staging directory must already be the post-extract layout
# (rootfs/ + metadata/) — list_paths.sh's normalization should
# have run first.
# ==========================================================
set -e
STAGING="${1:-}"
if [[ -z "$STAGING" || ! -d "$STAGING" ]]; then
printf 'compute_rollback_plan: usage: %s <staging-directory>\n' "$0" >&2
exit 64
fi
# ── Discover guest configs (VMs + LXCs) on both sides ──
# /etc/pve/nodes/<node>/qemu-server/<id>.conf for VMs
# /etc/pve/nodes/<node>/lxc/<id>.conf for LXCs
# We collect just the numeric IDs; the rollback only needs to
# know "exists in A, exists in B" — actual config rewrite is
# done by the standard restore path for /etc/pve.
_collect_ids() {
# $1 = root directory, $2 = subdir under /etc/pve/nodes/<n>/
local root="$1" sub="$2"
if [[ -d "$root/etc/pve/nodes" ]]; then
find "$root/etc/pve/nodes" -mindepth 3 -maxdepth 3 -path "*/$sub/*.conf" -print 2>/dev/null \
| sed -E "s|.*/([0-9]+)\\.conf$|\\1|" | sort -un
fi
}
mapfile -t vms_backup < <(_collect_ids "$STAGING/rootfs" qemu-server)
mapfile -t vms_host < <(_collect_ids "/" qemu-server)
mapfile -t lxcs_backup < <(_collect_ids "$STAGING/rootfs" lxc)
mapfile -t lxcs_host < <(_collect_ids "/" lxc)
_diff() {
# Echoes lines from A that are NOT in B.
local -n a_ref=$1
local -n b_ref=$2
local x y found
for x in "${a_ref[@]}"; do
found=0
for y in "${b_ref[@]}"; do
[[ "$x" == "$y" ]] && { found=1; break; }
done
(( found == 0 )) && echo "$x"
done
}
mapfile -t vms_to_remove < <(_diff vms_host vms_backup)
mapfile -t vms_to_restore < <(_diff vms_backup vms_host || true; for x in "${vms_backup[@]}"; do for y in "${vms_host[@]}"; do [[ "$x" == "$y" ]] && echo "$x"; done; done | sort -un)
mapfile -t vms_to_restore < <(printf '%s\n' "${vms_backup[@]}")
mapfile -t lxcs_to_remove < <(_diff lxcs_host lxcs_backup)
mapfile -t lxcs_to_restore < <(printf '%s\n' "${lxcs_backup[@]}")
# ── Components: parse JSON on both sides, list deltas ──
backup_components_file="$STAGING/rootfs/usr/local/share/proxmenux/components_status.json"
host_components_file="/usr/local/share/proxmenux/components_status.json"
_installed_keys() {
# Echoes one component key per line for `status == "installed"`.
local f="$1"
[[ ! -f "$f" ]] && return 0
command -v jq >/dev/null 2>&1 || { return 0; }
jq -r 'to_entries[] | select(.value.status == "installed") | .key' "$f" 2>/dev/null | sort -u
}
mapfile -t comps_backup < <(_installed_keys "$backup_components_file")
mapfile -t comps_host < <(_installed_keys "$host_components_file")
mapfile -t comps_to_uninstall < <(_diff comps_host comps_backup)
mapfile -t comps_to_reinstall < <(printf '%s\n' "${comps_backup[@]}")
# ── Backup timestamp from metadata for the UI ──
backup_time=""
if [[ -f "$STAGING/metadata/run_info.env" ]]; then
backup_time=$(grep -m1 '^generated_at=' "$STAGING/metadata/run_info.env" 2>/dev/null \
| cut -d= -f2-)
fi
# ── Emit JSON. Pure printf — no jq required for the writer ──
_json_arr_int() {
printf '['
local first=1 v
for v in "$@"; do
[[ -z "$v" ]] && continue
if (( first )); then printf '%s' "$v"; first=0
else printf ',%s' "$v"
fi
done
printf ']'
}
_json_arr_str() {
printf '['
local first=1 v esc
for v in "$@"; do
[[ -z "$v" ]] && continue
esc="${v//\\/\\\\}"; esc="${esc//\"/\\\"}"
if (( first )); then printf '"%s"' "$esc"; first=0
else printf ',"%s"' "$esc"
fi
done
printf ']'
}
printf '{'
printf '"backup_time":"%s"' "${backup_time//\"/\\\"}"
printf ',"vms_in_backup":'; _json_arr_int "${vms_backup[@]}"
printf ',"vms_in_host":'; _json_arr_int "${vms_host[@]}"
printf ',"vms_to_remove":'; _json_arr_int "${vms_to_remove[@]}"
printf ',"vms_to_restore":'; _json_arr_int "${vms_to_restore[@]}"
printf ',"lxcs_in_backup":'; _json_arr_int "${lxcs_backup[@]}"
printf ',"lxcs_in_host":'; _json_arr_int "${lxcs_host[@]}"
printf ',"lxcs_to_remove":'; _json_arr_int "${lxcs_to_remove[@]}"
printf ',"lxcs_to_restore":'; _json_arr_int "${lxcs_to_restore[@]}"
printf ',"components_to_uninstall":'; _json_arr_str "${comps_to_uninstall[@]}"
printf ',"components_to_reinstall":'; _json_arr_str "${comps_to_reinstall[@]}"
printf '}\n'

View File

@@ -0,0 +1,68 @@
#!/usr/bin/env bash
# ==========================================================
# ProxMenux — list backup paths actually present in staging
# ==========================================================
# Reads <staging>/metadata/selected_paths.txt (the canonical
# record of WHICH paths went into this backup) and emits a JSON
# array of those that still exist under <staging>/rootfs/.
#
# This replaces the 13-component grouping used previously: the
# Custom restore checklist now shows one entry per real path
# (default profile + any operator-added extras), so what you
# backed up is exactly what you can restore.
#
# Usage:
# list_paths.sh <staging-directory>
#
# Output: JSON array of paths with a leading slash, e.g.:
# ["/etc/pve","/etc/network","/opt/mistuff"]
# ==========================================================
set -e
STAGING="${1:-}"
if [[ -z "$STAGING" || ! -d "$STAGING" ]]; then
printf 'list_paths: usage: %s <staging-directory>\n' "$0" >&2
exit 64
fi
SELECTED="$STAGING/metadata/selected_paths.txt"
ROOTFS="$STAGING/rootfs"
paths=()
if [[ -f "$SELECTED" ]]; then
# The file stores paths without the leading slash (etc/pve,
# var/lib/pve-cluster, ...). Filter to only those that are
# actually present in the staged rootfs/ — anything in
# missing_paths.txt at backup time would otherwise show up
# here even though there's nothing to restore.
while IFS= read -r rel; do
[[ -z "$rel" ]] && continue
rel="${rel#/}"
if [[ -e "$ROOTFS/$rel" ]]; then
paths+=("/$rel")
fi
done < "$SELECTED"
else
# No selected_paths.txt sidecar — backup might be from a
# pre-metadata era. Walk rootfs/ at depth 1-2 and use those
# as the path list. Best-effort fallback.
while IFS= read -r entry; do
[[ -z "$entry" ]] && continue
paths+=("/${entry#./}")
done < <(cd "$ROOTFS" 2>/dev/null && find . -mindepth 1 -maxdepth 2 -print 2>/dev/null | sed 's|^\./||')
fi
# Emit JSON array — keep it dependency-free (no jq).
printf '['
first=1
for p in "${paths[@]}"; do
esc="${p//\\/\\\\}"
esc="${esc//\"/\\\"}"
if (( first )); then
printf '"%s"' "$esc"
first=0
else
printf ',"%s"' "$esc"
fi
done
printf ']\n'

View File

@@ -0,0 +1,107 @@
#!/usr/bin/env bash
# ==========================================================
# ProxMenux Monitor restore wrapper
# ==========================================================
# Bridges the Monitor's "Restore" button to the TUI restore
# functions in backup_host.sh. The Monitor takes care of the
# extraction (snapshot → staging directory + manifest), then
# launches this script in a ScriptTerminalModal so the operator
# sees the same dialog flow used by the shell TUI.
#
# The Monitor's script_runner only forwards env vars (no argv).
# Inputs (env):
# STAGING — absolute path to the prepared staging dir
# MODE — "full" or "custom"
# PATHS — CSV of absolute backup paths, only when MODE=custom
# (e.g. "/etc/pve,/etc/network,/opt/mistuff").
# Each must be present in <staging>/metadata/selected_paths.txt
# or the wrapper drops it. Omit to let the TUI checklist
# pop up so the operator can pick interactively.
# ==========================================================
# Note: NO `set -e` here. backup_host.sh + its sourced utils/lib
# emit a few non-zero exit codes during load (locale checks,
# optional commands missing, etc.) — under set -e those abort the
# wrapper silently and the operator only sees "Connection closed"
# in the Monitor terminal with no clue why. We rely on explicit
# error handling inside _rs_run_complete_guided / _rs_run_custom_restore.
STAGING="${STAGING:-}"
MODE="${MODE:-full}"
PATHS="${PATHS:-}"
# Also accept --staging / --mode / --paths positional args for
# direct CLI use outside the Monitor (handy for debugging).
while [[ $# -gt 0 ]]; do
case "$1" in
--staging) shift; STAGING="${1:-}" ;;
--mode) shift; MODE="${1:-full}" ;;
--paths) shift; PATHS="${1:-}" ;;
*) ;;
esac
shift
done
if [[ -z "$STAGING" || ! -d "$STAGING" ]]; then
printf 'monitor_apply: STAGING missing or not a directory (%s)\n' "$STAGING" >&2
exit 64
fi
HB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# Source the library + the TUI. backup_host.sh carries a guard so
# sourcing it does NOT spawn the main menu — only the functions
# (_rs_check_layout, _rs_run_complete_guided, _rs_run_custom_restore,
# _rs_apply, ...) get loaded.
# shellcheck source=/dev/null
. "$HB_DIR/lib_host_backup_common.sh"
# shellcheck source=/dev/null
. "$HB_DIR/backup_host.sh"
# Normalize the staging layout to the canonical rootfs/+metadata/
# shape. The Monitor's Python extract path already does this, but
# rerunning here keeps the wrapper safe when invoked directly.
if ! _rs_check_layout "$STAGING"; then
printf 'monitor_apply: archive layout not recognized in %s\n' "$STAGING" >&2
exit 65
fi
# Tell the apply helpers that we're running under the Monitor.
# Two effects:
# • _rs_apply skips its final `systemctl daemon-reload` so the
# Monitor service unit isn't marked "needs restart" and the
# WebSocket survives the apply (the restored unit file IS
# written to disk — it'll take effect at the next reboot).
# • _rs_run_complete_guided / _rs_run_custom_restore skip the
# second "Reboot now?" dialog and instead emit one final
# "Restore prepared — reboot when ready" line. The operator
# closes the modal in the web UI and reboots from the host
# panel; no need for two consecutive yes/no prompts.
export HB_MONITOR_FLOW=1
case "$MODE" in
full)
# The TUI path runs `_rs_collect_plan_stats` before calling
# `_rs_run_complete_guided`. We were skipping it here and that's
# why the confirm dialog showed "0 rutas seguras ahora /
# 0 rutas para el próximo arranque" when launched from the Monitor.
_rs_collect_plan_stats "$STAGING"
_rs_run_complete_guided "$STAGING"
;;
custom)
if [[ -n "$PATHS" ]]; then
HB_PRESELECTED_PATHS="$PATHS" _rs_run_custom_restore "$STAGING"
else
_rs_run_custom_restore "$STAGING"
fi
;;
*)
printf 'monitor_apply: unknown MODE %s (expected: full | custom)\n' "$MODE" >&2
exit 66
;;
esac
# Clean exit. _rs_finish_flow under HB_MONITOR_FLOW already returns
# without waiting for Enter, so falling off the end here closes the
# PTY → the WS closes → the modal sees `isComplete=true` (script_runner
# sends "[Script exited with code 0]" on normal termination) so the
# ScriptTerminalModal won't try to auto-reconnect.

0
scripts/backup_restore/restore/parse_manifest.sh Normal file → Executable file
View File

0
scripts/backup_restore/restore/preflight_checks.sh Normal file → Executable file
View File

0
scripts/backup_restore/restore/reinstall_drivers.sh Normal file → Executable file
View File

0
scripts/backup_restore/restore/remap_network.sh Normal file → Executable file
View File

0
scripts/backup_restore/restore/restore_modes.sh Normal file → Executable file
View File

0
scripts/backup_restore/restore/run_restore.sh Normal file → Executable file
View File

0
scripts/backup_restore/restore/validate_storage.sh Normal file → Executable file
View File