clusev/app/Support/Os/OsProfile.php

107 lines
4.4 KiB
PHP

<?php
namespace App\Support\Os;
/**
* Immutable description of a fleet host's operating system, resolved once by
* OsDetector from /etc/os-release + a `command -v` probe. Everything that used to
* hard-code Debian/apt/ufw/systemd now asks an OsProfile instead.
*
* Phase-1 fully-supported package managers: apt, dnf (incl. yum), zypper — all on
* systemd hosts with ufw or firewalld. Arch (pacman) and Alpine (apk/OpenRC) are
* DETECTED and surfaced, but features that need an unsupported manager/init are
* gracefully disabled with a German reason via supports().
*/
final class OsProfile
{
/** Package managers Clusev can drive for updates/installs in this release. */
public const SUPPORTED_MANAGERS = ['apt', 'dnf', 'zypper'];
/**
* @param list<string> $available detected binaries (apt-get, dnf, ufw, firewall-cmd, systemctl, …)
*/
public function __construct(
public readonly string $family, // debian|rhel|suse|arch|alpine|unknown
public readonly string $id, // raw /etc/os-release ID
public readonly string $prettyName, // PRETTY_NAME for display
public readonly string $packageManager, // apt|dnf|zypper|pacman|apk|none
public readonly string $firewallTool, // ufw|firewalld|none
public readonly string $serviceManager, // systemd|openrc|none
public readonly array $available = [],
) {}
public function has(string $binary): bool
{
return in_array($binary, $this->available, true);
}
public function isSystemd(): bool
{
return $this->serviceManager === 'systemd';
}
/** True when Clusev can drive package install/update on this host. */
public function managesPackages(): bool
{
return in_array($this->packageManager, self::SUPPORTED_MANAGERS, true);
}
/** German family label for the UI. */
public function familyLabel(): string
{
return match ($this->family) {
'debian' => __('backend.family_debian'),
'rhel' => __('backend.family_rhel'),
'suse' => __('backend.family_suse'),
'arch' => __('backend.family_arch'),
'alpine' => __('backend.family_alpine'),
default => __('backend.family_unknown'),
};
}
/** German label for the host firewall in the hardening checklist. */
public function firewallLabel(): string
{
return match ($this->firewallTool) {
'ufw' => __('backend.firewall_label_ufw'),
'firewalld' => __('backend.firewall_label_firewalld'),
default => __('backend.firewall_label_generic'),
};
}
/**
* Whether a feature is supported on this host. Returns NULL when supported, or
* a German reason to show the operator when not — so unsupported combinations
* degrade with an explanation instead of failing opaquely.
*/
public function supports(string $feature): ?string
{
return match ($feature) {
'ssh' => $this->isSystemd()
? null
: __('backend.support_ssh', ['family' => $this->familyLabel()]),
'services' => $this->isSystemd()
? null
: __('backend.support_services'),
'updates' => $this->managesPackages()
? null
: __('backend.support_updates', ['family' => $this->familyLabel()]),
'fail2ban' => ($this->managesPackages() && $this->isSystemd())
? null
: __('backend.support_fail2ban', ['family' => $this->familyLabel()]),
'firewall' => ($this->firewallTool !== 'none' && $this->isSystemd()
&& ($this->firewallTool === 'firewalld' || $this->managesPackages() || $this->has('ufw')))
? null
: __('backend.support_firewall', ['family' => $this->familyLabel()]),
// apt -> unattended-upgrades; dnf -> dnf-automatic. A yum-ONLY host (e.g.
// CentOS 7) maps to the dnf manager for upgrades but has no dnf-automatic
// (it uses yum-cron), so require a real `dnf` binary for this feature.
'auto_updates' => (($this->packageManager === 'apt'
|| ($this->packageManager === 'dnf' && $this->has('dnf'))) && $this->isSystemd())
? null
: __('backend.support_auto_updates', ['family' => $this->familyLabel()]),
default => null,
};
}
}