Files
k7/utils/wipe-disk.sh
T
G d65ea43536 Release 0.2.0
See CHANGELOG.md for what shipped.
2026-08-12 00:01:43 +02:00

166 lines
6.1 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# wipe-disk.sh — aggressively and safely wipe a single block device
# Usage: ./wipe-disk.sh /dev/sdX_or_nvmeXnY
#
# After you type YES, this will:
# - Unmount anything on the device/partitions and disable swap
# - Close dm-crypt mappings (best effort)
# - Stop mdadm arrays using its partitions and zero md superblocks
# - Deactivate LVM VGs using it, then wipe PV/FS signatures
# - Zap partition tables and signatures
# - Zero first/last regions and try blkdiscard (if supported)
die() { echo "Error: $*" >&2; exit 1; }
run() { echo "+ $*"; "$@"; }
[[ $# -ge 1 ]] || { echo "Usage: $0 /dev/sdX_or_nvmeXnY"; exit 1; }
DEVICE_RAW="$1"
DEVICE="$(readlink -f "$DEVICE_RAW" || echo "$DEVICE_RAW")"
[[ -b "$DEVICE" ]] || die "$DEVICE is not a block device"
# Accept only whole-disk nodes (not partitions)
# Use -d to show only the device itself (no children), -n to suppress header
dtype="$(lsblk -dn -o TYPE "$DEVICE" 2>/dev/null || echo "")"
case "$dtype" in
disk|mpath) : ;; # ok
*) die "You gave a partition node ($DEVICE). Please provide the WHOLE DISK (e.g., /dev/nvme0n1 or /dev/sda).";;
esac
echo "=============================="
echo " YOU ARE ABOUT TO WIPE: $DEVICE"
echo "=============================="
echo "This will destroy ALL partitions, RAID/LVM/crypto metadata, and filesystems."
confirm="${WIPE_CONFIRM:-}"
if [[ "$confirm" != "YES" ]]; then
read -r -p "Type YES to proceed: " confirm
fi
[[ "$confirm" == "YES" ]] || { echo "Aborted."; exit 0; }
# Helper: enumerate child partitions of the device
partitions() {
# Prints /dev/* entries for child partitions (if any)
lsblk -rno PATH "$DEVICE" | tail -n +2 || true
}
echo "[1/7] Unmounting and disabling swap on the target device..."
# Unmount anything mounted from the device or any of its partitions
while read -r src; do
[[ -z "$src" ]] && continue
# Unmount all mountpoints that have this source
while read -r mnt; do
[[ -n "$mnt" ]] && run umount -R "$mnt" || true
done < <(findmnt -rno TARGET -S "$src" 2>/dev/null || true)
done < <(printf "%s\n" "$DEVICE" "$(partitions)")
# Disable any swap that points to the device/partitions
if [[ -r /proc/swaps ]]; then
while read -r swdev _; do
for src in "$DEVICE" $(partitions); do
if [[ "$swdev" == "$src" ]]; then run swapoff "$swdev" || true; fi
done
done < <(tail -n +2 /proc/swaps | awk '{print $1" "$2}')
fi
echo "[2/7] Closing dm-crypt (LUKS) mappings on top of this device (best effort)..."
if command -v lsblk >/dev/null 2>&1; then
# Find any device-mapper names that depend on our base device or its parts
while read -r dmname dtype _pk; do
[[ "$dtype" != "crypt" ]] && continue
# If this crypt mapper ultimately uses our device or its partitions, close it
if lsblk -rno PKNAME "/dev/$dmname" 2>/dev/null | grep -Eq "$(basename "$DEVICE")(p[0-9]+)?"; then
if command -v cryptsetup >/dev/null 2>&1; then
run cryptsetup luksClose "$dmname" || true
fi
if command -v dmsetup >/dev/null 2>&1; then
run dmsetup remove "/dev/$dmname" || true
fi
fi
done < <(lsblk -rno NAME,TYPE,PKNAME | awk '{print $1" "$2" "$3}')
fi
echo "[3/7] Stopping mdraid arrays that include this disk..."
if command -v mdadm >/dev/null 2>&1; then
# Stop any /dev/md* that lists one of our partitions as a member
for md in /dev/md/* /dev/md*; do
[[ -e "$md" ]] || continue
if mdadm --detail "$md" >/dev/null 2>&1; then
if mdadm --detail "$md" 2>/dev/null | grep -qE "$(basename "$DEVICE")(p[0-9]+)?"; then
run mdadm --stop "$md" || true
fi
fi
done
fi
echo "[4/7] Deactivating LVM that sits on this disk (if any)..."
if command -v pvs >/dev/null 2>&1 && command -v vgchange >/dev/null 2>&1; then
# Find VGs that have PVs on our device or its partitions and deactivate them
while read -r vg pv; do
for src in "$DEVICE" $(partitions); do
if [[ "$pv" == "$src" ]]; then
run vgchange -an "$vg" || true
fi
done
done < <(pvs --noheadings -o vg_name,pv_name 2>/dev/null | awk '{$1=$1;print}')
fi
echo "[5/7] Clearing md superblocks and LVM/FS signatures on partitions..."
for p in $(partitions); do
if command -v mdadm >/dev/null 2>&1; then
run mdadm --zero-superblock "$p" || true
fi
if command -v wipefs >/dev/null 2>&1; then
run wipefs -fa "$p" || true
fi
done
# Let udev settle and drop partition caches before touching the whole disk
command -v udevadm >/dev/null 2>&1 && run udevadm settle || true
run blockdev --rereadpt "$DEVICE" || true
echo "[6/7] Zapping partition tables and signatures on the whole disk..."
if command -v wipefs >/dev/null 2>&1; then
run wipefs -fa "$DEVICE" || true
fi
if command -v sgdisk >/dev/null 2>&1; then
run sgdisk --zap-all "$DEVICE" || true
else
echo "sgdisk not found; zeroing first/last 1MiB as a fallback..."
run dd if=/dev/zero of="$DEVICE" bs=1M count=1 conv=fsync || true
if command -v blockdev >/dev/null 2>&1; then
SECTORS=$(blockdev --getsz "$DEVICE")
SEEK=$(( SECTORS / 2048 - 1 )) # 2048 sectors ≈ 1 MiB at 512-byte sectors
(( SEEK > 0 )) && run dd if=/dev/zero of="$DEVICE" bs=1M seek="$SEEK" count=1 conv=fsync || true
fi
fi
echo "[7/7] Overwriting start/end of the disk and attempting secure discard..."
# Zero first 100MiB to clear lingering metadata quickly
run dd if=/dev/zero of="$DEVICE" bs=1M count=100 status=progress conv=fsync || true
# Zero last ~100MiB
if command -v blockdev >/dev/null 2>&1; then
SECTORS=$(blockdev --getsz "$DEVICE")
SEEK_VAL=$(( SECTORS / 2048 - 100 ))
if (( SEEK_VAL > 0 )); then
run dd if=/dev/zero of="$DEVICE" bs=1M seek="$SEEK_VAL" count=100 status=progress conv=fsync || true
fi
fi
# Discard entire device if supported (SSD/NVMe)
if command -v blkdiscard >/dev/null 2>&1; then
run blkdiscard "$DEVICE" || echo "(blkdiscard not supported or failed; non-fatal)"
fi
# Final reread to clear any lingering kernel views
run blockdev --rereadpt "$DEVICE" || true
command -v partprobe >/dev/null 2>&1 && run partprobe "$DEVICE" || true
echo
echo "DONE: $DEVICE should now be blank. Current view:"
lsblk -o NAME,TYPE,SIZE,MOUNTPOINTS "$DEVICE" || true