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:
2026-01-20 15:27:05 -05:00
parent bc6a5cecf8
commit 591fad52cc
4 changed files with 217 additions and 1 deletions

View 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);
}