Clonezilla back up / restore by machine number
Boot menu: "Clonezilla: back up this PC" and "restore this PC" ask for the machine number and boot Clonezilla (64-bit with Debian shim, or 32-bit i686 by cpuid) with ocs_repository=smb3://clonezilla:<pw>@server/clonezilla/, ocs_prerun fetching clonezilla-scripts/pxe-cz.sh and ocs_live_run running it. The script validates the machine number, saves <machine>-<YYYY-MM-DD-HHMM> (server time), writes pxe-meta.txt, runs ocs-chkimg, and restores only that machine's images with -k1 -r after a typed YES. The old entry is "Clonezilla: manual". Playbook: clonezilla Samba user for the [clonezilla] share only, password from clonezilla_smb_password (validated, filled into the menu placeholder), scripts installed to the web root. Webapp: Clonezilla backups page lists image folders and zips grouped by machine number with serial/model/version/check, serial mismatch flag, delete, move to another machine, streamed tar download, disk space bar and an explicit keep-last-N retention with preview. Tests and a VM test plan added.
This commit is contained in:
473
playbook/clonezilla-scripts/pxe-cz.sh
Normal file
473
playbook/clonezilla-scripts/pxe-cz.sh
Normal file
@@ -0,0 +1,473 @@
|
||||
#!/bin/bash
|
||||
# PXE Clonezilla: back up or restore a PC by its machine number.
|
||||
#
|
||||
# The iPXE menu (GetPxeScript.aspx, "Clonezilla: back up / restore this
|
||||
# PC") boots Clonezilla live with:
|
||||
# ocs_prerun="wget ... -O /tmp/pxe-cz.sh http://<server>/clonezilla-scripts/pxe-cz.sh"
|
||||
# ocs_repository="smb3://clonezilla:<password>@<server>/clonezilla/"
|
||||
# ocs_live_run="bash /tmp/pxe-cz.sh"
|
||||
# pxe_cz_mode=save|restore pxe_machine=<typed text> pxe_end=1
|
||||
# Clonezilla runs the prerun (this download), mounts the repository on
|
||||
# /home/partimag, then runs this script as root on the console.
|
||||
#
|
||||
# Images are named <machine>-<YYYY-MM-DD-HHMM> (server time, site zone),
|
||||
# with pxe-meta.txt (machine, serial, model, disk, date, Clonezilla
|
||||
# version, 32/64-bit, check result) next to Clonezilla's own files. The
|
||||
# PXE webapp (Tools > Clonezilla backups) reads both.
|
||||
#
|
||||
# Runs under Clonezilla live's bash (3.3.3 amd64 and 3.2.0-5 i686, both
|
||||
# Debian with coreutils, util-linux, dmidecode, wget). Plain ASCII only.
|
||||
|
||||
set -u
|
||||
|
||||
# PXE_CZ_* overrides exist only for testing this script off Clonezilla.
|
||||
OCSROOT="${PXE_CZ_OCSROOT:-/home/partimag}"
|
||||
CMDLINE_FILE="${PXE_CZ_CMDLINE:-/proc/cmdline}"
|
||||
SYS_BLOCK="${PXE_CZ_SYS_BLOCK:-/sys/block}"
|
||||
SITE_TZ="America/New_York"
|
||||
SERVER="$(sed -n 's/.*ocs_prerun="[^"]*http:\/\/\([^/":]*\).*/\1/p' "$CMDLINE_FILE" 2>/dev/null)"
|
||||
[ -n "$SERVER" ] || SERVER=172.16.9.1
|
||||
CMDLINE=" $(cat "$CMDLINE_FILE" 2>/dev/null) "
|
||||
|
||||
# --- output helpers -------------------------------------------------------
|
||||
|
||||
line() { echo "======================================================================"; }
|
||||
|
||||
banner() {
|
||||
echo
|
||||
line
|
||||
for l in "$@"; do echo " $l"; done
|
||||
line
|
||||
echo
|
||||
}
|
||||
|
||||
# Ask what to do at the end. Success: power off after 60 s unless a key is
|
||||
# pressed. Failure: wait, the operator has to read the message.
|
||||
finish() {
|
||||
local ok="$1" ans
|
||||
sync
|
||||
while true; do
|
||||
echo
|
||||
echo " [Enter] power off r = reboot s = shell (type exit to come back)"
|
||||
if [ "$ok" = "yes" ]; then
|
||||
echo -n " Powering off in 60 seconds... "
|
||||
if ! read -r -t 60 ans; then ans=""; echo; fi
|
||||
else
|
||||
echo -n " Choice: "
|
||||
read -r ans || ans=""
|
||||
fi
|
||||
case "$ans" in
|
||||
r|R) umount "$OCSROOT" 2>/dev/null; sync; reboot; sleep 30 ;;
|
||||
s|S) bash -i ;;
|
||||
*) umount "$OCSROOT" 2>/dev/null; sync; poweroff; sleep 30 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
fail() {
|
||||
banner "FAILED: $1" "${@:2}"
|
||||
finish no
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- machine number -------------------------------------------------------
|
||||
|
||||
# Letters, digits and dash, starting with a letter or digit, up to 32.
|
||||
valid_machine() {
|
||||
[[ "$1" =~ ^[A-Za-z0-9][A-Za-z0-9-]{0,31}$ ]]
|
||||
}
|
||||
|
||||
# The text typed at the iPXE prompt sits between pxe_machine= and the
|
||||
# pxe_end=1 sentinel, so a typed space shows up here and is rejected
|
||||
# instead of being split into another kernel parameter.
|
||||
machine_from_cmdline() {
|
||||
local rest
|
||||
case "$CMDLINE" in
|
||||
*" pxe_machine="*" pxe_end=1 "*) ;;
|
||||
*) echo ""; return ;;
|
||||
esac
|
||||
rest="${CMDLINE#* pxe_machine=}"
|
||||
echo "${rest%% pxe_end=1 *}"
|
||||
}
|
||||
|
||||
# Sets MACHINE (upper case). Not run in $(...): finish() reads the
|
||||
# keyboard and must write to the console.
|
||||
get_machine() {
|
||||
local m
|
||||
m="$(machine_from_cmdline)"
|
||||
if [ -z "$m" ] || ! valid_machine "$m"; then
|
||||
if [ -n "$m" ]; then
|
||||
banner "The machine number typed at the boot menu is not valid:" \
|
||||
" [$m]" "Use letters, digits and dash only (for example 6501 or WJ-6501)."
|
||||
else
|
||||
banner "No machine number was given at the boot menu."
|
||||
fi
|
||||
while true; do
|
||||
echo -n " Machine number (letters, digits, dash; empty = stop): "
|
||||
read -r m || m=""
|
||||
[ -n "$m" ] || finish no
|
||||
valid_machine "$m" && break
|
||||
echo " [$m] is not a valid machine number: use letters, digits and dash only."
|
||||
done
|
||||
fi
|
||||
MACHINE="$(echo "$m" | tr '[:lower:]' '[:upper:]')"
|
||||
}
|
||||
|
||||
# --- time -----------------------------------------------------------------
|
||||
|
||||
# The PC's clock is often wrong (dead CMOS battery on old PCs, local time
|
||||
# on Windows PCs), so take the time from the PXE server's HTTP Date header
|
||||
# and set the live system clock from it. Falls back to the PC clock.
|
||||
TIME_SOURCE="pc-clock"
|
||||
sync_clock() {
|
||||
local hdr epoch
|
||||
hdr="$(wget -q -S --spider -T 10 -t 2 "http://$SERVER/clonezilla-scripts/pxe-cz.sh" 2>&1 \
|
||||
| sed -n 's/^ *Date: *//p' | head -1)"
|
||||
[ -n "$hdr" ] || return 0
|
||||
epoch="$(date -u -d "$hdr" +%s 2>/dev/null)" || return 0
|
||||
[ -n "$epoch" ] || return 0
|
||||
date -u -s "@$epoch" >/dev/null 2>&1 && TIME_SOURCE="pxe-server"
|
||||
}
|
||||
|
||||
site_date() {
|
||||
if [ -e "/usr/share/zoneinfo/$SITE_TZ" ]; then
|
||||
TZ="$SITE_TZ" date "$@"
|
||||
else
|
||||
date "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- hardware -------------------------------------------------------------
|
||||
|
||||
dmi() {
|
||||
local v
|
||||
v="$(dmidecode -s "$1" 2>/dev/null | grep -v '^#' | head -1 | tr -d '\r' | sed 's/^ *//; s/ *$//')"
|
||||
echo "$v"
|
||||
}
|
||||
|
||||
# Whole disks a backup can come from or a restore can go to: no CD, loop,
|
||||
# RAM, zram, device-mapper, md, eMMC boot areas, or empty card readers.
|
||||
list_disks() {
|
||||
local d n size
|
||||
for d in "$SYS_BLOCK"/*; do
|
||||
n="${d##*/}"
|
||||
case "$n" in
|
||||
loop*|sr*|ram*|zram*|dm-*|md*|fd*|nbd*|*boot[0-9]|*rpmb) continue ;;
|
||||
esac
|
||||
size="$(cat "$d/size" 2>/dev/null)"
|
||||
[ -n "$size" ] && [ "$size" -gt 0 ] 2>/dev/null || continue
|
||||
echo "$n"
|
||||
done
|
||||
}
|
||||
|
||||
disk_bytes() { echo $(( $(cat "$SYS_BLOCK/$1/size" 2>/dev/null || echo 0) * 512 )); }
|
||||
|
||||
disk_model() {
|
||||
local m
|
||||
m="$(cat "$SYS_BLOCK/$1/device/model" 2>/dev/null | sed 's/^ *//; s/ *$//')"
|
||||
[ -n "$m" ] || m="$(lsblk -dn -o MODEL "/dev/$1" 2>/dev/null | sed 's/^ *//; s/ *$//')"
|
||||
[ -n "$m" ] || m="unknown model"
|
||||
echo "$m"
|
||||
}
|
||||
|
||||
disk_tran() { lsblk -dn -o TRAN "/dev/$1" 2>/dev/null | sed 's/ //g'; }
|
||||
|
||||
human() {
|
||||
local b="$1"
|
||||
awk -v b="$b" 'BEGIN { if (b >= 1e12) printf "%.1f TB", b/1e12; else printf "%.0f GB", b/1e9 }'
|
||||
}
|
||||
|
||||
describe_disk() {
|
||||
local n="$1" t
|
||||
t="$(disk_tran "$n")"
|
||||
echo "/dev/$n $(human "$(disk_bytes "$n")") $(disk_model "$n")${t:+ ($t)}"
|
||||
}
|
||||
|
||||
# pick_disk "prompt" -> disk name on stdout. One disk: that one.
|
||||
pick_disk() {
|
||||
local prompt="$1" disks n i ans
|
||||
mapfile -t disks < <(list_disks)
|
||||
if [ "${#disks[@]}" -eq 0 ]; then
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
if [ "${#disks[@]}" -eq 1 ]; then
|
||||
echo "${disks[0]}"
|
||||
return
|
||||
fi
|
||||
while true; do
|
||||
{
|
||||
echo
|
||||
echo " $prompt"
|
||||
i=1
|
||||
for n in "${disks[@]}"; do
|
||||
echo " $i) $(describe_disk "$n")"
|
||||
i=$((i + 1))
|
||||
done
|
||||
echo -n " Number (1-${#disks[@]}): "
|
||||
} >&2
|
||||
read -r ans || ans=""
|
||||
if [[ "$ans" =~ ^[0-9]+$ ]] && [ "$ans" -ge 1 ] && [ "$ans" -le "${#disks[@]}" ]; then
|
||||
echo "${disks[$((ans - 1))]}"
|
||||
return
|
||||
fi
|
||||
echo " Type a number from the list." >&2
|
||||
done
|
||||
}
|
||||
|
||||
live_version() {
|
||||
local v=""
|
||||
if [ -e /etc/ocs/ocs-live.conf ]; then
|
||||
# shellcheck disable=SC1091
|
||||
v="$(. /etc/ocs/ocs-live.conf >/dev/null 2>&1; echo "${drbl_ocs_live_ver:-}")"
|
||||
fi
|
||||
[ -n "$v" ] || v="clonezilla $(dpkg-query -W -f='${Version}' clonezilla 2>/dev/null)"
|
||||
echo "$v"
|
||||
}
|
||||
|
||||
bits() {
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) echo 64 ;;
|
||||
*) echo 32 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# set_meta FILE KEY VALUE: replace or add one key=value line.
|
||||
set_meta() {
|
||||
local f="$1" k="$2" v="$3" tmp
|
||||
v="$(echo "$v" | tr -d '\r\n')"
|
||||
tmp="$(mktemp /tmp/pxe-meta.XXXXXX)"
|
||||
[ -e "$f" ] && grep -v "^$k=" "$f" > "$tmp"
|
||||
echo "$k=$v" >> "$tmp"
|
||||
cat "$tmp" > "$f"
|
||||
rm -f "$tmp"
|
||||
}
|
||||
|
||||
get_meta() {
|
||||
sed -n "s/^$2=//p" "$1" 2>/dev/null | head -1
|
||||
}
|
||||
|
||||
repo_ready() {
|
||||
mountpoint -q "$OCSROOT" 2>/dev/null || grep -qs " $OCSROOT cifs " /proc/mounts
|
||||
}
|
||||
|
||||
# --- backup ---------------------------------------------------------------
|
||||
|
||||
do_save() {
|
||||
local m="$1" disk stamp img n meta rc chk
|
||||
disk="$(pick_disk "This PC has more than one disk. Which one do you want to back up?")"
|
||||
[ -n "$disk" ] || fail "No disk found to back up." "Check the disk is connected and shows in the BIOS."
|
||||
stamp="$(site_date +%Y-%m-%d-%H%M)"
|
||||
img="$m-$stamp"
|
||||
n=2
|
||||
while [ -e "$OCSROOT/$img" ]; do
|
||||
img="$m-$stamp-$n"
|
||||
n=$((n + 1))
|
||||
done
|
||||
|
||||
banner "BACKUP of machine $m" \
|
||||
"Disk: $(describe_disk "$disk")" \
|
||||
"Image: $img" \
|
||||
"To: \\\\$SERVER\\clonezilla\\$img"
|
||||
|
||||
ocs-sr -q2 -batch -j2 -z9p -i 0 -sfsck -scs -senc -p command savedisk "$img" "$disk"
|
||||
rc=$?
|
||||
|
||||
meta="$OCSROOT/$img/pxe-meta.txt"
|
||||
if [ ! -d "$OCSROOT/$img" ]; then
|
||||
fail "Clonezilla did not create the image $img (exit code $rc)." \
|
||||
"See the messages above. Nothing was saved."
|
||||
fi
|
||||
: > "$meta"
|
||||
set_meta "$meta" machine "$m"
|
||||
set_meta "$meta" date "$(site_date '+%Y-%m-%d %H:%M %Z')"
|
||||
set_meta "$meta" date_utc "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
set_meta "$meta" time_source "$TIME_SOURCE"
|
||||
set_meta "$meta" serial "$(dmi system-serial-number)"
|
||||
set_meta "$meta" manufacturer "$(dmi system-manufacturer)"
|
||||
set_meta "$meta" model "$(dmi system-product-name)"
|
||||
set_meta "$meta" disk "$disk"
|
||||
set_meta "$meta" disk_model "$(disk_model "$disk")"
|
||||
set_meta "$meta" disk_size_bytes "$(disk_bytes "$disk")"
|
||||
set_meta "$meta" clonezilla "$(live_version)"
|
||||
set_meta "$meta" bits "$(bits)"
|
||||
if [ -d /sys/firmware/efi ]; then set_meta "$meta" firmware uefi; else set_meta "$meta" firmware bios; fi
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
set_meta "$meta" save failed
|
||||
set_meta "$meta" check not-run
|
||||
fail "Clonezilla could not save machine $m (exit code $rc)." \
|
||||
"The partial image $img is marked as failed on the share;" \
|
||||
"delete it in the webapp (Tools > Clonezilla backups) and try again."
|
||||
fi
|
||||
set_meta "$meta" save ok
|
||||
|
||||
banner "Checking the saved image $img ..."
|
||||
ocs-chkimg -b "$img"
|
||||
chk=$?
|
||||
set_meta "$meta" check_date "$(site_date '+%Y-%m-%d %H:%M %Z')"
|
||||
if [ "$chk" -ne 0 ]; then
|
||||
set_meta "$meta" check failed
|
||||
fail "The image $img was saved but its CHECK FAILED (exit code $chk)." \
|
||||
"Do not rely on it. Back this PC up again."
|
||||
fi
|
||||
set_meta "$meta" check ok
|
||||
sync
|
||||
banner "SUCCESS: machine $m backed up and checked." \
|
||||
"Image: $img" \
|
||||
"Serial: $(get_meta "$meta" serial) Model: $(get_meta "$meta" model)"
|
||||
finish yes
|
||||
}
|
||||
|
||||
# --- restore --------------------------------------------------------------
|
||||
|
||||
is_cz_image() {
|
||||
local d="$1"
|
||||
[ -d "$d" ] || return 1
|
||||
[ -f "$d/disk" ] || [ -f "$d/parts" ] || return 1
|
||||
local f
|
||||
for f in "$d"/*; do
|
||||
case "${f##*/}" in
|
||||
*-ptcl-img*|*.dd-img*|*-img.*|clonezilla-img) return 0 ;;
|
||||
esac
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Images of one machine, newest first: <machine>-YYYY-MM-DD-HHMM[-n] by
|
||||
# name, plus any image whose pxe-meta.txt says machine=<machine>.
|
||||
machine_images() {
|
||||
local m="$1" d n mm
|
||||
for d in "$OCSROOT"/*/; do
|
||||
d="${d%/}"
|
||||
n="${d##*/}"
|
||||
is_cz_image "$d" || continue
|
||||
mm="$(get_meta "$d/pxe-meta.txt" machine | tr '[:lower:]' '[:upper:]')"
|
||||
if [ "$mm" = "$m" ]; then
|
||||
echo "$n"
|
||||
elif [ -z "$mm" ] && echo "$n" | tr '[:lower:]' '[:upper:]' | grep -qE "^$m-[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}(-[0-9]+)?$"; then
|
||||
echo "$n"
|
||||
fi
|
||||
done | sort_newest
|
||||
}
|
||||
|
||||
# Sort names by their date stamp (the last YYYY-MM-DD-HHMM[-n]), newest first.
|
||||
sort_newest() {
|
||||
local n key
|
||||
while read -r n; do
|
||||
key="$(echo "$n" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}(-[0-9]+)?$')"
|
||||
echo "${key:-0000}|$n"
|
||||
done | sort -r | cut -d'|' -f2-
|
||||
}
|
||||
|
||||
image_disks() { tr -s ' \t' '\n' < "$OCSROOT/$1/disk" 2>/dev/null | grep -v '^$'; }
|
||||
|
||||
image_disk_bytes() {
|
||||
local img="$1" src="$2" b sec
|
||||
b="$(get_meta "$OCSROOT/$img/pxe-meta.txt" disk_size_bytes)"
|
||||
if [[ "$b" =~ ^[0-9]+$ ]]; then echo "$b"; return; fi
|
||||
# Clonezilla's sfdisk dump: "last-lba: N" (GPT) or the partitions' end.
|
||||
sec="$(sed -n 's/^last-lba: *//p' "$OCSROOT/$img/$src-pt.sf" 2>/dev/null | head -1)"
|
||||
if [[ "$sec" =~ ^[0-9]+$ ]]; then echo $(( (sec + 34) * 512 )); return; fi
|
||||
echo 0
|
||||
}
|
||||
|
||||
do_restore() {
|
||||
local m="$1" imgs i n ans img src srcb tgt tgtb meta chkopt rc mo se ck
|
||||
mapfile -t imgs < <(machine_images "$m")
|
||||
if [ "${#imgs[@]}" -eq 0 ]; then
|
||||
fail "No images for machine $m on \\\\$SERVER\\clonezilla." \
|
||||
"Check the machine number, or look in the webapp (Tools > Clonezilla backups)."
|
||||
fi
|
||||
while true; do
|
||||
echo
|
||||
echo " Images of machine $m (newest first):"
|
||||
i=1
|
||||
for n in "${imgs[@]}"; do
|
||||
meta="$OCSROOT/$n/pxe-meta.txt"
|
||||
mo="$(get_meta "$meta" model)"; se="$(get_meta "$meta" serial)"; ck="$(get_meta "$meta" check)"
|
||||
echo " $i) $n ${mo:-model ?} serial ${se:-?} check: ${ck:-?}"
|
||||
i=$((i + 1))
|
||||
done
|
||||
echo -n " Image to restore (1-${#imgs[@]}, q = quit): "
|
||||
read -r ans || ans=q
|
||||
case "$ans" in q|Q) finish no ;; esac
|
||||
if [[ "$ans" =~ ^[0-9]+$ ]] && [ "$ans" -ge 1 ] && [ "$ans" -le "${#imgs[@]}" ]; then
|
||||
img="${imgs[$((ans - 1))]}"
|
||||
break
|
||||
fi
|
||||
echo " Type a number from the list."
|
||||
done
|
||||
meta="$OCSROOT/$img/pxe-meta.txt"
|
||||
if [ "$(get_meta "$meta" save)" = "failed" ]; then
|
||||
fail "The image $img is marked as a FAILED backup and cannot be restored."
|
||||
fi
|
||||
mapfile -t src < <(image_disks "$img")
|
||||
if [ "${#src[@]}" -ne 1 ]; then
|
||||
fail "The image $img holds ${#src[@]} disks (${src[*]:-none})." \
|
||||
"Restore it with Clonezilla: manual (boot menu)."
|
||||
fi
|
||||
srcb="$(image_disk_bytes "$img" "${src[0]}")"
|
||||
|
||||
tgt="$(pick_disk "Which disk do you want to restore $img to? (ALL DATA ON IT WILL BE ERASED)")"
|
||||
[ -n "$tgt" ] || fail "No disk found to restore to."
|
||||
tgtb="$(disk_bytes "$tgt")"
|
||||
if [ "$srcb" -gt 0 ] && [ "$tgtb" -lt "$srcb" ]; then
|
||||
fail "The target disk is SMALLER than the disk the image came from." \
|
||||
"Image disk: $(human "$srcb")" "Target disk: $(describe_disk "$tgt")" \
|
||||
"Use a disk at least as large, or Clonezilla: manual (expert mode, -icds)."
|
||||
fi
|
||||
|
||||
banner "RESTORE machine $m" \
|
||||
"Image: $img (saved $(get_meta "$meta" date))" \
|
||||
"From: $(get_meta "$meta" model) serial $(get_meta "$meta" serial) disk $(human "$srcb")" \
|
||||
"Target: $(describe_disk "$tgt")" \
|
||||
"" \
|
||||
"EVERYTHING on /dev/$tgt will be ERASED and replaced by this image." \
|
||||
"This PC: $(dmi system-product-name) serial $(dmi system-serial-number)"
|
||||
echo -n " Type YES (capitals) to erase /dev/$tgt and restore: "
|
||||
read -r ans || ans=""
|
||||
if [ "$ans" != "YES" ]; then
|
||||
banner "Cancelled. Nothing was written."
|
||||
finish no
|
||||
fi
|
||||
|
||||
# A larger target: -k1 recreates the partition table proportionally
|
||||
# (every partition scaled by target/source size) and -r grows each
|
||||
# filesystem to its new partition, so the extra space is used. The
|
||||
# default (-k0) copies the table as-is and leaves the rest unallocated.
|
||||
# The image was checked after it was saved; check again only if that
|
||||
# check did not pass.
|
||||
chkopt=""
|
||||
[ "$(get_meta "$meta" check)" = "ok" ] && chkopt="-scr"
|
||||
ocs-sr -g auto -e1 auto -e2 -r -j2 -k1 $chkopt -batch -p command restoredisk "$img" "$tgt"
|
||||
rc=$?
|
||||
if [ "$rc" -ne 0 ]; then
|
||||
fail "Clonezilla could not restore $img to /dev/$tgt (exit code $rc)." \
|
||||
"The disk may be partly written: do not boot from it until a restore succeeds."
|
||||
fi
|
||||
sync
|
||||
banner "SUCCESS: $img restored to /dev/$tgt." "At the next start, boot this PC from its disk (not the network)."
|
||||
finish yes
|
||||
}
|
||||
|
||||
# --- main -----------------------------------------------------------------
|
||||
|
||||
MODE="$(echo "$CMDLINE" | sed -n 's/.* pxe_cz_mode=\([a-z]*\) .*/\1/p')"
|
||||
case "$MODE" in
|
||||
save|restore) ;;
|
||||
*) fail "Unknown mode [$MODE] on the kernel command line (pxe_cz_mode=save or restore)." ;;
|
||||
esac
|
||||
|
||||
if ! repo_ready; then
|
||||
fail "The backup share \\\\$SERVER\\clonezilla is not mounted on $OCSROOT." \
|
||||
"Check the PXE server is up and the clonezilla Samba account works" \
|
||||
"(playbook var clonezilla_smb_password), then boot this option again."
|
||||
fi
|
||||
|
||||
sync_clock
|
||||
MACHINE=""
|
||||
get_machine
|
||||
case "$MODE" in
|
||||
save) do_save "$MACHINE" ;;
|
||||
restore) do_restore "$MACHINE" ;;
|
||||
esac
|
||||
@@ -10,8 +10,10 @@ item --gap -- ---- Drive Erase ----
|
||||
item blancco Blancco Drive Eraser
|
||||
item blancco_debug Blancco Drive Eraser (debug / SSH)
|
||||
item --gap -- ---- Utilities ----
|
||||
item clonezilla Clonezilla Live (Disk Imaging)
|
||||
item clonezilla32 Clonezilla Live 32-bit (XP-era PCs, forced)
|
||||
item cz_backup Clonezilla: back up this PC
|
||||
item cz_restore Clonezilla: restore this PC
|
||||
item clonezilla Clonezilla: manual (USB or other location)
|
||||
item clonezilla32 Clonezilla: manual, 32-bit (XP-era PCs, forced)
|
||||
item --gap -- Memory test: Dell PCs press F12 at power-on, Diagnostics
|
||||
item --gap -- ----
|
||||
item reboot Reboot
|
||||
@@ -48,6 +50,47 @@ echo Windows PE boot FAILED (see the error above).
|
||||
echo Check that the PXE server is up and the win11 boot files are staged.
|
||||
prompt Press any key to return to menu... && goto menu || goto menu
|
||||
|
||||
# --- Clonezilla by machine number (back up / restore) ---------------------
|
||||
# The image share is \\${server}\clonezilla (Samba account clonezilla,
|
||||
# the playbook fills in its password). Clonezilla mounts it on
|
||||
# /home/partimag (ocs_repository, SMB 3), downloads
|
||||
# clonezilla-scripts/pxe-cz.sh (ocs_prerun) and runs it (ocs_live_run):
|
||||
# it checks the machine number, saves <machine>-<YYYY-MM-DD-HHMM> or lists
|
||||
# that machine's images to restore, and writes pxe-meta.txt.
|
||||
:cz_backup
|
||||
set cz_mode save
|
||||
goto cz_machine
|
||||
|
||||
:cz_restore
|
||||
set cz_mode restore
|
||||
goto cz_machine
|
||||
|
||||
:cz_machine
|
||||
# iPXE cannot check characters: pxe-cz.sh accepts letters, digits and dash
|
||||
# and asks again there. Empty: ask again. Esc / Ctrl-C: back to the menu.
|
||||
echo
|
||||
echo -n Machine number: && read czmachine || goto menu
|
||||
isset ${czmachine} || goto cz_machine
|
||||
# Same CPU check as the manual entry below (cpuid --ext 29 = 64-bit).
|
||||
cpuid --ext 29 && goto cz_auto64 || goto cz_auto32
|
||||
|
||||
:cz_auto64
|
||||
set base http://${server}/clonezilla
|
||||
kernel ${base}/vmlinuz boot=live username=user union=overlay config components noswap edd=on nomodeset nodmraid locales=en_US.UTF-8 keyboard-layouts=us ocs_prerun="wget -q -T 15 -t 5 -O /tmp/pxe-cz.sh http://${server}/clonezilla-scripts/pxe-cz.sh" ocs_repository="smb3://clonezilla:__CLONEZILLA_SMB_PASSWORD__@${server}/clonezilla/" echo_ocs_repository=no ocs_live_run="bash /tmp/pxe-cz.sh" ocs_live_extra_param="" ocs_live_batch=no net.ifnames=0 nosplash noprompt fetch=${base}/filesystem.squashfs pxe_cz_mode=${cz_mode} pxe_machine=${czmachine} pxe_end=1 || goto secureboot_warn
|
||||
initrd ${base}/initrd.img || goto boot_failed
|
||||
# Secure Boot ON: Debian's shim, as in :clonezilla64 below.
|
||||
shim ${base}/shimx64.efi ||
|
||||
boot || goto secureboot_warn
|
||||
|
||||
:cz_auto32
|
||||
# 3.2.0-5 i686 over legacy BIOS only; UEFI goes to the 64-bit build.
|
||||
iseq ${platform} efi && goto cz_auto64 ||
|
||||
set base http://${server}/clonezilla-i686
|
||||
kernel ${base}/vmlinuz boot=live username=user union=overlay config components noswap edd=on nomodeset nodmraid locales=en_US.UTF-8 keyboard-layouts=us ocs_prerun="wget -q -T 15 -t 5 -O /tmp/pxe-cz.sh http://${server}/clonezilla-scripts/pxe-cz.sh" ocs_repository="smb3://clonezilla:__CLONEZILLA_SMB_PASSWORD__@${server}/clonezilla/" echo_ocs_repository=no ocs_live_run="bash /tmp/pxe-cz.sh" ocs_live_extra_param="" ocs_live_batch=no net.ifnames=0 nosplash noprompt fetch=${base}/filesystem.squashfs pxe_cz_mode=${cz_mode} pxe_machine=${czmachine} pxe_end=1 || goto boot_failed
|
||||
initrd ${base}/initrd.img || goto boot_failed
|
||||
boot || goto boot_failed
|
||||
|
||||
# --- Clonezilla manual (interactive: USB, SSH, NFS or any share) ------------
|
||||
:clonezilla
|
||||
# 64-bit CPUs (cpuid extended bit 29 = long mode) get the current Clonezilla;
|
||||
# older 32-bit-only CPUs (Pentium 4 without EM64T, Pentium M, early Atom:
|
||||
|
||||
@@ -180,6 +180,14 @@
|
||||
# suffix is not blancco_ksuffix.
|
||||
blancco_kver: "7.0.0-34-generic"
|
||||
blancco_ksuffix: "{{ blancco_kver.split('.')[0] }}{{ blancco_kver.split('.')[1] }}"
|
||||
# Samba account for the [clonezilla] share only (Clonezilla back up /
|
||||
# restore by machine number). The iPXE menu carries it in Clonezilla's
|
||||
# ocs_repository URI (smb3://clonezilla:<password>@server/clonezilla/),
|
||||
# so any imaging client can read it: it opens that one share and
|
||||
# nothing else. Set a site value in site-vars.yml. Allowed characters:
|
||||
# letters, digits . _ ~ - (it sits in a URI, a kernel command line and
|
||||
# a mount option list), 8 to 64 of them.
|
||||
clonezilla_smb_password: "clonezilla-backup"
|
||||
image_types:
|
||||
- gea-standard
|
||||
- gea-engineer
|
||||
@@ -250,6 +258,17 @@
|
||||
Fix playbook/site-vars.yml and re-run.
|
||||
quiet: yes
|
||||
|
||||
- name: "Clonezilla: check the clonezilla Samba password"
|
||||
assert:
|
||||
that:
|
||||
- clonezilla_smb_password is string
|
||||
- clonezilla_smb_password is match('^[A-Za-z0-9._~-]{8,64}$')
|
||||
fail_msg: >-
|
||||
clonezilla_smb_password must be 8-64 characters from A-Z a-z 0-9 . _ ~ -
|
||||
(it goes into Clonezilla's smb3:// URI on the kernel command line).
|
||||
Fix it in playbook/site-vars.yml and re-run.
|
||||
quiet: yes
|
||||
|
||||
# --- Re-run safety (nothing below changes the box until these pass) ---
|
||||
- name: "Re-run safety: is imaging in progress? (SMB sessions, recent DHCP leases)"
|
||||
args:
|
||||
@@ -754,13 +773,50 @@
|
||||
# (iPXE shim command) so it runs with Secure Boot on. The Blancco
|
||||
# Releases page edits this file (test entry), so keep exactly one
|
||||
# "item blancco" line and the :blancco_efi kernel+initrd block.
|
||||
# The Clonezilla back up / restore entries carry the clonezilla
|
||||
# Samba password: the tracked file has the placeholder
|
||||
# __CLONEZILLA_SMB_PASSWORD__, filled in here (content, so a re-run
|
||||
# with the same password changes nothing).
|
||||
copy:
|
||||
src: "{{ usb_mount }}/ipxe/GetPxeScript.aspx"
|
||||
content: "{{ lookup('file', usb_mount ~ '/ipxe/GetPxeScript.aspx', rstrip=False) | replace('__CLONEZILLA_SMB_PASSWORD__', clonezilla_smb_password) }}"
|
||||
dest: "{{ web_root }}/Altiris/iPXE/GetPxeScript.aspx"
|
||||
mode: '0644'
|
||||
backup: yes
|
||||
diff: false # the content carries clonezilla_smb_password
|
||||
when: "'STAGED-TEST' not in menu_test_entry.stdout"
|
||||
|
||||
- name: "Menu: NOT replaced, so check the live menu's Clonezilla password"
|
||||
# The Blancco test entry blocked the menu update above: a changed
|
||||
# clonezilla_smb_password would leave the live menu with the old one
|
||||
# (Clonezilla back up / restore then cannot mount the share).
|
||||
shell: |
|
||||
F="{{ web_root }}/Altiris/iPXE/GetPxeScript.aspx"
|
||||
grep -q 'ocs_repository=' "$F" || { echo "no Clonezilla repository entries in the live menu"; exit 0; }
|
||||
if grep -qF "smb3://clonezilla:${CZ_PW}@" "$F"; then echo "password matches"; else
|
||||
echo "WARNING: the live menu's Clonezilla entries do not carry clonezilla_smb_password;"
|
||||
echo "remove the Blancco test entry and re-run to update the menu."
|
||||
fi
|
||||
environment:
|
||||
CZ_PW: "{{ clonezilla_smb_password }}"
|
||||
register: menu_cz_pw
|
||||
changed_when: false
|
||||
no_log: true
|
||||
when: "'STAGED-TEST' in menu_test_entry.stdout"
|
||||
|
||||
- name: "Menu: Clonezilla password check result"
|
||||
debug:
|
||||
msg: "{{ menu_cz_pw.stdout }}"
|
||||
when: "'STAGED-TEST' in menu_test_entry.stdout"
|
||||
|
||||
- name: "Install the Clonezilla back up / restore scripts (fetched by Clonezilla live)"
|
||||
# GetPxeScript.aspx: ocs_prerun downloads
|
||||
# http://<server>/clonezilla-scripts/pxe-cz.sh, ocs_live_run runs it.
|
||||
copy:
|
||||
src: "{{ usb_mount }}/clonezilla-scripts/"
|
||||
dest: "{{ web_root }}/clonezilla-scripts/"
|
||||
mode: '0644'
|
||||
directory_mode: '0755'
|
||||
|
||||
# Boot files (80) and the iPXE menu (4433) are served on the imaging IP
|
||||
# only, never on the GE NIC. 9009 (webapp) stays on all addresses: admins
|
||||
# reach it from the GE side (see pxe-server.conf below).
|
||||
@@ -1118,7 +1174,9 @@
|
||||
browseable = yes
|
||||
read only = no
|
||||
guest ok = no
|
||||
valid users = pxe-upload
|
||||
# clonezilla: the menu's back up / restore entries (this share
|
||||
# only); pxe-upload: admins and manual Clonezilla runs.
|
||||
valid users = pxe-upload clonezilla
|
||||
force user = root
|
||||
comment = Clonezilla backup images
|
||||
|
||||
@@ -1171,6 +1229,20 @@
|
||||
executable: /bin/bash
|
||||
changed_when: false
|
||||
|
||||
- name: "Create the clonezilla Samba user ([clonezilla] share only)"
|
||||
# System user, no home, no login shell. Only [clonezilla] lists it in
|
||||
# valid users. smbpasswd runs every time so a changed
|
||||
# clonezilla_smb_password takes effect (the menu is updated with it).
|
||||
shell: |
|
||||
id clonezilla >/dev/null 2>&1 || useradd -r -M -d /nonexistent -s /usr/sbin/nologin clonezilla
|
||||
printf '%s\n%s\n' "$CZ_PW" "$CZ_PW" | smbpasswd -a -s clonezilla >/dev/null
|
||||
environment:
|
||||
CZ_PW: "{{ clonezilla_smb_password }}"
|
||||
args:
|
||||
executable: /bin/bash
|
||||
changed_when: false
|
||||
no_log: true
|
||||
|
||||
- name: "Deploy nf_conntrack TCP timeout sysctl for PXE workload"
|
||||
copy:
|
||||
src: "{{ usb_mount }}/pxe-server-helpers/99-pxe-conntrack.conf"
|
||||
|
||||
@@ -35,3 +35,10 @@ corp_nic_mac: "" # e.g. "20:88:10:af:0d:e5"
|
||||
# Optional: kernel the Blancco UEFI entries boot (must be installed; see
|
||||
# scripts/download-packages.sh BLANCCO_KVER).
|
||||
# blancco_kver: "7.0.0-34-generic"
|
||||
|
||||
# Recommended: the password of the Samba account "clonezilla", which can
|
||||
# open only the [clonezilla] share (Clonezilla back up / restore by machine
|
||||
# number). The iPXE menu carries it, so treat it as an imaging-LAN secret,
|
||||
# not a real one. 8-64 characters from A-Z a-z 0-9 . _ ~ - (default
|
||||
# "clonezilla-backup").
|
||||
# clonezilla_smb_password: "change-me-per-site"
|
||||
|
||||
Reference in New Issue
Block a user