Support DirectAdmin .tar.zst backups
This commit is contained in:
@@ -18,6 +18,7 @@ use Illuminate\Support\Str;
|
||||
class ImportProcessCommand extends Command
|
||||
{
|
||||
protected $signature = 'import:process {import_id : The server import ID to process}';
|
||||
|
||||
protected $description = 'Process a server import job (cPanel/DirectAdmin migration)';
|
||||
|
||||
private ?AgentClient $agent = null;
|
||||
@@ -27,8 +28,9 @@ class ImportProcessCommand extends Command
|
||||
$importId = (int) $this->argument('import_id');
|
||||
|
||||
$import = ServerImport::with('accounts')->find($importId);
|
||||
if (!$import) {
|
||||
if (! $import) {
|
||||
$this->error("Import not found: $importId");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -43,6 +45,7 @@ class ImportProcessCommand extends Command
|
||||
'current_task' => null,
|
||||
]);
|
||||
$import->addError('No accounts selected for import');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -67,8 +70,8 @@ class ImportProcessCommand extends Command
|
||||
'status' => 'failed',
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
$account->addLog("Import failed: " . $e->getMessage());
|
||||
$import->addError("Account {$account->source_username}: " . $e->getMessage());
|
||||
$account->addLog('Import failed: '.$e->getMessage());
|
||||
$import->addError("Account {$account->source_username}: ".$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,10 +99,10 @@ class ImportProcessCommand extends Command
|
||||
'completed_at' => now(),
|
||||
'progress' => 100,
|
||||
]);
|
||||
$import->addLog("All accounts imported successfully");
|
||||
$import->addLog('All accounts imported successfully');
|
||||
}
|
||||
|
||||
$this->info("Import completed. Success: " . ($totalAccounts - $failedCount) . ", Failed: $failedCount");
|
||||
$this->info('Import completed. Success: '.($totalAccounts - $failedCount).", Failed: $failedCount");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -107,8 +110,9 @@ class ImportProcessCommand extends Command
|
||||
private function getAgent(): AgentClient
|
||||
{
|
||||
if ($this->agent === null) {
|
||||
$this->agent = new AgentClient();
|
||||
$this->agent = new AgentClient;
|
||||
}
|
||||
|
||||
return $this->agent;
|
||||
}
|
||||
|
||||
@@ -132,28 +136,28 @@ class ImportProcessCommand extends Command
|
||||
if ($account->main_domain) {
|
||||
$account->update(['current_task' => 'Creating domains...', 'progress' => 20]);
|
||||
$this->createDomains($account, $user);
|
||||
$account->addLog("Created domains");
|
||||
$account->addLog('Created domains');
|
||||
}
|
||||
|
||||
// Step 3: Import files
|
||||
if ($options['files'] ?? true) {
|
||||
$account->update(['current_task' => 'Importing files...', 'progress' => 40]);
|
||||
$this->importFiles($import, $account, $user);
|
||||
$account->addLog("Files imported");
|
||||
$account->addLog('Files imported');
|
||||
}
|
||||
|
||||
// Step 4: Import databases
|
||||
if (($options['databases'] ?? true) && !empty($account->databases)) {
|
||||
if (($options['databases'] ?? true) && ! empty($account->databases)) {
|
||||
$account->update(['current_task' => 'Importing databases...', 'progress' => 60]);
|
||||
$this->importDatabases($import, $account, $user);
|
||||
$account->addLog("Databases imported");
|
||||
$account->addLog('Databases imported');
|
||||
}
|
||||
|
||||
// Step 5: Import emails
|
||||
if (($options['emails'] ?? true) && !empty($account->email_accounts)) {
|
||||
if (($options['emails'] ?? true) && ! empty($account->email_accounts)) {
|
||||
$account->update(['current_task' => 'Importing email accounts...', 'progress' => 80]);
|
||||
$this->importEmails($import, $account, $user);
|
||||
$account->addLog("Email accounts imported");
|
||||
$account->addLog('Email accounts imported');
|
||||
}
|
||||
|
||||
$account->update([
|
||||
@@ -161,7 +165,7 @@ class ImportProcessCommand extends Command
|
||||
'progress' => 100,
|
||||
'current_task' => null,
|
||||
]);
|
||||
$account->addLog("Import completed successfully");
|
||||
$account->addLog('Import completed successfully');
|
||||
}
|
||||
|
||||
private function createUser(ServerImportAccount $account): User
|
||||
@@ -170,6 +174,7 @@ class ImportProcessCommand extends Command
|
||||
$existingUser = User::where('username', $account->target_username)->first();
|
||||
if ($existingUser) {
|
||||
$account->addLog("User already exists: {$account->target_username}");
|
||||
|
||||
return $existingUser;
|
||||
}
|
||||
|
||||
@@ -179,8 +184,8 @@ class ImportProcessCommand extends Command
|
||||
// Create user via agent
|
||||
$result = $this->getAgent()->createUser($account->target_username, $password);
|
||||
|
||||
if (!($result['success'] ?? false)) {
|
||||
throw new Exception("Failed to create system user: " . ($result['error'] ?? 'Unknown error'));
|
||||
if (! ($result['success'] ?? false)) {
|
||||
throw new Exception('Failed to create system user: '.($result['error'] ?? 'Unknown error'));
|
||||
}
|
||||
|
||||
// Create user in database
|
||||
@@ -191,7 +196,7 @@ class ImportProcessCommand extends Command
|
||||
'password' => Hash::make($password),
|
||||
]);
|
||||
|
||||
$account->addLog("Created user with temporary password. User should reset password.");
|
||||
$account->addLog('Created user with temporary password. User should reset password.');
|
||||
|
||||
return $user;
|
||||
}
|
||||
@@ -201,7 +206,7 @@ class ImportProcessCommand extends Command
|
||||
// Create main domain
|
||||
if ($account->main_domain) {
|
||||
$existingDomain = Domain::where('domain', $account->main_domain)->first();
|
||||
if (!$existingDomain) {
|
||||
if (! $existingDomain) {
|
||||
$result = $this->getAgent()->domainCreate($user->username, $account->main_domain);
|
||||
|
||||
if ($result['success'] ?? false) {
|
||||
@@ -213,7 +218,7 @@ class ImportProcessCommand extends Command
|
||||
]);
|
||||
$account->addLog("Created main domain: {$account->main_domain}");
|
||||
} else {
|
||||
$account->addLog("Warning: Failed to create main domain: " . ($result['error'] ?? 'Unknown'));
|
||||
$account->addLog('Warning: Failed to create main domain: '.($result['error'] ?? 'Unknown'));
|
||||
}
|
||||
} else {
|
||||
$account->addLog("Main domain already exists: {$account->main_domain}");
|
||||
@@ -223,7 +228,7 @@ class ImportProcessCommand extends Command
|
||||
// Create addon domains
|
||||
foreach ($account->addon_domains ?? [] as $domain) {
|
||||
$existingDomain = Domain::where('domain', $domain)->first();
|
||||
if (!$existingDomain) {
|
||||
if (! $existingDomain) {
|
||||
$result = $this->getAgent()->domainCreate($user->username, $domain);
|
||||
|
||||
if ($result['success'] ?? false) {
|
||||
@@ -243,31 +248,38 @@ class ImportProcessCommand extends Command
|
||||
|
||||
private function importFiles(ServerImport $import, ServerImportAccount $account, User $user): void
|
||||
{
|
||||
if ($import->import_method !== 'backup_file' || !$import->backup_path) {
|
||||
$account->addLog("File import skipped - not a backup file import");
|
||||
if ($import->import_method !== 'backup_file' || ! $import->backup_path) {
|
||||
$account->addLog('File import skipped - not a backup file import');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$backupPath = $this->resolveBackupFullPath($import);
|
||||
if (! $backupPath) {
|
||||
$account->addLog("Warning: Backup file not found");
|
||||
$account->addLog('Warning: Backup file not found');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$extractDir = "/tmp/import_{$import->id}_{$account->id}_" . time();
|
||||
if (!mkdir($extractDir, 0755, true)) {
|
||||
$account->addLog("Warning: Failed to create extraction directory");
|
||||
$extractDir = "/tmp/import_{$import->id}_{$account->id}_".time();
|
||||
if (! mkdir($extractDir, 0755, true)) {
|
||||
$account->addLog('Warning: Failed to create extraction directory');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$username = $account->source_username;
|
||||
$tarExtract = $this->getTarExtractCommandPrefix($backupPath);
|
||||
|
||||
if ($import->source_type === 'cpanel') {
|
||||
// Extract home directory from cPanel backup
|
||||
$cmd = "tar -xzf " . escapeshellarg($backupPath) . " -C " . escapeshellarg($extractDir) .
|
||||
$cmd = "{$tarExtract} ".escapeshellarg($backupPath).' -C '.escapeshellarg($extractDir).
|
||||
" --wildcards '*/{$username}/homedir/*' '*/homedir/*' 2>/dev/null";
|
||||
exec($cmd, $output, $code);
|
||||
if ($code !== 0) {
|
||||
$account->addLog('Warning: Failed to extract backup archive');
|
||||
}
|
||||
|
||||
// Find extracted files
|
||||
$homeDirs = glob("$extractDir/**/homedir", GLOB_ONLYDIR) ?:
|
||||
@@ -280,17 +292,20 @@ class ImportProcessCommand extends Command
|
||||
if (is_dir($publicHtml) && $account->main_domain) {
|
||||
$destDir = "/home/{$user->username}/domains/{$account->main_domain}/public";
|
||||
if (is_dir($destDir)) {
|
||||
exec("cp -r " . escapeshellarg($publicHtml) . "/* " . escapeshellarg($destDir) . "/ 2>&1");
|
||||
exec("chown -R " . escapeshellarg($user->username) . ":" . escapeshellarg($user->username) . " " . escapeshellarg($destDir) . " 2>&1");
|
||||
exec('cp -r '.escapeshellarg($publicHtml).'/* '.escapeshellarg($destDir).'/ 2>&1');
|
||||
exec('chown -R '.escapeshellarg($user->username).':'.escapeshellarg($user->username).' '.escapeshellarg($destDir).' 2>&1');
|
||||
$account->addLog("Copied public_html to {$account->main_domain}");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Extract from DirectAdmin backup
|
||||
$cmd = "tar -xzf " . escapeshellarg($backupPath) . " -C " . escapeshellarg($extractDir) .
|
||||
$cmd = "{$tarExtract} ".escapeshellarg($backupPath).' -C '.escapeshellarg($extractDir).
|
||||
" --wildcards 'domains/*' 'backup/domains/*' 2>/dev/null";
|
||||
exec($cmd, $output, $code);
|
||||
if ($code !== 0) {
|
||||
$account->addLog('Warning: Failed to extract DirectAdmin backup archive');
|
||||
}
|
||||
|
||||
// Find domain directories
|
||||
$domainDirs = glob("$extractDir/**/domains/*", GLOB_ONLYDIR) ?:
|
||||
@@ -303,8 +318,8 @@ class ImportProcessCommand extends Command
|
||||
if (is_dir($publicHtml)) {
|
||||
$destDir = "/home/{$user->username}/domains/{$domain}/public";
|
||||
if (is_dir($destDir)) {
|
||||
exec("cp -r " . escapeshellarg($publicHtml) . "/* " . escapeshellarg($destDir) . "/ 2>&1");
|
||||
exec("chown -R " . escapeshellarg($user->username) . ":" . escapeshellarg($user->username) . " " . escapeshellarg($destDir) . " 2>&1");
|
||||
exec('cp -r '.escapeshellarg($publicHtml).'/* '.escapeshellarg($destDir).'/ 2>&1');
|
||||
exec('chown -R '.escapeshellarg($user->username).':'.escapeshellarg($user->username).' '.escapeshellarg($destDir).' 2>&1');
|
||||
$account->addLog("Copied files for domain: {$domain}");
|
||||
}
|
||||
}
|
||||
@@ -312,14 +327,15 @@ class ImportProcessCommand extends Command
|
||||
}
|
||||
} finally {
|
||||
// Cleanup
|
||||
exec("rm -rf " . escapeshellarg($extractDir));
|
||||
exec('rm -rf '.escapeshellarg($extractDir));
|
||||
}
|
||||
}
|
||||
|
||||
private function importDatabases(ServerImport $import, ServerImportAccount $account, User $user): void
|
||||
{
|
||||
if ($import->import_method !== 'backup_file' || !$import->backup_path) {
|
||||
$account->addLog("Database import skipped - not a backup file import");
|
||||
if ($import->import_method !== 'backup_file' || ! $import->backup_path) {
|
||||
$account->addLog('Database import skipped - not a backup file import');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -328,51 +344,98 @@ class ImportProcessCommand extends Command
|
||||
return;
|
||||
}
|
||||
|
||||
$extractDir = "/tmp/import_db_{$import->id}_{$account->id}_" . time();
|
||||
if (!mkdir($extractDir, 0755, true)) {
|
||||
$extractDir = "/tmp/import_db_{$import->id}_{$account->id}_".time();
|
||||
if (! mkdir($extractDir, 0755, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$tarExtract = $this->getTarExtractCommandPrefix($backupPath);
|
||||
|
||||
// Extract MySQL dumps
|
||||
if ($import->source_type === 'cpanel') {
|
||||
$cmd = "tar -xzf " . escapeshellarg($backupPath) . " -C " . escapeshellarg($extractDir) .
|
||||
" --wildcards '*/mysql/*.sql' 'mysql/*.sql' 2>/dev/null";
|
||||
$cmd = "{$tarExtract} ".escapeshellarg($backupPath).' -C '.escapeshellarg($extractDir).
|
||||
" --wildcards '*/mysql/*.sql*' 'mysql/*.sql*' 2>/dev/null";
|
||||
} else {
|
||||
$cmd = "tar -xzf " . escapeshellarg($backupPath) . " -C " . escapeshellarg($extractDir) .
|
||||
" --wildcards 'backup/databases/*.sql' 'databases/*.sql' 2>/dev/null";
|
||||
$cmd = "{$tarExtract} ".escapeshellarg($backupPath).' -C '.escapeshellarg($extractDir).
|
||||
" --wildcards 'backup/databases/*.sql*' 'databases/*.sql*' 2>/dev/null";
|
||||
}
|
||||
exec($cmd, $output, $code);
|
||||
if ($code !== 0) {
|
||||
$account->addLog('Warning: Failed to extract database dumps from backup archive');
|
||||
}
|
||||
|
||||
// Find SQL files
|
||||
$sqlFiles = [];
|
||||
exec("find " . escapeshellarg($extractDir) . " -name '*.sql' -type f 2>/dev/null", $sqlFiles);
|
||||
exec('find '.escapeshellarg($extractDir)." -type f \\( -name '*.sql' -o -name '*.sql.gz' -o -name '*.sql.zst' \\) 2>/dev/null", $sqlFiles);
|
||||
|
||||
foreach ($sqlFiles as $sqlFile) {
|
||||
$dbName = basename($sqlFile, '.sql');
|
||||
$fileName = basename($sqlFile);
|
||||
$dbName = preg_replace('/\\.(sql|sql\\.gz|sql\\.zst)$/i', '', $fileName);
|
||||
|
||||
if (! is_string($dbName) || $dbName === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create database name with user prefix
|
||||
$newDbName = substr($user->username . '_' . preg_replace('/^[^_]+_/', '', $dbName), 0, 64);
|
||||
$newDbName = substr($user->username.'_'.preg_replace('/^[^_]+_/', '', $dbName), 0, 64);
|
||||
|
||||
// Create database via agent
|
||||
$result = $this->getAgent()->mysqlCreateDatabase($user->username, $newDbName);
|
||||
|
||||
if ($result['success'] ?? false) {
|
||||
// Import data
|
||||
$cmd = "mysql " . escapeshellarg($newDbName) . " < " . escapeshellarg($sqlFile) . " 2>&1";
|
||||
exec($cmd, $importOutput, $importCode);
|
||||
$sqlToImport = $sqlFile;
|
||||
$tmpSql = null;
|
||||
|
||||
if ($importCode === 0) {
|
||||
$account->addLog("Imported database: {$newDbName}");
|
||||
} else {
|
||||
$account->addLog("Warning: Database created but import failed: {$newDbName}");
|
||||
try {
|
||||
$lower = strtolower($sqlFile);
|
||||
|
||||
if (str_ends_with($lower, '.sql.gz')) {
|
||||
$tmpSql = $extractDir.'/import_'.$account->id.'_'.$dbName.'_'.uniqid('', true).'.sql';
|
||||
$decompressCmd = 'gzip -dc '.escapeshellarg($sqlFile).' > '.escapeshellarg($tmpSql).' 2>/dev/null';
|
||||
exec($decompressCmd, $decompressOutput, $decompressCode);
|
||||
|
||||
if ($decompressCode !== 0) {
|
||||
$account->addLog("Warning: Failed to decompress database dump: {$fileName}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$sqlToImport = $tmpSql;
|
||||
} elseif (str_ends_with($lower, '.sql.zst')) {
|
||||
$tmpSql = $extractDir.'/import_'.$account->id.'_'.$dbName.'_'.uniqid('', true).'.sql';
|
||||
$decompressCmd = 'zstd -dc '.escapeshellarg($sqlFile).' > '.escapeshellarg($tmpSql).' 2>/dev/null';
|
||||
exec($decompressCmd, $decompressOutput, $decompressCode);
|
||||
|
||||
if ($decompressCode !== 0) {
|
||||
$account->addLog("Warning: Failed to decompress database dump: {$fileName}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$sqlToImport = $tmpSql;
|
||||
}
|
||||
|
||||
// Import data
|
||||
$cmd = 'mysql '.escapeshellarg($newDbName).' < '.escapeshellarg($sqlToImport).' 2>&1';
|
||||
exec($cmd, $importOutput, $importCode);
|
||||
|
||||
if ($importCode === 0) {
|
||||
$account->addLog("Imported database: {$newDbName}");
|
||||
} else {
|
||||
$account->addLog("Warning: Database created but import failed: {$newDbName}");
|
||||
}
|
||||
} finally {
|
||||
if ($tmpSql && file_exists($tmpSql)) {
|
||||
@unlink($tmpSql);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$account->addLog("Warning: Failed to create database: {$newDbName}");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
exec("rm -rf " . escapeshellarg($extractDir));
|
||||
exec('rm -rf '.escapeshellarg($extractDir));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,7 +447,7 @@ class ImportProcessCommand extends Command
|
||||
$account->addLog("Email account found (not imported): {$emailAccount}@{$account->main_domain}");
|
||||
}
|
||||
|
||||
$account->addLog("Note: Email accounts must be recreated manually");
|
||||
$account->addLog('Note: Email accounts must be recreated manually');
|
||||
}
|
||||
|
||||
private function resolveBackupFullPath(ServerImport $import): ?string
|
||||
@@ -410,4 +473,19 @@ class ImportProcessCommand extends Command
|
||||
|
||||
return file_exists($path) ? $path : null;
|
||||
}
|
||||
|
||||
private function getTarExtractCommandPrefix(string $archivePath): string
|
||||
{
|
||||
$archivePath = strtolower($archivePath);
|
||||
|
||||
if (str_ends_with($archivePath, '.tar.zst')) {
|
||||
return 'tar --zstd -xf';
|
||||
}
|
||||
|
||||
if (str_ends_with($archivePath, '.tar.gz') || str_ends_with($archivePath, '.tgz')) {
|
||||
return 'tar -xzf';
|
||||
}
|
||||
|
||||
return 'tar -xf';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,8 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
|
||||
public ?string $backupPath = null;
|
||||
|
||||
public ?string $backupFilePath = null;
|
||||
|
||||
public bool $importFiles = true;
|
||||
|
||||
public bool $importDatabases = true;
|
||||
@@ -146,7 +148,7 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
Grid::make(['default' => 1, 'sm' => 2])->schema([
|
||||
TextInput::make('name')
|
||||
->label(__('Import Name'))
|
||||
->default(fn (): string => $this->name ?: ('DirectAdmin Import ' . now()->format('Y-m-d H:i')))
|
||||
->default(fn (): string => $this->name ?: ('DirectAdmin Import '.now()->format('Y-m-d H:i')))
|
||||
->maxLength(255)
|
||||
->required(),
|
||||
Radio::make('importMethod')
|
||||
@@ -186,17 +188,23 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
|
||||
FileUpload::make('backupPath')
|
||||
->label(__('DirectAdmin Backup Archive'))
|
||||
->helperText(__('Upload a DirectAdmin backup file to the server backups folder.'))
|
||||
->helperText(__('Upload a DirectAdmin backup archive (usually .tar.zst) to the server backups folder.'))
|
||||
->disk('backups')
|
||||
->directory('directadmin-migrations')
|
||||
->preserveFilenames()
|
||||
->acceptedFileTypes([
|
||||
'application/gzip',
|
||||
'application/x-gzip',
|
||||
'application/zstd',
|
||||
'application/x-zstd',
|
||||
'application/x-tar',
|
||||
'application/octet-stream',
|
||||
])
|
||||
->required()
|
||||
->visible(fn (Get $get): bool => $get('importMethod') === 'backup_file'),
|
||||
TextInput::make('backupFilePath')
|
||||
->label(__('Backup File Path'))
|
||||
->placeholder('/var/backups/jabali/directadmin-migrations/user.tar.zst')
|
||||
->helperText(__('Use this if the backup file already exists on the server.'))
|
||||
->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'),
|
||||
@@ -385,7 +393,7 @@ 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 upload a DirectAdmin backup archive or enter its full path.'));
|
||||
}
|
||||
|
||||
$backupFullPath = $this->resolveBackupFullPath($import->backup_path);
|
||||
@@ -554,6 +562,7 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
->title(__('Import job not found'))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -564,6 +573,7 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
->body(__('Please select at least one account to migrate.'))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -573,6 +583,7 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
->body(__('For now, please download a DirectAdmin backup archive and use the "Backup File" method.'))
|
||||
->warning()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -589,6 +600,7 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
->body((string) ($result['error'] ?? __('Unknown error')))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -617,6 +629,7 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
$this->remoteUser = null;
|
||||
$this->remotePassword = null;
|
||||
$this->backupPath = null;
|
||||
$this->backupFilePath = null;
|
||||
$this->importFiles = true;
|
||||
$this->importDatabases = true;
|
||||
$this->importEmails = true;
|
||||
@@ -641,7 +654,7 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
{
|
||||
$name = trim((string) ($this->name ?: ''));
|
||||
if ($name === '') {
|
||||
$name = 'DirectAdmin Import ' . now()->format('Y-m-d H:i');
|
||||
$name = 'DirectAdmin Import '.now()->format('Y-m-d H:i');
|
||||
}
|
||||
|
||||
$attributes = [
|
||||
@@ -660,7 +673,11 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
];
|
||||
|
||||
if ($this->importMethod === 'backup_file') {
|
||||
$attributes['backup_path'] = $this->backupPath;
|
||||
$backupPath = filled($this->backupFilePath)
|
||||
? trim((string) $this->backupFilePath)
|
||||
: $this->backupPath;
|
||||
|
||||
$attributes['backup_path'] = $backupPath ?: null;
|
||||
$attributes['remote_host'] = null;
|
||||
$attributes['remote_port'] = null;
|
||||
$attributes['remote_user'] = null;
|
||||
@@ -743,7 +760,16 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
|
||||
$this->name = $import->name;
|
||||
$this->importMethod = (string) ($import->import_method ?? 'remote_server');
|
||||
$this->backupPath = $import->backup_path;
|
||||
|
||||
$backupPath = is_string($import->backup_path) ? trim($import->backup_path) : null;
|
||||
if ($backupPath && str_starts_with($backupPath, '/')) {
|
||||
$this->backupFilePath = $backupPath;
|
||||
$this->backupPath = null;
|
||||
} else {
|
||||
$this->backupPath = $backupPath;
|
||||
$this->backupFilePath = null;
|
||||
}
|
||||
|
||||
$this->remoteHost = $import->remote_host;
|
||||
$this->remotePort = (int) ($import->remote_port ?? 2222);
|
||||
$this->remoteUser = $import->remote_user;
|
||||
|
||||
@@ -141,6 +141,7 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
{
|
||||
if (! $this->localBackupPath) {
|
||||
$this->backupPath = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -235,7 +236,7 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
? __('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(__('Supported formats: .tar.zst, .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.'))
|
||||
@@ -497,6 +498,7 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
->title(__('Import job not found'))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -506,6 +508,7 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
->title(__('No account selected'))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -515,6 +518,7 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
->body(__('For now, please download a DirectAdmin backup archive and use the "Backup File" method.'))
|
||||
->warning()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -531,6 +535,7 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
->body((string) ($result['error'] ?? __('Unknown error')))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -615,7 +620,7 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
}
|
||||
|
||||
$name = (string) ($item['name'] ?? '');
|
||||
if (! preg_match('/\\.(tar\\.gz|tgz)$/i', $name)) {
|
||||
if (! preg_match('/\\.(tar\\.zst|tar\\.gz|tgz)$/i', $name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -701,16 +706,16 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
protected function formatBytes(int $bytes): string
|
||||
{
|
||||
if ($bytes >= 1073741824) {
|
||||
return number_format($bytes / 1073741824, 2) . ' GB';
|
||||
return number_format($bytes / 1073741824, 2).' GB';
|
||||
}
|
||||
if ($bytes >= 1048576) {
|
||||
return number_format($bytes / 1048576, 2) . ' MB';
|
||||
return number_format($bytes / 1048576, 2).' MB';
|
||||
}
|
||||
if ($bytes >= 1024) {
|
||||
return number_format($bytes / 1024, 2) . ' KB';
|
||||
return number_format($bytes / 1024, 2).' KB';
|
||||
}
|
||||
|
||||
return $bytes . ' B';
|
||||
return $bytes.' B';
|
||||
}
|
||||
|
||||
protected function resolveBackupFullPath(?string $path): ?string
|
||||
@@ -749,7 +754,7 @@ class DirectAdminMigration extends Page implements HasActions, HasForms
|
||||
protected function upsertImportForDiscovery(): ServerImport
|
||||
{
|
||||
$user = Auth::user();
|
||||
$name = $user ? ('DirectAdmin Import - ' . $user->username . ' - ' . now()->format('Y-m-d H:i')) : ('DirectAdmin Import ' . now()->format('Y-m-d H:i'));
|
||||
$name = $user ? ('DirectAdmin Import - '.$user->username.' - '.now()->format('Y-m-d H:i')) : ('DirectAdmin Import '.now()->format('Y-m-d H:i'));
|
||||
|
||||
$attributes = [
|
||||
'name' => $name,
|
||||
|
||||
@@ -12681,23 +12681,30 @@ function discoverDirectAdminBackup(string $backupPath, string $extractDir): arra
|
||||
|
||||
$accounts = [];
|
||||
|
||||
$tarArgs = '';
|
||||
if (preg_match('/\\.tar\\.zst$/i', $backupPath)) {
|
||||
$tarArgs = '--zstd';
|
||||
} elseif (preg_match('/\\.(tar\\.gz|tgz)$/i', $backupPath)) {
|
||||
$tarArgs = '-I pigz';
|
||||
}
|
||||
|
||||
// List archive contents
|
||||
$cmd = "tar -I pigz -tf " . escapeshellarg($backupPath) . " 2>/dev/null | head -500";
|
||||
$cmd = "tar $tarArgs -tf " . escapeshellarg($backupPath) . " 2>/dev/null | head -500";
|
||||
exec($cmd, $fileList, $code);
|
||||
|
||||
if ($code !== 0) {
|
||||
throw new Exception('Failed to read backup file. Make sure it is a valid tar.gz archive.');
|
||||
throw new Exception('Failed to read backup file. Make sure it is a valid DirectAdmin archive (.tar.zst, .tar.gz, .tgz).');
|
||||
}
|
||||
|
||||
$fileListStr = implode("\n", $fileList);
|
||||
|
||||
// DirectAdmin backup structure: backup/user.conf, domains/, databases/
|
||||
// Or: user.username.tar.gz containing the above
|
||||
// Or: user.username.tar.zst (or .tar.gz) containing the above
|
||||
|
||||
// Check for user.conf file (single user backup)
|
||||
if (preg_match('/(backup\/)?user\.conf/', $fileListStr)) {
|
||||
// Extract user.conf and domains list
|
||||
$extractCmd = "tar -I pigz -xf " . escapeshellarg($backupPath) . " -C " . escapeshellarg($extractDir) . " --wildcards 'backup/user.conf' 'user.conf' 'domains/*' 'backup/domains/*' 2>/dev/null";
|
||||
$extractCmd = "tar $tarArgs -xf " . escapeshellarg($backupPath) . " -C " . escapeshellarg($extractDir) . " --wildcards 'backup/user.conf' 'user.conf' 'domains/*' 'backup/domains/*' 2>/dev/null";
|
||||
exec($extractCmd);
|
||||
|
||||
$userConf = null;
|
||||
@@ -12718,18 +12725,25 @@ function discoverDirectAdminBackup(string $backupPath, string $extractDir): arra
|
||||
|
||||
// Check for multiple user backups (full server backup)
|
||||
foreach ($fileList as $file) {
|
||||
if (preg_match('/user\.([a-z0-9_]+)\.tar\.gz/i', $file, $matches)) {
|
||||
if (preg_match('/user\\.([a-z0-9_]+)\\.(?:tar\\.zst|tar\\.gz|tgz)$/i', $file, $matches)) {
|
||||
$username = $matches[1];
|
||||
|
||||
// Extract just this user's backup
|
||||
$innerBackup = $file;
|
||||
exec("tar -I pigz -xf " . escapeshellarg($backupPath) . " -C " . escapeshellarg($extractDir) . " " . escapeshellarg($innerBackup) . " 2>/dev/null");
|
||||
exec("tar $tarArgs -xf " . escapeshellarg($backupPath) . " -C " . escapeshellarg($extractDir) . " " . escapeshellarg($innerBackup) . " 2>/dev/null");
|
||||
|
||||
$innerPath = "$extractDir/$innerBackup";
|
||||
if (file_exists($innerPath)) {
|
||||
$innerTarArgs = '';
|
||||
if (preg_match('/\\.tar\\.zst$/i', $innerPath)) {
|
||||
$innerTarArgs = '--zstd';
|
||||
} elseif (preg_match('/\\.(tar\\.gz|tgz)$/i', $innerPath)) {
|
||||
$innerTarArgs = '-I pigz';
|
||||
}
|
||||
|
||||
$innerExtract = "$extractDir/$username";
|
||||
mkdir($innerExtract, 0755, true);
|
||||
exec("tar -I pigz -xf " . escapeshellarg($innerPath) . " -C " . escapeshellarg($innerExtract) . " --wildcards 'backup/user.conf' 'user.conf' 2>/dev/null");
|
||||
exec("tar $innerTarArgs -xf " . escapeshellarg($innerPath) . " -C " . escapeshellarg($innerExtract) . " --wildcards 'backup/user.conf' 'user.conf' 2>/dev/null");
|
||||
|
||||
$userConf = glob("$innerExtract/*/user.conf")[0] ?? glob("$innerExtract/user.conf")[0] ?? null;
|
||||
if ($userConf) {
|
||||
|
||||
Reference in New Issue
Block a user