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:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -2,3 +2,8 @@
|
|||||||
debug.log
|
debug.log
|
||||||
.claude
|
.claude
|
||||||
settings.local.json
|
settings.local.json
|
||||||
|
|
||||||
|
# Upload files (keep folder structure, ignore actual uploads)
|
||||||
|
uploads/*
|
||||||
|
!uploads/.gitkeep
|
||||||
|
!uploads/.htaccess
|
||||||
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);
|
||||||
|
}
|
||||||
63
scripts/deploy.sh
Executable file
63
scripts/deploy.sh
Executable file
@@ -0,0 +1,63 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# TinkerTickets Deployment Script
|
||||||
|
# This script safely deploys updates while preserving user data
|
||||||
|
set -e
|
||||||
|
|
||||||
|
WEBROOT="/var/www/html/tinkertickets"
|
||||||
|
UPLOADS_BACKUP="/tmp/tinker_uploads_backup"
|
||||||
|
|
||||||
|
echo "[TinkerTickets] Starting deployment..."
|
||||||
|
|
||||||
|
# Backup .env if it exists
|
||||||
|
if [ -f "$WEBROOT/.env" ]; then
|
||||||
|
echo "[TinkerTickets] Backing up .env..."
|
||||||
|
cp "$WEBROOT/.env" /tmp/.env.backup
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Backup uploads folder if it exists and has files
|
||||||
|
if [ -d "$WEBROOT/uploads" ] && [ "$(ls -A $WEBROOT/uploads 2>/dev/null)" ]; then
|
||||||
|
echo "[TinkerTickets] Backing up uploads folder..."
|
||||||
|
rm -rf "$UPLOADS_BACKUP"
|
||||||
|
cp -r "$WEBROOT/uploads" "$UPLOADS_BACKUP"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$WEBROOT/.git" ]; then
|
||||||
|
echo "[TinkerTickets] Directory not a git repo — performing initial clone..."
|
||||||
|
rm -rf "$WEBROOT"
|
||||||
|
git clone https://code.lotusguild.org/LotusGuild/tinker_tickets.git "$WEBROOT"
|
||||||
|
else
|
||||||
|
echo "[TinkerTickets] Updating existing repo..."
|
||||||
|
cd "$WEBROOT"
|
||||||
|
git fetch --all
|
||||||
|
git reset --hard origin/main
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Restore .env if it was backed up
|
||||||
|
if [ -f /tmp/.env.backup ]; then
|
||||||
|
echo "[TinkerTickets] Restoring .env..."
|
||||||
|
mv /tmp/.env.backup "$WEBROOT/.env"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Restore uploads folder if it was backed up
|
||||||
|
if [ -d "$UPLOADS_BACKUP" ]; then
|
||||||
|
echo "[TinkerTickets] Restoring uploads folder..."
|
||||||
|
# Don't overwrite .htaccess from repo
|
||||||
|
rsync -av --exclude='.htaccess' --exclude='.gitkeep' "$UPLOADS_BACKUP/" "$WEBROOT/uploads/"
|
||||||
|
rm -rf "$UPLOADS_BACKUP"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure uploads directory exists with proper permissions
|
||||||
|
mkdir -p "$WEBROOT/uploads"
|
||||||
|
chmod 755 "$WEBROOT/uploads"
|
||||||
|
|
||||||
|
echo "[TinkerTickets] Setting permissions..."
|
||||||
|
chown -R www-data:www-data "$WEBROOT"
|
||||||
|
|
||||||
|
# Run migrations if .env exists
|
||||||
|
if [ -f "$WEBROOT/.env" ]; then
|
||||||
|
echo "[TinkerTickets] Running database migrations..."
|
||||||
|
cd "$WEBROOT/migrations"
|
||||||
|
php run_migrations.php || echo "[TinkerTickets] Warning: Migration errors occurred"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[TinkerTickets] Deployment complete!"
|
||||||
0
uploads/.gitkeep
Normal file
0
uploads/.gitkeep
Normal file
Reference in New Issue
Block a user