work on backup storages
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
This commit is contained in:
@@ -73,8 +73,8 @@ class BackupStorages extends ApiCommand implements ResourceEntity
|
||||
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list backup storages");
|
||||
$query_fields = [];
|
||||
$result_stmt = Database::prepare("
|
||||
SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "`
|
||||
");
|
||||
SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` ". $this->getSearchWhere($query_fields) . $this->getOrderBy() . $this->getLimit()
|
||||
);
|
||||
Database::pexecute($result_stmt, $query_fields, true, true);
|
||||
$result = [];
|
||||
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
|
||||
@@ -95,7 +95,8 @@ class Backups extends ApiCommand implements ResourceEntity
|
||||
FROM `" . TABLE_PANEL_BACKUPS . "` `b`
|
||||
LEFT JOIN `" . TABLE_PANEL_ADMINS . "` `a` USING(`adminid`)
|
||||
WHERE `b`.`customerid` IN (" . implode(', ', $customer_ids) . ")
|
||||
");
|
||||
" . $this->getSearchWhere($query_fields, true) . $this->getOrderBy() . $this->getLimit()
|
||||
);
|
||||
Database::pexecute($result_stmt, $query_fields, true, true);
|
||||
$result = [];
|
||||
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
|
||||
53
lib/Froxlor/Backup/Backup.php
Normal file
53
lib/Froxlor/Backup/Backup.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Froxlor project.
|
||||
* Copyright (c) 2010 the Froxlor Team (see authors).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, you can also view it online at
|
||||
* https://files.froxlor.org/misc/COPYING.txt
|
||||
*
|
||||
* @copyright the authors
|
||||
* @author Froxlor team <team@froxlor.org>
|
||||
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
|
||||
*/
|
||||
|
||||
namespace Froxlor\Backup;
|
||||
|
||||
use Froxlor\Database\Database;
|
||||
use PDO;
|
||||
|
||||
class Backup
|
||||
{
|
||||
/**
|
||||
* returns an array of existing backup-storages
|
||||
* in our database for the settings-array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getBackupStorages(): array
|
||||
{
|
||||
$storages_array = [
|
||||
0 => lng('backup.storage_none')
|
||||
];
|
||||
// get all storages
|
||||
$result_stmt = Database::query("SELECT id, type, description FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` ORDER BY type, description");
|
||||
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
if (!isset($storages_array[$row['id']]) && !in_array($row['id'], $storages_array)) {
|
||||
$storages_array[$row['id']] = "[" . $row['type'] . "] " . html_entity_decode($row['description']);
|
||||
}
|
||||
}
|
||||
return $storages_array;
|
||||
}
|
||||
}
|
||||
98
lib/Froxlor/Backup/Storages/Ftp.php
Normal file
98
lib/Froxlor/Backup/Storages/Ftp.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Froxlor\Backup\Storages;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\FileDir;
|
||||
|
||||
class Ftp extends Storage
|
||||
{
|
||||
private $ftp_conn = null;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public function init(): bool
|
||||
{
|
||||
$hostname = $this->sData['storage']['hostname'] ?? '';
|
||||
$username = $this->sData['storage']['username'] ?? '';
|
||||
$password = $this->sData['storage']['password'] ?? '';
|
||||
if (!empty($hostname) && !empty($username) && !empty($password)) {
|
||||
$tmp = explode(":", $hostname);
|
||||
$hostname = $tmp[0];
|
||||
$port = $tmp[1] ?? 21;
|
||||
$this->ftp_conn = ftp_connect($hostname, $port);
|
||||
if ($this->ftp_conn === false) {
|
||||
throw new Exception('Unable to connect to ftp-server "' . $hostname . ':' . $port . '"');
|
||||
}
|
||||
if (!ftp_login($this->ftp_conn, $username, $password)) {
|
||||
throw new Exception('Unable to login to ftp-server "' . $hostname . ':' . $port . '"');
|
||||
}
|
||||
return $this->changeToCorrectDirectory();
|
||||
}
|
||||
throw new Exception('Empty hostname for FTP backup storage');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param string $tmp_source_directory
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function putFile(string $filename, string $tmp_source_directory): bool
|
||||
{
|
||||
$source = FileDir::makeCorrectFile($tmp_source_directory . "/" . $filename);
|
||||
$target = basename($filename);
|
||||
if (file_exists($source) && !file_exists($target)) {
|
||||
return ftp_put($this->ftp_conn, $target, $source, FTP_BINARY);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function rmFile(string $filename): bool
|
||||
{
|
||||
$target = basename($filename);
|
||||
if (ftp_size($this->ftp_conn, $target) >= 0) {
|
||||
return ftp_delete($this->ftp_conn, $target);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function shutdown(): bool
|
||||
{
|
||||
return ftp_close($this->ftp_conn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
private function changeToCorrectDirectory(): bool
|
||||
{
|
||||
$dirs = explode("/", $this->getDestinationDirectory());
|
||||
array_shift($dirs);
|
||||
if (count($dirs) > 0 && !empty($dirs[0])) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (empty($dir)) {
|
||||
continue;
|
||||
}
|
||||
if (!@ftp_chdir($this->ftp_conn, $dir)) {
|
||||
ftp_mkdir($this->ftp_conn, $dir);
|
||||
ftp_chmod($this->ftp_conn, 0700, $dir);
|
||||
ftp_chdir($this->ftp_conn, $dir);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return ftp_chdir($this->ftp_conn, "/");
|
||||
}
|
||||
}
|
||||
60
lib/Froxlor/Backup/Storages/Local.php
Normal file
60
lib/Froxlor/Backup/Storages/Local.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Froxlor\Backup\Storages;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\FileDir;
|
||||
|
||||
class Local extends Storage
|
||||
{
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function init(): bool
|
||||
{
|
||||
// create destination_path
|
||||
if (!file_exists($this->getDestinationDirectory())) {
|
||||
return mkdir($this->getDestinationDirectory(), 0700, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param string $tmp_source_directory
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function putFile(string $filename, string $tmp_source_directory): bool
|
||||
{
|
||||
$source = FileDir::makeCorrectFile($tmp_source_directory . "/" . $filename);
|
||||
$target = FileDir::makeCorrectFile($this->getDestinationDirectory() . "/" . $filename);
|
||||
if (file_exists($source) && !file_exists($target)) {
|
||||
return rename($source, $target);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function rmFile(string $filename): bool
|
||||
{
|
||||
$target = FileDir::makeCorrectFile($this->getDestinationDirectory() . "/" . $filename);
|
||||
if (file_exists($target)) {
|
||||
return @unlink($target);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function shutdown(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
42
lib/Froxlor/Backup/Storages/Rsync.php
Normal file
42
lib/Froxlor/Backup/Storages/Rsync.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Froxlor\Backup\Storages;
|
||||
|
||||
class Rsync extends Storage
|
||||
{
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function init(): bool
|
||||
{
|
||||
// TODO: Implement init() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param string $tmp_source_directory
|
||||
* @return bool
|
||||
*/
|
||||
protected function putFile(string $filename, string $tmp_source_directory): bool
|
||||
{
|
||||
// TODO: Implement putFiles() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
protected function rmFile(string $filename): bool
|
||||
{
|
||||
// TODO: Implement removeOld() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function shutdown(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
42
lib/Froxlor/Backup/Storages/S3.php
Normal file
42
lib/Froxlor/Backup/Storages/S3.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Froxlor\Backup\Storages;
|
||||
|
||||
class S3 extends Storage
|
||||
{
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function init(): bool
|
||||
{
|
||||
// TODO: Implement init() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param string $tmp_source_directory
|
||||
* @return bool
|
||||
*/
|
||||
protected function putFile(string $filename, string $tmp_source_directory): bool
|
||||
{
|
||||
// TODO: Implement putFiles() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
protected function rmFile(string $filename): bool
|
||||
{
|
||||
// TODO: Implement removeOld() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function shutdown(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
42
lib/Froxlor/Backup/Storages/Sftp.php
Normal file
42
lib/Froxlor/Backup/Storages/Sftp.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Froxlor\Backup\Storages;
|
||||
|
||||
class Sftp extends Storage
|
||||
{
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function init(): bool
|
||||
{
|
||||
// TODO: Implement init() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param string $tmp_source_directory
|
||||
* @return bool
|
||||
*/
|
||||
protected function putFile(string $filename, string $tmp_source_directory): bool
|
||||
{
|
||||
// TODO: Implement putFiles() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
protected function rmFile(string $filename): bool
|
||||
{
|
||||
// TODO: Implement removeOld() method.
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function shutdown(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
155
lib/Froxlor/Backup/Storages/Storage.php
Normal file
155
lib/Froxlor/Backup/Storages/Storage.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
namespace Froxlor\Backup\Storages;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Database\Database;
|
||||
use Froxlor\FileDir;
|
||||
|
||||
abstract class Storage
|
||||
{
|
||||
private string $tmpDirectory;
|
||||
protected array $sData;
|
||||
|
||||
protected array $filesToStore;
|
||||
|
||||
public function __construct(array $storage_data)
|
||||
{
|
||||
$this->sData = $storage_data;
|
||||
$this->tmpDirectory = sys_get_temp_dir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate sData, open connection to target storage, etc.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function init(): bool;
|
||||
|
||||
/**
|
||||
* Disconnect / clean up connection if needed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function shutdown(): bool;
|
||||
|
||||
/**
|
||||
* prepare files to back up (e.g. create archive or similar) and fill $filesToStore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function prepareFiles(): void
|
||||
{
|
||||
$this->filesToStore = [];
|
||||
|
||||
// create archive of web, mail and database data
|
||||
|
||||
// create json-info-file
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param string $tmp_source_directory
|
||||
* @return bool
|
||||
*/
|
||||
abstract protected function putFile(string $filename, string $tmp_source_directory): bool;
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return bool
|
||||
*/
|
||||
abstract protected function rmFile(string $filename): bool;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public function removeOld(): bool
|
||||
{
|
||||
// retention in days
|
||||
$retention = $this->sData['storage']['retention'] ?? 3;
|
||||
// keep date
|
||||
$keepDate = new \DateTime();
|
||||
$keepDate->setTime(0, 0, 0, 1);
|
||||
// subtract retention days
|
||||
$keepDate->sub(new \DateInterval('P' . $retention . 'D'));
|
||||
// select target backups to remove for this storage-id and customer
|
||||
$sel_stmt = Database::prepare("
|
||||
SELECT * FROM `" . TABLE_PANEL_BACKUPS . "`
|
||||
WHERE `created_at` < :keepdate
|
||||
AND `storage_id` = :sid
|
||||
AND `customerid` = :cid
|
||||
");
|
||||
Database::pexecute($sel_stmt, [
|
||||
'keepdate' => $keepDate->format('U'),
|
||||
'sid' => $this->sData['backup'],
|
||||
'cid' => $this->sData['customerid']
|
||||
]);
|
||||
while ($oldBackup = $sel_stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
$this->rmFile($oldBackup['filename']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getDestinationDirectory(): string
|
||||
{
|
||||
return FileDir::makeCorrectDir($this->sData['storage']['destination_path'] ?? "/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create backup-archive/file from $filesToStore and call putFile()
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public function createFromFiles(): bool
|
||||
{
|
||||
if (empty($this->filesToStore)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$filename = FileDir::makeCorrectFile("/backup-" . $this->sData['loginname'] . "-" . date('c') . ".tar.gz");
|
||||
|
||||
// @todo create archive $filename from $filesToStore
|
||||
|
||||
// determine filesize (use stat locally here b/c files are possibly large and php's filesize() can't handle them)
|
||||
$sizeCheckFile = FileDir::makeCorrectFile($this->tmpDirectory . "/" . $filename);
|
||||
$fileSizeOutput = FileDir::safe_exec('/usr/bin/stat -c "%s" ' . escapeshellarg($sizeCheckFile));
|
||||
$fileSize = (int)array_shift($fileSizeOutput);
|
||||
|
||||
// add entry to database and upload/store file
|
||||
$this->addEntry($filename, $fileSize);
|
||||
return $this->putFile($filename, $this->tmpDirectory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param int $fileSize
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function addEntry(string $filename, int $fileSize): void
|
||||
{
|
||||
$ins_stmt = Database::prepare("
|
||||
INSERT INTO `" . TABLE_PANEL_BACKUPS . "` SET
|
||||
`adminid` = :adminid,
|
||||
`customerid` = :customerid,
|
||||
`loginname` = :loginname,
|
||||
`size` = :size,
|
||||
`storage_id` = :sid,
|
||||
`filename` = :filename,
|
||||
`created_at` = UNIX_TIMESTAMP()
|
||||
");
|
||||
Database::pexecute($ins_stmt, [
|
||||
'adminid' => $this->sData['adminid'],
|
||||
'customerid' => $this->sData['customerid'],
|
||||
'loginname' => $this->sData['loginname'],
|
||||
'size' => $fileSize,
|
||||
'sid' => $this->sData['backup'],
|
||||
'filename' => $filename
|
||||
]);
|
||||
}
|
||||
}
|
||||
12
lib/Froxlor/Backup/Storages/StorageFactory.php
Normal file
12
lib/Froxlor/Backup/Storages/StorageFactory.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Froxlor\Backup\Storages;
|
||||
|
||||
class StorageFactory
|
||||
{
|
||||
public static function fromType(string $type, array $storage_data): Storage
|
||||
{
|
||||
$type = "\\Froxlor\\Backup\\Storages\\" . ucfirst($type);
|
||||
return new $type($storage_data);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
namespace Froxlor\Cron\Backup;
|
||||
|
||||
use Exception;
|
||||
use Froxlor\Backup\Storages\StorageFactory;
|
||||
use Froxlor\Cron\Forkable;
|
||||
use Froxlor\Cron\FroxlorCron;
|
||||
use Froxlor\Database\Database;
|
||||
@@ -38,7 +40,7 @@ class BackupCron extends FroxlorCron
|
||||
|
||||
public static function run()
|
||||
{
|
||||
if(!Settings::Get('backup.enabled')) {
|
||||
if (!Settings::Get('backup.enabled')) {
|
||||
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'BackupCron: disabled - exiting');
|
||||
return -1;
|
||||
}
|
||||
@@ -71,11 +73,22 @@ class BackupCron extends FroxlorCron
|
||||
self::runFork([self::class, 'handle'], $customers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function handle(array $userdata)
|
||||
{
|
||||
echo "BackupCron: started - creating customer backup for user " . $userdata['loginname'] . "\n";
|
||||
|
||||
echo json_encode($userdata['storage']) . "\n";
|
||||
$backupStorage = StorageFactory::fromType($userdata['storage']['type'], $userdata);
|
||||
// initialize storage
|
||||
$backupStorage->init();
|
||||
// do what is required to obtain files/archives and move/upload
|
||||
$backupStorage->prepareFiles();
|
||||
// upload/move to target
|
||||
$backupStorage->createFromFiles();
|
||||
// remove by retention
|
||||
$backupStorage->removeOld();
|
||||
|
||||
echo "BackupCron: finished - creating customer backup for user " . $userdata['loginname'] . "\n";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user