9b18b11f32
Add token- and IP-protected ingest.php, boka status checks in monitor.php, and document the two-repo push workflow. Todo 04 marked complete. Co-authored-by: Cursor <cursoragent@cursor.com>
93 lines
2.8 KiB
PHP
93 lines
2.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Receive push metrics from boka (POST JSON, token + IP whitelist).
|
|
* Usage: POST /ingest.php with JSON body from push-status.sh
|
|
*/
|
|
|
|
$configDir = is_dir(dirname(__DIR__) . '/private/config')
|
|
? dirname(__DIR__) . '/private/config'
|
|
: dirname(__DIR__) . '/config';
|
|
|
|
require_once $configDir . '/monitor-lib.php';
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
function ingestError(int $code, string $message): never
|
|
{
|
|
http_response_code($code);
|
|
echo json_encode(['ok' => false, 'error' => $message], JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
ingestError(405, 'method not allowed');
|
|
}
|
|
|
|
$allowedIp = env('BOKA_IP');
|
|
if ($allowedIp !== null && $allowedIp !== '') {
|
|
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '';
|
|
if (!hash_equals($allowedIp, $remoteAddr)) {
|
|
ingestError(403, 'forbidden');
|
|
}
|
|
}
|
|
|
|
$raw = file_get_contents('php://input');
|
|
if ($raw === false || $raw === '') {
|
|
ingestError(400, 'empty body');
|
|
}
|
|
|
|
$data = json_decode($raw, true);
|
|
if (!is_array($data)) {
|
|
ingestError(400, 'invalid json');
|
|
}
|
|
|
|
$expectedToken = env('MONITOR_INGEST_TOKEN') ?? '';
|
|
$token = isset($data['token']) && is_string($data['token']) ? $data['token'] : '';
|
|
if ($expectedToken === '' || !hash_equals($expectedToken, $token)) {
|
|
ingestError(403, 'forbidden');
|
|
}
|
|
|
|
if (!isset($data['load']) || !is_array($data['load'])) {
|
|
ingestError(400, 'missing load');
|
|
}
|
|
|
|
foreach (['disk_percent', 'mem_available_mb', 'containers_running', 'containers_unhealthy'] as $field) {
|
|
if (!isset($data[$field]) || !is_numeric($data[$field])) {
|
|
ingestError(400, "missing {$field}");
|
|
}
|
|
}
|
|
|
|
$load = array_map(static fn (mixed $v): float => (float) $v, $data['load']);
|
|
|
|
$backupLastSuccess = null;
|
|
if (isset($data['backup_last_success']) && is_string($data['backup_last_success']) && $data['backup_last_success'] !== '') {
|
|
$backupLastSuccess = $data['backup_last_success'];
|
|
}
|
|
|
|
$host = isset($data['host']) && is_string($data['host']) ? $data['host'] : 'boka';
|
|
$clientTimestamp = isset($data['timestamp']) && is_string($data['timestamp']) ? $data['timestamp'] : null;
|
|
|
|
$status = [
|
|
'last_push' => gmdate('c'),
|
|
'received_from' => $_SERVER['REMOTE_ADDR'] ?? null,
|
|
'host' => $host,
|
|
'client_timestamp' => $clientTimestamp,
|
|
'load' => $load,
|
|
'disk_percent' => (int) $data['disk_percent'],
|
|
'mem_available_mb' => (int) $data['mem_available_mb'],
|
|
'containers_running' => (int) $data['containers_running'],
|
|
'containers_unhealthy' => (int) $data['containers_unhealthy'],
|
|
'backup_last_success' => $backupLastSuccess,
|
|
];
|
|
|
|
try {
|
|
writeJsonFile(privateDataDir() . '/boka-status.json', $status);
|
|
} catch (Throwable $e) {
|
|
ingestError(500, 'storage error');
|
|
}
|
|
|
|
echo json_encode(['ok' => true], JSON_UNESCAPED_SLASHES);
|