181 lines
7.3 KiB
PHP
181 lines
7.3 KiB
PHP
<?php
|
||
|
||
namespace App\Console\Commands;
|
||
|
||
use Illuminate\Console\Command;
|
||
|
||
class CheckUpdates extends Command
|
||
{
|
||
protected $signature = 'clubird:check-updates|mailwolt:check-updates';
|
||
protected $description = 'Check for newer CluBird releases via git tags';
|
||
|
||
public function handle(): int
|
||
{
|
||
$currentNorm = $this->readInstalledVersionNorm();
|
||
$currentRaw = $this->readInstalledVersionRaw() ?? ($currentNorm ? 'v'.$currentNorm : null);
|
||
|
||
$appPath = base_path();
|
||
$remoteFile = '/var/lib/mailwolt/version_remote';
|
||
|
||
// Fallback-Kette für Remote-Tags (erste funktionierende Methode gewinnt):
|
||
// 1. mailwolt-update --check-only (sudoers: mailwolt-update)
|
||
// 2. mailwolt-fetch-tags (sudoers: mailwolt-fetch-tags)
|
||
// 3. Gitea/GitHub API (kein Auth nötig wenn Repo öffentlich)
|
||
// 4. git ls-remote (klappt wenn Credentials im git-Config)
|
||
// 5. git fetch --tags direkt
|
||
$updateBin = '/usr/local/sbin/mailwolt-update';
|
||
$fetchHelper = '/usr/local/sbin/mailwolt-fetch-tags';
|
||
|
||
$fetched = false;
|
||
|
||
if (file_exists($updateBin) && str_contains((string) @file_get_contents($updateBin), '--check-only')) {
|
||
@exec('sudo -n ' . escapeshellarg($updateBin) . ' --check-only 2>/dev/null', $_, $rc);
|
||
$fetched = ($rc === 0);
|
||
}
|
||
|
||
if (!$fetched && file_exists($fetchHelper)) {
|
||
@exec('sudo -n ' . escapeshellarg($fetchHelper) . ' 2>/dev/null', $_, $rc);
|
||
$fetched = ($rc === 0);
|
||
}
|
||
|
||
// Gitea/GitHub API – funktioniert ohne Credentials wenn Repo öffentlich
|
||
if (!$fetched) {
|
||
$remoteUrl = trim((string) @shell_exec('git -C ' . escapeshellarg($appPath) . ' remote get-url origin 2>/dev/null'));
|
||
if (preg_match('~https?://([^/]+)/([^/]+/[^/]+?)(?:\.git)?$~', $remoteUrl, $rm)) {
|
||
$host = $rm[1];
|
||
$project = $rm[2];
|
||
// Gitea API
|
||
$apiUrl = "https://{$host}/api/v1/repos/{$project}/tags?limit=50";
|
||
$ctx = stream_context_create(['http' => ['timeout' => 5, 'ignore_errors' => true]]);
|
||
$json = @file_get_contents($apiUrl, false, $ctx);
|
||
if ($json) {
|
||
$tags = json_decode($json, true);
|
||
if (is_array($tags)) {
|
||
$versions = [];
|
||
foreach ($tags as $t) {
|
||
$name = $t['name'] ?? '';
|
||
if (preg_match('/^v[\d.]+$/', $name)) {
|
||
$versions[] = $name;
|
||
}
|
||
}
|
||
usort($versions, 'version_compare');
|
||
$latest = end($versions);
|
||
if ($latest) {
|
||
@mkdir(dirname($remoteFile), 0755, true);
|
||
file_put_contents($remoteFile, $latest);
|
||
$fetched = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// git ls-remote (klappt wenn Credentials im git-Config hinterlegt sind)
|
||
if (!$fetched) {
|
||
$lsOut = [];
|
||
@exec('git -C ' . escapeshellarg($appPath) . ' ls-remote --tags origin \'v*\' 2>/dev/null', $lsOut);
|
||
$lsVersions = [];
|
||
foreach ($lsOut as $line) {
|
||
if (preg_match('~refs/tags/(v[\d.]+)$~', $line, $m)) {
|
||
$lsVersions[] = $m[1];
|
||
}
|
||
}
|
||
if (!empty($lsVersions)) {
|
||
usort($lsVersions, 'version_compare');
|
||
$latest = end($lsVersions);
|
||
@mkdir(dirname($remoteFile), 0755, true);
|
||
file_put_contents($remoteFile, $latest);
|
||
$fetched = true;
|
||
}
|
||
}
|
||
|
||
// Direkter git fetch als letzter Fallback
|
||
if (!$fetched) {
|
||
@exec('git -C ' . escapeshellarg($appPath) . ' fetch --tags origin 2>/dev/null', $_, $rc);
|
||
}
|
||
|
||
// version_remote von Helfer geschrieben; als frisch wenn < 2h alt
|
||
$remoteRaw = '';
|
||
if (file_exists($remoteFile) && (time() - filemtime($remoteFile)) < 7200) {
|
||
$remoteRaw = trim((string) file_get_contents($remoteFile));
|
||
}
|
||
|
||
// Lokale Tags (nach fetch hoffentlich aktuell)
|
||
$out = [];
|
||
@exec("git -C " . escapeshellarg($appPath) . " tag -l 'v*' --sort=-v:refname 2>/dev/null", $out);
|
||
$latestTagRaw = $remoteRaw !== '' ? $remoteRaw : trim($out[0] ?? '');
|
||
|
||
$latestNorm = $this->normalizeVersion($latestTagRaw);
|
||
|
||
if (!$latestNorm) {
|
||
cache()->forget('updates:latest');
|
||
cache()->forget('updates:latest_raw');
|
||
cache()->forget('mailwolt.update_available');
|
||
$this->warn('Keine Release-Tags gefunden.');
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
if ($currentNorm && version_compare($latestNorm, $currentNorm, '>')) {
|
||
cache()->forever('updates:latest', $latestNorm);
|
||
cache()->forever('updates:latest_raw', $latestTagRaw ?: ('v'.$latestNorm));
|
||
cache()->forever('mailwolt.update_available', $latestNorm);
|
||
$this->info("Update verfügbar: {$latestTagRaw} (installiert: ".($currentRaw ?? $currentNorm).")");
|
||
} else {
|
||
cache()->forget('updates:latest');
|
||
cache()->forget('updates:latest_raw');
|
||
cache()->forget('mailwolt.update_available');
|
||
$this->info("Aktuell (installiert: ".($currentRaw ?? $currentNorm ?? 'unbekannt').").");
|
||
}
|
||
|
||
cache()->put('updates:last_checked_at', now(), now()->addMinutes(10));
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
/* ===== Helpers ===== */
|
||
|
||
private function readInstalledVersionNorm(): ?string
|
||
{
|
||
// version_raw ist die zuverlässigste Quelle – wird vom Update-Script geschrieben
|
||
$rawVer = $this->normalizeVersion($this->readInstalledVersionRaw() ?? '');
|
||
|
||
$fileVer = null;
|
||
foreach (['/var/lib/mailwolt/version', base_path('VERSION')] as $p) {
|
||
$raw = @trim(@file_get_contents($p) ?: '');
|
||
if ($raw !== '') { $fileVer = $this->normalizeVersion($raw); break; }
|
||
}
|
||
|
||
// version_raw bevorzugen (echter installierter Stand)
|
||
// Nur wenn version_raw fehlt: version-Datei oder git-Tag als Fallback
|
||
if ($rawVer) {
|
||
return $rawVer;
|
||
}
|
||
|
||
// git-Tag als letzter Fallback (funktioniert auf Dev-Servern)
|
||
$out = [];
|
||
@exec('git -C ' . escapeshellarg(base_path()) . ' describe --tags --abbrev=0 2>/dev/null', $out);
|
||
$gitVer = $this->normalizeVersion(trim($out[0] ?? ''));
|
||
|
||
if ($gitVer && (!$fileVer || version_compare($gitVer, $fileVer, '>'))) {
|
||
return $gitVer;
|
||
}
|
||
return $fileVer ?: null;
|
||
}
|
||
|
||
private function readInstalledVersionRaw(): ?string
|
||
{
|
||
$p = '/var/lib/mailwolt/version_raw';
|
||
$raw = @trim(@file_get_contents($p) ?: '');
|
||
return $raw !== '' ? $raw : null;
|
||
}
|
||
|
||
private function normalizeVersion(?string $v): ?string
|
||
{
|
||
if (!$v) return null;
|
||
$v = trim($v);
|
||
if ($v === '') return null;
|
||
$v = ltrim($v, "vV \t\n\r\0\x0B");
|
||
$v = preg_replace('/-.*$/', '', $v);
|
||
return $v !== '' ? $v : null;
|
||
}
|
||
}
|