partnership_onboard.sh: - --phase1-only (OWNER): SSH key exchange + conf push only; safe to run before HOST2 has Varaverk installed; writes HOST2_PHASE1_DONE to setup.db - --phase2-only (OWNER): skips SSH, runs Steps 2-9 (containers, arr stack, onboard, arr sync, conf push); writes HOST2_PHASE2_DONE to setup.db - Mirror path: after SSH setup, SSHes OWNER and fires --phase2-only in background (nohup); reads OWNER's SCRIPTS_DIR from varaverk.cfg first; falls back to manual instruction if SSH fails partnership.php (include): vv_pt_nodes() reads setup.db and exposes onboard_phase (0/1/2) per remote node partnership.php (page): phase-aware Actions section per partner host; node cards show phase badge (not provisioned / awaiting onboard / done); Phase 1 and Phase 2 buttons call run.php with extra_args monitor.php: partner card logo (top-right), CPU thread count, RAM total GB, unRAID version row in remote stats grid
186 lines
7.7 KiB
PHP
186 lines
7.7 KiB
PHP
<?php
|
|
// Partnership page data helpers
|
|
|
|
require_once __DIR__ . '/config.php';
|
|
require_once __DIR__ . '/arrs.php'; // vv_arr_known_hosts(), vv_arr_scalar()
|
|
|
|
// ── Config ────────────────────────────────────────────────────────────────────
|
|
|
|
function vv_pt_config(): array {
|
|
$v = vv_conf_vars();
|
|
return [
|
|
'enabled' => ($v['PARTNERSHIP_ENABLED'] ?? 'false') === 'true',
|
|
'owner_host' => $v['PARTNERSHIP_OWNER_HOST'] ?? '',
|
|
'sync_min' => (int)($v['PARTNERSHIP_SYNC_INTERVAL'] ?? 15),
|
|
'grace_hours' => (int)($v['PARTNERSHIP_GRACE_HOURS'] ?? 6),
|
|
'offline_threshold' => (int)($v['PARTNERSHIP_OFFLINE_THRESHOLD'] ?? 30),
|
|
'remove_tailscale' => ($v['PARTNERSHIP_REMOVE_TAILSCALE'] ?? 'true') === 'true',
|
|
'folderview3' => ($v['PARTNERSHIP_FOLDERVIEW3'] ?? 'false') === 'true',
|
|
'tailscale_configured' => !empty($v['TAILSCALE_API_KEY']) && !empty($v['TAILSCALE_TAILNET']),
|
|
];
|
|
}
|
|
|
|
// ── State file parser ─────────────────────────────────────────────────────────
|
|
|
|
function vv_pt_read_db(string $path): array {
|
|
if (!file_exists($path)) return [];
|
|
$out = [];
|
|
foreach (file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
|
|
[$k, $v] = array_pad(explode('=', trim($line), 2), 2, '');
|
|
if ($k) $out[trim($k)] = trim($v, '"\'');
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
// ── Tailscale peers ───────────────────────────────────────────────────────────
|
|
|
|
function vv_pt_ts_peers(): array {
|
|
$raw = shell_exec('tailscale status --json 2>/dev/null') ?: '{}';
|
|
$data = json_decode($raw, true) ?: [];
|
|
$peers = [];
|
|
|
|
// Self
|
|
$self = $data['Self'] ?? [];
|
|
$selfLabel = strtolower(explode('.', $self['DNSName'] ?? '')[0]);
|
|
if ($selfLabel) {
|
|
$peers[$selfLabel] = [
|
|
'online' => true,
|
|
'active' => true,
|
|
'ip' => $self['TailscaleIPs'][0] ?? null,
|
|
];
|
|
}
|
|
|
|
// Peers
|
|
foreach ($data['Peer'] ?? [] as $peer) {
|
|
$label = strtolower(explode('.', $peer['DNSName'] ?? '')[0]);
|
|
if (!$label) continue;
|
|
$peers[$label] = [
|
|
'online' => (bool)($peer['Online'] ?? false),
|
|
'active' => (bool)($peer['Active'] ?? false),
|
|
'ip' => $peer['TailscaleIPs'][0] ?? null,
|
|
];
|
|
}
|
|
return $peers;
|
|
}
|
|
|
|
// ── SSH helper — run a single command on a remote host ────────────────────────
|
|
|
|
function vv_pt_ssh(string $ip, string $sshKey, string $cmd, int $timeout = 4): string {
|
|
if (!$ip || !$sshKey || !file_exists($sshKey)) return '';
|
|
$full = sprintf(
|
|
'ssh -i %s -o ConnectTimeout=%d -o StrictHostKeyChecking=no -o BatchMode=yes root@%s %s 2>/dev/null',
|
|
escapeshellarg($sshKey), $timeout, escapeshellarg($ip), escapeshellarg($cmd)
|
|
);
|
|
return shell_exec($full) ?: '';
|
|
}
|
|
|
|
// ── System info ───────────────────────────────────────────────────────────────
|
|
|
|
function vv_pt_local_system(): array {
|
|
$ver = '';
|
|
if (file_exists('/etc/unraid-version')) {
|
|
preg_match('/VERSION="([^"]+)"/', file_get_contents('/etc/unraid-version'), $m);
|
|
$ver = $m[1] ?? '';
|
|
}
|
|
$uptime = 0;
|
|
if (file_exists('/proc/uptime')) {
|
|
$uptime = (int)explode(' ', file_get_contents('/proc/uptime'))[0];
|
|
}
|
|
return ['unraid_version' => $ver, 'uptime_sec' => $uptime];
|
|
}
|
|
|
|
function vv_pt_remote_system(string $ip, string $sshKey): array {
|
|
$out = vv_pt_ssh($ip, $sshKey,
|
|
'printf "%s\nUPTIME:%s\n" "$(cat /etc/unraid-version 2>/dev/null)" "$(cat /proc/uptime 2>/dev/null)"');
|
|
$ver = '';
|
|
preg_match('/VERSION="([^"]+)"/', $out, $m);
|
|
if ($m) $ver = $m[1];
|
|
$uptime = 0;
|
|
if (preg_match('/UPTIME:([\d.]+)/', $out, $m)) $uptime = (int)$m[1];
|
|
return ['unraid_version' => $ver, 'uptime_sec' => $uptime];
|
|
}
|
|
|
|
// ── Per-node data ─────────────────────────────────────────────────────────────
|
|
|
|
function vv_pt_nodes(): array {
|
|
$currentHost = vv_detect_host();
|
|
$hosts = vv_arr_known_hosts(); // ['host1' => 'hostname', ...]
|
|
$vars = vv_conf_vars();
|
|
$tsPeers = vv_pt_ts_peers();
|
|
$ownerSlot = strtolower($vars['PARTNERSHIP_OWNER_HOST'] ?? '');
|
|
$setupDb = vv_setup_state_read();
|
|
|
|
// SSH key for this host
|
|
$myId = strtoupper($currentHost);
|
|
$myRaw = vv_read_conf_raw($currentHost . '.conf');
|
|
$mySshKey = vv_arr_scalar($myRaw, $myId . '_SSH_KEY');
|
|
|
|
$nodes = [];
|
|
foreach ($hosts as $slot => $hostname) {
|
|
$isMe = ($slot === $currentHost || $currentHost === 'unknown');
|
|
$isOwner = (strtolower($ownerSlot) === $slot);
|
|
|
|
// Tailscale
|
|
$tsLabel = strtolower($hostname);
|
|
$ts = $tsPeers[$tsLabel] ?? ['online' => null, 'active' => false, 'ip' => null];
|
|
|
|
// Fallback state
|
|
$fbState = 'UNKNOWN';
|
|
$fbPath = '/boot/config/fallback_state.db';
|
|
if ($isMe) {
|
|
$fb = vv_pt_read_db($fbPath);
|
|
$fbState = $fb['state'] ?? 'UNKNOWN';
|
|
} elseif ($ts['online'] && $ts['ip'] && $mySshKey) {
|
|
$out = vv_pt_ssh($ts['ip'], $mySshKey, 'cat /boot/config/fallback_state.db 2>/dev/null');
|
|
if ($out) {
|
|
$fb = [];
|
|
foreach (explode("\n", $out) as $line) {
|
|
[$k, $v] = array_pad(explode('=', trim($line), 2), 2, '');
|
|
if ($k) $fb[trim($k)] = trim($v, '"\'');
|
|
}
|
|
$fbState = $fb['state'] ?? 'UNKNOWN';
|
|
}
|
|
}
|
|
|
|
// Partnership DB — local only (each server writes its own)
|
|
$dbPath = "/boot/config/partnership_{$hostname}.db";
|
|
$ptDb = vv_pt_read_db($dbPath);
|
|
|
|
// System info
|
|
$system = $isMe
|
|
? vv_pt_local_system()
|
|
: ($ts['online'] && $ts['ip'] && $mySshKey ? vv_pt_remote_system($ts['ip'], $mySshKey) : []);
|
|
|
|
// Onboard phase from setup.db — 0=not started, 1=SSH+conf done, 2=fully onboarded
|
|
$nodeIdUpper = strtoupper($slot);
|
|
$onboardPhase = ($setupDb[$nodeIdUpper . '_PHASE2_DONE'] ?? '') === 'true' ? 2
|
|
: (($setupDb[$nodeIdUpper . '_PHASE1_DONE'] ?? '') === 'true' ? 1 : 0);
|
|
|
|
$nodes[] = [
|
|
'slot' => $slot,
|
|
'id' => $nodeIdUpper,
|
|
'hostname' => $hostname,
|
|
'is_me' => $isMe,
|
|
'is_owner' => $isOwner,
|
|
'ts_online' => $ts['online'],
|
|
'ts_active' => $ts['active'],
|
|
'ts_ip' => $ts['ip'],
|
|
'fallback' => $fbState,
|
|
'partnership' => $ptDb,
|
|
'system' => $system,
|
|
'onboard_phase' => $isMe ? null : $onboardPhase,
|
|
];
|
|
}
|
|
return $nodes;
|
|
}
|
|
|
|
// ── Entry point ───────────────────────────────────────────────────────────────
|
|
|
|
function vv_partnership_all(): array {
|
|
return [
|
|
'config' => vv_pt_config(),
|
|
'nodes' => vv_pt_nodes(),
|
|
'ts' => time(),
|
|
];
|
|
}
|