{"parentUuid":null,"isSidechain":true,"promptId":"4ede2c2c-ae62-435a-a0fb-4f8d63fa41ab","agentId":"ab976a3a2d54c1360","type":"user","message":{"role":"user","content":"Audit every bash script in these directories against its own header documentation. For each script, read the full file and check:\n1. Does PURPOSE match what the code actually does?\n2. Does CONFIGURATION list all config vars the script actually uses (missing vars, or vars listed but never referenced)?\n3. Does RUNTIME MODES / flags match what's actually parsed (missing flags, or flags documented but not implemented)?\n4. Are there OPERATIONAL SAFEGUARDS documented that aren't in the code?\n5. Anything the code does that the header doesn't mention?\n\nScripts to audit:\n- /boot/config/plugins/varaverk/Fallback/fallback.sh\n- /boot/config/plugins/varaverk/Fallback/fallback_test.sh\n- /boot/config/plugins/varaverk/Kernel/decision_engine.sh\n- /boot/config/plugins/varaverk/Rsync/rsync.sh\n- /boot/config/plugins/varaverk/load_config.sh\n- /boot/config/plugins/varaverk/common.sh\n- /boot/config/plugins/varaverk/git_pull_execute.sh\n\nReport ONLY real discrepancies — not style opinions. Format: filename, then a short bullet list of issues. If a script's header matches its code exactly, just write \"✅ filename\". Be specific: quote the header claim and what the code actually does differently."},"uuid":"bf04c0a8-2441-467c-8abb-9b9b2bc68490","timestamp":"2026-06-14T05:32:07.024Z","userType":"external","entrypoint":"cli","cwd":"/boot/config/plugins/varaverk","sessionId":"685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f","version":"2.1.177","gitBranch":"HEAD","slug":"mutable-sniffing-naur"} {"parentUuid":"bf04c0a8-2441-467c-8abb-9b9b2bc68490","isSidechain":true,"agentId":"ab976a3a2d54c1360","attachment":{"type":"deferred_tools_delta","addedNames":["EnterWorktree","ExitWorktree","Monitor","TaskStop","WebFetch","WebSearch","mcp__claude_ai_Google_Drive__authenticate","mcp__claude_ai_Google_Drive__complete_authentication"],"addedLines":["EnterWorktree","ExitWorktree","Monitor","TaskStop","WebFetch","WebSearch","mcp__claude_ai_Google_Drive__authenticate","mcp__claude_ai_Google_Drive__complete_authentication"],"removedNames":[],"readdedNames":[]},"type":"attachment","uuid":"3ab20442-645d-437f-9327-d4f00798da82","timestamp":"2026-06-14T05:32:07.027Z","userType":"external","entrypoint":"cli","cwd":"/boot/config/plugins/varaverk","sessionId":"685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f","version":"2.1.177","gitBranch":"HEAD","slug":"mutable-sniffing-naur"} {"parentUuid":"3ab20442-645d-437f-9327-d4f00798da82","isSidechain":true,"agentId":"ab976a3a2d54c1360","attachment":{"type":"skill_listing","content":"- update-config: Use this skill to configure the Claude Code harness via settings.json. Automated behaviors (\"from now on when X\", \"each time X\", \"whenever X\", \"before/after X\") require hooks configured in settings.json - the harness executes these, not Claude, so memory/preferences cannot fulfill them. Also use for: permissions (\"allow X\", \"add permission\", \"move permission to\"), env vars (\"set X=Y\"), hook troubleshooting, or any changes to settings.json/settings.local.json files. Examples: \"allow npm commands\", \"add bq permission to global settings\", \"move permission to user settings\", \"set DEBUG=true\", \"when claude stops show X\". For simple settings like theme/model, suggest the /config command.\n- keybindings-help: Use when the user wants to customize keyboard shortcuts, rebind keys, add chord bindings, or modify ~/.claude/keybindings.json. Examples: \"rebind ctrl+s\", \"add a chord shortcut\", \"change the submit key\", \"customize keybindings\".\n- verify: Verify that a code change actually does what it's supposed to by running the app and observing behavior. Use when asked to verify a PR, confirm a fix works, test a change manually, check that a feature works, or validate local changes before pushing.\n- code-review: Review the current diff for correctness bugs and reuse/simplification/efficiency cleanups at the given effort level (low/medium: fewer, high-confidence findings; high→max: broader coverage, may include uncertain findings; ultra: deep multi-agent review in the cloud). Pass --comment to post findings as inline PR comments, or --fix to apply the findings to the working tree after the review.\n- simplify: Review the changed code for reuse, simplification, efficiency, and altitude cleanups, then apply the fixes. Quality only — it does not hunt for bugs; use /code-review for that.\n- fewer-permission-prompts: Scan your transcripts for common read-only Bash and MCP tool calls, then add a prioritized allowlist to project .claude/settings.json to reduce permission prompts.\n- loop: Run a prompt or slash command on a recurring interval (e.g. /loop 5m /foo). Omit the interval to let the model self-pace. - When the user wants to set up a recurring task, poll for status, or run something repeatedly on an interval (e.g. \"check the deploy every 5 minutes\", \"keep running /babysit-prs\"). Do NOT invoke for one-off tasks.\n- schedule: Create, update, list, or run scheduled cloud agents (routines) that execute on a cron schedule. - When the user wants to schedule a recurring cloud agent, set up automated tasks, create a cron job for Claude Code, or manage their scheduled agents/routines. Also use when the user wants a one-time scheduled run (\"run this once at 3pm\", \"remind me to check X tomorrow\").\n- claude-api: Reference for the Claude API / Anthropic SDK — model ids, pricing, params, streaming, tool use, MCP, agents, caching, token counting, model migration.\nTRIGGER — read BEFORE opening the target file; don't skip because it \"looks like a one-liner\" — whenever: the prompt names Claude/Anthropic in any form (Claude, Anthropic, Fable, Opus, Sonnet, Haiku, `anthropic`, `@anthropic-ai`, `claude-*`, `us.anthropic.*`, `[1m]`); the user asks about an LLM (pricing/model choice/limits/caching) — never answer from memory; OR the task is LLM-shaped with provider unstated (agent/MCP/tool-definition/multi-agent/RAG/LLM-judge/computer-use; generate/summarize/extract/classify/rewrite/converse over NL; debugging refusals/cutoffs/streaming/tool-calls/tokens).\nSKIP only when another provider is being worked on (overrides all triggers): OpenAI/GPT/Gemini/Llama/Mistral/Cohere/Ollama named in the query; OR `grep -rE 'openai|langchain_openai|google.generativeai|genai|mistralai|cohere|ollama'` over the project hits (run this grep FIRST if no provider named — don't Read the file).\n- run: Launch and drive this project's app to see a change working. Use when asked to run, start, or screenshot the app, or to confirm a change works in the real app (not just tests). First looks for a project skill that already covers launching the app; otherwise falls back to built-in patterns per project type (CLI, server, TUI, Electron, browser-driven, library).\n- init: Initialize a new CLAUDE.md file with codebase documentation\n- review: Review a pull request\n- security-review: Complete a security review of the pending changes on the current branch","skillCount":13,"isInitial":true,"names":["update-config","keybindings-help","verify","code-review","simplify","fewer-permission-prompts","loop","schedule","claude-api","run","init","review","security-review"]},"type":"attachment","uuid":"50d73c36-6b5e-44a8-94ff-221ec48b7913","timestamp":"2026-06-14T05:32:07.029Z","userType":"external","entrypoint":"cli","cwd":"/boot/config/plugins/varaverk","sessionId":"685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f","version":"2.1.177","gitBranch":"HEAD","slug":"mutable-sniffing-naur"} {"parentUuid":"50d73c36-6b5e-44a8-94ff-221ec48b7913","isSidechain":true,"agentId":"ab976a3a2d54c1360","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01KJvtia72nVTMza1m6EtBA8","type":"message","role":"assistant","content":[{"type":"text","text":"I'll audit each bash script against its header documentation. Let me start by reading all the scripts."}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":2024,"cache_read_input_tokens":5697,"cache_creation":{"ephemeral_5m_input_tokens":2024,"ephemeral_1h_input_tokens":0},"output_tokens":4,"service_tier":"standard","inference_geo":"not_available"},"diagnostics":null},"requestId":"req_011Cc2c3TfU89TfBpzoqpshv","attributionAgent":"Explore","type":"assistant","uuid":"0af94090-015c-480d-a4ae-60c04ad7180b","timestamp":"2026-06-14T05:32:11.640Z","userType":"external","entrypoint":"cli","cwd":"/boot/config/plugins/varaverk","sessionId":"685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f","version":"2.1.177","gitBranch":"HEAD","slug":"mutable-sniffing-naur"} {"parentUuid":"0af94090-015c-480d-a4ae-60c04ad7180b","isSidechain":true,"agentId":"ab976a3a2d54c1360","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01KJvtia72nVTMza1m6EtBA8","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_011jFiq3uJ9e54rnB8WebUhX","name":"Read","input":{"file_path":"/boot/config/plugins/varaverk/Fallback/fallback.sh"},"caller":{"type":"direct"}}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":2024,"cache_read_input_tokens":5697,"cache_creation":{"ephemeral_5m_input_tokens":2024,"ephemeral_1h_input_tokens":0},"output_tokens":4,"service_tier":"standard","inference_geo":"not_available"},"diagnostics":null},"requestId":"req_011Cc2c3TfU89TfBpzoqpshv","attributionAgent":"Explore","type":"assistant","uuid":"92eb9a40-7b8b-4827-86f3-a145f2e85e0c","timestamp":"2026-06-14T05:32:12.076Z","userType":"external","entrypoint":"cli","cwd":"/boot/config/plugins/varaverk","sessionId":"685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f","version":"2.1.177","gitBranch":"HEAD","slug":"mutable-sniffing-naur"} {"parentUuid":"92eb9a40-7b8b-4827-86f3-a145f2e85e0c","isSidechain":true,"agentId":"ab976a3a2d54c1360","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01KJvtia72nVTMza1m6EtBA8","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01LQznyebrPzLdxyfkVoJ1Gc","name":"Read","input":{"file_path":"/boot/config/plugins/varaverk/Fallback/fallback_test.sh"},"caller":{"type":"direct"}}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":2024,"cache_read_input_tokens":5697,"cache_creation":{"ephemeral_5m_input_tokens":2024,"ephemeral_1h_input_tokens":0},"output_tokens":4,"service_tier":"standard","inference_geo":"not_available"},"diagnostics":null},"requestId":"req_011Cc2c3TfU89TfBpzoqpshv","attributionAgent":"Explore","type":"assistant","uuid":"d221fd15-e2a3-42e9-b60f-115af3849dfa","timestamp":"2026-06-14T05:32:12.141Z","userType":"external","entrypoint":"cli","cwd":"/boot/config/plugins/varaverk","sessionId":"685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f","version":"2.1.177","gitBranch":"HEAD","slug":"mutable-sniffing-naur"} {"parentUuid":"d221fd15-e2a3-42e9-b60f-115af3849dfa","isSidechain":true,"promptId":"4ede2c2c-ae62-435a-a0fb-4f8d63fa41ab","agentId":"ab976a3a2d54c1360","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01LQznyebrPzLdxyfkVoJ1Gc","type":"tool_result","content":"1\t#!/bin/bash\n2\t# ==============================================================================================\n3\t# ================================= Fallback Test ==============================================\n4\t# ==============================================================================================\n5\t#\n6\t# PURPOSE\n7\t# ─────────────────────────────────────────────────────────────────────────────\n8\t# Controlled simulation of the fallback lifecycle. Validates the entire sequence\n9\t# without waiting for a real outage. Contains no fallback logic — exercises the\n10\t# real fallback.sh via an iptables DROP rule on the remote Tailscale IP.\n11\t#\n12\t# Run during a maintenance window. Users will experience a brief service\n13\t# interruption. Use --dry-run to walk through all phases without real changes.\n14\t#\n15\t# ==============================================================================================\n16\t# OPERATIONAL MODEL\n17\t# ==============================================================================================\n18\t#\n19\t# Phase 1 — Pre-flight Both servers reachable, Docker daemons healthy,\n20\t# version parity, fallback.sh exists, state NORMAL\n21\t# Phase 2 — Block Remote iptables DROP rule added — remote appears unreachable\n22\t# Phase 3 — Fallback Detection Wait FALLBACK_TEST_BLOCK_WAIT for fallback.sh to\n23\t# detect the outage and enter FALLBACK state\n24\t# Phase 4 — Container Start Verify Tier 1 containers started locally\n25\t# Phase 5 — Restore iptables rule removed — remote reachable again\n26\t# Phase 6 — Handback Wait FALLBACK_TEST_HANDBACK_WAIT for fallback.sh to\n27\t# complete full handback and return to NORMAL\n28\t# Phase 7 — Container Handback Verify Tier 1 containers stopped locally\n29\t# Report — Full pass/fail per phase with timing\n30\t#\n31\t# ==============================================================================================\n32\t# DESIGN PRINCIPLES\n33\t# ==============================================================================================\n34\t#\n35\t# Test Harness Only\n36\t# Contains zero fallback logic. All fallback is exercised through fallback.sh.\n37\t# Any change to fallback.sh is automatically reflected in the test result.\n38\t#\n39\t# ==============================================================================================\n40\t# OPERATIONAL SAFEGUARDS\n41\t# ==============================================================================================\n42\t#\n43\t# iptables Safety Trap\n44\t# The DROP rule is removed via trap on ANY exit — normal completion, crash, error,\n45\t# ctrl-c. Remote connectivity is always restored regardless of test outcome.\n46\t# You cannot accidentally leave the remote permanently blocked.\n47\t#\n48\t# FALLBACK_ENABLED Gate\n49\t# Aborts if FALLBACK_ENABLED=false. Testing a disabled fallback system is\n50\t# misleading and potentially destructive.\n51\t#\n52\t# State Must Be NORMAL\n53\t# Pre-flight fails if state is not NORMAL. Running a test during an actual\n54\t# fallback event would interfere with the real event.\n55\t#\n56\t# Version Parity Check\n57\t# Pre-flight verifies unRAID version parity before any iptables rules are\n58\t# added. A mismatch makes the test result unreliable.\n59\t#\n60\t# Remote Docker Daemon Check\n61\t# Pre-flight confirms remote Docker daemon is responsive before Phase 2.\n62\t#\n63\t# Lock Acquisition\n64\t# acquire_lock() prevents concurrent test runs. Running two tests simultaneously\n65\t# would produce conflicting iptables rules and unreliable results.\n66\t#\n67\t# Host Detection\n68\t# detect_hosts() resolves MY_ID / REMOTE_ID from master.conf at startup.\n69\t# Exits if the host cannot be identified — prevents testing on an unknown machine.\n70\t#\n71\t# ==============================================================================================\n72\t# CONFIGURATION\n73\t# ==============================================================================================\n74\t#\n75\t# master.conf\n76\t#\n77\t# FALLBACK_TEST_BLOCK_WAIT\n78\t# Seconds to wait in Phase 3 for fallback.sh to detect the outage.\n79\t# Must be > FALLBACK_CHECK_INTERVAL + buffer. At 30s interval: use ≥60s.\n80\t# (default: 60)\n81\t#\n82\t# FALLBACK_TEST_HANDBACK_WAIT\n83\t# Seconds to wait in Phase 6 for fallback.sh to complete handback.\n84\t# Must cover: FALLBACK_HANDBACK_STRIKES × FALLBACK_CHECK_INTERVAL + rsync\n85\t# duration + container start time. At 3 strikes × 30s + ~2min rsync +\n86\t# ~1min container start: use ≥240s. (default: 300)\n87\t#\n88\t# ==============================================================================================\n89\t# RUNTIME MODES\n90\t# ==============================================================================================\n91\t#\n92\t# fallback_test.sh --dry-run\n93\t# Walk through all 7 phases with output but no iptables changes and no\n94\t# container starts/stops. ALWAYS run this before a live test.\n95\t#\n96\t# fallback_test.sh\n97\t# Full live test — real iptables DROP rule, real container lifecycle.\n98\t# Users will experience a brief service interruption. Run during a\n99\t# maintenance window.\n100\t#\n101\t# fallback_test.sh --status\n102\t# Show current fallback state and test timing configuration. No test run.\n103\t#\n104\t# fallback_test.sh --log\n105\t# Verbose output on every check in every phase.\n106\t#\n107\t# ==============================================================================================\n108\t\n109\tSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n110\t\n111\tsource \"$SCRIPT_DIR/../load_config.sh\"\n112\t\n113\tparse_args \"$@\"\n114\t\n115\tFALLBACK_SCRIPT=\"$SCRIPT_DIR/fallback.sh\"\n116\tDOCKER_TIMEOUT=15\n117\t\n118\t# ==============================================================================================\n119\t# ── SAFETY TRAP — always remove iptables rule on exit ─────────────────────────────────────────\n120\t# ==============================================================================================\n121\t# Fires on normal exit, error exit, ctrl-c, and script crashes.\n122\t# Remote connectivity is ALWAYS restored regardless of test outcome.\n123\t\n124\tIPTABLES_RULE_ACTIVE=false\n125\t\n126\tcleanup() {\n127\t if [[ \"$IPTABLES_RULE_ACTIVE\" == true ]]; then\n128\t echo \"\"\n129\t warn \"$ICON_SHIELD Cleanup — removing iptables block on $REMOTE_SERVER...\"\n130\t if [[ \"$DRY_RUN\" == false ]]; then\n131\t iptables -D OUTPUT -d \"$REMOTE_SERVER\" -j DROP 2>/dev/null\n132\t IPTABLES_RULE_ACTIVE=false\n133\t warn \"iptables rule removed — remote connectivity restored\"\n134\t else\n135\t warn \"DRY RUN — would remove iptables rule\"\n136\t fi\n137\t fi\n138\t}\n139\t\n140\ttrap cleanup EXIT\n141\t\n142\t# ==============================================================================================\n143\t# ━━━ Setup ━━━\n144\t# ==============================================================================================\n145\t\n146\tif [[ \"$EUID\" -ne 0 ]]; then\n147\t error \"Must be run as root\"\n148\t exit 1\n149\tfi\n150\t\n151\t# FALLBACK_ENABLED gate — no point testing if fallback is disabled\n152\tif [[ \"${FALLBACK_ENABLED:-false}\" == false ]]; then\n153\t warn \"FALLBACK_ENABLED=false — fallback test aborted\"\n154\t warn \"Enable fallback in master.conf before running this test\"\n155\t exit 0\n156\tfi\n157\t\n158\tacquire_lock # strict single instance — modifies iptables and containers\n159\t\n160\tif ! command -v docker &>/dev/null; then\n161\t error \"Docker command not found\"\n162\t exit 1\n163\tfi\n164\t\n165\tdetect_hosts\n166\tresolve_remote_ip\n167\t\n168\t# Validate commands used by this script\n169\tplatform_require_cmd \\\n170\t \"$(which iptables 2>/dev/null || echo /sbin/iptables)\" \\\n171\t \"--version\" \"iptables\" \\\n172\t \"iptables\" || { error \"iptables not found — required for connectivity simulation\"; exit 1; }\n173\t\n174\t\n175\tif [[ ! -f \"$FALLBACK_SCRIPT\" ]]; then\n176\t error \"fallback.sh not found at $FALLBACK_SCRIPT\"\n177\t exit 1\n178\tfi\n179\tlog \"fallback.sh found at $FALLBACK_SCRIPT\"\n180\tlog \"$ICON_GEAR Config: remote=${REMOTE_SERVER_NAME} (${REMOTE_SERVER}) fallback-script=${FALLBACK_SCRIPT}\"\n181\t\n182\t[[ \"$DRY_RUN\" == true ]] && warn \"DRY RUN — no iptables rules or container changes will be made\"\n183\t\n184\t# ==============================================================================================\n185\t# ━━━ Status ━━━\n186\t# ==============================================================================================\n187\tif [[ \"$SHOW_STATUS\" == true ]]; then\n188\t local_ver=$(platform_get_os_version 2>/dev/null || echo \"unknown\")\n189\t\n190\t echo \"\"\n191\t echo \"━━━━━ $ICON_SUMMARY STATUS ━━━━━\"\n192\t echo \"$ICON_HOST My ID: $MY_ID ($LOCAL_SERVER_NAME)\"\n193\t echo \"$ICON_HOST Remote ID: $REMOTE_ID ($REMOTE_SERVER_NAME — $REMOTE_SERVER)\"\n194\t echo \"$ICON_GEAR OS ver: $local_ver\"\n195\t echo \"$ICON_FALLBACK Block wait: ${FALLBACK_TEST_BLOCK_WAIT}s\"\n196\t echo \"$ICON_FALLBACK Handback wait: ${FALLBACK_TEST_HANDBACK_WAIT}s\"\n197\t echo \"$ICON_FALLBACK Check interval: ${FALLBACK_CHECK_INTERVAL}s\"\n198\t echo \"$ICON_FALLBACK Handback strikes: ${FALLBACK_HANDBACK_STRIKES}\"\n199\t echo \"$ICON_GEAR Dry Run: $DRY_RUN\"\n200\t\n201\t if [[ -f \"$FALLBACK_STATE_FILE\" ]]; then\n202\t CURRENT_STATE=$(grep \"^state=\" \"$FALLBACK_STATE_FILE\" 2>/dev/null | cut -d= -f2)\n203\t echo \"$ICON_FALLBACK Current state: ${CURRENT_STATE:-unknown}\"\n204\t else\n205\t echo \"$ICON_FALLBACK Current state: no state file\"\n206\t fi\n207\t\n208\t # Show Tier 1 containers for this host\n209\t TIER1_VAR=\"FALLBACK_${MY_ID}_COVERS_${REMOTE_ID}_TIER1\"\n210\t eval \"TIER1_CONTAINERS=(\\\"\\${${TIER1_VAR}[@]:-}\\\")\"\n211\t echo \"$ICON_CONTAINERS Tier 1 to test: ${TIER1_CONTAINERS[*]:-none configured}\"\n212\t echo \"━━━━━━━━━━━━━━━━━━━━━━━\"\n213\t exit 0\n214\tfi\n215\t\n216\t# ==============================================================================================\n217\t# ── PHASE TRACKING ────────────────────────────────────────────────────────────────────────────\n218\t# ==============================================================================================\n219\tPHASES_PASS=()\n220\tPHASES_FAIL=()\n221\tTOTAL_START=$(date +%s)\n222\t\n223\tphase_pass() { PHASES_PASS+=(\"$1\"); warn \"$ICON_DONE Phase: $1 — PASSED ✅\"; }\n224\tphase_fail() { PHASES_FAIL+=(\"$1\"); error \"Phase: $1 — FAILED ❌\"; }\n225\t\n226\t# Get Tier 1 containers for this server's fallback responsibility\n227\tTIER1_VAR=\"FALLBACK_${MY_ID}_COVERS_${REMOTE_ID}_TIER1\"\n228\teval \"TIER1_CONTAINERS=(\\\"\\${${TIER1_VAR}[@]:-}\\\")\"\n229\t\n230\t# ==============================================================================================\n231\t# ━━━ Phase 1 — Pre-flight ━━━\n232\t# ==============================================================================================\n233\techo \"\"\n234\techo \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n235\techo \" $ICON_SHIELD FALLBACK TEST — $(date '+%Y-%m-%d %H:%M:%S')\"\n236\techo \" $ICON_HOST $MY_ID ($LOCAL_SERVER_NAME) → $REMOTE_ID ($REMOTE_SERVER_NAME)\"\n237\techo \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n238\techo \"\"\n239\techo \"━━━ $ICON_SHIELD Phase 1 — Pre-flight ━━━\"\n240\t\n241\t# Remote reachable\n242\tif ping_remote; then\n243\t log \"$REMOTE_SERVER_NAME is reachable\"\n244\telse\n245\t error \"$REMOTE_SERVER_NAME is not reachable — cannot run test\"\n246\t phase_fail \"Pre-flight\"\n247\t exit 1\n248\tfi\n249\t\n250\t# Internet reachable\n251\tif ping_internet; then\n252\t log \"Internet is reachable\"\n253\telse\n254\t error \"No internet connectivity — cannot run test\"\n255\t phase_fail \"Pre-flight\"\n256\t exit 1\n257\tfi\n258\t\n259\t# Version parity — test may produce misleading results on mismatch\n260\tif ! check_os_version_parity; then\n261\t error \"unRAID version mismatch — test aborted to prevent misleading results\"\n262\t phase_fail \"Pre-flight\"\n263\t exit 1\n264\tfi\n265\t\n266\t# Remote Docker daemon — must be responsive before test manipulates containers\n267\tif ! check_remote_docker_daemon; then\n268\t error \"Remote Docker daemon not responsive — cannot run test\"\n269\t phase_fail \"Pre-flight\"\n270\t exit 1\n271\tfi\n272\t\n273\t# Fallback state must be NORMAL before test\n274\tif [[ -f \"$FALLBACK_STATE_FILE\" ]]; then\n275\t CURRENT_STATE=$(grep \"^state=\" \"$FALLBACK_STATE_FILE\" 2>/dev/null | cut -d= -f2)\n276\t if [[ \"$CURRENT_STATE\" != \"NORMAL\" ]]; then\n277\t error \"Fallback state is $CURRENT_STATE — must be NORMAL before running test\"\n278\t phase_fail \"Pre-flight\"\n279\t exit 1\n280\t fi\n281\t log \"Fallback state is NORMAL\"\n282\telse\n283\t warn \"No state file found — assuming NORMAL (first run)\"\n284\tfi\n285\t\n286\t# Tier 1 containers configured\n287\tif [[ ${#TIER1_CONTAINERS[@]} -eq 0 ]]; then\n288\t error \"No Tier 1 containers configured for $MY_ID → $REMOTE_ID\"\n289\t error \"Check FALLBACK_${MY_ID}_COVERS_${REMOTE_ID}_TIER1 in host*.conf\"\n290\t phase_fail \"Pre-flight\"\n291\t exit 1\n292\tfi\n293\tlog \"Tier 1 containers: ${TIER1_CONTAINERS[*]}\"\n294\t\n295\tphase_pass \"Pre-flight\"\n296\t\n297\t# ==============================================================================================\n298\t# ━━━ Phase 2 — Block Remote Connectivity ━━━\n299\t# ==============================================================================================\n300\techo \"\"\n301\techo \"━━━ $ICON_PING Phase 2 — Block Remote Connectivity ━━━\"\n302\twarn \"Adding iptables rule — dropping all traffic to $REMOTE_SERVER ($REMOTE_SERVER_NAME)\"\n303\t\n304\tif [[ \"$DRY_RUN\" == false ]]; then\n305\t iptables -I OUTPUT -d \"$REMOTE_SERVER\" -j DROP\n306\t IPTABLES_RULE_ACTIVE=true\n307\t\n308\t # Verify block is working\n309\t sleep 2\n310\t if ! ping -c1 -W2 \"$REMOTE_SERVER\" &>/dev/null; then\n311\t log \"Connectivity block confirmed — ping to remote fails as expected\"\n312\t phase_pass \"Block Remote\"\n313\t else\n314\t error \"iptables rule did not block connectivity — ping still succeeds\"\n315\t phase_fail \"Block Remote\"\n316\t exit 1\n317\t fi\n318\telse\n319\t warn \"DRY RUN — would block $REMOTE_SERVER with iptables DROP rule\"\n320\t phase_pass \"Block Remote\"\n321\tfi\n322\t\n323\t# ==============================================================================================\n324\t# ━━━ Phase 3 — Fallback Detection ━━━\n325\t# ==============================================================================================\n326\techo \"\"\n327\techo \"━━━ $ICON_FALLBACK Phase 3 — Fallback Detection ━━━\"\n328\twarn \"Waiting ${FALLBACK_TEST_BLOCK_WAIT}s for fallback.sh to detect outage...\"\n329\tlog \"fallback.sh check interval: ${FALLBACK_CHECK_INTERVAL}s\"\n330\t\n331\tif [[ \"$DRY_RUN\" == false ]]; then\n332\t sleep \"$FALLBACK_TEST_BLOCK_WAIT\"\n333\t\n334\t if [[ -f \"$FALLBACK_STATE_FILE\" ]]; then\n335\t NEW_STATE=$(grep \"^state=\" \"$FALLBACK_STATE_FILE\" 2>/dev/null | cut -d= -f2)\n336\t if [[ \"$NEW_STATE\" == \"FALLBACK\" ]]; then\n337\t log \"State changed to FALLBACK — outage detected correctly ✅\"\n338\t phase_pass \"Fallback Detection\"\n339\t else\n340\t error \"State is $NEW_STATE — expected FALLBACK after ${FALLBACK_TEST_BLOCK_WAIT}s\"\n341\t warn \"Is fallback.sh running? Check User Scripts plugin\"\n342\t phase_fail \"Fallback Detection\"\n343\t fi\n344\t else\n345\t error \"No state file found after wait — fallback.sh may not be running\"\n346\t phase_fail \"Fallback Detection\"\n347\t fi\n348\telse\n349\t warn \"DRY RUN — would wait ${FALLBACK_TEST_BLOCK_WAIT}s then check for FALLBACK state\"\n350\t phase_pass \"Fallback Detection\"\n351\tfi\n352\t\n353\t# ==============================================================================================\n354\t# ━━━ Phase 4 — Container Start Verification ━━━\n355\t# ==============================================================================================\n356\techo \"\"\n357\techo \"━━━ $ICON_CONTAINERS Phase 4 — Tier 1 Containers Started Locally ━━━\"\n358\tlog \"Checking Tier 1 containers: ${TIER1_CONTAINERS[*]}\"\n359\t\n360\tif [[ \"$DRY_RUN\" == false ]]; then\n361\t CONTAINERS_OK=true\n362\t for container in \"${TIER1_CONTAINERS[@]}\"; do\n363\t [[ -z \"$container\" ]] && continue\n364\t STATUS=$(timeout \"$DOCKER_TIMEOUT\" docker inspect -f '{{.State.Running}}' \\\n365\t \"$container\" 2>/dev/null)\n366\t if [[ \"$STATUS\" == \"true\" ]]; then\n367\t log \"$ICON_RUNNING $container is running locally ✅\"\n368\t else\n369\t error \"$ICON_NOT_RUNNING $container is NOT running locally\"\n370\t CONTAINERS_OK=false\n371\t fi\n372\t done\n373\t\n374\t if [[ \"$CONTAINERS_OK\" == true ]]; then\n375\t phase_pass \"Container Start\"\n376\t else\n377\t phase_fail \"Container Start\"\n378\t fi\n379\telse\n380\t warn \"DRY RUN — would verify these Tier 1 containers started: ${TIER1_CONTAINERS[*]}\"\n381\t phase_pass \"Container Start\"\n382\tfi\n383\t\n384\t# ==============================================================================================\n385\t# ━━━ Phase 5 — Restore Remote Connectivity ━━━\n386\t# ==============================================================================================\n387\techo \"\"\n388\techo \"━━━ $ICON_PING Phase 5 — Restore Remote Connectivity ━━━\"\n389\twarn \"Removing iptables block — $REMOTE_SERVER_NAME becomes reachable again\"\n390\t\n391\tif [[ \"$DRY_RUN\" == false ]]; then\n392\t iptables -D OUTPUT -d \"$REMOTE_SERVER\" -j DROP 2>/dev/null\n393\t IPTABLES_RULE_ACTIVE=false\n394\t\n395\t sleep 3\n396\t if ping_remote; then\n397\t log \"$REMOTE_SERVER_NAME is reachable again ✅\"\n398\t phase_pass \"Restore Connectivity\"\n399\t else\n400\t error \"$REMOTE_SERVER_NAME still unreachable after removing iptables rule\"\n401\t phase_fail \"Restore Connectivity\"\n402\t fi\n403\telse\n404\t warn \"DRY RUN — would remove iptables rule\"\n405\t phase_pass \"Restore Connectivity\"\n406\tfi\n407\t\n408\t# ==============================================================================================\n409\t# ━━━ Phase 6 — Handback ━━━\n410\t# ==============================================================================================\n411\techo \"\"\n412\techo \"━━━ $ICON_FALLBACK Phase 6 — Handback ━━━\"\n413\twarn \"Waiting ${FALLBACK_TEST_HANDBACK_WAIT}s for fallback.sh to complete handback...\"\n414\tlog \"Requires $FALLBACK_HANDBACK_STRIKES consecutive checks at ${FALLBACK_CHECK_INTERVAL}s\"\n415\tlog \"Minimum handback time: $(( FALLBACK_HANDBACK_STRIKES * FALLBACK_CHECK_INTERVAL ))s\"\n416\t\n417\tif [[ \"$DRY_RUN\" == false ]]; then\n418\t sleep \"$FALLBACK_TEST_HANDBACK_WAIT\"\n419\t\n420\t if [[ -f \"$FALLBACK_STATE_FILE\" ]]; then\n421\t FINAL_STATE=$(grep \"^state=\" \"$FALLBACK_STATE_FILE\" 2>/dev/null | cut -d= -f2)\n422\t if [[ \"$FINAL_STATE\" == \"NORMAL\" ]]; then\n423\t log \"State returned to NORMAL — handback completed ✅\"\n424\t phase_pass \"Handback\"\n425\t else\n426\t error \"State is $FINAL_STATE — expected NORMAL after ${FALLBACK_TEST_HANDBACK_WAIT}s\"\n427\t warn \"Handback may still be in progress — check fallback.sh output\"\n428\t phase_fail \"Handback\"\n429\t fi\n430\t else\n431\t error \"No state file found\"\n432\t phase_fail \"Handback\"\n433\t fi\n434\telse\n435\t warn \"DRY RUN — would wait ${FALLBACK_TEST_HANDBACK_WAIT}s then verify NORMAL state\"\n436\t phase_pass \"Handback\"\n437\tfi\n438\t\n439\t# ==============================================================================================\n440\t# ━━━ Phase 7 — Container Handback Verification ━━━\n441\t# ==============================================================================================\n442\techo \"\"\n443\techo \"━━━ $ICON_CONTAINERS Phase 7 — Tier 1 Containers Stopped Locally ━━━\"\n444\tlog \"Verifying Tier 1 containers returned to $REMOTE_SERVER_NAME\"\n445\t\n446\tif [[ \"$DRY_RUN\" == false ]]; then\n447\t HANDBACK_OK=true\n448\t for container in \"${TIER1_CONTAINERS[@]}\"; do\n449\t [[ -z \"$container\" ]] && continue\n450\t STATUS=$(timeout \"$DOCKER_TIMEOUT\" docker inspect -f '{{.State.Running}}' \\\n451\t \"$container\" 2>/dev/null)\n452\t if [[ \"$STATUS\" != \"true\" ]]; then\n453\t log \"$ICON_NOT_RUNNING $container stopped locally — handed back ✅\"\n454\t else\n455\t error \"$ICON_RUNNING $container still running locally — handback may have failed\"\n456\t HANDBACK_OK=false\n457\t fi\n458\t done\n459\t\n460\t if [[ \"$HANDBACK_OK\" == true ]]; then\n461\t phase_pass \"Container Handback\"\n462\t else\n463\t phase_fail \"Container Handback\"\n464\t fi\n465\telse\n466\t warn \"DRY RUN — would verify Tier 1 containers stopped locally after handback\"\n467\t phase_pass \"Container Handback\"\n468\tfi\n469\t\n470\tTOTAL_END=$(date +%s)\n471\t\n472\t# ==============================================================================================\n473\t# ━━━ Test Report ━━━\n474\t# ==============================================================================================\n475\techo \"\"\n476\techo \"━━━━━ $ICON_SUMMARY FALLBACK TEST REPORT ━━━━━\"\n477\techo \"$ICON_HOST My ID: $MY_ID ($LOCAL_SERVER_NAME)\"\n478\techo \"$ICON_HOST Remote: $REMOTE_ID ($REMOTE_SERVER_NAME)\"\n479\techo \"$ICON_TIME Duration: $(format_duration $((TOTAL_END - TOTAL_START)))\"\n480\techo \"\"\n481\techo \" Phase Results:\"\n482\tfor phase in \"${PHASES_PASS[@]}\"; do\n483\t echo \" $ICON_SUCCESS $phase\"\n484\tdone\n485\tfor phase in \"${PHASES_FAIL[@]}\"; do\n486\t echo \" $ICON_ERROR $phase\"\n487\tdone\n488\techo \"\"\n489\t\n490\tPASS_COUNT=${#PHASES_PASS[@]}\n491\tFAIL_COUNT=${#PHASES_FAIL[@]}\n492\tTOTAL_PHASES=$(( PASS_COUNT + FAIL_COUNT ))\n493\t\n494\tif [[ \"$DRY_RUN\" == true ]]; then\n495\t warn \"DRY RUN — no changes made\"\n496\telif [[ \"$FAIL_COUNT\" -eq 0 ]]; then\n497\t warn \"$ICON_DONE ALL $TOTAL_PHASES PHASES PASSED\"\n498\t notify \"Fallback test PASSED on $(hostname) — all $TOTAL_PHASES phases completed\" \\\n499\t \"Fallback Test\" \"normal\"\n500\telse\n501\t error \"$FAIL_COUNT/$TOTAL_PHASES PHASES FAILED\"\n502\t notify \"Fallback test FAILED on $(hostname) — $FAIL_COUNT/$TOTAL_PHASES phases failed: ${PHASES_FAIL[*]}\" \\\n503\t \"Fallback Test\" \"warning\"\n504\tfi\n505\techo \"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\"\n506\t\n507\t[[ \"$FAIL_COUNT\" -gt 0 ]] && exit 1\n508\texit 0"}]},"uuid":"c3eb9676-c02a-44e1-8c9b-252e3f519d57","timestamp":"2026-06-14T05:32:12.151Z","sourceToolAssistantUUID":"d221fd15-e2a3-42e9-b60f-115af3849dfa","userType":"external","entrypoint":"cli","cwd":"/boot/config/plugins/varaverk","sessionId":"685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f","version":"2.1.177","gitBranch":"HEAD","slug":"mutable-sniffing-naur"} {"parentUuid":"c3eb9676-c02a-44e1-8c9b-252e3f519d57","isSidechain":true,"agentId":"ab976a3a2d54c1360","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01KJvtia72nVTMza1m6EtBA8","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01LW56UYpUBLR69BiaAZ3imt","name":"Read","input":{"file_path":"/boot/config/plugins/varaverk/Kernel/decision_engine.sh"},"caller":{"type":"direct"}}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":2024,"cache_read_input_tokens":5697,"cache_creation":{"ephemeral_5m_input_tokens":2024,"ephemeral_1h_input_tokens":0},"output_tokens":4,"service_tier":"standard","inference_geo":"not_available"},"diagnostics":null},"requestId":"req_011Cc2c3TfU89TfBpzoqpshv","attributionAgent":"Explore","type":"assistant","uuid":"d5933a6d-f045-4ed7-97ab-4897475f465a","timestamp":"2026-06-14T05:32:12.185Z","userType":"external","entrypoint":"cli","cwd":"/boot/config/plugins/varaverk","sessionId":"685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f","version":"2.1.177","gitBranch":"HEAD","slug":"mutable-sniffing-naur"} {"parentUuid":"d5933a6d-f045-4ed7-97ab-4897475f465a","isSidechain":true,"promptId":"4ede2c2c-ae62-435a-a0fb-4f8d63fa41ab","agentId":"ab976a3a2d54c1360","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01LW56UYpUBLR69BiaAZ3imt","type":"tool_result","content":"1\t#!/bin/bash\n2\t# ==============================================================================================\n3\t# ================================= Decision Engine ============================================\n4\t# ==============================================================================================\n5\t#\n6\t# PURPOSE\n7\t# ─────────────────────────────────────────────────────────────────────────────\n8\t# Behavior-driven scoring kernel for media automation. Sourced by consumer\n9\t# scripts — not run directly. Evaluates candidates, applies weighted scoring,\n10\t# temporal decay, and deduplication, then returns a verdict.\n11\t#\n12\t# Does NOT download media, search indexers, or manage applications. Has no\n13\t# side effects — no file writes, no API calls, no deletions.\n14\t#\n15\t# Currently paired with: Media/playback_aware_lidarr_discovery.sh\n16\t#\n17\t# ==============================================================================================\n18\t# DESIGN PRINCIPLES\n19\t# ==============================================================================================\n20\t#\n21\t# Domain-Agnostic Core\n22\t# The engine itself has no knowledge of music vs TV vs movies. Consumers\n23\t# define thresholds, weights, and strictness profiles — the engine just\n24\t# scores and decides. This prevents cross-domain bias pollution: music\n25\t# strictness cannot contaminate TV intake logic.\n26\t#\n27\t# Lidarr consumers → highly selective, quality-first discovery\n28\t# Sonarr consumers → balanced, family-aware episodic intake\n29\t# Radarr consumers → broader flexibility with intelligent filtering\n30\t#\n31\t# Temporal Decay\n32\t# Old behavioral signals lose influence over time (one decay unit per 30\n33\t# days). Prevents permanent genre lock-in, historical bias accumulation,\n34\t# and dead-user score dominance.\n35\t#\n36\t# Consumer Owns the Decision\n37\t# The engine returns ACCEPT/REJECT/SCORE. What happens next is entirely\n38\t# the consumer's concern — the engine never acts on its own verdict.\n39\t#\n40\t# ==============================================================================================\n41\t# FUNCTIONS\n42\t# ==============================================================================================\n43\t#\n44\t# score_candidate user_score popularity_score recency_score quality_score\n45\t# Returns TOTAL_SCORE (integer sum). Consumer defines actual weight values.\n46\t#\n47\t# evaluate_threshold score minimum\n48\t# Returns 0 (pass) or 1 (fail). Used as: if evaluate_threshold ...\n49\t#\n50\t# apply_temporal_decay score age_days\n51\t# Returns adjusted score. Subtracts (age_days / 30), floor at 0.\n52\t#\n53\t# is_duplicate_candidate candidate history_file\n54\t# Returns 0 (duplicate found in history_file) or 1 (not found).\n55\t#\n56\t# make_decision score threshold\n57\t# Returns \"ACCEPT\" or \"REJECT\". Calls evaluate_threshold internally.\n58\t#\n59\t# ==============================================================================================\n60\t\n61\t# ── score_candidate ───────────────────────────────────────────────────────────\n62\tscore_candidate() {\n63\t\n64\t local user_score=\"${1:-0}\"\n65\t local popularity_score=\"${2:-0}\"\n66\t local recency_score=\"${3:-0}\"\n67\t local quality_score=\"${4:-0}\"\n68\t\n69\t TOTAL_SCORE=$(( \\\n70\t user_score + \\\n71\t popularity_score + \\\n72\t recency_score + \\\n73\t quality_score \\\n74\t ))\n75\t\n76\t echo \"$TOTAL_SCORE\"\n77\t}\n78\t\n79\t# ── evaluate_threshold ───────────────────────────────────────────────────────\n80\tevaluate_threshold() {\n81\t\n82\t local score=\"$1\"\n83\t local minimum=\"$2\"\n84\t\n85\t if (( score >= minimum )); then\n86\t return 0\n87\t fi\n88\t\n89\t return 1\n90\t}\n91\t\n92\t# ── apply_temporal_decay ─────────────────────────────────────────────────────\n93\tapply_temporal_decay() {\n94\t\n95\t local score=\"$1\"\n96\t local age_days=\"$2\"\n97\t\n98\t local decay=$(( age_days / 30 ))\n99\t\n100\t local adjusted=$(( score - decay ))\n101\t\n102\t (( adjusted < 0 )) && adjusted=0\n103\t\n104\t echo \"$adjusted\"\n105\t}\n106\t\n107\t# ── is_duplicate_candidate ───────────────────────────────────────────────────\n108\tis_duplicate_candidate() {\n109\t\n110\t local candidate=\"$1\"\n111\t local history_file=\"$2\"\n112\t\n113\t grep -qi \"^${candidate}$\" \"$history_file\" 2>/dev/null\n114\t}\n115\t\n116\t# ── make_decision ─────────────────────────────────────────────────────────────\n117\tmake_decision() {\n118\t\n119\t local score=\"$1\"\n120\t local threshold=\"$2\"\n121\t\n122\t if evaluate_threshold \"$score\" \"$threshold\"; then\n123\t echo \"ACCEPT\"\n124\t else\n125\t echo \"REJECT\"\n126\t fi\n127\t}"}]},"uuid":"fb734b7e-2616-40b0-8c05-69d41e760f87","timestamp":"2026-06-14T05:32:12.207Z","sourceToolAssistantUUID":"d5933a6d-f045-4ed7-97ab-4897475f465a","userType":"external","entrypoint":"cli","cwd":"/boot/config/plugins/varaverk","sessionId":"685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f","version":"2.1.177","gitBranch":"HEAD","slug":"mutable-sniffing-naur"} {"parentUuid":"fb734b7e-2616-40b0-8c05-69d41e760f87","isSidechain":true,"agentId":"ab976a3a2d54c1360","message":{"model":"claude-haiku-4-5-20251001","id":"msg_01KJvtia72nVTMza1m6EtBA8","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_0174FazUeyWtdGFACjXD6jbL","name":"Read","input":{"file_path":"/boot/config/plugins/varaverk/Rsync/rsync.sh"},"caller":{"type":"direct"}}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":2024,"cache_read_input_tokens":5697,"cache_creation":{"ephemeral_5m_input_tokens":2024,"ephemeral_1h_input_tokens":0},"output_tokens":4,"service_tier":"standard","inference_geo":"not_available"},"diagnostics":null},"requestId":"req_011Cc2c3TfU89TfBpzoqpshv","attributionAgent":"Explore","type":"assistant","uuid":"dac09318-f367-414d-aba2-516a01211130","timestamp":"2026-06-14T05:32:12.303Z","userType":"external","entrypoint":"cli","cwd":"/boot/config/plugins/varaverk","sessionId":"685e6c5b-62c1-40bd-9cfd-2c9f7e15c50f","version":"2.1.177","gitBranch":"HEAD","slug":"mutable-sniffing-naur"} {"parentUuid":"dac09318-f367-414d-aba2-516a01211130","isSidechain":true,"promptId":"4ede2c2c-ae62-435a-a0fb-4f8d63fa41ab","agentId":"ab976a3a2d54c1360","type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_0174FazUeyWtdGFACjXD6jbL","type":"tool_result","content":"1\t#!/bin/bash\n2\t# ==============================================================================================\n3\t# ============================= Rsync Core Script ==============================================\n4\t# ==============================================================================================\n5\t#\n6\t# PURPOSE\n7\t# ─────────────────────────────────────────────────────────────────────────────\n8\t# Core rsync engine for the two-server ecosystem. Called per share or per\n9\t# appdata profile by orchestrators (daily_sync_maintenance, weekly_sync_maintenance,\n10\t# critical_sync_maintenance) and directly for manual or scheduled dirty syncs.\n11\t#\n12\t# Profile is inferred from the directory basename (lowercased). Override with\n13\t# --profile=name for explicit selection. If no profile matches, global defaults\n14\t# from master.conf apply and no containers are stopped.\n15\t#\n16\t# After each sync, logs transfer data to bandwidth_monitor.sh for the weekly\n17\t# bandwidth report. Silent on success — only failures produce visible output.\n18\t#\n19\t# ==============================================================================================\n20\t# OPERATIONAL MODEL\n21\t# ==============================================================================================\n22\t#\n23\t# Profiles define per-share behavior:\n24\t# PROFILE_CRITICAL_CONTAINER_NAMES — containers stopped on both servers before sync\n25\t# PROFILE_DELAYED_CONTAINERS — containers with a delay before restart after sync\n26\t# PROFILE_CONTAINER_DELAY — seconds before delayed containers start\n27\t# PROFILE_RSYNC_OPTS — rsync flags (does not inherit DEFAULT_RSYNC_OPTS)\n28\t# PROFILE_BW_LIMIT — bandwidth limit in KB/s\n29\t# PROFILE_RETRY_COUNT — retry attempts on failure\n30\t# PROFILE_SLEEP — seconds between retry attempts\n31\t# PROFILE_EXCLUDE_DIRS — paths excluded from transfer\n32\t# PROFILE_REMOTE_RESTART_CONTAINERS — containers restarted on remote after dirty sync\n33\t# Was running → restart. Was stopped → leave stopped.\n34\t#\n35\t# Two-tier rsync enable/disable:\n36\t# Tier 1: RSYNC_ENABLED=false → all rsync stops immediately (checked by this script)\n37\t# Tier 2: per-orchestrator flag (DAILY_RSYNC_ENABLED etc.) → checked by caller\n38\t#\n39\t# Bandwidth logging: after each sync, logs profile/duration/status/bytes to\n40\t# bandwidth_monitor.sh --log-transfer. Bytes captured from rsync --stats via awk\n41\t# using version-stable field names.\n42\t#\n43\t# ==============================================================================================\n44\t# OPERATIONAL SAFEGUARDS\n45\t# ==============================================================================================\n46\t#\n47\t# Global Rsync Gate\n48\t# check_rsync_enabled() — RSYNC_ENABLED=false exits cleanly before any operation.\n49\t#\n50\t# Partnership Blocklist\n51\t# Refuses to sync if REMOTE_SERVER_NAME appears in the partnership blocklist.\n52\t# Written at offboard — prevents stale access after a partnership ends.\n53\t#\n54\t# Version Parity\n55\t# check_os_version_parity — refuses sync if servers on incompatible unRAID versions.\n56\t#\n57\t# Remote Health Pre-flights\n58\t# check_connectivity() — Tailscale IP reachable before any SSH\n59\t# check_remote_rootfs() — aborts if remote rootfs exceeds ROOTFS_WARN_PCT\n60\t# check_remote_share() — aborts if target directory missing or empty on remote\n61\t# check_remote_disks() — verifies all backing disks online on remote\n62\t#\n63\t# Drive Temperature Check\n64\t# check_local_disk_temps() — runs before any transfer. Exit 1 = skip this profile,\n65\t# exit 2 = abort all remaining syncs (CRITICAL temperature).\n66\t#\n67\t# Remote Docker Daemon Check\n68\t# check_remote_docker_daemon — verified before any container stop/start operations.\n69\t# If daemon unresponsive: container operations skipped, rsync proceeds without stopping.\n70\t#\n71\t# Per-Profile Concurrency Lock\n72\t# acquire_rsync_lock() — per-profile lock prevents parallel runs of the same profile.\n73\t# Global concurrent limit prevents too many simultaneous rsync processes.\n74\t#\n75\t# Notification Validated\n76\t# platform_require_cmd confirms the notify script is present before use.\n77\t#\n78\t# ==============================================================================================\n79\t# CONFIGURATION\n80\t# ==============================================================================================\n81\t#\n82\t# master.conf\n83\t#\n84\t# RSYNC_ENABLED\n85\t# Global on/off toggle for all rsync operations. (default: true)\n86\t#\n87\t# DEFAULT_RSYNC_OPTS\n88\t# Base rsync flags for unproiled shares. Note: --delete is intentionally absent —\n89\t# media shares spread files only, arr cleanup scripts own deletions. Profile-specific\n90\t# opts set --delete explicitly where needed.\n91\t#\n92\t# BW_LIMIT\n93\t# Default bandwidth cap in KB/s when no PROFILE_BW_LIMIT is set. (default: 0 = unlimited)\n94\t#\n95\t# RETRY_COUNT\n96\t# Default retry attempts on rsync failure. (default: 3)\n97\t#\n98\t# SLEEP\n99\t# Default seconds between retry attempts. (default: 60)\n100\t#\n101\t# ROOTFS_WARN_PCT\n102\t# Abort threshold for remote rootfs percentage full. (default: 75)\n103\t#\n104\t# PROFILES[\"profile_KEY\"]\n105\t# Profile definitions — one entry per PROFILE_* key per profile name.\n106\t# See OPERATIONAL MODEL above for all supported keys.\n107\t#\n108\t# BANDWIDTH_LOG / BANDWIDTH_WARN_GB\n109\t# Shared with bandwidth_monitor.sh — set once, used by both.\n110\t#\n111\t# ==============================================================================================\n112\t# RUNTIME MODES\n113\t# ==============================================================================================\n114\t#\n115\t# rsync.sh /path/to/share\n116\t# Sync the given path to the remote. Profile inferred from directory basename.\n117\t#\n118\t# rsync.sh /path/to/share --profile=name\n119\t# Sync with explicit profile override — bypasses basename inference.\n120\t#\n121\t# rsync.sh /path/to/share --dry-run\n122\t# Run all pre-flight checks and show what rsync would transfer. No transfer,\n123\t# no container stops.\n124\t#\n125\t# rsync.sh /path/to/share --status\n126\t# Show resolved profile, remote identity, and configuration. Then exit.\n127\t#\n128\t# rsync.sh /path/to/share --log\n129\t# Verbose output throughout — every decision logged.\n130\t#\n131\t# ==============================================================================================\n132\t\n133\tSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n134\t\n135\tsource \"$SCRIPT_DIR/../load_config.sh\"\n136\t\n137\t# ── Separate positional directory arg from flags ───────────────────────────────────────────────\n138\tDIRECTORY=\"\"\n139\tPROFILE_OVERRIDE=\"\"\n140\tRAW_ARGS=()\n141\t\n142\tfor ARG in \"$@\"; do\n143\t case \"$ARG\" in\n144\t --profile=*) PROFILE_OVERRIDE=\"${ARG#--profile=}\" ;;\n145\t --*|*=*) RAW_ARGS+=(\"$ARG\") ;;\n146\t *) [[ -z \"$DIRECTORY\" ]] && DIRECTORY=\"$ARG\" || RAW_ARGS+=(\"$ARG\") ;;\n147\t esac\n148\tdone\n149\t\n150\tparse_args \"${RAW_ARGS[@]}\"\n151\t\n152\t[[ -z \"$DIRECTORY\" ]] && {\n153\t error \"No directory specified\"\n154\t error \"Usage: rsync.sh