Add the central site registry and remote updates
Every installation now reports address and version to sites.kant-it.de every 15 minutes and picks up update orders there (ADR 123). Reports and answers are HMAC-signed with a per-installation key sent on first contact; an order can only install a published release with checksum. The next request after the file swap runs the migrations, so a remote update finishes without anyone in the admin. The platform itself lives in zentrale/ (plain PHP + SQLite, not part of the release archive): overview with outdated, silent and unassigned installations, single and bulk updates, per-site detail and history. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
32cb77b72e
commit
723680b7de
39 files changed
+2355
-27
No files matched your search
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Zentrale;
|
||||
|
||||
/**
|
||||
* Die neueste veröffentlichte KantSites-Fassung - von derselben Releaseseite, die auch die
|
||||
* Installationen fragen. Eine halbe Stunde zwischengespeichert; ist die Releaseseite nicht
|
||||
* erreichbar, gilt der letzte bekannte Stand.
|
||||
*/
|
||||
final class Fassungen
|
||||
{
|
||||
private const GUELTIG = 1800;
|
||||
|
||||
public function __construct(private string $api, private string $cache)
|
||||
{
|
||||
}
|
||||
|
||||
/** @return array{version: string, name: string, published: string}|null */
|
||||
public function neueste(bool $frisch = false): ?array
|
||||
{
|
||||
$gespeichert = json_decode((string) @file_get_contents($this->cache), true);
|
||||
$gespeichert = is_array($gespeichert) && is_string($gespeichert['version'] ?? null) ? $gespeichert : null;
|
||||
if (!$frisch && null !== $gespeichert && (int) ($gespeichert['geholt'] ?? 0) > time() - self::GUELTIG) {
|
||||
return $gespeichert;
|
||||
}
|
||||
$neu = $this->holen();
|
||||
if (null === $neu) {
|
||||
return $gespeichert;
|
||||
}
|
||||
@file_put_contents($this->cache, json_encode([...$neu, 'geholt' => time()]));
|
||||
|
||||
return $neu;
|
||||
}
|
||||
|
||||
/** @return array{version: string, name: string, published: string}|null */
|
||||
private function holen(): ?array
|
||||
{
|
||||
if (!str_starts_with($this->api, 'https://')) {
|
||||
return null;
|
||||
}
|
||||
$kontext = stream_context_create([
|
||||
'http' => ['timeout' => 8, 'ignore_errors' => true, 'header' => "User-Agent: KantSites-Zentrale\r\nAccept: application/json\r\n"],
|
||||
'ssl' => ['verify_peer' => true, 'verify_peer_name' => true],
|
||||
]);
|
||||
if (function_exists('curl_init')) {
|
||||
$curl = curl_init(rtrim($this->api, '/').'/releases/latest');
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
CURLOPT_TIMEOUT => 8,
|
||||
CURLOPT_USERAGENT => 'KantSites-Zentrale',
|
||||
]);
|
||||
$antwort = curl_exec($curl);
|
||||
$status = (int) curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
|
||||
curl_close($curl);
|
||||
$antwort = 200 === $status ? $antwort : false;
|
||||
} else {
|
||||
$antwort = @file_get_contents(rtrim($this->api, '/').'/releases/latest', false, $kontext);
|
||||
}
|
||||
$daten = is_string($antwort) ? json_decode($antwort, true) : null;
|
||||
if (!is_array($daten) || 1 !== preg_match('/^v(\d+\.\d+\.\d+)$/D', (string) ($daten['tag_name'] ?? ''), $treffer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ['version' => $treffer[1], 'name' => (string) ($daten['name'] ?? ''), 'published' => (string) ($daten['published_at'] ?? '')];
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user