mirror of
https://git.imnavajas.es/jjimenez/safekat.git
synced 2025-07-25 22:52:08 +00:00
540 lines
19 KiB
PHP
540 lines
19 KiB
PHP
<?php
|
|
namespace App\Controllers\Sistema;
|
|
|
|
use App\Controllers\BaseController;
|
|
use App\Models\Sistema\BackupModel;
|
|
use phpseclib3\Net\SFTP;
|
|
use ZipArchive;
|
|
|
|
/*
|
|
erp-backup
|
|
5#xJ)6nENB!F~x^
|
|
*/
|
|
|
|
|
|
class Backups extends BaseController
|
|
{
|
|
protected $backupModel;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->backupModel = new BackupModel();
|
|
}
|
|
|
|
public function index()
|
|
{
|
|
helper('filesystem');
|
|
|
|
$entorno = getenv('SK_ENVIRONMENT');
|
|
$backups = [];
|
|
|
|
if ($entorno === 'development') {
|
|
// Leer archivos directamente del disco en entorno de desarrollo
|
|
$backups = [];
|
|
|
|
// === 1. Backups locales ===
|
|
$localDir = WRITEPATH . 'backups/';
|
|
$localFiles = get_filenames($localDir);
|
|
|
|
foreach ($localFiles as $file) {
|
|
if (!str_ends_with($file, '.zip') || !str_starts_with($file, 'backup_dev_')) {
|
|
continue;
|
|
}
|
|
|
|
$localPath = $localDir . $file;
|
|
|
|
$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';
|
|
|
|
$backups[$file] = [
|
|
'id' => null,
|
|
'filename' => $file,
|
|
'fecha' => $fecha,
|
|
'tamano' => $tamanoFmt,
|
|
'local' => true,
|
|
'remoto' => false,
|
|
];
|
|
}
|
|
|
|
// === 2. Backups remotos en SFTP ===
|
|
$sftpHost = getenv('HIDRIVE_HOST');
|
|
$sftpUser = getenv('HIDRIVE_USER');
|
|
$sftpPass = getenv('HIDRIVE_PASS');
|
|
$remoteDir = '/users/erp2019/backups_erp/';
|
|
|
|
$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]);
|
|
}
|
|
|
|
public function create()
|
|
{
|
|
if (getenv('SK_ENVIRONMENT') !== 'production') {
|
|
return redirect()->to(route_to('backupsList'))->with('error', 'No permitido en entorno de desarrollo.');
|
|
}
|
|
|
|
helper('filesystem');
|
|
|
|
$timestamp = date('Ymd_His');
|
|
$sqlFilename = "backup_{$timestamp}.sql";
|
|
$zipFilename = "backup_{$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" . escapeshellarg($username) . " -p'" . addslashes($password) . "' {$database} > {$sqlPath}";
|
|
|
|
system($command, $retval);
|
|
|
|
if ($retval !== 0) {
|
|
throw new \RuntimeException("Error al crear el backup.");
|
|
}
|
|
|
|
// Crear el zip
|
|
$zip = new ZipArchive();
|
|
if ($zip->open($zipPath, ZipArchive::CREATE) === TRUE) {
|
|
$zip->addFile($sqlPath, $sqlFilename);
|
|
$zip->close();
|
|
unlink($sqlPath); // eliminar el .sql original
|
|
} else {
|
|
throw new \RuntimeException("Error al comprimir el backup.");
|
|
}
|
|
|
|
// Insertar en BD
|
|
$backupId = $this->backupModel->insert([
|
|
'filename' => $zipFilename,
|
|
'type' => 'manual',
|
|
'path_local' => $zipPath,
|
|
'path_remote' => null,
|
|
'size' => filesize($zipPath),
|
|
'status' => 'pendiente',
|
|
'created_at' => date('Y-m-d H:i:s')
|
|
], true);
|
|
|
|
// Enviar a SFTP
|
|
$this->sendToSFTP($zipPath, $zipFilename);
|
|
|
|
// Actualizar BD
|
|
$remotePath = "/users/erp2019/backups_erp/" . $zipFilename;
|
|
$this->backupModel->update($backupId, [
|
|
'path_remote' => $remotePath,
|
|
'status' => 'subido'
|
|
]);
|
|
|
|
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)
|
|
{
|
|
$path = WRITEPATH . 'backups/' . $file;
|
|
|
|
if (!file_exists($path)) {
|
|
throw new \CodeIgniter\Exceptions\PageNotFoundException("Backup no encontrado.");
|
|
}
|
|
|
|
$zip = new \ZipArchive();
|
|
if ($zip->open($path) === TRUE) {
|
|
$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) {
|
|
throw new \RuntimeException("No se encontró ningún archivo .sql en el ZIP");
|
|
}
|
|
|
|
$sqlFile = $sqlFiles[0];
|
|
|
|
// === Verificar que el archivo SQL existe y tiene contenido
|
|
if (!file_exists($sqlFile)) {
|
|
throw new \RuntimeException("Archivo SQL no encontrado.");
|
|
}
|
|
|
|
if (filesize($sqlFile) === 0) {
|
|
throw new \RuntimeException("El archivo SQL está vacío.");
|
|
}
|
|
|
|
// === Configuración de base de datos
|
|
$dbConfig = config('Database')->default;
|
|
$host = escapeshellarg($dbConfig['hostname']);
|
|
$username = escapeshellarg($dbConfig['username']);
|
|
$password = escapeshellarg($dbConfig['password']);
|
|
$database = escapeshellarg($dbConfig['database']);
|
|
|
|
// === Construcción del comando con stderr redirigido
|
|
$cmd = "mysql -h $host -u $username -p$password $database -e \"source $sqlFile\" 2>&1";
|
|
|
|
// === Ejecutar y capturar la salida
|
|
exec($cmd, $output, $retval);
|
|
|
|
// === Verificar resultado
|
|
if ($retval !== 0) {
|
|
throw new \RuntimeException("Error al restaurar la base de datos:\n" . implode("\n", $output));
|
|
}
|
|
|
|
// === Limpieza
|
|
helper('filesystem');
|
|
delete_files($extractPath, true); // elimina contenido
|
|
rmdir($extractPath); // elimina el directorio
|
|
|
|
return redirect()->to(route_to('backupsList'))->with('message', 'Backup restaurado correctamente (vía sistema).');
|
|
} else {
|
|
throw new \RuntimeException("No se pudo abrir el archivo ZIP");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function restoreRemote($filename)
|
|
{
|
|
helper('filesystem');
|
|
|
|
$entorno = getenv('SK_ENVIRONMENT');
|
|
|
|
if ($entorno === 'development') {
|
|
// Construir ruta remota directamente
|
|
$remotePath = '/users/erp2019/backups_erp/' . $filename;
|
|
$localPath = WRITEPATH . 'backups/' . $filename;
|
|
|
|
$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 autenticar en el servidor SFTP.');
|
|
}
|
|
|
|
$fileContents = $sftp->get($remotePath);
|
|
if ($fileContents === false) {
|
|
return redirect()->to(route_to('backupsList'))->with('error', 'No se pudo descargar el archivo remoto.');
|
|
}
|
|
|
|
if (write_file($localPath, $fileContents) === false) {
|
|
return redirect()->to(route_to('backupsList'))->with('error', 'No se pudo guardar el archivo localmente.');
|
|
}
|
|
|
|
// Restaurar directamente
|
|
return $this->restoreLocal($filename);
|
|
}
|
|
|
|
// Producción: flujo normal con base de datos
|
|
$backup = $this->backupModel->where('filename', $filename)->first();
|
|
|
|
if (!$backup || empty($backup['path_remote'])) {
|
|
return redirect()->to(route_to('backupsList'))->with('error', 'Backup remoto no encontrado en la base de datos.');
|
|
}
|
|
|
|
$remotePath = $backup['path_remote'];
|
|
$localPath = WRITEPATH . 'backups/' . $filename;
|
|
|
|
$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 autenticar en el servidor SFTP.');
|
|
}
|
|
|
|
$fileContents = $sftp->get($remotePath);
|
|
if ($fileContents === false) {
|
|
return redirect()->to(route_to('backupsList'))->with('error', 'No se pudo descargar el archivo remoto.');
|
|
}
|
|
|
|
if (write_file($localPath, $fileContents) === false) {
|
|
return redirect()->to(route_to('backupsList'))->with('error', 'No se pudo guardar el archivo localmente.');
|
|
}
|
|
|
|
$this->backupModel->update($backup['id'], ['path_local' => $localPath]);
|
|
|
|
return $this->restoreLocal($filename);
|
|
}
|
|
|
|
|
|
|
|
|
|
private function sendToSFTP($localPath, $remoteFilename)
|
|
{
|
|
$sftpHost = getenv('HIDRIVE_HOST');
|
|
$sftpUser = getenv('HIDRIVE_USER');
|
|
$sftpPass = getenv('HIDRIVE_PASS');
|
|
$remotePath = '/users/erp2019/backups_erp/' . $remoteFilename;
|
|
|
|
$sftp = new SFTP($sftpHost);
|
|
|
|
if (!$sftp->login($sftpUser, $sftpPass)) {
|
|
throw new \RuntimeException('Error de autenticación SFTP');
|
|
}
|
|
|
|
$fileContents = file_get_contents($localPath);
|
|
|
|
if (!$sftp->put($remotePath, $fileContents)) {
|
|
throw new \RuntimeException("No se pudo subir el backup al servidor SFTP.");
|
|
}
|
|
}
|
|
}
|