#!/bin/sh
# Secure file destruction: strip metadata, overwrite, shred, remove
# Usage: nuke <file1> [file2] ...
# nuke -d <dir> nuke an entire directory
set -e
die() { echo "nuke: $*" >&2; exit 1; }
strip_metadata() {
file="$1"
mime=$(file -b --mime-type "$file" 2>/dev/null || echo "unknown")
case "$mime" in
image/*|video/*|audio/*|application/pdf)
if command -v exiftool >/dev/null 2>&1; then
exiftool -all= -overwrite_original "$file" 2>/dev/null || true
fi
if command -v mat2 >/dev/null 2>&1; then
mat2 --inplace "$file" 2>/dev/null || true
fi
;;
esac
}
nuke_file() {
file="$1"
[ -f "$file" ] || return
# 1. strip all metadata
strip_metadata "$file"
# 2. get file size, overwrite with /dev/urandom
size=$(stat -c%s "$file" 2>/dev/null || echo 0)
if [ "$size" -gt 0 ]; then
dd if=/dev/urandom of="$file" bs=1 count="$size" conv=notrunc 2>/dev/null || true
fi
# 3. shred: 7 passes random + 1 pass zeros to hide shredding
/usr/bin/shred -vzn 7 "$file" 2>/dev/null
# 4. truncate to zero
truncate -s 0 "$file" 2>/dev/null || true
# 5. rename to random name before unlinking (obscure filename from journal)
dir=$(dirname "$file")
rand=$(head -c 16 /dev/urandom | xxd -p)
mv "$file" "$dir/$rand" 2>/dev/null && sync && rm -f "$dir/$rand"
if [ -f "$file" ]; then
sync
rm -f "$file"
fi
echo "nuked: $file"
}
nuke_dir() {
dir="$1"
[ -d "$dir" ] || die "not a directory: $dir"
# nuke all files recursively
find "$dir" -type f | while read -r f; do
nuke_file "$f"
done
# remove empty directory tree
rm -rf "$dir"
echo "nuked directory: $dir"
}
[ -z "$1" ] && die "usage: nuke [-d] <files/dirs>"
if [ "$1" = "-d" ]; then
shift
[ -z "$1" ] && die "usage: nuke -d <directory>"
for d in "$@"; do
nuke_dir "$d"
done
else
for f in "$@"; do
if [ -d "$f" ]; then
printf "'%s' is a directory. use nuke -d to nuke directories. skip? [Y/n] " "$f"
read -r ans
case "$ans" in
n|N) nuke_dir "$f" ;;
*) echo "skipped: $f" ;;
esac
elif [ -f "$f" ]; then
nuke_file "$f"
else
echo "not found: $f"
fi
done
fi