diff --git a/cron/purge.php b/cron/purge.php new file mode 100755 index 0000000..f4f25c3 --- /dev/null +++ b/cron/purge.php @@ -0,0 +1,25 @@ +#!/usr/bin/env php +> /var/log/portspoof_concentrator/purge.log 2>&1 + */ + +if (PHP_SAPI !== 'cli') { + http_response_code(403); + exit; +} + + +require_once __DIR__ . '/../includes/functions.php'; + +$days = max(1, (int)get_setting('retention_days', '7')); +$deleted = purge_old_connections(); + +echo sprintf("[%s] Purged %d connection(s) older than %d day(s).\n", + date('Y-m-d H:i:s'), $deleted, $days); diff --git a/includes/functions.php b/includes/functions.php index 1ddb5b2..52cb989 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -181,6 +181,38 @@ function run_fetch(): array { return $results; } +// ── Settings ────────────────────────────────────────────────────────────────── + +function get_setting(string $key, string $default = ''): string { + $s = db()->prepare('SELECT value FROM settings WHERE key_name = ?'); + $s->execute([$key]); + $row = $s->fetchColumn(); + return $row !== false ? $row : $default; +} + +function set_setting(string $key, string $value): void { + $s = db()->prepare( + 'INSERT INTO settings (key_name, value) VALUES (?, ?) + ON DUPLICATE KEY UPDATE value = VALUES(value)' + ); + $s->execute([$key, $value]); +} + +// ── Purge ───────────────────────────────────────────────────────────────────── + +/** + * Delete connections older than retention_days setting. + * Returns the number of rows deleted. + */ +function purge_old_connections(): int { + $days = max(1, (int)get_setting('retention_days', '7')); + $s = db()->prepare( + 'DELETE FROM connections WHERE occurred_at < NOW() - INTERVAL ? DAY' + ); + $s->execute([$days]); + return $s->rowCount(); +} + // ── Upstream version check ──────────────────────────────────────────────────── define('UPSTREAM_VERSION_URL', 'https://git.ny.daprogs.com/api/v1/repos/DAProgs/portspoof_concentrator/raw/version.php?ref=main'); diff --git a/includes/style.php b/includes/style.php index d6a33db..ddee068 100644 --- a/includes/style.php +++ b/includes/style.php @@ -56,7 +56,7 @@ code { font-family: 'Cascadia Code', 'Fira Mono', monospace; font-size: .8rem; c label { display: block; margin-bottom: .75rem; font-size: .85rem; color: var(--muted); } label.inline { display: flex; align-items: center; gap: .5rem; color: var(--text); } -label input[type=text], label input[type=url], label input[type=password] { +label input[type=text], label input[type=url], label input[type=password], label input[type=number] { display: block; width: 100%; margin-top: .3rem; background: var(--bg); border: 1px solid var(--border); border-radius: 5px; color: var(--text); padding: .45rem .6rem; font-size: .9rem; diff --git a/purge.php b/purge.php new file mode 100644 index 0000000..c387b16 --- /dev/null +++ b/purge.php @@ -0,0 +1,67 @@ + + * - ?token= + * + * Usage: + * GET/POST /purge.php + * GET/POST /purge.php?token= + * GET/POST /purge.php (with header: Authorization: Bearer ) + * + * Always returns JSON. + */ + +require_once __DIR__ . '/includes/auth.php'; +require_once __DIR__ . '/includes/functions.php'; + +header('Content-Type: application/json'); + +// ── Auth ────────────────────────────────────────────────────────────────────── + +$session_ok = false; +if (auth_enabled()) { + if (session_status() === PHP_SESSION_NONE) { + session_start(); + } + $session_ok = !empty($_SESSION['authenticated']); +} + +$token_ok = false; +if (TRIGGER_TOKEN !== '') { + $provided = ''; + $auth_header = $_SERVER['HTTP_AUTHORIZATION'] ?? ''; + if (str_starts_with($auth_header, 'Bearer ')) { + $provided = substr($auth_header, 7); + } + if ($provided === '' && isset($_REQUEST['token'])) { + $provided = $_REQUEST['token']; + } + $token_ok = $provided !== '' && hash_equals(TRIGGER_TOKEN, $provided); +} + +if (!$session_ok && !$token_ok) { + http_response_code(401); + echo json_encode(['error' => 'Unauthorized']); + exit; +} + +// ── Run purge ───────────────────────────────────────────────────────────────── + +$started_at = microtime(true); +$retention_days = max(1, (int)get_setting('retention_days', '7')); +$deleted = purge_old_connections(); +$elapsed_ms = (int)round((microtime(true) - $started_at) * 1000); + +echo json_encode([ + 'ok' => true, + 'elapsed_ms' => $elapsed_ms, + 'retention_days' => $retention_days, + 'deleted' => $deleted, +], JSON_PRETTY_PRINT); diff --git a/schema.sql b/schema.sql index 4ead280..e4ebe8d 100644 --- a/schema.sql +++ b/schema.sql @@ -31,3 +31,11 @@ CREATE TABLE IF NOT EXISTS connections ( KEY idx_dst_port (dst_port), CONSTRAINT fk_conn_node FOREIGN KEY (node_id) REFERENCES nodes (id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS settings ( + key_name VARCHAR(64) NOT NULL, + value TEXT NOT NULL, + PRIMARY KEY (key_name) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT IGNORE INTO settings (key_name, value) VALUES ('retention_days', '7'); diff --git a/settings.php b/settings.php index ae331da..c277206 100644 --- a/settings.php +++ b/settings.php @@ -1,27 +1,43 @@ @@ -48,10 +64,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
-
+
-
+
@@ -60,6 +76,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {

Authentication is disabled. Set UI_PASS_HASH in config.php to enable it.

+ @@ -74,6 +91,21 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+
+

Data retention

+

+ Connections older than the retention period are removed by the daily purge cron. +

+ + + + + +
+
diff --git a/setup.php b/setup.php index bd4c006..a86bb8f 100644 --- a/setup.php +++ b/setup.php @@ -41,4 +41,14 @@ if ($row > 0) { echo "Dropped legacy uq_event unique key.\n"; } +// Migration: create settings table and seed default retention if upgrading +$pdo->exec( + "CREATE TABLE IF NOT EXISTS settings ( + key_name VARCHAR(64) NOT NULL, + value TEXT NOT NULL, + PRIMARY KEY (key_name) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" +); +$pdo->exec("INSERT IGNORE INTO settings (key_name, value) VALUES ('retention_days', '7')"); + echo "Database '" . DB_NAME . "' and tables created/migrated successfully.\n"; diff --git a/version.php b/version.php index b52aac1..9947dc0 100644 --- a/version.php +++ b/version.php @@ -1,2 +1,2 @@