- Introduce SSL mode configuration (`ssl_mode`) for `mysqldump` to support secure database backups. - Update database backup logic to handle SSL options (`DISABLED`, `PREFERRED`, `REQUIRED`, `VERIFY_CA`, `VERIFY_IDENTITY`). - Improve directory creation checks in backup logic with additional error handling. - Update `config.example.php` to include `ssl_mode` configuration.
314 lines
10 KiB
PHP
314 lines
10 KiB
PHP
<?php
|
|
|
|
namespace classes;
|
|
require_once WD . '/modules/backups/backups_c.php';
|
|
|
|
use backups\backups_c;
|
|
use Exception;
|
|
use interfaces\minio_backups_i;
|
|
use traits\minio_t;
|
|
|
|
class backup_store implements minio_backups_i
|
|
{
|
|
use minio_t;
|
|
|
|
/**
|
|
* The prefix for the backup files in the S3 bucket
|
|
* @var string
|
|
*/
|
|
const backup_s3_prefix = 'backups/';
|
|
/**
|
|
* The prefix for the metadata files in the S3 bucket
|
|
* @var string
|
|
*/
|
|
const metadata_s3_prefix = 'metadata/'; // The prefix for the backup files in the S3 bucket
|
|
/**
|
|
* The path to store the backups locally
|
|
* @var string
|
|
*/
|
|
const local_backup_path = '/tmp/backups/'; // The prefix for the metadata files in the S3 bucket
|
|
/**
|
|
* The configuration for the backup store
|
|
* @var backups_c
|
|
*/
|
|
public backups_c $config;
|
|
|
|
/**
|
|
* @throws Exception If the bucket does not exist, is not accessible, or is not configured.
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$this->config = new backups_c();
|
|
self::setBucket('backups'); // Change the bucket to backups
|
|
self::validateBucket();
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function validateBucket(): void
|
|
{
|
|
// Check if the bucket exists
|
|
if (!self::getS3Client()->doesBucketExist(self::getBucket())) {
|
|
// throw an exception
|
|
throw new Exception('The bucket does not exist in the S3 service! Missing: ' . self::getBucket());
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function backup_exists(string $backup_uuid): bool
|
|
{
|
|
// Check if the file exists
|
|
$objects = self::getS3Client()->listObjects([
|
|
'Bucket' => self::getBucket(),
|
|
'Prefix' => self::backup_s3_prefix . 'backup_' . $backup_uuid . '.zip'
|
|
]);
|
|
return count($objects['Contents'] ?? []) > 0;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function getBackupDownloadUrl(string $backup_uuid): string
|
|
{
|
|
// Get the presigned URL for the backup
|
|
return self::getPresignedUrl('backup_' . $backup_uuid . '.zip');
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function download(string $backup_uuid): string
|
|
{
|
|
// Download the backup
|
|
$path = self::local_backup_path . 'backup_' . $backup_uuid . '.zip';
|
|
$result = self::getS3Client()->getObject([
|
|
'Bucket' => self::getBucket(),
|
|
'Key' => self::backup_s3_prefix . 'backup_' . $backup_uuid . '.zip',
|
|
'SaveAs' => $path
|
|
]);
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
* @throws Exception
|
|
*/
|
|
public function createBackup($backup_name = null, $backup_description = null): string
|
|
{
|
|
// Check if the backup name is set
|
|
if (!$backup_name) {
|
|
$backup_name = 'Backup ' . date('Y-m-d H:i:s');
|
|
}
|
|
// Check if the backup description is set
|
|
if (!$backup_description) {
|
|
$backup_description = 'Backup created on ' . date('Y-m-d H:i:s');
|
|
}
|
|
// Generate the backup file
|
|
$backup_uuid = self::generateBackupFile();
|
|
// Upload the backup file
|
|
self::getS3Client()->putObject([
|
|
'Bucket' => self::getBucket(),
|
|
'Key' => self::backup_s3_prefix . 'backup_' . $backup_uuid . '.zip',
|
|
'SourceFile' => self::local_backup_path . 'backup_' . $backup_uuid . '.zip'
|
|
]);
|
|
// Update the metadata
|
|
self::createBackupMetadata($backup_uuid, $backup_name, $backup_description);
|
|
// Clean up the backup file
|
|
self::cleanUpBackupFile($backup_uuid);
|
|
// Return the backup UUID
|
|
return $backup_uuid;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function generateBackupFile(): string
|
|
{
|
|
// Generate a UUID for the backup
|
|
$backup_uuid = uniqid();
|
|
|
|
// Prepare the backup directories
|
|
if (!is_dir(self::local_backup_path)) {
|
|
mkdir(self::local_backup_path);
|
|
if (!is_dir(self::local_backup_path)) {
|
|
throw new Exception('Failed to create local backup path: ' . self::local_backup_path);
|
|
}
|
|
}
|
|
if (!is_dir(self::local_backup_path . 'backup_' . $backup_uuid)) {
|
|
mkdir(self::local_backup_path . 'backup_' . $backup_uuid);
|
|
if (!is_dir(self::local_backup_path . 'backup_' . $backup_uuid)) {
|
|
throw new Exception('Failed to create local backup directory: ' . self::local_backup_path . 'backup_' . $backup_uuid);
|
|
}
|
|
}
|
|
|
|
// Backup the database
|
|
$database_path = self::backupDatabase($backup_uuid);
|
|
|
|
// Web server files
|
|
$webserver_files_path = self::backupServerFiles($backup_uuid);
|
|
|
|
// Environment variables
|
|
$env_path = self::backupEnvironmentVariables($backup_uuid);
|
|
|
|
// Create the backup path
|
|
$backup_path = self::local_backup_path . 'backup_' . $backup_uuid . '.zip';
|
|
|
|
// Create the backups directory
|
|
if (!is_dir(self::local_backup_path)) {
|
|
mkdir(self::local_backup_path);
|
|
}
|
|
|
|
// Create the backup directory
|
|
if (!is_dir(self::local_backup_path . 'backup_' . $backup_uuid)) {
|
|
mkdir(self::local_backup_path . 'backup_' . $backup_uuid);
|
|
}
|
|
|
|
// Add the database backup to the backup directory
|
|
copy($database_path, self::local_backup_path . 'backup_' . $backup_uuid . '/database.sql');
|
|
|
|
// Add the web server files to the backup directory
|
|
copy($webserver_files_path, self::local_backup_path . 'backup_' . $backup_uuid . '/www.zip');
|
|
|
|
// Add the environment variables to the backup directory
|
|
copy($env_path, self::local_backup_path . 'backup_' . $backup_uuid . '/environment_variables.json');
|
|
|
|
// Zip the backup directory
|
|
exec('zip -r ' . $backup_path . ' ' . self::local_backup_path . 'backup_' . $backup_uuid);
|
|
|
|
// Return the backup UUID
|
|
return $backup_uuid;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
* @throws Exception
|
|
*/
|
|
public function backupDatabase(string $backup_uuid): string
|
|
{
|
|
global /** @var db $db */
|
|
$db;
|
|
// Backup the database
|
|
$path = self::local_backup_path . 'backup_' . $backup_uuid . '/database.sql';
|
|
$result = $db->backupDatabase($path);
|
|
if (!$result) {
|
|
throw new Exception('Failed to backup the database!');
|
|
}
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function backupServerFiles(string $backup_uuid): string
|
|
{
|
|
// Backup the server files from the /var/www/html directory
|
|
$path = self::local_backup_path . 'backup_' . $backup_uuid . '/www.zip';
|
|
exec('zip -r ' . $path . ' /var/www/html');
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function backupEnvironmentVariables(string $backup_uuid): string
|
|
{
|
|
// Backup the config variables
|
|
$path = self::local_backup_path . 'backup_' . $backup_uuid . '/environment_variables.json';
|
|
file_put_contents($path, json_encode($_ENV));
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function createBackupMetadata(string $backup_uuid, string $backup_name, string $backup_description): void
|
|
{
|
|
// Create the metadata file
|
|
$metadata = [
|
|
'backup_name' => $backup_name,
|
|
'backup_description' => $backup_description,
|
|
'backup_date' => date('Y-m-d H:i:s'),
|
|
'backup_size' => filesize(self::local_backup_path . 'backup_' . $backup_uuid . '.zip'),
|
|
'hash' => md5_file(self::local_backup_path . 'backup_' . $backup_uuid . '.zip')
|
|
];
|
|
file_put_contents(
|
|
self::local_backup_path . 'backup_' . $backup_uuid . '.json',
|
|
json_encode($metadata)
|
|
);
|
|
// Upload the metadata file
|
|
self::getS3Client()->putObject([
|
|
'Bucket' => self::getBucket(),
|
|
'Key' => self::metadata_s3_prefix . 'backup_' . $backup_uuid . '.json',
|
|
'SourceFile' => self::local_backup_path . 'backup_' . $backup_uuid . '.json'
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function cleanupBackupFile(string $backup_uuid): void
|
|
{
|
|
/** Clean up the backup files, and remove the temporary directories and files */
|
|
|
|
// Remove the backup directory
|
|
unlink(self::local_backup_path . 'backup_' . $backup_uuid . '.zip');
|
|
|
|
// Remove the metadata file
|
|
unlink(self::local_backup_path . 'backup_' . $backup_uuid . '.json');
|
|
|
|
// Remove the database backup
|
|
unlink(self::local_backup_path . 'backup_' . $backup_uuid . '/database.sql');
|
|
|
|
// Remove the web server files backup
|
|
unlink(self::local_backup_path . 'backup_' . $backup_uuid . '/www.zip');
|
|
|
|
// Remove the environment variables backup
|
|
unlink(self::local_backup_path . 'backup_' . $backup_uuid . '/environment_variables.json');
|
|
|
|
// Remove the backup directory
|
|
rmdir(self::local_backup_path . 'backup_' . $backup_uuid);
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function listBackups(): array
|
|
{
|
|
// List the backups
|
|
$backups = [];
|
|
$objects = self::getS3Client()->listObjects([
|
|
'Bucket' => self::getBucket(),
|
|
'Prefix' => self::metadata_s3_prefix
|
|
]);
|
|
// Check if there are any backups
|
|
if (!isset($objects['Contents'])) {
|
|
return [];
|
|
}
|
|
foreach ( $objects['Contents'] as $object ) {
|
|
$backup_uuid = str_replace(
|
|
self::metadata_s3_prefix . 'backup_',
|
|
'',
|
|
str_replace('.json', '', $object['Key'])
|
|
);
|
|
$backups[] = $this->getBackupMetadata($backup_uuid);
|
|
}
|
|
return $backups;
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function getBackupMetadata(string $backup_uuid): array
|
|
{
|
|
// Get the metadata file
|
|
$metadata = self::getS3Client()->getObject([
|
|
'Bucket' => self::getBucket(),
|
|
'Key' => self::metadata_s3_prefix . 'backup_' . $backup_uuid . '.json'
|
|
]);
|
|
return json_decode($metadata['Body'], true);
|
|
}
|
|
} |