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:
kantarciandClaude Opus 5.5 committed 2026-10-14 10:43:13 +02:00
1 parent 32cb77b72e
commit 723680b7de
39 files changed
+2355 -27

No files matched your search

+3 -23
View File
@@ -3,12 +3,11 @@
namespace App\Controller;
use App\Command\SiteOriginCommand;
use App\Content\PageRepository;
use App\Content\SiteSettings;
use App\Domain\InvalidPage;
use App\Ops\ArchiveUpdate;
use App\Ops\Backup;
use App\Ops\Environment;
use App\Ops\Funktionspruefung;
use App\Ops\HttpsRedirect;
use App\Ops\Installation;
use App\Ops\OriginCheck;
@@ -18,7 +17,6 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Routing\Attribute\Route;
/**
@@ -30,7 +28,7 @@ use Symfony\Component\Routing\Attribute\Route;
*/
final class SystemController extends AbstractController
{
public function __construct(private Updater $updater, private Backup $backup, private ArchiveUpdate $archive, private Installation $installation, private string $adminPath, private string $databaseUrl, private string $mediaDirectory, private string $projectDir, private string $siteOrigin, private OriginCheck $originCheck, private HttpKernelInterface $kernel, private PageRepository $pages, private SiteSettings $settings, private SitePackage $packages, private HttpsRedirect $https)
public function __construct(private Updater $updater, private Backup $backup, private ArchiveUpdate $archive, private Installation $installation, private string $adminPath, private string $databaseUrl, private string $mediaDirectory, private string $projectDir, private string $siteOrigin, private OriginCheck $originCheck, private Funktionspruefung $pruefung, private SitePackage $packages, private HttpsRedirect $https)
{
}
@@ -272,28 +270,10 @@ final class SystemController extends AbstractController
return new JsonResponse(['ok' => false, 'meldung' => 'Dieser Abschluss gehört zu keinem offenen Update.'], 403);
}
@set_time_limit(0);
$ergebnis = $this->archive->finish($this->installation, $this->pruefen(...));
$ergebnis = $this->archive->finish($this->installation, $this->pruefung);
$this->sendJsonAndStop(['ok' => $ergebnis['ok'], 'status' => $this->archive->status()]);
}
/**
* Läuft die neue Fassung wirklich? Datenbank, Startseite in der Datenbank und die Startseite
* als Seite - als Unteranfrage, die an der Wartungsseite vorbeigeht. null heißt: alles gut.
*/
private function pruefen(): ?string
{
$home = $this->settings->homePath();
if (null === $this->pages->findPublished($home)) {
return sprintf('Die Startseite /%s ist nicht lesbar.', $home);
}
$antwort = $this->kernel->handle(Request::create('/'), HttpKernelInterface::SUB_REQUEST, false);
if ($antwort->getStatusCode() >= 400) {
return sprintf('Die Startseite antwortet mit %d.', $antwort->getStatusCode());
}
return null;
}
/** Wie sendAndStop, nur als JSON für die Systemseite. */
private function sendJsonAndStop(array $daten): never
{