Add storage mode migration: internal NVMe vs USB flash, Settings tab, fix hardcoded /boot/ paths
This commit is contained in:
@@ -68,6 +68,18 @@
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
# ==============================================================================================
|
||||
# ── STORAGE MODE ──────────────────────────────────────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
|
||||
# ━━━ Storage mode ━━━
|
||||
# Controls where Varaverk stores scripts, conf, and state files.
|
||||
# true = internal NVMe/SSD — /boot/config/plugins/varaverk (write-safe, git-direct)
|
||||
# false = USB flash boot — /mnt/user/appdata/Varaverk (preserves flash lifetime)
|
||||
# Auto-detected from boot device transport on first setup.
|
||||
# To change: Settings → Storage → Migrate.
|
||||
HOSTN_STORAGE_MODE_INTERNAL=true
|
||||
|
||||
# ==============================================================================================
|
||||
# ── IDENTITY & CONNECTIVITY ───────────────────────────────────────────────────────────────────
|
||||
# ==============================================================================================
|
||||
|
||||
@@ -291,11 +291,16 @@ if [[ "$DRY_RUN" == false ]]; then
|
||||
|
||||
# Push updated master.conf to new owner so both servers agree immediately.
|
||||
# master.conf is shared — host-specific credentials live in host*.conf.
|
||||
_REMOTE_SD=$(ssh -i "$SSH_KEY" -o ConnectTimeout=5 -o StrictHostKeyChecking=no \
|
||||
"root@${MIRROR_IP}" \
|
||||
"grep -m1 '^SCRIPTS_DIR' /boot/config/plugins/varaverk/varaverk.cfg 2>/dev/null | cut -d= -f2 | tr -d '\"'" \
|
||||
2>/dev/null | tr -d '[:space:]')
|
||||
_REMOTE_SD="${_REMOTE_SD:-/boot/config/plugins/varaverk}"
|
||||
scp -i "$SSH_KEY" \
|
||||
-o ConnectTimeout="$SSH_TIMEOUT" \
|
||||
-o StrictHostKeyChecking=no \
|
||||
"$SCRIPTS_ROOT/Configurations/master.conf" \
|
||||
"root@${MIRROR_IP}:/boot/config/plugins/varaverk/Configurations/master.conf" 2>/dev/null && \
|
||||
"root@${MIRROR_IP}:${_REMOTE_SD}/Configurations/master.conf" 2>/dev/null && \
|
||||
log "master.conf pushed to $NEW_OWNER ✅" || \
|
||||
error "Failed to push master.conf to $NEW_OWNER — set PARTNERSHIP_OWNER_HOST=\"$NEW_OWNER_ID\" manually"
|
||||
else
|
||||
|
||||
@@ -24,9 +24,9 @@ unset($_master, $_h1m, $_host1_blank, $_my_hostid, $_conf_missing);
|
||||
|
||||
// Determine active tab
|
||||
$tab = $_GET['tab'] ?? 'monitor';
|
||||
$validTabs = ['monitor', 'scheduler', 'docker', 'watchdog', 'partnership', 'fallback', 'arrs', 'rsync'];
|
||||
$validTabs = ['monitor', 'scheduler', 'docker', 'watchdog', 'partnership', 'fallback', 'arrs', 'rsync', 'settings'];
|
||||
if (!in_array($tab, $validTabs)) $tab = 'monitor';
|
||||
$tabLabels = ['monitor' => 'Monitor', 'scheduler' => 'Scheduler', 'docker' => 'Docker', 'watchdog' => 'Watchdog', 'partnership' => 'Partnership', 'fallback' => 'FallBack', 'arrs' => 'Arrs', 'rsync' => 'Rsync'];
|
||||
$tabLabels = ['monitor' => 'Monitor', 'scheduler' => 'Scheduler', 'docker' => 'Docker', 'watchdog' => 'Watchdog', 'partnership' => 'Partnership', 'fallback' => 'FallBack', 'arrs' => 'Arrs', 'rsync' => 'Rsync', 'settings' => 'Settings'];
|
||||
?>
|
||||
|
||||
<link rel="stylesheet" href="/plugins/<?=$plugin?>/css/varaverk.css">
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once dirname(__DIR__) . '/include/config.php';
|
||||
|
||||
$action = $_GET['action'] ?? $_POST['action'] ?? '';
|
||||
|
||||
// ── Boot device detection ─────────────────────────────────────────────────────
|
||||
function vv_storage_detect_transport(): string {
|
||||
$part = trim(shell_exec("findmnt -n -o SOURCE /boot 2>/dev/null") ?: '');
|
||||
if (!$part) return 'unknown';
|
||||
$disk = trim(shell_exec("lsblk -no pkname " . escapeshellarg($part) . " 2>/dev/null") ?: '');
|
||||
if (!$disk) return 'unknown';
|
||||
return strtolower(trim(shell_exec("lsblk -dno TRAN /dev/" . escapeshellarg($disk) . " 2>/dev/null") ?: 'unknown'));
|
||||
}
|
||||
|
||||
// ── Current mode status ───────────────────────────────────────────────────────
|
||||
if ($action === 'status') {
|
||||
$transport = vv_storage_detect_transport();
|
||||
$detected = ($transport === 'usb') ? 'flash' : 'internal';
|
||||
$currentDir = SCRIPTS_DIR;
|
||||
$internalDir = '/boot/config/plugins/varaverk';
|
||||
$flashDir = '/mnt/user/appdata/Varaverk';
|
||||
$currentMode = ($currentDir === $internalDir) ? 'internal'
|
||||
: ($currentDir === $flashDir ? 'flash' : 'custom');
|
||||
|
||||
$myHost = vv_detect_host();
|
||||
$vars = vv_conf_vars();
|
||||
$confKey = strtoupper($myHost) . '_STORAGE_MODE_INTERNAL';
|
||||
$confVal = $vars[$confKey] ?? null;
|
||||
|
||||
// Boot device name for display
|
||||
$bootPart = trim(shell_exec("findmnt -n -o SOURCE /boot 2>/dev/null") ?: '');
|
||||
$bootDisk = $bootPart ? trim(shell_exec("lsblk -no pkname " . escapeshellarg($bootPart) . " 2>/dev/null") ?: '') : '';
|
||||
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'current_mode' => $currentMode,
|
||||
'current_dir' => $currentDir,
|
||||
'internal_dir' => $internalDir,
|
||||
'flash_dir' => $flashDir,
|
||||
'transport' => $transport,
|
||||
'detected' => $detected,
|
||||
'boot_disk' => $bootDisk ? '/dev/' . $bootDisk : 'unknown',
|
||||
'conf_key' => $confKey,
|
||||
'conf_val' => $confVal,
|
||||
'array_started'=> is_dir('/mnt/user') && count(scandir('/mnt/user')) > 2,
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Run migration ─────────────────────────────────────────────────────────────
|
||||
if ($action === 'migrate' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$to = trim($_POST['to'] ?? '');
|
||||
if (!in_array($to, ['internal', 'flash'], true)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'Invalid target: must be internal or flash']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$script = SCRIPTS_DIR . '/Tools/storage_migrate.sh';
|
||||
if (!file_exists($script)) {
|
||||
echo json_encode(['ok' => false, 'error' => 'storage_migrate.sh not found']);
|
||||
exit;
|
||||
}
|
||||
|
||||
set_time_limit(300);
|
||||
$output = [];
|
||||
$exit = 0;
|
||||
exec('bash ' . escapeshellarg($script) . ' --to=' . escapeshellarg($to) . ' 2>&1', $output, $exit);
|
||||
|
||||
echo json_encode([
|
||||
'ok' => $exit === 0,
|
||||
'exit' => $exit,
|
||||
'output' => implode("\n", $output),
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Auto-detect and write to conf ─────────────────────────────────────────────
|
||||
if ($action === 'detect' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$transport = vv_storage_detect_transport();
|
||||
$detected = ($transport === 'usb') ? 'false' : 'true';
|
||||
$myHost = vv_detect_host();
|
||||
$confKey = strtoupper($myHost) . '_STORAGE_MODE_INTERNAL';
|
||||
$confFile = $myHost . '.conf';
|
||||
|
||||
$results = vv_conf_write_changes([[
|
||||
'file' => $confFile,
|
||||
'key' => $confKey,
|
||||
'value' => $detected,
|
||||
'type' => 'scalar',
|
||||
]]);
|
||||
|
||||
$ok = !in_array(false, $results, true);
|
||||
echo json_encode(['ok' => $ok, 'detected' => $detected, 'transport' => $transport]);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['ok' => false, 'error' => 'Unknown action']);
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
define('VV_DOCKER_JSON', '/boot/config/plugins/varaverk/docker_folders.json');
|
||||
define('VV_DOCKER_JSON', SCRIPTS_DIR . '/docker_folders.json');
|
||||
define('VV_FV3_JSON', '/boot/config/plugins/folder.view3/docker.json');
|
||||
|
||||
// ── JSON helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
require_once __DIR__ . '/config.php';
|
||||
require_once __DIR__ . '/confform.php';
|
||||
|
||||
define('SCHEDULE_FILE', '/boot/config/plugins/varaverk/schedule.json');
|
||||
define('CRON_FILE', '/boot/config/plugins/varaverk/varaverk.cron');
|
||||
define('SCHEDULE_FILE', SCRIPTS_DIR . '/schedule.json');
|
||||
define('CRON_FILE', '/boot/config/plugins/varaverk/varaverk.cron'); // must stay in /boot — update_cron scans there
|
||||
|
||||
function vv_pretty_label(string $slug): string {
|
||||
return ucwords(str_replace('_', ' ', $slug));
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
$_myHost = vv_detect_host();
|
||||
$_vars = vv_conf_vars();
|
||||
$_confKey = strtoupper($_myHost) . '_STORAGE_MODE_INTERNAL';
|
||||
$_confVal = $_vars[$_confKey] ?? null;
|
||||
?>
|
||||
<style>
|
||||
.vv-set-card { background:#161616;border:1px solid #2a2a2a;border-radius:6px;padding:14px 16px;margin-bottom:14px; }
|
||||
.vv-set-hdr { font-size:11px;font-weight:700;color:#666;text-transform:uppercase;letter-spacing:.07em;margin-bottom:12px; }
|
||||
.vv-set-row { display:flex;justify-content:space-between;align-items:baseline;gap:8px;margin:4px 0; }
|
||||
.vv-set-lbl { font-size:11px;color:#444; }
|
||||
.vv-set-val { font-size:12px;color:#888;text-align:right;font-family:monospace; }
|
||||
.vv-set-badge { display:inline-block;font-size:10px;padding:2px 8px;border-radius:3px;font-weight:700;letter-spacing:.04em; }
|
||||
.vv-set-badge.internal { background:#0d1f0d;color:#4caf50;border:1px solid #1a3a1a; }
|
||||
.vv-set-badge.flash { background:#1a1200;color:#ffb74d;border:1px solid #3a2800; }
|
||||
.vv-set-badge.custom { background:#0a1a2a;color:#4a9eff;border:1px solid #1a3a5a; }
|
||||
.vv-set-sep { border:none;border-top:1px solid #1e1e1e;margin:10px 0; }
|
||||
.vv-set-btn { background:#1a1a1a;border:1px solid #333;color:#888;font-size:11px;padding:5px 14px;
|
||||
border-radius:3px;cursor:pointer;transition:border-color .15s,color .15s; }
|
||||
.vv-set-btn:hover { border-color:#555;color:#ccc; }
|
||||
.vv-set-btn:disabled { opacity:.4;cursor:default; }
|
||||
.vv-set-btn.primary { background:#1a2a1a;border-color:#2d4a2d;color:#4caf50; }
|
||||
.vv-set-btn.primary:hover { background:#223a22; }
|
||||
.vv-set-btn.warn { background:#1f1500;border-color:#3a2800;color:#ffb74d; }
|
||||
.vv-set-btn.warn:hover { background:#2a1e00; }
|
||||
.vv-set-out { background:#080808;border:1px solid #1a1a1a;border-radius:3px;padding:10px 12px;
|
||||
font-family:monospace;font-size:10px;color:#555;white-space:pre-wrap;word-break:break-all;
|
||||
max-height:320px;overflow-y:auto;margin-top:10px;display:none;
|
||||
scrollbar-width:none; }
|
||||
.vv-set-out::-webkit-scrollbar { display:none; }
|
||||
.vv-set-info { font-size:11px;color:#444;line-height:1.6;margin-bottom:10px; }
|
||||
.vv-set-warn-box { background:#1a1200;border:1px solid #3a2800;border-radius:4px;
|
||||
padding:8px 12px;font-size:11px;color:#ffb74d;margin-top:8px;display:none; }
|
||||
</style>
|
||||
|
||||
<div style="max-width:740px;margin:0 auto;">
|
||||
|
||||
<!-- Storage Location card -->
|
||||
<div class="vv-set-card" id="vv-stor-card">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
||||
<span class="vv-set-hdr" style="margin-bottom:0;">Storage Location</span>
|
||||
<span id="vv-stor-badge" class="vv-set-badge">…</span>
|
||||
</div>
|
||||
|
||||
<div id="vv-stor-info" style="font-size:11px;color:#3a3a3a;">Loading…</div>
|
||||
|
||||
<hr class="vv-set-sep">
|
||||
|
||||
<div class="vv-set-info">
|
||||
Varaverk stores all scripts, configuration, state files, and the git repository in
|
||||
<code style="font-size:10px;color:#4a7a4a;background:#080808;padding:1px 4px;border-radius:2px;">SCRIPTS_DIR</code>.
|
||||
<br>
|
||||
<strong style="color:#555;">Internal NVMe/SSD</strong> — direct access, zero write-wear concern, git pull/push from boot volume.<br>
|
||||
<strong style="color:#555;">USB Flash</strong> — preserves flash lifetime. Requires array to be started. git pull syncs
|
||||
<code style="font-size:10px;color:#4a7a4a;background:#080808;padding:1px 4px;border-radius:2px;">Plugin/</code>
|
||||
back to <code style="font-size:10px;color:#4a7a4a;background:#080808;padding:1px 4px;border-radius:2px;">/boot/</code>
|
||||
after each pull so the webUI always stays current.
|
||||
</div>
|
||||
|
||||
<div id="vv-stor-warn-flash" class="vv-set-warn-box">
|
||||
Array must be started for Varaverk to function in flash mode. The plugin tab is always accessible.
|
||||
</div>
|
||||
<div id="vv-stor-warn-noarray" class="vv-set-warn-box">
|
||||
Array is not started — cannot migrate to flash mode right now. Start the array first.
|
||||
</div>
|
||||
|
||||
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:10px;">
|
||||
<button id="vv-stor-migrate-btn" class="vv-set-btn" onclick="vvStorMigrate()" style="display:none;"></button>
|
||||
<button class="vv-set-btn" onclick="vvStorLoad()" style="font-size:10px;padding:4px 10px;">↻ Refresh</button>
|
||||
<span id="vv-stor-fb" style="font-size:11px;color:#444;"></span>
|
||||
</div>
|
||||
|
||||
<pre class="vv-set-out" id="vv-stor-out"></pre>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var _vvStorData = null;
|
||||
|
||||
function vvStorLoad() {
|
||||
const info = document.getElementById('vv-stor-info');
|
||||
const badge = document.getElementById('vv-stor-badge');
|
||||
if (info) info.textContent = 'Loading…';
|
||||
|
||||
fetch('/plugins/varaverk/api/storage.php?action=status&_=' + Date.now())
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
_vvStorData = d;
|
||||
if (!d.ok) { if (info) info.textContent = 'Error loading status'; return; }
|
||||
_vvStorRender(d);
|
||||
})
|
||||
.catch(() => { if (info) info.textContent = 'Request failed'; });
|
||||
}
|
||||
|
||||
function _vvStorRender(d) {
|
||||
const badge = document.getElementById('vv-stor-badge');
|
||||
const info = document.getElementById('vv-stor-info');
|
||||
const btn = document.getElementById('vv-stor-migrate-btn');
|
||||
const wFlash = document.getElementById('vv-stor-warn-flash');
|
||||
const wArr = document.getElementById('vv-stor-warn-noarray');
|
||||
|
||||
// Badge
|
||||
if (badge) {
|
||||
badge.textContent = d.current_mode.toUpperCase();
|
||||
badge.className = 'vv-set-badge ' + d.current_mode;
|
||||
}
|
||||
|
||||
// Info grid
|
||||
const modeLabel = d.current_mode === 'internal' ? 'Internal NVMe/SSD' : d.current_mode === 'flash' ? 'USB Flash' : 'Custom';
|
||||
const detLabel = d.detected === 'internal' ? 'internal NVMe/SSD' : 'USB flash';
|
||||
const confBadge = d.conf_val === null ? '<span style="color:#3a3a3a;">not set</span>'
|
||||
: d.conf_val === 'true' ? '<span style="color:#4caf50;">true (internal)</span>'
|
||||
: '<span style="color:#ffb74d;">false (flash)</span>';
|
||||
const matchIcon = (d.current_mode === d.detected || d.current_mode === 'custom') ? '' :
|
||||
' <span style="color:#ef5350;">⚠ mismatch with conf</span>';
|
||||
|
||||
if (info) info.innerHTML = `
|
||||
<div style="display:grid;grid-template-columns:auto 1fr;gap:3px 16px;align-items:baseline;">
|
||||
<span style="color:#333;">Mode</span> <span style="color:#888;">${modeLabel}${matchIcon}</span>
|
||||
<span style="color:#333;">SCRIPTS_DIR</span> <code style="font-size:10px;color:#4a7a4a;">${d.current_dir}</code>
|
||||
<span style="color:#333;">Boot device</span> <span style="color:#555;">${d.boot_disk} <span style="color:#2a2a2a;">(${d.transport})</span></span>
|
||||
<span style="color:#333;">Auto-detect</span> <span style="color:#555;">${detLabel}</span>
|
||||
<span style="color:#333;">${d.conf_key}</span> <span>${confBadge}</span>
|
||||
</div>`;
|
||||
|
||||
// Warnings
|
||||
if (wFlash) wFlash.style.display = (d.current_mode !== 'flash' && d.detected === 'usb') ? '' : 'none';
|
||||
if (wArr) wArr.style.display = 'none';
|
||||
|
||||
// Migrate button
|
||||
if (btn) {
|
||||
if (d.current_mode === 'internal') {
|
||||
btn.textContent = 'Migrate to Flash (appdata)';
|
||||
btn.className = 'vv-set-btn warn';
|
||||
btn.dataset.to = 'flash';
|
||||
btn.style.display = '';
|
||||
} else if (d.current_mode === 'flash') {
|
||||
btn.textContent = 'Migrate to Internal (/boot)';
|
||||
btn.className = 'vv-set-btn primary';
|
||||
btn.dataset.to = 'internal';
|
||||
btn.style.display = '';
|
||||
} else {
|
||||
btn.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function vvStorMigrate() {
|
||||
const btn = document.getElementById('vv-stor-migrate-btn');
|
||||
const out = document.getElementById('vv-stor-out');
|
||||
const fb = document.getElementById('vv-stor-fb');
|
||||
const wArr = document.getElementById('vv-stor-warn-noarray');
|
||||
const to = btn?.dataset.to;
|
||||
if (!to) return;
|
||||
|
||||
// Flash guard: array must be started
|
||||
if (to === 'flash' && _vvStorData && !_vvStorData.array_started) {
|
||||
if (wArr) wArr.style.display = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const label = to === 'flash' ? 'Flash (appdata)' : 'Internal (/boot)';
|
||||
if (!confirm(`Migrate Varaverk storage to ${label}?\n\nThis will:\n• Copy all scripts, conf, and git repo to the new location\n• Update varaverk.cfg and master.conf\n• Delete the old location\n\nThe page will need a reload after migration.`)) return;
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Migrating…';
|
||||
if (fb) fb.textContent = '';
|
||||
if (out) { out.textContent = 'Starting migration…\n'; out.style.display = ''; }
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('action', 'migrate');
|
||||
fd.append('to', to);
|
||||
|
||||
fetch('/plugins/varaverk/api/storage.php', { method: 'POST', body: fd })
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
btn.disabled = false;
|
||||
if (out) {
|
||||
out.textContent = d.output || '(no output)';
|
||||
out.scrollTop = out.scrollHeight;
|
||||
}
|
||||
if (d.ok) {
|
||||
btn.textContent = '✓ Done — reload page';
|
||||
btn.className = 'vv-set-btn primary';
|
||||
btn.onclick = () => location.reload();
|
||||
if (fb) { fb.style.color = '#4caf50'; fb.textContent = 'Migration complete — reload to apply'; }
|
||||
} else {
|
||||
btn.textContent = btn.dataset.to === 'flash' ? 'Migrate to Flash (appdata)' : 'Migrate to Internal (/boot)';
|
||||
if (fb) { fb.style.color = '#ef5350'; fb.textContent = 'Migration failed — see output above'; }
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
btn.disabled = false;
|
||||
btn.textContent = btn.dataset.to === 'flash' ? 'Migrate to Flash (appdata)' : 'Migrate to Internal (/boot)';
|
||||
if (fb) { fb.style.color = '#ef5350'; fb.textContent = 'Request failed: ' + e; }
|
||||
});
|
||||
}
|
||||
|
||||
vvStorLoad();
|
||||
</script>
|
||||
@@ -44,7 +44,9 @@ if [[ "$MANUAL" == false && -f "$MANUAL_TS_FILE" ]]; then
|
||||
# Only suppress for interval-based crons (*/N * * * *).
|
||||
# Static schedules like "30 2 * * 0" run at their appointed time and are never suppressed.
|
||||
INTERVAL=0
|
||||
SCHEDULE_FILE="/boot/config/plugins/varaverk/schedule.json"
|
||||
_VV_CFG="/boot/config/plugins/varaverk/varaverk.cfg"
|
||||
_SCRIPTS_DIR=$(grep -m1 '^SCRIPTS_DIR' "$_VV_CFG" 2>/dev/null | cut -d= -f2 | tr -d '"'"'" | tr -d '[:space:]')
|
||||
SCHEDULE_FILE="${_SCRIPTS_DIR:-/boot/config/plugins/varaverk}/schedule.json"
|
||||
if [[ -f "$SCHEDULE_FILE" ]]; then
|
||||
CRON_EXPR=$(php -r "
|
||||
\$s = json_decode(file_get_contents('$SCHEDULE_FILE'), true) ?: [];
|
||||
|
||||
Executable
+323
@@ -0,0 +1,323 @@
|
||||
#!/bin/bash
|
||||
# ==============================================================================================
|
||||
# ================================= Storage Migration ==========================================
|
||||
# ==============================================================================================
|
||||
#
|
||||
# PURPOSE
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Migrates Varaverk between internal NVMe and USB flash storage modes.
|
||||
#
|
||||
# Internal mode: SCRIPTS_DIR = /boot/config/plugins/varaverk
|
||||
# All scripts, conf, state, and git repo live on fast internal storage.
|
||||
# Direct git pull/push. Zero write-wear concern.
|
||||
#
|
||||
# Flash mode: SCRIPTS_DIR = /mnt/user/appdata/Varaverk
|
||||
# All scripts, conf, state, and git repo live in appdata.
|
||||
# Preserves USB flash lifetime. Array must be started for Varaverk to function.
|
||||
# git_pull_execute.sh syncs Plugin/ back to /boot/ after each pull so the
|
||||
# Unraid webUI always serves current PHP files.
|
||||
#
|
||||
# What this script updates:
|
||||
# varaverk.cfg SCRIPTS_DIR
|
||||
# master.conf TARGET_DIR
|
||||
# host*.conf HOST*_STORAGE_MODE_INTERNAL
|
||||
# varaverk.cron rebuilt via PHP (job paths regenerated for new SCRIPTS_DIR)
|
||||
#
|
||||
# ==============================================================================================
|
||||
# RUNTIME MODES
|
||||
# ==============================================================================================
|
||||
#
|
||||
# storage_migrate.sh --to=internal
|
||||
# Migrate to /boot/config/plugins/varaverk
|
||||
#
|
||||
# storage_migrate.sh --to=flash
|
||||
# Migrate to /mnt/user/appdata/Varaverk
|
||||
#
|
||||
# storage_migrate.sh --dry-run --to=<mode>
|
||||
# Show what would happen — no changes made
|
||||
#
|
||||
# storage_migrate.sh --status
|
||||
# Show current mode, paths, and boot device info
|
||||
#
|
||||
# ==============================================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/../load_config.sh"
|
||||
|
||||
parse_args "$@"
|
||||
acquire_lock
|
||||
|
||||
VV_CFG="/boot/config/plugins/varaverk/varaverk.cfg"
|
||||
INTERNAL_DIR="/boot/config/plugins/varaverk"
|
||||
FLASH_DIR="/mnt/user/appdata/Varaverk"
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Parse --to= from raw args (parse_args doesn't handle this flag)
|
||||
TO_MODE=""
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--to=internal) TO_MODE="internal" ;;
|
||||
--to=flash) TO_MODE="flash" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Boot device detection
|
||||
detect_boot_storage() {
|
||||
local boot_part boot_disk transport
|
||||
boot_part=$(findmnt -n -o SOURCE /boot 2>/dev/null)
|
||||
boot_disk=$(lsblk -no pkname "$boot_part" 2>/dev/null)
|
||||
transport=$(lsblk -dno TRAN "/dev/$boot_disk" 2>/dev/null | tr '[:upper:]' '[:lower:]')
|
||||
echo "${transport:-unknown}"
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Status
|
||||
if [[ "$SHOW_STATUS" == true ]]; then
|
||||
TRANSPORT=$(detect_boot_storage)
|
||||
DETECTED=$([[ "$TRANSPORT" == "usb" ]] && echo "flash" || echo "internal")
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SUMMARY STORAGE STATUS ━━━━━"
|
||||
echo "$ICON_GEAR SCRIPTS_DIR: $SCRIPTS_DIR"
|
||||
echo "$ICON_GEAR varaverk.cfg: $VV_CFG"
|
||||
echo "$ICON_HOST Boot device: transport=$TRANSPORT → detected=$DETECTED"
|
||||
echo "$ICON_GEAR Target dirs:"
|
||||
echo " internal: $INTERNAL_DIR"
|
||||
echo " flash: $FLASH_DIR"
|
||||
CONF_MODE=$(grep -m1 "${MY_ID}_STORAGE_MODE_INTERNAL" "$CONF_FILE" 2>/dev/null | cut -d= -f2 | tr -d '"' | tr -d '[:space:]')
|
||||
echo "$ICON_GEAR conf setting: ${MY_ID}_STORAGE_MODE_INTERNAL=${CONF_MODE:-not set}"
|
||||
if [[ "$SCRIPTS_DIR" == "$INTERNAL_DIR" ]]; then
|
||||
echo "$ICON_DONE Current mode: INTERNAL ✅"
|
||||
elif [[ "$SCRIPTS_DIR" == "$FLASH_DIR" ]]; then
|
||||
echo "$ICON_DONE Current mode: FLASH ✅"
|
||||
else
|
||||
echo "$ICON_WARN Current mode: CUSTOM ($SCRIPTS_DIR)"
|
||||
fi
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
if [[ -z "$TO_MODE" ]]; then
|
||||
error "Usage: storage_migrate.sh --to=internal|flash [--dry-run] [--log]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SRC="$SCRIPTS_DIR"
|
||||
DST=$([[ "$TO_MODE" == "internal" ]] && echo "$INTERNAL_DIR" || echo "$FLASH_DIR")
|
||||
NEW_INTERNAL=$([[ "$TO_MODE" == "internal" ]] && echo "true" || echo "false")
|
||||
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_SYNC Storage Migration ━━━━━"
|
||||
echo "$ICON_GEAR From: $SRC"
|
||||
echo "$ICON_GEAR To: $DST"
|
||||
echo "$ICON_GEAR Mode: $TO_MODE"
|
||||
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no changes will be made"
|
||||
echo ""
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Pre-flight checks
|
||||
if [[ "$SRC" == "$DST" ]]; then
|
||||
info "Already in $TO_MODE mode — nothing to do"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "$TO_MODE" == "flash" ]]; then
|
||||
if ! mountpoint -q /mnt/user 2>/dev/null; then
|
||||
error "Array not started — /mnt/user is not mounted. Start the array before migrating to flash."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -f "$SRC/load_config.sh" ]]; then
|
||||
error "Source directory looks invalid: $SRC (load_config.sh not found)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Step 1: Git push — ensure remote has everything before we touch the local repo
|
||||
echo "━━━ $ICON_SYNC Step 1: Git push ━━━"
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
if [[ -d "$SRC/.git" ]]; then
|
||||
log "Pushing to Gitea before migration..."
|
||||
if GIT_SSH_COMMAND="ssh -i $GITEA_SSH_KEY -p $SSH_PORT" \
|
||||
git -C "$SRC" push origin main 2>&1 | while IFS= read -r line; do echo " $line"; done; then
|
||||
echo " Git push complete ✅"
|
||||
else
|
||||
warn "Git push failed — continuing (data safe locally, push manually after migration)"
|
||||
fi
|
||||
else
|
||||
warn "No .git directory in $SRC — skipping push"
|
||||
fi
|
||||
else
|
||||
warn "DRY RUN — would push $SRC to Gitea"
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Step 2: Rsync content to destination
|
||||
echo ""
|
||||
echo "━━━ $ICON_SYNC Step 2: Copy files ━━━"
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
mkdir -p "$DST"
|
||||
echo " rsync: $SRC/ → $DST/"
|
||||
if rsync -av --delete \
|
||||
--exclude='.git' \
|
||||
"$SRC/" "$DST/" 2>&1 | \
|
||||
grep -v "/$" | \
|
||||
while IFS= read -r line; do log "$line"; done; then
|
||||
echo " Files copied ✅"
|
||||
else
|
||||
error "rsync failed — aborting migration"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy .git separately (rsync --exclude='.git' above skipped it)
|
||||
echo " Copying .git..."
|
||||
if cp -a "$SRC/.git" "$DST/.git" 2>/dev/null || \
|
||||
rsync -a "$SRC/.git/" "$DST/.git/" 2>/dev/null; then
|
||||
echo " .git copied ✅"
|
||||
else
|
||||
error ".git copy failed — aborting"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Mark git safe directory
|
||||
git config --global --add safe.directory "$DST" 2>/dev/null
|
||||
else
|
||||
warn "DRY RUN — would rsync $SRC/ → $DST/ (including .git)"
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Step 3: Update varaverk.cfg
|
||||
echo ""
|
||||
echo "━━━ $ICON_GEAR Step 3: Update varaverk.cfg ━━━"
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
if grep -q '^SCRIPTS_DIR' "$VV_CFG"; then
|
||||
sed -i "s|^SCRIPTS_DIR=.*|SCRIPTS_DIR=\"$DST\"|" "$VV_CFG"
|
||||
else
|
||||
echo "SCRIPTS_DIR=\"$DST\"" >> "$VV_CFG"
|
||||
fi
|
||||
echo " SCRIPTS_DIR → $DST ✅"
|
||||
else
|
||||
warn "DRY RUN — would set SCRIPTS_DIR=\"$DST\" in $VV_CFG"
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Step 4: Update TARGET_DIR in master.conf (new location)
|
||||
echo ""
|
||||
echo "━━━ $ICON_GEAR Step 4: Update TARGET_DIR ━━━"
|
||||
NEW_MASTER="$DST/Configurations/master.conf"
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
if [[ -f "$NEW_MASTER" ]]; then
|
||||
sed -i "s|^\(\s*TARGET_DIR\s*=\s*\)\"[^\"]*\"|\1\"$DST\"|" "$NEW_MASTER"
|
||||
echo " TARGET_DIR → $DST ✅"
|
||||
else
|
||||
error "master.conf not found at $NEW_MASTER"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
warn "DRY RUN — would set TARGET_DIR=\"$DST\" in master.conf"
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Step 5: Update STORAGE_MODE_INTERNAL in host*.conf (new location)
|
||||
echo ""
|
||||
echo "━━━ $ICON_GEAR Step 5: Update STORAGE_MODE_INTERNAL ━━━"
|
||||
NEW_CONF="$DST/Configurations/${MY_ID,,}.conf"
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
if [[ -f "$NEW_CONF" ]]; then
|
||||
if grep -q "${MY_ID}_STORAGE_MODE_INTERNAL" "$NEW_CONF"; then
|
||||
sed -i "s|^\(\s*${MY_ID}_STORAGE_MODE_INTERNAL\s*=\s*\).*|\1${NEW_INTERNAL}|" "$NEW_CONF"
|
||||
else
|
||||
sed -i "/# ━━━ Storage mode/a\\ ${MY_ID}_STORAGE_MODE_INTERNAL=${NEW_INTERNAL}" "$NEW_CONF"
|
||||
fi
|
||||
echo " ${MY_ID}_STORAGE_MODE_INTERNAL → $NEW_INTERNAL ✅"
|
||||
else
|
||||
warn "${MY_ID,,}.conf not found at $NEW_CONF — skipping conf update"
|
||||
fi
|
||||
else
|
||||
warn "DRY RUN — would set ${MY_ID}_STORAGE_MODE_INTERNAL=$NEW_INTERNAL"
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Step 6: Rebuild cron (paths must reference new SCRIPTS_DIR)
|
||||
echo ""
|
||||
echo "━━━ $ICON_GEAR Step 6: Rebuild cron ━━━"
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
RESULT=$(php -r "
|
||||
define('PLUGIN_CFG', '/boot/config/plugins/varaverk/varaverk.cfg');
|
||||
\$_c = @parse_ini_file(PLUGIN_CFG) ?: [];
|
||||
define('SCRIPTS_DIR', \$_c['SCRIPTS_DIR'] ?? '/boot/config/plugins/varaverk');
|
||||
define('CONF_DIR', SCRIPTS_DIR . '/Configurations');
|
||||
define('DATA_DIR', SCRIPTS_DIR . '/data');
|
||||
define('STATE_DIR', SCRIPTS_DIR . '/State_Files');
|
||||
define('LOG_DIR', '/var/log/varaverk');
|
||||
define('VV_SETUP_STATE_FILE', STATE_DIR . '/varaverk_setup.db');
|
||||
require_once '/usr/local/emhttp/plugins/varaverk/include/confform.php';
|
||||
require_once '/usr/local/emhttp/plugins/varaverk/include/scheduler.php';
|
||||
\$ok = vv_cron_rebuild(vv_schedule_load());
|
||||
echo \$ok ? 'ok' : 'fail';
|
||||
" 2>/dev/null)
|
||||
if [[ "$RESULT" == "ok" ]]; then
|
||||
echo " Cron rebuilt ✅"
|
||||
else
|
||||
warn "Cron rebuild failed — run Settings → Scheduler → Save to regenerate"
|
||||
fi
|
||||
else
|
||||
warn "DRY RUN — would rebuild cron with new SCRIPTS_DIR paths"
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Step 7: Flash mode — sync Plugin/ to /boot/ so webUI is current
|
||||
if [[ "$TO_MODE" == "flash" && "$DRY_RUN" == false ]]; then
|
||||
echo ""
|
||||
echo "━━━ $ICON_SYNC Step 7: Sync Plugin/ → /boot/ ━━━"
|
||||
if rsync -a --delete "$DST/Plugin/" "/boot/config/plugins/varaverk/Plugin/" 2>/dev/null; then
|
||||
echo " Plugin/ synced to /boot/ ✅"
|
||||
else
|
||||
warn "Plugin/ sync to /boot/ failed — webUI may be stale"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Step 8: Delete old location
|
||||
echo ""
|
||||
echo "━━━ $ICON_GEAR Step $([[ "$TO_MODE" == "flash" ]] && echo 8 || echo 7): Clean up old location ━━━"
|
||||
if [[ "$DRY_RUN" == false ]]; then
|
||||
if [[ "$SRC" == "$INTERNAL_DIR" ]]; then
|
||||
# Migrating internal→flash: keep varaverk.cfg and Plugin/ in /boot/, remove everything else
|
||||
echo " Removing scripts/conf/state from /boot/ (keeping Plugin/ and varaverk.cfg)..."
|
||||
find "$SRC" -mindepth 1 -maxdepth 1 \
|
||||
! -name 'Plugin' \
|
||||
! -name 'varaverk.cfg' \
|
||||
! -name '*.plg' \
|
||||
! -name '*.txz' \
|
||||
-exec rm -rf {} + 2>/dev/null
|
||||
echo " /boot/ cleaned ✅ (Plugin/ and varaverk.cfg preserved)"
|
||||
else
|
||||
# Migrating flash→internal: remove appdata copy entirely
|
||||
echo " Removing $SRC..."
|
||||
rm -rf "$SRC"
|
||||
echo " $SRC removed ✅"
|
||||
fi
|
||||
else
|
||||
if [[ "$SRC" == "$INTERNAL_DIR" ]]; then
|
||||
warn "DRY RUN — would remove scripts/conf/state from /boot/ (keeping Plugin/ and varaverk.cfg)"
|
||||
else
|
||||
warn "DRY RUN — would remove $SRC"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "━━━━━ $ICON_DONE Migration complete ━━━━━"
|
||||
echo "$ICON_GEAR Mode: $TO_MODE"
|
||||
echo "$ICON_GEAR SCRIPTS_DIR: $DST"
|
||||
if [[ "$TO_MODE" == "flash" ]]; then
|
||||
echo ""
|
||||
warn "IMPORTANT: Varaverk requires the array to be started to function in flash mode."
|
||||
warn "The webUI plugin tab will load normally at all times (Plugin/ stays in /boot/)."
|
||||
fi
|
||||
echo ""
|
||||
echo " Reload the Varaverk plugin tab to apply changes."
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
@@ -255,6 +255,19 @@ else
|
||||
log "Setting executable permissions on all .sh files..."
|
||||
find "$TARGET_DIR" -type f -name "*.sh" -exec chmod +x {} \;
|
||||
echo " Permissions set on .sh files"
|
||||
|
||||
# ── Flash mode: sync Plugin/ to /boot/ so the webUI picks up updates ─────
|
||||
# In flash mode SCRIPTS_DIR is in appdata — Plugin/ lives in the repo there
|
||||
# but Unraid serves PHP from /boot/. Sync after every pull to keep them in step.
|
||||
if [[ "$TARGET_DIR" != "/boot/config/plugins/varaverk" ]]; then
|
||||
echo ""
|
||||
echo "━━━ $ICON_SYNC Flash mode: sync Plugin/ → /boot/ ━━━"
|
||||
if rsync -a --delete "$TARGET_DIR/Plugin/" "/boot/config/plugins/varaverk/Plugin/" 2>/dev/null; then
|
||||
echo " Plugin/ synced to /boot/ ✅"
|
||||
else
|
||||
warn "Plugin/ sync to /boot/ failed — webUI may be stale until next pull"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
END=$(date +%s)
|
||||
|
||||
Reference in New Issue
Block a user