Ajoute les mises à jour autonomes

Les installations hors YunoHost peuvent désormais vérifier et installer une release Forgejo avec sauvegarde et retour arrière complet.
This commit is contained in:
Alexandre NOEL 2026-09-08 20:13:55 +02:00
commit 53e79a72d7
25 changed files with 1146 additions and 61 deletions

View file

@ -0,0 +1,92 @@
<?php require __DIR__ . '/settings_nav.php'; ?>
<div class="mb-3">
<h2 class="mb-1"><?= h(t('updates.heading')) ?></h2>
<div class="text-muted"><?= h(t('updates.intro')) ?></div>
</div>
<?php if ($status === 'checked'): ?><div class="alert alert-success">✅ <?= h(t('updates.checked')) ?></div><?php endif; ?>
<?php if ($status === 'installed'): ?><div class="alert alert-success">✅ <?= h(t('updates.installed', ['version' => (string) ($_GET['version'] ?? '')])) ?></div><?php endif; ?>
<?php if ($error !== ''): ?><div class="alert alert-danger"><strong><?= h(t('updates.failed')) ?></strong> <?= h($error) ?></div><?php endif; ?>
<div class="row g-3 mb-3">
<div class="col-lg-7">
<section class="card h-100">
<div class="card-header"><h3 class="card-title"><?= h(t('updates.version_status')) ?></h3></div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-5"><?= h(t('updates.installed_version')) ?></dt><dd class="col-sm-7"><code><?= h($updateStatus['current']) ?></code></dd>
<dt class="col-sm-5"><?= h(t('updates.available_version')) ?></dt><dd class="col-sm-7"><?= $updateStatus['latest'] !== '' ? '<code>' . h($updateStatus['latest']) . '</code>' : '—' ?></dd>
<dt class="col-sm-5"><?= h(t('updates.last_check')) ?></dt><dd class="col-sm-7"><?= h($updateStatus['checked_at'] ?: '—') ?></dd>
</dl>
<?php if ($updateStatus['available']): ?><div class="alert alert-warning mt-3 mb-0">⬆️ <?= h(t('updates.available', ['version' => $updateStatus['latest']])) ?></div>
<?php elseif ($updateStatus['latest'] !== ''): ?><div class="alert alert-success mt-3 mb-0">✅ <?= h(t('updates.current')) ?></div><?php endif; ?>
<?php if ($updateStatus['error'] !== ''): ?><div class="alert alert-secondary mt-3 mb-0"><?= h(t('updates.check_error', ['error' => $updateStatus['error']])) ?></div><?php endif; ?>
</div>
<div class="card-footer d-flex flex-wrap gap-2 justify-content-between">
<form method="post" action="/settings/updates/check"><button class="btn btn-outline-primary">🔄 <?= h(t('updates.check_now')) ?></button></form>
<?php if ($updateStatus['notes_url'] !== ''): ?><a class="btn btn-link" href="<?= h($updateStatus['notes_url']) ?>" target="_blank" rel="noopener noreferrer"><?= h(t('updates.release_notes')) ?></a><?php endif; ?>
</div>
</section>
</div>
<div class="col-lg-5">
<section class="card h-100">
<div class="card-header"><h3 class="card-title"><?= h(t('updates.preflight')) ?></h3></div>
<div class="list-group list-group-flush">
<?php $ready = true; foreach ($checks as $check): $ready = $ready && $check['ok']; ?><div class="list-group-item d-flex justify-content-between gap-3">
<span><?= $check['ok'] ? '✅' : '❌' ?> <?= h($check['label']) ?></span><small class="text-muted text-end"><?= h($check['detail']) ?></small>
</div><?php endforeach; ?>
</div>
</section>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-lg-6">
<section class="card h-100 <?= $updateStatus['available'] ? 'border-primary' : '' ?>">
<div class="card-header"><h3 class="card-title"><?= h(t('updates.automatic')) ?></h3></div>
<form method="post" action="/settings/updates/install" onsubmit="return confirm(<?= h(json_encode(t('updates.install_confirm'), JSON_HEX_APOS | JSON_HEX_QUOT)) ?>)">
<div class="card-body">
<p><?= h(t('updates.automatic_help')) ?></p>
<label class="form-label" for="updatePassword"><?= h(t('updates.password')) ?></label>
<input class="form-control" id="updatePassword" type="password" name="password" autocomplete="current-password" required>
</div>
<div class="card-footer text-end"><button class="btn btn-primary" <?= !$ready || !$updateStatus['available'] ? 'disabled' : '' ?>><?= h(t('updates.backup_and_install')) ?></button></div>
</form>
</section>
</div>
<div class="col-lg-6">
<section class="card h-100">
<div class="card-header"><h3 class="card-title"><?= h(t('updates.manual_archive')) ?></h3></div>
<form method="post" action="/settings/updates/upload" enctype="multipart/form-data" onsubmit="return confirm(<?= h(json_encode(t('updates.install_confirm'), JSON_HEX_APOS | JSON_HEX_QUOT)) ?>)">
<div class="card-body">
<p><?= h(t('updates.manual_help')) ?></p>
<label class="form-label" for="updateRelease"><?= h(t('updates.archive')) ?></label>
<input class="form-control" id="updateRelease" type="file" name="release" accept=".tar.gz,.tgz,application/gzip" required>
<label class="form-label mt-3" for="updateHash"><?= h(t('updates.sha256')) ?></label>
<input class="form-control font-monospace" id="updateHash" name="sha256" minlength="64" maxlength="64" pattern="[a-fA-F0-9]{64}" required>
<label class="form-label mt-3" for="uploadPassword"><?= h(t('updates.password')) ?></label>
<input class="form-control" id="uploadPassword" type="password" name="password" autocomplete="current-password" required>
</div>
<div class="card-footer text-end"><button class="btn btn-outline-primary" <?= !$ready ? 'disabled' : '' ?>><?= h(t('updates.install_archive')) ?></button></div>
</form>
</section>
</div>
</div>
<?php if ($lastUpdate): ?><section class="card border-warning">
<div class="card-header"><h3 class="card-title"><?= h(t('updates.rollback')) ?></h3></div>
<form method="post" action="/settings/updates/rollback" onsubmit="return confirm(<?= h(json_encode(t('updates.rollback_confirm'), JSON_HEX_APOS | JSON_HEX_QUOT)) ?>)">
<div class="card-body">
<p><?= h(t('updates.rollback_help', ['version' => (string) $lastUpdate['from'], 'date' => (string) $lastUpdate['installed_at']])) ?></p>
<div class="alert alert-warning"><?= h(t('updates.rollback_data_warning')) ?></div>
<label class="form-label" for="rollbackPassword"><?= h(t('updates.password')) ?></label>
<input class="form-control" id="rollbackPassword" type="password" name="password" autocomplete="current-password" required>
</div>
<div class="card-footer text-end"><button class="btn btn-warning"><?= h(t('updates.rollback_button', ['version' => (string) $lastUpdate['from']])) ?></button></div>
</form>
</section><?php endif; ?>
<div class="d-flex flex-wrap justify-content-between gap-2 mt-3">
<div class="text-muted small"><?= h(t('updates.privacy')) ?><br><code><?= h(UpdateService::repositoryUrl()) ?></code></div>
<?php if (UpdateService::logPath()): ?><a class="btn btn-sm btn-outline-secondary" href="/settings/updates/log">⬇ <?= h(t('updates.download_log')) ?></a><?php endif; ?>
</div>