Add initial scripts and project conventions

This commit is contained in:
dido committed 2026-09-27 12:09:46 +03:00
1 parent af9f32facc
commit c777cf115b
14 files changed
+982 -9

No files matched your search

+58
View File
@@ -0,0 +1,58 @@
# erase-storage.sh
Изтрива един дял с еднократен запис на нули или псевдослучайни данни, или
използва поддържания от устройството ATA Secure Erase/NVMe Sanitize за цял диск.
## Какво прави
При подаване на дял, скриптът записва точно размера на целия дял един път. По подразбиране
използва нули; може да се избере и `/dev/urandom`. Изисква root права,
проверява че устройството не е монтирано, не е активен swap и не участва в
root filesystem, след което иска потвърждение чрез въвеждане на точния път.
При подаване на цял диск скриптът проверява поддръжката преди да продължи.
За NVMe избира наличния Sanitize метод в следния ред: Crypto Erase, Block
Erase, Overwrite. За ATA избира Enhanced Security Erase, ако устройството го
обявява, иначе стандартен Security Erase. Ако методът не може да бъде
потвърден, устройството е замразено/вече е с активирана ATA защита или
информацията за capability е неясна, скриптът отказва да продължи. Преди
изтриването на цял диск показва отделно предупреждение и изисква две
потвърждения, като второто е точното име на устройството.
**Изтриването е необратимо и при режим за цял диск засяга всички дялове.**
Не прекъсвай захранването или връзката с диска. При прекъсване на ATA
Security Erase устройството може да остане заключено с временната парола,
посочена в съобщението за грешка. Secure Erase/Sanitize зависи от коректната
реализация във firmware на устройството; скриптът не може да гарантира
възстановимост или физическо състояние на носителя.
## Изисквания
- Linux, Bash и root права (`sudo`)
- `lsblk`, `findmnt`, `swapon`, `dd` и GNU/Linux стандартни помощни команди
- За NVMe: `nvme-cli` и `jq`
- За ATA: `hdparm`
## Употреба
```bash
sudo ./HDD/erase-storage.sh --help
sudo ./HDD/erase-storage.sh /dev/sda1
sudo ./HDD/erase-storage.sh /dev/nvme0n1p2 random
sudo ./HDD/erase-storage.sh /dev/sda
sudo ./HDD/erase-storage.sh /dev/nvme0n1
```
`dev/sda1` без начална наклонена черта също се приема като `/dev/sda1`.
| Аргумент | Описание |
|----------|----------|
| `DEVICE` | Block device — дял или цял диск |
| `zero` | Един проход с нули върху дял; режим по подразбиране |
| `random` | Един проход с псевдослучайни данни върху дял |
| `-h`, `--help` | Показва помощта и приключва |
Режимът `zero`/`random` не е Secure Erase и не гарантира изтриване на
пренасочени или недостъпни физически сектори. При SSD не използвай обикновен
запис към дял като гаранция за заличаване; използвай само успешно завършен
Sanitize/Secure Erase, поддържан от устройството.
+240
View File
@@ -0,0 +1,240 @@
#!/usr/bin/env bash
#
# erase-storage.sh — overwrite a partition or securely erase a whole disk.
# Full documentation: see erase-storage.md in this same folder.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/common.sh
source "${SCRIPT_DIR}/../lib/common.sh"
SCRIPT_HELP=$(cat <<'HELP'
Usage: sudo ./HDD/erase-storage.sh DEVICE [zero|random]
For a partition, writes one full pass of zeros (default) or pseudorandom data.
For a whole disk, detects and runs a supported ATA Secure Erase or NVMe Sanitize
operation. Whole-disk erasure requires two separate confirmations.
Arguments:
DEVICE Partition (for example /dev/sda1) or whole disk
zero Write zeros to a partition (default)
random Write pseudorandom data to a partition
Options:
-h, --help Show this help message and exit
Examples:
sudo ./HDD/erase-storage.sh /dev/sda1
sudo ./HDD/erase-storage.sh /dev/nvme0n1p2 random
sudo ./HDD/erase-storage.sh /dev/sda
sudo ./HDD/erase-storage.sh /dev/nvme0n1
HELP
)
common::show_help_if_requested "$@"
[[ $# -ge 1 && $# -le 2 ]] || common::die "expected DEVICE [zero|random]; run with --help for usage"
DEVICE_INPUT="$1"
if [[ "$DEVICE_INPUT" == dev/* ]]; then
DEVICE_INPUT="/$DEVICE_INPUT"
fi
[[ "$DEVICE_INPUT" == /dev/* ]] || common::die "DEVICE must be a path under /dev"
FILL_MODE="${2:-zero}"
case "$FILL_MODE" in
zero|random)
;;
*)
common::die "partition fill mode must be 'zero' or 'random'"
;;
esac
for required_command in findmnt lsblk readlink swapon date dd sleep; do
common::require_cmd "$required_command"
done
[[ "$EUID" -eq 0 ]] || common::die "this script must be run as root (for example, with sudo)"
[[ -b "$DEVICE_INPUT" ]] || common::die "$DEVICE_INPUT does not exist or is not a block device"
DEVICE="$(readlink -f -- "$DEVICE_INPUT")" || common::die "cannot resolve device path: $DEVICE_INPUT"
DEVICE_TYPE="$(lsblk -dnro TYPE -- "$DEVICE")" || common::die "cannot determine device type"
[[ "$DEVICE_TYPE" == "part" || "$DEVICE_TYPE" == "disk" ]] \
|| common::die "only a partition or a whole disk is accepted (detected: $DEVICE_TYPE)"
ROOT_SOURCE="$(findmnt -n -o SOURCE /)" || common::die "cannot determine the root filesystem device"
ROOT_SOURCE="${ROOT_SOURCE%%\[*}"
[[ -b "$ROOT_SOURCE" ]] || common::die "cannot safely resolve the root filesystem device ($ROOT_SOURCE)"
ROOT_DEVICE="$(readlink -f -- "$ROOT_SOURCE")" || common::die "cannot resolve the root filesystem device"
ROOT_TREE="$(lsblk --inverse --list --noheadings --paths --output PATH -- "$ROOT_DEVICE")" \
|| common::die "cannot determine the devices backing the root filesystem"
while IFS= read -r root_device; do
[[ -n "$root_device" ]] || continue
if [[ "$(readlink -f -- "$root_device")" == "$DEVICE" ]]; then
common::die "$DEVICE backs the root filesystem; refusing to erase it"
fi
done <<< "$ROOT_TREE"
DEVICE_TREE="$(lsblk --list --noheadings --paths --output PATH -- "$DEVICE")" \
|| common::die "cannot inspect devices below $DEVICE"
ACTIVE_SWAP_DEVICES="$(swapon --show --noheadings --raw --output NAME)" \
|| common::die "cannot inspect active swap devices"
while IFS= read -r child_device; do
[[ -n "$child_device" ]] || continue
if findmnt -rn -S "$child_device" >/dev/null 2>&1; then
common::die "$child_device is mounted; unmount it before running this script"
fi
while IFS= read -r swap_device; do
[[ -n "$swap_device" ]] || continue
if [[ -b "$swap_device" ]]; then
swap_source="$swap_device"
else
swap_mount_source="$(findmnt -n -o SOURCE -T "$swap_device")" \
|| common::die "cannot determine the filesystem backing active swap file $swap_device"
swap_mount_source="${swap_mount_source%%\[*}"
[[ -b "$swap_mount_source" ]] \
|| common::die "cannot safely resolve the block device backing active swap file $swap_device"
swap_source="$swap_mount_source"
fi
swap_source_tree="$(lsblk --inverse --list --noheadings --paths --output PATH -- "$swap_source")" \
|| common::die "cannot determine the devices backing active swap $swap_device"
while IFS= read -r swap_backing_device; do
[[ -n "$swap_backing_device" ]] || continue
if [[ "$(readlink -f -- "$swap_backing_device")" == "$(readlink -f -- "$child_device")" ]]; then
common::die "$child_device backs active swap ($swap_device); disable it before running this script"
fi
done <<< "$swap_source_tree"
done <<< "$ACTIVE_SWAP_DEVICES"
done <<< "$DEVICE_TREE"
if [[ "$DEVICE_TYPE" == "part" ]]; then
[[ $# -eq 1 || "$FILL_MODE" == "zero" || "$FILL_MODE" == "random" ]] \
|| common::die "invalid fill mode; use 'zero' or 'random'"
DEVICE_SIZE="$(lsblk -dnro SIZE --bytes -- "$DEVICE")" || common::die "cannot determine partition size"
common::log "WARNING: this will irreversibly overwrite all data on partition $DEVICE ($DEVICE_SIZE bytes)."
read -r -p "Type the exact device path '$DEVICE' to continue: " CONFIRM
[[ "$CONFIRM" == "$DEVICE" ]] || common::die "operation cancelled; no data was changed"
common::log "Writing one full $FILL_MODE pass to $DEVICE."
if [[ "$FILL_MODE" == "zero" ]]; then
dd if=/dev/zero of="$DEVICE" bs=4M count="$DEVICE_SIZE" iflag=count_bytes conv=fsync status=progress
else
dd if=/dev/urandom of="$DEVICE" bs=4M count="$DEVICE_SIZE" iflag=count_bytes conv=fsync status=progress
fi
common::log "Partition overwrite completed successfully."
exit 0
fi
[[ $# -eq 1 ]] || common::die "a fill mode is only valid for a partition"
DEVICE_SIZE="$(lsblk -dnro SIZE --bytes -- "$DEVICE")" || common::die "cannot determine disk size"
DEVICE_NAME="${DEVICE##*/}"
ERASE_METHOD=""
ERASE_TOOL=""
NVME_ACTION=""
NVME_ACTION_NAME=""
ATA_ERASE_OPTION=""
if [[ "$DEVICE_NAME" =~ ^(nvme[0-9]+)n[0-9]+$ ]]; then
common::require_cmd nvme
common::require_cmd jq
NVME_CONTROLLER="/dev/${BASH_REMATCH[1]}"
NVME_IDENTIFY="$(nvme id-ctrl "$NVME_CONTROLLER" -o json)" \
|| common::die "could not read NVMe controller capabilities"
SANICAP="$(jq -er '.sanicap' <<< "$NVME_IDENTIFY")" \
|| common::die "NVMe controller did not report SANICAP"
[[ "$SANICAP" =~ ^[0-9]+$ ]] || common::die "invalid NVMe SANICAP value: $SANICAP"
SANICAP_VALUE=$((SANICAP))
if (( SANICAP_VALUE & 1 )); then
NVME_ACTION=4
NVME_ACTION_NAME="Crypto Erase"
elif (( SANICAP_VALUE & 2 )); then
NVME_ACTION=2
NVME_ACTION_NAME="Block Erase"
elif (( SANICAP_VALUE & 4 )); then
NVME_ACTION=3
NVME_ACTION_NAME="Overwrite"
else
common::die "NVMe controller does not advertise a supported Sanitize action"
fi
ERASE_METHOD="NVMe Sanitize: $NVME_ACTION_NAME"
ERASE_TOOL="nvme"
else
common::require_cmd hdparm
common::require_cmd awk
common::require_cmd grep
ATA_IDENTIFY="$(hdparm -I "$DEVICE" 2>&1)" || common::die "could not read ATA security capabilities"
ATA_SECURITY="$(awk '
/^[[:space:]]*Security:/ { in_security = 1; print; next }
in_security && /^[^[:space:]]/ { exit }
in_security { print }
' <<< "$ATA_IDENTIFY")"
[[ -n "$ATA_SECURITY" ]] || common::die "device did not report an ATA Security section"
grep -Eiq '^[[:space:]]+supported[[:space:]]*$' <<< "$ATA_SECURITY" \
|| common::die "device does not report ATA Security Erase support"
grep -Eiq '^[[:space:]]+not frozen[[:space:]]*$' <<< "$ATA_SECURITY" \
|| common::die "ATA security is frozen or its state could not be determined; refusing to continue"
grep -Eiq '^[[:space:]]+not enabled[[:space:]]*$' <<< "$ATA_SECURITY" \
|| common::die "ATA security is already enabled; refusing to risk leaving the disk locked"
if grep -Eiq 'supported:[[:space:]]+enhanced erase' <<< "$ATA_SECURITY"; then
ATA_ERASE_OPTION="--security-erase-enhanced"
ERASE_METHOD="ATA Enhanced Security Erase"
else
ATA_ERASE_OPTION="--security-erase"
ERASE_METHOD="ATA Security Erase"
fi
ERASE_TOOL="hdparm"
fi
common::log "WARNING: whole-disk operation on $DEVICE ($DEVICE_SIZE bytes)."
common::log "Detected supported method: $ERASE_METHOD. ALL DATA on this disk will be destroyed."
read -r -p "First confirmation: type ERASE to continue: " FIRST_CONFIRM
[[ "$FIRST_CONFIRM" == "ERASE" ]] || common::die "operation cancelled; no data was changed"
common::log "Final warning: $ERASE_METHOD will erase the entire disk $DEVICE, including every partition."
read -r -p "Final confirmation: type the exact device name '$DEVICE_NAME': " FINAL_CONFIRM
[[ "$FINAL_CONFIRM" == "$DEVICE_NAME" ]] || common::die "operation cancelled; no data was changed"
if [[ "$ERASE_TOOL" == "nvme" ]]; then
common::log "Starting $ERASE_METHOD on $DEVICE. The device may continue sanitizing after the command returns."
nvme sanitize "$DEVICE" --sanact="$NVME_ACTION"
while true; do
SANITIZE_LOG="$(nvme sanitize-log "$DEVICE" -o json)" \
|| common::die "could not read NVMe Sanitize status; the operation may still be running"
SANITIZE_STATUS="$(jq -er '.sstat' <<< "$SANITIZE_LOG")" \
|| common::die "NVMe Sanitize status was missing or invalid"
[[ "$SANITIZE_STATUS" =~ ^[0-9]+$ ]] || common::die "invalid NVMe Sanitize status: $SANITIZE_STATUS"
case $((SANITIZE_STATUS & 7)) in
1|4)
common::log "NVMe Sanitize completed successfully."
break
;;
2)
common::log "NVMe Sanitize is still in progress; checking again in 30 seconds."
sleep 30
;;
3)
common::die "NVMe Sanitize reported failure; consult the device sanitize log"
;;
*)
common::die "NVMe Sanitize is not running or reported an unknown status ($SANITIZE_STATUS)"
;;
esac
done
else
common::log "Starting $ERASE_METHOD on $DEVICE. Do not interrupt power or disconnect the drive."
ATA_PASSWORD="strix-erase"
hdparm --user-master u --security-set-pass "$ATA_PASSWORD" "$DEVICE"
if ! hdparm --user-master u "$ATA_ERASE_OPTION" "$ATA_PASSWORD" "$DEVICE"; then
common::die "ATA erase failed; the disk may remain security-locked with the temporary password '$ATA_PASSWORD'"
fi
common::log "$ERASE_METHOD completed successfully."
fi
+53
View File
@@ -0,0 +1,53 @@
# repair-hdd.sh
Презаписва избран диск, за да задейства remap на проблемни сектори, след
което изпълнява SMART extended self-test и показва финален отчет.
## Какво прави
Скриптът записва целия block device с нули или псевдослучайни данни. След
това стартира продължителен SMART self-test и изчаква очакваното му време,
преди да покаже резултата и SMART атрибутите. Изходът се записва в
`/var/log/hdd-repair/`.
**Операцията унищожава всички данни на избрания диск.** Преди запис скриптът
проверява дали дискът участва в root filesystem, проверява за монтирани
дъщерни устройства и активен swap, и изисква изрично потвърждение `YES`.
Ако дъщерно устройство не може да се демонтира, скриптът прекратява
операцията.
Използвай само с правилно идентифициран диск, който можеш безопасно да
изтриеш. Изисква пряка SATA връзка; `smartctl` не използва `-d sat`, затова
USB адаптер може да изисква ръчна промяна на извикванията в скрипта.
## Изисквания
- Linux и Bash
- Root права (`sudo`)
- `smartctl` от пакета smartmontools
- `findmnt`, `lsblk`, `readlink`, `swapon`, `umount` и стандартните GNU/Linux
помощни команди, използвани от скрипта
- OpenSSL е незадължителен; при липса режимът `random` използва `/dev/urandom`
## Употреба
```bash
sudo ./HDD/repair-hdd.sh --help
sudo ./HDD/repair-hdd.sh /dev/sdX
sudo ./HDD/repair-hdd.sh /dev/sdX zero
sudo ./HDD/repair-hdd.sh /dev/sdX null
sudo ./HDD/repair-hdd.sh /dev/sdX random
```
| Аргумент | Описание |
|----------|----------|
| `DEVICE` | Целият диск, например `/dev/sdX`, не дял от него |
| `zero` | Записва нули; режим по подразбиране |
| `null` | Синоним на `zero` |
| `random` | Записва псевдослучайни данни |
| `-h`, `--help` | Показва помощта и приключва |
Изпълнението може да продължи часове. Не прекъсвай захранването или връзката
с диска по време на записа или SMART теста. SMART remap не възстановява
данни и не гарантира, че дискът ще стане надежден; запази резервно копие и
подмени диск с повтарящи се грешки.
+217
View File
@@ -0,0 +1,217 @@
#!/usr/bin/env bash
#
# repair-hdd.sh — overwrite a disk to trigger remapping of pending/bad sectors,
# then run a SMART extended self-test and print a final report.
# Full documentation: see repair-hdd.md in this same folder.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/common.sh
source "${SCRIPT_DIR}/../lib/common.sh"
SCRIPT_HELP=$(cat <<'HELP'
Usage: sudo ./HDD/repair-hdd.sh DEVICE [zero|null|random]
Overwrites the selected disk, then runs a SMART extended self-test. This
destroys all data on DEVICE. Use only with a directly connected SATA disk;
USB adapters may require adding -d sat to the smartctl calls.
Arguments:
DEVICE Whole-disk block device, for example /dev/sdX
zero Write zeros (default; also accepted as "null")
random Write pseudorandom data
Options:
-h, --help Show this help message and exit
Examples:
sudo ./HDD/repair-hdd.sh /dev/sdX
sudo ./HDD/repair-hdd.sh /dev/sdX random
HELP
)
common::show_help_if_requested "$@"
[[ $# -le 2 ]] || common::die "too many arguments; run with --help for usage"
LOG_DIR="/var/log/hdd-repair"
DEVICE="${1:-}"
FILL_MODE="${2:-zero}"
# --- Валидация на входа ---------------------------------------------------
if [[ -z "$DEVICE" ]]; then
common::die "a whole-disk block device is required; run with --help for usage"
fi
case "$FILL_MODE" in
zero|null)
FILL_MODE="zero"
;;
random)
;;
*)
common::die "fill mode must be 'zero', 'null', or 'random'"
;;
esac
for required_command in findmnt lsblk readlink swapon mkdir date tee smartctl grep umount dd sync sleep head base64; do
common::require_cmd "$required_command"
done
if [[ "$EUID" -ne 0 ]]; then
common::die "this script must be run as root (for example, with sudo)"
fi
if [[ ! -b "$DEVICE" ]]; then
common::die "$DEVICE does not exist or is not a block device"
fi
# Refuse the root filesystem device and any block device it depends on.
TARGET_DEVICE="$(readlink -f -- "$DEVICE")" || common::die "cannot resolve device path: $DEVICE"
ROOT_SOURCE="$(findmnt -n -o SOURCE /)" || common::die "cannot determine the root filesystem device"
ROOT_SOURCE="${ROOT_SOURCE%%\[*}"
ROOT_DEVICE="$(readlink -f -- "$ROOT_SOURCE")" || common::die "cannot resolve root device path: $ROOT_SOURCE"
ROOT_DEVICE_TREE="$(lsblk --inverse --list --noheadings --paths --output PATH "$ROOT_DEVICE")" \
|| common::die "cannot determine the devices backing the root filesystem"
while IFS= read -r root_device; do
[[ -n "$root_device" ]] || continue
if [[ "$(readlink -f -- "$root_device")" == "$TARGET_DEVICE" ]]; then
common::die "$DEVICE backs the root filesystem; refusing to overwrite it"
fi
done <<< "$ROOT_DEVICE_TREE"
DEVICE_TREE="$(lsblk --list --noheadings --paths --output PATH "$TARGET_DEVICE")" \
|| common::die "cannot inspect block devices below $DEVICE"
while IFS= read -r child_device; do
[[ -n "$child_device" ]] || continue
while IFS= read -r swap_device; do
[[ -n "$swap_device" ]] || continue
if [[ "$(readlink -f -- "$swap_device")" == "$(readlink -f -- "$child_device")" ]]; then
common::die "$child_device is active swap; disable it before running this script"
fi
done < <(swapon --show --noheadings --raw --output NAME)
done <<< "$DEVICE_TREE"
if [[ "$FILL_MODE" == "random" ]] && ! command -v openssl >/dev/null 2>&1; then
common::log "OpenSSL is unavailable; random mode will use /dev/urandom."
fi
mkdir -p "$LOG_DIR"
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
MAIN_LOG="$LOG_DIR/hdd-repair-${DEVICE##*/}-${TIMESTAMP}.log"
# Send all output to both the console and the log file.
exec > >(tee -a "$MAIN_LOG") 2>&1
common::log "=============================================================="
common::log "HDD Repair — $DEVICE"
common::log "Write mode: $FILL_MODE"
common::log "Started: $(date '+%Y-%m-%d %H:%M:%S')"
common::log "Log: $MAIN_LOG"
common::log "=============================================================="
# --- Disk information before the operation --------------------------------
common::log "--- Device identification ---"
smartctl -i "$DEVICE" || true
common::log "--- SMART values before overwrite ---"
smartctl -A "$DEVICE" | grep -E \
"Reallocated_Sector_Ct|Current_Pending_Sector|Offline_Uncorrectable|UDMA_CRC_Error_Count|Power_On_Hours" \
|| common::log "SMART read failed; check the connection."
common::log "WARNING: ALL DATA ON $DEVICE WILL BE DESTROYED."
read -r -p "Type uppercase YES to confirm: " CONFIRM
if [[ "$CONFIRM" != "YES" ]]; then
common::die "operation cancelled; no changes were made"
fi
while IFS= read -r child_device; do
[[ -n "$child_device" ]] || continue
if findmnt -n -S "$child_device" >/dev/null 2>&1; then
common::log "Unmounting $child_device..."
umount "$child_device" || common::die "could not unmount $child_device; refusing to overwrite $DEVICE"
fi
done <<< "$DEVICE_TREE"
# --- Phase 1: overwrite the disk to trigger sector remapping ---------------
common::log "--- Phase 1: overwriting $DEVICE ---"
common::log "Started: $(date '+%Y-%m-%d %H:%M:%S')"
common::log "This may take hours, depending on disk size and speed."
DD_START=$(date +%s)
DD_RESULT=0
if [[ "$FILL_MODE" == "zero" ]]; then
RANDOM_SOURCE_DESC="/dev/zero"
dd if=/dev/zero of="$DEVICE" bs=4M conv=fsync,noerror status=progress || DD_RESULT=$?
else
# Use OpenSSL for faster pseudorandom generation when available.
if command -v openssl >/dev/null 2>&1; then
RANDOM_SOURCE_DESC="openssl enc (AES-256-CTR PRNG)"
openssl enc -aes-256-ctr -pass pass:"$(head -c 32 /dev/urandom | base64)" \
-nosalt < /dev/zero 2>/dev/null | \
dd of="$DEVICE" bs=4M conv=fsync,noerror status=progress || DD_RESULT=$?
else
RANDOM_SOURCE_DESC="/dev/urandom (по-бавно, openssl не е намерен)"
dd if=/dev/urandom of="$DEVICE" bs=4M conv=fsync,noerror status=progress || DD_RESULT=$?
fi
fi
common::log "Data source: $RANDOM_SOURCE_DESC"
if [[ "$DD_RESULT" -eq 0 ]]; then
common::log "dd completed successfully (no write I/O errors)."
else
common::log "dd exited with status $DD_RESULT; the disk may have reached its end."
fi
DD_END=$(date +%s)
common::log "Overwrite finished: $(date '+%Y-%m-%d %H:%M:%S')"
common::log "Duration: $(( (DD_END - DD_START) / 3600 ))h $(( ((DD_END - DD_START) % 3600) / 60 ))m"
sync
common::log "Waiting 10 seconds before SMART verification."
sleep 10
common::log "--- SMART values after overwrite ---"
smartctl -A "$DEVICE" | grep -E \
"Reallocated_Sector_Ct|Current_Pending_Sector|Offline_Uncorrectable|UDMA_CRC_Error_Count" \
|| common::log "SMART read failed."
# --- Phase 2: SMART extended self-test -------------------------------------
common::log "--- Phase 2: starting SMART extended self-test ---"
smartctl -t long "$DEVICE"
# Read the estimated duration from smartctl, falling back to 360 minutes.
EST_MIN="$(smartctl -c "$DEVICE" 2>/dev/null | grep -oP 'Extended self-test routine\s*\K[0-9]+' || true)"
EST_MIN="${EST_MIN:-360}"
WAIT_SECONDS=$(( (EST_MIN + 10) * 60 ))
common::log "Expected self-test duration: about ${EST_MIN} minutes."
common::log "Waiting $(( WAIT_SECONDS / 60 )) minutes before reading the result."
common::log "The drive firmware runs the self-test while this script waits."
sleep "$WAIT_SECONDS"
common::log "--- Self-test result ---"
smartctl -l selftest "$DEVICE"
# --- Final report ----------------------------------------------------------
common::log "=============================================================="
common::log "Final SMART report — $DEVICE"
common::log "Finished: $(date '+%Y-%m-%d %H:%M:%S')"
common::log "=============================================================="
smartctl -A "$DEVICE" | grep -E \
"Reallocated_Sector_Ct|Current_Pending_Sector|Offline_Uncorrectable|UDMA_CRC_Error_Count|Reallocated_Event_Count"
smartctl -H "$DEVICE"
common::log "Full log: $MAIN_LOG"
common::log "Compare these values with the initial values in the log:"
common::log "Current_Pending_Sector should decrease (ideally to 0)."
common::log "Reallocated_Sector_Ct may increase as sectors are remapped."
common::log "If pending sectors remain, the drive may be unable to write them."
common::log "=============================================================="