mirror of
https://git.imnavajas.es/jjimenez/safekat.git
synced 2025-07-25 22:52:08 +00:00
Implementada logica para entornos dev/prod
This commit is contained in:
@ -9,7 +9,7 @@ use App\Models\Catalogo\IdentificadorIsknModel;
|
|||||||
|
|
||||||
class CatalogoLibroAsignarIskn extends BaseCommand
|
class CatalogoLibroAsignarIskn extends BaseCommand
|
||||||
{
|
{
|
||||||
protected $group = 'custom';
|
protected $group = 'Safekat';
|
||||||
protected $name = 'catalogo:libro-asignar-iskn';
|
protected $name = 'catalogo:libro-asignar-iskn';
|
||||||
protected $description = 'Asigna ISKN directamente en la base de datos a los libros que no lo tienen.';
|
protected $description = 'Asigna ISKN directamente en la base de datos a los libros que no lo tienen.';
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ use CodeIgniter\CLI\CLI;
|
|||||||
|
|
||||||
class CatalogoLibroImportar extends BaseCommand
|
class CatalogoLibroImportar extends BaseCommand
|
||||||
{
|
{
|
||||||
protected $group = 'custom';
|
protected $group = 'Safekat';
|
||||||
protected $name = 'catalogo:libro-importar';
|
protected $name = 'catalogo:libro-importar';
|
||||||
protected $description = 'Importa los registros de catalogo_libro a catalogo_libros para un customer_id dado';
|
protected $description = 'Importa los registros de catalogo_libro a catalogo_libros para un customer_id dado';
|
||||||
|
|
||||||
|
|||||||
93
ci4/app/Commands/RestoreBackup.php
Normal file
93
ci4/app/Commands/RestoreBackup.php
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
use CodeIgniter\CLI\BaseCommand;
|
||||||
|
use CodeIgniter\CLI\CLI;
|
||||||
|
use ZipArchive;
|
||||||
|
|
||||||
|
class RestoreBackup extends BaseCommand
|
||||||
|
{
|
||||||
|
protected $group = 'Safekat';
|
||||||
|
protected $name = 'restore:backup';
|
||||||
|
protected $description = 'Restaura un backup desde un archivo .zip en writable/backups/.';
|
||||||
|
protected $usage = 'restore:backup [--dry-run]';
|
||||||
|
protected $options = [
|
||||||
|
'--dry-run' => 'Simula el proceso de restauración sin ejecutarlo realmente.',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function run(array $params)
|
||||||
|
{
|
||||||
|
$isDryRun = CLI::getOption('dry-run');
|
||||||
|
|
||||||
|
$backupDir = WRITEPATH . 'backups/';
|
||||||
|
$backups = glob($backupDir . '*.zip');
|
||||||
|
|
||||||
|
if (empty($backups)) {
|
||||||
|
CLI::error("No se encontraron backups .zip en: $backupDir");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLI::write("Backups disponibles:", 'blue');
|
||||||
|
foreach ($backups as $i => $file) {
|
||||||
|
CLI::write("[" . ($i + 1) . "] " . basename($file));
|
||||||
|
}
|
||||||
|
|
||||||
|
$index = CLI::prompt("Selecciona el número del backup a restaurar", null, 'required');
|
||||||
|
|
||||||
|
if (!is_numeric($index) || $index < 1 || $index > count($backups)) {
|
||||||
|
CLI::error("Selección no válida.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$selectedFile = $backups[$index - 1];
|
||||||
|
CLI::write("🎯 Seleccionado: " . basename($selectedFile), 'cyan');
|
||||||
|
|
||||||
|
if ($isDryRun) {
|
||||||
|
CLI::write("🔍 Modo simulación activado (--dry-run)", 'yellow');
|
||||||
|
}
|
||||||
|
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
if ($zip->open($selectedFile) !== TRUE) {
|
||||||
|
CLI::error("No se pudo abrir el archivo ZIP.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$extractPath = WRITEPATH . 'backups/tmp_restore/';
|
||||||
|
if (!is_dir($extractPath)) {
|
||||||
|
mkdir($extractPath, 0775, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$zip->extractTo($extractPath);
|
||||||
|
$zip->close();
|
||||||
|
|
||||||
|
$sqlFiles = glob($extractPath . '*.sql');
|
||||||
|
if (count($sqlFiles) === 0) {
|
||||||
|
CLI::error("No se encontró ningún .sql dentro del backup.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sqlFile = escapeshellarg($sqlFiles[0]);
|
||||||
|
$db = config('Database')->default;
|
||||||
|
|
||||||
|
$cmd = "mysql -h {$db['hostname']} -u {$db['username']} -p'{$db['password']}' {$db['database']} < {$sqlFile}";
|
||||||
|
|
||||||
|
if ($isDryRun) {
|
||||||
|
CLI::write("📋 Comando que se ejecutaría:", 'yellow');
|
||||||
|
CLI::write($cmd, 'light_gray');
|
||||||
|
CLI::write("✅ Simulación completa. No se hizo ninguna modificación.", 'green');
|
||||||
|
} else {
|
||||||
|
CLI::write("⏳ Restaurando...", 'yellow');
|
||||||
|
system($cmd, $retval);
|
||||||
|
|
||||||
|
if ($retval !== 0) {
|
||||||
|
CLI::error("❌ Error al restaurar la base de datos (código $retval).");
|
||||||
|
} else {
|
||||||
|
CLI::write("✅ Backup restaurado correctamente.", 'green');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
array_map('unlink', glob($extractPath . '*'));
|
||||||
|
rmdir($extractPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,17 +17,19 @@ $routes->group('sistema', ['namespace' => 'App\Controllers\Sistema'], function (
|
|||||||
});
|
});
|
||||||
|
|
||||||
/* Backups */
|
/* Backups */
|
||||||
$routes->group('backups', ['namespace' => 'App\Controllers\Sistema'], function ($routes) {
|
$routes->group('backups', function ($routes) {
|
||||||
/**======================
|
/**======================
|
||||||
* Tool
|
* Tool
|
||||||
*========================**/
|
*========================**/
|
||||||
$routes->get('', 'Backups::index', ['as' => 'backupsList']);
|
$routes->get('', 'Backups::index', ['as' => 'backupsList']);
|
||||||
$routes->get('create', 'Backups::create', ['as' => 'backupsCreate']);
|
$routes->get('create', 'Backups::create', ['as' => 'backupsCreate']);
|
||||||
|
$routes->get('create-dev', 'Backups::createDevelopment', ['as' => 'backupsCreateDev']);
|
||||||
|
$routes->get('backups/download/(:segment)', 'Backups::download/$1', ['as' => 'backupsDownload']);
|
||||||
$routes->get('delete-local/(:num)', 'Backups::deleteLocal/$1', ['as' => 'backupsDeleteLocal']);
|
$routes->get('delete-local/(:num)', 'Backups::deleteLocal/$1', ['as' => 'backupsDeleteLocal']);
|
||||||
|
$routes->get('delete-local-dev/(:segment)', 'Backups::deleteLocalDevelopment/$1', ['as' => 'backupsDeleteLocalDev']);
|
||||||
$routes->get('restore/(:segment)/local', 'Backups::restoreLocal/$1', ['as' => 'backupsRestoreLocal']);
|
$routes->get('restore/(:segment)/local', 'Backups::restoreLocal/$1', ['as' => 'backupsRestoreLocal']);
|
||||||
$routes->get('restore/(:segment)/remote', 'Backups::restoreRemote/$1', ['as' => 'backupsRestoreRemote']);
|
$routes->get('restore/(:segment)/remote', 'Backups::restoreRemote/$1', ['as' => 'backupsRestoreRemote']);
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@ -19,51 +19,141 @@ class Backups extends BaseController
|
|||||||
{
|
{
|
||||||
helper('filesystem');
|
helper('filesystem');
|
||||||
|
|
||||||
$entries = $this->backupModel->orderBy('created_at', 'DESC')->findAll();
|
$entorno = getenv('SK_ENVIRONMENT');
|
||||||
|
|
||||||
$backups = [];
|
$backups = [];
|
||||||
foreach ($entries as $entry) {
|
|
||||||
$file = $entry['filename'];
|
|
||||||
$localPath = $entry['path_local'];
|
|
||||||
$remotePath = $entry['path_remote'];
|
|
||||||
|
|
||||||
$isLocal = $localPath && file_exists($localPath);
|
if ($entorno === 'development') {
|
||||||
$isRemote = !empty($remotePath);
|
// Leer archivos directamente del disco en entorno de desarrollo
|
||||||
|
$backups = [];
|
||||||
|
|
||||||
if ($isLocal) {
|
// === 1. Backups locales ===
|
||||||
$fecha = date('Y-m-d H:i', filemtime($localPath));
|
$localDir = WRITEPATH . 'backups/';
|
||||||
$tamano = filesize($localPath);
|
$localFiles = get_filenames($localDir);
|
||||||
$tamanoFmt = number_format($tamano / 1024, 2) . ' KB';
|
|
||||||
|
|
||||||
// Actualizar la BD si ha cambiado
|
foreach ($localFiles as $file) {
|
||||||
if ($entry['size'] != $tamano) {
|
if (!str_ends_with($file, '.zip') || !str_starts_with($file, 'backup_dev_')) {
|
||||||
$this->backupModel->update($entry['id'], [
|
continue;
|
||||||
'size' => $tamano,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
$localPath = $localDir . $file;
|
||||||
$fecha = $entry['created_at'] ?? '-';
|
|
||||||
$tamano = $entry['size'] ?? null;
|
$fecha = date('Y-m-d H:i', filemtime($localPath));
|
||||||
$tamanoFmt = $tamano ? number_format($tamano / 1024, 2) . ' KB' : '-';
|
$tamano = filesize($localPath);
|
||||||
|
$tamanoFmt = $tamano > 1048576
|
||||||
|
? number_format($tamano / 1048576, 2) . ' MB'
|
||||||
|
: number_format($tamano / 1024, 2) . ' KB';
|
||||||
|
|
||||||
|
$backups[$file] = [
|
||||||
|
'id' => null,
|
||||||
|
'filename' => $file,
|
||||||
|
'fecha' => $fecha,
|
||||||
|
'tamano' => $tamanoFmt,
|
||||||
|
'local' => true,
|
||||||
|
'remoto' => false,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$backups[] = [
|
// === 2. Backups remotos en SFTP ===
|
||||||
'id' => $entry['id'],
|
$sftpHost = getenv('HIDRIVE_HOST');
|
||||||
'filename' => $file,
|
$sftpUser = getenv('HIDRIVE_USER');
|
||||||
'fecha' => $fecha,
|
$sftpPass = getenv('HIDRIVE_PASS');
|
||||||
'tamano' => $tamanoFmt,
|
$remoteDir = '/users/erp2019/backups_erp/';
|
||||||
'local' => $isLocal,
|
|
||||||
'remoto' => $isRemote,
|
$sftp = new SFTP($sftpHost);
|
||||||
];
|
if ($sftp->login($sftpUser, $sftpPass)) {
|
||||||
|
$remoteFiles = $sftp->nlist($remoteDir);
|
||||||
|
|
||||||
|
foreach ($remoteFiles as $file) {
|
||||||
|
if (!str_ends_with($file, '.zip') || !str_starts_with($file, 'backup_')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar si ya se cargó como local
|
||||||
|
$alreadyLocal = isset($backups[$file]);
|
||||||
|
|
||||||
|
// Obtener info de archivo remoto
|
||||||
|
$stat = $sftp->stat($remoteDir . $file);
|
||||||
|
$fecha = isset($stat['mtime']) ? date('Y-m-d H:i', $stat['mtime']) : '-';
|
||||||
|
$tamano = $stat['size'] ?? null;
|
||||||
|
$tamanoFmt = $tamano > 1048576
|
||||||
|
? number_format($tamano / 1048576, 2) . ' MB'
|
||||||
|
: number_format($tamano / 1024, 2) . ' KB';
|
||||||
|
|
||||||
|
$backups[$file] = [
|
||||||
|
'id' => null,
|
||||||
|
'filename' => $file,
|
||||||
|
'fecha' => $fecha,
|
||||||
|
'tamano' => $tamanoFmt,
|
||||||
|
'local' => $alreadyLocal,
|
||||||
|
'remoto' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Opcional: mostrar un error o dejarlo pasar silenciosamente
|
||||||
|
session()->setFlashdata('warning', 'No se pudo conectar al servidor SFTP para obtener backups remotos.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convertir a lista (por si se usó índice por filename)
|
||||||
|
$backups = array_values($backups);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// En producción: seguir usando la base de datos
|
||||||
|
$entries = $this->backupModel->orderBy('created_at', 'DESC')->findAll();
|
||||||
|
|
||||||
|
foreach ($entries as $entry) {
|
||||||
|
$file = $entry['filename'];
|
||||||
|
|
||||||
|
if (!str_ends_with($file, '.zip')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$localPath = $entry['path_local'];
|
||||||
|
$remotePath = $entry['path_remote'];
|
||||||
|
|
||||||
|
$isLocal = $localPath && file_exists($localPath);
|
||||||
|
$isRemote = !empty($remotePath);
|
||||||
|
|
||||||
|
if ($isLocal) {
|
||||||
|
$fecha = date('Y-m-d H:i', filemtime($localPath));
|
||||||
|
$tamano = filesize($localPath);
|
||||||
|
$tamanoFmt = $tamano > 1048576
|
||||||
|
? number_format($tamano / 1048576, 2) . ' MB'
|
||||||
|
: number_format($tamano / 1024, 2) . ' KB';
|
||||||
|
|
||||||
|
if ($entry['size'] != $tamano) {
|
||||||
|
$this->backupModel->update($entry['id'], [
|
||||||
|
'size' => $tamano,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$fecha = $entry['created_at'] ?? '-';
|
||||||
|
$tamano = $entry['size'] ?? null;
|
||||||
|
$tamanoFmt = $tamano > 1048576
|
||||||
|
? number_format($tamano / 1048576, 2) . ' MB'
|
||||||
|
: number_format($tamano / 1024, 2) . ' KB';
|
||||||
|
}
|
||||||
|
|
||||||
|
$backups[] = [
|
||||||
|
'id' => $entry['id'],
|
||||||
|
'filename' => $file,
|
||||||
|
'fecha' => $fecha,
|
||||||
|
'tamano' => $tamanoFmt,
|
||||||
|
'local' => $isLocal,
|
||||||
|
'remoto' => $isRemote,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('themes/vuexy/form/backups/backupList', ['backups' => $backups]);
|
return view('themes/vuexy/form/backups/backupList', ['backups' => $backups]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
|
if (getenv('SK_ENVIRONMENT') !== 'production') {
|
||||||
|
return redirect()->to(route_to('backupsList'))->with('error', 'No permitido en entorno de desarrollo.');
|
||||||
|
}
|
||||||
|
|
||||||
helper('filesystem');
|
helper('filesystem');
|
||||||
|
|
||||||
$timestamp = date('Ymd_His');
|
$timestamp = date('Ymd_His');
|
||||||
@ -78,7 +168,8 @@ class Backups extends BaseController
|
|||||||
$password = $dbConfig['password'];
|
$password = $dbConfig['password'];
|
||||||
$database = $dbConfig['database'];
|
$database = $dbConfig['database'];
|
||||||
|
|
||||||
$command = "mysqldump -h {$host} -u{$username} -p'{$password}' {$database} > {$sqlPath}";
|
$command = "mysqldump -h {$host} -u" . escapeshellarg($username) . " -p'" . addslashes($password) . "' {$database} > {$sqlPath}";
|
||||||
|
|
||||||
system($command, $retval);
|
system($command, $retval);
|
||||||
|
|
||||||
if ($retval !== 0) {
|
if ($retval !== 0) {
|
||||||
@ -116,9 +207,175 @@ class Backups extends BaseController
|
|||||||
'status' => 'subido'
|
'status' => 'subido'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return redirect()->to(route_to('backupsList'))->with('message', 'Backup creado, comprimido y enviado.');
|
return redirect()->to(route_to('backupsList'))->with('message', 'Backup del entorno de produccion creado, comprimido y enviado al remoto.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function createDevelopment()
|
||||||
|
{
|
||||||
|
if (getenv('SK_ENVIRONMENT') !== 'development') {
|
||||||
|
return redirect()->to(route_to('backupsList'))->with('error', 'Esta acción solo está permitida en desarrollo.');
|
||||||
|
}
|
||||||
|
|
||||||
|
helper('filesystem');
|
||||||
|
|
||||||
|
$timestamp = date('Ymd_His');
|
||||||
|
$sqlFilename = "backup_dev_{$timestamp}.sql";
|
||||||
|
$zipFilename = "backup_dev_{$timestamp}.zip";
|
||||||
|
$sqlPath = WRITEPATH . 'backups/' . $sqlFilename;
|
||||||
|
$zipPath = WRITEPATH . 'backups/' . $zipFilename;
|
||||||
|
|
||||||
|
$dbConfig = config('Database')->default;
|
||||||
|
$host = $dbConfig['hostname'];
|
||||||
|
$username = $dbConfig['username'];
|
||||||
|
$password = $dbConfig['password'];
|
||||||
|
$database = $dbConfig['database'];
|
||||||
|
|
||||||
|
$command = "mysqldump -h {$host} -u{$username} -p'{$password}' {$database} > {$sqlPath}";
|
||||||
|
system($command, $retval);
|
||||||
|
|
||||||
|
if ($retval !== 0) {
|
||||||
|
throw new \RuntimeException("Error al crear el backup local.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$zip = new \ZipArchive();
|
||||||
|
if ($zip->open($zipPath, \ZipArchive::CREATE) === TRUE) {
|
||||||
|
$zip->addFile($sqlPath, $sqlFilename);
|
||||||
|
$zip->close();
|
||||||
|
unlink($sqlPath);
|
||||||
|
} else {
|
||||||
|
throw new \RuntimeException("Error al comprimir el backup local.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ya no insertamos en la base de datos; el archivo queda en disco y se listará dinámicamente
|
||||||
|
return redirect()->to(route_to('backupsList'))->with('message', 'Backup local del entorno de desarrollo creado.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function download($filename)
|
||||||
|
{
|
||||||
|
helper('filesystem');
|
||||||
|
|
||||||
|
$entorno = getenv('SK_ENVIRONMENT');
|
||||||
|
|
||||||
|
$backup = $this->backupModel->where('filename', $filename)->first();
|
||||||
|
|
||||||
|
// 1. Si hay entrada en la base de datos
|
||||||
|
if ($backup) {
|
||||||
|
$localPath = $backup['path_local'];
|
||||||
|
$remotePath = $backup['path_remote'];
|
||||||
|
|
||||||
|
if ($localPath && file_exists($localPath)) {
|
||||||
|
return $this->response->download($localPath, null)->setFileName($filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($remotePath)) {
|
||||||
|
$sftpHost = getenv('HIDRIVE_HOST');
|
||||||
|
$sftpUser = getenv('HIDRIVE_USER');
|
||||||
|
$sftpPass = getenv('HIDRIVE_PASS');
|
||||||
|
|
||||||
|
$sftp = new SFTP($sftpHost);
|
||||||
|
|
||||||
|
if (!$sftp->login($sftpUser, $sftpPass)) {
|
||||||
|
return redirect()->to(route_to('backupsList'))->with('error', 'No se pudo conectar al servidor SFTP.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileContents = $sftp->get($remotePath);
|
||||||
|
if ($fileContents === false) {
|
||||||
|
return redirect()->to(route_to('backupsList'))->with('error', 'Error al descargar desde SFTP.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$newLocalPath = WRITEPATH . 'backups/' . $filename;
|
||||||
|
write_file($newLocalPath, $fileContents);
|
||||||
|
|
||||||
|
// Omitimos update() si estamos en desarrollo sin base de datos
|
||||||
|
if ($entorno === 'production') {
|
||||||
|
$this->backupModel->update($backup['id'], ['path_local' => $newLocalPath]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->response->download($newLocalPath, null)->setFileName($filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->to(route_to('backupsList'))->with('error', 'No se pudo encontrar el archivo ni local ni remoto.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Si NO hay entrada en la BD y estamos en desarrollo
|
||||||
|
if ($entorno === 'development') {
|
||||||
|
$localPath = WRITEPATH . 'backups/' . $filename;
|
||||||
|
|
||||||
|
if (file_exists($localPath)) {
|
||||||
|
return $this->response->download($localPath, null)->setFileName($filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
// También se puede intentar buscar en el SFTP si quieres
|
||||||
|
$sftpHost = getenv('HIDRIVE_HOST');
|
||||||
|
$sftpUser = getenv('HIDRIVE_USER');
|
||||||
|
$sftpPass = getenv('HIDRIVE_PASS');
|
||||||
|
$remotePath = '/users/erp2019/backups_erp/' . $filename;
|
||||||
|
|
||||||
|
$sftp = new SFTP($sftpHost);
|
||||||
|
if ($sftp->login($sftpUser, $sftpPass)) {
|
||||||
|
$fileContents = $sftp->get($remotePath);
|
||||||
|
if ($fileContents !== false) {
|
||||||
|
$newLocalPath = WRITEPATH . 'backups/' . $filename;
|
||||||
|
write_file($newLocalPath, $fileContents);
|
||||||
|
return $this->response->download($newLocalPath, null)->setFileName($filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->to(route_to('backupsList'))->with('error', 'Archivo no encontrado local ni remoto (sin base de datos).');
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->to(route_to('backupsList'))->with('error', 'Backup no encontrado.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function deleteLocal($id)
|
||||||
|
{
|
||||||
|
$entorno = getenv('SK_ENVIRONMENT');
|
||||||
|
$backup = $this->backupModel->find($id);
|
||||||
|
|
||||||
|
if (!$backup) {
|
||||||
|
return redirect()->to(route_to('backupsList'))->with('error', 'Backup no encontrado en la base de datos.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$localPath = $backup['path_local'];
|
||||||
|
|
||||||
|
// Si existe el archivo, intentamos borrarlo
|
||||||
|
if ($localPath && file_exists($localPath)) {
|
||||||
|
unlink($localPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($entorno === 'production') {
|
||||||
|
// Solo desvincular el archivo local
|
||||||
|
$this->backupModel->update($id, ['path_local' => null]);
|
||||||
|
return redirect()->to(route_to('backupsList'))->with('message', 'Archivo local eliminado (registro conservado).');
|
||||||
|
} else {
|
||||||
|
// Eliminar completamente en desarrollo
|
||||||
|
$this->backupModel->delete($id);
|
||||||
|
return redirect()->to(route_to('backupsList'))->with('message', 'Backup de desarrollo eliminado completamente.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteLocalDevelopment($filename)
|
||||||
|
{
|
||||||
|
$entorno = getenv('SK_ENVIRONMENT');
|
||||||
|
if ($entorno !== 'development') {
|
||||||
|
return redirect()->to(route_to('backupsList'))->with('error', 'Solo permitido en desarrollo.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = WRITEPATH . 'backups/' . $filename;
|
||||||
|
|
||||||
|
if (file_exists($path)) {
|
||||||
|
unlink($path);
|
||||||
|
return redirect()->to(route_to('backupsList'))->with('message', "Archivo '$filename' eliminado del sistema de archivos.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->to(route_to('backupsList'))->with('error', "Archivo '$filename' no encontrado en el sistema.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function restoreLocal($file)
|
public function restoreLocal($file)
|
||||||
{
|
{
|
||||||
$path = WRITEPATH . 'backups/' . $file;
|
$path = WRITEPATH . 'backups/' . $file;
|
||||||
@ -126,7 +383,7 @@ class Backups extends BaseController
|
|||||||
throw new \CodeIgniter\Exceptions\PageNotFoundException("Backup no encontrado.");
|
throw new \CodeIgniter\Exceptions\PageNotFoundException("Backup no encontrado.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$zip = new ZipArchive();
|
$zip = new \ZipArchive();
|
||||||
if ($zip->open($path) === TRUE) {
|
if ($zip->open($path) === TRUE) {
|
||||||
$extractPath = WRITEPATH . 'backups/tmp_restore/';
|
$extractPath = WRITEPATH . 'backups/tmp_restore/';
|
||||||
if (!is_dir($extractPath)) {
|
if (!is_dir($extractPath)) {
|
||||||
@ -140,31 +397,32 @@ class Backups extends BaseController
|
|||||||
throw new \RuntimeException("No se encontró ningún archivo .sql en el ZIP");
|
throw new \RuntimeException("No se encontró ningún archivo .sql en el ZIP");
|
||||||
}
|
}
|
||||||
|
|
||||||
$db = \Config\Database::connect();
|
$sqlFile = $sqlFiles[0];
|
||||||
$sql = file_get_contents($sqlFiles[0]);
|
|
||||||
$db->query('SET FOREIGN_KEY_CHECKS=0;');
|
$dbConfig = config('Database')->default;
|
||||||
$db->query($sql);
|
$host = $dbConfig['hostname'];
|
||||||
$db->query('SET FOREIGN_KEY_CHECKS=1;');
|
$username = $dbConfig['username'];
|
||||||
|
$password = $dbConfig['password'];
|
||||||
|
$database = $dbConfig['database'];
|
||||||
|
|
||||||
|
$cmd = "mysql -h {$host} -u{$username} -p'{$password}' {$database} < {$sqlFile}";
|
||||||
|
system($cmd, $retval);
|
||||||
|
|
||||||
|
if ($retval !== 0) {
|
||||||
|
throw new \RuntimeException("Error al restaurar la base de datos. Código: $retval");
|
||||||
|
}
|
||||||
|
|
||||||
array_map('unlink', glob($extractPath . '*'));
|
array_map('unlink', glob($extractPath . '*'));
|
||||||
rmdir($extractPath);
|
rmdir($extractPath);
|
||||||
|
|
||||||
return redirect()->to('/backups')->with('message', 'Backup restaurado correctamente.');
|
return redirect()->to(route_to('backupsList'))->with('message', 'Backup restaurado correctamente (vía sistema).');
|
||||||
} else {
|
} else {
|
||||||
throw new \RuntimeException("No se pudo abrir el archivo ZIP");
|
throw new \RuntimeException("No se pudo abrir el archivo ZIP");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteLocal($id)
|
|
||||||
{
|
|
||||||
$backup = $this->backupModel->find($id);
|
|
||||||
if ($backup && $backup['path_local'] && file_exists($backup['path_local'])) {
|
|
||||||
unlink($backup['path_local']);
|
|
||||||
$this->backupModel->update($id, ['path_local' => null]);
|
|
||||||
return redirect()->to(route_to('backupsList'))->with('message', 'Backup local eliminado.');
|
|
||||||
}
|
|
||||||
return redirect()->to(route_to('backupsList'))->with('error', 'Archivo no encontrado.');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function restoreRemote($filename)
|
public function restoreRemote($filename)
|
||||||
{
|
{
|
||||||
@ -178,9 +436,9 @@ class Backups extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parámetros SFTP
|
// Parámetros SFTP
|
||||||
$sftpHost = 'sftp.hidrive.ionos.com';
|
$sftpHost = getenv('HIDRIVE_HOST');
|
||||||
$sftpUser = 'erp2019';
|
$sftpUser = getenv('HIDRIVE_USER');
|
||||||
$sftpPass = 'Z2CjX7kd2h';
|
$sftpPass = getenv('HIDRIVE_PASS');
|
||||||
$remotePath = $backup['path_remote'];
|
$remotePath = $backup['path_remote'];
|
||||||
$localPath = WRITEPATH . 'backups/' . $filename;
|
$localPath = WRITEPATH . 'backups/' . $filename;
|
||||||
|
|
||||||
@ -216,9 +474,9 @@ class Backups extends BaseController
|
|||||||
|
|
||||||
private function sendToSFTP($localPath, $remoteFilename)
|
private function sendToSFTP($localPath, $remoteFilename)
|
||||||
{
|
{
|
||||||
$sftpHost = 'sftp.hidrive.ionos.com';
|
$sftpHost = getenv('HIDRIVE_HOST');
|
||||||
$sftpUser = 'erp2019';
|
$sftpUser = getenv('HIDRIVE_USER');
|
||||||
$sftpPass = 'Z2CjX7kd2h';
|
$sftpPass = getenv('HIDRIVE_PASS');
|
||||||
$remotePath = '/users/erp2019/backups_erp/' . $remoteFilename;
|
$remotePath = '/users/erp2019/backups_erp/' . $remoteFilename;
|
||||||
|
|
||||||
$sftp = new SFTP($sftpHost);
|
$sftp = new SFTP($sftpHost);
|
||||||
|
|||||||
@ -16,11 +16,11 @@ class CreateRestoresTable extends Migration
|
|||||||
]);
|
]);
|
||||||
$this->forge->addKey('id', true);
|
$this->forge->addKey('id', true);
|
||||||
$this->forge->addForeignKey('backup_id', 'backups', 'id', 'CASCADE', 'CASCADE');
|
$this->forge->addForeignKey('backup_id', 'backups', 'id', 'CASCADE', 'CASCADE');
|
||||||
$this->forge->createTable('restores');
|
$this->forge->createTable('backup_restores');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down()
|
public function down()
|
||||||
{
|
{
|
||||||
$this->forge->dropTable('restores');
|
$this->forge->dropTable('backup_restores');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,70 +1,111 @@
|
|||||||
<?= $this->include("themes/_commonPartialsBs/select2bs5") ?>
|
<?= $this->include("themes/_commonPartialsBs/select2bs5") ?>
|
||||||
<?= $this->include("themes/_commonPartialsBs/datatables") ?>
|
<?= $this->include("themes/_commonPartialsBs/datatables") ?>
|
||||||
|
<?= $this->include("themes/_commonPartialsBs/sweetalert") ?>
|
||||||
<?= $this->extend('themes/vuexy/main/defaultlayout') ?>
|
<?= $this->extend('themes/vuexy/main/defaultlayout') ?>
|
||||||
|
|
||||||
<?= $this->section('content'); ?>
|
<?= $this->section("content") ?>
|
||||||
<!--Content Body-->
|
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="col-md-12">
|
<div class="col-12">
|
||||||
<h4 class="mb-4">Backups disponibles</h4>
|
<div class="card card-info">
|
||||||
<a href="<?= route_to('backupsCreate') ?>" class="btn btn-primary mb-3">Crear backup</a>
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">Backups disponibles</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="card-body">
|
||||||
<table id="tablaBackups" class="table table-striped table-hover w-100">
|
<?= view("themes/_commonPartialsBs/_alertBoxes") ?>
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Archivo</th>
|
|
||||||
<th>Fecha</th>
|
|
||||||
<th>Tamaño</th>
|
|
||||||
<th>Local</th>
|
|
||||||
<th>Remoto</th>
|
|
||||||
<th>Acciones</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach ($backups as $b): ?>
|
|
||||||
<tr>
|
|
||||||
<td><?= esc($b['filename']) ?></td>
|
|
||||||
<td><?= esc($b['fecha']) ?></td>
|
|
||||||
<td><?= esc($b['tamano']) ?></td>
|
|
||||||
<td><span class="badge bg-<?= $b['local'] ? 'success' : 'secondary' ?>">
|
|
||||||
<?= $b['local'] ? 'Sí' : 'No' ?>
|
|
||||||
</span></td>
|
|
||||||
<td><span class="badge bg-<?= $b['remoto'] ? 'info' : 'secondary' ?>">
|
|
||||||
<?= $b['remoto'] ? 'Sí' : 'No' ?>
|
|
||||||
</span></td>
|
|
||||||
<td class="text-nowrap">
|
|
||||||
<?php if ($b['local']): ?>
|
|
||||||
<a href="<?= route_to('backupsRestoreLocal', $b['filename']) ?>"
|
|
||||||
class="btn btn-sm btn-warning">Restaurar Local</a>
|
|
||||||
<a href="<?= route_to('backupsDeleteLocal', $b['id']) ?>"
|
|
||||||
class="btn btn-sm btn-danger">Eliminar local</a>
|
|
||||||
<?php elseif ($b['remoto']): ?>
|
|
||||||
<a href="<?= route_to('backupsRestoreRemote', $b['filename']) ?>"
|
|
||||||
class="btn btn-sm btn-warning">Restaurar Remoto</a>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="text-muted">No disponible</span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
</div>
|
<?php if (getenv('SK_ENVIRONMENT') === 'production'): ?>
|
||||||
|
<a href="<?= route_to('backupsCreate') ?>" class="btn btn-primary mb-3">Crear backup</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<a href="<?= route_to('backupsCreateDev') ?>" class="btn btn-secondary mb-3">Crear backup desarrollo</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<?= session('message') ?>
|
<div class="table-responsive">
|
||||||
</div>
|
<table id="tablaBackups" class="table table-striped table-hover w-100">
|
||||||
</div>
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Archivo</th>
|
||||||
|
<th>Fecha</th>
|
||||||
|
<th>Tamaño</th>
|
||||||
|
<th>Local</th>
|
||||||
|
<th>Remoto</th>
|
||||||
|
<th>Acciones</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($backups as $b): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?= esc($b['filename']) ?></td>
|
||||||
|
<td><?= esc($b['fecha']) ?></td>
|
||||||
|
<td><?= esc($b['tamano']) ?></td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-<?= $b['local'] ? 'success' : 'secondary' ?>">
|
||||||
|
<?= $b['local'] ? 'Sí' : 'No' ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-<?= $b['remoto'] ? 'info' : 'secondary' ?>">
|
||||||
|
<?= $b['remoto'] ? 'Sí' : 'No' ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-nowrap">
|
||||||
|
<!-- Descargar siempre disponible -->
|
||||||
|
<a href="<?= route_to('backupsDownload', $b['filename']) ?>"
|
||||||
|
class="btn btn-sm btn-info" title="Descargar backup">
|
||||||
|
Descargar
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Restaurar y eliminar solo si local -->
|
||||||
|
<?php if ($b['local']): ?>
|
||||||
|
<?php if (getenv('SK_ENVIRONMENT') === 'production'): ?>
|
||||||
|
<a href="<?= route_to('backupsRestoreLocal', $b['filename']) ?>"
|
||||||
|
class="btn btn-sm btn-warning" title="Restaurar backup local">
|
||||||
|
Restaurar Local
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (getenv('SK_ENVIRONMENT') === 'production' && !empty($b['id'])): ?>
|
||||||
|
<a href="<?= route_to('backupsDeleteLocal', $b['id']) ?>"
|
||||||
|
class="btn btn-sm btn-danger"
|
||||||
|
onclick="return confirm('¿Seguro que deseas eliminar este backup?')"
|
||||||
|
title="Eliminar archivo local (producción)">
|
||||||
|
Eliminar [PROD]
|
||||||
|
</a>
|
||||||
|
<?php elseif (getenv('SK_ENVIRONMENT') === 'development' && !empty($b['filename'])): ?>
|
||||||
|
<a href="<?= route_to('backupsDeleteLocalDev', $b['filename']) ?>"
|
||||||
|
class="btn btn-sm btn-danger"
|
||||||
|
onclick="return confirm('¿Eliminar archivo del sistema de archivos local?')"
|
||||||
|
title="Eliminar archivo local (DEV)">
|
||||||
|
Eliminar [DEV]
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php elseif ($b['remoto']): ?>
|
||||||
|
<!-- Restaurar remoto si solo existe remoto -->
|
||||||
|
<a href="<?= route_to('backupsRestoreRemote', $b['filename']) ?>"
|
||||||
|
class="btn btn-sm btn-warning" title="Restaurar desde servidor remoto">
|
||||||
|
Restaurar Remoto
|
||||||
|
</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted">No disponible</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div><!-- /.card-body -->
|
||||||
|
</div><!-- /.card -->
|
||||||
|
</div><!-- /.col -->
|
||||||
|
</div><!-- /.row -->
|
||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
<?= $this->section('additionalInlineJs') ?>
|
<?= $this->section('additionalInlineJs') ?>
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
$('#tablaBackups').DataTable({
|
$('#tablaBackups').DataTable({
|
||||||
order: [[1, 'desc']],
|
order: [[1, 'desc']]
|
||||||
language: {
|
});
|
||||||
url: '/assets/vendor/datatables/i18n/es-ES.json' // ajusta si usas idioma español
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
Reference in New Issue
Block a user