Add deployment scripts and preserve uploads folder
- Add scripts/deploy.sh for safe deployment with uploads preservation - Add scripts/cleanup_orphan_uploads.php to remove orphaned files - Add .gitkeep to uploads folder - Update .gitignore to exclude uploaded files but keep folder structure The deploy script now: - Backs up and restores .env file - Backs up and restores uploads folder contents - Runs database migrations automatically Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
148
scripts/cleanup_orphan_uploads.php
Executable file
148
scripts/cleanup_orphan_uploads.php
Executable file
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Cleanup Orphan Uploads
|
||||
*
|
||||
* Removes uploaded files that are no longer associated with any ticket.
|
||||
* Run periodically via cron: 0 2 * * * php /path/to/cleanup_orphan_uploads.php
|
||||
*/
|
||||
|
||||
require_once dirname(__DIR__) . '/config/config.php';
|
||||
|
||||
$conn = new mysqli(
|
||||
$GLOBALS['config']['DB_HOST'],
|
||||
$GLOBALS['config']['DB_USER'],
|
||||
$GLOBALS['config']['DB_PASS'],
|
||||
$GLOBALS['config']['DB_NAME']
|
||||
);
|
||||
|
||||
if ($conn->connect_error) {
|
||||
die("Database connection failed: " . $conn->connect_error . "\n");
|
||||
}
|
||||
|
||||
$uploadsDir = dirname(__DIR__) . '/uploads';
|
||||
$dryRun = in_array('--dry-run', $argv);
|
||||
|
||||
if ($dryRun) {
|
||||
echo "DRY RUN MODE - No files will be deleted\n";
|
||||
}
|
||||
|
||||
echo "Scanning uploads directory: $uploadsDir\n";
|
||||
|
||||
// Get all valid ticket IDs from database
|
||||
$ticketIds = [];
|
||||
$result = $conn->query("SELECT ticket_id FROM tickets");
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$ticketIds[] = $row['ticket_id'];
|
||||
}
|
||||
echo "Found " . count($ticketIds) . " tickets in database\n";
|
||||
|
||||
// Get all attachment records
|
||||
$attachments = [];
|
||||
$result = $conn->query("SELECT ticket_id, filename FROM ticket_attachments");
|
||||
if ($result) {
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$key = $row['ticket_id'] . '/' . $row['filename'];
|
||||
$attachments[$key] = true;
|
||||
}
|
||||
}
|
||||
echo "Found " . count($attachments) . " attachment records in database\n";
|
||||
|
||||
// Scan uploads directory
|
||||
$orphanedFolders = [];
|
||||
$orphanedFiles = [];
|
||||
$totalSize = 0;
|
||||
|
||||
$ticketDirs = glob($uploadsDir . '/*', GLOB_ONLYDIR);
|
||||
foreach ($ticketDirs as $ticketDir) {
|
||||
$ticketId = basename($ticketDir);
|
||||
|
||||
// Skip non-ticket directories
|
||||
if (!preg_match('/^\d{9}$/', $ticketId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if ticket exists
|
||||
if (!in_array($ticketId, $ticketIds)) {
|
||||
// Ticket doesn't exist - entire folder is orphaned
|
||||
$orphanedFolders[] = $ticketDir;
|
||||
$folderSize = 0;
|
||||
foreach (glob($ticketDir . '/*') as $file) {
|
||||
if (is_file($file)) {
|
||||
$folderSize += filesize($file);
|
||||
}
|
||||
}
|
||||
$totalSize += $folderSize;
|
||||
echo "Orphan folder (ticket deleted): $ticketDir (" . formatBytes($folderSize) . ")\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check individual files
|
||||
$files = glob($ticketDir . '/*');
|
||||
foreach ($files as $file) {
|
||||
if (is_file($file)) {
|
||||
$filename = basename($file);
|
||||
$key = $ticketId . '/' . $filename;
|
||||
|
||||
if (!isset($attachments[$key])) {
|
||||
$orphanedFiles[] = $file;
|
||||
$fileSize = filesize($file);
|
||||
$totalSize += $fileSize;
|
||||
echo "Orphan file (no DB record): $file (" . formatBytes($fileSize) . ")\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n=== Summary ===\n";
|
||||
echo "Orphaned folders: " . count($orphanedFolders) . "\n";
|
||||
echo "Orphaned files: " . count($orphanedFiles) . "\n";
|
||||
echo "Total size to recover: " . formatBytes($totalSize) . "\n";
|
||||
|
||||
if (!$dryRun && ($orphanedFolders || $orphanedFiles)) {
|
||||
echo "\nDeleting orphaned items...\n";
|
||||
|
||||
foreach ($orphanedFiles as $file) {
|
||||
if (unlink($file)) {
|
||||
echo "Deleted: $file\n";
|
||||
} else {
|
||||
echo "Failed to delete: $file\n";
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($orphanedFolders as $folder) {
|
||||
deleteDirectory($folder);
|
||||
echo "Deleted folder: $folder\n";
|
||||
}
|
||||
|
||||
echo "Cleanup complete!\n";
|
||||
} elseif ($dryRun) {
|
||||
echo "\nRun without --dry-run to delete these items.\n";
|
||||
} else {
|
||||
echo "\nNo orphaned items found.\n";
|
||||
}
|
||||
|
||||
$conn->close();
|
||||
|
||||
function formatBytes($bytes) {
|
||||
if ($bytes >= 1073741824) {
|
||||
return number_format($bytes / 1073741824, 2) . ' GB';
|
||||
} elseif ($bytes >= 1048576) {
|
||||
return number_format($bytes / 1048576, 2) . ' MB';
|
||||
} elseif ($bytes >= 1024) {
|
||||
return number_format($bytes / 1024, 2) . ' KB';
|
||||
} else {
|
||||
return $bytes . ' bytes';
|
||||
}
|
||||
}
|
||||
|
||||
function deleteDirectory($dir) {
|
||||
if (!is_dir($dir)) return;
|
||||
|
||||
$files = array_diff(scandir($dir), ['.', '..']);
|
||||
foreach ($files as $file) {
|
||||
$path = "$dir/$file";
|
||||
is_dir($path) ? deleteDirectory($path) : unlink($path);
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
Reference in New Issue
Block a user