mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-07-28 11:28:24 +00:00
update 1.2.2.2 beta
This commit is contained in:
144
scripts/backup_restore/restore/compute_rollback_plan.sh
Executable file
144
scripts/backup_restore/restore/compute_rollback_plan.sh
Executable 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'
|
||||
68
scripts/backup_restore/restore/list_paths.sh
Executable file
68
scripts/backup_restore/restore/list_paths.sh
Executable 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'
|
||||
107
scripts/backup_restore/restore/monitor_apply.sh
Executable file
107
scripts/backup_restore/restore/monitor_apply.sh
Executable 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
0
scripts/backup_restore/restore/parse_manifest.sh
Normal file → Executable file
0
scripts/backup_restore/restore/preflight_checks.sh
Normal file → Executable file
0
scripts/backup_restore/restore/preflight_checks.sh
Normal file → Executable file
0
scripts/backup_restore/restore/reinstall_drivers.sh
Normal file → Executable file
0
scripts/backup_restore/restore/reinstall_drivers.sh
Normal file → Executable file
0
scripts/backup_restore/restore/remap_network.sh
Normal file → Executable file
0
scripts/backup_restore/restore/remap_network.sh
Normal file → Executable file
0
scripts/backup_restore/restore/restore_modes.sh
Normal file → Executable file
0
scripts/backup_restore/restore/restore_modes.sh
Normal file → Executable file
0
scripts/backup_restore/restore/run_restore.sh
Normal file → Executable file
0
scripts/backup_restore/restore/run_restore.sh
Normal file → Executable file
0
scripts/backup_restore/restore/validate_storage.sh
Normal file → Executable file
0
scripts/backup_restore/restore/validate_storage.sh
Normal file → Executable file
Reference in New Issue
Block a user