This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation.

Summary:
1. Primary Request and Intent:
   - Fix "No transcode state — ramdisk_setup.sh not yet run" on the Monitor page
   - Investigate conf file security (are they in /tmp ramdisk)
   - Determine which orchestrators call conf_sync.sh
   - Add `--pull-only` mode to conf_sync.sh and wire it into intermediate_sync_maintenance.sh (runs every 4h) to keep partner conf RAM cache fresh
   - Do a broad codebase audit of Varaverk to find half-built or missing pieces
   - Build the two missing monthly maintenance scripts: zfs_pool_scrub.sh and smart_long_test.sh
   - Move Unraid-adapter-specific tools (api_cache_writer, conf_populate, remote_arr_cache_writer, storage_migrate) from Tools/ into Plugin/unraid/tools/
   - Update the Scheduler page Tools card to merge Plugin/unraid/tools/ scripts into it (excluding the background writers)

2. Key Technical Concepts:
   - Varaverk two-server Unraid home media ecosystem plugin
   - Plugin architecture: pages/, api/, include/, tools/ all under Plugin/unraid/
   - bash conf variables not expanded when read by PHP's vv_conf_vars() — must construct paths from primitive vars (STATE_DIR) not compound ones (TRANSCODE_STATE_FILE)
   - /tmp/.vv/config/cached/.confs/ — RAM-only store for partner host confs, cleared on reboot; own conf stays on flash
   - conf_sync.sh three modes: full (array start), --push-only (conf save), --pull-only (intermediate orch)
   - MONTHLY_MAINTENANCE_SCRIPTS — uptime-gated (≥30d) AND interval-gated (≥30d since last run), fires on 15th of each month
   - SMART extended self-test: sequential drive testing to avoid I/O saturation, polls firmware every 60s
   - ZFS pool scrub: starts scrub on all non-ignored pools, polls until complete
   - Platform adapter pattern: Plugin/$PLATFORM/ contains OS-specific code; Plugin/unraid/tools/ is the Unraid bash adapter layer
   - schedule.json: per-host schedule store; keys are SCRIPTS_DIR-relative paths like "Plugin/unraid/tools/api_cache_writer.sh"
   - vv_cron_rebuild(): reads schedule.json, builds varaverk.cron, calls update_cron
   - vv_tools_scripts(): PHP function in include/scheduler.php that discovers Tools/ scripts for the scheduler page Tools card
   - SCRIPTS_ROOT bug: conf_populate.sh referenced $SCRIPTS_ROOT which was never set anywhere — fixed by setting it from $SCRIPTS_DIR after sourcing load_config.sh
   - Source depth: scripts in Plugin/unraid/tools/ must use `source "$SCRIPT_DIR/../../../load_config.sh"` (3 levels up to repo root)

3. Files and Code Sections:

   - **Plugin/unraid/include/common.php** (line 728–732)
     - vv_transcode_sessions() was hardcoded to `/tmp/transcode_state.db`
     - master.conf sets STATE_DIR=/boot/config/plugins/varaverk/State_Files; TRANSCODE_STATE_FILE=$STATE_DIR/transcode_state.db (bash string, not expanded by PHP)
     - Fix: read STATE_DIR from conf vars and build path directly
     ```php
     function vv_transcode_sessions(): array {
         $v         = vv_conf_vars();
         $stateDir  = rtrim($v['STATE_DIR'] ?? '/boot/config/plugins/varaverk/State_Files', '/');
         $stateFile = "$stateDir/transcode_state.db";
         if (!file_exists($stateFile)) return ['available' => false];
     ```

   - **System_Essentials/conf_sync.sh**
     - Added `--pull-only` mode: pulls partner confs into RAM cache, skips own conf copy and push
     ```bash
     PUSH_ONLY=false
     PULL_ONLY=false
     for arg in "$@"; do
         case "$arg" in
             --push-only) PUSH_ONLY=true ;;
             --pull-only) PULL_ONLY=true ;;
             *)           FILTERED_ARGS+=("$arg") ;;
         esac
     done
     ```
     - Own conf copy gate: `if [[ "$PUSH_ONLY" == false ]] && [[ "$PULL_ONLY" == false ]]; then`
     - Push skip: `if [[ "$PULL_ONLY" == true ]]; then continue; fi` before push section
     - Summary: `elif [[ "$PULL_ONLY" == true ]]; then info "Conf pull complete — pulled $PULLED partner conf(s)${FAILED:+, $FAILED failed}"`

   - **Orchestrators/intermediate_sync_maintenance.sh**
     - Added Conf Pull as Step 1 (before Arr Sync), updated execution order comment to 4 steps
     ```bash
     echo "━━━ $ICON_GEAR Conf Pull ━━━"
     CONF_SYNC_SCRIPT="$SCRIPTS_ROOT/System_Essentials/conf_sync.sh"
     if [[ ! -f "$CONF_SYNC_SCRIPT" ]]; then
         warn "conf_sync.sh not found — skipping partner conf refresh"
     else
         _conf_args=("--pull-only")
         [[ "$DRY_RUN" == true ]] && _conf_args+=("--dry-run")
         if bash "$CONF_SYNC_SCRIPT" "${_conf_args[@]}"; then
             log "Partner conf cache refreshed ✅"
             JOB_PASS+=("conf_sync.sh --pull-only")
         else
             warn "Partner conf pull failed — cache may be stale"
             JOB_FAIL+=("conf_sync.sh --pull-only")
         fi
         unset _conf_args
     fi
     ```

   - **Tools/smart_long_test.sh** (now at Plugin/unraid/tools/ — wait, this was NOT moved; only the four specific scripts were moved; smart_long_test.sh stays in Tools/)
     - NEW file built for monthly maintenance
     - Sequential SMART extended self-test on all drives
     - Respects SMART_IGNORE_DRIVES (aliased by detect_hosts() from HOST*_SMART_IGNORE_DRIVES)
     - Uses platform_require_cmd to validate smartctl
     - Polls every 60s per drive until firmware reports completion
     - SIGTERM trap exits poll loop cleanly (tests continue in drive firmware)
     - Modes: default (all drives), specific /dev/sdX, --dry-run, --status, --log
     - Exit 1 if any drive fails

   - **Tools/zfs_pool_scrub.sh**
     - Was already fully implemented — just commented out in master.conf
     - Starts scrub on all pools not in ZFS_REPORT_IGNORE_POOLS
     - Polls every 60s until complete
     - Reports errors per pool, notifies on failures
     - Supports --status, --dry-run, specific pool by name

   - **Configurations/master.conf** (gitignored — local change only)
     - Uncommented both monthly maintenance scripts:
     ```bash
     MONTHLY_MAINTENANCE_SCRIPTS=(
         "Tools/zfs_pool_scrub.sh"           # ZFS pool integrity scrub — all pools, skip ZFS_REPORT_IGNORE_POOLS
         "Tools/smart_long_test.sh"          # SMART extended self-test — all drives, sequential
     )
     ```

   - **Plugin/unraid/tools/** (NEW directory — all moved from Tools/)
     - api_cache_writer.sh — thin wrapper calling api_cache_writer.php
     - api_cache_writer.php — PHP cache builder for monitor/arrs pages
     - conf_populate.sh — auto-detects and populates empty conf fields from running services
     - remote_arr_cache_writer.sh — SSHes to remote hosts, caches arr data locally
     - storage_migrate.sh — migrates Varaverk between internal NVMe and USB flash modes

   - **Plugin/unraid/tools/conf_populate.sh**
     - Source depth fixed: `source "$SCRIPT_DIR/../../../load_config.sh"`
     - SCRIPTS_ROOT bug fixed: added `SCRIPTS_ROOT="$SCRIPTS_DIR"` after source line
     ```bash
     source "$SCRIPT_DIR/../../../load_config.sh"
     SCRIPTS_ROOT="$SCRIPTS_DIR"
     ```

   - **Plugin/unraid/tools/remote_arr_cache_writer.sh**
     - Source depth fixed: `source "$SCRIPT_DIR/../../../load_config.sh"`

   - **Plugin/unraid/tools/storage_migrate.sh**
     - Source depth fixed: `source "$SCRIPT_DIR/../../../load_config.sh"`

   - **Plugin/unraid/api/arrs.php**
     - Path reference updated:
     ```php
     $script = dirname(__DIR__) . '/tools/remote_arr_cache_writer.sh';
     ```

   - **Plugin/unraid/api/storage.php**
     - Path reference updated:
     ```php
     $script = dirname(__DIR__) . '/tools/storage_migrate.sh';
     ```

   - **schedule.json**
     - Keys updated from Tools/ to Plugin/unraid/tools/:
     ```json
     "Plugin\/unraid\/tools\/api_cache_writer.sh": {
         "id": "Plugin\/unraid\/tools\/api_cache_writer.sh",
         "enabled": true,
         "cron": "* * * * *",
         ...
     },
     "Plugin\/unraid\/tools\/remote_arr_cache_writer.sh": {
         "id": "Plugin\/unraid\/tools\/remote_arr_cache_writer.sh",
         "enabled": true,
         "cron": "0 *\/2 * * *",
         ...
     }
     ```

   - **varaverk.cron** (auto-rebuilt by PHP)
     - Now points to new paths:
     ```
     * * * * * bash ".../run_job.sh" "Plugin/unraid/tools/api_cache_writer.sh" ".../Plugin/unraid/tools/api_cache_writer.sh"
     0 */2 * * * bash ".../run_job.sh" "Plugin/unraid/tools/remote_arr_cache_writer.sh" ".../Plugin/unraid/tools/remote_arr_cache_writer.sh"
     ```

   - **Plugin/unraid/pages/scheduler.php**
     - READ (lines 1–923) to understand Tools card structure
     - Line 16: `$tools = vv_tools_scripts();`
     - Lines 213–259: Tools card HTML with count from `count($tools)`, toggle/cron/run/log per tool
     - NOT yet modified — this is the pending task

4. Errors and Fixes:
   - **Transcode state path — first attempt failed**: Tried using `$v['TRANSCODE_STATE_FILE']` which returns literal string `"$STATE_DIR/transcode_state.db"` — bash variable expansion doesn't happen when PHP reads conf files as text. Fix: use `$v['STATE_DIR']` directly and construct path in PHP.
   - **conf_populate.sh SCRIPTS_ROOT bug**: $SCRIPTS_ROOT was referenced in conf_populate.sh (lines 61 and 227) but never set. Every other script that uses SCRIPTS_ROOT sets it as `SCRIPTS_ROOT="$SCRIPT_DIR/.."`. conf_populate never did. Fix: added `SCRIPTS_ROOT="$SCRIPTS_DIR"` after sourcing load_config.sh (SCRIPTS_DIR is set to repo root by load_config.sh via LOAD_CONFIG_DIR export).
   - **Source depth after move**: All three bash scripts in Plugin/unraid/tools/ that source load_config.sh had `source "$SCRIPT_DIR/../load_config.sh"` which would now point to Plugin/unraid/ (not repo root). Fixed to `source "$SCRIPT_DIR/../../../load_config.sh"` (Plugin/unraid/tools/ → Plugin/unraid/ → Plugin/ → repo root).

5. Problem Solving:
   - **Transcode monitor**: Solved by reading STATE_DIR from vv_conf_vars() and building path in PHP instead of relying on bash variable expansion.
   - **Partner conf staleness**: Solved by adding --pull-only to conf_sync.sh and running it as Step 1 of intermediate_sync_maintenance.sh (every 4h). Three modes now: full (array start), --push-only (conf save), --pull-only (4h refresh).
   - **Monthly maintenance empty**: zfs_pool_scrub.sh was already implemented; built smart_long_test.sh and uncommented both in master.conf.
   - **Unraid adapter tool placement**: Moved four scripts to Plugin/unraid/tools/ with all path references, schedule.json keys, and cron entries updated. Triggered immediate cron rebuild via PHP.

6. All User Messages:
   - "Transcode — No transcode state — ramdisk_setup.sh not yet run." (showing monitor UI state)
   - "ok now it shows up" (confirming transcode fix worked)
   - "ok so do we still have all .conf in /tmp.. all hosts.confsd are stored obsecure in ram disk even us,"
   - "its my machine acceptable" (credentials on flash is fine)
   - "ok so conf_sync.sh, what all orch is this in?"
   - "should we also trigger pulls only in the in intermediate_sync_maintenance"
   - "yes go ahead" (build the --pull-only feature)
   - "ok so thingare seem hit and mis.. lets scan varaverk and see if there are half setup or missing projects"
   - "build the monthly maintenance scripts"
   - "Api Cache Writer, Conf Populate, Remote Arr Cache Writer, Storage Migrate. these belong in the Varaverk/plugin/unraid, right? they are specific to the unrsid adspter"
   - "Nuanced — conf_populate.sh, isnt this specific to unraid. i imagine i will need a different script for other distros..... this should be called in the setup wizard... but that coming up soon"
   - "ok so scheduler page we should add the plugin/***/tools to the current tools card, merge them. then all basic tools and os specific tools will show in tools card... we should exclude the writers"

7. Pending Tasks:
   - **Scheduler page Tools card merge**: Update vv_tools_scripts() (in Plugin/unraid/include/scheduler.php) to also scan Plugin/unraid/tools/ (and generically Plugin/*/tools/ for future platforms), merge results with existing Tools/ scripts, exclude api_cache_writer.sh and remote_arr_cache_writer.sh (background writers not user-facing). Update the Tools card count and header accordingly.
   - **Setup wizard**: conf_populate.sh should be called from the setup wizard (user mentioned "coming up soon" — not started yet)
   - **Auth Stack page**: Credentials still need filling in host1.conf (HOST1_NPM_USER, HOST1_NPM_PASS, HOST1_LLDAP_PASS) and host2.conf when HOST2 is back online

8. Current Work:
   The most recent work was reading scheduler.php to understand how to implement the Tools card merge. The user's request: "ok so scheduler page we should add the plugin/***/tools to the current tools card, merge them. then all basic tools and os specific tools will show in tools card... we should exclude the writers"

   We read Plugin/unraid/pages/scheduler.php lines 1–923 and identified:
   - Line 16: `$tools = vv_tools_scripts();` — this function needs to be modified
   - Lines 213–259: The Tools card HTML iterates `$tools` array
   - The card header shows `count($tools)` and the label "Tools"
   - Each tool entry has: toggle (vv-enabled), label, cron field, Run/Dry Run/Log/Verbose/args buttons

   The function vv_tools_scripts() is defined in Plugin/unraid/include/scheduler.php. We had not yet read that function — that is the immediate next step.

   Writers to EXCLUDE from the merged card:
   - `api_cache_writer.sh` (background cron, managed automatically)
   - `remote_arr_cache_writer.sh` (background cron, managed automatically)

9. Optional Next Step:
   Read vv_tools_scripts() in Plugin/unraid/include/scheduler.php to understand how it currently discovers Tools/ scripts, then modify it to also scan `Plugin/*/tools/` directories, merge with Tools/ results, and exclude the two writer scripts. Then update the Tools card header in pages/scheduler.php if needed (e.g., distinguish "General" vs "OS Adapter" tools with a separator, or just merge flat).

   The user said: "we should add the plugin/***/tools to the current tools card, merge them. then all basic tools and os specific tools will show in tools card... we should exclude the writers"

If you need specific details from before compaction (like exact code snippets, error messages, or content you generated), read the full transcript at: /root/.claude/projects/-root/d6d51683-5f05-41b4-ad5d-afbe06985ad9.jsonl
Continue the conversation from where it left off without asking the user any further questions. Resume directly — do not acknowledge the summary, do not recap what was happening, do not preface with "I'll continue" or similar. Pick up the last task as if the break never happened.


<?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 = dirname(__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;
}

// ── Unraid API key status ─────────────────────────────────────────────────────
// Keys live in host*.conf (private, not master.conf):
//   host1.conf: HOST1_UNRAID_API_KEY (own) + HOST2_UNRAID_API_KEY (HOST1's access to HOST2)
//   host2.conf: HOST2_UNRAID_API_KEY (own) + HOST1_UNRAID_API_KEY (HOST2's access to HOST1)
if ($action === 'api_status') {
    require_once dirname(__DIR__) . '/include/unraid_api.php';
    $localStatus = vv_api_get_status();
    $vars        = vv_conf_vars();
    $myHost      = vv_detect_host();
    $myId        = strtoupper($myHost);

    $hosts = [];
    foreach ($vars as $k => $v) {
        if (!preg_match('/^HOST(\d+)$/', $k, $m) || !$v) continue;
        $id      = 'HOST' . $m[1];
        $keyVar  = $id . '_UNRAID_API_KEY';
        $key     = $vars[$keyVar] ?? '';
        $isLocal = ($id === $myId);
        // For local: key is Varaverk_HOST1 registered on own machine
        // For remote: key is Varaverk_HOST1 registered on HOST2's machine (stored in host1.conf)
        $hosts[] = [
            'host_id'     => $id,
            'hostname'    => $v,
            'is_local'    => $isLocal,
            'key_var'     => $keyVar,
            'key_name'    => 'Varaverk_' . ($isLocal ? $myId : $myId), // Varaverk_HOST1 on that registry
            'key_present' => !empty($key),
            'key_preview' => $key ? substr($key, 0, 8) . '...' . substr($key, -4) : null,
            'api_ok'      => $isLocal
                             ? (!$localStatus['key_missing'] && $localStatus['available'])
                             : !empty($key),
        ];
    }
    usort($hosts, fn($a, $b) => strcmp($a['host_id'], $b['host_id']));

    echo json_encode([
        'ok'        => true,
        'my_id'     => $myId,
        'hosts'     => $hosts,
        'fallbacks' => $localStatus['fallbacks'],
    ]);
    exit;
}

// ── Setup/renew API keys (local + all partners via SSH) ───────────────────────
if ($action === 'setup_apikeys' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    $script = SCRIPTS_DIR . '/System_Essentials/unraid_api_key_renew.sh';
    if (!file_exists($script)) {
        echo json_encode(['ok' => false, 'error' => 'unraid_api_key_renew.sh not found']); exit;
    }
    $allHosts = ($_POST['all_hosts'] ?? '0') === '1';
    $flags    = $allHosts ? ' --all-hosts' : '';
    set_time_limit(60);
    $output = []; $exit = 0;
    exec('bash ' . escapeshellarg($script) . $flags . ' 2>&1', $output, $exit);
    echo json_encode(['ok' => $exit === 0, 'exit' => $exit, 'output' => implode("\n", $output)]);
    exit;
}

echo json_encode(['ok' => false, 'error' => 'Unknown action']);


<?php
header('Content-Type: application/json');
require_once dirname(__DIR__) . '/include/config.php';

// ── Manual remote refresh — runs remote_arr_cache_writer for one host ─────────
$_action = trim($_GET['action'] ?? '');
if ($_action === 'refresh_remote') {
    $host = strtolower(trim($_GET['host'] ?? ''));
    if (!preg_match('/^host\d+$/', $host)) {
        echo json_encode(['ok' => false, 'error' => 'Invalid host']); exit;
    }
    $script = dirname(__DIR__) . '/tools/remote_arr_cache_writer.sh';
    if (!file_exists($script)) {
        echo json_encode(['ok' => false, 'error' => 'remote_arr_cache_writer.sh not found']); exit;
    }
    set_time_limit(30);
    $out = []; $exit = 0;
    exec('bash ' . escapeshellarg($script) . ' --host=' . escapeshellarg(strtoupper($host)) . ' 2>&1', $out, $exit);

    $cacheFile = VV_CACHE_DIR . '/arrs_remote_' . $host . '.json';
    $node = null;
    if (file_exists($cacheFile)) {
        $node = json_decode(file_get_contents($cacheFile), true) ?: null;
        if ($node) {
            $node['cached']    = true;
            $node['cache_age'] = time() - (int)filemtime($cacheFile);
        }
    }
    // Bust the main arrs cache so next poll gets fresh data
    @unlink(VV_CACHE_DIR . '/arrs.json');
    echo json_encode(['ok' => $exit === 0, 'node' => $node, 'output' => implode("\n", $out)]);
    exit;
}
unset($_action);

// ── Normal data load ──────────────────────────────────────────────────────────
$_vv_cached = vv_cache_read('arrs', 300);
if ($_vv_cached !== null) { echo json_encode($_vv_cached); exit; }
unset($_vv_cached);

require_once dirname(__DIR__) . '/include/arrs.php';
echo json_encode(vv_arrs_all());


#!/bin/bash
# ==============================================================================================
# ============================= Conf Auto-Populate =============================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Reads credentials and settings from locally running services and writes
# them into the local host conf. Safe to run multiple times — only populates
# EMPTY fields, never overwrites existing values unless --overwrite is passed.
#
# After populating, pushes the updated conf to all partners via conf_sync.sh
# so they have the fresh keys in their /tmp/.vv/ cache immediately.
#
# ==============================================================================================
# AUTO-DETECTED FIELDS
# ==============================================================================================
#
#   HOSTN_RADARR_API_KEY      from Radarr config.xml (found via docker volume mount)
#   HOSTN_SONARR_API_KEY      from Sonarr config.xml
#   HOSTN_LIDARR_API_KEY      from Lidarr config.xml
#   HOSTN_SLSKD_API_KEY       from slskd config.yml
#   HOSTN_SABNZBD_API_KEY     from sabnzbd.ini
#   HOSTN_EMBY_CONTAINER      fuzzy match from docker ps
#   HOSTN_JELLYFIN_CONTAINER  fuzzy match from docker ps
#   HOSTN_RADARR_MOVIE_ROOT   from Radarr rootFolder API
#   HOSTN_SONARR_TV_ROOT      from Sonarr rootFolder API
#   HOSTN_LIDARR_MUSIC_ROOT   from Lidarr rootFolder API
#   HOSTN_SYS_WATCHDOG_NIC    from ip route default gateway interface
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
#   conf_populate.sh              Populate empty fields only
#   conf_populate.sh --overwrite  Overwrite all detected fields (re-sync after arr key rotation)
#   conf_populate.sh --dry-run    Show what would be written, no changes
#   conf_populate.sh --log        Verbose output
#   conf_populate.sh --no-push    Skip pushing to partners after update
#
# ==============================================================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../../../load_config.sh"
SCRIPTS_ROOT="$SCRIPTS_DIR"

OVERWRITE=false
NO_PUSH=false
FILTERED_ARGS=()
for arg in "$@"; do
    case "$arg" in
        --overwrite) OVERWRITE=true ;;
        --no-push)   NO_PUSH=true ;;
        *)           FILTERED_ARGS+=("$arg") ;;
    esac
done
parse_args "${FILTERED_ARGS[@]}"
detect_hosts

[[ "$EUID" -ne 0 ]] && { error "Must be run as root"; exit 1; }

CONF_FILE="$SCRIPTS_ROOT/Configurations/${MY_ID,,}.conf"
[[ ! -f "$CONF_FILE" ]] && { error "Conf file not found: $CONF_FILE"; exit 1; }

log "$ICON_GEAR Config: conf=${CONF_FILE} overwrite=${OVERWRITE:-false} no-push=${NO_PUSH:-false}"

UPDATED=0
SKIPPED=0

echo ""
echo "━━━ $ICON_GEAR Conf Auto-Populate — $MY_ID ($LOCAL_SERVER_NAME) ━━━"
echo ""
[[ "$DRY_RUN"   == true ]] && warn "DRY RUN — no changes will be made"
[[ "$OVERWRITE" == true ]] && warn "OVERWRITE mode — existing values will be replaced"

# ── Helper: write a var into conf if empty (or --overwrite) ──────────────────
_set_conf_var() {
    local var_name="$1" value="$2" label="$3"
    [[ -z "$value" ]] && return

    # Check current value in conf
    local current
    current=$(grep -oP "(?<=^\s*${var_name}=\")[^\"]*" "$CONF_FILE" 2>/dev/null | head -1)

    if [[ -n "$current" ]] && [[ "$OVERWRITE" == false ]]; then
        log "$label: already set (${current:0:8}…) — skipping"
        (( SKIPPED++ ))
        return
    fi

    if [[ "$DRY_RUN" == true ]]; then
        warn "DRY RUN — would set $var_name = ${value:0:8}…"
        return
    fi

    # Update or append the var line
    if grep -q "^\s*${var_name}=" "$CONF_FILE"; then
        sed -i "s|^\(\s*${var_name}\s*=\s*\)\"[^\"]*\"|\1\"${value}\"|" "$CONF_FILE"
    else
        printf '\n    %s="%s"\n' "$var_name" "$value" >> "$CONF_FILE"
    fi
    info "$label: set ✅"
    (( UPDATED++ ))
}

# ── Helper: find arr config dir via docker volume mount ───────────────────────
# Looks for a container matching the pattern, then reads its /config volume path.
# Falls back to DOCKER_APPDATA_BASE/<ContainerName> if volume not found.
_arr_config_dir() {
    local pattern="$1"
    local container_name
    container_name=$(docker ps -a --format '{{.Names}}' 2>/dev/null | \
        grep -im1 "^${pattern}")
    [[ -z "$container_name" ]] && return 1

    local config_path
    config_path=$(docker inspect "$container_name" 2>/dev/null | \
        jq -r '.[0].Mounts[]? | select(.Destination == "/config") | .Source' 2>/dev/null | head -1)
    [[ -z "$config_path" ]] && config_path="${DOCKER_APPDATA_BASE:-/mnt/user/appdata}/${container_name}"

    [[ -d "$config_path" ]] && echo "$config_path" || return 1
}

# ── Helper: read XML tag value ────────────────────────────────────────────────
_xml_val() {
    local file="$1" tag="$2"
    grep -oP "(?<=<${tag}>)[^<]+" "$file" 2>/dev/null | head -1
}

# ==============================================================================================
# ── Arr API keys + root paths ─────────────────────────────────────────────────────────────────
# ==============================================================================================

for arr in radarr sonarr lidarr; do
    arr_upper="${arr^^}"
    config_dir=$(_arr_config_dir "$arr") || {
        log "${arr_upper}: no running container found — skipping"
        continue
    }
    config_xml="${config_dir}/config.xml"

    if [[ ! -f "$config_xml" ]]; then
        log "${arr_upper}: config.xml not found at $config_xml — skipping"
        continue
    fi

    key=$(_xml_val "$config_xml" "ApiKey")
    port=$(_xml_val "$config_xml" "Port")
    url_base="http://localhost:${port:-$(case $arr in radarr) echo 7878;; sonarr) echo 8989;; lidarr) echo 8686;; esac)}"

    _set_conf_var "${MY_ID}_${arr_upper}_API_KEY" "$key" "${arr_upper} API key"

    # Root paths from arr's own rootFolder API
    if [[ -n "$key" ]]; then
        local api_ver; case "$arr" in lidarr) api_ver="v1" ;; *) api_ver="v3" ;; esac
        root_json=$(curl -sf --max-time 5 \
            -H "X-Api-Key: $key" "${url_base}/api/${api_ver}/rootfolder" 2>/dev/null)
        root_path=$(echo "$root_json" | jq -r '.[0].path // empty' 2>/dev/null)

        case "$arr" in
            radarr) _set_conf_var "${MY_ID}_RADARR_MOVIE_ROOT" "$root_path" "Radarr movie root" ;;
            sonarr) _set_conf_var "${MY_ID}_SONARR_TV_ROOT"    "$root_path" "Sonarr TV root" ;;
            lidarr) _set_conf_var "${MY_ID}_LIDARR_MUSIC_ROOT" "$root_path" "Lidarr music root" ;;
        esac
    fi
done

# ==============================================================================================
# ── SABnzbd API key ───────────────────────────────────────────────────────────────────────────
# ==============================================================================================

sab_dir=$(_arr_config_dir "sabnzbd") && {
    sab_ini=$(find "$sab_dir" -maxdepth 2 -name "sabnzbd.ini" 2>/dev/null | head -1)
    if [[ -f "$sab_ini" ]]; then
        sab_key=$(grep -oP '(?<=^api_key\s*=\s*)\S+' "$sab_ini" 2>/dev/null | head -1)
        _set_conf_var "${MY_ID}_SABNZBD_API_KEY" "$sab_key" "SABnzbd API key"
    fi
}

# ==============================================================================================
# ── slskd API key ─────────────────────────────────────────────────────────────────────────────
# ==============================================================================================

slskd_dir=$(_arr_config_dir "slskd") && {
    slskd_yml=$(find "$slskd_dir" -maxdepth 2 -name "*.yml" -o -name "*.yaml" 2>/dev/null | head -1)
    if [[ -f "$slskd_yml" ]]; then
        slskd_key=$(grep -oP '(?<=api_key:\s)[\w-]+' "$slskd_yml" 2>/dev/null | head -1)
        [[ -z "$slskd_key" ]] && \
            slskd_key=$(grep -oP '(?<=apikey:\s)[\w-]+' "$slskd_yml" 2>/dev/null | head -1)
        _set_conf_var "${MY_ID}_SLSKD_API_KEY" "$slskd_key" "slskd API key"
    fi
}

# ==============================================================================================
# ── Container names ───────────────────────────────────────────────────────────────────────────
# ==============================================================================================

for pattern in "emby" "jellyfin"; do
    container=$(docker ps -a --format '{{.Names}}' 2>/dev/null | grep -im1 "^${pattern}")
    [[ -z "$container" ]] && continue
    case "$pattern" in
        emby)     _set_conf_var "${MY_ID}_EMBY_CONTAINER"     "$container" "Emby container name" ;;
        jellyfin) _set_conf_var "${MY_ID}_JELLYFIN_CONTAINER" "$container" "Jellyfin container name" ;;
    esac
done

# ==============================================================================================
# ── Network interface ─────────────────────────────────────────────────────────────────────────
# ==============================================================================================

nic=$(ip route show default 2>/dev/null | grep -oP '(?<=dev )\S+' | head -1)
_set_conf_var "${MY_ID}_SYS_WATCHDOG_NIC" "$nic" "Default NIC"

# ==============================================================================================
# ── Summary + push ────────────────────────────────────────────────────────────────────────────
# ==============================================================================================

echo ""
echo "━━━━━ $ICON_SUMMARY Conf Populate Summary ━━━━━"
echo "  Updated: $UPDATED field(s)"
echo "  Skipped: $SKIPPED already set"
echo "  Conf:    $CONF_FILE"
echo "━━━━━━━━━━━━━━━━━━━━━"

if [[ "$UPDATED" -gt 0 ]] && [[ "$DRY_RUN" == false ]] && [[ "$NO_PUSH" == false ]]; then
    echo ""
    info "Pushing updated conf to partners..."
    bash "$SCRIPTS_ROOT/System_Essentials/conf_sync.sh" --push-only "${EXTRA_FLAGS[@]}" || true
fi


#!/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")

log "$ICON_GEAR Config: to=${TO_MODE} src=${SRC} dst=${DST} dry-run=${DRY_RUN}"

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, DATA_DIR, STATE_DIR in master.conf (new location)
echo ""
echo "━━━ $ICON_GEAR Step 4: Update master.conf paths ━━━"
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"
        sed -i "s|^\(\s*DATA_DIR\s*=\s*\)\"[^\"]*\"|\1\"$DST/data\"|"                           "$NEW_MASTER"
        sed -i "s|^\(\s*STATE_DIR\s*=\s*\)\"[^\"]*\"|\1\"$DST/State_Files\"|"                   "$NEW_MASTER"
        echo "  TARGET_DIR → $DST ✅"
        echo "  DATA_DIR   → $DST/data ✅"
        echo "  STATE_DIR  → $DST/State_Files ✅"
    else
        error "master.conf not found at $NEW_MASTER"
        exit 1
    fi
else
    warn "DRY RUN — would update TARGET_DIR, DATA_DIR, STATE_DIR 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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"


ok so scheduler page we should add the plugin/***/tools to the current tools card, merge them. then all basic tools and os specific tools will show in tools card... we should exclude the writers


233	function vv_tools_scripts(): array {
234	    $dir      = SCRIPTS_DIR . '/Tools';
235	    $schedule = vv_schedule_load();
236	    $scripts  = [];
237	    foreach (glob("$dir/*.sh") ?: [] as $path) {
238	        $rel       = 'Tools/' . basename($path);
239	        $entry     = $schedule[$rel] ?? [];
240	        $scripts[] = [
241	            'id'          => $rel,
242	            'label'       => vv_pretty_label(basename($path, '.sh')),
243	            'desc'        => vv_script_description($path),
244	            'enabled'     => (bool)($entry['enabled'] ?? false),
245	            'cron'        => $entry['cron'] ?? '',
246	            'log_enabled' => (bool)($entry['log_enabled'] ?? false),
247	        ];
248	    }
249	    usort($scripts, fn($a, $b) => strcmp($a['label'], $b['label']));
250	    return $scripts;
251	}
252
253	function vv_custom_scripts(): array {
254	    $dir      = SCRIPTS_DIR . '/Custom';
255	    $schedule = vv_schedule_load();
256	    $scripts  = [];
257	    foreach (glob("$dir/*.sh") ?: [] as $path) {
258	        $rel       = 'Custom/' . basename($path);
259	        $entry     = $schedule[$rel] ?? [];
260	        $scripts[] = [
261	            'id'          => $rel,
262	            'label'       => vv_pretty_label(basename($path, '.sh')),
263	            'desc'        => vv_script_description($path),
264	            'enabled'     => (bool)($entry['enabled'] ?? false),
265	            'cron'        => $entry['cron'] ?? '',
266	            'log_enabled' => (bool)($entry['log_enabled'] ?? false),
267	        ];
268	    }
269	    return $scripts;
270	}
271
272	// Load rsync standalone config (location + cron) for a flag name from schedule.json.
273	function vv_rsync_standalone(string $flagName): array {
274	    $s = vv_schedule_load();
275	    $r = $s['__rsync_' . $flagName] ?? [];
276	    return [
277	        'location' => (string)($r['location'] ?? ''),
278	        'cron'     => (string)($r['cron']     ?? ''),
279	    ];
280	}
281
282	// Extract *_SCRIPTS array variable names that an orchestrator iterates over.
283	function vv_orch_conf_arrays(string $orchPath): array {
284	    $content = file_get_contents($orchPath) ?: '';
285	    preg_match_all('/\$\{([A-Z_]+_SCRIPTS)\[@\]\}/', $content, $refs);
286	    return array_unique($refs[1] ?? []);
287	}
288
289	// Return .sh scripts that exist in SCRIPTS_DIR but are not referenced in any
290	// master.conf *_SCRIPTS array and are not orchestrators or custom scripts.
291	function vv_script_library(): array {
292	    $scriptsDir = SCRIPTS_DIR;

function vv_tools_scripts(): array {
    $dir      = SCRIPTS_DIR . '/Tools';
    $schedule = vv_schedule_load();
    $scripts  = [];
    foreach (glob("$dir/*.sh") ?: [] as $path) {
        $rel       = 'Tools/' . basename($path);
        $entry     = $schedule[$rel] ?? [];
        $scripts[] = [
            'id'          => $rel,
            'label'       => vv_pretty_label(basename($path, '.sh')),
            'desc'        => vv_script_description($path),
            'enabled'     => (bool)($entry['enabled'] ?? false),
            'cron'        => $entry['cron'] ?? '',
            'log_enabled' => (bool)($entry['log_enabled'] ?? false),
        ];
    }
    usort($scripts, fn($a, $b) => strcmp($a['label'], $b['label']));
    return $scripts;
}

function vv_custom_scripts(): array {
    $dir      = SCRIPTS_DIR . '/Custom';
    $schedule = vv_schedule_load();
    $scripts  = [];
    foreach (glob("$dir/*.sh") ?: [] as $path) {
        $rel       = 'Custom/' . basename($path);
        $entry     = $schedule[$rel] ?? [];
        $scripts[] = [
            'id'          => $rel,
            'label'       => vv_pretty_label(basename($path, '.sh')),
            'desc'        => vv_script_description($path),
            'enabled'     => (bool)($entry['enabled'] ?? false),
            'cron'        => $entry['cron'] ?? '',
            'log_enabled' => (bool)($entry['log_enabled'] ?? false),
        ];
    }
    return $scripts;
}

// Load rsync standalone config (location + cron) for a flag name from schedule.json.
function vv_rsync_standalone(string $flagName): array {
    $s = vv_schedule_load();
    $r = $s['__rsync_' . $flagName] ?? [];
    return [
        'location' => (string)($r['location'] ?? ''),
        'cron'     => (string)($r['cron']     ?? ''),
    ];
}

// Extract *_SCRIPTS array variable names that an orchestrator iterates over.
function vv_orch_conf_arrays(string $orchPath): array {
    $content = file_get_contents($orchPath) ?: '';
    preg_match_all('/\$\{([A-Z_]+_SCRIPTS)\[@\]\}/', $content, $refs);
    return array_unique($refs[1] ?? []);
}

// Return .sh scripts that exist in SCRIPTS_DIR but are not referenced in any
// master.conf *_SCRIPTS array and are not orchestrators or custom scripts.
function vv_script_library(): array {
    $scriptsDir = SCRIPTS_DIR;


Now let me check the Tools card in the page to see the current render and if there's a "group" or "type" field used:


213

214

215	          Tools
216	          <?= count($tools) ?> tool<?= count($tools) !== 1 ? 's' : '' ?>
217	          <button class="vv-advanced-toggle" style="margin-left:auto;width:100px" onclick="vvToggleTools(this)">Tools ▸</button>
218

219

220	          <?php if (empty($tools)): ?>
221
No scripts found in Tools/.

222	          <?php else: ?>
223	            <?php foreach ($tools as $ts): $tsid = htmlspecialchars($ts['id']); ?>
224	              <div class="vv-script" data-id="<?= $tsid ?>">
225

226
227	                    <input type="checkbox" class="vv-enabled"
228	                      <?= $ts['enabled'] ? 'checked' : '' ?>
229	                      onchange="vvSaveJob(this)">
230
231
232	                  <?= htmlspecialchars($ts['label']) ?>
233	                  <input type="text" class="vv-cron" value="<?= htmlspecialchars($ts['cron']) ?>"
234	                    placeholder="cron expression"
235	                    onblur="vvSaveCronBlur(this)">
236
237

238	                <?php if (!empty($ts['desc'])): ?>
239	                  <div class="vv-job-desc" title="<?= htmlspecialchars($ts['desc']) ?>"><?= htmlspecialchars($ts['desc']) ?>

240	                <?php endif; ?>
241

242	                  <button class="vv-btn-sm vv-run-btn" onclick="vvRunJob(this)">▶ Run</button>
243	                  <button class="vv-btn-sm vv-dry-btn" onclick="vvDryRun(this)">▶ Dry Run</button>
244	                  <?php $tsLog = vv_job_log_path($ts['id']); ?>
245	                  <button class="vv-btn-sm vv-log-btn<?= (file_exists($tsLog) && filesize($tsLog) > 0) ? ' vv-has-log' : '' ?>" onclick="vvSelectLog(this)">Log</button>
246
247	                    <input type="checkbox" class="vv-log-enabled"
248	                      <?= $ts['log_enabled'] ? 'checked' : '' ?>
249	                      onchange="vvSaveJob(this)">
250	                    Verbose
251
252	                  <input type="text" class="vv-script-args" placeholder="extra args">
253
254

255

256	            <?php endforeach; ?>
257	          <?php endif; ?>
258

259

260
261
262	      <?php

          Tools
          <?= count($tools) ?> tool<?= count($tools) !== 1 ? 's' : '' ?>
          <button class="vv-advanced-toggle" style="margin-left:auto;width:100px" onclick="vvToggleTools(this)">Tools ▸</button>

          <?php if (empty($tools)): ?>

No scripts found in Tools/.

          <?php else: ?>
            <?php foreach ($tools as $ts): $tsid = htmlspecialchars($ts['id']); ?>
              <div class="vv-script" data-id="<?= $tsid ?>">

                    <input type="checkbox" class="vv-enabled"
                      <?= $ts['enabled'] ? 'checked' : '' ?>
                      onchange="vvSaveJob(this)">

                  <?= htmlspecialchars($ts['label']) ?>
                  <input type="text" class="vv-cron" value="<?= htmlspecialchars($ts['cron']) ?>"
                    placeholder="cron expression"
                    onblur="vvSaveCronBlur(this)">

                <?php if (!empty($ts['desc'])): ?>
                  <div class="vv-job-desc" title="<?= htmlspecialchars($ts['desc']) ?>"><?= htmlspecialchars($ts['desc']) ?>

                <?php endif; ?>

                  <button class="vv-btn-sm vv-run-btn" onclick="vvRunJob(this)">▶ Run</button>
                  <button class="vv-btn-sm vv-dry-btn" onclick="vvDryRun(this)">▶ Dry Run</button>
                  <?php $tsLog = vv_job_log_path($ts['id']); ?>
                  <button class="vv-btn-sm vv-log-btn<?= (file_exists($tsLog) && filesize($tsLog) > 0) ? ' vv-has-log' : '' ?>" onclick="vvSelectLog(this)">Log</button>

                    <input type="checkbox" class="vv-log-enabled"
                      <?= $ts['log_enabled'] ? 'checked' : '' ?>
                      onchange="vvSaveJob(this)">
                    Verbose

                  <input type="text" class="vv-script-args" placeholder="extra args">

            <?php endforeach; ?>
          <?php endif; ?>

      <?php


Bash

List tools directory contents


api_cache_writer.php
api_cache_writer.sh*
conf_populate.sh*
remote_arr_cache_writer.sh*
storage_migrate.sh*


Good. Now I'll update `vv_tools_scripts()` to merge both directories and exclude the two background writers:


Edit

function vv_tools_scripts(): array {
    $dir      = SCRIPTS_DIR . '/Tools';
    $schedule = vv_schedule_load();
    $scripts  = [];
    foreach (glob("$dir/*.sh") ?: [] as $path) {
        $rel       = 'Tools/' . basename($path);
        $entry     = $schedule[$rel] ?? [];
        $scripts[] = [
            'id'          => $rel,
            'label'       => vv_pretty_label(basename($path, '.sh')),
            'desc'        => vv_script_description($path),
            'enabled'     => (bool)($entry['enabled'] ?? false),
            'cron'        => $entry['cron'] ?? '',
            'log_enabled' => (bool)($entry['log_enabled'] ?? false),
        ];
    }
    usort($scripts, fn($a, $b) => strcmp($a['label'], $b['label']));
    return $scripts;
}

function vv_tools_scripts(): array {
    // Background writers managed automatically — not user-facing tools
    static $EXCLUDE = ['api_cache_writer.sh', 'remote_arr_cache_writer.sh'];

    $schedule = vv_schedule_load();
    $scripts  = [];

    $collect = function(string $dir, string $relPrefix) use ($schedule, $EXCLUDE, &$scripts): void {
        foreach (glob("$dir/*.sh") ?: [] as $path) {
            $base = basename($path);
            if (in_array($base, $EXCLUDE, true)) continue;
            $rel       = $relPrefix . $base;
            $entry     = $schedule[$rel] ?? [];
            $scripts[] = [
                'id'          => $rel,
                'label'       => vv_pretty_label(basename($path, '.sh')),
                'desc'        => vv_script_description($path),
                'enabled'     => (bool)($entry['enabled'] ?? false),
                'cron'        => $entry['cron'] ?? '',
                'log_enabled' => (bool)($entry['log_enabled'] ?? false),
            ];
        }
    };

    // General tools
    $collect(SCRIPTS_DIR . '/Tools', 'Tools/');

    // Platform adapter tools (Plugin/<platform>/tools/)
    foreach (glob(SCRIPTS_DIR . '/Plugin/*/tools') ?: [] as $toolsDir) {
        $platform = basename(dirname($toolsDir));
        $collect($toolsDir, "Plugin/$platform/tools/");
    }

    usort($scripts, fn($a, $b) => strcmp($a['label'], $b['label']));
    return $scripts;
}


The file /boot/config/plugins/varaverk/Plugin/unraid/include/scheduler.php has been updated successfully. (file state is current in your context — no need to Read it back)

function vv_tools_scripts(): array {
    $dir      = SCRIPTS_DIR . '/Tools';
    $schedule = vv_schedule_load();
    $scripts  = [];
    foreach (glob("$dir/*.sh") ?: [] as $path) {
        $rel       = 'Tools/' . basename($path);
        $entry     = $schedule[$rel] ?? [];
        $scripts[] = [
            'id'          => $rel,
            'label'       => vv_pretty_label(basename($path, '.sh')),
            'desc'        => vv_script_description($path),
            'enabled'     => (bool)($entry['enabled'] ?? false),
            'cron'        => $entry['cron'] ?? '',
            'log_enabled' => (bool)($entry['log_enabled'] ?? false),
        ];
    }
    usort($scripts, fn($a, $b) => strcmp($a['label'], $b['label']));
    return $scripts;
}

function vv_tools_scripts(): array {
    // Background writers managed automatically — not user-facing tools
    static $EXCLUDE = ['api_cache_writer.sh', 'remote_arr_cache_writer.sh'];

    $schedule = vv_schedule_load();
    $scripts  = [];

    $collect = function(string $dir, string $relPrefix) use ($schedule, $EXCLUDE, &$scripts): void {
        foreach (glob("$dir/*.sh") ?: [] as $path) {
            $base = basename($path);
            if (in_array($base, $EXCLUDE, true)) continue;
            $rel       = $relPrefix . $base;
            $entry     = $schedule[$rel] ?? [];
            $scripts[] = [
                'id'          => $rel,
                'label'       => vv_pretty_label(basename($path, '.sh')),
                'desc'        => vv_script_description($path),
                'enabled'     => (bool)($entry['enabled'] ?? false),
                'cron'        => $entry['cron'] ?? '',
                'log_enabled' => (bool)($entry['log_enabled'] ?? false),
            ];
        }
    };

    // General tools
    $collect(SCRIPTS_DIR . '/Tools', 'Tools/');

    // Platform adapter tools (Plugin/<platform>/tools/)
    foreach (glob(SCRIPTS_DIR . '/Plugin/*/tools') ?: [] as $toolsDir) {
        $platform = basename(dirname($toolsDir));
        $collect($toolsDir, "Plugin/$platform/tools/");
    }

    usort($scripts, fn($a, $b) => strcmp($a['label'], $b['label']));
    return $scripts;
}

-    $dir      = SCRIPTS_DIR . '/Tools';

+    // Background writers managed automatically — not user-facing tools

+    static $EXCLUDE = ['api_cache_writer.sh', 'remote_arr_cache_writer.sh'];

-    foreach (glob("$dir/*.sh") ?: [] as $path) {

-        $rel       = 'Tools/' . basename($path);

-        $entry     = $schedule[$rel] ?? [];

-            'label'       => vv_pretty_label(basename($path, '.sh')),

-            'desc'        => vv_script_description($path),

-            'enabled'     => (bool)($entry['enabled'] ?? false),

-            'log_enabled' => (bool)($entry['log_enabled'] ?? false),

+    $collect = function(string $dir, string $relPrefix) use ($schedule, $EXCLUDE, &$scripts): void {

+        foreach (glob("$dir/*.sh") ?: [] as $path) {

+            if (in_array($base, $EXCLUDE, true)) continue;

+            $rel       = $relPrefix . $base;

+            $entry     = $schedule[$rel] ?? [];

+                'label'       => vv_pretty_label(basename($path, '.sh')),

+                'desc'        => vv_script_description($path),

+                'enabled'     => (bool)($entry['enabled'] ?? false),

+                'log_enabled' => (bool)($entry['log_enabled'] ?? false),

+    $collect(SCRIPTS_DIR . '/Tools', 'Tools/');

+    // Platform adapter tools (Plugin/<platform>/tools/)

+    foreach (glob(SCRIPTS_DIR . '/Plugin/*/tools') ?: [] as $toolsDir) {

+        $platform = basename(dirname($toolsDir));

+        $collect($toolsDir, "Plugin/$platform/tools/");


Now update the empty-state message in the page to match the broader scope:


No scripts found in Tools/ or Plugin/*/tools/.


The file /boot/config/plugins/varaverk/Plugin/unraid/pages/scheduler.php has been updated successfully. (file state is current in your context — no need to Read it back)

<div class="vv-children" id="vv-tools-children" style="display:none;">

+
No scripts found in Tools/ or Plugin/*/tools/.

<?php foreach ($tools as $ts): $tsid = htmlspecialchars($ts['id']); ?>

<div class="vv-script" data-id="<?= $tsid ?>">


Now verify the function looks right:


233	function vv_tools_scripts(): array {
234	    // Background writers managed automatically — not user-facing tools
235	    static $EXCLUDE = ['api_cache_writer.sh', 'remote_arr_cache_writer.sh'];
236
237	    $schedule = vv_schedule_load();
238	    $scripts  = [];
239
240	    $collect = function(string $dir, string $relPrefix) use ($schedule, $EXCLUDE, &$scripts): void {
241	        foreach (glob("$dir/*.sh") ?: [] as $path) {
242	            $base = basename($path);
243	            if (in_array($base, $EXCLUDE, true)) continue;
244	            $rel       = $relPrefix . $base;
245	            $entry     = $schedule[$rel] ?? [];
246	            $scripts[] = [
247	                'id'          => $rel,
248	                'label'       => vv_pretty_label(basename($path, '.sh')),
249	                'desc'        => vv_script_description($path),
250	                'enabled'     => (bool)($entry['enabled'] ?? false),
251	                'cron'        => $entry['cron'] ?? '',
252	                'log_enabled' => (bool)($entry['log_enabled'] ?? false),
253	            ];
254	        }
255	    };
256
257	    // General tools
258	    $collect(SCRIPTS_DIR . '/Tools', 'Tools/');
259
260	    // Platform adapter tools (Plugin/<platform>/tools/)
261	    foreach (glob(SCRIPTS_DIR . '/Plugin/*/tools') ?: [] as $toolsDir) {
262	        $platform = basename(dirname($toolsDir));
263	        $collect($toolsDir, "Plugin/$platform/tools/");
264	    }
265
266	    usort($scripts, fn($a, $b) => strcmp($a['label'], $b['label']));
267	    return $scripts;
268	}
269
270	function vv_custom_scripts(): array {
271	    $dir      = SCRIPTS_DIR . '/Custom';
272	    $schedule = vv_schedule_load();

function vv_tools_scripts(): array {
    // Background writers managed automatically — not user-facing tools
    static $EXCLUDE = ['api_cache_writer.sh', 'remote_arr_cache_writer.sh'];

    $schedule = vv_schedule_load();
    $scripts  = [];

    $collect = function(string $dir, string $relPrefix) use ($schedule, $EXCLUDE, &$scripts): void {
        foreach (glob("$dir/*.sh") ?: [] as $path) {
            $base = basename($path);
            if (in_array($base, $EXCLUDE, true)) continue;
            $rel       = $relPrefix . $base;
            $entry     = $schedule[$rel] ?? [];
            $scripts[] = [
                'id'          => $rel,
                'label'       => vv_pretty_label(basename($path, '.sh')),
                'desc'        => vv_script_description($path),
                'enabled'     => (bool)($entry['enabled'] ?? false),
                'cron'        => $entry['cron'] ?? '',
                'log_enabled' => (bool)($entry['log_enabled'] ?? false),
            ];
        }
    };

    // General tools
    $collect(SCRIPTS_DIR . '/Tools', 'Tools/');

    // Platform adapter tools (Plugin/<platform>/tools/)
    foreach (glob(SCRIPTS_DIR . '/Plugin/*/tools') ?: [] as $toolsDir) {
        $platform = basename(dirname($toolsDir));
        $collect($toolsDir, "Plugin/$platform/tools/");
    }

    usort($scripts, fn($a, $b) => strcmp($a['label'], $b['label']));
    return $scripts;
}

function vv_custom_scripts(): array {
    $dir      = SCRIPTS_DIR . '/Custom';
    $schedule = vv_schedule_load();


Let me do a quick sanity check — verify the three visible tools from `Plugin/unraid/tools/` would be found:


Bash

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/scheduler.php';
\$tools = vv_tools_scripts();
foreach (\$tools as \$t) {
    echo \$t['id'] . ' — ' . \$t['label'] . PHP_EOL;
}
echo count(\$tools) . ' total' . PHP_EOL;
" 2>&1

Verify vv_tools_scripts returns merged list


Tools/bulk_permissions_repair.sh — Bulk Permissions Repair
Tools/claude_startup.sh — Claude Startup
Plugin/unraid/tools/conf_populate.sh — Conf Populate
Tools/container_data_export.sh — Container Data Export
Tools/docker_prune_images.sh — Docker Prune Images
Tools/emby_database_repair.sh — Emby Database Repair
Tools/emby_to_lidarr_sync.sh — Emby To Lidarr Sync
Tools/emby_to_radarr_sync.sh — Emby To Radarr Sync
Tools/emby_to_sonarr_sync.sh — Emby To Sonarr Sync
Tools/fallback_state_reset.sh — Fallback State Reset
Tools/ramdisk_stop.sh — Ramdisk Stop
Tools/recreate_shares.sh — Recreate Shares
Tools/smart_long_test.sh — Smart Long Test
Plugin/unraid/tools/storage_migrate.sh — Storage Migrate
Tools/watchdog_skip_list_manager.sh — Watchdog Skip List Manager
Tools/zfs_pool_scrub.sh — Zfs Pool Scrub
16 total


All three user-facing platform tools (`conf_populate`, `storage_migrate`) are merged in, both writers are excluded, and everything sorts alphabetically. 16 tools total across both directories.

The Tools card will now show all general tools and OS-specific adapter tools in one merged, alphabetically-sorted list. Writers (`api_cache_writer.sh`, `remote_arr_cache_writer.sh`) are silently excluded. When a future platform folder like `Plugin/proxmox/tools/` appears, it'll be picked up automatically.


ok so monitors and tools what else belongs in os specific adapter if any


Let me scan the full structure to reason about what's Unraid-specific vs platform-agnostic:


find /boot/config/plugins/varaverk -mindepth 1 -maxdepth 4 \
  \( -name "*.sh" -o -name "*.php" \) \
  ! -path "*/Plugin/*" \
  | sort | sed "s|/boot/config/plugins/varaverk/||"

List all scripts outside Plugin/ directory


Deployment/conf_upgrade.sh
Deployment/deploy.sh
Docker_Essentials/docker_container_stop.sh
Docker_Essentials/docker_daily_restart.sh
Docker_Essentials/docker_network_connect.sh
Docker_Essentials/docker_update.sh
Docker_Essentials/docker_update_remaining.sh
Docker_Essentials/docker_weekly_restart.sh
Docker_Essentials/downloaders_reset.sh
Fallback/fallback.sh
Fallback/fallback_test.sh
Kernel/decision_engine.sh
Media/arr_sync.sh
Media/arrs_failed_stalled_recovery.sh
Media/lidarr_cleanup.sh
Media/lidarr_missing_art.sh
Media/media_cleaner.sh
Media/media_shares_permissions.sh
Media/play_state_sync.sh
Media/playback_aware_lidarr_discovery.sh
Media/playback_aware_radarr_discovery.sh
Media/playback_aware_sonarr_discovery.sh
Media/radarr_cleanup.sh
Media/radarr_tmdb_removed.sh
Media/sonarr_cleanup.sh
Media/sonarr_tvdb_removed.sh
Monitors/backup_verify.sh
Monitors/bandwidth_monitor.sh
Monitors/cert_monitor.sh
Monitors/emby_session_report.sh
Monitors/mesh_monitor.sh
Monitors/smart_health.sh
Monitors/system_tuning_monitor.sh
Monitors/weekly_health_digest.sh
Monitors/zfs_memory_snapshot.sh
Old_Arch_Still_Works/arr_cleanup.sh
Old_Arch_Still_Works/continuous_scripts_status.sh
Orchestrators/array_started.sh
Orchestrators/array_stopping.sh
Orchestrators/critical_sync_maintenance.sh
Orchestrators/daily_sync_maintenance.sh
Orchestrators/intermediate_sync_maintenance.sh
Orchestrators/monthly_maintenance.sh
Orchestrators/sunday_morning_coffee_report.sh
Orchestrators/transcode_management.sh
Orchestrators/watchdog_orchestrator.sh
Orchestrators/weekly_sync_maintenance.sh
Partnership/gitea_ssh_setup.sh
Partnership/onboard_cancel.sh
Partnership/partnership_manager.sh
Partnership/partnership_offboard.sh
Partnership/partnership_onboard.sh
Partnership/partnership_transfer.sh
Partnership/ssh_setup.sh
Rsync/rsync.sh
System_Essentials/clear_logs.sh
System_Essentials/conf_sync.sh
System_Essentials/docker_syslog_filter.sh
System_Essentials/inotify_tuning.sh
System_Essentials/mover_stop.sh
System_Essentials/php_fpm_max_children.sh
System_Essentials/rsync_stop.sh
System_Essentials/server_reboot.sh
System_Essentials/unraid_api_key_renew.sh
System_Essentials/user_scripts_stop.sh
Tools/bulk_permissions_repair.sh
Tools/claude_startup.sh
Tools/container_data_export.sh
Tools/docker_prune_images.sh
Tools/emby_database_repair.sh
Tools/emby_to_lidarr_sync.sh
Tools/emby_to_radarr_sync.sh
Tools/emby_to_sonarr_sync.sh
Tools/fallback_state_reset.sh
Tools/ramdisk_stop.sh
Tools/recreate_shares.sh
Tools/smart_long_test.sh
Tools/watchdog_skip_list_manager.sh
Tools/zfs_pool_scrub.sh
Transcodes/ramdisk_setup.sh
Transcodes/transcode_cleanup.sh
Transcodes/transcode_manager.sh
Watchdogs/System/network_watchdog.sh
Watchdogs/System/storage_watchdog.sh
Watchdogs/System/webgui_watchdog.sh
Watchdogs/docker_watchdog.sh
Watchdogs/resource_watchdog.sh
Watchdogs/stability_watchdog.sh
Watchdogs/system_watchdog.sh
common.sh
git_pull_execute.sh
load_config.sh
user_script_plug-in.sh


find /boot/config/plugins/varaverk/Plugin -mindepth 1 -maxdepth 5 | sort | sed "s|/boot/config/plugins/varaverk/||"

List everything currently in Plugin/


Plugin/Icons
Plugin/Icons/Varaverk.png
Plugin/Manual-Plugin.md
Plugin/README-Plugin.md
Plugin/build.sh
Plugin/dev_install.sh
Plugin/dist
Plugin/dist/varaverk-2026.05.31-noarch-1.txz.sha256
Plugin/unraid
Plugin/unraid/Varaverk.page
Plugin/unraid/VaraverkSettings.page
Plugin/unraid/adapter.sh
Plugin/unraid/api
Plugin/unraid/api/api_test.php
Plugin/unraid/api/arrs.php
Plugin/unraid/api/auth.php
Plugin/unraid/api/board.php
Plugin/unraid/api/clearlock.php
Plugin/unraid/api/conf_toggle.php
Plugin/unraid/api/confform.php
Plugin/unraid/api/config.php
Plugin/unraid/api/create_api_key.php
Plugin/unraid/api/docker.php
Plugin/unraid/api/docker_action.php
Plugin/unraid/api/dryrun.php
Plugin/unraid/api/fallback.php
Plugin/unraid/api/flag_toggle.php
Plugin/unraid/api/log.php
Plugin/unraid/api/manual_sync.php
Plugin/unraid/api/media.php
Plugin/unraid/api/monitor.php
Plugin/unraid/api/monitor_fast.php
Plugin/unraid/api/monitor_remote.php
Plugin/unraid/api/movescript.php
Plugin/unraid/api/partnership.php
Plugin/unraid/api/partnership_ping.php
Plugin/unraid/api/partnership_settings.php
Plugin/unraid/api/rawconf.php
Plugin/unraid/api/readscript.php
Plugin/unraid/api/recent.php
Plugin/unraid/api/reorderarray.php
Plugin/unraid/api/rsync.php
Plugin/unraid/api/rsync_profiles.php
Plugin/unraid/api/rsync_standalone.php
Plugin/unraid/api/rsync_win_arrays.php
Plugin/unraid/api/run.php
Plugin/unraid/api/savefolders.php
Plugin/unraid/api/scheduler.php
Plugin/unraid/api/script.php
Plugin/unraid/api/scriptinfo.php
Plugin/unraid/api/settings.php
Plugin/unraid/api/setup.php
Plugin/unraid/api/snapshot.php
Plugin/unraid/api/status.php
Plugin/unraid/api/stop.php
Plugin/unraid/api/storage.php
Plugin/unraid/api/system.php
Plugin/unraid/api/watchdog.php
Plugin/unraid/css
Plugin/unraid/css/varaverk.css
Plugin/unraid/event
Plugin/unraid/event/disks_mounted
Plugin/unraid/event/disks_mounted/array_start_jobs
Plugin/unraid/event/disks_mounted/rebuild_cron
Plugin/unraid/event/disks_unmounting
Plugin/unraid/event/disks_unmounting/array_stop_jobs
Plugin/unraid/icons
Plugin/unraid/icons/varaverk.png
Plugin/unraid/include
Plugin/unraid/include/arrs.php
Plugin/unraid/include/auth.php
Plugin/unraid/include/common.php
Plugin/unraid/include/confform.php
Plugin/unraid/include/config.php
Plugin/unraid/include/docker.php
Plugin/unraid/include/docker_folders.php
Plugin/unraid/include/docs.php
Plugin/unraid/include/fallback.php
Plugin/unraid/include/media.php
Plugin/unraid/include/monitor.php
Plugin/unraid/include/partnership.php
Plugin/unraid/include/scheduler.php
Plugin/unraid/include/unraid_api.php
Plugin/unraid/include/vms.php
Plugin/unraid/include/watchdog.php
Plugin/unraid/js
Plugin/unraid/js/varaverk.js
Plugin/unraid/pages
Plugin/unraid/pages/arrs.php
Plugin/unraid/pages/auth.php
Plugin/unraid/pages/docker.php
Plugin/unraid/pages/fallback.php
Plugin/unraid/pages/monitor.php
Plugin/unraid/pages/partnership.php
Plugin/unraid/pages/rsync.php
Plugin/unraid/pages/scheduler.php
Plugin/unraid/pages/settings.php
Plugin/unraid/pages/setup.php
Plugin/unraid/pages/watchdog.php
Plugin/unraid/run_job.sh
Plugin/unraid/tools
Plugin/unraid/tools/api_cache_writer.php
Plugin/unraid/tools/api_cache_writer.sh
Plugin/unraid/tools/conf_populate.sh
Plugin/unraid/tools/remote_arr_cache_writer.sh
Plugin/unraid/tools/storage_migrate.sh
Plugin/varaverk.plg


Let me look at the scripts I think are clearly Unraid-specific before giving you my take:


head -30 /boot/config/plugins/varaverk/System_Essentials/mover_stop.sh
echo "---"
head -30 /boot/config/plugins/varaverk/System_Essentials/user_scripts_stop.sh
echo "---"
head -30 /boot/config/plugins/varaverk/System_Essentials/php_fpm_max_children.sh
echo "---"
head -20 /boot/config/plugins/varaverk/System_Essentials/unraid_api_key_renew.sh
echo "---"
head -20 /boot/config/plugins/varaverk/user_script_plug-in.sh

Inspect potentially Unraid-specific system scripts


#!/bin/bash
# ==============================================================================================
# ================================= Mover Stop =================================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Safely stops the unRAID mover with a wall warning, configurable timeout, and
# SIGTERM → SIGKILL sequence. Use before planned reboots, disk operations, or
# any operation where mover and rsync running simultaneously could corrupt files.
# Exits cleanly if mover is not running.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# Stop Sequence
#   1. Check if mover is running — exit cleanly if not
#   2. Wall message to all logged-in terminal users
#   3. Wait MOVER_STOP_TIMEOUT seconds
#   4. SIGTERM — allows mover to finish its current file before stopping
#      (no partial files — the mover completes what it is working on)
#   5. Wait 5 seconds → verify stopped
#   6. SIGKILL if still running — forced stop, partial files possible
#   7. Final verify — error if still running after SIGKILL
#
# SIGTERM first because the mover has an opportunity to finish the file it is
# currently moving, leaving no partial copies on cache or array. SIGKILL is only
# used as a last resort and may leave a file split across cache and array.
#
---
#!/bin/bash
# ==============================================================================================
# ============================= User Scripts Stop ==============================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Stops all running User Script processes spawned by the unRAID User Scripts
# plugin. Shows script names not just PIDs so you know what's being stopped.
# Called automatically by server_reboot.sh as part of the shutdown sequence,
# and useful directly when a script is stuck and won't respond to the UI.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# Process Identification
#   Scans /proc/*/cmdline for processes whose command line contains
#   "/tmp/user.scripts". The User Scripts plugin stages all scripts in
#   /tmp/user.scripts/ before execution — more reliable than process name
#   matching which can vary.
#
# Stop Sequence Per Process
#   1. Send SIGTERM — allows the script to trap and clean up gracefully
#   2. Wait 5 seconds
#   3. If still running → SIGKILL (force)
#   4. Verify dead after SIGKILL — error if still running
#
# Self-Exclusion
#   If this script is run via the User Scripts plugin it would find its own
---
#!/bin/bash
# ==============================================================================================
# ============================= PHP-FPM Max Children ===========================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Raises PHP-FPM pm.max_children to prevent WebGUI slowdowns under load. Run
# once at array start via ARRAY_START_SCRIPTS. Idempotent — silent when the
# value is already correct, no restart on clean boot.
#
# unRAID's WebGUI runs through PHP-FPM. The default pm.max_children is very
# low (4–8). Under load — multiple users, Docker operations, heavy dashboard
# usage — all PHP workers saturate and new requests queue. The WebGUI becomes
# slow or unresponsive.
#
# PHP_MAX_CHILDREN=250 is appropriate for 128GB RAM: ~2MB per worker = ~500MB
# total. Too high wastes RAM; too low causes slowdowns.
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Idempotent Content Check
#   Reads the current pm.max_children value before writing. If already at
#   target → silent exit, no PHP-FPM restart. Restarting PHP-FPM unnecessarily
#   disrupts active WebGUI sessions on every boot.
#
# Pattern Match Before Write
#   Verifies the sed pattern finds pm.max_children in the config before
---
#!/bin/bash
# ==============================================================================================
# ================================= Unraid API Key Renewal ====================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Creates/overwrites the Varaverk API key in the unraid-api service registry at
# array start. The registry is ephemeral — OS updates and service restarts clear
# it. This script re-registers the key every boot so Varaverk's enhanced
# monitoring self-heals without manual intervention.
#
# Also updates HOST*_UNRAID_API_KEY in the local host conf so the partnership
# page always reflects the live key value.
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# unraid_api_key_renew.sh
---
#!/bin/bash
# ==============================================================================================
# ============================= USER SCRIPTS MASTER TEMPLATE ===================================
# ==============================================================================================
#
# Paste this file into a User Script entry. Uncomment ONE script block and set the schedule.
# Every script in the ecosystem is listed here — from the orchestrators that run it all,
# down to the individual scripts you can run standalone for specific tasks.
#
# ── HOW THIS ECOSYSTEM WORKS ──────────────────────────────────────────────────────────────────
# When used as intended, only a handful of orchestrators need to be scheduled.
# The orchestrators handle everything else in the correct order — they call child scripts,
# manage timing and dependencies, track pass/fail, and send one notification per window.
#
# You do not need to schedule every script below. The orchestrators cover it all:
#
#   array_started.sh                    at array start — launches ALL startup scripts in order
#   array_stopping.sh                 at array stop  — graceful shutdown sequence
#   watchdog_orchestrator.sh          every 15 min   — resource + docker + system watchdog
#   transcode_management.sh           every 7 min   — cleanup then manager (order critical)


head -20 /boot/config/plugins/varaverk/Watchdogs/System/webgui_watchdog.sh
echo "---"
head -25 /boot/config/plugins/varaverk/Tools/recreate_shares.sh
echo "---"
head -15 /boot/config/plugins/varaverk/Orchestrators/array_started.sh
echo "---"
head -15 /boot/config/plugins/varaverk/git_pull_execute.sh

Inspect remaining Unraid-specific candidates


#!/bin/bash
# ==============================================================================================
# ================================= WebGUI Watchdog ============================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Monitors the unRAID WebGUI and restarts services if unresponsive. Uses a
# three-step escalating strategy — lightest fix first, heaviest last. Called
# by system_watchdog.sh each cycle. Silent when healthy.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# Escalation Path
#   WebGUI responding → log() + exit 0 (completely silent ✅)
#
#   Not responding:
#     Step 1 — nginx restart
---
#!/bin/bash
# ==============================================================================================
# ============================= Recreate Shares ================================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Creates share directories on the correct disks after a fresh unRAID install
# or disk rebuild. Reads all .cfg files from /boot/config/shares/ and creates
# the corresponding directories on each disk listed in the shareInclude setting.
# The array must be started before running — /mnt/user must be mounted.
#
# Typically run on HOST2 after a full disk replacement or fresh install where
# share folders were lost but /boot/config/shares/*.cfg files were restored.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# For each share .cfg file:
#   1. Reads shareInclude= to determine which disks own this share
#   2. Creates /mnt/diskN/ShareName/ on each listed disk if it doesn't exist
#   3. Places a .recovery marker file in /mnt/user/ShareName/ via the union filesystem
#
# .recovery Marker
---
#!/bin/bash
# ==============================================================================================
# ================================= Array Start Orchestrator ===================================
# ==============================================================================================
# Single entry point for array start — fired by the Varaverk plugin's
# disks_mounted event hook (Plugin/unraid/event/disks_mounted/array_start_jobs).
# Launches everything configured in ARRAY_START_SCRIPTS in master.conf.
# This script exits after launching all scripts — the event hook sees it complete normally.
#
# ── WHAT IT LAUNCHES ──────────────────────────────────────────────────────────────────────────
# Configured in master.conf ARRAY_START_SCRIPTS — no changes to this script ever needed.
# Current order (order matters — see below):
#
#   ONE-SHOT (run and exit naturally):
#     System_Essentials/unraid_api_key_renew.sh — re-register Varaverk API key at boot
---
#!/bin/bash
# ==============================================================================================
# ================================= Git Pull & Execute =========================================
# ==============================================================================================
# Pulls the latest scripts from the Gitea repository via SSH.
# Lives at the repo root — sources load_config.sh from the same directory.
#
# ── WHAT THIS SCRIPT DOES ─────────────────────────────────────────────────────────────────────
# 1. Detects which server it's running on via detect_hosts() (MY_ID)
# 2. Configures sparse checkout to exclude other servers' credential files
#    Each server only pulls its own host*.conf — never sees peer credentials
# 3. Pulls or clones latest scripts from Gitea
# 4. Sets executable permissions on all .sh files
#
# ── SPARSE CHECKOUT ───────────────────────────────────────────────────────────────────────────


Here's my read:

**Clearly Unraid-specific — belong in the adapter:**

| Current path | Why Unraid-specific |
|---|---|
| `System_Essentials/unraid_api_key_renew.sh` | Registers keys in Unraid's ephemeral API service registry |
| `System_Essentials/mover_stop.sh` | Unraid Mover is a unique Unraid concept |
| `System_Essentials/user_scripts_stop.sh` | Unraid User Scripts plugin (`/tmp/user.scripts/`) |
| `System_Essentials/php_fpm_max_children.sh` | Tunes Unraid's WebGUI PHP-FPM pool |
| `Watchdogs/System/webgui_watchdog.sh` | Watches and restarts Unraid's nginx/WebGUI stack |
| `Tools/recreate_shares.sh` | Reads `/boot/config/shares/*.cfg`, creates `/mnt/diskN/` — Unraid share model exclusively |
| `user_script_plug-in.sh` | Template for the Unraid User Scripts plugin |

**Gray area — tied to Unraid lifecycle but not necessarily the adapter:**

- `Orchestrators/array_started.sh` / `array_stopping.sh` — the "array" concept is Unraid-specific, and they're already triggered by `Plugin/unraid/event/` hooks. You could argue these belong in `Plugin/unraid/` as lifecycle orchestrators. But they're just "run ARRAY_START_SCRIPTS in order" — the logic is generic even if the trigger isn't.

**Everything else is platform-agnostic** — Docker, Media, Monitors, most Watchdogs, Rsync, Partnership, Fallback all work the same on any Linux box.

**My recommendation:** Don't move anything right now. The seven above are genuinely Unraid-specific but most of them are called by orchestrators — moving them would require updating `master.conf` arrays, schedule.json, and any hardcoded paths. The payoff is conceptual clarity, not functional improvement. Worth doing when you tackle the setup wizard (since `conf_populate.sh` is already there and that'll touch the adapter boundary again), but not before.

The `user_script_plug-in.sh` is the one that really belongs in `Plugin/unraid/` today since it's literally a plugin file, not a script in the execution graph.


- Orchestrators/array_started.sh / array_stopping.sh, stay, all others migrate with the same folder stucture... i want folders in plugins to match main. easy to follow


Good call on the mirrored structure — easy to reason about. Let me first audit all the cross-references before touching anything:


grep -rn "mover_stop\|user_scripts_stop\|php_fpm_max_children\|unraid_api_key_renew\|webgui_watchdog\|recreate_shares\|user_script_plug" \
  /boot/config/plugins/varaverk \
  --include="*.sh" --include="*.php" --include="*.conf" \
  | grep -v "^Binary\|Plugin/unraid/tools\|Plugin/unraid/Tools" \
  | sort

Find all references to the 7 scripts being moved


/boot/config/plugins/varaverk/Configurations/master.conf:284:        "System_Essentials/unraid_api_key_renew.sh"   # re-register Varaverk API key — registry is ephemeral
/boot/config/plugins/varaverk/Configurations/master.conf:288:        "System_Essentials/php_fpm_max_children.sh"   # WebGUI performance tuning
/boot/config/plugins/varaverk/Configurations/master.conf:300:        "System_Essentials/user_scripts_stop.sh"        # stop background scripts before they start new ops
/boot/config/plugins/varaverk/Configurations/master.conf:301:        "Fallback/fallback.sh --stop"                   # gracefully stop fallback (not caught by user_scripts_stop)
/boot/config/plugins/varaverk/Configurations/master.conf:303:        "System_Essentials/mover_stop.sh"               # stop mover after rsync (they conflict on same files)
/boot/config/plugins/varaverk/Configurations/master.conf:317:        "System_Essentials/unraid_api_key_renew.sh"   # re-register API key if registry lost it
/boot/config/plugins/varaverk/Configurations/master.conf:326:        "Watchdogs/System/webgui_watchdog.sh"    # WebGUI availability — nginx → php-fpm → emhttp
/boot/config/plugins/varaverk/Configurations/master.conf:833:# Seconds to wait before mover_stop.sh sends SIGTERM to the mover process.
/boot/config/plugins/varaverk/Deployment/conf_templates/master.conf:286:        "System_Essentials/php_fpm_max_children.sh"   # WebGUI performance tuning
/boot/config/plugins/varaverk/Deployment/conf_templates/master.conf:297:        "System_Essentials/user_scripts_stop.sh"        # stop background scripts before they start new ops
/boot/config/plugins/varaverk/Deployment/conf_templates/master.conf:298:        "Fallback/fallback.sh --stop"                   # gracefully stop fallback (not caught by user_scripts_stop)
/boot/config/plugins/varaverk/Deployment/conf_templates/master.conf:300:        "System_Essentials/mover_stop.sh"               # stop mover after rsync (they conflict on same files)
/boot/config/plugins/varaverk/Deployment/conf_templates/master.conf:322:        "Watchdogs/System/webgui_watchdog.sh"    # WebGUI availability — nginx → php-fpm → emhttp
/boot/config/plugins/varaverk/Deployment/conf_templates/master.conf:828:# Seconds to wait before mover_stop.sh sends SIGTERM to the mover process.
/boot/config/plugins/varaverk/Monitors/system_tuning_monitor.sh:75:#       Maximum php-fpm workers — set by php_fpm_max_children.sh in System_Essentials.
/boot/config/plugins/varaverk/Orchestrators/array_started.sh:15:#     System_Essentials/unraid_api_key_renew.sh — re-register Varaverk API key at boot
/boot/config/plugins/varaverk/Orchestrators/array_started.sh:18:#     System_Essentials/php_fpm_max_children.sh — WebGUI performance tuning
/boot/config/plugins/varaverk/Orchestrators/array_started.sh:29:#   unraid_api_key_renew.sh — before anything else — self-heals API registry at boot
/boot/config/plugins/varaverk/Orchestrators/array_stopping.sh:11:#   3. mover_stop.sh               — stop mover after rsync (both write to same paths)
/boot/config/plugins/varaverk/Orchestrators/array_stopping.sh:9:#   1. user_scripts_stop.sh        — kill background user scripts (prevents new operations)
/boot/config/plugins/varaverk/Orchestrators/watchdog_orchestrator.sh:10:#   Default: resource_watchdog → docker_watchdog → system_watchdog → unraid_api_key_renew → stability_watchdog
/boot/config/plugins/varaverk/Plugin/unraid/api/storage.php:144:    $script = SCRIPTS_DIR . '/System_Essentials/unraid_api_key_renew.sh';
/boot/config/plugins/varaverk/Plugin/unraid/api/storage.php:146:        echo json_encode(['ok' => false, 'error' => 'unraid_api_key_renew.sh not found']); exit;
/boot/config/plugins/varaverk/Plugin/unraid/include/confform.php:27:    'Watchdogs/System/webgui_watchdog.sh'             => ['WebGUI Watchdog'],
/boot/config/plugins/varaverk/Plugin/unraid/include/scheduler.php:149:// Parse user_script_plug-in.sh into an array of script blocks.
/boot/config/plugins/varaverk/Plugin/unraid/include/scheduler.php:152:    $file   = SCRIPTS_DIR . '/user_script_plug-in.sh';
/boot/config/plugins/varaverk/System_Essentials/mover_stop.sh:61:# mover_stop.sh
/boot/config/plugins/varaverk/System_Essentials/mover_stop.sh:64:# mover_stop.sh --dry-run
/boot/config/plugins/varaverk/System_Essentials/mover_stop.sh:67:# mover_stop.sh --status
/boot/config/plugins/varaverk/System_Essentials/mover_stop.sh:70:# mover_stop.sh --log
/boot/config/plugins/varaverk/System_Essentials/php_fpm_max_children.sh:78:# php_fpm_max_children.sh
/boot/config/plugins/varaverk/System_Essentials/php_fpm_max_children.sh:81:# php_fpm_max_children.sh --dry-run
/boot/config/plugins/varaverk/System_Essentials/php_fpm_max_children.sh:84:# php_fpm_max_children.sh --status
/boot/config/plugins/varaverk/System_Essentials/php_fpm_max_children.sh:87:# php_fpm_max_children.sh --log
/boot/config/plugins/varaverk/System_Essentials/server_reboot.sh:180:    warn "Consider: mover_stop.sh before rebooting"
/boot/config/plugins/varaverk/System_Essentials/unraid_api_key_renew.sh:20:# unraid_api_key_renew.sh
/boot/config/plugins/varaverk/System_Essentials/unraid_api_key_renew.sh:23:# unraid_api_key_renew.sh --dry-run
/boot/config/plugins/varaverk/System_Essentials/unraid_api_key_renew.sh:26:# unraid_api_key_renew.sh --log
/boot/config/plugins/varaverk/System_Essentials/user_scripts_stop.sh:57:# user_scripts_stop.sh
/boot/config/plugins/varaverk/System_Essentials/user_scripts_stop.sh:60:# user_scripts_stop.sh --dry-run
/boot/config/plugins/varaverk/System_Essentials/user_scripts_stop.sh:63:# user_scripts_stop.sh --status
/boot/config/plugins/varaverk/System_Essentials/user_scripts_stop.sh:66:# user_scripts_stop.sh --log
/boot/config/plugins/varaverk/Tools/recreate_shares.sh:64:# recreate_shares.sh
/boot/config/plugins/varaverk/Tools/recreate_shares.sh:67:# recreate_shares.sh --dry-run
/boot/config/plugins/varaverk/Tools/recreate_shares.sh:70:# recreate_shares.sh --log
/boot/config/plugins/varaverk/Tools/recreate_shares.sh:73:# recreate_shares.sh --status
/boot/config/plugins/varaverk/Watchdogs/System/webgui_watchdog.sh:79:# webgui_watchdog.sh
/boot/config/plugins/varaverk/Watchdogs/System/webgui_watchdog.sh:82:# webgui_watchdog.sh --dry-run
/boot/config/plugins/varaverk/Watchdogs/System/webgui_watchdog.sh:85:# webgui_watchdog.sh --status
/boot/config/plugins/varaverk/Watchdogs/System/webgui_watchdog.sh:88:# webgui_watchdog.sh --log
/boot/config/plugins/varaverk/Watchdogs/system_watchdog.sh:11:#   Default: storage_watchdog → webgui_watchdog → network_watchdog
/boot/config/plugins/varaverk/user_script_plug-in.sh:129:#   php_fpm_max_children.sh   set PHP-FPM workers to 250 BEFORE WebGUI serves requests
/boot/config/plugins/varaverk/user_script_plug-in.sh:1398:# recreate_shares.sh — create share directories on disks after fresh install or rebuild
/boot/config/plugins/varaverk/user_script_plug-in.sh:1404:# bash /boot/config/plugins/varaverk/Tools/recreate_shares.sh --dry-run
/boot/config/plugins/varaverk/user_script_plug-in.sh:1405:# bash /boot/config/plugins/varaverk/Tools/recreate_shares.sh --status
/boot/config/plugins/varaverk/user_script_plug-in.sh:1406:# bash /boot/config/plugins/varaverk/Tools/recreate_shares.sh
/boot/config/plugins/varaverk/user_script_plug-in.sh:149:#   user_scripts_stop.sh       stop background user scripts first (prevents new operations)
/boot/config/plugins/varaverk/user_script_plug-in.sh:150:#   fallback.sh --stop         gracefully stop fallback (not caught by user_scripts_stop)
/boot/config/plugins/varaverk/user_script_plug-in.sh:152:#   mover_stop.sh              stop mover after rsync (both write to same paths)
/boot/config/plugins/varaverk/user_script_plug-in.sh:176:#                               webgui_watchdog.sh   WebGUI availability (nginx → php-fpm → emhttp)
/boot/config/plugins/varaverk/user_script_plug-in.sh:707:# Iterates SYSTEM_WATCHDOG_SCRIPTS sequentially: storage_watchdog → webgui_watchdog → network_watchdog.
/boot/config/plugins/varaverk/user_script_plug-in.sh:799:# php_fpm_max_children.sh — set PHP-FPM pm.max_children at array start
/boot/config/plugins/varaverk/user_script_plug-in.sh:805:# bash /boot/config/plugins/varaverk/System_Essentials/php_fpm_max_children.sh --status
/boot/config/plugins/varaverk/user_script_plug-in.sh:806:# bash /boot/config/plugins/varaverk/System_Essentials/php_fpm_max_children.sh
/boot/config/plugins/varaverk/user_script_plug-in.sh:817:# webgui_watchdog.sh — WebGUI availability watchdog (called by system_watchdog via SYSTEM_WATCHDOG_SCRIPTS)
/boot/config/plugins/varaverk/user_script_plug-in.sh:820:# bash /boot/config/plugins/varaverk/Watchdogs/System/webgui_watchdog.sh --dry-run
/boot/config/plugins/varaverk/user_script_plug-in.sh:821:# bash /boot/config/plugins/varaverk/Watchdogs/System/webgui_watchdog.sh --status
/boot/config/plugins/varaverk/user_script_plug-in.sh:829:# mover_stop.sh — stop unRAID mover cleanly before array operations
/boot/config/plugins/varaverk/user_script_plug-in.sh:834:# bash /boot/config/plugins/varaverk/System_Essentials/mover_stop.sh --status
/boot/config/plugins/varaverk/user_script_plug-in.sh:835:# bash /boot/config/plugins/varaverk/System_Essentials/mover_stop.sh --dry-run
/boot/config/plugins/varaverk/user_script_plug-in.sh:836:# bash /boot/config/plugins/varaverk/System_Essentials/mover_stop.sh
/boot/config/plugins/varaverk/user_script_plug-in.sh:848:# user_scripts_stop.sh — stop all running User Script processes
/boot/config/plugins/varaverk/user_script_plug-in.sh:854:# bash /boot/config/plugins/varaverk/System_Essentials/user_scripts_stop.sh --status
/boot/config/plugins/varaverk/user_script_plug-in.sh:855:# bash /boot/config/plugins/varaverk/System_Essentials/user_scripts_stop.sh --dry-run
/boot/config/plugins/varaverk/user_script_plug-in.sh:856:# bash /boot/config/plugins/varaverk/System_Essentials/user_scripts_stop.sh
/boot/config/plugins/varaverk/user_script_plug-in.sh:89:#   v2.5 — fallback.sh --stop: added to ARRAY_STOP_SCRIPTS as step 2 (after user_scripts_stop,
/boot/config/plugins/varaverk/user_script_plug-in.sh:91:#           so it is not caught by user_scripts_stop.sh.
/boot/config/plugins/varaverk/user_script_plug-in.sh:96:#           webgui_watchdog.sh moved from standalone cron to SYSTEM_WATCHDOG_SCRIPTS chain.


Now let me check source depths in all 7 scripts and the current tools/ source depth: