Add Arch Linux server support and fix package sanitization

- Add Arch Linux (pacman) support to server operations: CheckUpdates, InstallDocker, InstallPrerequisites, UpdatePackage
- Implement parsePacmanOutput() to parse 'pacman -Qu' output format
- Add security improvement: package name sanitization to prevent command injection
- Initialize variables in CheckUpdates to prevent undefined variable errors in catch block
- Use proper Arch pacman flags: -Syu for full system upgrade before operations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai 2025-12-08 09:02:00 +01:00
parent a5b13b57cc
commit 9bb77da9a4
4 changed files with 94 additions and 3 deletions

View file

@ -13,6 +13,9 @@ class CheckUpdates
public function handle(Server $server)
{
$osId = 'unknown';
$packageManager = null;
try {
if ($server->serverStatus() === false) {
return [
@ -93,6 +96,16 @@ public function handle(Server $server)
$out['osId'] = $osId;
$out['package_manager'] = $packageManager;
return $out;
case 'pacman':
// Sync database first, then check for updates
// Using -Sy to refresh package database before querying available updates
instant_remote_process(['pacman -Sy'], $server);
$output = instant_remote_process(['pacman -Qu 2>/dev/null'], $server);
$out = $this->parsePacmanOutput($output);
$out['osId'] = $osId;
$out['package_manager'] = $packageManager;
return $out;
default:
return [
@ -219,4 +232,45 @@ private function parseAptOutput(string $output): array
'updates' => $updates,
];
}
private function parsePacmanOutput(string $output): array
{
$updates = [];
$unparsedLines = [];
$lines = explode("\n", $output);
foreach ($lines as $line) {
if (empty($line)) {
continue;
}
// Format: package current_version -> new_version
if (preg_match('/^(\S+)\s+(\S+)\s+->\s+(\S+)$/', $line, $matches)) {
$updates[] = [
'package' => $matches[1],
'current_version' => $matches[2],
'new_version' => $matches[3],
'architecture' => 'unknown',
'repository' => 'unknown',
];
} else {
// Log unmatched lines for debugging purposes
$unparsedLines[] = $line;
}
}
$result = [
'total_updates' => count($updates),
'updates' => $updates,
];
// Include unparsed lines in the result for debugging if any exist
if (! empty($unparsedLines)) {
$result['unparsed_lines'] = $unparsedLines;
\Illuminate\Support\Facades\Log::debug('Pacman output contained unparsed lines', [
'unparsed_lines' => $unparsedLines,
]);
}
return $result;
}
}

View file

@ -78,6 +78,8 @@ public function handle(Server $server)
$command = $command->merge([$this->getRhelDockerInstallCommand()]);
} elseif ($supported_os_type->contains('sles')) {
$command = $command->merge([$this->getSuseDockerInstallCommand()]);
} elseif ($supported_os_type->contains('arch')) {
$command = $command->merge([$this->getArchDockerInstallCommand()]);
} else {
$command = $command->merge([$this->getGenericDockerInstallCommand()]);
}
@ -150,4 +152,14 @@ private function getGenericDockerInstallCommand(): string
{
return "curl https://releases.rancher.com/install-docker/{$this->dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$this->dockerVersion}";
}
private function getArchDockerInstallCommand(): string
{
// Use -Syu to perform full system upgrade before installing Docker
// Partial upgrades (-Sy without -u) are discouraged on Arch Linux
// as they can lead to broken dependencies and system instability
return 'pacman -Syu --noconfirm docker docker-compose && '.
'systemctl enable docker.service && '.
'systemctl start docker.service';
}
}

View file

@ -46,6 +46,13 @@ public function handle(Server $server)
'command -v git >/dev/null || zypper install -y git',
'command -v jq >/dev/null || zypper install -y jq',
]);
} elseif ($supported_os_type->contains('arch')) {
// Use -Syu for full system upgrade to avoid partial upgrade issues on Arch Linux
// --needed flag skips packages that are already installed and up-to-date
$command = $command->merge([
"echo 'Installing Prerequisites for Arch Linux...'",
'pacman -Syu --noconfirm --needed curl wget git jq',
]);
} else {
throw new \Exception('Unsupported OS type for prerequisites installation');
}

View file

@ -20,18 +20,36 @@ public function handle(Server $server, string $osId, ?string $package = null, ?s
'error' => 'Server is not reachable or not ready.',
];
}
// Sanitize package name to prevent command injection
// Only allow alphanumeric characters, hyphens, underscores, periods, plus signs, and colons
// These are valid characters in package names across most package managers
$sanitizedPackage = '';
if ($package !== null && ! $all) {
if (! preg_match('/^[a-zA-Z0-9._+:-]+$/', $package)) {
return [
'error' => 'Invalid package name. Package names can only contain alphanumeric characters, hyphens, underscores, periods, plus signs, and colons.',
];
}
$sanitizedPackage = escapeshellarg($package);
}
switch ($packageManager) {
case 'zypper':
$commandAll = 'zypper update -y';
$commandInstall = 'zypper install -y '.$package;
$commandInstall = 'zypper install -y '.$sanitizedPackage;
break;
case 'dnf':
$commandAll = 'dnf update -y';
$commandInstall = 'dnf update -y '.$package;
$commandInstall = 'dnf update -y '.$sanitizedPackage;
break;
case 'apt':
$commandAll = 'apt update && apt upgrade -y';
$commandInstall = 'apt install -y '.$package;
$commandInstall = 'apt install -y '.$sanitizedPackage;
break;
case 'pacman':
$commandAll = 'pacman -Syu --noconfirm';
$commandInstall = 'pacman -S --noconfirm '.$sanitizedPackage;
break;
default:
return [