Files
KantSites/zentrale/app/Fassungen.php
T
kantarciandClaude Opus 5.5 723680b7de 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>
2026-10-14 10:43:13 +02:00

71 lines
2.7 KiB
PHP

<?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'] ?? '')];
}
}