|
|
@@ -0,0 +1,83 @@
|
|
|
+=== BAMBUDDY FILE DELETION ISSUE - Jan 8, 2026 ===
|
|
|
+=== ROOT CAUSE IDENTIFIED ===
|
|
|
+
|
|
|
+WHAT HAPPENED:
|
|
|
+- /opt was COMPLETELY DELETED on TWO containers:
|
|
|
+ - Container 109 (claude): ~11:22 and ~12:22
|
|
|
+ - Container 107 (3dp): ~13:28
|
|
|
+- Container 107 was "untouched" (no SSH, no Claude Code) - just running BamBuddy
|
|
|
+
|
|
|
+ROOT CAUSE FOUND:
|
|
|
+Bug in backend/app/services/archive.py delete_archive() function (lines 914-929):
|
|
|
+
|
|
|
+ file_path = settings.base_dir / archive.file_path
|
|
|
+ if file_path.exists():
|
|
|
+ archive_dir = file_path.parent
|
|
|
+ shutil.rmtree(archive_dir, ignore_errors=True) # <-- THE BUG
|
|
|
+
|
|
|
+If archive.file_path is EMPTY or MALFORMED:
|
|
|
+- file_path = /opt/bambuddy / "" = /opt/bambuddy
|
|
|
+- archive_dir = file_path.parent = /opt
|
|
|
+- shutil.rmtree("/opt") --> DELETES ENTIRE /opt DIRECTORY!
|
|
|
+
|
|
|
+TRIGGER:
|
|
|
+- User was deleting archives via BamBuddy web UI on container 107 (3dp)
|
|
|
+- One archive had corrupted/empty file_path in database
|
|
|
+- Deleting that archive triggered shutil.rmtree("/opt")
|
|
|
+- This deleted the entire /opt directory including BamBuddy itself
|
|
|
+
|
|
|
+TIMELINE FOR CONTAINER 107 (3dp):
|
|
|
+- 13:28:19 - Normal operation (WebSocket disconnect)
|
|
|
+- 13:28:44 - DELETE /api/v1/archives/* requests failing with 500
|
|
|
+ (database already gone because /opt was deleted)
|
|
|
+- ls -la / shows root directory modified at 13:28
|
|
|
+
|
|
|
+FIX APPLIED (on container 109):
|
|
|
+Safety checks added to delete_archive() in archive.py:
|
|
|
+1. Check if file_path is not empty
|
|
|
+2. Verify archive_dir is inside settings.archive_dir
|
|
|
+3. Ensure archive_dir is at least 2 levels deep
|
|
|
+4. Log error and refuse to delete if checks fail
|
|
|
+
|
|
|
+TO INVESTIGATE AFTER ROLLBACK:
|
|
|
+On container 107, after rolling back to autodaily260108003006:
|
|
|
+
|
|
|
+ # Find corrupted archive records
|
|
|
+ sqlite3 /opt/bambuddy/data/bambuddy.db \
|
|
|
+ "SELECT id, filename, file_path FROM print_archives
|
|
|
+ WHERE file_path = '' OR file_path IS NULL
|
|
|
+ OR file_path NOT LIKE 'archive/%';"
|
|
|
+
|
|
|
+ # Check all file_path values
|
|
|
+ sqlite3 /opt/bambuddy/data/bambuddy.db \
|
|
|
+ "SELECT id, file_path FROM print_archives ORDER BY id;"
|
|
|
+
|
|
|
+CONTAINER 109 (this host):
|
|
|
+- Were you also deleting archives around 11:22 and 12:22?
|
|
|
+- Same bug could have been triggered here too
|
|
|
+
|
|
|
+PROXMOX COMMANDS FOR ROLLBACK:
|
|
|
+ # Container 107 (3dp)
|
|
|
+ pct rollback 107 autodaily260108003006
|
|
|
+ pct start 107
|
|
|
+
|
|
|
+ # Container 109 (claude) - already done via UI
|
|
|
+ # Current snapshot: autodaily260108003004
|
|
|
+
|
|
|
+WHAT TO DO NEXT:
|
|
|
+1. Rollback container 107 to morning snapshot
|
|
|
+2. Run the SQL query above to find corrupted archive
|
|
|
+3. Apply the fix from container 109 to container 107
|
|
|
+4. Understand how the file_path got corrupted in the first place
|
|
|
+
|
|
|
+THE FIX (apply to both containers):
|
|
|
+In backend/app/services/archive.py, the delete_archive function now has:
|
|
|
+- Empty file_path check
|
|
|
+- Path traversal protection (relative_to check)
|
|
|
+- Minimum depth check (must be 2+ levels inside archive dir)
|
|
|
+- Error logging for refused deletions
|
|
|
+
|
|
|
+NOT CLAUDE CODE'S FAULT:
|
|
|
+This was a bug in BamBuddy's own code that was triggered by:
|
|
|
+1. Corrupted database record (unknown how it got corrupted)
|
|
|
+2. User action (deleting archives via web UI)
|