Add docker actions, arr profile enforcer, monitor caching, and web file symlink

Web files now served via symlink to the git repo so git pull changes survive
reboots without rebuilding the txz. Also includes: docker pull/rebuild/restart
with live log streaming, arr_profile_enforcer for Sonarr/Radarr quality
profiles, monitor page cache fix (background writer now in cron), and
ARR_KIDS/SONARR/RADARR profile name vars in master.conf.
This commit is contained in:
Gmer4Lfe
2026-06-19 11:09:40 -04:00
parent ac2986b141
commit f42ecc8464
15 changed files with 657 additions and 62 deletions
+76 -9
View File
@@ -1,21 +1,88 @@
<?php
header('Content-Type: application/json');
define('VV_JOB_DIR', '/tmp/varaverk_dk_jobs');
$action = trim($_POST['action'] ?? '');
$name = trim($_POST['name'] ?? '');
$jobId = trim($_POST['job_id'] ?? '');
if (!$name || !in_array($action, ['start', 'stop'], true)) {
echo json_encode(['ok' => false, 'error' => 'Invalid request']);
exit;
// ── Job status (no name required) ─────────────────────────────────────────────
if ($action === 'job_status') {
if (!$jobId || !preg_match('/^[0-9a-f]+$/', $jobId)) {
echo json_encode(['ok' => false, 'error' => 'invalid job_id']); exit;
}
$file = VV_JOB_DIR . '/' . $jobId . '.json';
if (!file_exists($file)) {
echo json_encode(['ok' => true, 'status' => 'pending']); exit;
}
echo file_get_contents($file); exit;
}
// Confirm container exists
$check = trim(shell_exec('docker ps -a --filter ' . escapeshellarg('name=^' . $name . '$') . " --format '{{.Names}}' 2>/dev/null") ?? '');
// ── Logs ──────────────────────────────────────────────────────────────────────
if ($action === 'logs') {
if (!$name || !preg_match('/^[a-zA-Z0-9_.-]+$/', $name)) {
echo json_encode(['ok' => false, 'error' => 'invalid name']); exit;
}
$out = shell_exec('docker logs --tail 200 --timestamps ' . escapeshellarg($name) . ' 2>&1');
echo json_encode(['ok' => true, 'logs' => $out ?? '']); exit;
}
// ── Container-scoped actions ──────────────────────────────────────────────────
if (!$name || !preg_match('/^[a-zA-Z0-9_.-]+$/', $name)) {
echo json_encode(['ok' => false, 'error' => 'invalid name']); exit;
}
$check = trim(shell_exec(
'docker ps -a --filter ' . escapeshellarg('name=^' . $name . '$') . " --format '{{.Names}}' 2>/dev/null"
) ?? '');
if ($check !== $name) {
echo json_encode(['ok' => false, 'error' => 'Container not found']);
exit;
echo json_encode(['ok' => false, 'error' => 'Container not found']); exit;
}
exec(($action === 'start' ? 'docker start' : 'docker stop') . ' ' . escapeshellarg($name) . ' 2>&1', $out, $rc);
if ($action === 'start' || $action === 'stop') {
exec(($action === 'start' ? 'docker start' : 'docker stop') . ' ' . escapeshellarg($name) . ' 2>&1', $out, $rc);
echo json_encode(['ok' => $rc === 0, 'output' => implode("\n", $out)]); exit;
}
echo json_encode(['ok' => $rc === 0, 'output' => implode("\n", $out)]);
if ($action === 'restart') {
$rebuild = '/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/rebuild_container';
if (is_executable($rebuild)) {
exec($rebuild . ' ' . escapeshellarg($name) . ' 2>&1', $out, $rc);
} else {
exec('docker stop ' . escapeshellarg($name) . ' 2>&1', $o1, $rc1);
exec('docker start ' . escapeshellarg($name) . ' 2>&1', $o2, $rc2);
$out = array_merge($o1, $o2);
$rc = ($rc1 === 0 && $rc2 === 0) ? 0 : 1;
}
echo json_encode(['ok' => $rc === 0, 'output' => implode("\n", $out)]); exit;
}
if ($action === 'pull_rebuild') {
@mkdir(VV_JOB_DIR, 0700, true);
$jobId = bin2hex(random_bytes(8));
$jobFile = VV_JOB_DIR . '/' . $jobId . '.json';
$worker = __DIR__ . '/docker_pull_worker.php';
$rebuild = '/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/rebuild_container';
$oldId = trim(shell_exec('docker inspect --format={{.Image}} ' . escapeshellarg($name) . ' 2>/dev/null') ?: '');
$image = trim(shell_exec('docker inspect --format={{.Config.Image}} ' . escapeshellarg($name) . ' 2>/dev/null') ?: '');
if (!$image) {
echo json_encode(['ok' => false, 'error' => 'Could not determine image']); exit;
}
file_put_contents($jobFile, json_encode(['ok' => true, 'status' => 'pulling', 'container' => $name]));
$cmd = 'php ' . escapeshellarg($worker) . ' ' .
escapeshellarg($name) . ' ' .
escapeshellarg($jobFile) . ' ' .
escapeshellarg($oldId) . ' ' .
escapeshellarg($image) . ' ' .
escapeshellarg($rebuild) . ' >/dev/null 2>&1 &';
exec($cmd);
echo json_encode(['ok' => true, 'status' => 'started', 'job_id' => $jobId]); exit;
}
echo json_encode(['ok' => false, 'error' => 'Unknown action']);
+34
View File
@@ -0,0 +1,34 @@
<?php
// Background worker: docker pull → compare image ID → rebuild if updated.
// Called via: php docker_pull_worker.php <name> <jobFile> <oldId> <image> <rebuild>
[$name, $jobFile, $oldId, $image, $rebuild] = array_slice($argv, 1, 5);
if (!$name || !$jobFile || !$image) exit(1);
function jw(string $f, array $d): void { file_put_contents($f, json_encode($d)); }
shell_exec('docker pull ' . escapeshellarg($image) . ' 2>&1');
$rawInfo = shell_exec('docker image inspect ' . escapeshellarg($image) . ' 2>/dev/null') ?: '[]';
$info = json_decode($rawInfo, true) ?: [];
$newId = $info[0]['Id'] ?? '';
if ($oldId && $newId && $oldId === $newId) {
jw($jobFile, ['ok' => true, 'status' => 'done', 'updated' => false, 'message' => 'Already up to date']);
exit;
}
jw($jobFile, ['ok' => true, 'status' => 'rebuilding']);
if ($rebuild && is_executable($rebuild)) {
exec($rebuild . ' ' . escapeshellarg($name) . ' 2>&1', $out, $rc);
} else {
exec('docker stop ' . escapeshellarg($name) . ' 2>&1', $o1, $rc1);
exec('docker start ' . escapeshellarg($name) . ' 2>&1', $o2, $rc2);
$rc = ($rc1 === 0 && $rc2 === 0) ? 0 : 1;
}
jw($jobFile, $rc === 0
? ['ok' => true, 'status' => 'done', 'updated' => true, 'message' => 'Updated and rebuilt']
: ['ok' => false, 'status' => 'done', 'error' => 'Rebuild failed after pull']
);
+3 -1
View File
@@ -48,7 +48,8 @@ if ($action === 'rsync_log') {
$base = vv_rsync_status();
$vars = vv_conf_vars();
$base['windows']['fallback'] = ($vars['FALLBACK_RSYNC_ENABLED'] ?? 'true') !== 'false';
$base['windows']['fallback'] = ($vars['FALLBACK_RSYNC_ENABLED'] ?? 'true') !== 'false';
$base['windows']['monthly'] = ($vars['MONTHLY_RSYNC_ENABLED'] ?? 'true') !== 'false';
// Bandwidth history — last 30 days
$bwLog = DATA_DIR . '/bandwidth_history.db';
@@ -82,6 +83,7 @@ $winArrayDefs = [
'intermediate' => ['INTERMEDIATE_MAINTENANCE_SCRIPTS', "{$myId}_INTERMEDIATE_SYNC_SHARES"],
'daily' => ['DAILY_MAINTENANCE_SCRIPTS', "{$myId}_DAILY_SYNC_SHARES"],
'weekly' => ['WEEKLY_MAINTENANCE_SCRIPTS', "{$myId}_WEEKLY_SYNC_SHARES"],
'monthly' => ['MONTHLY_MAINTENANCE_SCRIPTS', "{$myId}_MONTHLY_SYNC_SHARES"],
'fallback' => [null, null],
];
$winArrays = [];