Aenderungen an Codeberg-Issues landeten bisher erst beim naechsten Lauf des sync-Services auf dem Board (MONITOR_SYNC_INTERVAL, Default 600s). Neu meldet Codeberg jede Aenderung aktiv per Webhook, der Weg aufs Board dauert damit wenige Sekunden (Webhook -> Sync -> SSE-Push ans Frontend). POST /api/v1/webhooks/codeberg wertet den Payload bewusst nicht aus, sondern fordert nur einen regulaeren Sync an - CodebergSyncService bleibt die einzige Stelle, die Issues auf Tickets abbildet, und ein verpasster Webhook ist folgenlos, weil der periodische Lauf denselben Stand ohnehin nachholt. Der Sync passiert nicht im Request: Forgejo bricht die Zustellung nach wenigen Sekunden ab (DELIVER_TIMEOUT, Default 5s), ein Sync macht aber mehrere Requests nach codeberg.org. Der Endpunkt setzt daher nur eine Trigger-Datei im gemeinsamen ./api-Volume (Sync\SyncTrigger) und antwortet sofort mit 202; der sync-Service laeuft jetzt im Watch-Modus und greift sie im Sekundentakt auf. Authentifiziert wird per HMAC-SHA256 ueber den rohen Request-Body (X-Forgejo-Signature bzw. X-Gitea-Signature) - Codeberg kennt unsere API-Keys nicht. Ohne gesetztes CODEBERG_WEBHOOK_SECRET weist der Endpunkt grundsaetzlich mit 401 ab, damit ein vergessenes Secret keinen offenen Sync-Ausloeser im Internet hinterlaesst. Request traegt dafuer neu den unveraenderten rawBody. Anders als der sync-Service braucht der api-Service damit eine eingehende Freigabe in Proxy und Firewall (POST /api/v1/webhooks/codeberg). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
59 lines
1.9 KiB
PHP
Executable file
59 lines
1.9 KiB
PHP
Executable file
#!/usr/bin/env php
|
|
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use Monitor\Api\Config;
|
|
use Monitor\Api\Db\Database;
|
|
use Monitor\Api\Repository\TicketRepository;
|
|
use Monitor\Api\Sync\CodebergSyncService;
|
|
use Monitor\Api\Sync\CurlCodebergClient;
|
|
use Monitor\Api\Sync\SyncTrigger;
|
|
|
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
|
|
|
$config = Config::fromEnvironment();
|
|
$db = Database::forFile($config->dbPath());
|
|
|
|
$service = new CodebergSyncService(
|
|
new CurlCodebergClient($config->codebergApiBase(), $config->codebergToken()),
|
|
new TicketRepository($db),
|
|
$config->codebergOwner(),
|
|
$config->codebergRepo(),
|
|
);
|
|
|
|
// Ohne --watch bleibt das Skript ein einmaliger Lauf (manueller Aufruf, siehe README).
|
|
if (!in_array('--watch', $argv, true)) {
|
|
$service->sync();
|
|
fwrite(STDOUT, "Codeberg-Sync abgeschlossen.\n");
|
|
|
|
return;
|
|
}
|
|
|
|
$trigger = new SyncTrigger($config->syncTriggerPath());
|
|
$interval = $config->syncIntervalSeconds();
|
|
|
|
fwrite(STDOUT, sprintf("[sync] Watch-Modus, periodischer Lauf alle %ds, Webhook-Trigger sofort.\n", $interval));
|
|
|
|
$lastRun = 0;
|
|
while (true) {
|
|
$triggered = $trigger->consume();
|
|
$due = (time() - $lastRun) >= $interval;
|
|
|
|
if ($triggered || $due) {
|
|
$lastRun = time();
|
|
try {
|
|
$service->sync();
|
|
fwrite(STDOUT, sprintf("[sync] Lauf beendet (%s).\n", $triggered ? 'Webhook' : 'periodisch'));
|
|
} catch (Throwable $e) {
|
|
// Ein fehlgeschlagener Lauf (Codeberg nicht erreichbar, Rate-Limit) darf die
|
|
// Schleife nicht beenden, sonst landet der Container in einer Restart-Schleife.
|
|
fwrite(STDERR, sprintf("[sync] Lauf fehlgeschlagen: %s\n", $e->getMessage()));
|
|
}
|
|
}
|
|
|
|
// Sekundentakt: bestimmt, wie schnell ein Webhook-Trigger bemerkt wird. Der Check ist
|
|
// ein file_exists() auf einem lokalen Volume, kostet also praktisch nichts - im
|
|
// Gegensatz zum Codeberg-Request, der weiterhin nur bei Bedarf laeuft.
|
|
sleep(1);
|
|
}
|