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; } }