safe-recycle.py
Fail-safe deletion — moves files to a @Recycle area with a JSON audit trail instead of ever hard-deleting.
Note
Live script from my home-lab server. Tokens, IDs, phone numbers and other secrets have been replaced with
placeholders like
<WHATSAPP_GROUP_ID> — everything else is the real, running code.
#!/usr/bin/env python3
import datetime
import json
import os
import shutil
import sys
import uuid
ROOTS = ("/mnt/nas", "/mnt/jelly")
REPORT_DIR = "/home/lanky/reports"
LOG_PATH = os.path.join(REPORT_DIR, "media-recycle-audit.jsonl")
def resolve_source(value: str) -> tuple[str, str]:
path = os.path.realpath(value)
for root in ROOTS:
root_real = os.path.realpath(root)
if os.path.commonpath([path, root_real]) != root_real:
continue
relative = os.path.relpath(path, root_real)
if relative == "." or relative.startswith("@Recycle" + os.sep):
raise ValueError(f"refusing recycle source: {value}")
return path, root_real
raise ValueError(f"path is outside approved NAS roots: {value}")
def recycle(source_value: str, reason: str) -> dict:
source, root = resolve_source(source_value)
if not os.path.lexists(source):
raise FileNotFoundError(source)
stamp = datetime.datetime.now().astimezone()
relative = os.path.relpath(source, root)
destination = os.path.join(
root,
"@Recycle",
"CodexReview",
stamp.strftime("%Y-%m-%d"),
relative,
)
if os.path.lexists(destination):
base, extension = os.path.splitext(destination)
destination = f"{base}.{stamp.strftime('%H%M%S')}.{uuid.uuid4().hex[:8]}{extension}"
os.makedirs(os.path.dirname(destination), exist_ok=True)
shutil.move(source, destination)
record = {
"timestamp": stamp.isoformat(),
"source": source,
"destination": destination,
"reason": reason,
"action": "moved_to_recycle",
}
os.makedirs(REPORT_DIR, exist_ok=True)
with open(LOG_PATH, "a", encoding="utf-8") as log:
log.write(json.dumps(record, ensure_ascii=True) + "\n")
return record
def main() -> int:
if len(sys.argv) < 3:
print("Usage: safe-recycle.py <reason> <path> [path ...]", file=sys.stderr)
return 2
reason = sys.argv[1]
failures = 0
for value in sys.argv[2:]:
try:
record = recycle(value, reason)
print(f"RECYCLED: {record['source']}")
print(f"REVIEW AT: {record['destination']}")
except Exception as error:
failures += 1
print(f"ERROR: {value}: {error}", file=sys.stderr)
return 1 if failures else 0
if __name__ == "__main__":
raise SystemExit(main())