mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-09-15 11:16:50 +00:00
Merge pull request #341 from f3rs3n/fix/validate-pending-restore
fix(backup): reject incomplete pending restore staging
This commit is contained in:
@@ -2300,59 +2300,71 @@ _rs_prepare_pending_restore() {
|
||||
local pending_base="/var/lib/proxmenux/restore-pending"
|
||||
local restore_id pending_dir created_at
|
||||
restore_id="$(date +%Y%m%d_%H%M%S)"
|
||||
pending_dir="${pending_base}/${restore_id}"
|
||||
mkdir -p "$pending_base/completed" || return 1
|
||||
# Never reuse a published tree, even for two restores in the same second.
|
||||
pending_dir=$(mktemp -d "${pending_base}/${restore_id}.XXXXXX") || return 1
|
||||
restore_id="${pending_dir##*/}"
|
||||
created_at="$(date -Iseconds)"
|
||||
|
||||
mkdir -p "$pending_dir/rootfs" "$pending_dir/metadata" "$pending_base/completed" || return 1
|
||||
# Keep all writes private; callers use if, so do not rely on errexit.
|
||||
if ! (
|
||||
mkdir -p "$pending_dir/rootfs" "$pending_dir/metadata" || exit 1
|
||||
|
||||
local rel src dst
|
||||
: > "$pending_dir/apply-on-boot.list"
|
||||
for rel in "${pending_paths[@]}"; do
|
||||
src="$staging_root/rootfs/$rel"
|
||||
[[ -e "$src" ]] || continue
|
||||
dst="$pending_dir/rootfs/$rel"
|
||||
mkdir -p "$(dirname "$dst")"
|
||||
if [[ -d "$src" ]]; then
|
||||
mkdir -p "$dst"
|
||||
rsync -aAXH --delete "$src/" "$dst/" 2>/dev/null || true
|
||||
else
|
||||
cp -a "$src" "$dst" 2>/dev/null || true
|
||||
local rel src dst
|
||||
: > "$pending_dir/apply-on-boot.list" || exit 1
|
||||
for rel in "${pending_paths[@]}"; do
|
||||
src="$staging_root/rootfs/$rel"
|
||||
[[ -e "$src" ]] || {
|
||||
msg_error "$(translate "Pending restore source is missing:") $rel"
|
||||
exit 1
|
||||
}
|
||||
dst="$pending_dir/rootfs/$rel"
|
||||
mkdir -p "$(dirname "$dst")" || exit 1
|
||||
if [[ -d "$src" ]]; then
|
||||
mkdir -p "$dst" || exit 1
|
||||
if ! rsync -aAXH --delete "$src/" "$dst/" 2>/dev/null; then
|
||||
msg_error "$(translate "Could not stage pending restore path:") $rel"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if ! cp -a "$src" "$dst" 2>/dev/null; then
|
||||
msg_error "$(translate "Could not stage pending restore path:") $rel"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo "$rel" >> "$pending_dir/apply-on-boot.list" || exit 1
|
||||
done
|
||||
|
||||
if [[ ! -s "$pending_dir/apply-on-boot.list" ]]; then
|
||||
msg_warn "$(translate "Nothing to schedule for reboot from selected paths.")"
|
||||
exit 1
|
||||
fi
|
||||
echo "$rel" >> "$pending_dir/apply-on-boot.list"
|
||||
done
|
||||
|
||||
if [[ ! -s "$pending_dir/apply-on-boot.list" ]]; then
|
||||
rm -rf "$pending_dir"
|
||||
msg_warn "$(translate "Nothing to schedule for reboot from selected paths.")"
|
||||
return 1
|
||||
fi
|
||||
if [[ -d "$staging_root/metadata" ]]; then
|
||||
cp -a "$staging_root/metadata/." "$pending_dir/metadata/" 2>/dev/null || exit 1
|
||||
fi
|
||||
|
||||
[[ -d "$staging_root/metadata" ]] && cp -a "$staging_root/metadata/." "$pending_dir/metadata/" 2>/dev/null || true
|
||||
# Persist the rollback plan: VMs/LXCs/components that exist
|
||||
# on the host but not in the backup. apply_cluster_postboot.sh
|
||||
# surfaces a read-only "what differs" report from this file
|
||||
# after the boot. When the operator opted into destructive
|
||||
# rollback (HB_ROLLBACK_EXECUTE=1) we also destroy those
|
||||
# guests RIGHT NOW — before the reboot — so the operator
|
||||
# sees `qm destroy` live in the Monitor terminal AND the
|
||||
# next boot comes up without those guests reserving hardware
|
||||
# (critical for GPU passthrough: a stale VM with hostpci
|
||||
# entries makes Proxmox auto-bind the GPU to vfio-pci before
|
||||
# the nvidia driver can claim it).
|
||||
local _rb_script="${SCRIPT_DIR}/restore/compute_rollback_plan.sh"
|
||||
[[ ! -x "$_rb_script" ]] && _rb_script="${LOCAL_SCRIPTS:-/usr/local/share/proxmenux/scripts}/backup_restore/restore/compute_rollback_plan.sh"
|
||||
if [[ -x "$_rb_script" ]]; then
|
||||
bash "$_rb_script" "$staging_root" > "$pending_dir/rollback.json" 2>/dev/null || exit 1
|
||||
if [[ ! -s "$pending_dir/rollback.json" ]]; then
|
||||
rm -f "$pending_dir/rollback.json" || exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Persist the rollback plan: VMs/LXCs/components that exist
|
||||
# on the host but not in the backup. apply_cluster_postboot.sh
|
||||
# surfaces a read-only "what differs" report from this file
|
||||
# after the boot. When the operator opted into destructive
|
||||
# rollback (HB_ROLLBACK_EXECUTE=1) we also destroy those
|
||||
# guests RIGHT NOW — before the reboot — so the operator
|
||||
# sees `qm destroy` live in the Monitor terminal AND the
|
||||
# next boot comes up without those guests reserving hardware
|
||||
# (critical for GPU passthrough: a stale VM with hostpci
|
||||
# entries makes Proxmox auto-bind the GPU to vfio-pci before
|
||||
# the nvidia driver can claim it).
|
||||
local _rb_script="${SCRIPT_DIR}/restore/compute_rollback_plan.sh"
|
||||
[[ ! -x "$_rb_script" ]] && _rb_script="${LOCAL_SCRIPTS:-/usr/local/share/proxmenux/scripts}/backup_restore/restore/compute_rollback_plan.sh"
|
||||
if [[ -x "$_rb_script" ]]; then
|
||||
bash "$_rb_script" "$staging_root" > "$pending_dir/rollback.json" 2>/dev/null || true
|
||||
[[ ! -s "$pending_dir/rollback.json" ]] && rm -f "$pending_dir/rollback.json"
|
||||
fi
|
||||
|
||||
if [[ "${HB_ROLLBACK_EXECUTE:-0}" == "1" && -s "$pending_dir/rollback.json" ]] \
|
||||
&& command -v jq >/dev/null 2>&1; then
|
||||
_rs_execute_rollback "$pending_dir/rollback.json"
|
||||
fi
|
||||
|
||||
cat > "$pending_dir/plan.env" <<EOF
|
||||
cat > "$pending_dir/plan.env" <<EOF || exit 1
|
||||
RESTORE_ID=${restore_id}
|
||||
CREATED_AT=${created_at}
|
||||
HB_RESTORE_INCLUDE_ZFS=${HB_RESTORE_INCLUDE_ZFS:-0}
|
||||
@@ -2361,26 +2373,45 @@ HB_COMPAT_CROSS_VERSION=${HB_COMPAT_CROSS_VERSION:-0}
|
||||
HB_COMPAT_KERNEL_DIRECTION=${HB_COMPAT_KERNEL_DIRECTION:-same}
|
||||
HB_HYDRATION_APPLIED=${HB_HYDRATION_APPLIED:-0}
|
||||
EOF
|
||||
# Persist hardware-drift skips so apply_pending_restore.sh can filter
|
||||
# them at boot. The RS_SKIP_PATHS env var only lives in the restore
|
||||
# menu session; without writing it to disk, paths that would break
|
||||
# the boot (stale EFI UUIDs, foreign zpool.cache, ...) leaked through
|
||||
# to the post-boot apply and ended up corrupting the bootloader.
|
||||
if [[ -n "${RS_SKIP_PATHS:-}" ]]; then
|
||||
printf '%s\n' "$RS_SKIP_PATHS" > "$pending_dir/rs-skip-paths.txt"
|
||||
chmod 600 "$pending_dir/rs-skip-paths.txt"
|
||||
fi
|
||||
echo "pending" > "$pending_dir/state"
|
||||
|
||||
ln -sfn "$pending_dir" "$pending_base/current"
|
||||
|
||||
_rs_install_pending_service_unit "$onboot_script"
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
if ! systemctl enable proxmenux-restore-onboot.service >/dev/null 2>&1; then
|
||||
msg_error "$(translate "Could not enable on-boot restore service.")"
|
||||
# Persist hardware-drift skips so apply_pending_restore.sh can filter
|
||||
# them at boot. The RS_SKIP_PATHS env var only lives in the restore
|
||||
# menu session; without writing it to disk, paths that would break
|
||||
# the boot (stale EFI UUIDs, foreign zpool.cache, ...) leaked through
|
||||
# to the post-boot apply and ended up corrupting the bootloader.
|
||||
if [[ -n "${RS_SKIP_PATHS:-}" ]]; then
|
||||
printf '%s\n' "$RS_SKIP_PATHS" > "$pending_dir/rs-skip-paths.txt" || exit 1
|
||||
chmod 600 "$pending_dir/rs-skip-paths.txt" || exit 1
|
||||
fi
|
||||
echo "pending" > "$pending_dir/state" || exit 1
|
||||
); then
|
||||
rm -rf "$pending_dir"
|
||||
msg_error "$(translate "Could not stage pending restore. Nothing new was scheduled.")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# current is the boot consumer's readiness boundary (state alone is not).
|
||||
# Prepare a sibling-filesystem link privately, then rename over current;
|
||||
# never unlink the old job first or let mv follow a directory symlink.
|
||||
if ! ln -s "$pending_dir" "$pending_dir/.current" \
|
||||
|| ! _rs_install_pending_service_unit "$onboot_script" \
|
||||
|| ! systemctl daemon-reload >/dev/null 2>&1 \
|
||||
|| ! systemctl enable proxmenux-restore-onboot.service >/dev/null 2>&1; then
|
||||
rm -rf "$pending_dir"
|
||||
msg_error "$(translate "Could not prepare on-boot restore service. Nothing new was scheduled.")"
|
||||
return 1
|
||||
fi
|
||||
if ! mv -Tf "$pending_dir/.current" "$pending_base/current"; then
|
||||
rm -rf "$pending_dir"
|
||||
msg_error "$(translate "Could not publish pending restore. Previous pending restore was kept.")"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Destructive rollback is allowed only after staging and publication succeed.
|
||||
if [[ "${HB_ROLLBACK_EXECUTE:-0}" == "1" && -s "$pending_dir/rollback.json" ]] \
|
||||
&& command -v jq >/dev/null 2>&1; then
|
||||
_rs_execute_rollback "$pending_dir/rollback.json"
|
||||
fi
|
||||
|
||||
echo -e ""
|
||||
echo -e "${TAB}${BGN}$(translate "Pending restore ID:")${CL} ${BL}${restore_id}${CL}"
|
||||
echo -e "${TAB}${BGN}$(translate "Pending restore dir:")${CL} ${BL}${pending_dir}${CL}"
|
||||
|
||||
Reference in New Issue
Block a user