2024-04-22 11:14:05 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Models;
|
|
|
|
|
2024-05-02 14:51:26 +00:00
|
|
|
use App\BackupStorage;
|
2024-04-25 09:30:01 +00:00
|
|
|
use App\Filament\Enums\BackupStatus;
|
2024-04-25 12:11:44 +00:00
|
|
|
use App\Helpers;
|
2024-04-30 23:04:24 +00:00
|
|
|
use App\ShellApi;
|
2024-04-29 15:12:08 +00:00
|
|
|
use Dotenv\Dotenv;
|
2024-04-22 11:14:05 +00:00
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
2024-05-02 14:51:26 +00:00
|
|
|
use Illuminate\Support\Facades\Config;
|
2024-05-07 09:19:49 +00:00
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
use Illuminate\Support\Facades\Schema;
|
2024-04-30 22:32:29 +00:00
|
|
|
use Illuminate\Support\Facades\Storage;
|
2024-04-24 17:51:36 +00:00
|
|
|
use Illuminate\Support\Str;
|
2024-05-02 12:47:45 +00:00
|
|
|
use Jackiedo\DotenvEditor\Facades\DotenvEditor;
|
2024-04-24 17:51:36 +00:00
|
|
|
|
2024-04-22 11:14:05 +00:00
|
|
|
class Backup extends Model
|
|
|
|
{
|
|
|
|
use HasFactory;
|
2024-04-24 17:51:36 +00:00
|
|
|
|
2024-04-25 09:30:01 +00:00
|
|
|
const STATUS_PENDING = 'pending';
|
|
|
|
const STATUS_PROCESSING = 'processing';
|
|
|
|
const STATUS_COMPLETED = 'completed';
|
|
|
|
const STATUS_FAILED = 'failed';
|
|
|
|
const STATUS_CANCELLED = 'cancelled';
|
|
|
|
|
2024-04-24 17:51:36 +00:00
|
|
|
protected $fillable = [
|
|
|
|
'backup_type',
|
|
|
|
'status',
|
|
|
|
'path',
|
|
|
|
'size',
|
|
|
|
'disk',
|
|
|
|
];
|
|
|
|
|
2024-04-25 09:30:01 +00:00
|
|
|
protected $casts = [
|
|
|
|
'status' => BackupStatus::class,
|
|
|
|
];
|
|
|
|
|
2024-04-24 18:03:08 +00:00
|
|
|
public static function boot()
|
|
|
|
{
|
|
|
|
parent::boot();
|
|
|
|
|
|
|
|
static::creating(function ($model) {
|
|
|
|
$model->status = 'pending';
|
2024-04-24 19:19:31 +00:00
|
|
|
$model->checkCronJob();
|
2024-04-24 18:03:08 +00:00
|
|
|
});
|
|
|
|
|
2024-04-25 12:11:44 +00:00
|
|
|
static::created(function ($model) {
|
|
|
|
$model->startBackup();
|
|
|
|
});
|
|
|
|
|
2024-04-25 08:46:24 +00:00
|
|
|
static::deleting(function ($model) {
|
2024-05-02 12:47:45 +00:00
|
|
|
if (is_file($model->filepath)) {
|
|
|
|
// ShellApi::safeDelete($model->path, [
|
|
|
|
// Storage::path('backups')
|
|
|
|
// ]);
|
2024-05-09 12:47:42 +00:00
|
|
|
// if (Storage::disk('backups')->exists($model->filepath)) {
|
|
|
|
// Storage::disk('backups')->delete($model->filepath);
|
|
|
|
// }
|
2024-05-02 12:47:45 +00:00
|
|
|
}
|
2024-04-25 08:46:24 +00:00
|
|
|
});
|
2024-04-24 18:03:08 +00:00
|
|
|
}
|
|
|
|
|
2024-04-25 12:38:39 +00:00
|
|
|
public function checkCronJob()
|
2024-04-24 19:19:31 +00:00
|
|
|
{
|
2024-04-30 22:46:33 +00:00
|
|
|
$cronJobCommand = 'phyre-php /usr/local/phyre/web/artisan phyre:run-backup-checks';
|
2024-04-24 20:18:50 +00:00
|
|
|
$findCronJob = CronJob::where('command', $cronJobCommand)->first();
|
2024-04-24 19:19:31 +00:00
|
|
|
if (! $findCronJob) {
|
|
|
|
$cronJob = new CronJob();
|
|
|
|
$cronJob->schedule = '*/5 * * * *';
|
2024-04-24 20:18:50 +00:00
|
|
|
$cronJob->command = $cronJobCommand;
|
2024-04-24 19:19:31 +00:00
|
|
|
$cronJob->user = 'root';
|
|
|
|
$cronJob->save();
|
|
|
|
}
|
2024-04-30 22:46:33 +00:00
|
|
|
|
|
|
|
$cronJobCommand = 'phyre-php /usr/local/phyre/web/artisan phyre:create-daily-full-backup';
|
|
|
|
$findCronJob = CronJob::where('command', $cronJobCommand)->first();
|
|
|
|
if (! $findCronJob) {
|
|
|
|
$cronJob = new CronJob();
|
|
|
|
$cronJob->schedule = '0 0 * * *';
|
|
|
|
$cronJob->command = $cronJobCommand;
|
|
|
|
$cronJob->user = 'root';
|
|
|
|
$cronJob->save();
|
|
|
|
}
|
|
|
|
|
2024-04-25 12:38:39 +00:00
|
|
|
return true;
|
2024-04-24 19:19:31 +00:00
|
|
|
}
|
|
|
|
|
2024-04-24 18:03:08 +00:00
|
|
|
public function checkBackup()
|
|
|
|
{
|
2024-04-25 10:36:22 +00:00
|
|
|
if ($this->status == BackupStatus::Processing) {
|
2024-04-24 18:03:08 +00:00
|
|
|
$backupDoneFile = $this->path.'/backup.done';
|
|
|
|
if (file_exists($backupDoneFile)) {
|
2024-04-29 15:12:08 +00:00
|
|
|
|
|
|
|
$tempValidatePath = $this->path.'/temp-validate';
|
|
|
|
if (! is_dir($tempValidatePath)) {
|
|
|
|
mkdir($tempValidatePath);
|
|
|
|
}
|
2024-04-30 22:32:29 +00:00
|
|
|
|
2024-05-02 15:38:42 +00:00
|
|
|
shell_exec('cd '.$tempValidatePath.' && unzip -o '.$this->file_path);
|
2024-04-30 22:32:29 +00:00
|
|
|
|
2024-04-29 15:12:08 +00:00
|
|
|
$validateDatabaseFile = $tempValidatePath.'/database.sql';
|
2024-05-02 12:47:45 +00:00
|
|
|
$validateEnvFile = $tempValidatePath.'/.env';
|
2024-04-29 15:12:08 +00:00
|
|
|
|
|
|
|
$errorsBag = [];
|
|
|
|
if (! file_exists($validateDatabaseFile)) {
|
|
|
|
$errorsBag[] = 'Database file not found';
|
|
|
|
}
|
|
|
|
if (! file_exists($validateEnvFile)) {
|
|
|
|
$errorsBag[] = 'Env file not found';
|
|
|
|
}
|
2024-05-02 12:47:45 +00:00
|
|
|
if (count($errorsBag) > 0) {
|
|
|
|
$this->status = 'failed';
|
2024-05-02 20:06:52 +00:00
|
|
|
$this->backup_log = 'Backup failed. Database or env file missing.';
|
2024-05-02 12:47:45 +00:00
|
|
|
$this->save();
|
|
|
|
return [
|
|
|
|
'status' => 'failed',
|
|
|
|
'message' => 'Backup failed. Database or env file missing.',
|
|
|
|
'errors' => $errorsBag
|
|
|
|
];
|
|
|
|
}
|
|
|
|
$originalEnvContent = file_get_contents(base_path().'/.env');
|
|
|
|
$backupEnvContent = file_get_contents($validateEnvFile);
|
|
|
|
if ($originalEnvContent != $backupEnvContent) {
|
|
|
|
$errorsBag[] = 'Env file content mismatch';
|
2024-04-29 15:12:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (count($errorsBag) > 0) {
|
|
|
|
$this->status = 'failed';
|
2024-05-02 20:06:52 +00:00
|
|
|
$this->backup_log = 'Backup failed. Database or env file content mismatch.';
|
2024-04-29 15:12:08 +00:00
|
|
|
$this->save();
|
|
|
|
return [
|
|
|
|
'status' => 'failed',
|
|
|
|
'message' => 'Backup failed',
|
|
|
|
'errors' => $errorsBag
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2024-04-30 23:14:27 +00:00
|
|
|
ShellApi::safeDelete($this->path,[
|
2024-05-02 15:38:42 +00:00
|
|
|
$this->root_path
|
|
|
|
]);
|
|
|
|
ShellApi::safeDelete($this->temp_path,[
|
|
|
|
$this->root_path
|
2024-04-30 23:14:27 +00:00
|
|
|
]);
|
2024-04-30 22:37:43 +00:00
|
|
|
|
2024-05-02 15:38:42 +00:00
|
|
|
$this->size = filesize($this->file_path);
|
2024-04-24 18:03:08 +00:00
|
|
|
$this->status = 'completed';
|
|
|
|
$this->completed = true;
|
|
|
|
$this->completed_at = now();
|
2024-05-02 20:06:52 +00:00
|
|
|
$this->backup_log = 'Backup completed';
|
2024-04-24 18:03:08 +00:00
|
|
|
$this->save();
|
2024-04-29 15:12:08 +00:00
|
|
|
|
2024-04-25 10:36:22 +00:00
|
|
|
return [
|
|
|
|
'status' => 'completed',
|
|
|
|
'message' => 'Backup completed'
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
$checkProcess = shell_exec('ps -p ' . $this->process_id . ' | grep ' . $this->process_id);
|
|
|
|
if (Str::contains($checkProcess, $this->process_id)) {
|
2024-04-25 10:46:57 +00:00
|
|
|
|
2024-05-02 20:22:17 +00:00
|
|
|
|
2024-05-09 12:47:42 +00:00
|
|
|
$this->size = 0;
|
2024-05-02 20:22:17 +00:00
|
|
|
$this->backup_log = file_get_contents($this->path.'/backup.log');
|
2024-04-25 10:51:02 +00:00
|
|
|
$this->save();
|
2024-04-25 10:46:57 +00:00
|
|
|
|
2024-04-25 10:36:22 +00:00
|
|
|
return [
|
|
|
|
'status' => 'processing',
|
|
|
|
'message' => 'Backup is still processing'
|
|
|
|
];
|
|
|
|
} else {
|
2024-05-02 20:06:52 +00:00
|
|
|
$this->backup_log = 'Backup failed. Process not found';
|
2024-04-25 10:36:22 +00:00
|
|
|
$this->status = 'failed';
|
|
|
|
$this->save();
|
|
|
|
return [
|
|
|
|
'status' => 'failed',
|
|
|
|
'message' => 'Backup failed'
|
|
|
|
];
|
2024-04-24 18:03:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-24 17:51:36 +00:00
|
|
|
public function startBackup()
|
|
|
|
{
|
2024-04-25 10:36:22 +00:00
|
|
|
if ($this->status == BackupStatus::Processing) {
|
2024-04-24 17:51:36 +00:00
|
|
|
return [
|
2024-04-25 09:30:01 +00:00
|
|
|
'status' => 'processing',
|
|
|
|
'message' => 'Backup is already processing'
|
2024-04-24 17:51:36 +00:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
2024-05-02 14:51:26 +00:00
|
|
|
$storagePath = BackupStorage::getPath();
|
2024-04-30 22:32:29 +00:00
|
|
|
$backupPath = $storagePath.'/'.$this->id;
|
2024-04-24 17:51:36 +00:00
|
|
|
$backupTempPath = $backupPath.'/temp';
|
|
|
|
if (! is_dir($backupTempPath)) {
|
2024-05-02 14:51:26 +00:00
|
|
|
shell_exec('mkdir -p '.$backupTempPath);
|
2024-04-24 17:51:36 +00:00
|
|
|
}
|
2024-04-25 13:23:15 +00:00
|
|
|
|
2024-05-02 12:47:45 +00:00
|
|
|
$backupFilename = 'phyre-backup-'.date('Ymd-His').'.zip';
|
2024-04-30 22:32:29 +00:00
|
|
|
$backupFilePath = $storagePath.'/' . $backupFilename;
|
|
|
|
|
2024-04-25 18:57:47 +00:00
|
|
|
if ($this->backup_type == 'full') {
|
2024-04-25 13:23:15 +00:00
|
|
|
|
|
|
|
// Export Phyre Panel database
|
|
|
|
$databaseBackupPath = $backupTempPath.'/database.sql';
|
|
|
|
|
|
|
|
$backupLogFileName = 'backup.log';
|
|
|
|
$backupLogFilePath = $backupPath.'/'.$backupLogFileName;
|
|
|
|
|
|
|
|
$backupTempScript = '/tmp/backup-script-'.$this->id.'.sh';
|
|
|
|
$shellFileContent = '';
|
2024-04-29 15:12:08 +00:00
|
|
|
$shellFileContent .= 'echo "Backup Phyre Panel files"'. PHP_EOL;
|
|
|
|
|
|
|
|
// Export Phyre Panel database
|
2024-05-02 17:56:10 +00:00
|
|
|
$mysqlAuthConf = '/root/.phyre-mysql.cnf';
|
|
|
|
$mysqlAuthContent = '[client]' . PHP_EOL;
|
|
|
|
$mysqlAuthContent .= 'user="' . env('MYSQL_ROOT_USERNAME') .'"'. PHP_EOL;
|
|
|
|
$mysqlAuthContent .= 'password="' . env('MYSQL_ROOT_PASSWORD') . '"' . PHP_EOL;
|
|
|
|
file_put_contents($mysqlAuthConf, $mysqlAuthContent);
|
|
|
|
|
|
|
|
$shellFileContent .= 'mysqldump --defaults-extra-file='.$mysqlAuthConf.' "'.env('DB_DATABASE').'" > '.$databaseBackupPath . PHP_EOL;
|
2024-04-29 15:12:08 +00:00
|
|
|
|
2024-05-07 09:19:49 +00:00
|
|
|
// Export Phyre Panel Database
|
|
|
|
$database = [];
|
|
|
|
$tables = Schema::getTables();
|
|
|
|
if (count($tables) > 0) {
|
|
|
|
foreach ($tables as $table) {
|
|
|
|
$tableData = [];
|
|
|
|
$tableData['table'] = $table;
|
|
|
|
$tableData['columns'] = Schema::getColumnListing($table['name']);
|
|
|
|
$tableData['data'] = DB::table($table['name'])->get()->toArray();
|
|
|
|
$database[$table['name']] = $tableData;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-29 15:12:08 +00:00
|
|
|
// Export Phyre Panel ENV
|
|
|
|
$getEnv = Dotenv::createArrayBacked(base_path())->load();
|
2024-05-02 12:47:45 +00:00
|
|
|
$backupStructure = [
|
2024-05-07 09:19:49 +00:00
|
|
|
'database'=>$database,
|
2024-05-02 12:47:45 +00:00
|
|
|
'env'=>$getEnv,
|
|
|
|
];
|
|
|
|
file_put_contents($backupTempPath.'/backup.json', json_encode($backupStructure, JSON_PRETTY_PRINT));
|
|
|
|
$shellFileContent .= 'echo "Backup Phyre Panel ENV"'. PHP_EOL;
|
|
|
|
$shellFileContent .= 'cp '.base_path().'/.env '.$backupTempPath.'/.env'. PHP_EOL;
|
|
|
|
|
|
|
|
// Export Phyre Panel Hosting Subscription
|
|
|
|
$findHostingSubscription = HostingSubscription::all();
|
|
|
|
if ($findHostingSubscription->count() > 0) {
|
|
|
|
foreach ($findHostingSubscription as $hostingSubscription) {
|
2024-05-02 15:43:54 +00:00
|
|
|
$hostingSubscriptionsMainPath = $backupTempPath .'/hosting_subscriptions';
|
|
|
|
$hostingSubscriptionPath = $hostingSubscriptionsMainPath .'/'. $hostingSubscription->system_username;
|
2024-05-02 12:47:45 +00:00
|
|
|
$shellFileContent .= PHP_EOL;
|
|
|
|
$shellFileContent .= 'echo "Backup up hosting subscription: ' . $hostingSubscription->system_username .'" '. PHP_EOL;
|
|
|
|
$shellFileContent .= 'mkdir -p '.$hostingSubscriptionPath.PHP_EOL;
|
2024-05-02 13:53:05 +00:00
|
|
|
|
|
|
|
// cp -r (copy recursively, also copy hidden files)
|
2024-05-02 15:43:54 +00:00
|
|
|
$shellFileContent .= 'cp -r /home/'.$hostingSubscription->system_username.'/ ' . $hostingSubscriptionsMainPath .PHP_EOL;
|
2024-05-02 12:47:45 +00:00
|
|
|
|
|
|
|
$shellFileContent .= 'mkdir -p '.$hostingSubscriptionPath.'/databases'.PHP_EOL;
|
|
|
|
|
|
|
|
$getDatabases = Database::where('hosting_subscription_id', $hostingSubscription->id)
|
|
|
|
->where(function ($query) {
|
|
|
|
$query->where('is_remote_database_server', '0')
|
|
|
|
->orWhereNull('is_remote_database_server');
|
|
|
|
})
|
|
|
|
->get();
|
|
|
|
|
|
|
|
if ($getDatabases->count() > 0) {
|
|
|
|
foreach ($getDatabases as $database) {
|
|
|
|
$databaseName = $database->database_name_prefix . $database->database_name;
|
|
|
|
$shellFileContent .= 'echo "Backup up database: ' . $databaseName . '" ' . PHP_EOL;
|
|
|
|
$databaseBackupPath = $hostingSubscriptionPath . '/databases/' . $databaseName . '.sql';
|
2024-05-02 17:56:10 +00:00
|
|
|
$shellFileContent .= 'mysqldump --defaults-extra-file='.$mysqlAuthConf.' "' . $databaseName . '" > ' . $databaseBackupPath . PHP_EOL;
|
2024-05-02 12:47:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$shellFileContent .= PHP_EOL;
|
|
|
|
}
|
|
|
|
}
|
2024-04-29 15:12:08 +00:00
|
|
|
|
2024-05-02 13:53:53 +00:00
|
|
|
// With find, we can search for all files,directories (including hidden) in the current directory and zip them
|
2024-05-09 12:55:23 +00:00
|
|
|
$shellFileContent .= 'cd '.$backupTempPath .' && find . -exec zip --symlinks -r '.$backupFilePath.' {} \;'. PHP_EOL;
|
2024-04-25 13:23:15 +00:00
|
|
|
|
|
|
|
$shellFileContent .= 'rm -rf '.$backupTempPath.PHP_EOL;
|
|
|
|
$shellFileContent .= 'echo "Backup complete"' . PHP_EOL;
|
|
|
|
$shellFileContent .= 'touch ' . $backupPath. '/backup.done' . PHP_EOL;
|
|
|
|
$shellFileContent .= 'rm -rf ' . $backupTempScript;
|
|
|
|
|
|
|
|
file_put_contents($backupTempScript, $shellFileContent);
|
|
|
|
|
|
|
|
$processId = shell_exec('bash '.$backupTempScript.' >> ' . $backupLogFilePath . ' & echo $!');
|
|
|
|
$processId = intval($processId);
|
|
|
|
|
|
|
|
if ($processId > 0 && is_numeric($processId)) {
|
|
|
|
|
|
|
|
$this->path = $backupPath;
|
2024-05-02 14:51:26 +00:00
|
|
|
$this->root_path = $storagePath;
|
|
|
|
$this->temp_path = $backupTempPath;
|
|
|
|
$this->file_path = $backupFilePath;
|
|
|
|
$this->file_name = $backupFilename;
|
|
|
|
|
2024-04-25 13:23:15 +00:00
|
|
|
$this->status = 'processing';
|
|
|
|
$this->queued = true;
|
|
|
|
$this->queued_at = now();
|
|
|
|
$this->process_id = $processId;
|
2024-05-02 14:51:26 +00:00
|
|
|
|
2024-04-25 13:23:15 +00:00
|
|
|
$this->save();
|
|
|
|
|
|
|
|
return [
|
|
|
|
'status' => 'processing',
|
|
|
|
'message' => 'System backup started'
|
|
|
|
];
|
|
|
|
} else {
|
|
|
|
$this->status = 'failed';
|
|
|
|
$this->save();
|
|
|
|
return [
|
|
|
|
'status' => 'failed',
|
|
|
|
'message' => 'System backup failed to start'
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2024-04-24 17:51:36 +00:00
|
|
|
}
|
2024-04-22 11:14:05 +00:00
|
|
|
}
|