/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; }