Add comprehensive backup functionality and refactor email handling
Implemented server, database, and environment backups with configurable modules. Introduced new routes and configuration classes for backups alongside improved zip and S3 storage. Replaced PHPMailer with a cURL-based email service for streamlined message sending.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
<?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;
|
||||
@@ -10,15 +12,33 @@ class backup_store implements minio_backups_i
|
||||
{
|
||||
use minio_t;
|
||||
|
||||
const backup_s3_prefix = 'backups/'; // The prefix for the backup files in the S3 bucket
|
||||
const metadata_s3_prefix = 'metadata/'; // The prefix for the metadata files in the S3 bucket
|
||||
const local_backup_path = '/tmp/backups/'; // The path to store the backups locally
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
@@ -120,7 +140,13 @@ class backup_store implements minio_backups_i
|
||||
}
|
||||
|
||||
// Backup the database
|
||||
$database_path = $this->backupDatabase($backup_uuid);
|
||||
$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';
|
||||
@@ -138,6 +164,12 @@ class backup_store implements minio_backups_i
|
||||
// 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);
|
||||
|
||||
@@ -154,7 +186,7 @@ class backup_store implements minio_backups_i
|
||||
global /** @var db $db */
|
||||
$db;
|
||||
// Backup the database
|
||||
$path = self::local_backup_path . 'backup_' . $backup_uuid;
|
||||
$path = self::local_backup_path . 'backup_' . $backup_uuid . '/database.sql';
|
||||
$result = $db->backupDatabase($path);
|
||||
if (!$result) {
|
||||
throw new Exception('Failed to backup the database!');
|
||||
@@ -162,6 +194,28 @@ class backup_store implements minio_backups_i
|
||||
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
|
||||
*/
|
||||
@@ -202,6 +256,15 @@ class backup_store implements minio_backups_i
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,7 +283,11 @@ class backup_store implements minio_backups_i
|
||||
return [];
|
||||
}
|
||||
foreach ( $objects['Contents'] as $object ) {
|
||||
$backup_uuid = str_replace([self::metadata_s3_prefix, '.json'], '', $object['Key']);
|
||||
$backup_uuid = str_replace(
|
||||
self::metadata_s3_prefix . 'backup_',
|
||||
'',
|
||||
str_replace('.json', '', $object['Key'])
|
||||
);
|
||||
$backups[] = $this->getBackupMetadata($backup_uuid);
|
||||
}
|
||||
return $backups;
|
||||
|
||||
@@ -136,16 +136,9 @@ class db
|
||||
|
||||
public function backupDatabase(string $path): bool
|
||||
{
|
||||
// Check if the path is writable
|
||||
if (!is_writable($path)) {
|
||||
throw new Exception('Path is not writable or does not exist, unable to backup database! Path: ' . $path);
|
||||
}
|
||||
// Add the file name to the path
|
||||
$path .= '/database.sql';
|
||||
// Save the database to the path
|
||||
$command = "mysqldump -h {$this->host} -u {$this->user} -p '{$this->password}' '{$this->database}' > $path";
|
||||
$command = "mysqldump -h {$this->host} -u {$this->user} -p{$this->password} {$this->database} > $path";
|
||||
exec($command, $output, $return);
|
||||
echo $command;
|
||||
return $return === 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ require_once WD . '/modules/email/email_c.php';
|
||||
|
||||
use email\email_c;
|
||||
use interfaces\email_i;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
|
||||
class email implements email_i
|
||||
{
|
||||
@@ -16,32 +14,10 @@ class email implements email_i
|
||||
*/
|
||||
public email_c $config;
|
||||
|
||||
/**
|
||||
* PHPMailer
|
||||
* @var PHPMailer
|
||||
*/
|
||||
public PHPMailer $mailer;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->config = new email_c();
|
||||
$this->mailer = new PHPMailer(true);
|
||||
try {
|
||||
self::setupMailer();
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception('Email error: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function setupMailer(): void
|
||||
{
|
||||
$this->mailer->isSMTP();
|
||||
$this->mailer->SMTPAuth = true;
|
||||
$this->mailer->Host = $this->config->smtp_host->getVariableValue();
|
||||
$this->mailer->Username = $this->config->smtp_username->getVariableValue();
|
||||
$this->mailer->Password = $this->config->smtp_password->getVariableValue();
|
||||
$this->mailer->Port = $this->config->smtp_port->getVariableValue();
|
||||
$this->mailer->setFrom($this->config->smtp_from->getVariableValue(), $this->config->smtp_from_name->getVariableValue());
|
||||
}
|
||||
|
||||
public function testConfigRequest(string $test_recipient): string
|
||||
@@ -58,20 +34,28 @@ class email implements email_i
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function sendEmail($to, $subject, $message): void
|
||||
private function sendEmail($to, $subject, $message): void
|
||||
{
|
||||
if ($this->config->enabled->isTrue()) {
|
||||
try {
|
||||
$this->mailer->addAddress($to);
|
||||
$this->mailer->Subject = $subject;
|
||||
$this->mailer->Body = $message;
|
||||
$this->mailer->send();
|
||||
} catch (Exception $e) {
|
||||
throw new \Exception('Email error: ' . $e->getMessage());
|
||||
}
|
||||
// Send POST request to email service
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, 'https://maintenancemode.cloud/mailer.php');
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
// Add the data to the request
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, [
|
||||
'recipient' => $to,
|
||||
'subject' => $subject,
|
||||
'message' => $message,
|
||||
]);
|
||||
// Return the response instead of outputting it
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
// Execute the POST request
|
||||
$response = curl_exec($ch);
|
||||
// Close cURL resource
|
||||
curl_close($ch);
|
||||
// Check for errors
|
||||
if ($response === false) {
|
||||
throw new \Exception('Curl error: ' . curl_error($ch));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,6 +9,6 @@
|
||||
"ext-json": "*",
|
||||
"aws/aws-sdk-php": "^3.0",
|
||||
"predis/predis": "*",
|
||||
"phpmailer/phpmailer": "^6.9"
|
||||
"phpmailer/phpmailer": "^6.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+25
-26
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "ccdb2011a0cba46aac7345fbab9a674a",
|
||||
"content-hash": "6e466a76dd200da32e79f91ab209ca0e",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aws/aws-crt-php",
|
||||
@@ -62,16 +62,16 @@
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.337.0",
|
||||
"version": "3.339.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "7e40cecb3ce66749bbd5eaa9f370de48c16acd6c"
|
||||
"reference": "85b90f70f88739f4f363c1af7c230e294af3dbd0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7e40cecb3ce66749bbd5eaa9f370de48c16acd6c",
|
||||
"reference": "7e40cecb3ce66749bbd5eaa9f370de48c16acd6c",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/85b90f70f88739f4f363c1af7c230e294af3dbd0",
|
||||
"reference": "85b90f70f88739f4f363c1af7c230e294af3dbd0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -79,31 +79,30 @@
|
||||
"ext-json": "*",
|
||||
"ext-pcre": "*",
|
||||
"ext-simplexml": "*",
|
||||
"guzzlehttp/guzzle": "^6.5.8 || ^7.4.5",
|
||||
"guzzlehttp/promises": "^1.4.0 || ^2.0",
|
||||
"guzzlehttp/psr7": "^1.9.1 || ^2.4.5",
|
||||
"mtdowling/jmespath.php": "^2.6",
|
||||
"php": ">=7.2.5",
|
||||
"psr/http-message": "^1.0 || ^2.0"
|
||||
"guzzlehttp/guzzle": "^7.4.5",
|
||||
"guzzlehttp/promises": "^2.0",
|
||||
"guzzlehttp/psr7": "^2.4.5",
|
||||
"mtdowling/jmespath.php": "^2.8.0",
|
||||
"php": ">=8.1",
|
||||
"psr/http-message": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"andrewsville/php-token-reflection": "^1.4",
|
||||
"aws/aws-php-sns-message-validator": "~1.0",
|
||||
"behat/behat": "~3.0",
|
||||
"composer/composer": "^1.10.22",
|
||||
"composer/composer": "^2.7.8",
|
||||
"dms/phpunit-arraysubset-asserts": "^0.4.0",
|
||||
"doctrine/cache": "~1.4",
|
||||
"ext-dom": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-pcntl": "*",
|
||||
"ext-sockets": "*",
|
||||
"nette/neon": "^2.3",
|
||||
"paragonie/random_compat": ">= 2",
|
||||
"phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5",
|
||||
"psr/cache": "^1.0 || ^2.0 || ^3.0",
|
||||
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0",
|
||||
"sebastian/comparator": "^1.2.3 || ^4.0",
|
||||
"yoast/phpunit-polyfills": "^1.0"
|
||||
"psr/cache": "^2.0 || ^3.0",
|
||||
"psr/simple-cache": "^2.0 || ^3.0",
|
||||
"sebastian/comparator": "^1.2.3 || ^4.0 || ^5.0",
|
||||
"symfony/filesystem": "^v6.4.0 || ^v7.1.0",
|
||||
"yoast/phpunit-polyfills": "^2.0"
|
||||
},
|
||||
"suggest": {
|
||||
"aws/aws-php-sns-message-validator": "To validate incoming SNS notifications",
|
||||
@@ -152,11 +151,11 @@
|
||||
"sdk"
|
||||
],
|
||||
"support": {
|
||||
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
||||
"forum": "https://github.com/aws/aws-sdk-php/discussions",
|
||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.337.0"
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.339.10"
|
||||
},
|
||||
"time": "2025-01-15T19:58:52+00:00"
|
||||
"time": "2025-02-10T19:24:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
@@ -1046,16 +1045,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.12.15",
|
||||
"version": "1.12.17",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "c91d4e8bc056f46cf653656e6f71004b254574d1"
|
||||
"reference": "7027b3b0270bf392de0cfba12825979768d728bf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/c91d4e8bc056f46cf653656e6f71004b254574d1",
|
||||
"reference": "c91d4e8bc056f46cf653656e6f71004b254574d1",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/7027b3b0270bf392de0cfba12825979768d728bf",
|
||||
"reference": "7027b3b0270bf392de0cfba12825979768d728bf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1100,7 +1099,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-01-05T16:40:22+00:00"
|
||||
"time": "2025-02-07T15:01:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "rector/rector",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php global $CONFIG_DB, $DEBUG, $ENCRYPTION_KEY, $CORS, $ECONOMIC_API, $WORDPRESS_STATIC_TOKEN, $EMAIL_WASH_CERTIFICATE_TOKEN, $MINIO, $REDIS_CONFIG, $WORDPRESS_API_URL, $SLACK_DEFAULT_WEBHOOK;
|
||||
<?php global $CONFIG_DB, $DEBUG, $ENCRYPTION_KEY, $CORS, $ECONOMIC_API, $WORDPRESS_STATIC_TOKEN, $EMAIL_WASH_CERTIFICATE_TOKEN, $MINIO, $REDIS_CONFIG, $WORDPRESS_API_URL, $SLACK_DEFAULT_WEBHOOK, $ALL_CONFIG;
|
||||
|
||||
/**
|
||||
* Check if the environment variables are set (If we are running in a Docker container)
|
||||
@@ -92,6 +92,9 @@ if (isset($_ENV['USE_ENV']) && $_ENV['USE_ENV'] === 'true') {
|
||||
// Set the timezone
|
||||
date_default_timezone_set($_ENV['CONFIG_TIMEZONE']) ?? 'Europe/Copenhagen';
|
||||
|
||||
// Set the all config
|
||||
$ALL_CONFIG = $_ENV; // This is used by the backup job.
|
||||
|
||||
} else {
|
||||
// Throw an error if the environment variables are not set
|
||||
throw new Exception('Environment variables are not set');
|
||||
|
||||
@@ -84,4 +84,18 @@ interface minio_backups_i
|
||||
*/
|
||||
public function validateBucket(): void;
|
||||
|
||||
/**
|
||||
* Backup server files, this is a helper function for generateBackupFile
|
||||
* @param string $backup_uuid
|
||||
* @return string The path to the backup file
|
||||
*/
|
||||
public function backupServerFiles(string $backup_uuid): string;
|
||||
|
||||
/**
|
||||
* Backup the environment variables, this is a helper function for generateBackupFile
|
||||
* @param string $backup_uuid
|
||||
* @return string The path to the backup file
|
||||
*/
|
||||
public function backupEnvironmentVariables(string $backup_uuid): string;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace backups;
|
||||
require_once WD . '/modules/backups/config/backups_enabled_c.php';
|
||||
|
||||
use backups\config\backups_enabled_c;
|
||||
use traits\module_config_t;
|
||||
|
||||
class backups_c
|
||||
{
|
||||
use module_config_t;
|
||||
|
||||
/**
|
||||
* The status of Backups, whether it is enabled or not
|
||||
* @var backups_enabled_c
|
||||
*/
|
||||
public backups_enabled_c $enabled;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setupConfig('Backups');
|
||||
$this->allowUpdate([
|
||||
backups_enabled_c::class,
|
||||
]);
|
||||
$this->enabled = new backups_enabled_c();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace backups\config;
|
||||
|
||||
use Exception;
|
||||
use traits\module_config_variable;
|
||||
|
||||
class backups_enabled_c
|
||||
{
|
||||
use module_config_variable;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
self::setupConfigVariable(
|
||||
'Backups',
|
||||
'enabled',
|
||||
'bool',
|
||||
true,
|
||||
null,
|
||||
'Whether the backup module is enabled or not',
|
||||
'1',
|
||||
false,
|
||||
'false'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ class moduleBackupsRoute
|
||||
|
||||
|
||||
/** Modules > Backups > GET */
|
||||
$this->get('/modules/backup', function () {
|
||||
$this->get('/modules/backup/backups', function () {
|
||||
global $response;
|
||||
$this->requirePermission('modules_backup_list');
|
||||
$user = (new authentication())->get_user();
|
||||
@@ -37,7 +37,7 @@ class moduleBackupsRoute
|
||||
});
|
||||
|
||||
/** Modules > Backups > POST */
|
||||
$this->post('/modules/backup', function () {
|
||||
$this->post('/modules/backup/backups', function () {
|
||||
global $response;
|
||||
$this->requirePermission('modules_backup_create');
|
||||
$user = (new authentication())->get_user();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace routes;
|
||||
|
||||
use classes\authentication;
|
||||
use classes\backup_store;
|
||||
use classes\economic;
|
||||
use classes\email;
|
||||
use classes\recaptcha;
|
||||
@@ -145,5 +146,35 @@ class moduleConfigRoute
|
||||
}
|
||||
});
|
||||
|
||||
$this->get('/backups/config', function () {
|
||||
global $response;
|
||||
$this->requirePermission('backups_config');
|
||||
$user = (new authentication())->get_user();
|
||||
if ($user) {
|
||||
(new logs_o())->add('backups_config', 'global', 1, $user->id, 'BACKUPS_CONFIG', 'Successfully fetched backups config');
|
||||
$response->success(
|
||||
(new backup_store())->config->getConfigRequest()
|
||||
);
|
||||
} else {
|
||||
(new logs_o())->add('backups_config', 'global', 1, 0, 'BACKUPS_CONFIG', 'No user found, or invalid session');
|
||||
$response->error('Invalid session', 400);
|
||||
}
|
||||
});
|
||||
|
||||
$this->post('/backups/config', function () {
|
||||
global $response;
|
||||
$this->requirePermission('backups_config');
|
||||
$user = (new authentication())->get_user();
|
||||
if ($user) {
|
||||
(new logs_o())->add('backups_config', 'global', 1, $user->id, 'BACKUPS_CONFIG', 'Successfully updated backups config');
|
||||
$response->success(
|
||||
(new backup_store())->config->postConfigRequest()
|
||||
);
|
||||
} else {
|
||||
(new logs_o())->add('backups_config', 'global', 1, 0, 'BACKUPS_CONFIG', 'No user found, or invalid session');
|
||||
$response->error('Invalid session', 400);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user