KantTresor instances (from 1.82.0) report to /api/kanttresor/melden with the same key-then-HMAC protocol. The new tab lists them with tenants, disk usage and failing services; it never offers or accepts an update order, since KantTresor is updated with ./update.sh on the server. See ADR 124. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
107 lines
3.9 KiB
PHP
107 lines
3.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Zentrale;
|
|
|
|
/**
|
|
* SQLite in var/zentrale.db. Das Schema legt sich beim ersten Aufruf selbst an; spätere
|
|
* Änderungen kommen als nummerierte Schritte in MIGRATIONEN dazu, nie als Änderung eines alten.
|
|
*/
|
|
final class Datenbank
|
|
{
|
|
private const MIGRATIONEN = [
|
|
1 => [
|
|
'CREATE TABLE konto (id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE, passwort TEXT NOT NULL, erstellt TEXT NOT NULL)',
|
|
'CREATE TABLE anmeldeversuch (ip TEXT NOT NULL, zeit TEXT NOT NULL)',
|
|
'CREATE INDEX anmeldeversuch_ip ON anmeldeversuch (ip, zeit)',
|
|
'CREATE TABLE installation (
|
|
id TEXT PRIMARY KEY,
|
|
schluessel TEXT NOT NULL,
|
|
adresse TEXT NOT NULL,
|
|
domain TEXT NOT NULL,
|
|
domain_vorher TEXT,
|
|
fassung TEXT NOT NULL,
|
|
php TEXT NOT NULL DEFAULT \'\',
|
|
datenbank TEXT NOT NULL DEFAULT \'\',
|
|
art TEXT NOT NULL DEFAULT \'archiv\',
|
|
wartung INTEGER NOT NULL DEFAULT 0,
|
|
update_phase TEXT,
|
|
update_meldung TEXT,
|
|
update_zeit TEXT,
|
|
notiz TEXT NOT NULL DEFAULT \'\',
|
|
ip TEXT NOT NULL DEFAULT \'\',
|
|
erste_meldung TEXT NOT NULL,
|
|
letzte_meldung TEXT NOT NULL
|
|
)',
|
|
'CREATE TABLE auftrag (
|
|
id INTEGER PRIMARY KEY,
|
|
installation TEXT NOT NULL REFERENCES installation (id) ON DELETE CASCADE,
|
|
aktion TEXT NOT NULL,
|
|
fassung TEXT NOT NULL,
|
|
erstellt TEXT NOT NULL,
|
|
abgeholt TEXT,
|
|
erledigt TEXT,
|
|
ok INTEGER,
|
|
meldung TEXT
|
|
)',
|
|
'CREATE INDEX auftrag_installation ON auftrag (installation, id)',
|
|
],
|
|
// KantTresor meldet sich ebenfalls (ADR 124). Bestehende Zeilen sind KantSites.
|
|
2 => [
|
|
'ALTER TABLE installation ADD COLUMN produkt TEXT NOT NULL DEFAULT \'kantsites\'',
|
|
'ALTER TABLE installation ADD COLUMN laufzeit TEXT NOT NULL DEFAULT \'\'',
|
|
'ALTER TABLE installation ADD COLUMN mandanten INTEGER',
|
|
'ALTER TABLE installation ADD COLUMN stoerungen TEXT NOT NULL DEFAULT \'\'',
|
|
'ALTER TABLE installation ADD COLUMN speicher INTEGER',
|
|
'CREATE INDEX installation_produkt ON installation (produkt)',
|
|
],
|
|
];
|
|
|
|
public static function oeffnen(string $datei): \PDO
|
|
{
|
|
$neu = !is_file($datei);
|
|
if ($neu && !is_dir(dirname($datei)) && !@mkdir(dirname($datei), 0o775, true)) {
|
|
throw new \RuntimeException(dirname($datei).' lässt sich nicht anlegen.');
|
|
}
|
|
$pdo = new \PDO('sqlite:'.$datei, null, null, [
|
|
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
|
|
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
|
|
]);
|
|
$pdo->exec('PRAGMA foreign_keys = ON');
|
|
$pdo->exec('PRAGMA busy_timeout = 5000');
|
|
if ($neu) {
|
|
$pdo->exec('PRAGMA journal_mode = WAL');
|
|
}
|
|
self::migrieren($pdo);
|
|
|
|
return $pdo;
|
|
}
|
|
|
|
private static function migrieren(\PDO $pdo): void
|
|
{
|
|
$stand = (int) $pdo->query('PRAGMA user_version')->fetchColumn();
|
|
foreach (self::MIGRATIONEN as $nummer => $anweisungen) {
|
|
if ($nummer <= $stand) {
|
|
continue;
|
|
}
|
|
$pdo->beginTransaction();
|
|
try {
|
|
foreach ($anweisungen as $sql) {
|
|
$pdo->exec($sql);
|
|
}
|
|
$pdo->exec('PRAGMA user_version = '.$nummer);
|
|
$pdo->commit();
|
|
} catch (\Throwable $fehler) {
|
|
$pdo->rollBack();
|
|
throw $fehler;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static function jetzt(): string
|
|
{
|
|
return gmdate('Y-m-d\TH:i:s\Z');
|
|
}
|
|
}
|