#!/bin/sh # Secure file destruction: strip metadata, overwrite, shred, remove # Usage: nuke [file2] ... # nuke -d 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] " if [ "$1" = "-d" ]; then shift [ -z "$1" ] && die "usage: nuke -d " 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