Consolidate all paths to plugin flash dir, fix watchdog 7.3 triggers
- Move SCRIPTS_DIR/DATA_DIR/STATE_DIR from appdata to /boot/config/plugins/varaverk - All state files now in STATE_DIR (no more /tmp or /boot/config root writes) - Bootstrap: Gitea-first clone with GitHub fallback, no array dependency - varaverk.cfg seeded with Gitea connection settings - .gitignore: add State_Files/, varaverk.cfg, varaverk-*.txz - Partnership/transcode/fallback scripts use STATE_DIR variables - PHP config.php: DATA_DIR/STATE_DIR constants, VV_SETUP_STATE_FILE dynamic - deploy.sh PROD_ROOT updated to plugin flash dir Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
9191a54637
commit
fb0530deba
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
// Diagnostic endpoint — tests the Unraid GraphQL API and returns raw results.
|
||||
// Hit from browser: /plugins/varaverk/api/api_test.php
|
||||
header('Content-Type: application/json');
|
||||
require_once dirname(__DIR__) . '/include/config.php';
|
||||
|
||||
$hostId = vv_detect_host();
|
||||
$vars = vv_conf_vars();
|
||||
$key = $vars[strtoupper($hostId) . '_UNRAID_API_KEY'] ?? '';
|
||||
|
||||
$result = [
|
||||
'host' => $hostId,
|
||||
'key_present' => $key !== '',
|
||||
'key_prefix' => $key ? substr($key, 0, 8) . '...' : null,
|
||||
'curl_available' => function_exists('curl_init'),
|
||||
'allow_url_fopen'=> (bool)ini_get('allow_url_fopen'),
|
||||
'debug_log' => null,
|
||||
'probe' => null,
|
||||
'probe_raw' => null,
|
||||
];
|
||||
|
||||
// Show last debug log if present
|
||||
$debugFile = '/tmp/vv_api_debug.json';
|
||||
if (file_exists($debugFile)) {
|
||||
$result['debug_log'] = json_decode(file_get_contents($debugFile), true);
|
||||
}
|
||||
|
||||
// Run a minimal probe query
|
||||
if ($key) {
|
||||
$url = 'http://localhost/graphql';
|
||||
$body = json_encode(['query' => '{ info { os { hostname } } }']);
|
||||
|
||||
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 => 5,
|
||||
CURLOPT_CONNECTTIMEOUT => 3,
|
||||
]);
|
||||
$raw = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlErr = curl_error($ch);
|
||||
curl_close($ch);
|
||||
} else {
|
||||
$ctx = stream_context_create(['http' => [
|
||||
'method' => 'POST',
|
||||
'header' => "Content-Type: application/json\r\nx-api-key: {$key}",
|
||||
'content' => $body,
|
||||
'timeout' => 5,
|
||||
'ignore_errors' => true,
|
||||
]]);
|
||||
$raw = @file_get_contents($url, false, $ctx);
|
||||
$httpCode = $raw !== false ? 200 : 0;
|
||||
$curlErr = '';
|
||||
}
|
||||
|
||||
$result['probe'] = [
|
||||
'url' => $url,
|
||||
'http_code' => $httpCode,
|
||||
'curl_err' => $curlErr ?: null,
|
||||
'decoded' => json_decode((string)$raw, true),
|
||||
];
|
||||
$result['probe_raw'] = substr((string)$raw, 0, 1000);
|
||||
|
||||
// ── Schema introspection — discover actual field names ────────────────────────
|
||||
if ($key) {
|
||||
$types = ['InfoOs','InfoCpu','InfoMemory','ArrayDisk','ArrayParity','ArrayCache','Vm','Domain','VmDomain'];
|
||||
$introspectGql = '{ ' . implode(' ', array_map(fn($t) =>
|
||||
"{$t}: __type(name: \"{$t}\") { fields { name type { name kind ofType { name kind } } } }",
|
||||
$types
|
||||
)) . ' }';
|
||||
|
||||
$body2 = json_encode(['query' => $introspectGql]);
|
||||
$ch2 = curl_init('http://localhost/graphql');
|
||||
curl_setopt_array($ch2, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_HTTPHEADER => ['Content-Type: application/json', "x-api-key: {$key}"],
|
||||
CURLOPT_POSTFIELDS => $body2,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 8,
|
||||
]);
|
||||
$raw2 = curl_exec($ch2);
|
||||
curl_close($ch2);
|
||||
$result['schema'] = json_decode((string)$raw2, true)['data'] ?? null;
|
||||
|
||||
// Pool drive names — what does the API actually return for cache/pool drives?
|
||||
$ch_pools = curl_init('http://localhost/graphql');
|
||||
curl_setopt_array($ch_pools, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_HTTPHEADER => ['Content-Type: application/json', "x-api-key: {$key}"],
|
||||
CURLOPT_POSTFIELDS => json_encode(['query' => '{ array { caches { name device type status fsType } } }']),
|
||||
CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 8,
|
||||
]);
|
||||
$result['pool_drives'] = json_decode((string)curl_exec($ch_pools), true)['data'] ?? null;
|
||||
curl_close($ch_pools);
|
||||
|
||||
// Round 2: introspect CpuUtilization and MemoryUtilization field names
|
||||
$types2 = ['CpuUtilization','MemoryUtilization','TemperatureMetrics'];
|
||||
$gql2 = '{ ' . implode(' ', array_map(fn($t) =>
|
||||
"{$t}: __type(name: \"{$t}\") { kind fields { name type { name kind ofType { name kind } } } }",
|
||||
$types2
|
||||
)) . ' }';
|
||||
$ch3 = curl_init('http://localhost/graphql');
|
||||
curl_setopt_array($ch3, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_HTTPHEADER => ['Content-Type: application/json', "x-api-key: {$key}"],
|
||||
CURLOPT_POSTFIELDS => json_encode(['query' => $gql2]),
|
||||
CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 8,
|
||||
]);
|
||||
$result['schema2'] = json_decode((string)curl_exec($ch3), true)['data'] ?? null;
|
||||
curl_close($ch3);
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($result, JSON_PRETTY_PRINT);
|
||||
@@ -36,7 +36,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
|
||||
$results = vv_conf_write_changes($changes);
|
||||
echo json_encode(['ok' => !in_array(false, $results, true), 'files' => $results]);
|
||||
|
||||
// Propagate master.conf to partner hosts when the owner edits it (mirrors rawconf.php).
|
||||
$push = [];
|
||||
if (($results['master.conf'] ?? false) === true) {
|
||||
$push = vv_push_master_conf();
|
||||
vv_push_setup_state();
|
||||
}
|
||||
|
||||
echo json_encode(['ok' => !in_array(false, $results, true), 'files' => $results, 'push' => $push]);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
header('Cache-Control: no-store, no-cache');
|
||||
require_once dirname(__DIR__) . '/include/config.php';
|
||||
|
||||
$host = vv_detect_host();
|
||||
if (!preg_match('/^host\d+$/', $host)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'Cannot detect local host']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$hostUpper = strtoupper($host);
|
||||
$varName = $hostUpper . '_UNRAID_API_KEY';
|
||||
$confFile = $host . '.conf';
|
||||
|
||||
// Create/overwrite the Varaverk API key.
|
||||
// --description and --roles are required to suppress interactive prompts.
|
||||
// --overwrite replaces any existing key with the same name (keeps it to one).
|
||||
$dbg = ['ts' => date('H:i:s'), 'user' => trim(shell_exec('whoami'))];
|
||||
$output = shell_exec('timeout 10 /usr/local/sbin/unraid-api apikey --name "Varaverk" --create --overwrite --description "Varaverk plugin" --roles ADMIN --json </dev/null 2>&1');
|
||||
$dbg['raw'] = $output;
|
||||
file_put_contents('/tmp/vv_apikey_debug.json', json_encode($dbg, JSON_PRETTY_PRINT));
|
||||
|
||||
if (!$output) {
|
||||
echo json_encode(['ok' => false, 'error' => 'unraid-api returned no output — check /tmp/vv_apikey_debug.json']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$data = json_decode(trim($output), true);
|
||||
if (!is_array($data)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'Could not parse unraid-api output', 'raw' => substr($output, 0, 300)]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$key = $data['key'] ?? null;
|
||||
if (!$key) {
|
||||
echo json_encode(['ok' => false, 'error' => 'No key in response', 'raw' => substr($output, 0, 300)]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Read conf, replace the key value, write back
|
||||
$raw = vv_read_conf_raw($confFile);
|
||||
if ($raw === '') {
|
||||
echo json_encode(['ok' => false, 'error' => 'Cannot read ' . $confFile]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// If line is missing (older conf created before this field was added to the template),
|
||||
// insert it after HOST*_OWNER_EMAIL, or after HOST*_SSH_KEY, or append to file.
|
||||
if (!str_contains($raw, $varName)) {
|
||||
$inserted = false;
|
||||
foreach ([$hostUpper . '_OWNER_EMAIL', $hostUpper . '_SSH_KEY'] as $anchor) {
|
||||
if (str_contains($raw, $anchor)) {
|
||||
$raw = preg_replace(
|
||||
'/^(\s*' . preg_quote($anchor, '/') . '\s*=.*$)/m',
|
||||
'$1' . "\n " . $varName . '=""',
|
||||
$raw, 1
|
||||
);
|
||||
$inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$inserted) {
|
||||
$raw = rtrim($raw) . "\n " . $varName . '=""' . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Replace quoted value in-place
|
||||
$updated = preg_replace(
|
||||
'/^(\s*' . preg_quote($varName, '/') . '\s*=\s*)"[^"]*"/m',
|
||||
'${1}"' . $key . '"',
|
||||
$raw
|
||||
);
|
||||
|
||||
if (!vv_write_conf_raw($confFile, $updated)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'Failed to write ' . $confFile]);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'key_preview' => substr($key, 0, 8) . '...' . substr($key, -4),
|
||||
'conf_file' => $confFile,
|
||||
]);
|
||||
@@ -11,4 +11,12 @@ if (!$name || !preg_match('/^[A-Z_]+_RSYNC_ENABLED$/', $name)) {
|
||||
}
|
||||
|
||||
$ok = vv_conf_flag_set($name, $enabled);
|
||||
echo json_encode(['ok' => $ok, 'error' => $ok ? null : 'Failed to write master.conf']);
|
||||
|
||||
// master.conf is shared — propagate the change to partner hosts (no-op on non-owner).
|
||||
$push = [];
|
||||
if ($ok) {
|
||||
$push = vv_push_master_conf();
|
||||
vv_push_setup_state();
|
||||
}
|
||||
|
||||
echo json_encode(['ok' => $ok, 'error' => $ok ? null : 'Failed to write master.conf', 'push' => $push]);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
// Connectivity test — SSH echo to a partner host with round-trip latency.
|
||||
// GET ?id=HOST2 (GET avoids the bodyless-POST issue on this nginx setup).
|
||||
header('Content-Type: application/json');
|
||||
header('Cache-Control: no-store, no-cache');
|
||||
require_once dirname(__DIR__) . '/include/partnership.php';
|
||||
|
||||
$id = trim($_GET['id'] ?? '');
|
||||
if (!preg_match('/^host\d+$/i', $id)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'Invalid host id']);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(vv_pt_ping($id));
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
// Partnership-related settings for this host — the Partnership section of each accessible conf.
|
||||
// HOST1 (owner): master.conf PARTNERSHIP + host1.conf Partnership. Other hosts: their own.
|
||||
// Uses vv_conf_all_groups() (handles master.conf's sandwiched major header) then filters
|
||||
// to partnership sections. Writes go through confform.php (which pushes master.conf to partners).
|
||||
header('Content-Type: application/json');
|
||||
header('Cache-Control: no-store, no-cache');
|
||||
require_once dirname(__DIR__) . '/include/confform.php';
|
||||
|
||||
$files = vv_get_conf_files();
|
||||
$out = [];
|
||||
foreach ($files as $f) {
|
||||
$groups = array_values(array_filter(
|
||||
vv_conf_all_groups($f),
|
||||
fn($g) => stripos($g['subsection'], 'partnership') !== false
|
||||
));
|
||||
if ($groups) {
|
||||
$out[] = ['file' => $f, 'groups' => $groups];
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(['ok' => true, 'files' => $out]);
|
||||
@@ -50,7 +50,7 @@ if ($action === 'pull') {
|
||||
. ' -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@' . $ip;
|
||||
$remoteCfg = trim(shell_exec($sshBase . ' "grep SCRIPTS_DIR /boot/config/plugins/varaverk/varaverk.cfg 2>/dev/null"') ?: '');
|
||||
preg_match('/SCRIPTS_DIR\s*=\s*["\']?([^"\']+)["\']?/', $remoteCfg, $sm);
|
||||
$remoteConf = rtrim($sm[1] ?? '/mnt/user/appdata/Varaverk', '/') . '/Configurations';
|
||||
$remoteConf = rtrim($sm[1] ?? '/boot/config/plugins/varaverk', '/') . '/Configurations';
|
||||
|
||||
// SCP master.conf from HOST1
|
||||
$localMaster = CONF_DIR . '/master.conf';
|
||||
|
||||
Reference in New Issue
Block a user