diff --git a/app/Actions/Server/InstallDocker.php b/app/Actions/Server/InstallDocker.php index d718d37358..e26de56ad4 100644 --- a/app/Actions/Server/InstallDocker.php +++ b/app/Actions/Server/InstallDocker.php @@ -80,6 +80,8 @@ public function handle(Server $server) $command = $command->merge([$this->getSuseDockerInstallCommand()]); } elseif ($supported_os_type->contains('arch')) { $command = $command->merge([$this->getArchDockerInstallCommand()]); + } elseif ($supported_os_type->contains('alpine')) { + $command = $command->merge([$this->getAlpineDockerInstallCommand()]); } else { $command = $command->merge([$this->getGenericDockerInstallCommand()]); } @@ -94,8 +96,8 @@ public function handle(Server $server) "jq -s '.[0] * .[1]' /etc/docker/daemon.json.coolify /etc/docker/daemon.json | tee /etc/docker/daemon.json.appended > /dev/null", 'mv /etc/docker/daemon.json.appended /etc/docker/daemon.json', "echo 'Restarting Docker Engine...'", - 'systemctl enable docker >/dev/null 2>&1 || true', - 'systemctl restart docker', + 'command -v systemctl >/dev/null 2>&1 && systemctl enable docker >/dev/null 2>&1 || command -v rc-update >/dev/null 2>&1 && rc-update add docker default >/dev/null 2>&1 || true', + 'command -v systemctl >/dev/null 2>&1 && systemctl restart docker || command -v rc-service >/dev/null 2>&1 && rc-service docker restart || command -v service >/dev/null 2>&1 && service docker restart || true', ]); if ($server->isSwarm()) { $command = $command->merge([ @@ -159,6 +161,13 @@ private function getArchDockerInstallCommand(): string 'systemctl start docker.service'; } + private function getAlpineDockerInstallCommand(): string + { + return 'apk add --no-cache docker docker-cli-compose && '. + 'command -v rc-update >/dev/null 2>&1 && rc-update add docker default >/dev/null 2>&1 || true && '. + 'command -v rc-service >/dev/null 2>&1 && rc-service docker start || command -v service >/dev/null 2>&1 && service docker start || true'; + } + private function getGenericDockerInstallCommand(): string { return "curl --max-time 300 --retry 3 https://releases.rancher.com/install-docker/{$this->dockerVersion}.sh | sh || curl --max-time 300 --retry 3 https://get.docker.com | sh -s -- --version {$this->dockerVersion}"; diff --git a/app/Actions/Server/InstallPrerequisites.php b/app/Actions/Server/InstallPrerequisites.php index 84be7f2068..fcc71a79a9 100644 --- a/app/Actions/Server/InstallPrerequisites.php +++ b/app/Actions/Server/InstallPrerequisites.php @@ -53,6 +53,12 @@ public function handle(Server $server) "echo 'Installing Prerequisites for Arch Linux...'", 'pacman -Syu --noconfirm --needed curl wget git jq', ]); + } elseif ($supported_os_type->contains('alpine')) { + $command = $command->merge([ + "echo 'Installing Prerequisites for Alpine Linux...'", + 'apk update', + 'apk add --no-cache curl wget git jq', + ]); } else { throw new \Exception('Unsupported OS type for prerequisites installation'); } diff --git a/app/Models/Server.php b/app/Models/Server.php index d693aea6d0..5c11f5c0ef 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -1050,19 +1050,30 @@ public function validateOS(): bool|Stringable $item = str($line)->trim(); $collectedData->put($item->before('=')->value(), $item->after('=')->lower()->replace('"', '')->value()); } - $ID = data_get($collectedData, 'ID'); - // $ID_LIKE = data_get($collectedData, 'ID_LIKE'); - // $VERSION_ID = data_get($collectedData, 'VERSION_ID'); - $supported = collect(SUPPORTED_OS)->filter(function ($supportedOs) use ($ID) { - if (str($supportedOs)->contains($ID)) { - return str($ID); - } + return $this->resolveSupportedOsType($collectedData); + } + + public function resolveSupportedOsType($osReleaseData): bool|Stringable + { + $ID = data_get($osReleaseData, 'ID'); + $ID_LIKE = data_get($osReleaseData, 'ID_LIKE', ''); + + $detectedIds = collect([$ID]) + ->merge(str($ID_LIKE)->explode(' ')) + ->map(fn ($item) => str($item)->trim()->lower()->value()) + ->filter() + ->unique(); + + $supported = collect(SUPPORTED_OS)->first(function ($supportedOs) use ($detectedIds) { + $supportedIds = str($supportedOs) + ->explode(' ') + ->map(fn ($item) => str($item)->trim()->lower()->value()) + ->filter(); + + return $detectedIds->intersect($supportedIds)->isNotEmpty(); }); - if ($supported->count() === 1) { - return str($supported->first()); - } else { - return false; - } + + return $supported ? str($supported) : false; } public function isTerminalEnabled() diff --git a/tests/Unit/Actions/Server/InstallDockerAlpineCommandTest.php b/tests/Unit/Actions/Server/InstallDockerAlpineCommandTest.php new file mode 100644 index 0000000000..752810b500 --- /dev/null +++ b/tests/Unit/Actions/Server/InstallDockerAlpineCommandTest.php @@ -0,0 +1,18 @@ +getMethod('getAlpineDockerInstallCommand'); + $method->setAccessible(true); + + $command = $method->invoke($action); + + expect($command)->toBeString() + ->and($command)->toContain('apk add --no-cache docker docker-cli-compose') + ->and($command)->toContain('rc-update add docker default') + ->and($command)->toContain('rc-service docker start'); +}); diff --git a/tests/Unit/ServerResolveSupportedOsTypeTest.php b/tests/Unit/ServerResolveSupportedOsTypeTest.php new file mode 100644 index 0000000000..809bf4fbe5 --- /dev/null +++ b/tests/Unit/ServerResolveSupportedOsTypeTest.php @@ -0,0 +1,63 @@ +resolveSupportedOsType(collect([ + 'ID' => 'debian', + 'ID_LIKE' => '', + ])); + + expect($result)->not->toBeFalse() + ->and((string) $result)->toContain('debian'); +}); + +it('detects alpine by ID', function () { + $server = new Server; + + $result = $server->resolveSupportedOsType(collect([ + 'ID' => 'alpine', + 'ID_LIKE' => '', + ])); + + expect($result)->not->toBeFalse() + ->and((string) $result)->toContain('alpine'); +}); + +it('detects supported family using ID_LIKE fallback', function () { + $server = new Server; + + $result = $server->resolveSupportedOsType(collect([ + 'ID' => 'linuxmint', + 'ID_LIKE' => 'ubuntu debian', + ])); + + expect($result)->not->toBeFalse() + ->and((string) $result)->toContain('ubuntu') + ->and((string) $result)->toContain('debian'); +}); + +it('returns false for unsupported os identifiers', function () { + $server = new Server; + + $result = $server->resolveSupportedOsType(collect([ + 'ID' => 'freebsd', + 'ID_LIKE' => '', + ])); + + expect($result)->toBeFalse(); +}); + +it('normalizes mixed casing and extra spaces in os release identifiers', function () { + $server = new Server; + + $result = $server->resolveSupportedOsType(collect([ + 'ID' => ' AlPiNe ', + 'ID_LIKE' => ' MUSL busybox ', + ])); + + expect($result)->not->toBeFalse() + ->and((string) $result)->toContain('alpine'); +});