80 lines
2.6 KiB
PHP
80 lines
2.6 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Server;
|
|
use App\Support\Os\OsDetector;
|
|
use App\Support\Os\PackageManager;
|
|
|
|
/**
|
|
* Package-maintenance helpers across apt/dnf/zypper (resolved per OS via
|
|
* OsDetector): counts pending package updates and applies an upgrade.
|
|
*
|
|
* Every reader/writer goes through FleetService, so quoting is base64-safe and
|
|
* privileged work runs as root. (fail2ban tuning + management lives in Fail2banService.)
|
|
*/
|
|
class MaintenanceService
|
|
{
|
|
public function __construct(private FleetService $fleet, private OsDetector $detector) {}
|
|
|
|
/**
|
|
* NULL when Clusev can manage package updates on this host, otherwise a German
|
|
* reason (e.g. unsupported package manager) for the operator.
|
|
*/
|
|
public function updateSupport(Server $server): ?string
|
|
{
|
|
return $this->detector->detect($server)->supports('updates');
|
|
}
|
|
|
|
/**
|
|
* Number of pending package upgrades via a dry-run/metadata read (no root
|
|
* needed), or NULL when it could not run (unsupported manager / errored) — so
|
|
* the caller shows "unbekannt" rather than a misleading "0 / up to date". The
|
|
* remote script emits a CLUSEV_OK sentinel only when the query actually ran.
|
|
*/
|
|
public function pendingUpdates(Server $server): ?int
|
|
{
|
|
$os = $this->detector->detect($server);
|
|
if (! $os->managesPackages()) {
|
|
return null;
|
|
}
|
|
|
|
$res = $this->fleet->runPlain($server, PackageManager::for($os)->pendingScript());
|
|
if (! str_contains($res['output'], 'CLUSEV_OK')) {
|
|
return null;
|
|
}
|
|
|
|
return preg_match('/CLUSEV_PENDING=(\d+)/', $res['output'], $m) ? (int) $m[1] : null;
|
|
}
|
|
|
|
/**
|
|
* Refresh the index and apply all upgrades as root, using the host's package
|
|
* manager. Long-running, so a 900s timeout. Output trimmed to the last ~400
|
|
* chars for a compact result panel.
|
|
*
|
|
* @return array{ok: bool, output: string}
|
|
*/
|
|
public function applyUpgrades(Server $server): array
|
|
{
|
|
$os = $this->detector->detect($server);
|
|
if ($reason = $os->supports('updates')) {
|
|
return ['ok' => false, 'output' => $reason];
|
|
}
|
|
|
|
$res = $this->fleet->runPrivileged($server, PackageManager::for($os)->applyScript(), 900);
|
|
|
|
return ['ok' => $res['ok'], 'output' => $this->tail($res['output'], 400)];
|
|
}
|
|
|
|
/** Keep only the last $max characters of a (multi-line) output. */
|
|
private function tail(string $out, int $max): string
|
|
{
|
|
$out = trim($out);
|
|
if (mb_strlen($out) <= $max) {
|
|
return $out;
|
|
}
|
|
|
|
return '…'.mb_substr($out, -$max);
|
|
}
|
|
}
|