first commit
This commit is contained in:
@@ -0,0 +1,86 @@
|
|||||||
|
# ZFS Metadata Eraser
|
||||||
|
|
||||||
|
`erase-zfs-metadata.sh` permanently overwrites disk metadata that can identify
|
||||||
|
a device as a ZFS vdev. It is intended for decommissioning or repurposing a
|
||||||
|
device after it has been cleanly removed from its pool.
|
||||||
|
|
||||||
|
## What It Erases
|
||||||
|
|
||||||
|
The script performs 10 overwrite passes with zeroes over the first 4 MiB and
|
||||||
|
the last 4 MiB of the target block device. These regions include:
|
||||||
|
|
||||||
|
- MBR data and GPT primary partition table at the beginning of a disk.
|
||||||
|
- GPT backup partition table at the end of a disk.
|
||||||
|
- The four conventional ZFS vdev-label regions, including their redundant
|
||||||
|
copies at both ends of the device.
|
||||||
|
|
||||||
|
It does not overwrite the entire device. Data outside those metadata regions
|
||||||
|
may remain recoverable.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Linux with Bash.
|
||||||
|
- Root privileges.
|
||||||
|
- `blockdev`, `dd`, `findmnt`, `lsblk`, and the ZFS `zpool` command.
|
||||||
|
- A target device of at least 8 MiB that is not mounted and is not part of an
|
||||||
|
imported ZFS pool.
|
||||||
|
|
||||||
|
## Use
|
||||||
|
|
||||||
|
First, verify the target device carefully:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lsblk --output NAME,SIZE,TYPE,MOUNTPOINTS
|
||||||
|
zpool status -P
|
||||||
|
```
|
||||||
|
|
||||||
|
Then make the script executable and run it with the full block-device path:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x erase-zfs-metadata.sh
|
||||||
|
sudo ./erase-zfs-metadata.sh /dev/sdX
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `/dev/sdX` with the actual device, such as `/dev/sdb`,
|
||||||
|
`/dev/nvme0n1`, or a device-mapper path. Do not use the disk containing the
|
||||||
|
running system.
|
||||||
|
|
||||||
|
The script displays target details and requires this exact confirmation before
|
||||||
|
it writes anything:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ERASE /dev/sdX
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safety Behavior
|
||||||
|
|
||||||
|
Before overwriting, the script refuses to run when:
|
||||||
|
|
||||||
|
- The supplied path is not a block device.
|
||||||
|
- The device itself is mounted.
|
||||||
|
- A child partition of the device is mounted.
|
||||||
|
- `zpool status -P` shows the device in an imported pool.
|
||||||
|
- The device is smaller than 8 MiB.
|
||||||
|
|
||||||
|
These checks reduce common mistakes but are not a substitute for verifying the
|
||||||
|
device identity yourself. The operation is destructive and cannot be undone.
|
||||||
|
|
||||||
|
## Prepare a ZFS Device
|
||||||
|
|
||||||
|
For a device that belongs to a pool, first use the appropriate ZFS procedure,
|
||||||
|
such as replacing or detaching the vdev, then export the pool if necessary.
|
||||||
|
Confirm the target no longer appears in `zpool status -P` before running the
|
||||||
|
eraser. Do not use this utility to remove a live vdev from an active pool.
|
||||||
|
|
||||||
|
## Verify
|
||||||
|
|
||||||
|
After completion, inspect the device for residual filesystem or partition
|
||||||
|
signatures:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo wipefs --all --no-act /dev/sdX
|
||||||
|
sudo zdb -l /dev/sdX
|
||||||
|
```
|
||||||
|
|
||||||
|
`wipefs --no-act` reports signatures without modifying the device. `zdb -l`
|
||||||
|
should not find a readable ZFS label after a successful run.
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Overwrite partition-table and ZFS vdev-label metadata at both ends of a device.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
readonly PASSES=10
|
||||||
|
readonly MIB=$((1024 * 1024))
|
||||||
|
readonly WIPE_MIB=4
|
||||||
|
readonly WIPE_BYTES=$((WIPE_MIB * MIB))
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage: sudo ./erase-zfs-metadata.sh /dev/<device>
|
||||||
|
|
||||||
|
Overwrites the first and last 4 MiB of the specified block device ten times.
|
||||||
|
This destroys MBR/GPT partition tables and all conventional ZFS vdev labels.
|
||||||
|
The device must not be mounted or part of an imported ZFS pool.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
printf 'Error: %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ $# -eq 1 ]] || { usage >&2; exit 2; }
|
||||||
|
device=$1
|
||||||
|
|
||||||
|
[[ $EUID -eq 0 ]] || die 'run as root (for example, with sudo).'
|
||||||
|
[[ -b $device ]] || die "'$device' is not a block device."
|
||||||
|
|
||||||
|
for command in blockdev dd findmnt lsblk zpool; do
|
||||||
|
command -v "$command" >/dev/null 2>&1 || die "required command '$command' was not found."
|
||||||
|
done
|
||||||
|
|
||||||
|
if findmnt --noheadings --source "$device" >/dev/null 2>&1; then
|
||||||
|
die "'$device' is mounted; unmount it before continuing."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if lsblk --noheadings --raw --output MOUNTPOINT "$device" | grep -q '[^[:space:]]'; then
|
||||||
|
die "'$device' or one of its children is mounted; unmount it before continuing."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if zpool status -P 2>/dev/null | grep -Fq -- "$device"; then
|
||||||
|
die "'$device' belongs to an imported ZFS pool; export or detach it before continuing."
|
||||||
|
fi
|
||||||
|
|
||||||
|
size_bytes=$(blockdev --getsize64 "$device")
|
||||||
|
(( size_bytes >= 2 * WIPE_BYTES )) || die "'$device' is smaller than $((2 * WIPE_MIB)) MiB."
|
||||||
|
|
||||||
|
last_offset=$((size_bytes - WIPE_BYTES))
|
||||||
|
|
||||||
|
printf '\nWARNING: this permanently destroys partition tables and ZFS metadata on:\n %s\n\n' "$device"
|
||||||
|
lsblk --output NAME,SIZE,TYPE,MOUNTPOINTS "$device"
|
||||||
|
read -r -p "Type exactly 'ERASE $device' to continue: " confirmation
|
||||||
|
[[ $confirmation == "ERASE $device" ]] || die 'confirmation did not match; no data was changed.'
|
||||||
|
|
||||||
|
for ((pass = 1; pass <= PASSES; pass++)); do
|
||||||
|
printf 'Pass %d/%d...\n' "$pass" "$PASSES"
|
||||||
|
dd if=/dev/zero of="$device" bs="$MIB" count="$WIPE_MIB" conv=fsync,notrunc status=none
|
||||||
|
dd if=/dev/zero of="$device" bs=1 count="$WIPE_BYTES" seek="$last_offset" conv=fsync,notrunc status=none
|
||||||
|
done
|
||||||
|
|
||||||
|
sync
|
||||||
|
printf 'Completed %d overwrite passes on %s.\n' "$PASSES" "$device"
|
||||||
Reference in New Issue
Block a user