Use backups folder for DirectAdmin backup restores
This commit is contained in:
@@ -248,8 +248,8 @@ class ImportProcessCommand extends Command
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$backupPath = Storage::disk('local')->path($import->backup_path);
|
$backupPath = $this->resolveBackupFullPath($import);
|
||||||
if (!file_exists($backupPath)) {
|
if (! $backupPath) {
|
||||||
$account->addLog("Warning: Backup file not found");
|
$account->addLog("Warning: Backup file not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -323,8 +323,8 @@ class ImportProcessCommand extends Command
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$backupPath = Storage::disk('local')->path($import->backup_path);
|
$backupPath = $this->resolveBackupFullPath($import);
|
||||||
if (!file_exists($backupPath)) {
|
if (! $backupPath) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,4 +386,28 @@ class ImportProcessCommand extends Command
|
|||||||
|
|
||||||
$account->addLog("Note: Email accounts must be recreated manually");
|
$account->addLog("Note: Email accounts must be recreated manually");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function resolveBackupFullPath(ServerImport $import): ?string
|
||||||
|
{
|
||||||
|
$path = trim((string) ($import->backup_path ?? ''));
|
||||||
|
if ($path === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_starts_with($path, '/') && file_exists($path)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
$localCandidate = Storage::disk('local')->path($path);
|
||||||
|
if (file_exists($localCandidate)) {
|
||||||
|
return $localCandidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
$backupCandidate = Storage::disk('backups')->path($path);
|
||||||
|
if (file_exists($backupCandidate)) {
|
||||||
|
return $backupCandidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return file_exists($path) ? $path : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,9 +186,9 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
|||||||
|
|
||||||
FileUpload::make('backupPath')
|
FileUpload::make('backupPath')
|
||||||
->label(__('DirectAdmin Backup Archive'))
|
->label(__('DirectAdmin Backup Archive'))
|
||||||
->helperText(__('Upload a .tar.gz DirectAdmin backup file.'))
|
->helperText(__('Upload a DirectAdmin backup file to the server backups folder.'))
|
||||||
->disk('local')
|
->disk('backups')
|
||||||
->directory('imports/directadmin')
|
->directory('directadmin-migrations')
|
||||||
->preserveFilenames()
|
->preserveFilenames()
|
||||||
->acceptedFileTypes([
|
->acceptedFileTypes([
|
||||||
'application/gzip',
|
'application/gzip',
|
||||||
@@ -198,6 +198,8 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
|||||||
])
|
])
|
||||||
->required()
|
->required()
|
||||||
->visible(fn (Get $get): bool => $get('importMethod') === 'backup_file'),
|
->visible(fn (Get $get): bool => $get('importMethod') === 'backup_file'),
|
||||||
|
Text::make(__('Tip: Upload backups to /var/backups/jabali/directadmin-migrations/'))->color('gray')
|
||||||
|
->visible(fn (Get $get): bool => $get('importMethod') === 'backup_file'),
|
||||||
|
|
||||||
FormActions::make([
|
FormActions::make([
|
||||||
Action::make('discoverAccounts')
|
Action::make('discoverAccounts')
|
||||||
@@ -386,7 +388,10 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
|||||||
throw new Exception(__('Please upload a DirectAdmin backup archive.'));
|
throw new Exception(__('Please upload a DirectAdmin backup archive.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$backupFullPath = Storage::disk('local')->path($import->backup_path);
|
$backupFullPath = $this->resolveBackupFullPath($import->backup_path);
|
||||||
|
if (! $backupFullPath) {
|
||||||
|
throw new Exception(__('Backup file not found: :path', ['path' => $import->backup_path]));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$remotePassword = $this->remotePassword;
|
$remotePassword = $this->remotePassword;
|
||||||
|
|
||||||
@@ -486,6 +491,30 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function resolveBackupFullPath(?string $path): ?string
|
||||||
|
{
|
||||||
|
$path = trim((string) ($path ?? ''));
|
||||||
|
if ($path === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_starts_with($path, '/') && file_exists($path)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
$localCandidate = Storage::disk('local')->path($path);
|
||||||
|
if (file_exists($localCandidate)) {
|
||||||
|
return $localCandidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
$backupCandidate = Storage::disk('backups')->path($path);
|
||||||
|
if (file_exists($backupCandidate)) {
|
||||||
|
return $backupCandidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return file_exists($path) ? $path : null;
|
||||||
|
}
|
||||||
|
|
||||||
public function selectAllAccounts(): void
|
public function selectAllAccounts(): void
|
||||||
{
|
{
|
||||||
$import = $this->getImport();
|
$import = $this->getImport();
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ use Filament\Actions\Action;
|
|||||||
use Filament\Actions\Concerns\InteractsWithActions;
|
use Filament\Actions\Concerns\InteractsWithActions;
|
||||||
use Filament\Actions\Contracts\HasActions;
|
use Filament\Actions\Contracts\HasActions;
|
||||||
use Filament\Forms\Components\Checkbox;
|
use Filament\Forms\Components\Checkbox;
|
||||||
use Filament\Forms\Components\FileUpload;
|
|
||||||
use Filament\Forms\Components\Radio;
|
use Filament\Forms\Components\Radio;
|
||||||
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Forms\Concerns\InteractsWithForms;
|
use Filament\Forms\Concerns\InteractsWithForms;
|
||||||
use Filament\Forms\Contracts\HasForms;
|
use Filament\Forms\Contracts\HasForms;
|
||||||
@@ -66,6 +66,10 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
|||||||
|
|
||||||
public ?string $remotePassword = null;
|
public ?string $remotePassword = null;
|
||||||
|
|
||||||
|
public ?string $localBackupPath = null;
|
||||||
|
|
||||||
|
public array $availableBackups = [];
|
||||||
|
|
||||||
public ?string $backupPath = null;
|
public ?string $backupPath = null;
|
||||||
|
|
||||||
public bool $importFiles = true;
|
public bool $importFiles = true;
|
||||||
@@ -111,6 +115,36 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
|||||||
{
|
{
|
||||||
$this->restoreFromSession();
|
$this->restoreFromSession();
|
||||||
$this->restoreFromImport();
|
$this->restoreFromImport();
|
||||||
|
|
||||||
|
if ($this->importMethod === 'backup_file') {
|
||||||
|
$this->loadLocalBackups();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatedImportMethod(): void
|
||||||
|
{
|
||||||
|
$this->remoteHost = null;
|
||||||
|
$this->remotePort = 2222;
|
||||||
|
$this->remoteUser = null;
|
||||||
|
$this->remotePassword = null;
|
||||||
|
|
||||||
|
$this->localBackupPath = null;
|
||||||
|
$this->backupPath = null;
|
||||||
|
$this->availableBackups = [];
|
||||||
|
|
||||||
|
if ($this->importMethod === 'backup_file') {
|
||||||
|
$this->loadLocalBackups();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatedLocalBackupPath(): void
|
||||||
|
{
|
||||||
|
if (! $this->localBackupPath) {
|
||||||
|
$this->backupPath = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->selectLocalBackup();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getForms(): array
|
protected function getForms(): array
|
||||||
@@ -175,20 +209,39 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
|||||||
->visible(fn (Get $get): bool => $get('importMethod') === 'remote_server'),
|
->visible(fn (Get $get): bool => $get('importMethod') === 'remote_server'),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
FileUpload::make('backupPath')
|
Section::make(__('Backup File'))
|
||||||
->label(__('DirectAdmin Backup Archive'))
|
->description(__('Upload your DirectAdmin backup archive to your backups folder, then select it here.'))
|
||||||
->helperText(__('Upload a .tar.gz DirectAdmin backup file.'))
|
->icon('heroicon-o-folder')
|
||||||
->disk('local')
|
->visible(fn (Get $get): bool => $get('importMethod') === 'backup_file')
|
||||||
->directory('imports/directadmin')
|
->headerActions([
|
||||||
->preserveFilenames()
|
Action::make('refreshLocalBackups')
|
||||||
->acceptedFileTypes([
|
->label(__('Refresh'))
|
||||||
'application/gzip',
|
->icon('heroicon-o-arrow-path')
|
||||||
'application/x-gzip',
|
->color('gray')
|
||||||
'application/x-tar',
|
->action('refreshLocalBackups'),
|
||||||
'application/octet-stream',
|
|
||||||
])
|
])
|
||||||
->required()
|
->schema([
|
||||||
->visible(fn (Get $get): bool => $get('importMethod') === 'backup_file'),
|
Select::make('localBackupPath')
|
||||||
|
->label(__('Backup File'))
|
||||||
|
->options(fn (): array => $this->getLocalBackupOptions())
|
||||||
|
->searchable()
|
||||||
|
->required(fn (Get $get): bool => $get('importMethod') === 'backup_file')
|
||||||
|
->live(),
|
||||||
|
Text::make(fn (): string => $this->backupPath
|
||||||
|
? __('Selected file: :file', ['file' => basename($this->backupPath)])
|
||||||
|
: __('No backup selected yet.'))
|
||||||
|
->color('gray'),
|
||||||
|
Text::make(fn (): string => ($user = $this->getUser())
|
||||||
|
? __('Upload the file to: /home/:user/backups', ['user' => $user->username])
|
||||||
|
: __('Upload the file to your /home/<user>/backups folder.'))
|
||||||
|
->color('gray'),
|
||||||
|
Text::make(__('Supported formats: .tar.gz, .tgz'))->color('gray'),
|
||||||
|
Text::make(fn (): string => ($user = $this->getUser())
|
||||||
|
? __('No backups found in /home/:user/backups. Upload a file there and click Refresh.', ['user' => $user->username])
|
||||||
|
: __('No backups found.'))
|
||||||
|
->color('gray')
|
||||||
|
->visible(fn (): bool => empty($this->availableBackups)),
|
||||||
|
]),
|
||||||
|
|
||||||
FormActions::make([
|
FormActions::make([
|
||||||
Action::make('discoverAccount')
|
Action::make('discoverAccount')
|
||||||
@@ -323,10 +376,13 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
|||||||
|
|
||||||
if ($this->importMethod === 'backup_file') {
|
if ($this->importMethod === 'backup_file') {
|
||||||
if (! $import->backup_path) {
|
if (! $import->backup_path) {
|
||||||
throw new Exception(__('Please upload a DirectAdmin backup archive.'));
|
throw new Exception(__('Please select a DirectAdmin backup archive.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$backupFullPath = Storage::disk('local')->path($import->backup_path);
|
$backupFullPath = $this->resolveBackupFullPath($import->backup_path);
|
||||||
|
if (! $backupFullPath) {
|
||||||
|
throw new Exception(__('Backup file not found: :path', ['path' => $import->backup_path]));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$remotePassword = $this->remotePassword;
|
$remotePassword = $this->remotePassword;
|
||||||
|
|
||||||
@@ -503,6 +559,8 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
|||||||
$this->remotePort = 2222;
|
$this->remotePort = 2222;
|
||||||
$this->remoteUser = null;
|
$this->remoteUser = null;
|
||||||
$this->remotePassword = null;
|
$this->remotePassword = null;
|
||||||
|
$this->localBackupPath = null;
|
||||||
|
$this->availableBackups = [];
|
||||||
$this->backupPath = null;
|
$this->backupPath = null;
|
||||||
$this->importFiles = true;
|
$this->importFiles = true;
|
||||||
$this->importDatabases = true;
|
$this->importDatabases = true;
|
||||||
@@ -515,6 +573,170 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
|||||||
return $this->agent ??= new AgentClient;
|
return $this->agent ??= new AgentClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getUser()
|
||||||
|
{
|
||||||
|
return Auth::user();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function loadLocalBackups(): void
|
||||||
|
{
|
||||||
|
$this->availableBackups = [];
|
||||||
|
|
||||||
|
$user = $this->getUser();
|
||||||
|
if (! $user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->getAgent()->send('file.list', [
|
||||||
|
'username' => $user->username,
|
||||||
|
'path' => 'backups',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! ($result['success'] ?? false)) {
|
||||||
|
$this->getAgent()->send('file.mkdir', [
|
||||||
|
'username' => $user->username,
|
||||||
|
'path' => 'backups',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result = $this->getAgent()->send('file.list', [
|
||||||
|
'username' => $user->username,
|
||||||
|
'path' => 'backups',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! ($result['success'] ?? false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = $result['items'] ?? [];
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if (($item['is_dir'] ?? false) === true) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = (string) ($item['name'] ?? '');
|
||||||
|
if (! preg_match('/\\.(tar\\.gz|tgz)$/i', $name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->availableBackups[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refreshLocalBackups(): void
|
||||||
|
{
|
||||||
|
$this->loadLocalBackups();
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->title(__('Backup list refreshed'))
|
||||||
|
->success()
|
||||||
|
->send();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getLocalBackupOptions(): array
|
||||||
|
{
|
||||||
|
$options = [];
|
||||||
|
|
||||||
|
foreach ($this->availableBackups as $item) {
|
||||||
|
$path = $item['path'] ?? null;
|
||||||
|
$name = $item['name'] ?? null;
|
||||||
|
if (! $path || ! $name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$size = $this->formatBytes((int) ($item['size'] ?? 0));
|
||||||
|
$options[$path] = "{$name} ({$size})";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function selectLocalBackup(): void
|
||||||
|
{
|
||||||
|
$user = $this->getUser();
|
||||||
|
if (! $user || ! $this->localBackupPath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$info = $this->getAgent()->send('file.info', [
|
||||||
|
'username' => $user->username,
|
||||||
|
'path' => $this->localBackupPath,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (! ($info['success'] ?? false)) {
|
||||||
|
Notification::make()
|
||||||
|
->title(__('Backup file not found'))
|
||||||
|
->body($info['error'] ?? __('Unable to read backup file'))
|
||||||
|
->danger()
|
||||||
|
->send();
|
||||||
|
$this->backupPath = null;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$details = $info['info'] ?? [];
|
||||||
|
if (! ($details['is_file'] ?? false)) {
|
||||||
|
Notification::make()
|
||||||
|
->title(__('Invalid backup selection'))
|
||||||
|
->body(__('Please select a backup file'))
|
||||||
|
->warning()
|
||||||
|
->send();
|
||||||
|
$this->backupPath = null;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->backupPath = "/home/{$user->username}/{$this->localBackupPath}";
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->title(__('Backup selected'))
|
||||||
|
->body(__('Selected :name (:size)', [
|
||||||
|
'name' => $details['name'] ?? basename($this->backupPath),
|
||||||
|
'size' => $this->formatBytes((int) ($details['size'] ?? 0)),
|
||||||
|
]))
|
||||||
|
->success()
|
||||||
|
->send();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function formatBytes(int $bytes): string
|
||||||
|
{
|
||||||
|
if ($bytes >= 1073741824) {
|
||||||
|
return number_format($bytes / 1073741824, 2) . ' GB';
|
||||||
|
}
|
||||||
|
if ($bytes >= 1048576) {
|
||||||
|
return number_format($bytes / 1048576, 2) . ' MB';
|
||||||
|
}
|
||||||
|
if ($bytes >= 1024) {
|
||||||
|
return number_format($bytes / 1024, 2) . ' KB';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bytes . ' B';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function resolveBackupFullPath(?string $path): ?string
|
||||||
|
{
|
||||||
|
$path = trim((string) ($path ?? ''));
|
||||||
|
if ($path === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_starts_with($path, '/') && file_exists($path)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
$localCandidate = Storage::disk('local')->path($path);
|
||||||
|
if (file_exists($localCandidate)) {
|
||||||
|
return $localCandidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
$backupCandidate = Storage::disk('backups')->path($path);
|
||||||
|
if (file_exists($backupCandidate)) {
|
||||||
|
return $backupCandidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return file_exists($path) ? $path : null;
|
||||||
|
}
|
||||||
|
|
||||||
protected function getImport(): ?ServerImport
|
protected function getImport(): ?ServerImport
|
||||||
{
|
{
|
||||||
if (! $this->importId) {
|
if (! $this->importId) {
|
||||||
@@ -597,6 +819,12 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
|||||||
|
|
||||||
$this->importMethod = (string) ($import->import_method ?? 'backup_file');
|
$this->importMethod = (string) ($import->import_method ?? 'backup_file');
|
||||||
$this->backupPath = $import->backup_path;
|
$this->backupPath = $import->backup_path;
|
||||||
|
if ($this->backupPath && ($user = $this->getUser())) {
|
||||||
|
$prefix = "/home/{$user->username}/";
|
||||||
|
if (str_starts_with($this->backupPath, $prefix)) {
|
||||||
|
$this->localBackupPath = ltrim(substr($this->backupPath, strlen($prefix)), '/');
|
||||||
|
}
|
||||||
|
}
|
||||||
$this->remoteHost = $import->remote_host;
|
$this->remoteHost = $import->remote_host;
|
||||||
$this->remotePort = (int) ($import->remote_port ?? 2222);
|
$this->remotePort = (int) ($import->remote_port ?? 2222);
|
||||||
$this->remoteUser = $import->remote_user;
|
$this->remoteUser = $import->remote_user;
|
||||||
@@ -612,4 +840,3 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
|||||||
$this->step1Complete = $import->accounts()->exists();
|
$this->step1Complete = $import->accounts()->exists();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,13 @@ return [
|
|||||||
'root' => '/tmp',
|
'root' => '/tmp',
|
||||||
'throw' => false,
|
'throw' => false,
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// Server-wide backups folder (created by install.sh)
|
||||||
|
'backups' => [
|
||||||
|
'driver' => 'local',
|
||||||
|
'root' => env('JABALI_BACKUPS_ROOT', '/var/backups/jabali'),
|
||||||
|
'throw' => false,
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
'links' => [
|
'links' => [
|
||||||
|
|||||||
@@ -2945,8 +2945,9 @@ EOF
|
|||||||
mkdir -p /var/backups/jabali
|
mkdir -p /var/backups/jabali
|
||||||
mkdir -p /var/backups/jabali/cpanel-migrations
|
mkdir -p /var/backups/jabali/cpanel-migrations
|
||||||
mkdir -p /var/backups/jabali/whm-migrations
|
mkdir -p /var/backups/jabali/whm-migrations
|
||||||
|
mkdir -p /var/backups/jabali/directadmin-migrations
|
||||||
chown -R $JABALI_USER:$JABALI_USER /var/backups/jabali
|
chown -R $JABALI_USER:$JABALI_USER /var/backups/jabali
|
||||||
chmod 755 /var/backups/jabali /var/backups/jabali/cpanel-migrations /var/backups/jabali/whm-migrations
|
chmod 755 /var/backups/jabali /var/backups/jabali/cpanel-migrations /var/backups/jabali/whm-migrations /var/backups/jabali/directadmin-migrations
|
||||||
|
|
||||||
log "Jabali Panel setup complete"
|
log "Jabali Panel setup complete"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user