Files
LauckCycles/app/Console/Commands/BackupDatabase.php
T
2026-05-27 15:59:44 -03:00

160 lines
5.3 KiB
PHP

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Illuminate\Support\Facades\File;
use ZipArchive;
class BackupDatabase extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'db:backup';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Crea una copia de seguridad comprimida de la base de datos MySQL y elimina respaldos antiguos.';
/**
* Execute the console command.
*/
public function handle()
{
$this->info('Iniciando copia de seguridad de la base de datos...');
$dbConfig = config('database.connections.mysql');
if (!$dbConfig) {
$this->error('No se pudo cargar la configuración de la base de datos MySQL.');
return 1;
}
$host = $dbConfig['host'] ?? '127.0.0.1';
$port = $dbConfig['port'] ?? '3306';
$database = $dbConfig['database'] ?? 'lauck';
$username = $dbConfig['username'] ?? 'root';
$password = $dbConfig['password'] ?? '';
// Asegurarse de que el directorio de respaldos exista
$backupDir = storage_path('app/backups');
if (!File::exists($backupDir)) {
File::makeDirectory($backupDir, 0755, true);
}
// Nombre de los archivos
$timestamp = now()->format('Y-m-d_H-i-s');
$sqlFile = "backup-{$database}-{$timestamp}.sql";
$sqlPath = $backupDir . '/' . $sqlFile;
$zipFile = "backup-{$database}-{$timestamp}.zip";
$zipPath = $backupDir . '/' . $zipFile;
// Buscar el ejecutable mysqldump
$mysqldumpPath = 'mysqldump';
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
// En Windows/XAMPP comúnmente está en C:\xampp\mysql\bin\mysqldump.exe
$xamppPath = 'C:\\xampp\\mysql\\bin\\mysqldump.exe';
if (File::exists($xamppPath)) {
$mysqldumpPath = $xamppPath;
}
}
// Ejecutar mysqldump con Process
// Usamos variables de entorno para la contraseña por seguridad
$command = [
$mysqldumpPath,
"--host={$host}",
"--port={$port}",
"--user={$username}",
"--no-tablespaces",
$database
];
// Inherit SystemRoot and PATH from the system environment to prevent Winsock 10106 errors on Windows
$env = [
'SystemRoot' => getenv('SystemRoot') ?: 'C:\\Windows',
'windir' => getenv('windir') ?: 'C:\\Windows',
'PATH' => getenv('PATH'),
];
if ($password !== '') {
$env['MYSQL_PWD'] = $password;
}
$this->info("Ejecutando volcado a archivo temporal SQL...");
$process = new Process($command, null, $env);
$process->setTimeout(300); // 5 minutos máximo
try {
$process->run();
if (!$process->isSuccessful()) {
throw new \Exception(trim($process->getErrorOutput()) ?: 'Error desconocido al ejecutar mysqldump.');
}
// Guardar salida en el archivo SQL
File::put($sqlPath, $process->getOutput());
} catch (\Exception $e) {
$this->error('Error durante el volcado de la base de datos: ' . $e->getMessage());
// Limpieza si quedó el archivo a medio hacer
if (File::exists($sqlPath)) {
File::delete($sqlPath);
}
return 1;
}
// Comprimir el archivo SQL a ZIP
$this->info('Comprimiendo respaldo...');
$zip = new ZipArchive();
if ($zip->open($zipPath, ZipArchive::CREATE) === true) {
$zip->addFile($sqlPath, $sqlFile);
$zip->close();
// Eliminar el archivo SQL temporal
File::delete($sqlPath);
$this->info("Copia de seguridad guardada con éxito en: {$zipFile}");
} else {
$this->error('No se pudo crear el archivo ZIP.');
return 1;
}
// Retención de respaldos (limpieza de archivos antiguos)
$this->cleanupOldBackups($backupDir);
return 0;
}
/**
* Elimina respaldos antiguos que exceden el límite de días (30 días).
*/
protected function cleanupOldBackups($backupDir)
{
$this->info('Revisando si hay respaldos antiguos para limpiar...');
$days = 30; // Conservar los últimos 30 días de respaldos semanales
$files = File::files($backupDir);
foreach ($files as $file) {
// Verificar que sea un archivo de backup
if ($file->getExtension() === 'zip' && strpos($file->getFilename(), 'backup-') === 0) {
$lastModified = $file->getMTime();
$ageInDays = (time() - $lastModified) / (24 * 60 * 60);
if ($ageInDays > $days) {
$this->warn("Eliminando respaldo antiguo: " . $file->getFilename());
File::delete($file->getPathname());
}
}
}
$this->info('Limpieza finalizada.');
}
}