- TARGET_DIR, DATA_DIR: /mnt/user/appdata/unraid_scripts → /mnt/user/appdata/varaverk - GITEA_REPO_PATH: Varaverk/Unraid_Scripts.git → Varaverk/varaverk.git - DEV_ROOT: .../Development/Unraid_Scripts → .../Development/varaverk - deploy hook path updated in .claude/settings.json - All script comments, docs, and manual paths updated throughout
180 lines
6.7 KiB
PHP
180 lines
6.7 KiB
PHP
<?php
|
|
// Config file parser and writer.
|
|
// Reads master.conf and the appropriate host*.conf based on running host.
|
|
|
|
define('PLUGIN_CFG', '/boot/config/plugins/varaverk/varaverk.cfg');
|
|
|
|
$_vv_cfg = @parse_ini_file(PLUGIN_CFG) ?: [];
|
|
define('SCRIPTS_DIR', $_vv_cfg['SCRIPTS_DIR'] ?? '/mnt/user/appdata/varaverk');
|
|
define('CONF_DIR', SCRIPTS_DIR . '/Configurations');
|
|
define('LOG_DIR', '/var/log/varaverk');
|
|
unset($_vv_cfg);
|
|
|
|
function vv_get_hostname(): string {
|
|
return trim(shell_exec('hostname -s') ?: '');
|
|
}
|
|
|
|
// Mirror of common.sh resolve_tailscale_ip(): tries `tailscale ip -4` first (Tailscale manages
|
|
// the mapping so this survives IP changes), falls back to parsing `tailscale status` text.
|
|
function vv_resolve_tailscale_ip(string $hostname): string {
|
|
$h = strtolower($hostname);
|
|
$ip = trim(shell_exec('tailscale ip -4 ' . escapeshellarg($h) . ' 2>/dev/null') ?: '');
|
|
if ($ip) return $ip;
|
|
$out = shell_exec('tailscale status 2>/dev/null') ?: '';
|
|
foreach (explode("\n", $out) as $line) {
|
|
$cols = preg_split('/\s+/', trim($line));
|
|
if (isset($cols[1]) && stripos($cols[1], $h . '.') === 0) return $cols[0];
|
|
}
|
|
return '';
|
|
}
|
|
|
|
function vv_detect_host(): string {
|
|
// Reads master.conf for HOST1="name" (or HOST1_NAME="name") and matches running hostname.
|
|
// Returns 'host1', 'host2', 'host3', ... or 'unknown'. Works for any number of hosts.
|
|
$master = vv_read_conf_raw('master.conf');
|
|
preg_match_all('/^\s*(HOST\d+)(?:_NAME)?\s*=\s*["\']?(\S+?)["\']?\s*$/m', $master, $m);
|
|
$hostname = vv_get_hostname();
|
|
foreach ($m[1] as $i => $key) {
|
|
if (strcasecmp($hostname, trim($m[2][$i])) === 0) return strtolower($key);
|
|
}
|
|
return 'unknown';
|
|
}
|
|
|
|
function vv_is_owner(): bool {
|
|
return vv_detect_host() === 'host1';
|
|
}
|
|
|
|
function vv_read_conf_raw(string $filename): string {
|
|
$path = CONF_DIR . '/' . $filename;
|
|
return file_exists($path) ? file_get_contents($path) : '';
|
|
}
|
|
|
|
function vv_write_conf_raw(string $filename, string $content): bool {
|
|
$path = CONF_DIR . '/' . $filename;
|
|
$tmp = $path . '.vv.tmp';
|
|
if (file_put_contents($tmp, $content) === false) return false;
|
|
return rename($tmp, $path);
|
|
}
|
|
|
|
function vv_get_conf_files(): array {
|
|
// Returns conf files this host is allowed to view/edit
|
|
$host = vv_detect_host();
|
|
$files = [];
|
|
if ($host === 'host1') {
|
|
// Owner sees master.conf + their own host conf
|
|
$files[] = 'master.conf';
|
|
$files[] = 'host1.conf';
|
|
} elseif (preg_match('/^host(\d+)$/', $host)) {
|
|
// Any other numbered host sees only their own conf
|
|
$files[] = $host . '.conf';
|
|
} else {
|
|
// Unknown host — show all for dev/debug
|
|
foreach (glob(CONF_DIR . '/*.conf') as $f) {
|
|
$files[] = basename($f);
|
|
}
|
|
}
|
|
return $files;
|
|
}
|
|
|
|
// Parse conf into key=>value map for $VAR substitution in docs
|
|
function vv_conf_vars(): array {
|
|
$vars = [];
|
|
$files = ['master.conf'];
|
|
$host = vv_detect_host();
|
|
if (preg_match('/^host\d+$/', $host)) $files[] = $host . '.conf';
|
|
|
|
foreach ($files as $f) {
|
|
$raw = vv_read_conf_raw($f);
|
|
// Match: VAR_NAME="value" or VAR_NAME=value (no quotes)
|
|
preg_match_all('/^\s*([A-Z0-9_]+)\s*=\s*["\']?([^"\'#\n]*?)["\']?\s*(?:#.*)?$/m', $raw, $m);
|
|
foreach ($m[1] as $i => $key) {
|
|
$vars[$key] = trim($m[2][$i]);
|
|
}
|
|
}
|
|
return $vars;
|
|
}
|
|
|
|
// Query the Unraid GraphQL API for a given host.
|
|
// For the local host queries http://localhost/graphql; for remote hosts uses the Tailscale IP.
|
|
// $apiKey may be passed explicitly (needed when querying a remote host from the local host,
|
|
// since vv_conf_vars() only loads the current host's conf file).
|
|
// Returns the decoded 'data' object on success, null on any failure.
|
|
// Debug log written to /tmp/vv_api_debug.json on failure.
|
|
function vv_unraid_api_query(string $hostId, string $gql, int $timeoutSec = 5, string $apiKey = ''): ?array {
|
|
$vars = vv_conf_vars();
|
|
$key = $apiKey ?: ($vars[strtoupper($hostId) . '_UNRAID_API_KEY'] ?? '');
|
|
if (!$key) return null;
|
|
|
|
$myHostId = vv_detect_host();
|
|
if (strtolower($hostId) === strtolower($myHostId)) {
|
|
$url = 'http://localhost/graphql';
|
|
} else {
|
|
$hostname = $vars[strtoupper($hostId)] ?? '';
|
|
if (!$hostname) return null;
|
|
$ip = vv_resolve_tailscale_ip($hostname);
|
|
if (!$ip) return null;
|
|
$url = "http://{$ip}/graphql";
|
|
}
|
|
|
|
$body = json_encode(['query' => $gql]);
|
|
|
|
// Use curl (preferred — doesn't require allow_url_fopen, better error handling).
|
|
if (function_exists('curl_init')) {
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_POST => true,
|
|
CURLOPT_HTTPHEADER => ['Content-Type: application/json', "x-api-key: {$key}"],
|
|
CURLOPT_POSTFIELDS => $body,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_TIMEOUT => $timeoutSec,
|
|
CURLOPT_CONNECTTIMEOUT => 3,
|
|
CURLOPT_FOLLOWLOCATION => false,
|
|
]);
|
|
$resp = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$curlErr = curl_error($ch);
|
|
curl_close($ch);
|
|
} else {
|
|
// Fallback to file_get_contents if curl is unavailable.
|
|
$ctx = stream_context_create(['http' => [
|
|
'method' => 'POST',
|
|
'header' => "Content-Type: application/json\r\nx-api-key: {$key}",
|
|
'content' => $body,
|
|
'timeout' => $timeoutSec,
|
|
'ignore_errors' => true,
|
|
]]);
|
|
$resp = @file_get_contents($url, false, $ctx);
|
|
$httpCode = $resp !== false ? 200 : 0;
|
|
$curlErr = '';
|
|
}
|
|
|
|
if ($resp === false || $resp === '' || ($httpCode !== 0 && $httpCode !== 200)) {
|
|
@file_put_contents('/tmp/vv_api_debug.json', json_encode([
|
|
'ts' => time(),
|
|
'host' => $hostId,
|
|
'url' => $url,
|
|
'http_code' => $httpCode,
|
|
'curl_err' => $curlErr,
|
|
'response' => substr((string)$resp, 0, 800),
|
|
], JSON_PRETTY_PRINT));
|
|
return null;
|
|
}
|
|
|
|
$decoded = json_decode((string)$resp, true);
|
|
|
|
// If the API returned GraphQL errors, log them for diagnosis.
|
|
if (!empty($decoded['errors'])) {
|
|
@file_put_contents('/tmp/vv_api_debug.json', json_encode([
|
|
'ts' => time(),
|
|
'host' => $hostId,
|
|
'url' => $url,
|
|
'http_code' => $httpCode,
|
|
'errors' => $decoded['errors'],
|
|
'data' => $decoded['data'] ?? null,
|
|
], JSON_PRETTY_PRINT));
|
|
}
|
|
|
|
// data key present (even if null means query ran but returned nothing useful).
|
|
return array_key_exists('data', $decoded ?? []) ? $decoded['data'] : null;
|
|
}
|