Stripped from the internal repo: per-box deploy trees, Go services (nodelist/astmeta/proxy/svxdr), deploy/sync scripts and all secrets. Config templates for nginx, PHP-FPM, Redis and tmpfiles live in install/ with CHANGEME placeholders. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
90 lines
2.9 KiB
PHP
90 lines
2.9 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
// redis.php — shared PHP helper for every web (dmr-web, svx-web).
|
|
// Canonical copy: callmgr2/www-mumien/auth/lib/redis.php; other repos carry a COPY.
|
|
// Redis connections (main + aux) and the batched SCAN+MGET read — the only way to read many keys.
|
|
// Loaded by auth/lib.php; needs cfg() from the page's own lib.php.
|
|
|
|
// One connection from a config block {unix_socket|host,port, password, db}.
|
|
// $read_timeout > 0 keeps a blocking SUBSCRIBE alive (lastheard relay: 86400 s).
|
|
function redis_connect(array $c, float $read_timeout = 0.0): Redis {
|
|
$r = new Redis();
|
|
if (!empty($c['unix_socket'])) {
|
|
$r->connect($c['unix_socket'], 0, 0.0, null, 0, $read_timeout);
|
|
} else {
|
|
$r->connect($c['host'], (int)$c['port'], 0.0, null, 0, $read_timeout);
|
|
}
|
|
if (!empty($c['password'])) $r->auth($c['password']);
|
|
if (isset($c['db'])) $r->select((int)$c['db']);
|
|
return $r;
|
|
}
|
|
|
|
function redis(): Redis {
|
|
static $r = null;
|
|
if ($r instanceof Redis) return $r;
|
|
|
|
$c = cfg()['redis'];
|
|
$r = new Redis();
|
|
if (!empty($c['unix_socket'])) {
|
|
$r->connect($c['unix_socket']);
|
|
} else {
|
|
$r->connect($c['host'], (int)$c['port']);
|
|
}
|
|
if (!empty($c['password'])) $r->auth($c['password']);
|
|
if (isset($c['db'])) $r->select((int)$c['db']);
|
|
return $r;
|
|
}
|
|
|
|
// V5-14F64: aux redis instance — daemon's APRS inbox/outbox lives here
|
|
// (same place stats/events go). Falls back to main redis if no aux config
|
|
// is present (older installs without redis-aux).
|
|
function redis_aux(): Redis {
|
|
static $r = null;
|
|
if ($r instanceof Redis) return $r;
|
|
|
|
$c = cfg()['redis_aux'] ?? null;
|
|
if (!$c) return redis(); // fallback: main socket
|
|
|
|
$r = new Redis();
|
|
if (!empty($c['unix_socket'])) {
|
|
$r->connect($c['unix_socket']);
|
|
} else {
|
|
$r->connect($c['host'], (int)$c['port']);
|
|
}
|
|
if (!empty($c['password'])) $r->auth($c['password']);
|
|
if (isset($c['db'])) $r->select((int)$c['db']);
|
|
return $r;
|
|
}
|
|
|
|
// Every key matching $pattern in ONE SCAN sweep (the only way to enumerate
|
|
// keys: no KEYS, no per-page loops copied around). Returns [] on error.
|
|
function redis_scan_keys(Redis $r, string $pattern, int $count = 500): array {
|
|
$keys = [];
|
|
$it = null;
|
|
do {
|
|
$batch = $r->scan($it, $pattern, $count);
|
|
if (is_array($batch)) {
|
|
foreach ($batch as $k) $keys[] = $k;
|
|
}
|
|
} while ($it > 0);
|
|
return $keys;
|
|
}
|
|
|
|
// Batch-read every key under $scan_pattern: ONE SCAN sweep + one MGET, keyed
|
|
// by the part after $base_prefix. With ~700k keys in the instance this is the
|
|
// difference between 2 round-trips and one per key.
|
|
function redis_scan_mget(Redis $r, string $base_prefix, string $scan_pattern): array {
|
|
$keys = redis_scan_keys($r, $scan_pattern, 200);
|
|
$out = [];
|
|
if (!empty($keys)) {
|
|
$vals = $r->mGet($keys);
|
|
foreach ($keys as $i => $k) {
|
|
$rel = substr($k, strlen($base_prefix));
|
|
$v = $vals[$i] ?? false;
|
|
$out[$rel] = is_string($v) ? $v : '';
|
|
}
|
|
}
|
|
ksort($out);
|
|
return $out;
|
|
}
|