PHP app layer: consolidate common functions, fix critical bugs, standardize patterns
Consolidations (config.php gains 5 shared utilities): - vv_format_uptime() replaces 4 inline uptime-formatting blocks - vv_parse_conf_scalar() replaces vv_arr_scalar/vv_wd_scalar/vv_fb_scalar/vv_media_conf_scalar - vv_known_hosts() replaces vv_arr_known_hosts/vv_fb_known_hosts + inline parser in watchdog - vv_parse_kv_db() replaces inline key=value parsing in snapshot and monitor - vv_local_ip() replaces duplicate in docker_folders.php and inline in docker.php All module-level function names kept as thin aliases so call sites unchanged. Critical bug fixes: - api/system.php: added require_once config.php and POST-only guard (no auth on shutdown) - api/movescript.php + reorderarray.php: use vv_write_conf_raw (atomic) + vv_push_master_conf - api/snapshot.php: share /tmp/vv_cpu_stat.json with vv_cpu_per_core() instead of own state file Correctness: - vv_cpu_per_core() and vv_network_stats(): atomic tmp+rename for state files (concurrent poll safety) - ext_ip curl cache moved from /tmp/vv_ext_ip.cache to vv_cache_read/write (canonical cache dir) - monitor_remote.php + board.php + snapshot.php: all use vv_cache_read/write instead of ad-hoc /tmp files HTTP method guards added to write-only APIs that were missing them: - api/scheduler.php, conf_toggle.php, flag_toggle.php
This commit is contained in:
@@ -6,23 +6,11 @@ require_once __DIR__ . '/config.php';
|
||||
// ── Conf helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
function vv_arr_scalar(string $raw, string $key): string {
|
||||
return preg_match('/^\s*' . preg_quote($key, '/') . '\s*=\s*"?([^"\n]*)"?/m', $raw, $m)
|
||||
? trim($m[1]) : '';
|
||||
return vv_parse_conf_scalar($raw, $key);
|
||||
}
|
||||
|
||||
// Returns all configured hosts found in master.conf as ['host1'=>'hostname', ...]
|
||||
// Returns ['host1' => 'hostname', 'host2' => 'hostname', ...] from master.conf.
|
||||
// Matches both HOST1="name" and HOST1_NAME="name" (either convention).
|
||||
function vv_arr_known_hosts(): array {
|
||||
$vars = vv_conf_vars(); // already parses HOST1="val" correctly
|
||||
$hosts = [];
|
||||
foreach ($vars as $k => $v) {
|
||||
if (preg_match('/^HOST(\d+)$/', $k, $m) && $v !== '') {
|
||||
$hosts['host' . $m[1]] = $v;
|
||||
}
|
||||
}
|
||||
ksort($hosts);
|
||||
return $hosts ?: ['host1' => 'HOST1'];
|
||||
return vv_known_hosts();
|
||||
}
|
||||
|
||||
function vv_arr_node_names(): array {
|
||||
|
||||
@@ -23,13 +23,10 @@ function vv_system_info(): array {
|
||||
$uptimeRaw = $os['uptime'] ?? '';
|
||||
if (is_numeric($uptimeRaw)) {
|
||||
$uptimeSec = (int)$uptimeRaw;
|
||||
$days = intdiv($uptimeSec, 86400);
|
||||
$hours = intdiv($uptimeSec % 86400, 3600);
|
||||
$mins = intdiv($uptimeSec % 3600, 60);
|
||||
$uptime = ($days ? "{$days}d " : '') . ($hours ? "{$hours}h " : '') . "{$mins}m";
|
||||
$uptime = vv_format_uptime($uptimeSec);
|
||||
} else {
|
||||
$uptimeSec = 0;
|
||||
$uptime = $uptimeRaw ?: '—';
|
||||
$uptime = $uptimeRaw ?: '—';
|
||||
}
|
||||
|
||||
$load = sys_getloadavg();
|
||||
@@ -57,10 +54,7 @@ function vv_system_info(): array {
|
||||
if (preg_match('/^model name\s*:\s*(.+)/', $line, $m)) { $cpuModel = trim($m[1]); break; }
|
||||
}
|
||||
$uptimeSec = (int)explode(' ', @file_get_contents('/proc/uptime') ?: '0')[0];
|
||||
$days = intdiv($uptimeSec, 86400);
|
||||
$hours = intdiv($uptimeSec % 86400, 3600);
|
||||
$mins = intdiv($uptimeSec % 3600, 60);
|
||||
$uptime = ($days ? "{$days}d " : '') . ($hours ? "{$hours}h " : '') . "{$mins}m";
|
||||
$uptime = vv_format_uptime($uptimeSec);
|
||||
|
||||
$load = sys_getloadavg();
|
||||
return [
|
||||
@@ -159,7 +153,10 @@ function vv_cpu_per_core(): array {
|
||||
|
||||
$stateFile = '/tmp/vv_cpu_stat.json';
|
||||
$prev = file_exists($stateFile) ? (json_decode(file_get_contents($stateFile), true) ?: []) : [];
|
||||
file_put_contents($stateFile, json_encode($raw));
|
||||
// Atomic write — concurrent fast/slow polls read a consistent snapshot
|
||||
$tmp = $stateFile . '.tmp';
|
||||
file_put_contents($tmp, json_encode($raw));
|
||||
rename($tmp, $stateFile);
|
||||
|
||||
$usage = function(array $c, ?array $p): int {
|
||||
if (!$p) return 0;
|
||||
@@ -298,7 +295,9 @@ function vv_network_stats(): array {
|
||||
$stateFile = '/tmp/vv_net_stat.json';
|
||||
$now = ['rx' => $rxBytes, 'tx' => $txBytes, 'ts' => microtime(true)];
|
||||
$prev = file_exists($stateFile) ? (json_decode(file_get_contents($stateFile), true) ?: []) : [];
|
||||
file_put_contents($stateFile, json_encode($now));
|
||||
$tmp = $stateFile . '.tmp';
|
||||
file_put_contents($tmp, json_encode($now));
|
||||
rename($tmp, $stateFile);
|
||||
|
||||
$rxRate = $txRate = 0;
|
||||
if (!empty($prev['ts']) && ($dt = $now['ts'] - $prev['ts']) > 0.1) {
|
||||
@@ -314,15 +313,15 @@ function vv_network_stats(): array {
|
||||
) ?: '');
|
||||
|
||||
// External IP — curl ifconfig.me, cached 5 min so we don't hammer it
|
||||
$extIpCache = '/tmp/vv_ext_ip.cache';
|
||||
$extIp = '';
|
||||
if (file_exists($extIpCache) && (time() - filemtime($extIpCache)) < 300) {
|
||||
$extIp = trim(file_get_contents($extIpCache) ?: '');
|
||||
$extIp = '';
|
||||
$extData = vv_cache_read('ext_ip', 300);
|
||||
if ($extData) {
|
||||
$extIp = $extData['ip'] ?? '';
|
||||
} else {
|
||||
$fetched = trim(shell_exec('curl -sf --max-time 4 https://ifconfig.me 2>/dev/null') ?: '');
|
||||
if (preg_match('/^\d+\.\d+\.\d+\.\d+$/', $fetched)) {
|
||||
$extIp = $fetched;
|
||||
file_put_contents($extIpCache, $extIp);
|
||||
vv_cache_write('ext_ip', ['ip' => $extIp]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -312,3 +312,57 @@ function vv_cache_write(string $key, array $data): void {
|
||||
file_put_contents($tmp, json_encode($data));
|
||||
rename($tmp, $f);
|
||||
}
|
||||
|
||||
// ── Shared utility functions (used across include/ and api/ files) ────────────
|
||||
|
||||
// Format seconds into "2d 3h 15m".
|
||||
function vv_format_uptime(int $seconds): string {
|
||||
$d = intdiv($seconds, 86400);
|
||||
$h = intdiv($seconds % 86400, 3600);
|
||||
$m = intdiv($seconds % 3600, 60);
|
||||
return ($d ? "{$d}d " : '') . ($h ? "{$h}h " : '') . "{$m}m";
|
||||
}
|
||||
|
||||
// Parse a scalar value from raw conf text. Matches KEY="value" or KEY=value.
|
||||
// Identical logic was previously duplicated as vv_arr_scalar / vv_wd_scalar /
|
||||
// vv_fb_scalar / vv_media_conf_scalar — all reduce to this one regex.
|
||||
function vv_parse_conf_scalar(string $raw, string $key): string {
|
||||
return preg_match('/^\s*' . preg_quote($key, '/') . '\s*=\s*"?([^"\n]*)"?/m', $raw, $m)
|
||||
? trim($m[1]) : '';
|
||||
}
|
||||
|
||||
// Parse a key=value state file (e.g. fallback_state.db, partnership_state.db).
|
||||
// Returns ['key' => 'value', ...]. Lines without '=' are ignored.
|
||||
function vv_parse_kv_db(string $text): array {
|
||||
$out = [];
|
||||
foreach (explode("\n", $text) as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '' || $line[0] === '#') continue;
|
||||
[$k, $v] = array_pad(explode('=', $line, 2), 2, '');
|
||||
if ($k !== '') $out[trim($k)] = trim($v);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
// All configured hosts from master.conf as ['host1' => 'hostname', ...].
|
||||
// Canonical version — previously duplicated as vv_arr_known_hosts / vv_fb_known_hosts.
|
||||
function vv_known_hosts(): array {
|
||||
$vars = vv_conf_vars();
|
||||
$hosts = [];
|
||||
foreach ($vars as $k => $v) {
|
||||
if (preg_match('/^HOST(\d+)$/', $k, $m) && $v !== '') {
|
||||
$hosts['host' . $m[1]] = $v;
|
||||
}
|
||||
}
|
||||
ksort($hosts);
|
||||
return $hosts ?: ['host1' => 'HOST1'];
|
||||
}
|
||||
|
||||
// Local LAN IP via routing table — static-cached per request.
|
||||
// Previously duplicated in include/docker_folders.php and inline in include/docker.php.
|
||||
function vv_local_ip(): string {
|
||||
static $ip = null;
|
||||
if ($ip !== null) return $ip;
|
||||
$ip = trim(shell_exec("ip route get 8.8.8.8 2>/dev/null | awk '/src/{for(i=1;i<=NF;i++)if(\$i==\"src\")print \$(i+1)}'") ?? '');
|
||||
return $ip;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<?php
|
||||
function vv_local_ip(): string {
|
||||
static $ip = null;
|
||||
if ($ip !== null) return $ip;
|
||||
$ip = trim(shell_exec("ip route get 8.8.8.8 2>/dev/null | awk '/src/{for(i=1;i<=NF;i++)if(\$i==\"src\")print \$(i+1)}'") ?? '');
|
||||
if (!$ip) $ip = gethostbyname(gethostname());
|
||||
return $ip;
|
||||
}
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
function vv_container_webui(string $name, array $portMap): string {
|
||||
$template = '/boot/config/plugins/dockerMan/templates-user/my-' . $name . '.xml';
|
||||
|
||||
@@ -7,15 +7,11 @@ require_once __DIR__ . '/partnership.php'; // vv_pt_ssh(), vv_pt_ts_peers()
|
||||
// ── Conf parsers ──────────────────────────────────────────────────────────────
|
||||
|
||||
function vv_fb_bash_array(string $raw, string $varname): array {
|
||||
if (!preg_match('/^\s*' . preg_quote($varname, '/') . '\s*=\s*\(\s*(.*?)\s*\)/ms', $raw, $m))
|
||||
return [];
|
||||
preg_match_all('/"([^"]*)"/', $m[1], $items);
|
||||
return array_values(array_filter($items[1]));
|
||||
return vv_parse_bash_array($raw, $varname);
|
||||
}
|
||||
|
||||
function vv_fb_scalar(string $raw, string $varname): string {
|
||||
return preg_match('/^\s*' . preg_quote($varname, '/') . '\s*=\s*"?([^"\n]*)"?/m', $raw, $m)
|
||||
? trim($m[1]) : '';
|
||||
return vv_parse_conf_scalar($raw, $varname);
|
||||
}
|
||||
|
||||
// ── State file ────────────────────────────────────────────────────────────────
|
||||
@@ -90,16 +86,7 @@ function vv_fb_covers(string $covering, string $remote, string $coveringRaw, str
|
||||
// ── Known hosts ───────────────────────────────────────────────────────────────
|
||||
|
||||
function vv_fb_known_hosts(): array {
|
||||
$master = vv_read_conf_raw('master.conf');
|
||||
preg_match_all('/^\s*(HOST(\d+))(?:_NAME)?\s*=\s*["\']?(\S+?)["\']?\s*$/m', $master, $m);
|
||||
$hosts = [];
|
||||
foreach ($m[1] as $i => $key) {
|
||||
$num = $m[2][$i];
|
||||
$name = trim($m[3][$i]);
|
||||
$hosts['host' . $num] = $name;
|
||||
}
|
||||
ksort($hosts);
|
||||
return $hosts ?: ['host1' => 'HOST1'];
|
||||
return vv_known_hosts();
|
||||
}
|
||||
|
||||
// ── Main data builder ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -6,8 +6,7 @@ require_once __DIR__ . '/config.php';
|
||||
// ── Conf reader ───────────────────────────────────────────────────────────────
|
||||
|
||||
function vv_media_conf_scalar(string $raw, string $key): string {
|
||||
return preg_match('/^\s*' . preg_quote($key, '/') . '\s*=\s*"?([^"\n]*)"?/m', $raw, $m)
|
||||
? trim($m[1]) : '';
|
||||
return vv_parse_conf_scalar($raw, $key);
|
||||
}
|
||||
|
||||
// ── Server list from host conf ────────────────────────────────────────────────
|
||||
|
||||
@@ -7,10 +7,7 @@ require_once __DIR__ . '/partnership.php';
|
||||
// ── Conf array parser (bash arrays) ──────────────────────────────────────────
|
||||
|
||||
function vv_wd_bash_array(string $raw, string $varname): array {
|
||||
if (!preg_match('/^\s*' . preg_quote($varname, '/') . '\s*=\s*\(\s*(.*?)\s*\)/ms', $raw, $m))
|
||||
return [];
|
||||
preg_match_all('/"([^"]*)"/', $m[1], $items);
|
||||
return array_values(array_filter($items[1]));
|
||||
return vv_parse_bash_array($raw, $varname);
|
||||
}
|
||||
|
||||
function vv_wd_bash_assoc(string $raw, string $varname): array {
|
||||
@@ -24,8 +21,7 @@ function vv_wd_bash_assoc(string $raw, string $varname): array {
|
||||
}
|
||||
|
||||
function vv_wd_scalar(string $raw, string $varname): string {
|
||||
return preg_match('/^\s*' . preg_quote($varname, '/') . '\s*=\s*"?([^"\n]*)"?/m', $raw, $m)
|
||||
? trim($m[1]) : '';
|
||||
return vv_parse_conf_scalar($raw, $varname);
|
||||
}
|
||||
|
||||
// ── State file parsers ────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user