Add ability to test pool recovery
This commit is contained in:
@@ -95,4 +95,42 @@ sudo zdb -l /dev/sdX
|
|||||||
```
|
```
|
||||||
|
|
||||||
`wipefs --no-act` reports signatures without modifying the device. `zdb -l`
|
`wipefs --no-act` reports signatures without modifying the device. `zdb -l`
|
||||||
should not find a readable ZFS label after a successful run.
|
should not find a readable ZFS label after a successful run.
|
||||||
|
|
||||||
|
## Recovery Assessment
|
||||||
|
|
||||||
|
`verify-zpool-recovery.sh` checks whether ZFS can find and rewind an exported
|
||||||
|
or faulted pool. It cannot restore labels that have been completely overwritten
|
||||||
|
by the eraser, so recovery after a successful erase is unlikely. Use it before
|
||||||
|
erasing a device, or when ZFS still detects at least part of the pool metadata.
|
||||||
|
|
||||||
|
List pools ZFS can discover without modifying anything:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./verify-zpool-recovery.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Check whether a specific pool can be recovered with a transaction rewind. This
|
||||||
|
uses `zpool import -nF`, which is a dry run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./verify-zpool-recovery.sh tank
|
||||||
|
```
|
||||||
|
|
||||||
|
If vdevs must be found in a non-default directory, supply it explicitly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./verify-zpool-recovery.sh --directory /dev/disk/by-id tank
|
||||||
|
```
|
||||||
|
|
||||||
|
After reviewing the dry-run output, attempt the import with rewind recovery:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./verify-zpool-recovery.sh --apply tank
|
||||||
|
```
|
||||||
|
|
||||||
|
`--apply` runs `zpool import -N -F`: `-F` may discard the most recent
|
||||||
|
transaction groups, and `-N` imports the pool without mounting datasets. The
|
||||||
|
script requires the exact confirmation `RECOVER tank` before issuing that
|
||||||
|
command. Inspect the result with `zpool status tank` before mounting datasets
|
||||||
|
or performing any further changes.
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Assess whether ZFS can rewind and import an exported or faulted pool.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Usage: sudo ./verify-zpool-recovery.sh [--directory <path>] [--apply] [pool]
|
||||||
|
|
||||||
|
Lists pools ZFS can discover, or checks whether a named pool can be recovered
|
||||||
|
with a transaction rewind. The default is a dry run and changes nothing.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-d, --directory <path> Search a specific directory for vdev devices.
|
||||||
|
--apply Import the named pool with rewind recovery.
|
||||||
|
-h, --help Show this help message.
|
||||||
|
|
||||||
|
Without a pool name, the script only lists importable pools. --apply requires
|
||||||
|
a pool name and imports it with datasets left unmounted.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
printf 'Error: %s\n' "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
search_directory=
|
||||||
|
apply=false
|
||||||
|
pool=
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-d|--directory)
|
||||||
|
[[ $# -ge 2 ]] || die "$1 requires a path."
|
||||||
|
search_directory=$2
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--apply)
|
||||||
|
apply=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
die "unknown option '$1'."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
[[ -z $pool ]] || die 'specify at most one pool name or GUID.'
|
||||||
|
pool=$1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ $EUID -eq 0 ]] || die 'run as root (for example, with sudo).'
|
||||||
|
command -v zpool >/dev/null 2>&1 || die "required command 'zpool' was not found."
|
||||||
|
|
||||||
|
directory_args=()
|
||||||
|
if [[ -n $search_directory ]]; then
|
||||||
|
[[ -d $search_directory ]] || die "'$search_directory' is not a directory."
|
||||||
|
directory_args=(-d "$search_directory")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z $pool ]]; then
|
||||||
|
[[ $apply == false ]] || die '--apply requires a pool name or GUID.'
|
||||||
|
printf 'Discovering importable pools. This does not modify any pool.\n\n'
|
||||||
|
zpool import "${directory_args[@]}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if zpool list -H -o name "$pool" >/dev/null 2>&1; then
|
||||||
|
die "'$pool' is already imported; inspect it with 'zpool status $pool'."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $apply == false ]]; then
|
||||||
|
printf 'Dry-run rewind recovery check for pool %s. No changes will be made.\n\n' "$pool"
|
||||||
|
zpool import -nF "${directory_args[@]}" "$pool"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '\nWARNING: this will attempt to rewind and import pool:\n %s\n\n' "$pool"
|
||||||
|
printf 'This can discard the most recent transaction groups. Datasets will remain unmounted.\n'
|
||||||
|
read -r -p "Type exactly 'RECOVER $pool' to continue: " confirmation
|
||||||
|
[[ $confirmation == "RECOVER $pool" ]] || die 'confirmation did not match; no changes were made.'
|
||||||
|
|
||||||
|
zpool import -N -F "${directory_args[@]}" "$pool"
|
||||||
|
printf "Pool '%s' was imported without mounting datasets. Inspect it with:\n zpool status %s\n" "$pool" "$pool"
|
||||||
Reference in New Issue
Block a user