fix(server): accept OS derivatives through ID_LIKE in validateOS

validateOS matched only ID from /etc/os-release, so derivatives such as
Omarchy, CachyOS, Manjaro or Linux Mint failed validation before the
Docker checks ran. Check ID first, then each ID_LIKE entry, and keep the
exactly-one-match rule. Move the parsing into a testable function.

Closes #12033

Co-authored-by: Furox <177975472+Furox-Art@users.noreply.github.com>
This commit is contained in:
Andras Bacsai
2026-10-15 22:35:45 +02:00
co-authored by Furox
parent bd87baa690
commit 6cf1516749
2 changed files with 50 additions and 17 deletions
+26 -17
View File
@@ -1728,25 +1728,34 @@ $siteAddress {
public function validateOS(): bool|Stringable
{
$os_release = instant_remote_process(['cat /etc/os-release'], $this);
$releaseLines = collect(explode("\n", $os_release));
$collectedData = collect([]);
foreach ($releaseLines as $line) {
$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 self::supportedOsFromOsRelease($os_release);
}
/**
* Match /etc/os-release against SUPPORTED_OS: ID first, then each ID_LIKE token (e.g. Manjaro: ID=manjaro, ID_LIKE=arch).
*/
public static function supportedOsFromOsRelease(?string $osRelease): bool|Stringable
{
$collectedData = collect(explode("\n", (string) $osRelease))
->mapWithKeys(function ($line) {
$item = str($line)->trim();
return [$item->before('=')->value() => $item->after('=')->lower()->replace(['"', "'"], '')->value()];
});
$candidates = collect([data_get($collectedData, 'ID')])
->merge(explode(' ', (string) data_get($collectedData, 'ID_LIKE')))
->filter();
foreach ($candidates as $candidate) {
$supported = collect(SUPPORTED_OS)->filter(fn ($supportedOs) => str($supportedOs)->contains($candidate));
if ($supported->count() === 1) {
return str($supported->first());
}
});
if ($supported->count() === 1) {
return str($supported->first());
} else {
return false;
}
return false;
}
public function gatherServerMetadata(): ?array
+24
View File
@@ -0,0 +1,24 @@
<?php
use App\Models\Server;
it('matches the supported OS family from os-release', function (string $osRelease, string|false $expected) {
$result = Server::supportedOsFromOsRelease($osRelease);
expect($result === false ? false : $result->value())->toBe($expected);
})->with([
'ubuntu by ID' => ["ID=ubuntu\nID_LIKE=debian", 'ubuntu debian raspbian pop'],
'arch by ID' => ['ID=arch', 'arch'],
'rocky by ID' => ["ID=\"rocky\"\nID_LIKE=\"rhel centos fedora\"", 'centos fedora rhel ol rocky amzn almalinux'],
'omarchy by ID_LIKE' => ["NAME=\"Omarchy\"\nID=omarchy\nID_LIKE=arch", 'arch'],
'manjaro by ID_LIKE' => ["ID=manjaro\nID_LIKE=arch", 'arch'],
'linux mint by ID_LIKE' => ["ID=linuxmint\nID_LIKE=\"ubuntu debian\"", 'ubuntu debian raspbian pop'],
'single-quoted ID_LIKE' => ["ID=zorin\nID_LIKE='ubuntu debian'", 'ubuntu debian raspbian pop'],
'unsupported without ID_LIKE' => ['ID=gentoo', false],
'unsupported ID_LIKE' => ["ID=foo\nID_LIKE=bar", false],
'empty output' => ['', false],
]);
it('returns false for null os-release output', function () {
expect(Server::supportedOsFromOsRelease(null))->toBeFalse();
});