lets double check our onboarding setup, and first launch setup... we may test it out on the second server and start setting it up


- 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.
- 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".
- 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.
- 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.
- 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.
- 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.
- 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.
- 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").
- claude-api: Reference for the Claude API / Anthropic SDK — model ids, pricing, params, streaming, tool use, MCP, agents, caching, token counting, model migration.
TRIGGER — 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).
SKIP 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).
- 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).
- init: Initialize a new CLAUDE.md file with codebase documentation
- review: Review a pull request
- security-review: Complete a security review of the pending changes on the current branch

update-config keybindings-help verify code-review simplify fewer-permission-prompts loop schedule claude-api run init review security-review


Review onboarding and first launch setup


Let me explore the onboarding and first-launch setup files in the workspace.


Bash

find /boot/config/plugins/varaverk -type f | sort | head -80

List all files in the varaverk plugin directory


List top-level directory contents


CLAUDE.md
Configurations/
Deployment/
Docker_Essentials/
Fallback/
Kernel/
Manual.md
Media/
Monitors/
Notes_To-Do.md*
Old_Arch_Still_Works/
Orchestrators/
Partnership/
Plugin/
README-User_Script_Plug-in.md
README.md
Rsync/
State_Files/
System_Essentials/
Tools/
Transcodes/
Watchdogs/
common.sh*
data/
docker_folders.json
git_pull_execute.sh*
install.log
load_config.sh*
schedule.json
varaverk-2026.05.31-noarch-1.txz
varaverk.cfg
varaverk.cron


ls /boot/config/plugins/varaverk/Deployment/ && ls /boot/config/plugins/varaverk/Tools/

List Deployment and Tools directories


conf_templates/
conf_upgrade.sh*
deploy.sh*
Manual-Tools.md
README-Tools.md
bulk_permissions_repair.sh*
claude_startup.sh*
container_data_export.sh*
docker_prune_images.sh*
emby_database_repair.sh*
emby_to_lidarr_sync.sh*
emby_to_radarr_sync.sh*
emby_to_sonarr_sync.sh*
fallback_state_reset.sh*
ramdisk_stop.sh*
smart_long_test.sh*
watchdog_skip_list_manager.sh*
zfs_pool_scrub.sh*


ls /boot/config/plugins/varaverk/Plugin/ && ls /boot/config/plugins/varaverk/Plugin/unraid/ 2>/dev/null || true

List Plugin directory contents


Icons/
Manual-Plugin.md
README-Plugin.md
build.sh*
dev_install.sh*
dist/
unraid/
varaverk.plg
Partnership/
System_Essentials/
Tools/
Varaverk.page
VaraverkSettings.page
Watchdogs/
adapter.sh*
api/
css/
event/
icons/
include/
js/
pages/
run_job.sh*
user_script_plug-in.sh*


List conf_templates directory


1	#!/bin/bash
2	# ==============================================================================================
3	# deploy.sh — Sync dev working tree → prod runtime for Plugin PHP and conf files.
4	# ==============================================================================================
5	#
6	# Direct call (interactive):  ! bash Deployment/deploy.sh
7	#   Deploys both Plugin and Configurations with full output.
8	#
9	# Hook call (Claude Code PostToolUse):
10	#   Reads the tool JSON from stdin, deploys only the component that was actually changed.
11	#   Plugin/  → rsync --delete into prod Plugin/unraid/
12	#   Configurations/ → conf_upgrade.sh (adds new keys, preserves prod values)
13	#
14	# ==============================================================================================
15
16	DEV_ROOT="/mnt/cloud-storage/Important Shit/Git/Development/Varaverk"
17	PROD_ROOT="/boot/config/plugins/varaverk"
18	UPGRADE="$PROD_ROOT/Deployment/conf_upgrade.sh"
19	LOG="/tmp/vv_deploy.log"
20
21	# ── Deploy functions ──────────────────────────────────────────────────────────
22
23	deploy_plugin() {
24	    rsync -a --delete --exclude='.git' \
25	        "$DEV_ROOT/Plugin/unraid/" \
26	        "$PROD_ROOT/Plugin/unraid/"
27	}
28
29	deploy_conf() {
30	    for conf in master.conf host1.conf host2.conf; do
31	        tmpl="$DEV_ROOT/Configurations/$conf"
32	        target="$PROD_ROOT/Configurations/$conf"
33	        [[ -f "$tmpl" && -f "$target" ]] && \
34	            "$UPGRADE" --template "$tmpl" --target "$target"
35	    done
36	}
37
38	# ── Entry point ───────────────────────────────────────────────────────────────
39
40	# Read stdin (non-blocking — empty when called directly or via '!')
41	STDIN=$(cat 2>/dev/null || true)
42
43	# Extract file_path from hook JSON if present
44	FILE=$(python3 -c "
45	import json, sys
46	try:
47	    d = json.loads(sys.stdin.read())
48	    print(d.get('tool_input', {}).get('file_path', ''))
49	except:
50	    print('')
51	" <<< "$STDIN" 2>/dev/null || echo "")
52
53	if [[ -z "$FILE" ]]; then
54	    # Direct call (interactive or via !) — deploy everything with output
55	    echo "=== deploy: dev → prod ==="
56	    echo ""
57	    echo "── Plugin ──"
58	    deploy_plugin && echo "  rsync done"
59	    echo ""
60	    echo "── Configurations ──"
61	    deploy_conf
62	    echo ""
63	    echo "=== done ==="
64	    exit 0
65	fi
66
67	# Hook call — deploy only the affected component, log silently
68	{
69	    echo "[$(date '+%H:%M:%S')] hook: $FILE"
70
71	    if [[ "$FILE" == "$DEV_ROOT/Plugin/"* ]]; then
72	        deploy_plugin && echo "  plugin synced"
73	    fi
74
75	    if [[ "$FILE" == "$DEV_ROOT/Configurations/"* ]]; then
76	        deploy_conf && echo "  conf upgraded"
77	    fi
78	} >> "$LOG" 2>&1
79
80	exit 0
81

#!/bin/bash
# ==============================================================================================
# deploy.sh — Sync dev working tree → prod runtime for Plugin PHP and conf files.
# ==============================================================================================
#
# Direct call (interactive):  ! bash Deployment/deploy.sh
#   Deploys both Plugin and Configurations with full output.
#
# Hook call (Claude Code PostToolUse):
#   Reads the tool JSON from stdin, deploys only the component that was actually changed.
#   Plugin/  → rsync --delete into prod Plugin/unraid/
#   Configurations/ → conf_upgrade.sh (adds new keys, preserves prod values)
#
# ==============================================================================================

DEV_ROOT="/mnt/cloud-storage/Important Shit/Git/Development/Varaverk"
PROD_ROOT="/boot/config/plugins/varaverk"
UPGRADE="$PROD_ROOT/Deployment/conf_upgrade.sh"
LOG="/tmp/vv_deploy.log"

# ── Deploy functions ──────────────────────────────────────────────────────────

deploy_plugin() {
    rsync -a --delete --exclude='.git' \
        "$DEV_ROOT/Plugin/unraid/" \
        "$PROD_ROOT/Plugin/unraid/"
}

deploy_conf() {
    for conf in master.conf host1.conf host2.conf; do
        tmpl="$DEV_ROOT/Configurations/$conf"
        target="$PROD_ROOT/Configurations/$conf"
        [[ -f "$tmpl" && -f "$target" ]] && \
            "$UPGRADE" --template "$tmpl" --target "$target"
    done
}

# ── Entry point ───────────────────────────────────────────────────────────────

# Read stdin (non-blocking — empty when called directly or via '!')
STDIN=$(cat 2>/dev/null || true)

# Extract file_path from hook JSON if present
FILE=$(python3 -c "
import json, sys
try:
    d = json.loads(sys.stdin.read())
    print(d.get('tool_input', {}).get('file_path', ''))
except:
    print('')
" <<< "$STDIN" 2>/dev/null || echo "")

if [[ -z "$FILE" ]]; then
    # Direct call (interactive or via !) — deploy everything with output
    echo "=== deploy: dev → prod ==="
    echo ""
    echo "── Plugin ──"
    deploy_plugin && echo "  rsync done"
    echo ""
    echo "── Configurations ──"
    deploy_conf
    echo ""
    echo "=== done ==="
    exit 0
fi

# Hook call — deploy only the affected component, log silently
{
    echo "[$(date '+%H:%M:%S')] hook: $FILE"

    if [[ "$FILE" == "$DEV_ROOT/Plugin/"* ]]; then
        deploy_plugin && echo "  plugin synced"
    fi

    if [[ "$FILE" == "$DEV_ROOT/Configurations/"* ]]; then
        deploy_conf && echo "  conf upgraded"
    fi
} >> "$LOG" 2>&1

exit 0


host.conf.template
master.conf


1	#!/bin/bash
2	# ==============================================================================================
3	# ================================= Claude Code Startup ========================================
4	# ==============================================================================================
5	#
6	# PURPOSE
7	# ─────────────────────────────────────────────────────────────────────────────
8	# Restores Claude Code's persistent data after an Unraid reboot and launches Claude.
9	# Unraid's root filesystem lives in RAM — /root/.claude and /root/.local are wiped on
10	# every reboot. This script symlinks both directories back to persistent appdata storage
11	# before launching Claude, so memory, sessions, and settings survive across reboots.
12	#
13	# On first run with no existing persistent data, migrates from the current live locations:
14	#   /root/.claude              → PERSIST_DIR/.claude            (memory, sessions, settings)
15	#   /root/.local/share/claude  → PERSIST_DIR/local/share/claude (installed binaries)
16	# Subsequent runs skip the migration and only create the symlinks.
17	#
18	# Standalone script — no common.sh dependency. Safe to run directly from terminal
19	# or from array_started.sh.
20	#
21	# ==============================================================================================
22	# RUNTIME MODES
23	# ==============================================================================================
24	#
25	# claude_startup.sh
26	#     Set up persistent symlinks only — default, used by array_started.sh on boot.
27	#
28	# claude_startup.sh --launch
29	#     Set up persistent symlinks and launch Claude interactively.
30	#
31	# ==============================================================================================
32
33	PERSIST_DIR="/mnt/user/appdata/claude-code"
34	CLAUDE_DATA="$PERSIST_DIR/.claude"
35	CLAUDE_BIN="$PERSIST_DIR/local/share/claude"
36
37	LAUNCH=false
38	[[ "$1" == "--launch" ]] && LAUNCH=true
39
40	# Standalone — no common.sh dependency
41	_log()  { echo "  ✅  $*"; }
42	_warn() { echo "  ⚠   $*"; }
43	_err()  { echo "  ❌  $*" >&2; }
44
45	echo ""
46	echo "━━━ Claude Code Startup ━━━"
47	echo ""
48
49	# ── Array must be mounted ─────────────────────────────────────────────────────────────────────
50	if ! mountpoint -q /mnt/user 2>/dev/null; then
51	    _err "Array not mounted — /mnt/user not available"
52	    exit 1
53	fi
54
55	# ── Create persistent dirs ────────────────────────────────────────────────────────────────────
56	mkdir -p "$CLAUDE_DATA" "$CLAUDE_BIN"
57
58	# ── Migrate .claude on first run ──────────────────────────────────────────────────────────────
59	if [[ ! -L /root/.claude && -d /root/.claude ]]; then
60	    _warn "First run — migrating /root/.claude → $CLAUDE_DATA"
61	    cp -a /root/.claude/. "$CLAUDE_DATA/"
62	    rm -rf /root/.claude
63	    _log "Migrated .claude (memory, sessions, settings)"
64	elif [[ -z "$(ls -A "$CLAUDE_DATA" 2>/dev/null)" && -d /root/.claude ]]; then
65	    _warn "Persistent storage empty — copying current .claude data"
66	    cp -a /root/.claude/. "$CLAUDE_DATA/"
67	    _log "Copied .claude data to persistent storage"
68	fi
69
70	# ── Migrate Claude binaries on first run ──────────────────────────────────────────────────────
71	if [[ ! -L /root/.local/share/claude && -d /root/.local/share/claude ]]; then
72	    _warn "First run — migrating Claude binaries → $CLAUDE_BIN"
73	    cp -a /root/.local/share/claude/. "$CLAUDE_BIN/"
74	    _log "Migrated Claude binaries"
75	fi
76
77	# ── Create symlinks ───────────────────────────────────────────────────────────────────────────
78	# Remove any real directories first — ln -sfn silently creates inside a dir instead of
79	# replacing it, which produces a circular symlink on subsequent runs after migration.
80	mkdir -p /root/.local/share /root/.local/bin
81
82	[[ -d /root/.claude && ! -L /root/.claude ]] && rm -rf /root/.claude
83	ln -sfn "$CLAUDE_DATA" /root/.claude
84	_log ".claude        → $CLAUDE_DATA"
85
86	[[ -d /root/.local/share/claude && ! -L /root/.local/share/claude ]] && rm -rf /root/.local/share/claude
87	ln -sfn "$CLAUDE_BIN" /root/.local/share/claude
88	_log "claude binary  → $CLAUDE_BIN"
89
90	# ── Symlink CLAUDE.md ─────────────────────────────────────────────────────────────────────────
91	# Lives on /boot so it survives reboots without appdata. Symlinked into /root so Claude
92	# picks it up automatically from the working directory on every session.
93	CLAUDE_MD="/boot/config/plugins/varaverk/CLAUDE.md"
94	if [[ -f "$CLAUDE_MD" ]]; then
95	    ln -sfn "$CLAUDE_MD" /root/CLAUDE.md
96	    _log "CLAUDE.md      → $CLAUDE_MD"
97	else
98	    _warn "CLAUDE.md not found at $CLAUDE_MD — skipping symlink"
99	fi
100
101	# ── Point the claude binary at the latest installed version ───────────────────────────────────
102	LATEST=$(ls "$CLAUDE_BIN/versions/" 2>/dev/null | sort -V | tail -1)
103	if [[ -z "$LATEST" ]]; then
104	    _err "No Claude versions found in $CLAUDE_BIN/versions/"
105	    _err "Install Claude Code first:  npm install -g @anthropic-ai/claude-code"
106	    exit 1
107	fi
108
109	ln -sfn "$CLAUDE_BIN/versions/$LATEST" /root/.local/bin/claude
110	_log "claude v$LATEST ready"
111
112	echo ""
113
114	# ── Setup-only mode (used by array_started.sh or other callers) ─────────────────────────────────
115	if [[ "$LAUNCH" == false ]]; then
116	    _log "Setup complete — run 'claude' to start"
117	    echo ""
118	    exit 0
119	fi
120
121	# ── Launch ────────────────────────────────────────────────────────────────────────────────────
122	export PATH="$HOME/.local/bin:$PATH"
123	exec claude
124

#!/bin/bash
# ==============================================================================================
# ================================= Claude Code Startup ========================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Restores Claude Code's persistent data after an Unraid reboot and launches Claude.
# Unraid's root filesystem lives in RAM — /root/.claude and /root/.local are wiped on
# every reboot. This script symlinks both directories back to persistent appdata storage
# before launching Claude, so memory, sessions, and settings survive across reboots.
#
# On first run with no existing persistent data, migrates from the current live locations:
#   /root/.claude              → PERSIST_DIR/.claude            (memory, sessions, settings)
#   /root/.local/share/claude  → PERSIST_DIR/local/share/claude (installed binaries)
# Subsequent runs skip the migration and only create the symlinks.
#
# Standalone script — no common.sh dependency. Safe to run directly from terminal
# or from array_started.sh.
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# claude_startup.sh
#     Set up persistent symlinks only — default, used by array_started.sh on boot.
#
# claude_startup.sh --launch
#     Set up persistent symlinks and launch Claude interactively.
#
# ==============================================================================================

PERSIST_DIR="/mnt/user/appdata/claude-code"
CLAUDE_DATA="$PERSIST_DIR/.claude"
CLAUDE_BIN="$PERSIST_DIR/local/share/claude"

LAUNCH=false
[[ "$1" == "--launch" ]] && LAUNCH=true

# Standalone — no common.sh dependency
_log()  { echo "  ✅  $*"; }
_warn() { echo "  ⚠   $*"; }
_err()  { echo "  ❌  $*" >&2; }

echo ""
echo "━━━ Claude Code Startup ━━━"
echo ""

# ── Array must be mounted ─────────────────────────────────────────────────────────────────────
if ! mountpoint -q /mnt/user 2>/dev/null; then
    _err "Array not mounted — /mnt/user not available"
    exit 1
fi

# ── Create persistent dirs ────────────────────────────────────────────────────────────────────
mkdir -p "$CLAUDE_DATA" "$CLAUDE_BIN"

# ── Migrate .claude on first run ──────────────────────────────────────────────────────────────
if [[ ! -L /root/.claude && -d /root/.claude ]]; then
    _warn "First run — migrating /root/.claude → $CLAUDE_DATA"
    cp -a /root/.claude/. "$CLAUDE_DATA/"
    rm -rf /root/.claude
    _log "Migrated .claude (memory, sessions, settings)"
elif [[ -z "$(ls -A "$CLAUDE_DATA" 2>/dev/null)" && -d /root/.claude ]]; then
    _warn "Persistent storage empty — copying current .claude data"
    cp -a /root/.claude/. "$CLAUDE_DATA/"
    _log "Copied .claude data to persistent storage"
fi

# ── Migrate Claude binaries on first run ──────────────────────────────────────────────────────
if [[ ! -L /root/.local/share/claude && -d /root/.local/share/claude ]]; then
    _warn "First run — migrating Claude binaries → $CLAUDE_BIN"
    cp -a /root/.local/share/claude/. "$CLAUDE_BIN/"
    _log "Migrated Claude binaries"
fi

# ── Create symlinks ───────────────────────────────────────────────────────────────────────────
# Remove any real directories first — ln -sfn silently creates inside a dir instead of
# replacing it, which produces a circular symlink on subsequent runs after migration.
mkdir -p /root/.local/share /root/.local/bin

[[ -d /root/.claude && ! -L /root/.claude ]] && rm -rf /root/.claude
ln -sfn "$CLAUDE_DATA" /root/.claude
_log ".claude        → $CLAUDE_DATA"

[[ -d /root/.local/share/claude && ! -L /root/.local/share/claude ]] && rm -rf /root/.local/share/claude
ln -sfn "$CLAUDE_BIN" /root/.local/share/claude
_log "claude binary  → $CLAUDE_BIN"

# ── Symlink CLAUDE.md ─────────────────────────────────────────────────────────────────────────
# Lives on /boot so it survives reboots without appdata. Symlinked into /root so Claude
# picks it up automatically from the working directory on every session.
CLAUDE_MD="/boot/config/plugins/varaverk/CLAUDE.md"
if [[ -f "$CLAUDE_MD" ]]; then
    ln -sfn "$CLAUDE_MD" /root/CLAUDE.md
    _log "CLAUDE.md      → $CLAUDE_MD"
else
    _warn "CLAUDE.md not found at $CLAUDE_MD — skipping symlink"
fi

# ── Point the claude binary at the latest installed version ───────────────────────────────────
LATEST=$(ls "$CLAUDE_BIN/versions/" 2>/dev/null | sort -V | tail -1)
if [[ -z "$LATEST" ]]; then
    _err "No Claude versions found in $CLAUDE_BIN/versions/"
    _err "Install Claude Code first:  npm install -g @anthropic-ai/claude-code"
    exit 1
fi

ln -sfn "$CLAUDE_BIN/versions/$LATEST" /root/.local/bin/claude
_log "claude v$LATEST ready"

echo ""

# ── Setup-only mode (used by array_started.sh or other callers) ─────────────────────────────────
if [[ "$LAUNCH" == false ]]; then
    _log "Setup complete — run 'claude' to start"
    echo ""
    exit 0
fi

# ── Launch ────────────────────────────────────────────────────────────────────────────────────
export PATH="$HOME/.local/bin:$PATH"
exec claude


1	#!/bin/bash
2	# ==============================================================================================
3	# ========================== HOST1 CONFIGURATION — unRAID-Gmer4Lfe ============================
4	# ==============================================================================================
5	# HOST1-specific variables — credentials, container names, share paths, failover lists.
6	# Sourced after master.conf — values here extend shared profile arrays and add HOST1-specific
7	# identity, credentials, and container configuration.
8	#
9	# Sparse checkout (git) ensures HOST2 never receives this file.
10	# HOST2 never sees HOST1 credentials — clean separation at the file level.
11	#
12	# DO NOT put shared config here — thresholds, toggles, profiles belong in master.conf.
13	# DO NOT put HOST2 variables here — they belong in host2.conf.
14	#
15	# ── INDEX ─────────────────────────────────────────────────────────────────────────────────────
16	#
17	#   ── IDENTITY & CONNECTIVITY ────────────────────────────────────────────────────────────────
18	#   IDENTITY                hostname, SSH key
19	#   EMBY                    container name, URL, API key
20	#   NOTIFICATIONS           Discord webhook
21	#   PARTNERSHIP             auth containers, backup paths
22	#
23	#   ── RSYNC ──────────────────────────────────────────────────────────────────────────────────
24	#   DAILY SYNC SHARES       media shares HOST1 owns and pushes to HOST2
25	#   WEEKLY SYNC SHARES      appdata shares synced weekly (Sunday 2:30am)
26	#   CRITICAL SYNC SHARES    appdata shares synced every 30 minutes
27	#   BACKUP VERIFY           shares for checksum verification against remote
28	#   HOST1 RSYNC PROFILE     host1-appdata profile for HOST1-specific appdata syncs
29	#
30	#   ── DOCKER ─────────────────────────────────────────────────────────────────────────────────
31	#   DOCKER DAILY RESTART    containers restarted daily
32	#   DOCKER WEEKLY RESTART   containers restarted weekly
33	#   DOCKER WATCHDOG         memory limits, health URLs, required containers, ignore list
34	#   DOCKER NETWORK CONNECT  networks and containers for docker_network_connect.sh
35	#
36	#   ── FALLBACK ───────────────────────────────────────────────────────────────────────────────
37	#   DDNS                    DDNS containers managed by HOST1
38	#   INTERNET LOSS           containers stopped when internet is lost
39	#   FALLBACK TIERS          what HOST1 runs for HOST2 per tier
40	#   TIER DELAYS             how long HOST1 must be down before each tier activates on HOST2
41	#   RSYNC WRITEBACK         HOST1 appdata synced back on handback
42	#
43	#   ── MEDIA ──────────────────────────────────────────────────────────────────────────────────
44	#   MEDIA PERMISSIONS       share list for media_shares_permissions.sh
45	#   MEDIA CLEANER           folder lists for media_cleaner.sh
46	#
47	#   ── MONITORS ───────────────────────────────────────────────────────────────────────────────
48	#   CERTIFICATE MONITOR     domains checked for SSL expiry
49	#   SMART HEALTH            drives to skip in SMART monitoring
50	#   ZFS REPORT              pools to exclude from ZFS health report
51	#
52	#   ── TRANSCODES ─────────────────────────────────────────────────────────────────────────────
53	#   TRANSCODES              ramdisk size, thresholds, SSD path, server array
54	#
55	#   ── ARR STACK ──────────────────────────────────────────────────────────────────────────────
56	#   DOWNLOADERS             slskd, SABnzbd, qBittorrent credentials and URLs
57	#   LIDARR                  URL, API key, path map
58	#   SONARR                  URL, API key, path map
59	#   RADARR                  URL, API key, path map
60	#   ARR RECOVERY            per-arr recovery toggles
61	#
62	#   ── SYSTEM WATCHDOG ────────────────────────────────────────────────────────────────────────
63	#   SYSTEM WATCHDOG         per-host check toggles and NIC configuration
64	#
65	#   ── RESOURCE MANAGER ───────────────────────────────────────────────────────────────────────
66	#   RESOURCE MANAGER        containers paused/stopped under memory pressure
67	#
68	# ==============================================================================================
69
70	# ==============================================================================================
71	# ── IDENTITY & CONNECTIVITY ───────────────────────────────────────────────────────────────────
72	# ==============================================================================================
73
74	# ━━━ Identity ━━━
75	# HOST1 hostname lives in master.conf (not a credential — safe for all servers).
76	# SSH key used for all server-to-server operations — rsync, failover container commands.
77	# Must be in /root/.ssh/ and authorised in HOST2's /root/.ssh/authorized_keys.
78	    HOST1_SSH_KEY=""            # /root/.ssh/your_key_name
79	    HOST1_OWNER=""                # your Gitea/GitHub username
80	    HOST1_OWNER_EMAIL=""    # your email
81
82	# ━━━ Emby ━━━
83	# Referenced by transcode_manager.sh, emby_session_report.sh, emby_database_repair.sh,
84	# weekly_sync_maintenance.sh, and HOST1_TRANSCODE_SERVERS below.
85	# API key: Emby Dashboard → API Keys → + New Key
86	    HOST1_EMBY_CONTAINER="Emby"
87	    HOST1_EMBY_URL="http://localhost:8096"
88	    HOST1_EMBY_API_KEY=""  # Emby Dashboard → API Keys → + New Key
89
90	# ━━━ Jellyfin ━━━
91	# API key: Jellyfin Dashboard → Administration → API Keys → + New Key
92	    HOST1_JELLYFIN_CONTAINER="Jellyfin"
93	    HOST1_JELLYFIN_URL="http://localhost:8096"
94	    HOST1_JELLYFIN_API_KEY=""  # Jellyfin Dashboard → API Keys → + New Key
95
96	# ━━━ Gitea ━━━
97	# Personal access token for gitea_ssh_setup.sh — registers this server's SSH public key
98	# with Gitea so git operations use key auth instead of passwords.
99	# Create in Gitea: Settings → Applications → Generate Token → scope: write:user
100	    HOST1_GITEA_API_TOKEN=""    # Gitea Settings → Applications → Generate Token
101
102	# ━━━ Notifications ━━━
103	# Discord webhook — leave blank to disable.
104	# Per-host so HOST1 and HOST2 can post to different channels or only one server notifies.
105	    HOST1_DISCORD_WEBHOOK=""    # Discord channel → Integrations → Webhooks
106
107	# ━━━ Partnership ━━━
108	# HOST1 is always the owner (source of truth) unless --transfer has been run.
109	# See README-Partnership.md and master.conf PARTNERSHIP section for full lifecycle docs.
110
111	# Auth containers reconfigured on onboard/offboard.
112	# Format: "ContainerName|WebUIPort"
113	# On onboard → WebUI pointed at owner's Tailscale IP (mirror clicks NPM, gets owner's auth)
114	# On offboard → WebUI pointed back at localhost
115	    HOST1_PARTNERSHIP_AUTH_WEBUIS=(
116	        "NginxProxyManager|81"
117	        "Lldap-Gmer4Lfe|17170"
118	        "Authelia|9091"
119	        "Authelia-Secondary|9092"
120	    )
121
122	# XML templates (from this server's templates-user/) pushed to mirror during onboard.
123	# These become the mirror's active auth stack, backed by the rsync-synced appdata.
124	# Update filename if Lldap is renamed to drop the host suffix.
125	    HOST1_PARTNERSHIP_AUTH_STACK=(
126	        # Dependencies first — Mariadb/Redis must be healthy before Authelia starts
127	        "my-Mariadb-Authelia.xml"
128	        "my-Mariadb-Authelia-Secondary.xml"
129	        "my-Redis-Authelia.xml"
130	        "my-Redis-Authelia-Secondary.xml"
131	        # Auth apps — deployed after their deps are confirmed healthy
132	        "my-Authelia.xml"
133	        "my-Authelia-Secondary.xml"
134	        "my-NginxProxyManager.xml"
135	        "my-Lldap-Gmer4Lfe.xml"
136	        # Source of truth — must be available on HOST2 independently of the auth stack
137	        "my-Gitea.xml"
138	    )
139
140	# XML templates pushed to mirror for the arr stack during onboard.
141	# Deps (e.g. databases) first if any — same ordering rule as auth stack.
142	    HOST1_PARTNERSHIP_ARR_STACK=(
143	        # "my-Sonarr.xml"
144	        # "my-Radarr.xml"
145	        # "my-Lidarr.xml"
146	        # "my-Prowlarr.xml"
147	        # "my-Bazarr.xml"
148	    )
149
150	# Paths HOST2 should collect during the grace window after offboard.
151	# Notified on offboard — no auto-deletion, HOST2 must collect manually within PARTNERSHIP_GRACE_HOURS.
152	    HOST1_PARTNERSHIP_MIRROR_BACKUPS=(
153	        # "/mnt/user/appdata-Fallback/Jayred365-Emby"
154	    )
155
156	# Containers parked on this server when partnership is active.
157	# Stopped on onboard (owner deploys its stack instead), restarted on offboard.
158	    HOST1_PARTNERSHIP_OWN_CONTAINERS=(
159	        # "Emby"
160	        # "NginxProxyManager"
161	    )
162
163	# Emby admin provisioning — toggle is owner-only, credentials are per-host.
164	# Owner enables/disables the feature. Each host sets the account they want on the shared Emby.
165	# On onboard: owner reads mirror's HOST*_PARTNERSHIP_EMBY_ADMIN_* and creates that account.
166	# On offboard: account is deleted. Username collision → onboard exits with error.
167	    HOST1_PARTNERSHIP_PROVISION_EMBY_ADMIN=false   # owner controls whether Emby is shared
168	    HOST1_PARTNERSHIP_EMBY_PORT=8096
169	    HOST1_PARTNERSHIP_EMBY_ADMIN_USER=""            # this server's desired Emby username
170	    HOST1_PARTNERSHIP_EMBY_ADMIN_PASS=""            # this server's desired Emby password
171
172	# ==============================================================================================
173	# ── RSYNC ─────────────────────────────────────────────────────────────────────────────────────
174	# ==============================================================================================
175
176	# ━━━ Daily Sync Shares ━━━
177	# Shares HOST1 pushes to all other nodes every night (1am via daily_sync_maintenance.sh).
178	# Mesh model: every node pushes every media share — no ownership, no mirrors.
179	# arr_sync ensures all arr libraries converge (union). rsync spreads files (additive, no --delete).
180	# arr_cleanup removes true orphans based on local arr state.
181	# Any node can download content to any share — it propagates to all nodes on the next cycle.
182	# Nextcloud is intentionally one-directional (HOST1→HOST2 offsite backup — not arr-managed).
183	# Uses DEFAULT_RSYNC_OPTS from master.conf — no profile needed.
184	# For shares needing container stops or custom options — add a profile in master.conf.
185	    HOST1_DAILY_SYNC_SHARES=(
186	        /mnt/user/Books
187	        /mnt/user/Intros
188	        /mnt/user/Kids_Movies
189	        /mnt/user/Kids_Tv_Shows
190	        /mnt/user/Movies
191	        /mnt/user/Music
192	        /mnt/user/Music_Videos
193	        /mnt/user/Nextcloud
194	        /mnt/user/stand-up_comedy
195	        /mnt/user/Sports
196	        /mnt/user/Tv_Shows
197	        /mnt/user/Anime_Shows-Old
198	        /mnt/user/Anime_Movies-Old
199	        /mnt/user/Anime_Movies
200	        /mnt/user/Anime_Shows
201	    )
202
203	# Personal encrypted shares — synced for offsite backup, independent of media shares.
204	# ZFS encrypted at dataset level — remote receives encrypted blocks, cannot read content.
205	# See README-Rsync_Setup.md for ZFS encryption setup before uncommenting.
206	    HOST1_PERSONAL_SHARES=(
207	        # /mnt/user/HOST1-Personal     # uncomment after creating encrypted dataset
208	    )
209
210	# ━━━ Weekly Sync Shares ━━━
211	# Appdata shares synced during the weekly maintenance window (Sunday 2:30am).
212	# Containers stopped both sides before sync — full clean state guaranteed.
213	# Profiles drive container stops, excludes, and options — configured in master.conf RSYNC section.
214	# Order matters — Emby first (larger transfer), then Critical-Data (auth stack).
215	    HOST1_WEEKLY_SYNC_SHARES=(
216	        "/mnt/user/Media_Server/Emby"               # emby profile — full clean mirror
217	        "/mnt/user/appdata-Fallback/Critical-Data"  # critical-data profile — auth stack
218	    )
219
220	# ━━━ Intermediate Sync Shares ━━━
221	# Shares synced every 4 hours by intermediate_sync_maintenance.sh.
222	# Uses DEFAULT_RSYNC_OPTS (no --delete) — for sub-daily propagation of metadata or watch state.
223	# Full media share sync stays in the daily window. Leave empty to skip mid-day rsync entirely.
224	    HOST1_INTERMEDIATE_SYNC_SHARES=(
225	        # Add shares here to enable mid-day rsync
226	        # Example: "/mnt/user/Emby_Metadata"
227	    )
228
229	# ━━━ Critical Sync Shares ━━━
230	# Appdata shares synced every 30 minutes by critical_sync_maintenance.sh.
231	# Format: "/path/to/share" or "/path/to/share|profile-name"
232	# Order matters — Critical-Data first (auth stack), then Emby dirty sync.
233	    HOST1_CRITICAL_SYNC_SHARES=(
234	        "/mnt/user/appdata-Fallback/Critical-Data|critical-fallback"  # auth dirty sync — stays running
235	        "/mnt/user/Media_Server/Emby|emby-fallback"                   # Emby dirty sync — stays running
236	    )
237
238	# ━━━ Backup Verify ━━━
239	# Shares verified by backup_verify.sh — random file checksum comparison against remote.
240	# Leave empty to use HOST1_DAILY_SYNC_SHARES automatically.
241	# Sample size and minimum file size defined in master.conf.
242	    HOST1_BACKUP_VERIFY_SHARES=(
243	        # leave empty to use HOST1_DAILY_SYNC_SHARES automatically
244	    )
245
246	# ━━━ HOST1 Rsync Profile — host1-appdata ━━━
247	# HOST1-specific appdata sync profile — extends the shared PROFILE_* arrays in master.conf.
248	# Use for appdata unique to HOST1 (Organizrv2, VaultWarden, UptimeKuma etc.)
249	# Shared appdata (auth stack, Emby) use dedicated profiles defined in master.conf.
250	# Run manually: bash Rsync/rsync.sh /mnt/user/appdata-Fallback/HOST1-Appdata --profile=host1-appdata
251	    PROFILE_RSYNC_OPTS[host1-appdata]="-av --info=progress2 --bwlimit=${PROFILE_BW_LIMIT[host1-appdata]:-8000}"
252	    PROFILE_BW_LIMIT[host1-appdata]=8000
253	    PROFILE_RETRY_COUNT[host1-appdata]=3
254	    PROFILE_SLEEP[host1-appdata]=300
255	    PROFILE_CRITICAL_CONTAINER_NAMES[host1-appdata]="Organizrv2-Gmer4Lfe UptimeKuma-Gmer4Lfe VaultWarden-Gmer4Lfe"
256	    PROFILE_DELAYED_CONTAINERS[host1-appdata]=""
257	    PROFILE_CONTAINER_DELAY[host1-appdata]=5
258	    PROFILE_EXCLUDE_DIRS[host1-appdata]="logs *.tmp"
259
260	# ==============================================================================================
261	# ── DOCKER ────────────────────────────────────────────────────────────────────────────────────
262	# ==============================================================================================
263
264	# ━━━ Docker Daily Restart ━━━
265	# Containers restarted every day via DAILY_MAINTENANCE_SCRIPTS.
266	# Dispatcharr degrades over time without restart — daily is intentional, not just housekeeping.
267	# Order matters — auth stack first, then media services.
268	    HOST1_DAILY_RESTART_CONTAINERS=(
269	        "NginxProxyManager"
270	        "Lldap-Gmer4Lfe"
271	        "Authelia"
272	        "Authelia-Secondary"
273	        "Dispatcharr-Iptv-Users"
274	        "Dispatcharr"               # Live TV scheduler — degrades without daily restart
275	        "Dispatcharr-Basic"
276	        "ErsatzTV-Emby"
277	        "Slskd"                     # Soulseek connection drops after extended uptime; restart refreshes share index
278	    )
279
280	# ━━━ Docker Weekly Restart ━━━
281	# Less critical services restarted weekly via WEEKLY_MAINTENANCE_SCRIPTS (Sunday 2:30am).
282	# Containers already stopped for weekly sync — restart adds zero extra downtime.
283	    HOST1_WEEKLY_RESTART_CONTAINERS=(
284	        "NextCloud"
285	        "Organizrv2-Gmer4Lfe"
286	        "AdGuard-Home"
287	        "Immich-Gmer4Lfe"
288	    )
289
290	# ━━━ Docker Watchdog ━━━
291	# Per-HOST1 container configuration for docker_watchdog.sh.
292	# Shared thresholds and toggles live in master.conf.
293
294	# Memory hard limits in MB — immediate restart if exceeded.
295	# Set at "container is clearly broken" not "container is busy".
296	# 20GB=20480  18GB=18432  16GB=16384  12GB=12288  8GB=8192  4GB=4096  2GB=2048  1GB=1024
297	    declare -A HOST1_WATCHDOG_CONTAINERS=(
298	        ["Emby"]=18432              # 18GB — large library + active transcodes
299	        ["LidaTube"]=6144           # 6GB  — memory leak over time
300	        ["Tdarr"]=6144              # 6GB  — encoding is memory intensive
301	        ["Code-Server"]=1024        # 1GB  — should never need more
302	    )
303
304	# HTTP health check URLs — checked every cycle, strike system before restart.
305	# Only add containers with a meaningful web interface to check.
306	    declare -A HOST1_WATCHDOG_CONTAINER_URLS=(
307	        ["Emby"]="http://localhost:8096"
308	    )
309
310	# Required containers — must always be running on HOST1.
311	# Strike system before restart — repeated failures go on skip list, auto-clears on recovery.
312	# Listed in dependency order — dependencies before dependents.
313	    HOST1_WATCHDOG_REQUIRED_CONTAINERS=(
314	        "NginxProxyManager"
315	        "Lldap-Gmer4Lfe"
316	        "Mariadb-Authelia"
317	        "Mariadb-Authelia-Secondary"
318	        "Redis-Authelia"
319	        "Redis-Authelia-Secondary"
320	        "Authelia"
321	        "Authelia-Secondary"
322	    )
323
324	# Containers to skip in Tier 2 global scan — legitimately stopped or frequently restarting.
325	# Watchdog leaves these alone entirely — no restart attempts, no crash loop tracking.
326	    HOST1_WATCHDOG_SCAN_IGNORE=(
327	        "DashGate"
328	        "PIA-WG-Config-Generator"
329	        "Aperture"
330	        "Aperture-Kids"
331	        "pgvector-18-Apeture-Kids"
332	        "Pgvector18-Aperture"
333	    )
334
335	# Dependency ordering — skip restarting a container if its dependency is also down.
336	# Prevents watchdog from restarting Authelia before Mariadb is back up.
337	# SPACE-SEPARATED STRINGS — converted to array at runtime.
338	    declare -A HOST1_WATCHDOG_DEPENDENCIES=(
339	        ["Authelia"]="Mariadb-Authelia Redis-Authelia"
340	        ["Authelia-Secondary"]="Mariadb-Authelia Redis-Authelia-Secondary"
341	        ["NextCloud"]="Postgres-NextCloud"
342	    )
343
344	# Per-container appdata growth suppress ceilings in MB.
345	# ONLY needed in specific cases — growth rate detection covers all containers automatically.
346	# Use this when a container legitimately has large stable data and you want to guarantee
347	# it never triggers a false-positive growth alert. Growth warnings are suppressed while the
348	# container's dir stays below this ceiling; above it, warnings resume as normal.
349	# 50GB=51200  25GB=25600  20GB=20480  15GB=15360  10GB=10240  5GB=5120
350	    declare -A HOST1_WATCHDOG_APPDATA_SIZES=(
351	        ["Tdarr"]="25600"       # 25GB — transcode cache grows legitimately during active jobs
352	        ["7dtd"]="20480"        # 20GB — game server world data, expected to be large
353	    )
354
355	# ━━━ Network Watchdog ━━━
356	# Host-specific connectivity config for Watchdogs/System/network_watchdog.sh.
357	    HOST1_NETWORK_WATCHDOG_DDNS_DOMAIN="gmer4lfe.com"
358	    HOST1_NETWORK_WATCHDOG_DDNS_CONTAINER="Gmer4Lfe.com"
359	    HOST1_NETWORK_WATCHDOG_NPM_URL="https://gmer4lfe.com"
360
361	# ━━━ Docker Network Connect ━━━
362	# Containers connected to custom networks at array start by docker_network_connect.sh.
363	# Networks created if they don't exist — idempotent, safe to re-run.
364	    HOST1_NETWORK_CONNECT_CONTAINERS=(
365	        "memcached"
366	        "Npm-CrowdSec"
367	    )
368
369	    HOST1_NETWORK_CONNECT_NETWORKS=(
370	        "high-availability"
371	    )
372
373	# ==============================================================================================
374	# ── FALLBACK ──────────────────────────────────────────────────────────────────────────────────
375	# ==============================================================================================
376
377	# ━━━ DDNS ━━━
378	# DDNS containers HOST1 manages — started/stopped by fallback.sh per DDNS absolute rules:
379	#   Internet loss → stop immediately
380	#   Failover → HOST2 starts HOST1's DDNS as Tier 1 (before any other containers)
381	#   Handback → stop HOST1's DDNS on HOST2 → rsync → start containers → start local DDNS last
382	    HOST1_DDNS_CONTAINERS=(
383	        "Gmer4Lfe.com"
384	    )
385
386	# ━━━ Internet Loss ━━━
387	# Containers stopped immediately on HOST1 when internet connection is lost.
388	# Prevents external-facing services from operating without connectivity.
389	    FALLBACK_HOST1_STOP_ON_NO_NET=(
390	        "Gmer4Lfe.com"
391	    )
392
393	# ━━━ Fallback Tiers — HOST1 Runs for HOST2 ━━━
394	# Containers HOST1 starts when HOST2 goes down.
395	# Tier 1 is always immediate — vital services cannot wait.
396	# Higher tiers activate after HOST2_TIER*_DELAY minutes (set in host2.conf).
397	    FALLBACK_HOST1_COVERS_HOST2_TIER1=(
398	        "Gmer4Lfe.us"
399	        "VaultWarden-Jayred365"
400	    )
401
402	    FALLBACK_HOST1_COVERS_HOST2_TIER2=(
403	        # "container-placeholder"
404	    )
405
406	    FALLBACK_HOST1_COVERS_HOST2_TIER3=(
407	        # "container-placeholder"
408	    )
409
410	    FALLBACK_HOST1_COVERS_HOST2_TIER4=(
411	        # "container-placeholder"
412	    )
413
414	# ━━━ Tier Delays — HOST1's Containers on HOST2 ━━━
415	# How long HOST1 must be down before each tier activates on HOST2 — in minutes.
416	# Tier 1 is always immediate — no delay var needed.
417	    HOST1_TIER2_DELAY=240           # 4 hours  — NextCloud, Immich
418	    HOST1_TIER3_DELAY=720           # 12 hours — secondary services
419	    HOST1_TIER4_DELAY=1440          # 24 hours — arrs + downloaders
420
421	# ━━━ Rsync Writeback — HOST1 Appdata Back on Handback ━━━
422	# Syncs HOST1 appdata BACK to HOST1 when it comes back online after a failover.
423	# Containers stopped before writeback — clean source, no competing writes.
424	#
425	# HOST1_TIER1_WRITEBACK_DELAY: short outages skip Tier 1 writeback — primary state
426	# is more reliable than dirty sync data for brief outages.
427	    HOST1_TIER1_WRITEBACK_DELAY=60  # skip Emby writeback if outage under 1hr
428
429	# Tier 4 automatically syncs HOST1_DAILY_SYNC_SHARES — only list paths NOT in that array.
430	    FALLBACK_HOST1_WRITEBACK_TIER1=(
431	        "/mnt/user/Media_Server/Emby"               # watch states built up during outage
432	    )
433
434	    FALLBACK_HOST1_WRITEBACK_TIER2=(
435	        "/mnt/user/appdata-Fallback/Important-Data" # NextCloud + Postgres — files added during outage
436	    )
437
438	    FALLBACK_HOST1_WRITEBACK_TIER3=(
439	        # "location-placeholder"
440	    )
441
442	    FALLBACK_HOST1_WRITEBACK_TIER4=(
443	        "/mnt/user/appdata-Fallback/Arrs_Stack"     # arr databases — downloads queued during outage
444	    )
445
446	# ==============================================================================================
447	# ── MEDIA ─────────────────────────────────────────────────────────────────────────────────────
448	# ==============================================================================================
449
450	# ━━━ Media Permissions ━━━
451	# Shares that media_shares_permissions.sh applies PERMISSIONS_MODE and PERMISSIONS_OWNER to.
452	# Runs first in DAILY_MAINTENANCE_SCRIPTS — arr cleanup depends on correct ownership.
453	    HOST1_MEDIA_PERMISSION_SHARES=(
454	        /mnt/user/Anime_Movies
455	        /mnt/user/Anime_Movies-Old
456	        /mnt/user/Anime_Shows
457	        /mnt/user/Anime_Shows-Old
458	        /mnt/user/appcache
459	        /mnt/user/Books
460	        /mnt/user/Downloads
461	        /mnt/user/Games
462	        /mnt/user/Intros
463	        /mnt/user/Kids_Movies
464	        /mnt/user/Kids_Tv_Shows
465	        /mnt/user/Movie_Recordings
466	        /mnt/user/Movies
467	        /mnt/user/Music
468	        /mnt/user/Music_Videos
469	        /mnt/user/Photo
470	        /mnt/user/Sports
471	        /mnt/user/stand-up_comedy
472	        /mnt/user/Temp_Storage
473	        /mnt/user/Tv_Recordings
474	        /mnt/user/Tv_Shows
475	        /mnt/user/YouTube
476	    )
477
478	# ━━━ Media Cleaner ━━━
479	# Folder lists for media_cleaner.sh — two profiles: anime and media.
480	# File patterns shared across all servers — defined in master.conf.
481	# Called via DAILY_MAINTENANCE_SCRIPTS. Run manually: Media/media_cleaner.sh anime|media
482	    HOST1_ANIME_CLEAN_FOLDERS=(
483	        /mnt/user/Anime_Movies
484	        /mnt/user/Anime_Movies-Old
485	        /mnt/user/Anime_Shows
486	        /mnt/user/Anime_Shows-Old
487	    )
488
489	    HOST1_MEDIA_CLEAN_FOLDERS=(
490	        /mnt/user/Kids_Movies
491	        /mnt/user/Kids_Tv_Shows
492	        /mnt/user/Movies
493	        /mnt/user/Music
494	        /mnt/user/Sports
495	        /mnt/user/stand-up_comedy
496	        /mnt/user/Tv_Shows
497	    )
498
499	# ==============================================================================================
500	# ── MONITORS ──────────────────────────────────────────────────────────────────────────────────
501	# ==============================================================================================
502
503	# ━━━ Certificate Monitor ━━━
504	# Domains checked via direct openssl connection — not relying on NPM's certificate state.
505	# Checks the actual certificate served, not what NPM thinks it has.
506	# Thresholds (CERT_WARN_DAYS, CERT_CRIT_DAYS) defined in master.conf.
507	    HOST1_CERT_MONITOR_DOMAINS=(
508	        "Gmer4Lfe.com"
509	        "Gmer4Lfe.us"
510	    )
511
512	# ━━━ SMART Health ━━━
513	# Drives skipped in SMART attribute monitoring — hardware is server-specific.
514	# Thresholds read from dynamix.cfg at runtime — fallbacks in master.conf.
515	    HOST1_SMART_IGNORE_DRIVES=(
516	        "sda"                       # boot USB — SMART not meaningful on flash drives
517	    )
518
519	# ━━━ ZFS Report ━━━
520	# Pools excluded from the weekly ZFS health report — reduces noise from single-disk array pools.
521	# These are individual array disks formatted as ZFS — converting to XFS over time via unBalance.
522	# Pool health thresholds defined in master.conf.
523	    HOST1_ZFS_REPORT_IGNORE_POOLS=(
524	        "disk5"
525	        "disk6"
526	        "disk8"
527	        "disk9"
528	        "disk10"
529	    )
530
531	# ==============================================================================================
532	# ── TRANSCODES ────────────────────────────────────────────────────────────────────────────────
533	# ==============================================================================================
534
535	# Ramdisk size ceiling — tmpfs only uses RAM actually needed, not the full size upfront.
536	# Real-world: 9 streams peaked at ~5.5GB — 10G gives generous headroom on 128GB RAM.
537	    HOST1_RAMDISK_SIZE="10G"
538
539	# Usage thresholds — coupled to HOST1_RAMDISK_SIZE, adjust all three together if size changes.
540	# Hysteresis gap (8.5 - 7 = 1.5GB) prevents flip-flop between ramdisk and SSD.
541	    HOST1_RAMDISK_WARN_GB=8.5      # flip to SSD when ramdisk usage reaches this
542	    HOST1_RAMDISK_LOW_GB=7         # flip back to ramdisk when usage drops to this
543
544	# SSD fallback path — where transcodes land when ramdisk exceeds HOST1_RAMDISK_WARN_GB.
545	# Must be on cache pool — array disks too slow for active transcode writes.
546	    HOST1_TRANSCODE_SSD="/mnt/cache/Temp_Storage/Emby/Transcodes/"
547
548	# Media servers sharing the ramdisk transcode space on HOST1.
549	# Format: "ContainerName|URL|APIKey|Type" — Type: emby | jellyfin | plex
550	# Entries with placeholder API keys are skipped automatically.
551	# ⚠ Tdarr does NOT belong here — keep Tdarr on SSD, not ramdisk.
552	    HOST1_TRANSCODE_SERVERS=(
553	        "${HOST1_EMBY_CONTAINER}|${HOST1_EMBY_URL}|${HOST1_EMBY_API_KEY}|emby"
554	        "${HOST1_JELLYFIN_CONTAINER}|${HOST1_JELLYFIN_URL}|${HOST1_JELLYFIN_API_KEY}|jellyfin"
555	    )
556
557	# ==============================================================================================
558	# ── ARR STACK ─────────────────────────────────────────────────────────────────────────────────
559	# ==============================================================================================
560	# Used by arr cleanup scripts and arrs_failed_stalled_recovery.sh.
561	# detect_hosts() selects HOST1 vars when running on HOST1.
562	#
563	# PATH MAPS — container path → host path translation.
564	# Arr stores file paths using container-internal paths — scripts need host paths to scan.
565	# Add one entry per root folder in arr Settings → Media Management → Root Folders.
566
567	# ━━━ Downloaders ━━━
568	# Used by downloaders_reset.sh — runs every 30min via CRITICAL_MAINTENANCE_SCRIPTS.
569	# Clears stuck states, purges old history, prepares each client for a clean cycle.
570
571	# slskd — clears stuck searches, dead transfers, purges expired failed imports.
572	# SLSKD_FAILED_IMPORTS_DIR: where Soularr moves albums Lidarr rejected.
573	    HOST1_SLSKD_URL="http://localhost:8980"
574	    HOST1_SLSKD_API_KEY=""        # slskd Settings → API key
575	    HOST1_SLSKD_FAILED_IMPORTS_DIR="/mnt/user/Temp_Storage/Slskd/completed/failed_imports"
576
577	# SABnzbd
578	    HOST1_SABNZBD_URL="http://localhost:8180"
579	    HOST1_SABNZBD_API_KEY=""    # SABnzbd Config → General → API Key
580
581	# qBittorrent — deleteFiles=false removes torrent from qBit but leaves files on disk.
582	# Radarr/Sonarr manage actual files independently.
583	    HOST1_QBIT_URL="http://localhost:8080"
584	    HOST1_QBIT_USERNAME="root"
585	    HOST1_QBIT_PASSWORD=""        # qBittorrent WebUI password
586
587	# ━━━ Lidarr — HOST1 only ━━━
588	# HOST2 does not run Lidarr — HOST1_LIDARR_RECOVERY flag handles the exit cleanly.
589	    HOST1_LIDARR_URL="http://localhost:8686"
590	    HOST1_LIDARR_API_KEY=""      # Lidarr Settings → General → API Key
591	    HOST1_LIDARR_MUSIC_ROOT="/mnt/user/Music-New"
592	    HOST1_FANART_API_KEY=""      # fanart.tv account → API Key
593	    HOST1_LASTFM_API_KEY=""      # last.fm API → Create API Account
594
595	    declare -A HOST1_LIDARR_PATH_MAP=(
596	        ["/ext-music"]="/mnt/user/Music-New"
597	    )
598
599	# ━━━ Sonarr ━━━
600	    HOST1_SONARR_URL="http://localhost:8989"
601	    HOST1_SONARR_API_KEY=""      # Sonarr Settings → General → API Key
602	    HOST1_SONARR_TV_ROOT="/mnt/user/Tv_Shows"
603
604	# Note: stand-up_comedy in both Sonarr + Radarr — TV specials and movie specials, one folder
605	    declare -A HOST1_SONARR_PATH_MAP=(
606	        ["/tv"]="/mnt/user/Tv_Shows"
607	        ["/ext-standup-comedy"]="/mnt/user/stand-up_comedy"
608	        ["/kids tv"]="/mnt/user/Kids_Tv_Shows"
609	        ["/ext-anime-shows"]="/mnt/user/Anime_Shows-Old"
610	    )
611
612	# ━━━ Radarr ━━━
613	    HOST1_RADARR_URL="http://localhost:7878"
614	    HOST1_RADARR_API_KEY=""      # Radarr Settings → General → API Key
615	    HOST1_TMDB_API_KEY=""          # themoviedb.org → Settings → API
616	    HOST1_RADARR_MOVIES_ROOT="/mnt/user/Movies"
617
618	# Note: stand-up_comedy in both Radarr + Sonarr — movie specials and TV specials, one folder
619	    declare -A HOST1_RADARR_PATH_MAP=(
620	        ["/movies"]="/mnt/user/Movies"
621	        ["/kids movies"]="/mnt/user/Kids_Movies"
622	        ["/ext-stand-up-comedy"]="/mnt/user/stand-up_comedy"
623	        ["/anime-movies"]="/mnt/user/Anime_Movies-Old"
624	    )
625
626	# ━━━ Arr Recovery Toggles ━━━
627	# false = skip that arr on this host — exits cleanly without error
628	    HOST1_SONARR_RECOVERY=true
629	    HOST1_RADARR_RECOVERY=true
630	    HOST1_LIDARR_RECOVERY=true      # HOST1 only — exits cleanly on HOST2
631
632	# ==============================================================================================
633	# ── SYSTEM WATCHDOG ───────────────────────────────────────────────────────────────────────────
634	# ==============================================================================================
635	# Per-host check toggles and NIC config for system_watchdog.sh.
636	# Aliased by detect_hosts() — script uses unprefixed SYS_WATCHDOG_* names.
637	# HOST1: TR1950X 128GB — full media server, active transcoding, ZFS cache pools.
638	#
639	# Three-tier response — all critical checks enabled by default on HOST1:
640	#   Tier 1 (bypass strikes, reboot now):  docker daemon, rootfs full, kernel oops, FD, /boot
641	#   Tier 2 (bypass strikes with OOM):     RAM critical + OOM kills in cycle
642	#   Tier 3 (standard strike system):      everything else
643	#
644	# RAM tiers, OOM limits, and reboot loop settings in master.conf System Watchdog section.
645
646	# ━━━ Primary NIC ━━━
647	# Network interface for NIC state check — verify with: ip link show | grep "^[0-9]"
648	# Common values: eth0, bond0, br0, eno1
649	    HOST1_SYS_WATCHDOG_NIC="eth0"
650
651	# ━━━ Tier 1 — Critical Checks ━━━
652	# These bypass the strike system — a single hit triggers immediate reboot.
653	# Disabling any of these is not recommended — they protect against acute system failure.
654
655	# Docker daemon unresponsive → try restart, reboot if restart fails.
656	# Without a working daemon docker_watchdog.sh is blind and containers cannot be managed.
657	    HOST1_SYS_WATCHDOG_CHECK_DOCKER_DAEMON=true
658
659	# rootfs at critical threshold (ROOTFS_CRITICAL_PCT=99) → reboot immediately.
660	# At 99% rootfs writes fail silently — logs stop, Docker errors out, SSH may stop working.
661	# Standard 95% threshold still uses strike system — only 99%+ is critical tier.
662	    HOST1_SYS_WATCHDOG_CHECK_ROOTFS=true
663
664	# Kernel BUG/Oops in dmesg delta since last cycle → reboot immediately.
665	# A kernel oops means the kernel ran with a corrupted state — stability is not guaranteed.
666	    HOST1_SYS_WATCHDOG_CHECK_KERNEL_OOPS=true
667
668	# File descriptor exhaustion at FD_CRITICAL_PCT (95%) → reboot immediately.
669	# At 95% FD: new connections fail, Docker can't spawn processes, SSH drops.
670	    HOST1_SYS_WATCHDOG_CHECK_FD=true
671
672	# /boot read-only detected → reboot immediately.
673	# Unexpected read-only /boot means state files and config writes are silently failing.
674	# Fallback state, watchdog reboot log, and lock files all go stale silently.
675	    HOST1_SYS_WATCHDOG_CHECK_BOOT=true
676
677	# ━━━ Tier 2 — Urgent OOM Check ━━━
678	# Bypass strikes when RAM is critically low AND OOM kill rate confirms active crisis.
679	# Both must be enabled for Tier 2 bypass to function — disable either to always use strikes.
680
681	# Track kernel OOM kills each cycle via /proc/vmstat oom_kill delta.
682	# Also provides diagnostic context in reboot messages (which processes were killed).
683	    HOST1_SYS_WATCHDOG_CHECK_OOM=true
684
685	# Free RAM check — required for both Tier 2 bypass and RAM tier logic.
686	# Tiers: MEM_WARN_GB(10) → notify | MEM_SHUTDOWN_GB(6) → stop containers | MEM_GB(4) → strikes
687	    HOST1_SYS_WATCHDOG_CHECK_RAM=true
688
689	# ━━━ Tier 3 — Standard Checks (strike system) ━━━
690	# Each check must fail SYS_WATCHDOG_STRIKE_LIMIT consecutive cycles before action is taken.
691	# Single spikes are ignored — sustained problems trigger reboot.
692
693	# /var/log filesystem usage above SYS_WATCHDOG_LOG_PCT.
694	# Log spam (Docker log storms, syslog loops) fills rootfs — indicates something broken.
695	    HOST1_SYS_WATCHDOG_CHECK_LOG=true
696
697	# ZFS ARC memory pinned above SYS_WATCHDOG_ARC_PINNED_PCT after cache drop.
698	# Enabled on HOST1 — ZFS cache pools actively used. Disable on hosts without ZFS.
699	    HOST1_SYS_WATCHDOG_CHECK_ARC=true
700
701	# CPU temperature above SYS_WATCHDOG_CPU_TEMP_MAX (95°C).
702	# Sustained high temp causes kernel throttling or panic. Requires lm-sensors.
703	    HOST1_SYS_WATCHDOG_CHECK_CPU_TEMP=true
704
705	# Load average above SYS_WATCHDOG_LOAD_MULTIPLIER × core count.
706	# DISABLED on HOST1 — Tdarr and Emby cause legitimate sustained load spikes during encoding.
707	# Enable on idle servers or adjust SYS_WATCHDOG_LOAD_MULTIPLIER if load is always high.
708	    HOST1_SYS_WATCHDOG_CHECK_LOAD=false
709
710	# Zombie process count above SYS_WATCHDOG_ZOMBIE_LIMIT (50).
711	# Large zombie counts indicate serious process management failure — something is stuck.
712	    HOST1_SYS_WATCHDOG_CHECK_ZOMBIES=true
713
714	# Check docker_watchdog.sh persistent skip list — required containers on skip list.
715	# Cross-watchdog coordination: if docker_watchdog gave up, system_watchdog escalates.
716	# ENABLED — HOST1 fully built and operational, skip list is meaningful.
717	    HOST1_SYS_WATCHDOG_CHECK_CONTAINERS=true
718
719	# /tmp filesystem usage above SYS_WATCHDOG_TMP_PCT with auto-clear attempt.
720	# Script tries to clear aged /tmp files first — only strikes if clear fails.
721	# Lock files, rsync temp files, and Docker ops use /tmp — 100% means lock failures.
722	    HOST1_SYS_WATCHDOG_CHECK_TMP=true
723
724	# Array disk error count delta in /proc/mdstat — accumulating errors = disk failing now.
725	# Triggers on SYS_WATCHDOG_MDSTAT_ERROR_LIMIT new errors in one cycle.
726	    HOST1_SYS_WATCHDOG_CHECK_MDSTAT=true
727
728	# Primary NIC operstate — detects NIC going down (physical or driver failure).
729	# Uses HOST1_SYS_WATCHDOG_NIC above. Strike system — brief flaps don't trigger reboot.
730	    HOST1_SYS_WATCHDOG_CHECK_NETWORK=true
731
732	# sshd running check — attempts restart before escalating.
733	# sshd down = no remote access. Script tries rc.sshd start, notifies, strikes on failure.
734	    HOST1_SYS_WATCHDOG_CHECK_SSHD=true
735
736	# Runaway process detection — single process above SYS_WATCHDOG_RUNAWAY_CPU_PCT sustained.
737	# DISABLED — Tdarr encoding and Emby transcoding legitimately peg CPU for extended periods.
738	# Enable only if HOST1 has no CPU-intensive workloads.
739	    HOST1_SYS_WATCHDOG_CHECK_RUNAWAY=false
740
741	# ==============================================================================================
742	# ── RESOURCE MANAGER ──────────────────────────────────────────────────────────────────────────
743	# ==============================================================================================
744	# Containers to manage under pressure — see master.conf RW_CRITICAL_CONTAINERS for exclusions.
745
746	# docker pause at medium pressure (RAM < RW_RAM_MEDIUM_GB or load > medium threshold)
747	# Suspended in-place — instant to pause/unpause, no state lost, no restart delay.
748	    HOST1_RW_PAUSE_CONTAINERS=(
749	        "Huntarr"                       # arr search automation — safe to suspend
750	        "Cleanuparr"                    # download cleanup — safe to suspend
751	        "Healarr"                       # arr health checks — safe to suspend
752	        "Soularr"                       # Slskd automation — background only
753	        "ChannelTube"                   # YouTube archiver — background only
754	        "Pinchflat"                     # YouTube archiver — background only
755	    )
756
757	# docker stop at hard pressure (RAM < RW_RAM_HARD_GB)
758	# Full stop — these are optional/heavy services that free significant RAM when stopped.
759	# resource_watchdog.sh restarts them when pressure fully clears (RAM >= RW_RAM_RECOVER_GB).
760	    HOST1_RW_STOP_CONTAINERS=(
761	        "LocalAI"                       # GPU/CPU heavy — largest RAM consumer when idle
762	        "7DaysToDie"                    # game server — optional
763	        "V-Rising"                      # game server — optional
764	        "Code-Server"                   # IDE — not needed during pressure events
765	    )
766
767	# ==============================================================================================
768	# ──────────────────────── End Of HOST1 Variables ──────────────────────────────────────────────
769	# ==============================================================================================

#!/bin/bash
# ==============================================================================================
# ========================== HOST1 CONFIGURATION — unRAID-Gmer4Lfe ============================
# ==============================================================================================
# HOST1-specific variables — credentials, container names, share paths, failover lists.
# Sourced after master.conf — values here extend shared profile arrays and add HOST1-specific
# identity, credentials, and container configuration.
#
# Sparse checkout (git) ensures HOST2 never receives this file.
# HOST2 never sees HOST1 credentials — clean separation at the file level.
#
# DO NOT put shared config here — thresholds, toggles, profiles belong in master.conf.
# DO NOT put HOST2 variables here — they belong in host2.conf.
#
# ── INDEX ─────────────────────────────────────────────────────────────────────────────────────
#
#   ── IDENTITY & CONNECTIVITY ────────────────────────────────────────────────────────────────
#   IDENTITY                hostname, SSH key
#   EMBY                    container name, URL, API key
#   NOTIFICATIONS           Discord webhook
#   PARTNERSHIP             auth containers, backup paths
#
#   ── RSYNC ──────────────────────────────────────────────────────────────────────────────────
#   DAILY SYNC SHARES       media shares HOST1 owns and pushes to HOST2
#   WEEKLY SYNC SHARES      appdata shares synced weekly (Sunday 2:30am)
#   CRITICAL SYNC SHARES    appdata shares synced every 30 minutes
#   BACKUP VERIFY           shares for checksum verification against remote
#   HOST1 RSYNC PROFILE     host1-appdata profile for HOST1-specific appdata syncs
#
#   ── DOCKER ─────────────────────────────────────────────────────────────────────────────────
#   DOCKER DAILY RESTART    containers restarted daily
#   DOCKER WEEKLY RESTART   containers restarted weekly
#   DOCKER WATCHDOG         memory limits, health URLs, required containers, ignore list
#   DOCKER NETWORK CONNECT  networks and containers for docker_network_connect.sh
#
#   ── FALLBACK ───────────────────────────────────────────────────────────────────────────────
#   DDNS                    DDNS containers managed by HOST1
#   INTERNET LOSS           containers stopped when internet is lost
#   FALLBACK TIERS          what HOST1 runs for HOST2 per tier
#   TIER DELAYS             how long HOST1 must be down before each tier activates on HOST2
#   RSYNC WRITEBACK         HOST1 appdata synced back on handback
#
#   ── MEDIA ──────────────────────────────────────────────────────────────────────────────────
#   MEDIA PERMISSIONS       share list for media_shares_permissions.sh
#   MEDIA CLEANER           folder lists for media_cleaner.sh
#
#   ── MONITORS ───────────────────────────────────────────────────────────────────────────────
#   CERTIFICATE MONITOR     domains checked for SSL expiry
#   SMART HEALTH            drives to skip in SMART monitoring
#   ZFS REPORT              pools to exclude from ZFS health report
#
#   ── TRANSCODES ─────────────────────────────────────────────────────────────────────────────
#   TRANSCODES              ramdisk size, thresholds, SSD path, server array
#
#   ── ARR STACK ──────────────────────────────────────────────────────────────────────────────
#   DOWNLOADERS             slskd, SABnzbd, qBittorrent credentials and URLs
#   LIDARR                  URL, API key, path map
#   SONARR                  URL, API key, path map
#   RADARR                  URL, API key, path map
#   ARR RECOVERY            per-arr recovery toggles
#
#   ── SYSTEM WATCHDOG ────────────────────────────────────────────────────────────────────────
#   SYSTEM WATCHDOG         per-host check toggles and NIC configuration
#
#   ── RESOURCE MANAGER ───────────────────────────────────────────────────────────────────────
#   RESOURCE MANAGER        containers paused/stopped under memory pressure
#
# ==============================================================================================

# ==============================================================================================
# ── IDENTITY & CONNECTIVITY ───────────────────────────────────────────────────────────────────
# ==============================================================================================

# ━━━ Identity ━━━
# HOST1 hostname lives in master.conf (not a credential — safe for all servers).
# SSH key used for all server-to-server operations — rsync, failover container commands.
# Must be in /root/.ssh/ and authorised in HOST2's /root/.ssh/authorized_keys.
    HOST1_SSH_KEY=""            # /root/.ssh/your_key_name
    HOST1_OWNER=""                # your Gitea/GitHub username
    HOST1_OWNER_EMAIL=""    # your email

# ━━━ Emby ━━━
# Referenced by transcode_manager.sh, emby_session_report.sh, emby_database_repair.sh,
# weekly_sync_maintenance.sh, and HOST1_TRANSCODE_SERVERS below.
# API key: Emby Dashboard → API Keys → + New Key
    HOST1_EMBY_CONTAINER="Emby"
    HOST1_EMBY_URL="http://localhost:8096"
    HOST1_EMBY_API_KEY=""  # Emby Dashboard → API Keys → + New Key

# ━━━ Jellyfin ━━━
# API key: Jellyfin Dashboard → Administration → API Keys → + New Key
    HOST1_JELLYFIN_CONTAINER="Jellyfin"
    HOST1_JELLYFIN_URL="http://localhost:8096"
    HOST1_JELLYFIN_API_KEY=""  # Jellyfin Dashboard → API Keys → + New Key

# ━━━ Gitea ━━━
# Personal access token for gitea_ssh_setup.sh — registers this server's SSH public key
# with Gitea so git operations use key auth instead of passwords.
# Create in Gitea: Settings → Applications → Generate Token → scope: write:user
    HOST1_GITEA_API_TOKEN=""    # Gitea Settings → Applications → Generate Token

# ━━━ Notifications ━━━
# Discord webhook — leave blank to disable.
# Per-host so HOST1 and HOST2 can post to different channels or only one server notifies.
    HOST1_DISCORD_WEBHOOK=""    # Discord channel → Integrations → Webhooks

# ━━━ Partnership ━━━
# HOST1 is always the owner (source of truth) unless --transfer has been run.
# See README-Partnership.md and master.conf PARTNERSHIP section for full lifecycle docs.

# Auth containers reconfigured on onboard/offboard.
# Format: "ContainerName|WebUIPort"
# On onboard → WebUI pointed at owner's Tailscale IP (mirror clicks NPM, gets owner's auth)
# On offboard → WebUI pointed back at localhost
    HOST1_PARTNERSHIP_AUTH_WEBUIS=(
        "NginxProxyManager|81"
        "Lldap-Gmer4Lfe|17170"
        "Authelia|9091"
        "Authelia-Secondary|9092"
    )

# XML templates (from this server's templates-user/) pushed to mirror during onboard.
# These become the mirror's active auth stack, backed by the rsync-synced appdata.
# Update filename if Lldap is renamed to drop the host suffix.
    HOST1_PARTNERSHIP_AUTH_STACK=(
        # Dependencies first — Mariadb/Redis must be healthy before Authelia starts
        "my-Mariadb-Authelia.xml"
        "my-Mariadb-Authelia-Secondary.xml"
        "my-Redis-Authelia.xml"
        "my-Redis-Authelia-Secondary.xml"
        # Auth apps — deployed after their deps are confirmed healthy
        "my-Authelia.xml"
        "my-Authelia-Secondary.xml"
        "my-NginxProxyManager.xml"
        "my-Lldap-Gmer4Lfe.xml"
        # Source of truth — must be available on HOST2 independently of the auth stack
        "my-Gitea.xml"
    )

# XML templates pushed to mirror for the arr stack during onboard.
# Deps (e.g. databases) first if any — same ordering rule as auth stack.
    HOST1_PARTNERSHIP_ARR_STACK=(
        # "my-Sonarr.xml"
        # "my-Radarr.xml"
        # "my-Lidarr.xml"
        # "my-Prowlarr.xml"
        # "my-Bazarr.xml"
    )

# Paths HOST2 should collect during the grace window after offboard.
# Notified on offboard — no auto-deletion, HOST2 must collect manually within PARTNERSHIP_GRACE_HOURS.
    HOST1_PARTNERSHIP_MIRROR_BACKUPS=(
        # "/mnt/user/appdata-Fallback/Jayred365-Emby"
    )

# Containers parked on this server when partnership is active.
# Stopped on onboard (owner deploys its stack instead), restarted on offboard.
    HOST1_PARTNERSHIP_OWN_CONTAINERS=(
        # "Emby"
        # "NginxProxyManager"
    )

# Emby admin provisioning — toggle is owner-only, credentials are per-host.
# Owner enables/disables the feature. Each host sets the account they want on the shared Emby.
# On onboard: owner reads mirror's HOST*_PARTNERSHIP_EMBY_ADMIN_* and creates that account.
# On offboard: account is deleted. Username collision → onboard exits with error.
    HOST1_PARTNERSHIP_PROVISION_EMBY_ADMIN=false   # owner controls whether Emby is shared
    HOST1_PARTNERSHIP_EMBY_PORT=8096
    HOST1_PARTNERSHIP_EMBY_ADMIN_USER=""            # this server's desired Emby username
    HOST1_PARTNERSHIP_EMBY_ADMIN_PASS=""            # this server's desired Emby password

# ==============================================================================================
# ── RSYNC ─────────────────────────────────────────────────────────────────────────────────────
# ==============================================================================================

# ━━━ Daily Sync Shares ━━━
# Shares HOST1 pushes to all other nodes every night (1am via daily_sync_maintenance.sh).
# Mesh model: every node pushes every media share — no ownership, no mirrors.
# arr_sync ensures all arr libraries converge (union). rsync spreads files (additive, no --delete).
# arr_cleanup removes true orphans based on local arr state.
# Any node can download content to any share — it propagates to all nodes on the next cycle.
# Nextcloud is intentionally one-directional (HOST1→HOST2 offsite backup — not arr-managed).
# Uses DEFAULT_RSYNC_OPTS from master.conf — no profile needed.
# For shares needing container stops or custom options — add a profile in master.conf.
    HOST1_DAILY_SYNC_SHARES=(
        /mnt/user/Books
        /mnt/user/Intros
        /mnt/user/Kids_Movies
        /mnt/user/Kids_Tv_Shows
        /mnt/user/Movies
        /mnt/user/Music
        /mnt/user/Music_Videos
        /mnt/user/Nextcloud
        /mnt/user/stand-up_comedy
        /mnt/user/Sports
        /mnt/user/Tv_Shows
        /mnt/user/Anime_Shows-Old
        /mnt/user/Anime_Movies-Old
        /mnt/user/Anime_Movies
        /mnt/user/Anime_Shows
    )

# Personal encrypted shares — synced for offsite backup, independent of media shares.
# ZFS encrypted at dataset level — remote receives encrypted blocks, cannot read content.
# See README-Rsync_Setup.md for ZFS encryption setup before uncommenting.
    HOST1_PERSONAL_SHARES=(
        # /mnt/user/HOST1-Personal     # uncomment after creating encrypted dataset
    )

# ━━━ Weekly Sync Shares ━━━
# Appdata shares synced during the weekly maintenance window (Sunday 2:30am).
# Containers stopped both sides before sync — full clean state guaranteed.
# Profiles drive container stops, excludes, and options — configured in master.conf RSYNC section.
# Order matters — Emby first (larger transfer), then Critical-Data (auth stack).
    HOST1_WEEKLY_SYNC_SHARES=(
        "/mnt/user/Media_Server/Emby"               # emby profile — full clean mirror
        "/mnt/user/appdata-Fallback/Critical-Data"  # critical-data profile — auth stack
    )

# ━━━ Intermediate Sync Shares ━━━
# Shares synced every 4 hours by intermediate_sync_maintenance.sh.
# Uses DEFAULT_RSYNC_OPTS (no --delete) — for sub-daily propagation of metadata or watch state.
# Full media share sync stays in the daily window. Leave empty to skip mid-day rsync entirely.
    HOST1_INTERMEDIATE_SYNC_SHARES=(
        # Add shares here to enable mid-day rsync
        # Example: "/mnt/user/Emby_Metadata"
    )

# ━━━ Critical Sync Shares ━━━
# Appdata shares synced every 30 minutes by critical_sync_maintenance.sh.
# Format: "/path/to/share" or "/path/to/share|profile-name"
# Order matters — Critical-Data first (auth stack), then Emby dirty sync.
    HOST1_CRITICAL_SYNC_SHARES=(
        "/mnt/user/appdata-Fallback/Critical-Data|critical-fallback"  # auth dirty sync — stays running
        "/mnt/user/Media_Server/Emby|emby-fallback"                   # Emby dirty sync — stays running
    )

# ━━━ Backup Verify ━━━
# Shares verified by backup_verify.sh — random file checksum comparison against remote.
# Leave empty to use HOST1_DAILY_SYNC_SHARES automatically.
# Sample size and minimum file size defined in master.conf.
    HOST1_BACKUP_VERIFY_SHARES=(
        # leave empty to use HOST1_DAILY_SYNC_SHARES automatically
    )

# ━━━ HOST1 Rsync Profile — host1-appdata ━━━
# HOST1-specific appdata sync profile — extends the shared PROFILE_* arrays in master.conf.
# Use for appdata unique to HOST1 (Organizrv2, VaultWarden, UptimeKuma etc.)
# Shared appdata (auth stack, Emby) use dedicated profiles defined in master.conf.
# Run manually: bash Rsync/rsync.sh /mnt/user/appdata-Fallback/HOST1-Appdata --profile=host1-appdata
    PROFILE_RSYNC_OPTS[host1-appdata]="-av --info=progress2 --bwlimit=${PROFILE_BW_LIMIT[host1-appdata]:-8000}"
    PROFILE_BW_LIMIT[host1-appdata]=8000
    PROFILE_RETRY_COUNT[host1-appdata]=3
    PROFILE_SLEEP[host1-appdata]=300
    PROFILE_CRITICAL_CONTAINER_NAMES[host1-appdata]="Organizrv2-Gmer4Lfe UptimeKuma-Gmer4Lfe VaultWarden-Gmer4Lfe"
    PROFILE_DELAYED_CONTAINERS[host1-appdata]=""
    PROFILE_CONTAINER_DELAY[host1-appdata]=5
    PROFILE_EXCLUDE_DIRS[host1-appdata]="logs *.tmp"

# ==============================================================================================
# ── DOCKER ────────────────────────────────────────────────────────────────────────────────────
# ==============================================================================================

# ━━━ Docker Daily Restart ━━━
# Containers restarted every day via DAILY_MAINTENANCE_SCRIPTS.
# Dispatcharr degrades over time without restart — daily is intentional, not just housekeeping.
# Order matters — auth stack first, then media services.
    HOST1_DAILY_RESTART_CONTAINERS=(
        "NginxProxyManager"
        "Lldap-Gmer4Lfe"
        "Authelia"
        "Authelia-Secondary"
        "Dispatcharr-Iptv-Users"
        "Dispatcharr"               # Live TV scheduler — degrades without daily restart
        "Dispatcharr-Basic"
        "ErsatzTV-Emby"
        "Slskd"                     # Soulseek connection drops after extended uptime; restart refreshes share index
    )

# ━━━ Docker Weekly Restart ━━━
# Less critical services restarted weekly via WEEKLY_MAINTENANCE_SCRIPTS (Sunday 2:30am).
# Containers already stopped for weekly sync — restart adds zero extra downtime.
    HOST1_WEEKLY_RESTART_CONTAINERS=(
        "NextCloud"
        "Organizrv2-Gmer4Lfe"
        "AdGuard-Home"
        "Immich-Gmer4Lfe"
    )

# ━━━ Docker Watchdog ━━━
# Per-HOST1 container configuration for docker_watchdog.sh.
# Shared thresholds and toggles live in master.conf.

# Memory hard limits in MB — immediate restart if exceeded.
# Set at "container is clearly broken" not "container is busy".
# 20GB=20480  18GB=18432  16GB=16384  12GB=12288  8GB=8192  4GB=4096  2GB=2048  1GB=1024
    declare -A HOST1_WATCHDOG_CONTAINERS=(
        ["Emby"]=18432              # 18GB — large library + active transcodes
        ["LidaTube"]=6144           # 6GB  — memory leak over time
        ["Tdarr"]=6144              # 6GB  — encoding is memory intensive
        ["Code-Server"]=1024        # 1GB  — should never need more
    )

# HTTP health check URLs — checked every cycle, strike system before restart.
# Only add containers with a meaningful web interface to check.
    declare -A HOST1_WATCHDOG_CONTAINER_URLS=(
        ["Emby"]="http://localhost:8096"
    )

# Required containers — must always be running on HOST1.
# Strike system before restart — repeated failures go on skip list, auto-clears on recovery.
# Listed in dependency order — dependencies before dependents.
    HOST1_WATCHDOG_REQUIRED_CONTAINERS=(
        "NginxProxyManager"
        "Lldap-Gmer4Lfe"
        "Mariadb-Authelia"
        "Mariadb-Authelia-Secondary"
        "Redis-Authelia"
        "Redis-Authelia-Secondary"
        "Authelia"
        "Authelia-Secondary"
    )

# Containers to skip in Tier 2 global scan — legitimately stopped or frequently restarting.
# Watchdog leaves these alone entirely — no restart attempts, no crash loop tracking.
    HOST1_WATCHDOG_SCAN_IGNORE=(
        "DashGate"
        "PIA-WG-Config-Generator"
        "Aperture"
        "Aperture-Kids"
        "pgvector-18-Apeture-Kids"
        "Pgvector18-Aperture"
    )

# Dependency ordering — skip restarting a container if its dependency is also down.
# Prevents watchdog from restarting Authelia before Mariadb is back up.
# SPACE-SEPARATED STRINGS — converted to array at runtime.
    declare -A HOST1_WATCHDOG_DEPENDENCIES=(
        ["Authelia"]="Mariadb-Authelia Redis-Authelia"
        ["Authelia-Secondary"]="Mariadb-Authelia Redis-Authelia-Secondary"
        ["NextCloud"]="Postgres-NextCloud"
    )

# Per-container appdata growth suppress ceilings in MB.
# ONLY needed in specific cases — growth rate detection covers all containers automatically.
# Use this when a container legitimately has large stable data and you want to guarantee
# it never triggers a false-positive growth alert. Growth warnings are suppressed while the
# container's dir stays below this ceiling; above it, warnings resume as normal.
# 50GB=51200  25GB=25600  20GB=20480  15GB=15360  10GB=10240  5GB=5120
    declare -A HOST1_WATCHDOG_APPDATA_SIZES=(
        ["Tdarr"]="25600"       # 25GB — transcode cache grows legitimately during active jobs
        ["7dtd"]="20480"        # 20GB — game server world data, expected to be large
    )

# ━━━ Network Watchdog ━━━
# Host-specific connectivity config for Watchdogs/System/network_watchdog.sh.
    HOST1_NETWORK_WATCHDOG_DDNS_DOMAIN="gmer4lfe.com"
    HOST1_NETWORK_WATCHDOG_DDNS_CONTAINER="Gmer4Lfe.com"
    HOST1_NETWORK_WATCHDOG_NPM_URL="https://gmer4lfe.com"

# ━━━ Docker Network Connect ━━━
# Containers connected to custom networks at array start by docker_network_connect.sh.
# Networks created if they don't exist — idempotent, safe to re-run.
    HOST1_NETWORK_CONNECT_CONTAINERS=(
        "memcached"
        "Npm-CrowdSec"
    )

    HOST1_NETWORK_CONNECT_NETWORKS=(
        "high-availability"
    )

# ==============================================================================================
# ── FALLBACK ──────────────────────────────────────────────────────────────────────────────────
# ==============================================================================================

# ━━━ DDNS ━━━
# DDNS containers HOST1 manages — started/stopped by fallback.sh per DDNS absolute rules:
#   Internet loss → stop immediately
#   Failover → HOST2 starts HOST1's DDNS as Tier 1 (before any other containers)
#   Handback → stop HOST1's DDNS on HOST2 → rsync → start containers → start local DDNS last
    HOST1_DDNS_CONTAINERS=(
        "Gmer4Lfe.com"
    )

# ━━━ Internet Loss ━━━
# Containers stopped immediately on HOST1 when internet connection is lost.
# Prevents external-facing services from operating without connectivity.
    FALLBACK_HOST1_STOP_ON_NO_NET=(
        "Gmer4Lfe.com"
    )

# ━━━ Fallback Tiers — HOST1 Runs for HOST2 ━━━
# Containers HOST1 starts when HOST2 goes down.
# Tier 1 is always immediate — vital services cannot wait.
# Higher tiers activate after HOST2_TIER*_DELAY minutes (set in host2.conf).
    FALLBACK_HOST1_COVERS_HOST2_TIER1=(
        "Gmer4Lfe.us"
        "VaultWarden-Jayred365"
    )

    FALLBACK_HOST1_COVERS_HOST2_TIER2=(
        # "container-placeholder"
    )

    FALLBACK_HOST1_COVERS_HOST2_TIER3=(
        # "container-placeholder"
    )

    FALLBACK_HOST1_COVERS_HOST2_TIER4=(
        # "container-placeholder"
    )

# ━━━ Tier Delays — HOST1's Containers on HOST2 ━━━
# How long HOST1 must be down before each tier activates on HOST2 — in minutes.
# Tier 1 is always immediate — no delay var needed.
    HOST1_TIER2_DELAY=240           # 4 hours  — NextCloud, Immich
    HOST1_TIER3_DELAY=720           # 12 hours — secondary services
    HOST1_TIER4_DELAY=1440          # 24 hours — arrs + downloaders

# ━━━ Rsync Writeback — HOST1 Appdata Back on Handback ━━━
# Syncs HOST1 appdata BACK to HOST1 when it comes back online after a failover.
# Containers stopped before writeback — clean source, no competing writes.
#
# HOST1_TIER1_WRITEBACK_DELAY: short outages skip Tier 1 writeback — primary state
# is more reliable than dirty sync data for brief outages.
    HOST1_TIER1_WRITEBACK_DELAY=60  # skip Emby writeback if outage under 1hr

# Tier 4 automatically syncs HOST1_DAILY_SYNC_SHARES — only list paths NOT in that array.
    FALLBACK_HOST1_WRITEBACK_TIER1=(
        "/mnt/user/Media_Server/Emby"               # watch states built up during outage
    )

    FALLBACK_HOST1_WRITEBACK_TIER2=(
        "/mnt/user/appdata-Fallback/Important-Data" # NextCloud + Postgres — files added during outage
    )

    FALLBACK_HOST1_WRITEBACK_TIER3=(
        # "location-placeholder"
    )

    FALLBACK_HOST1_WRITEBACK_TIER4=(
        "/mnt/user/appdata-Fallback/Arrs_Stack"     # arr databases — downloads queued during outage
    )

# ==============================================================================================
# ── MEDIA ─────────────────────────────────────────────────────────────────────────────────────
# ==============================================================================================

# ━━━ Media Permissions ━━━
# Shares that media_shares_permissions.sh applies PERMISSIONS_MODE and PERMISSIONS_OWNER to.
# Runs first in DAILY_MAINTENANCE_SCRIPTS — arr cleanup depends on correct ownership.
    HOST1_MEDIA_PERMISSION_SHARES=(
        /mnt/user/Anime_Movies
        /mnt/user/Anime_Movies-Old
        /mnt/user/Anime_Shows
        /mnt/user/Anime_Shows-Old
        /mnt/user/appcache
        /mnt/user/Books
        /mnt/user/Downloads
        /mnt/user/Games
        /mnt/user/Intros
        /mnt/user/Kids_Movies
        /mnt/user/Kids_Tv_Shows
        /mnt/user/Movie_Recordings
        /mnt/user/Movies
        /mnt/user/Music
        /mnt/user/Music_Videos
        /mnt/user/Photo
        /mnt/user/Sports
        /mnt/user/stand-up_comedy
        /mnt/user/Temp_Storage
        /mnt/user/Tv_Recordings
        /mnt/user/Tv_Shows
        /mnt/user/YouTube
    )

# ━━━ Media Cleaner ━━━
# Folder lists for media_cleaner.sh — two profiles: anime and media.
# File patterns shared across all servers — defined in master.conf.
# Called via DAILY_MAINTENANCE_SCRIPTS. Run manually: Media/media_cleaner.sh anime|media
    HOST1_ANIME_CLEAN_FOLDERS=(
        /mnt/user/Anime_Movies
        /mnt/user/Anime_Movies-Old
        /mnt/user/Anime_Shows
        /mnt/user/Anime_Shows-Old
    )

    HOST1_MEDIA_CLEAN_FOLDERS=(
        /mnt/user/Kids_Movies
        /mnt/user/Kids_Tv_Shows
        /mnt/user/Movies
        /mnt/user/Music
        /mnt/user/Sports
        /mnt/user/stand-up_comedy
        /mnt/user/Tv_Shows
    )

# ==============================================================================================
# ── MONITORS ──────────────────────────────────────────────────────────────────────────────────
# ==============================================================================================

# ━━━ Certificate Monitor ━━━
# Domains checked via direct openssl connection — not relying on NPM's certificate state.
# Checks the actual certificate served, not what NPM thinks it has.
# Thresholds (CERT_WARN_DAYS, CERT_CRIT_DAYS) defined in master.conf.
    HOST1_CERT_MONITOR_DOMAINS=(
        "Gmer4Lfe.com"
        "Gmer4Lfe.us"
    )

# ━━━ SMART Health ━━━
# Drives skipped in SMART attribute monitoring — hardware is server-specific.
# Thresholds read from dynamix.cfg at runtime — fallbacks in master.conf.
    HOST1_SMART_IGNORE_DRIVES=(
        "sda"                       # boot USB — SMART not meaningful on flash drives
    )

# ━━━ ZFS Report ━━━
# Pools excluded from the weekly ZFS health report — reduces noise from single-disk array pools.
# These are individual array disks formatted as ZFS — converting to XFS over time via unBalance.
# Pool health thresholds defined in master.conf.
    HOST1_ZFS_REPORT_IGNORE_POOLS=(
        "disk5"
        "disk6"
        "disk8"
        "disk9"
        "disk10"
    )

# ==============================================================================================
# ── TRANSCODES ────────────────────────────────────────────────────────────────────────────────
# ==============================================================================================

# Ramdisk size ceiling — tmpfs only uses RAM actually needed, not the full size upfront.
# Real-world: 9 streams peaked at ~5.5GB — 10G gives generous headroom on 128GB RAM.
    HOST1_RAMDISK_SIZE="10G"

# Usage thresholds — coupled to HOST1_RAMDISK_SIZE, adjust all three together if size changes.
# Hysteresis gap (8.5 - 7 = 1.5GB) prevents flip-flop between ramdisk and SSD.
    HOST1_RAMDISK_WARN_GB=8.5      # flip to SSD when ramdisk usage reaches this
    HOST1_RAMDISK_LOW_GB=7         # flip back to ramdisk when usage drops to this

# SSD fallback path — where transcodes land when ramdisk exceeds HOST1_RAMDISK_WARN_GB.
# Must be on cache pool — array disks too slow for active transcode writes.
    HOST1_TRANSCODE_SSD="/mnt/cache/Temp_Storage/Emby/Transcodes/"

# Media servers sharing the ramdisk transcode space on HOST1.
# Format: "ContainerName|URL|APIKey|Type" — Type: emby | jellyfin | plex
# Entries with placeholder API keys are skipped automatically.
# ⚠ Tdarr does NOT belong here — keep Tdarr on SSD, not ramdisk.
    HOST1_TRANSCODE_SERVERS=(
        "${HOST1_EMBY_CONTAINER}|${HOST1_EMBY_URL}|${HOST1_EMBY_API_KEY}|emby"
        "${HOST1_JELLYFIN_CONTAINER}|${HOST1_JELLYFIN_URL}|${HOST1_JELLYFIN_API_KEY}|jellyfin"
    )

# ==============================================================================================
# ── ARR STACK ─────────────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# Used by arr cleanup scripts and arrs_failed_stalled_recovery.sh.
# detect_hosts() selects HOST1 vars when running on HOST1.
#
# PATH MAPS — container path → host path translation.
# Arr stores file paths using container-internal paths — scripts need host paths to scan.
# Add one entry per root folder in arr Settings → Media Management → Root Folders.

# ━━━ Downloaders ━━━
# Used by downloaders_reset.sh — runs every 30min via CRITICAL_MAINTENANCE_SCRIPTS.
# Clears stuck states, purges old history, prepares each client for a clean cycle.

# slskd — clears stuck searches, dead transfers, purges expired failed imports.
# SLSKD_FAILED_IMPORTS_DIR: where Soularr moves albums Lidarr rejected.
    HOST1_SLSKD_URL="http://localhost:8980"
    HOST1_SLSKD_API_KEY=""        # slskd Settings → API key
    HOST1_SLSKD_FAILED_IMPORTS_DIR="/mnt/user/Temp_Storage/Slskd/completed/failed_imports"

# SABnzbd
    HOST1_SABNZBD_URL="http://localhost:8180"
    HOST1_SABNZBD_API_KEY=""    # SABnzbd Config → General → API Key

# qBittorrent — deleteFiles=false removes torrent from qBit but leaves files on disk.
# Radarr/Sonarr manage actual files independently.
    HOST1_QBIT_URL="http://localhost:8080"
    HOST1_QBIT_USERNAME="root"
    HOST1_QBIT_PASSWORD=""        # qBittorrent WebUI password

# ━━━ Lidarr — HOST1 only ━━━
# HOST2 does not run Lidarr — HOST1_LIDARR_RECOVERY flag handles the exit cleanly.
    HOST1_LIDARR_URL="http://localhost:8686"
    HOST1_LIDARR_API_KEY=""      # Lidarr Settings → General → API Key
    HOST1_LIDARR_MUSIC_ROOT="/mnt/user/Music-New"
    HOST1_FANART_API_KEY=""      # fanart.tv account → API Key
    HOST1_LASTFM_API_KEY=""      # last.fm API → Create API Account

    declare -A HOST1_LIDARR_PATH_MAP=(
        ["/ext-music"]="/mnt/user/Music-New"
    )

# ━━━ Sonarr ━━━
    HOST1_SONARR_URL="http://localhost:8989"
    HOST1_SONARR_API_KEY=""      # Sonarr Settings → General → API Key
    HOST1_SONARR_TV_ROOT="/mnt/user/Tv_Shows"

# Note: stand-up_comedy in both Sonarr + Radarr — TV specials and movie specials, one folder
    declare -A HOST1_SONARR_PATH_MAP=(
        ["/tv"]="/mnt/user/Tv_Shows"
        ["/ext-standup-comedy"]="/mnt/user/stand-up_comedy"
        ["/kids tv"]="/mnt/user/Kids_Tv_Shows"
        ["/ext-anime-shows"]="/mnt/user/Anime_Shows-Old"
    )

# ━━━ Radarr ━━━
    HOST1_RADARR_URL="http://localhost:7878"
    HOST1_RADARR_API_KEY=""      # Radarr Settings → General → API Key
    HOST1_TMDB_API_KEY=""          # themoviedb.org → Settings → API
    HOST1_RADARR_MOVIES_ROOT="/mnt/user/Movies"

# Note: stand-up_comedy in both Radarr + Sonarr — movie specials and TV specials, one folder
    declare -A HOST1_RADARR_PATH_MAP=(
        ["/movies"]="/mnt/user/Movies"
        ["/kids movies"]="/mnt/user/Kids_Movies"
        ["/ext-stand-up-comedy"]="/mnt/user/stand-up_comedy"
        ["/anime-movies"]="/mnt/user/Anime_Movies-Old"
    )

# ━━━ Arr Recovery Toggles ━━━
# false = skip that arr on this host — exits cleanly without error
    HOST1_SONARR_RECOVERY=true
    HOST1_RADARR_RECOVERY=true
    HOST1_LIDARR_RECOVERY=true      # HOST1 only — exits cleanly on HOST2

# ==============================================================================================
# ── SYSTEM WATCHDOG ───────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# Per-host check toggles and NIC config for system_watchdog.sh.
# Aliased by detect_hosts() — script uses unprefixed SYS_WATCHDOG_* names.
# HOST1: TR1950X 128GB — full media server, active transcoding, ZFS cache pools.
#
# Three-tier response — all critical checks enabled by default on HOST1:
#   Tier 1 (bypass strikes, reboot now):  docker daemon, rootfs full, kernel oops, FD, /boot
#   Tier 2 (bypass strikes with OOM):     RAM critical + OOM kills in cycle
#   Tier 3 (standard strike system):      everything else
#
# RAM tiers, OOM limits, and reboot loop settings in master.conf System Watchdog section.

# ━━━ Primary NIC ━━━
# Network interface for NIC state check — verify with: ip link show | grep "^[0-9]"
# Common values: eth0, bond0, br0, eno1
    HOST1_SYS_WATCHDOG_NIC="eth0"

# ━━━ Tier 1 — Critical Checks ━━━
# These bypass the strike system — a single hit triggers immediate reboot.
# Disabling any of these is not recommended — they protect against acute system failure.

# Docker daemon unresponsive → try restart, reboot if restart fails.
# Without a working daemon docker_watchdog.sh is blind and containers cannot be managed.
    HOST1_SYS_WATCHDOG_CHECK_DOCKER_DAEMON=true

# rootfs at critical threshold (ROOTFS_CRITICAL_PCT=99) → reboot immediately.
# At 99% rootfs writes fail silently — logs stop, Docker errors out, SSH may stop working.
# Standard 95% threshold still uses strike system — only 99%+ is critical tier.
    HOST1_SYS_WATCHDOG_CHECK_ROOTFS=true

# Kernel BUG/Oops in dmesg delta since last cycle → reboot immediately.
# A kernel oops means the kernel ran with a corrupted state — stability is not guaranteed.
    HOST1_SYS_WATCHDOG_CHECK_KERNEL_OOPS=true

# File descriptor exhaustion at FD_CRITICAL_PCT (95%) → reboot immediately.
# At 95% FD: new connections fail, Docker can't spawn processes, SSH drops.
    HOST1_SYS_WATCHDOG_CHECK_FD=true

# /boot read-only detected → reboot immediately.
# Unexpected read-only /boot means state files and config writes are silently failing.
# Fallback state, watchdog reboot log, and lock files all go stale silently.
    HOST1_SYS_WATCHDOG_CHECK_BOOT=true

# ━━━ Tier 2 — Urgent OOM Check ━━━
# Bypass strikes when RAM is critically low AND OOM kill rate confirms active crisis.
# Both must be enabled for Tier 2 bypass to function — disable either to always use strikes.

# Track kernel OOM kills each cycle via /proc/vmstat oom_kill delta.
# Also provides diagnostic context in reboot messages (which processes were killed).
    HOST1_SYS_WATCHDOG_CHECK_OOM=true

# Free RAM check — required for both Tier 2 bypass and RAM tier logic.
# Tiers: MEM_WARN_GB(10) → notify | MEM_SHUTDOWN_GB(6) → stop containers | MEM_GB(4) → strikes
    HOST1_SYS_WATCHDOG_CHECK_RAM=true

# ━━━ Tier 3 — Standard Checks (strike system) ━━━
# Each check must fail SYS_WATCHDOG_STRIKE_LIMIT consecutive cycles before action is taken.
# Single spikes are ignored — sustained problems trigger reboot.

# /var/log filesystem usage above SYS_WATCHDOG_LOG_PCT.
# Log spam (Docker log storms, syslog loops) fills rootfs — indicates something broken.
    HOST1_SYS_WATCHDOG_CHECK_LOG=true

# ZFS ARC memory pinned above SYS_WATCHDOG_ARC_PINNED_PCT after cache drop.
# Enabled on HOST1 — ZFS cache pools actively used. Disable on hosts without ZFS.
    HOST1_SYS_WATCHDOG_CHECK_ARC=true

# CPU temperature above SYS_WATCHDOG_CPU_TEMP_MAX (95°C).
# Sustained high temp causes kernel throttling or panic. Requires lm-sensors.
    HOST1_SYS_WATCHDOG_CHECK_CPU_TEMP=true

# Load average above SYS_WATCHDOG_LOAD_MULTIPLIER × core count.
# DISABLED on HOST1 — Tdarr and Emby cause legitimate sustained load spikes during encoding.
# Enable on idle servers or adjust SYS_WATCHDOG_LOAD_MULTIPLIER if load is always high.
    HOST1_SYS_WATCHDOG_CHECK_LOAD=false

# Zombie process count above SYS_WATCHDOG_ZOMBIE_LIMIT (50).
# Large zombie counts indicate serious process management failure — something is stuck.
    HOST1_SYS_WATCHDOG_CHECK_ZOMBIES=true

# Check docker_watchdog.sh persistent skip list — required containers on skip list.
# Cross-watchdog coordination: if docker_watchdog gave up, system_watchdog escalates.
# ENABLED — HOST1 fully built and operational, skip list is meaningful.
    HOST1_SYS_WATCHDOG_CHECK_CONTAINERS=true

# /tmp filesystem usage above SYS_WATCHDOG_TMP_PCT with auto-clear attempt.
# Script tries to clear aged /tmp files first — only strikes if clear fails.
# Lock files, rsync temp files, and Docker ops use /tmp — 100% means lock failures.
    HOST1_SYS_WATCHDOG_CHECK_TMP=true

# Array disk error count delta in /proc/mdstat — accumulating errors = disk failing now.
# Triggers on SYS_WATCHDOG_MDSTAT_ERROR_LIMIT new errors in one cycle.
    HOST1_SYS_WATCHDOG_CHECK_MDSTAT=true

# Primary NIC operstate — detects NIC going down (physical or driver failure).
# Uses HOST1_SYS_WATCHDOG_NIC above. Strike system — brief flaps don't trigger reboot.
    HOST1_SYS_WATCHDOG_CHECK_NETWORK=true

# sshd running check — attempts restart before escalating.
# sshd down = no remote access. Script tries rc.sshd start, notifies, strikes on failure.
    HOST1_SYS_WATCHDOG_CHECK_SSHD=true

# Runaway process detection — single process above SYS_WATCHDOG_RUNAWAY_CPU_PCT sustained.
# DISABLED — Tdarr encoding and Emby transcoding legitimately peg CPU for extended periods.
# Enable only if HOST1 has no CPU-intensive workloads.
    HOST1_SYS_WATCHDOG_CHECK_RUNAWAY=false

# ==============================================================================================
# ── RESOURCE MANAGER ──────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# Containers to manage under pressure — see master.conf RW_CRITICAL_CONTAINERS for exclusions.

# docker pause at medium pressure (RAM < RW_RAM_MEDIUM_GB or load > medium threshold)
# Suspended in-place — instant to pause/unpause, no state lost, no restart delay.
    HOST1_RW_PAUSE_CONTAINERS=(
        "Huntarr"                       # arr search automation — safe to suspend
        "Cleanuparr"                    # download cleanup — safe to suspend
        "Healarr"                       # arr health checks — safe to suspend
        "Soularr"                       # Slskd automation — background only
        "ChannelTube"                   # YouTube archiver — background only
        "Pinchflat"                     # YouTube archiver — background only
    )

# docker stop at hard pressure (RAM < RW_RAM_HARD_GB)
# Full stop — these are optional/heavy services that free significant RAM when stopped.
# resource_watchdog.sh restarts them when pressure fully clears (RAM >= RW_RAM_RECOVER_GB).
    HOST1_RW_STOP_CONTAINERS=(
        "LocalAI"                       # GPU/CPU heavy — largest RAM consumer when idle
        "7DaysToDie"                    # game server — optional
        "V-Rising"                      # game server — optional
        "Code-Server"                   # IDE — not needed during pressure events
    )

# ==============================================================================================
# ──────────────────────── End Of HOST1 Variables ──────────────────────────────────────────────
# ==============================================================================================


Now let me read the key onboarding/setup related files in parallel.