Files
folio/src/UpdateChecker.php
T
cedricAbonnel af0a0bb9d5 feat : UpdateChecker délègue la mise à jour à un script sudo externe
Le bouton "Mettre à jour" appelle désormais `sudo /usr/local/bin/folio-upgrade.sh`
via exec() plutôt que d'exécuter git pull + composer + migrations directement en PHP.
Le script shell (template dans scripts/server/) gère la séquence complète : clone fresh,
permissions www-data, restauration .env, composer install, migrations SQL, .sessions,
safe.directory. Le journal de la dernière mise à jour est conservé dans DATA_PATH/.upgrade-log
et affiché en <details> dans l'admin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 15:46:26 +02:00

172 lines
5.2 KiB
PHP

<?php
declare(strict_types=1);
/**
* Vérifie si une mise à jour de Folio est disponible sur le dépôt Git,
* et si des migrations de contenu sont en attente.
*
* Aucune dépendance externe : utilise file_get_contents() + cache JSON.
*/
class UpdateChecker
{
private string $dataDir;
private string $baseDir;
public function __construct(string $dataDir, string $baseDir)
{
$this->dataDir = $dataDir;
$this->baseDir = $baseDir;
}
/** Retourne la liste des alertes à afficher aux administrateurs. */
public function adminNotices(): array
{
$notices = [];
if ($this->hasPendingContentMigrations()) {
$notices[] = [
'type' => 'warning',
'message' => 'Des migrations de contenu sont en attente.',
];
}
$update = $this->checkRemoteVersion();
if ($update !== null) {
$notices[] = [
'type' => 'info',
'message' => 'Une nouvelle version de Folio est disponible : <strong>v' . htmlspecialchars($update) . '</strong>.',
];
}
return $notices;
}
// ─── Migrations de contenu en attente ────────────────────────────────────
private function hasPendingContentMigrations(): bool
{
$trackFile = $this->dataDir . '/.content_migrations.json';
$applied = [];
if (file_exists($trackFile)) {
$applied = json_decode((string) file_get_contents($trackFile), true) ?? [];
}
foreach (glob($this->baseDir . '/scripts/content/migration_*.php') ?: [] as $f) {
if (!isset($applied[basename($f)])) {
return true;
}
}
return false;
}
// ─── Vérification version distante (Gitea) ───────────────────────────────
/**
* Retourne le numéro de la version disponible si elle est supérieure
* à la version déployée, null sinon.
*/
private function checkRemoteVersion(): ?string
{
$repoUrl = folioRepoUrl();
if ($repoUrl === '') {
return null;
}
$deployedFile = $this->baseDir . '/public/version.txt';
if (!file_exists($deployedFile)) {
return null;
}
$deployedVer = trim((string) file_get_contents($deployedFile));
if ($deployedVer === '' || !preg_match('/^\d+\.\d+\.\d+/', $deployedVer)) {
return null;
}
$remoteVer = $this->fetchRemoteVersion($repoUrl);
if ($remoteVer === null) {
return null;
}
return version_compare($remoteVer, $deployedVer, '>') ? $remoteVer : null;
}
public function getBranch(): string
{
return folioUpdateBranch();
}
public function getLastChecked(): ?int
{
$cacheFile = $this->dataDir . '/.version_check_cache.json';
if (!file_exists($cacheFile)) {
return null;
}
$cache = json_decode((string) file_get_contents($cacheFile), true) ?? [];
return isset($cache['fetched_at']) ? (int) $cache['fetched_at'] : null;
}
public function clearCache(): void
{
$cacheFile = $this->dataDir . '/.version_check_cache.json';
if (file_exists($cacheFile)) {
unlink($cacheFile);
}
}
public function getLastUpgradeLog(): ?string
{
$logFile = $this->dataDir . '/.upgrade-log';
if (!file_exists($logFile)) {
return null;
}
return (string) file_get_contents($logFile);
}
/**
* Récupère `public/version.txt` depuis le dépôt Gitea.
* Résultat mis en cache 1 h dans `data/.version_check_cache.json`.
*/
private function fetchRemoteVersion(string $repoUrl): ?string
{
$cacheFile = $this->dataDir . '/.version_check_cache.json';
$ttl = 3600;
if (file_exists($cacheFile)) {
$cache = json_decode((string) file_get_contents($cacheFile), true) ?? [];
if (isset($cache['fetched_at'], $cache['version'])
&& (time() - (int) $cache['fetched_at']) < $ttl
) {
return (string) $cache['version'];
}
}
$branch = $this->getBranch();
// URL du fichier brut : {repo}/raw/branch/{branch}/public/version.txt
$rawUrl = $repoUrl . '/raw/branch/' . $branch . '/public/version.txt';
$token = (string) ($_ENV['GITEA_TOKEN'] ?? getenv('GITEA_TOKEN') ?: '');
$opts = [
'http' => [
'timeout' => 5,
'header' => $token !== '' ? "Authorization: token $token" : '',
],
];
$body = @file_get_contents($rawUrl, false, stream_context_create($opts));
if ($body === false) {
return null;
}
$version = trim($body);
if (!preg_match('/^\d+\.\d+\.\d+/', $version)) {
return null;
}
file_put_contents(
$cacheFile,
json_encode(['fetched_at' => time(), 'version' => $version]) . "\n"
);
return $version;
}
}