mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-07-26 18:38:30 +00:00
update 1.2.2.2 beta
This commit is contained in:
@@ -40,6 +40,9 @@ fi
|
||||
|
||||
: "${HB_RESTORE_INCLUDE_ZFS:=0}"
|
||||
: "${HB_COMPAT_CROSS_VERSION:=0}"
|
||||
: "${HB_KERNEL_PIN_ACTION:=none}"
|
||||
: "${HB_KERNEL_PIN_KVER:=}"
|
||||
: "${HB_KERNEL_PIN_PKG:=}"
|
||||
|
||||
if [[ ! -f "$APPLY_LIST" ]]; then
|
||||
echo "Apply list missing: $APPLY_LIST"
|
||||
@@ -47,10 +50,13 @@ if [[ ! -f "$APPLY_LIST" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Pending dir: $PENDING_DIR"
|
||||
echo "Apply list: $APPLY_LIST"
|
||||
echo "Include ZFS: $HB_RESTORE_INCLUDE_ZFS"
|
||||
echo "Cross-version: $HB_COMPAT_CROSS_VERSION"
|
||||
echo "Pending dir: $PENDING_DIR"
|
||||
echo "Apply list: $APPLY_LIST"
|
||||
echo "Include ZFS: $HB_RESTORE_INCLUDE_ZFS"
|
||||
echo "Cross-version: $HB_COMPAT_CROSS_VERSION"
|
||||
echo "Kernel pin act: $HB_KERNEL_PIN_ACTION"
|
||||
echo "Kernel pin ver: $HB_KERNEL_PIN_KVER"
|
||||
echo "Kernel pin pkg: $HB_KERNEL_PIN_PKG"
|
||||
|
||||
# Hardware-drift skips persisted by _rs_prepare_pending_restore.
|
||||
# Each line is an absolute path; we drop any rel path that matches
|
||||
@@ -304,6 +310,60 @@ UNITEOF
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
systemctl enable proxmenux-apply-cluster-postboot.service >/dev/null 2>&1 || true
|
||||
|
||||
# ── Cross-kernel: install (if needed) + pin backup's kernel ──
|
||||
# When the backup was taken on a different kernel major.minor
|
||||
# than the target, the restore flow pre-computed a plan telling
|
||||
# us whether the backup's kernel is already on the target or
|
||||
# can be pulled from Proxmox repos. Acting here — during the
|
||||
# onboot restore, before the cluster-postboot fires — gives us
|
||||
# a controlled reboot into the pinned kernel, so the DKMS
|
||||
# auto-reinstall in postboot builds every module against the
|
||||
# right ABI (the kernel the operator's backup actually expects).
|
||||
_kernel_pin_reboot_needed=0
|
||||
if [[ "$HB_KERNEL_PIN_ACTION" != "none" && -n "$HB_KERNEL_PIN_KVER" ]]; then
|
||||
echo ""
|
||||
echo "── Cross-kernel: preparing kernel $HB_KERNEL_PIN_KVER ──"
|
||||
if [[ "$HB_KERNEL_PIN_ACTION" == "installable" ]]; then
|
||||
echo "Installing $HB_KERNEL_PIN_PKG from Proxmox repositories..."
|
||||
apt-get update -qq 2>&1 | tail -5
|
||||
if DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "$HB_KERNEL_PIN_PKG" 2>&1 | tail -10; then
|
||||
echo " ✓ $HB_KERNEL_PIN_PKG installed"
|
||||
else
|
||||
echo " ✗ apt install failed — pin will fall back to whatever kernels are present"
|
||||
fi
|
||||
fi
|
||||
if command -v proxmox-boot-tool >/dev/null 2>&1; then
|
||||
proxmox-boot-tool kernel pin "$HB_KERNEL_PIN_KVER" 2>&1 | sed 's/^/ pin: /'
|
||||
proxmox-boot-tool refresh 2>&1 | sed 's/^/ refresh: /'
|
||||
_kernel_pin_reboot_needed=1
|
||||
else
|
||||
echo " ! proxmox-boot-tool not available — kernel pin skipped"
|
||||
fi
|
||||
fi
|
||||
|
||||
if (( _kernel_pin_reboot_needed == 1 )); then
|
||||
# Do NOT start the cluster-postboot service in this boot.
|
||||
# We must reboot first so the kernel pin takes effect;
|
||||
# once the machine is back up on the pinned kernel, the
|
||||
# postboot service (enabled with a ConditionPathExists on
|
||||
# cluster-apply-pending) will fire automatically and its
|
||||
# DKMS reinstalls will target the correct kernel.
|
||||
echo ""
|
||||
echo "Rebooting into pinned kernel $HB_KERNEL_PIN_KVER..."
|
||||
echo "completed" >"$STATE_FILE" 2>/dev/null || true
|
||||
# Mark this restore as completed so the on-boot service
|
||||
# doesn't re-fire after we reboot.
|
||||
_rid="$(basename "$PENDING_DIR")"
|
||||
mv "$PENDING_DIR" "${PENDING_BASE}/completed/${_rid}" >/dev/null 2>&1 || true
|
||||
rm -f "$CURRENT_LINK" >/dev/null 2>&1 || true
|
||||
systemctl disable proxmenux-restore-onboot.service >/dev/null 2>&1 || true
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
sync
|
||||
systemctl --no-block reboot
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Same-kernel path: fire cluster-postboot inline in this boot.
|
||||
# `systemctl enable` only adds the unit to multi-user.target.wants/.
|
||||
# It does NOT pull the unit into the currently-running boot
|
||||
# transaction — by the time we run, multi-user.target may have
|
||||
|
||||
@@ -2061,6 +2061,9 @@ CREATED_AT=${created_at}
|
||||
HB_RESTORE_INCLUDE_ZFS=${HB_RESTORE_INCLUDE_ZFS:-0}
|
||||
HB_ROLLBACK_EXECUTE=${HB_ROLLBACK_EXECUTE:-0}
|
||||
HB_COMPAT_CROSS_VERSION=${HB_COMPAT_CROSS_VERSION:-0}
|
||||
HB_KERNEL_PIN_ACTION=${HB_KERNEL_PIN_ACTION:-none}
|
||||
HB_KERNEL_PIN_KVER=${HB_KERNEL_PIN_KVER:-}
|
||||
HB_KERNEL_PIN_PKG=${HB_KERNEL_PIN_PKG:-}
|
||||
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
|
||||
@@ -2226,6 +2229,15 @@ _rs_run_complete_guided() {
|
||||
if [[ "${HB_COMPAT_CROSS_VERSION:-0}" == "1" ]]; then
|
||||
local -a cv_skipped=()
|
||||
local cv_line cv_path cv_reason cv_rel
|
||||
# The drift block above trims the trailing newline off
|
||||
# RS_SKIP_PATHS ("${skip_paths%$'\n'}"), so the first cv_path
|
||||
# appended below would be pasted onto the previous drift path
|
||||
# (bench-reproduced: `/etc/kernel/proxmox-boot-uuids/etc/default/grub`
|
||||
# in rs-skip-paths.txt broke the match on /etc/default/grub,
|
||||
# apply_pending_restore restored it from the backup, update-grub
|
||||
# generated a boot config against the backup kernel and the
|
||||
# host paniced on next reboot). Restore the missing separator.
|
||||
[[ -n "$RS_SKIP_PATHS" && "${RS_SKIP_PATHS: -1}" != $'\n' ]] && RS_SKIP_PATHS+=$'\n'
|
||||
while IFS=$'\t' read -r cv_path cv_reason; do
|
||||
[[ -z "$cv_path" ]] && continue
|
||||
cv_rel="${cv_path#/}"
|
||||
@@ -2711,18 +2723,174 @@ _rs_run_complete_extras() {
|
||||
rm -f "$cur_pkgs_file"
|
||||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||||
echo
|
||||
# Split into apt-known vs unknown so the install count
|
||||
# announced below matches what apt-get will actually attempt.
|
||||
local -a installable=() unknown=()
|
||||
# ── Intelligent package filter (universal, not cross-version-only) ─
|
||||
#
|
||||
# `packages.manual.list` lists everything apt-marked as manual on
|
||||
# the SOURCE host. A blind `apt install` of that list on the
|
||||
# target can wreck it in two ways:
|
||||
#
|
||||
# 1. The backup pinned a package to an older SO-major (e.g.
|
||||
# libzfs6linux from Proxmox 9.1) but the target already
|
||||
# ships the newer sibling (libzfs7linux from 9.2). APT
|
||||
# would resolve the older version by cascade-removing the
|
||||
# newer one plus everything that depends on it
|
||||
# (zfsutils-linux, zfs-initramfs, zfs-zed). The `zpool`
|
||||
# binary vanishes → next reboot panics on rpool mount.
|
||||
# Bench-reproduced on nvidia3 (9.2.3 / kernel 7.0.12) with
|
||||
# an nvidia2 backup (9.1.9 / kernel 6.17.2).
|
||||
#
|
||||
# 2. Some packages are already present on the target because
|
||||
# the fresh install included them — apt doesn't need us
|
||||
# to re-request them, and re-requesting can pull config
|
||||
# prompts we don't want during unattended restore.
|
||||
#
|
||||
# The filter runs three passes:
|
||||
# (a) already_installed → dpkg -s says the pkg is on target
|
||||
# (b) provided_by_newer → target already has libFOO<N>* where
|
||||
# the backup's pkg is libFOO<M>* with
|
||||
# M < N (SO-major bump between
|
||||
# Proxmox releases)
|
||||
# (c) cascade_risk → apt-get simulate proves the install
|
||||
# would REMOVE something critical
|
||||
#
|
||||
# Each pass populates a report array. Nothing is silent — the
|
||||
# operator sees the final tally with reasons.
|
||||
local -a rep_skipped_installed=()
|
||||
local -a rep_skipped_provided=()
|
||||
local -a rep_skipped_cascade=()
|
||||
local -a rep_unknown=()
|
||||
local -a candidates=()
|
||||
|
||||
local pkg
|
||||
for pkg in "${missing[@]}"; do
|
||||
# Pass (a): already installed?
|
||||
if dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null \
|
||||
| grep -q '^install ok installed$'; then
|
||||
rep_skipped_installed+=("$pkg")
|
||||
continue
|
||||
fi
|
||||
# Pass (b): provided by newer sibling? Only for
|
||||
# `<base><N><suffix>` shaped names — the SO-major
|
||||
# convention used by libzfs<N>linux, libzpool<N>linux,
|
||||
# libnvpair<N>linux, libuutil<N>linux, libzstd<N>, etc.
|
||||
if [[ "$pkg" =~ ^(lib[a-z]+)([0-9]+)([a-z]*[0-9]*)$ ]]; then
|
||||
local _base="${BASH_REMATCH[1]}"
|
||||
local _num="${BASH_REMATCH[2]}"
|
||||
local _suffix="${BASH_REMATCH[3]}"
|
||||
local _newer_found=""
|
||||
# Only look up to +5 majors ahead — enough to cover any
|
||||
# realistic Proxmox jump without probing forever.
|
||||
local _n
|
||||
for _n in $(seq $((_num + 1)) $((_num + 5))); do
|
||||
local _sibling="${_base}${_n}${_suffix}"
|
||||
if dpkg-query -W -f='${Status}' "$_sibling" 2>/dev/null \
|
||||
| grep -q '^install ok installed$'; then
|
||||
_newer_found="$_sibling"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -n "$_newer_found" ]]; then
|
||||
rep_skipped_provided+=("${pkg}|${_newer_found}")
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
# Not skipped yet — needs the apt cache to know if it's
|
||||
# installable at all. Unknown-to-apt goes to its own list.
|
||||
if apt-cache show "$pkg" >/dev/null 2>&1; then
|
||||
installable+=("$pkg")
|
||||
candidates+=("$pkg")
|
||||
else
|
||||
unknown+=("$pkg")
|
||||
rep_unknown+=("$pkg")
|
||||
fi
|
||||
done
|
||||
|
||||
# Pass (c): apt-simulate to catch cascade-remove risk. We
|
||||
# simulate the full candidate set first; if apt would remove
|
||||
# anything, iteratively drop the "first offender" (the pkg
|
||||
# that appears earliest in the simulated remove path) and
|
||||
# retry until the simulation is clean or the list is empty.
|
||||
local -a installable=()
|
||||
if (( ${#candidates[@]} > 0 )) && command -v apt-get >/dev/null 2>&1; then
|
||||
local _work=("${candidates[@]}")
|
||||
local _guard=0
|
||||
while (( ${#_work[@]} > 0 && _guard < 20 )); do
|
||||
_guard=$((_guard + 1))
|
||||
local _sim_out
|
||||
_sim_out=$(DEBIAN_FRONTEND=noninteractive \
|
||||
apt-get install -y --simulate --no-install-recommends \
|
||||
-o Dpkg::Options::="--force-confdef" \
|
||||
-o Dpkg::Options::="--force-confold" \
|
||||
"${_work[@]}" 2>&1)
|
||||
# Look for removals: apt marks them as `Remv <pkg>...`
|
||||
# in the plan output.
|
||||
local -a _would_remove=()
|
||||
mapfile -t _would_remove < <(printf '%s\n' "$_sim_out" \
|
||||
| awk '/^Remv / {print $2}')
|
||||
if (( ${#_would_remove[@]} == 0 )); then
|
||||
installable=("${_work[@]}")
|
||||
break
|
||||
fi
|
||||
# Pick the offender: a package in _work whose install
|
||||
# is directly linked to the removal. Heuristic — if
|
||||
# any _work package shares the base+suffix pattern
|
||||
# with a would-remove package but a different major,
|
||||
# that's the offender. Otherwise, drop the last one
|
||||
# (least "essential" by convention of the list).
|
||||
local _offender=""
|
||||
local _rm
|
||||
for _rm in "${_would_remove[@]}"; do
|
||||
if [[ "$_rm" =~ ^(lib[a-z]+)([0-9]+)([a-z]*[0-9]*)$ ]]; then
|
||||
local _rmbase="${BASH_REMATCH[1]}"
|
||||
local _rmsuffix="${BASH_REMATCH[3]}"
|
||||
local _cand
|
||||
for _cand in "${_work[@]}"; do
|
||||
if [[ "$_cand" =~ ^${_rmbase}[0-9]+${_rmsuffix}$ ]]; then
|
||||
_offender="$_cand"; break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
[[ -n "$_offender" ]] && break
|
||||
done
|
||||
if [[ -z "$_offender" ]]; then
|
||||
# No obvious mapping — bail out safely: report
|
||||
# everything as cascade risk and install nothing.
|
||||
rep_skipped_cascade+=("${_work[@]/#/${_would_remove[0]}<-}")
|
||||
_work=()
|
||||
break
|
||||
fi
|
||||
rep_skipped_cascade+=("${_offender}|would remove ${_would_remove[*]:0:3}")
|
||||
# Drop the offender and retry.
|
||||
local -a _new_work=()
|
||||
local _c
|
||||
for _c in "${_work[@]}"; do
|
||||
[[ "$_c" == "$_offender" ]] || _new_work+=("$_c")
|
||||
done
|
||||
_work=("${_new_work[@]}")
|
||||
done
|
||||
fi
|
||||
|
||||
local -a unknown=("${rep_unknown[@]}")
|
||||
|
||||
# ── Summarize what the filter decided ──
|
||||
if (( ${#rep_skipped_installed[@]} > 0 )); then
|
||||
local _prev="${rep_skipped_installed[*]:0:5}"
|
||||
(( ${#rep_skipped_installed[@]} > 5 )) && _prev+=" … (+ $((${#rep_skipped_installed[@]} - 5)) more)"
|
||||
echo -e "${TAB}${BGN}$(translate "Already installed — skipping"):${CL} ${BL}${_prev}${CL}"
|
||||
fi
|
||||
if (( ${#rep_skipped_provided[@]} > 0 )); then
|
||||
echo -e "${TAB}${BGN}$(translate "Provided by newer version — skipping"):${CL}"
|
||||
local _rec
|
||||
for _rec in "${rep_skipped_provided[@]}"; do
|
||||
echo -e "${TAB} ${DGN}${_rec%%|*}${CL} → ${BL}${_rec##*|}${CL}"
|
||||
done
|
||||
fi
|
||||
if (( ${#rep_skipped_cascade[@]} > 0 )); then
|
||||
echo -e "${TAB}${YWB}$(translate "Skipped to protect target system (would cascade-remove packages)"):${CL}"
|
||||
local _rec
|
||||
for _rec in "${rep_skipped_cascade[@]}"; do
|
||||
echo -e "${TAB} ${YWB}${_rec%%|*}${CL} — ${_rec##*|}"
|
||||
done
|
||||
fi
|
||||
|
||||
if (( ${#installable[@]} > 0 )); then
|
||||
local _preview="${installable[*]:0:6}"
|
||||
(( ${#installable[@]} > 6 )) && _preview+=" … (+ $((${#installable[@]} - 6)) more)"
|
||||
@@ -2902,6 +3070,40 @@ restore_menu() {
|
||||
# (_rs_prompt_zfs_opt_in) read to choose between the
|
||||
# silent same-host path and the loud cross-host path.
|
||||
hb_compat_check "$staging_root"
|
||||
|
||||
# Cross-kernel restore requires the backup's kernel to be
|
||||
# bootable on the target — either already installed or
|
||||
# obtainable from the Proxmox repositories. When it's
|
||||
# neither, refuse cleanly: continuing would leave the
|
||||
# operator with drivers built against a kernel that isn't
|
||||
# present, and DKMS would silently pin the wrong ABI.
|
||||
if [[ "${HB_COMPAT_CROSS_VERSION:-0}" == "1" ]]; then
|
||||
local _bk_kernel=""
|
||||
if [[ -f "$staging_root/metadata/run_info.env" ]]; then
|
||||
_bk_kernel=$(grep -m1 '^kernel=' "$staging_root/metadata/run_info.env" 2>/dev/null | cut -d= -f2-)
|
||||
fi
|
||||
if [[ -n "$_bk_kernel" ]]; then
|
||||
hb_kernel_pin_plan "$_bk_kernel"
|
||||
if [[ "$HB_KERNEL_PIN_ACTION" == "unavailable" ]]; then
|
||||
# Log so the operator can inspect after the fact.
|
||||
local _refuse_log="/var/log/proxmenux/restore-refused-$(date +%Y%m%d_%H%M%S).log"
|
||||
mkdir -p "$(dirname "$_refuse_log")" 2>/dev/null
|
||||
{
|
||||
echo "Refused at: $(date -Iseconds)"
|
||||
echo "Backup kernel: $_bk_kernel"
|
||||
echo "Target running: $(uname -r)"
|
||||
echo "Reason: $HB_KERNEL_PIN_REASON"
|
||||
} > "$_refuse_log"
|
||||
dialog --backtitle "ProxMenux" --colors \
|
||||
--title "$(translate "Restore refused — incompatible kernel")" \
|
||||
--msgbox "\Zb\Z1$(translate "This backup was taken on kernel"):\Zn \Zb${_bk_kernel}\ZB"$'\n\n'"$(translate "That kernel is") ${HB_KERNEL_PIN_REASON}."$'\n\n'"$(translate "Restoring without a matching kernel would leave drivers and modules built against a kernel that is not present on this host — the system may fail to boot or run degraded.")"$'\n\n'"\Zb$(translate "How to proceed"):\ZB"$'\n'" • $(translate "Install Proxmox VE from an ISO whose kernel matches")"$'\n'" • $(translate "Or fetch the kernel manually:") apt install proxmox-kernel-${_bk_kernel}"$'\n'" • $(translate "Then relaunch the restore.")"$'\n\n'"$(translate "Details logged at"): $_refuse_log" \
|
||||
22 84
|
||||
rm -rf "$staging_root"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if hb_show_compat_report; then
|
||||
if _rs_apply_menu "$staging_root"; then
|
||||
rm -rf "$staging_root"
|
||||
|
||||
@@ -2711,6 +2711,85 @@ hb_apply_nic_remaps() {
|
||||
# Output: one line per path, tab-separated: <abs_path>\t<reason>
|
||||
# The reason is a short label used verbatim in the confirm dialog.
|
||||
# ==========================================================
|
||||
# ==========================================================
|
||||
# KERNEL PIN PLAN — cross-kernel restore only
|
||||
# ==========================================================
|
||||
# When the backup was taken on a kernel major.minor different
|
||||
# from the target's running kernel, the safe and universally
|
||||
# correct action is to boot the target with the backup's kernel
|
||||
# after the restore. That way every driver / DKMS module that
|
||||
# the backup expects (nvidia, coral, intel_gpu, plus every
|
||||
# out-of-tree ZFS build) targets a kernel that actually exists
|
||||
# and matches, and the auto-reinstall on post-boot builds them
|
||||
# against the right ABI. Universal: works for ZFS root, LVM/ext4
|
||||
# root, BIOS or UEFI hosts alike.
|
||||
#
|
||||
# This function decides how to obtain that kernel on the target
|
||||
# and publishes the plan for the restore flow to act on.
|
||||
#
|
||||
# Input:
|
||||
# $1 bk_kernel — kernel release string from the backup
|
||||
# (e.g. "6.17.2-1-pve")
|
||||
# Output (env vars, all exported):
|
||||
# HB_KERNEL_PIN_ACTION installed | installable | unavailable
|
||||
# HB_KERNEL_PIN_PKG candidate package name to install (empty
|
||||
# when installed / unavailable)
|
||||
# HB_KERNEL_PIN_KVER the kernel release to pin (same as $1)
|
||||
# Also fills HB_KERNEL_PIN_REASON with a short human message
|
||||
# when action is "unavailable" (for the refuse dialog).
|
||||
# ==========================================================
|
||||
hb_kernel_pin_plan() {
|
||||
local bk_kernel="$1"
|
||||
export HB_KERNEL_PIN_ACTION="unavailable"
|
||||
export HB_KERNEL_PIN_PKG=""
|
||||
export HB_KERNEL_PIN_KVER="$bk_kernel"
|
||||
export HB_KERNEL_PIN_REASON=""
|
||||
|
||||
[[ -z "$bk_kernel" ]] && {
|
||||
HB_KERNEL_PIN_REASON="backup did not record its kernel release"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Signed package name convention on modern Proxmox: the actual
|
||||
# binary kernel image ships as `proxmox-kernel-<ver>-signed`, the
|
||||
# unsigned build is `proxmox-kernel-<ver>` (only in special cases).
|
||||
# `pve-kernel-<ver>` was the legacy name pre-Proxmox 8; try both
|
||||
# so this works whether the backup came from an older PVE.
|
||||
local -a candidates=(
|
||||
"proxmox-kernel-${bk_kernel}-signed"
|
||||
"proxmox-kernel-${bk_kernel}"
|
||||
"pve-kernel-${bk_kernel}"
|
||||
)
|
||||
local pkg
|
||||
for pkg in "${candidates[@]}"; do
|
||||
# Already installed → nothing to fetch, just pin later.
|
||||
if dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null \
|
||||
| grep -q '^install ok installed$'; then
|
||||
HB_KERNEL_PIN_ACTION="installed"
|
||||
HB_KERNEL_PIN_PKG="$pkg"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
# Not installed. Refresh apt metadata quietly, then look each
|
||||
# candidate up. `apt-cache policy` shows a non-empty "Candidate"
|
||||
# only when there IS an installable version in the enabled repos.
|
||||
apt-get update -qq >/dev/null 2>&1 || true
|
||||
for pkg in "${candidates[@]}"; do
|
||||
local cand
|
||||
cand=$(apt-cache policy "$pkg" 2>/dev/null \
|
||||
| awk '/Candidate:/ {print $2; exit}')
|
||||
if [[ -n "$cand" && "$cand" != "(none)" ]]; then
|
||||
HB_KERNEL_PIN_ACTION="installable"
|
||||
HB_KERNEL_PIN_PKG="$pkg"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
HB_KERNEL_PIN_ACTION="unavailable"
|
||||
HB_KERNEL_PIN_REASON="not installed on this host and not available in the configured Proxmox repositories"
|
||||
return 0
|
||||
}
|
||||
|
||||
hb_unsafe_paths_cross_version() {
|
||||
cat <<'EOF'
|
||||
/etc/default/grub GRUB defaults tied to prior kernel order
|
||||
|
||||
93
scripts/backup_restore/restore/kernel_pin_plan.sh
Normal file
93
scripts/backup_restore/restore/kernel_pin_plan.sh
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
# ==========================================================
|
||||
# kernel_pin_plan.sh — JSON reporter for cross-kernel restore
|
||||
# ==========================================================
|
||||
# Usage: bash kernel_pin_plan.sh <staging_root>
|
||||
# Output: JSON on stdout — one of:
|
||||
#
|
||||
# {"detected":false}
|
||||
# Backup + target run the same kernel major.minor.
|
||||
# No action needed.
|
||||
#
|
||||
# {"detected":true,
|
||||
# "backup_kernel":"6.17.2-1-pve",
|
||||
# "target_kernel":"7.0.12-1-pve",
|
||||
# "action":"installed",
|
||||
# "pkg":"proxmox-kernel-6.17.2-1-pve-signed"}
|
||||
# Cross-kernel. The backup's kernel is already installed on the
|
||||
# target; the restore flow will pin it.
|
||||
#
|
||||
# {"detected":true, ...,
|
||||
# "action":"installable",
|
||||
# "pkg":"proxmox-kernel-6.17.2-1-pve-signed"}
|
||||
# Cross-kernel. The kernel is available in Proxmox repos and
|
||||
# will be installed before the pin.
|
||||
#
|
||||
# {"detected":true, ...,
|
||||
# "action":"unavailable",
|
||||
# "reason":"not installed on this host and not available in the
|
||||
# configured Proxmox repositories"}
|
||||
# Cross-kernel. The kernel is neither installed nor obtainable.
|
||||
# The restore flow must refuse.
|
||||
#
|
||||
# The heavy lifting is delegated to hb_kernel_pin_plan in
|
||||
# lib_host_backup_common.sh, so the CLI menu and the Web Monitor
|
||||
# agree byte-for-byte on which restores are safe to start.
|
||||
# ==========================================================
|
||||
set -euo pipefail
|
||||
|
||||
staging_root="${1:-}"
|
||||
[[ -z "$staging_root" || ! -d "$staging_root" ]] && {
|
||||
printf '{"detected":false,"error":"staging_root missing"}\n'
|
||||
exit 0
|
||||
}
|
||||
|
||||
LIB="/usr/local/share/proxmenux/scripts/backup_restore/lib_host_backup_common.sh"
|
||||
if [[ ! -f "$LIB" ]]; then
|
||||
printf '{"detected":false,"error":"lib_host_backup_common.sh not found"}\n'
|
||||
exit 0
|
||||
fi
|
||||
# shellcheck source=/dev/null
|
||||
source "$LIB"
|
||||
|
||||
# Read the backup's kernel release.
|
||||
bk_kernel=""
|
||||
if [[ -f "$staging_root/metadata/run_info.env" ]]; then
|
||||
bk_kernel=$(grep -m1 '^kernel=' "$staging_root/metadata/run_info.env" 2>/dev/null \
|
||||
| cut -d= -f2-)
|
||||
fi
|
||||
cur_kernel=$(uname -r 2>/dev/null || echo "")
|
||||
|
||||
if [[ -z "$bk_kernel" || -z "$cur_kernel" ]]; then
|
||||
printf '{"detected":false}\n'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Compare major.minor — the same rule hb_compat_check uses.
|
||||
bk_mm=$(echo "$bk_kernel" | cut -d. -f1-2)
|
||||
cur_mm=$(echo "$cur_kernel" | cut -d. -f1-2)
|
||||
if [[ "$bk_mm" == "$cur_mm" ]]; then
|
||||
printf '{"detected":false}\n'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Cross-kernel: compute the pin plan.
|
||||
hb_kernel_pin_plan "$bk_kernel"
|
||||
|
||||
# Emit JSON. jq if we have it (safer with special chars), else printf.
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
jq -cn \
|
||||
--arg bkk "$bk_kernel" \
|
||||
--arg trk "$cur_kernel" \
|
||||
--arg act "${HB_KERNEL_PIN_ACTION:-unavailable}" \
|
||||
--arg pkg "${HB_KERNEL_PIN_PKG:-}" \
|
||||
--arg rsn "${HB_KERNEL_PIN_REASON:-}" \
|
||||
'{detected:true, backup_kernel:$bkk, target_kernel:$trk,
|
||||
action:$act, pkg:$pkg, reason:$rsn}'
|
||||
else
|
||||
printf '{"detected":true,"backup_kernel":"%s","target_kernel":"%s","action":"%s","pkg":"%s","reason":"%s"}\n' \
|
||||
"$bk_kernel" "$cur_kernel" \
|
||||
"${HB_KERNEL_PIN_ACTION:-unavailable}" \
|
||||
"${HB_KERNEL_PIN_PKG:-}" \
|
||||
"${HB_KERNEL_PIN_REASON:-}"
|
||||
fi
|
||||
Reference in New Issue
Block a user