commit 04c5e6cf1263d456ab146f50c51c9bb45b9652c1 Author: Tero Date: Fri Sep 4 16:07:18 2026 +0300 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..db72198 --- /dev/null +++ b/README.md @@ -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. \ No newline at end of file diff --git a/erase-zfs-metadata.sh b/erase-zfs-metadata.sh new file mode 100644 index 0000000..5d3f9bb --- /dev/null +++ b/erase-zfs-metadata.sh @@ -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/ + +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" \ No newline at end of file