Use backups folder for DirectAdmin backup restores

This commit is contained in:
2026-02-11 00:22:27 +02:00
parent e7920366d7
commit 13685615cb
5 changed files with 314 additions and 26 deletions

View File

@@ -248,8 +248,8 @@ class ImportProcessCommand extends Command
return;
}
$backupPath = Storage::disk('local')->path($import->backup_path);
if (!file_exists($backupPath)) {
$backupPath = $this->resolveBackupFullPath($import);
if (! $backupPath) {
$account->addLog("Warning: Backup file not found");
return;
}
@@ -323,8 +323,8 @@ class ImportProcessCommand extends Command
return;
}
$backupPath = Storage::disk('local')->path($import->backup_path);
if (!file_exists($backupPath)) {
$backupPath = $this->resolveBackupFullPath($import);
if (! $backupPath) {
return;
}
@@ -386,4 +386,28 @@ class ImportProcessCommand extends Command
$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;
}
}

View File

@@ -186,9 +186,9 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
FileUpload::make('backupPath')
->label(__('DirectAdmin Backup Archive'))
->helperText(__('Upload a .tar.gz DirectAdmin backup file.'))
->disk('local')
->directory('imports/directadmin')
->helperText(__('Upload a DirectAdmin backup file to the server backups folder.'))
->disk('backups')
->directory('directadmin-migrations')
->preserveFilenames()
->acceptedFileTypes([
'application/gzip',
@@ -198,6 +198,8 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
])
->required()
->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([
Action::make('discoverAccounts')
@@ -386,7 +388,10 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
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 {
$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
{
$import = $this->getImport();

View File

@@ -13,8 +13,8 @@ use Filament\Actions\Action;
use Filament\Actions\Concerns\InteractsWithActions;
use Filament\Actions\Contracts\HasActions;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Radio;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
@@ -66,6 +66,10 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
public ?string $remotePassword = null;
public ?string $localBackupPath = null;
public array $availableBackups = [];
public ?string $backupPath = null;
public bool $importFiles = true;
@@ -111,6 +115,36 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
{
$this->restoreFromSession();
$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
@@ -175,20 +209,39 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
->visible(fn (Get $get): bool => $get('importMethod') === 'remote_server'),
]),
FileUpload::make('backupPath')
->label(__('DirectAdmin Backup Archive'))
->helperText(__('Upload a .tar.gz DirectAdmin backup file.'))
->disk('local')
->directory('imports/directadmin')
->preserveFilenames()
->acceptedFileTypes([
'application/gzip',
'application/x-gzip',
'application/x-tar',
'application/octet-stream',
Section::make(__('Backup File'))
->description(__('Upload your DirectAdmin backup archive to your backups folder, then select it here.'))
->icon('heroicon-o-folder')
->visible(fn (Get $get): bool => $get('importMethod') === 'backup_file')
->headerActions([
Action::make('refreshLocalBackups')
->label(__('Refresh'))
->icon('heroicon-o-arrow-path')
->color('gray')
->action('refreshLocalBackups'),
])
->required()
->visible(fn (Get $get): bool => $get('importMethod') === 'backup_file'),
->schema([
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([
Action::make('discoverAccount')
@@ -323,10 +376,13 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
if ($this->importMethod === 'backup_file') {
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 {
$remotePassword = $this->remotePassword;
@@ -503,6 +559,8 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
$this->remotePort = 2222;
$this->remoteUser = null;
$this->remotePassword = null;
$this->localBackupPath = null;
$this->availableBackups = [];
$this->backupPath = null;
$this->importFiles = true;
$this->importDatabases = true;
@@ -515,6 +573,170 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
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
{
if (! $this->importId) {
@@ -597,6 +819,12 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
$this->importMethod = (string) ($import->import_method ?? 'backup_file');
$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->remotePort = (int) ($import->remote_port ?? 2222);
$this->remoteUser = $import->remote_user;
@@ -612,4 +840,3 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
$this->step1Complete = $import->accounts()->exists();
}
}

View File

@@ -37,6 +37,13 @@ return [
'root' => '/tmp',
'throw' => false,
],
// Server-wide backups folder (created by install.sh)
'backups' => [
'driver' => 'local',
'root' => env('JABALI_BACKUPS_ROOT', '/var/backups/jabali'),
'throw' => false,
],
],
'links' => [

View File

@@ -2945,8 +2945,9 @@ EOF
mkdir -p /var/backups/jabali
mkdir -p /var/backups/jabali/cpanel-migrations
mkdir -p /var/backups/jabali/whm-migrations
mkdir -p /var/backups/jabali/directadmin-migrations
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"
}