This commit is contained in:
Bozhidar 2024-04-24 20:51:36 +03:00
parent b265c154b6
commit 2325192f78
8 changed files with 285 additions and 13 deletions

View file

@ -0,0 +1,44 @@
<?php
namespace App\Console\Commands;
use App\Models\Backup;
use App\Models\HostingSubscription;
use Illuminate\Console\Command;
class RunBackup extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'phyre:run-backup';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*/
public function handle()
{
// Delete backups older than 7 days
$findBackupsForDeleting = Backup::where('created_at', '<', now()->subDays(7))->get();
foreach ($findBackupsForDeleting as $backup) {
$backup->delete();
}
$getBackups = Backup::where('backup_type', 'hosting_subscription')->get();
if ($getBackups->count() > 0) {
foreach ($getBackups as $backup) {
$status = $backup->startBackup();
}
}
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace App\Filament\Enums;
use Filament\Support\Contracts\HasLabel;
use JaOcero\RadioDeck\Contracts\HasDescriptions;
use JaOcero\RadioDeck\Contracts\HasIcons;
enum BackupType: string implements HasLabel, HasDescriptions, HasIcons
{
case FULL_BACKUP = 'full';
case SYSTEM_BACKUP = 'system';
case DATABASE_BACKUP = 'database';
case HOSTING_SUBSCRIPTION_BACKUP = 'hosting_subscription';
public function getLabel(): ?string
{
return match ($this) {
self::FULL_BACKUP => 'Full Backup',
self::SYSTEM_BACKUP => 'System Backup',
self::DATABASE_BACKUP => 'Database Backup',
self::HOSTING_SUBSCRIPTION_BACKUP => 'Hosting Subscription Backup',
};
}
public function getDescriptions(): ?string
{
return match ($this) {
self::FULL_BACKUP => 'A full backup is a complete copy of your website files and database. It is the best way to protect your website data.',
self::SYSTEM_BACKUP => 'A system backup is a copy of your website files and system files. It is useful for restoring your website if there is a problem with the system files.',
self::DATABASE_BACKUP => 'A database backup is a copy of your website database. It is useful for restoring your website if there is a problem with the database.',
self::HOSTING_SUBSCRIPTION_BACKUP => 'A hosting subscription backup is a copy of your website files and database. It is useful for restoring your website if there is a problem with the hosting subscription.',
};
}
public function getIcons(): ?string
{
return match ($this) {
self::FULL_BACKUP => 'heroicon-o-inbox-stack',
self::SYSTEM_BACKUP => 'heroicon-o-cog',
self::DATABASE_BACKUP => 'phyre-mysql',
self::HOSTING_SUBSCRIPTION_BACKUP => 'heroicon-o-server',
};
}
}

View file

@ -2,12 +2,22 @@
namespace App\Filament\Resources;
use App\Filament\Enums\BackupType;
use App\Filament\Resources\BackupResource\Pages;
use App\Models\Backup;
use App\Models\HostingSubscription;
use Filament\Forms\Components\Select;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use JaOcero\RadioDeck\Forms\Components\RadioDeck;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class BackupResource extends Resource
{
@ -23,7 +33,29 @@ class BackupResource extends Resource
{
return $form
->schema([
//
RadioDeck::make('backup_type')
->live()
->default('full_backup')
->options(BackupType::class)
->icons(BackupType::class)
->descriptions(BackupType::class)
->required()
->color('primary')
->columnSpanFull(),
Select::make('hosting_subscription_id')
->label('Hosting Subscription')
->hidden(function (Get $get) {
return $get('backup_type') !== 'hosting_subscription';
})
->options(
HostingSubscription::all()->pluck('domain', 'id')
)
->columnSpanFull()
->required(),
]);
}
@ -31,7 +63,9 @@ class BackupResource extends Resource
{
return $table
->columns([
//
Tables\Columns\TextColumn::make('backup_type'),
Tables\Columns\TextColumn::make('backupRelated'),
Tables\Columns\TextColumn::make('status'),
])
->filters([
//
@ -59,7 +93,7 @@ class BackupResource extends Resource
return [
'index' => Pages\ListBackups::route('/'),
'create' => Pages\CreateBackup::route('/create'),
'edit' => Pages\EditBackup::route('/{record}/edit'),
'view' => Pages\ViewBackup::route('/{record}'),
];
}
}

View file

@ -4,9 +4,9 @@ namespace App\Filament\Resources\BackupResource\Pages;
use App\Filament\Resources\BackupResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
use Filament\Resources\Pages\ViewRecord;
class EditBackup extends EditRecord
class ViewBackup extends ViewRecord
{
protected static string $resource = BackupResource::class;

View file

@ -4,7 +4,104 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Str;
class Backup extends Model
{
use HasFactory;
protected $fillable = [
'hosting_subscription_id',
'backup_type',
'status',
'path',
'size',
'disk',
];
protected function backupRelated() : Attribute
{
$relatedWith = $this->backup_type;
if ($this->backup_type === 'hosting_subscription') {
$findHostingSubscription = HostingSubscription::where('id', $this->hosting_subscription_id)->first();
if ($findHostingSubscription) {
$relatedWith = $findHostingSubscription->domain;
}
}
return Attribute::make(
get: fn () => $relatedWith
);
}
public function startBackup()
{
if ($this->status == 'running') {
return [
'status' => 'running',
'message' => 'Backup already running'
];
}
$storagePath = storage_path('backups');
if (! is_dir($storagePath)) {
mkdir($storagePath);
}
$backupPath = $storagePath.'/'.$this->id;
if (! is_dir($backupPath)) {
mkdir($backupPath);
}
$backupTempPath = $backupPath.'/temp';
if (! is_dir($backupTempPath)) {
mkdir($backupTempPath);
}
if ($this->backup_type == 'hosting_subscription') {
$findHostingSubscription = HostingSubscription::where('id', $this->hosting_subscription_id)->first();
if ($findHostingSubscription) {
$backupFileName = Str::slug($findHostingSubscription->domain .'-'. date('Y-m-d-H-i-s')) . '.zip';
$backupFilePath = $backupPath.'/'.$backupFileName;
$backupLogFileName = 'backup.log';
$backupLogFilePath = $backupPath.'/'.$backupLogFileName;
$backupTempScript = '/tmp/backup-script-'.$this->id.'.sh';
$shellFileContent = '';
$shellFileContent .= 'echo "Backup up domain: '.$findHostingSubscription->domain . PHP_EOL;
$shellFileContent .= 'echo "Backup filename: '.$backupFileName. PHP_EOL;
$shellFileContent .= 'cp -r /home/'.$findHostingSubscription->system_username.' '.$backupTempPath.PHP_EOL;
$shellFileContent .= 'cd '.$backupTempPath .' && zip -r '.$backupFilePath.' ./* '. PHP_EOL;
$shellFileContent .= 'rm -rf '.$backupTempPath.PHP_EOL;
$shellFileContent .= 'echo "Backup complete"' . PHP_EOL;
$shellFileContent .= 'touch ' . $backupPath. '/backup.done' . PHP_EOL;
$shellFileContent .= 'rm -rf ' . $backupTempScript;
$this->status = 'running';
$this->queued = true;
$this->queued_at = now();
$this->save();
file_put_contents($backupTempScript, $shellFileContent);
shell_exec('bash '.$backupTempScript.' >> ' . $backupLogFilePath . ' &');
return [
'status' => 'running',
'message' => 'Backup started'
];
} else {
$this->status = 'failed';
$this->save();
return [
'status' => 'failed',
'message' => 'Hosting subscription not found'
];
}
}
}
}

View file

@ -124,7 +124,6 @@ class HostingSubscription extends Model
return [];
}
private static function _generateUsername($string)
{
$removedMultispace = preg_replace('/\s+/', ' ', $string);

View file

@ -35,13 +35,15 @@ class AdminPanelProvider extends PanelProvider
$defaultColor = Color::Yellow;
$brandLogo = asset('images/phyre-logo.svg');
$isAppInstalled = file_exists(storage_path('installed'));
if ($isAppInstalled) {
if (setting('general.brand_logo_url')) {
$brandLogo = setting('general.brand_logo_url');
}
if (setting('general.brand_primary_color')) {
$defaultColor = Color::hex(setting('general.brand_primary_color'));
if (!app()->runningInConsole()) {
$isAppInstalled = file_exists(storage_path('installed'));
if ($isAppInstalled) {
if (setting('general.brand_logo_url')) {
$brandLogo = setting('general.brand_logo_url');
}
if (setting('general.brand_primary_color')) {
$defaultColor = Color::hex(setting('general.brand_primary_color'));
}
}
}

View file

@ -0,0 +1,51 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('backups', function (Blueprint $table) {
$table->id();
$table->bigInteger('hosting_subscription_id')->nullable();
$table->string('backup_type')->nullable();
$table->string('status')->nullable();
$table->string('path')->nullable();
$table->string('size')->nullable();
$table->string('disk')->nullable();
$table->longText('settings')->nullable();
$table->tinyInteger('queued')->nullable();
$table->timestamp('queued_at')->nullable();
$table->tinyInteger('started')->nullable();
$table->timestamp('started_at')->nullable();
$table->tinyInteger('completed')->nullable();
$table->timestamp('completed_at')->nullable();
$table->tinyInteger('failed')->nullable();
$table->timestamp('failed_at')->nullable();
$table->tinyInteger('cancelled')->nullable();
$table->timestamp('cancelled_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('backups');
}
};