Files
api/services/nginx/app/classes/backup_store.php
T
Jeppe Bundgaard 4778486dac Add SSL mode support for database backups and enhance directory creation checks
- 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.
2026-02-02 14:24:28 +01:00

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);
}
}