Files
Varaverk/Master.conf
T

1030 lines
45 KiB
Bash

#!/bin/bash
# ==============================================================================================
# ================================= MASTER CONFIGURATION =======================================
# ==============================================================================================
# All user-facing variables for the unRAID script ecosystem.
# Scripts source this file — edit here, changes apply everywhere on next git pull.
#
# ── HOW THIS FILE WORKS ───────────────────────────────────────────────────────────────────────
# Every script sources Master.conf and common.sh at startup.
# Change a value here and it affects all scripts that use it — no hunting through files.
# To disable something: comment it out with # rather than deleting it.
# To add a new rsync profile: add a key to each PROFILE_* array.
# To add or remove orchestrator jobs: edit the arrays in the ORCHESTRATORS section.
#
# ── INDEX ─────────────────────────────────────────────────────────────────────────────────────
#
# Section Description
# ───────────────────────────────────────────────────────────────────────────────────────────
# HOST CONFIGURATION Server hostnames, SSH keys, and Emby connection details
# LOGGING Enable or disable verbose logging
# NOTIFICATIONS unRAID native and Discord webhook settings
# GIT / REPO Gitea repository and SSH settings
#
# ── ORCHESTRATORS ──────────────────────────────────────────────────────────────────────────
# ARRAY START Scripts launched at array start (array_start.sh)
# DAILY SYNC MAINTENANCE Job list + media shares (daily_sync_maintenance.sh)
# WEEKLY SYNC MAINTENANCE Job list + sync jobs + sync settings (weekly_sync_maintenance.sh)
# MEDIA MANAGEMENT Job list for media_management.sh
#
# ── RSYNC ──────────────────────────────────────────────────────────────────────────────────
# RSYNC DEFAULTS Global fallback rsync options and limits
# REMOTE HEALTH CHECKS Rootfs threshold for pre-flight abort
# RSYNC PROFILE SYSTEM Per-profile overrides for appdata syncs
#
# ── FAILOVER ───────────────────────────────────────────────────────────────────────────────
# FAILOVER Mutual container failover between two servers
# FAILOVER TEST Simulated outage settings for failover_test.sh
# DDNS Script-controlled DDNS — absolute rules
# INTERNET LOSS Containers to stop when internet is lost
# TIERED CONTAINER LISTS What each server runs for the other per tier
# TIER DELAY SETTINGS How long before each tier activates (minutes)
# RSYNC WRITEBACK JOBS Appdata synced back to primary on handback
#
# ── DOCKER ESSENTIALS ──────────────────────────────────────────────────────────────────────
# DOCKER DAILY RESTART Containers restarted daily
# DOCKER WEEKLY RESTART Containers restarted weekly
# DOCKER WATCHDOG Continuous two-tier self-healing container monitoring
# DOCKER NETWORK CONNECT Connect containers to extra networks on array start
#
# ── UNRAID ESSENTIALS ──────────────────────────────────────────────────────────────────────
# REBOOT User warning delay before scheduled reboot
# MOVER Mover stop timeout
# SYSLOG FILTER Docker veth noise filter file path
# PHP-FPM PHP-FPM max children config
# CLEAR LOGS System log file paths
# WEBGUI WATCHDOG WebGUI nginx + emhttp monitoring and restart
#
# ── MEDIA ──────────────────────────────────────────────────────────────────────────────────
# MEDIA PERMISSIONS Share list, mode and owner for permissions script
# MEDIA CLEANER Anime and media folder lists and file patterns
# ARR CLEANUP Lidarr, Sonarr, Radarr orphan file cleanup
# ARR FAILED/STALLED RECOVERY Auto blocklist + re-search failed imports and stalled downloads
#
# ── TRANSCODES ─────────────────────────────────────────────────────────────────────────────
# TRANSCODE MANAGER Ramdisk and SSD fallback transcode management
# TRANSCODE SERVER ARRAY Multi-server session monitoring (Emby, Jellyfin, Plex)
#
# ── MONITORS ───────────────────────────────────────────────────────────────────────────────
# CERTIFICATE MONITOR SSL certificate expiry monitoring
# BACKUP VERIFY Random sample checksum verification against remote
# SMART HEALTH Drive SMART attribute monitoring
# ZFS MEMORY SNAPSHOT Weekly ZFS health and memory diagnostic report
# BANDWIDTH MONITOR Daily rsync transfer logging and weekly summary
# HEALTH DIGEST Aggregated system health digest — always/smart/weekly
# EMBY SESSION REPORT Weekly Emby usage statistics via API
#
# ── SYSTEM WATCHDOG ────────────────────────────────────────────────────────────────────────
# SYSTEM WATCHDOG Continuous system health monitoring — last line of defense
#
# ==============================================================================================
# ==============================================================================================
# ── HOST CONFIGURATION ────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# ━━━ Hosts ━━━
# Hostnames must match exact Docker/unRAID hostnames — case sensitive.
# Used by detect_hosts() in common.sh to determine which server is local and which is remote.
# Both servers run identical scripts — host detection makes them bidirectional.
HOST1="unRAID-Gmer4Lfe"
HOST2="unRAID-Jayred365"
# SSH keys for server-to-server rsync and failover container operations.
# Both keys must be in /root/.ssh/ and authorised in the remote server's authorized_keys.
HOST1_SSH_KEY="/root/.ssh/Gmer4Lfe-rsync-key"
HOST2_SSH_KEY="/root/.ssh/Jayred365-rsync-key"
# ━━━ Emby ━━━
# Defined once here — referenced by transcode_manager.sh, emby_session_report.sh,
# emby_database_repair.sh, weekly_sync_maintenance.sh, and TRANSCODE_SERVERS array.
# API key: Emby Dashboard → API Keys → + New Key
HOST1_EMBY_CONTAINER="Emby"
HOST1_EMBY_URL="http://localhost:8096"
HOST1_EMBY_API_KEY="0c27448d93a7431f9ac63569f7655829"
HOST2_EMBY_CONTAINER="Emby-Jayred365"
HOST2_EMBY_URL="http://localhost:8096" # same port — different server, different key
HOST2_EMBY_API_KEY="your-host2-emby-api-key"
# ==============================================================================================
# ── LOGGING ───────────────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# Controls verbose [LOG] output across all scripts.
# true = show detailed [LOG] lines — useful for debugging or first-time setup
# false = show only user-facing output — cleaner for scheduled runs
ENABLE_LOGGING=true
# ==============================================================================================
# ── NOTIFICATIONS ─────────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# unRAID native notification system — integrates with the bell icon in the WebGUI.
# normal = job completed successfully / warning = something failed or needs attention
NOTIFY_UNRAID=true
# Discord webhook URL — leave blank to disable
DISCORD_WEBHOOK=""
# ==============================================================================================
# ── GIT / REPO ────────────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# Gitea self-hosted repository — used by git_pull_execute.sh.
# Detects Gitea container location at runtime — works through failover automatically.
# Falls back to GITEA_DOMAIN if local and Tailscale both fail.
GITEA_CONTAINER="Gitea"
GITEA_REPO_PATH="FailedProxy/Unraid_Scripts.git"
GITEA_DOMAIN="" # e.g. git.gmer4lfe.com — requires NPM + DNS
TARGET_DIR="/mnt/user/appdata/unraid_scripts"
GITEA_SSH_KEY="/root/.ssh/unraid_gitea"
SSH_PORT=221
# ==============================================================================================
# ── ORCHESTRATORS ──────────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# All orchestrator job lists live here — edit arrays to add/remove scripts.
# No changes to orchestrator scripts needed when adding or removing jobs.
# ━━━ Array Start ━━━
# Scripts launched by array_start.sh when the array comes online.
# Launched in order — each as a background process.
# One-shot scripts (ramdisk, syslog, fpm, network) run and exit naturally.
# Continuous scripts (watchdogs, failover) run until array stops.
ARRAY_START_SCRIPTS=(
"Transcodes/ramdisk_setup.sh" # creates ramdisk + symlink before Emby starts
"unRAID_Essentials/docker_syslog_filter.sh" # suppress veth noise before logs fill
"unRAID_Essentials/php_fpm_max_children.sh" # WebGUI performance tuning
"Docker_Essentials/docker_network_connect.sh" # connect containers to extra networks
"unRAID_Essentials/system_watchdog.sh" # system health monitor — continuous loop
"Docker_Essentials/docker_watchdog.sh" # container health monitor — continuous loop
"Failover/failover.sh" # mutual failover — continuous loop
)
# ━━━ Daily Sync Maintenance ━━━
# daily_sync_maintenance.sh runs the media share sync built into the script first,
# then iterates DAILY_MAINTENANCE_SCRIPTS for additional jobs.
# Schedule: 0 1 * * * (1am daily)
DAILY_MAINTENANCE_SCRIPTS=(
"git_pull_execute.sh" # pull latest scripts — always runs first
"Docker_Essentials/docker_daily_restart.sh" # daily container restarts
)
# Media shares synced daily by daily_sync_maintenance.sh.
# Each server syncs only the shares it owns (source of truth) — direction is automatic.
# HOST1 pushes its truth shares to HOST2. HOST2 pushes its truth shares to HOST1.
# Never both pushing the same share — one server is always the truth holder.
# These shares use DEFAULT_RSYNC_OPTS — no profile entry needed.
# For shares needing custom options or container stops — create a profile in the RSYNC section.
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
)
HOST2_DAILY_SYNC_SHARES=(
/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/Gmer4Lfe-Personal # uncomment after creating encrypted dataset
)
HOST2_PERSONAL_SHARES=(
# /mnt/user/Jayred365-Personal # uncomment after creating encrypted dataset
)
# ━━━ Weekly Sync Maintenance ━━━
# weekly_sync_maintenance.sh handles the critical sync built into the script first:
# stop containers both sides → pull updates → sync Emby + Critical-Data → restart
# Then iterates WEEKLY_MAINTENANCE_SCRIPTS for additional jobs.
# Schedule: 30 2 * * 0 (Sunday 2:30am)
WEEKLY_MAINTENANCE_SCRIPTS=(
"Docker_Essentials/docker_weekly_restart.sh" # weekly container restarts after sync
)
# Shares synced by weekly_sync_maintenance.sh during the maintenance window.
# Containers are stopped both sides before these sync — full clean state guaranteed.
# Profiles drive container stops, excludes, and options — configure in RSYNC section.
# Order matters — Emby first, then auth stack.
WEEKLY_SYNC_JOBS=(
"/mnt/user/Media_Server/Emby" # emby profile — full clean mirror
"/mnt/user/appdata-Failover/Critical-Data" # critical-data profile — auth stack
)
# Container update toggles for the weekly sync window.
# Containers are already stopped for the sync — updates pull at no extra downtime.
# Both false → sync only, no updates.
# Toggle false temporarily to skip updates without changing the schedule.
CRITICAL_SYNC_UPDATES=true # pull container updates locally
CRITICAL_SYNC_UPDATES_REMOTE=true # pull container updates on remote via SSH
# ━━━ Media Management ━━━
# Job list run directly by daily_sync_maintenance.sh after the media share sync.
# Runs sequentially — permissions first, then cleaners, then arr cleanup.
# Comment out any job to disable without removing it.
# Each individual script can still be run manually for one-off maintenance.
MEDIA_MANAGEMENT_JOBS=(
"Media/media_shares_permissions.sh" # apply permissions — runs first
"Media/media_cleaner.sh anime" # remove junk from anime shares
"Media/media_cleaner.sh media" # remove junk from media shares
"Media/lidarr_cleanup.sh" # remove orphaned music files
"Media/sonarr_cleanup.sh" # remove orphaned TV files
"Media/radarr_cleanup.sh" # remove orphaned movie files
)
# ==============================================================================================
# ── RSYNC ─────────────────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# ━━━ Rsync Defaults ━━━
# Global fallback values used when no profile match is found.
# Media shares in HOST*_DAILY_SYNC_SHARES always use these globals — no profile needed.
# Appdata shares match profiles by directory basename (lowercased).
BW_LIMIT=12500
RETRY_COUNT=3
SLEEP=300
CRITICAL_CONTAINER_NAMES=()
DELAYED_CONTAINERS=()
CONTAINER_DELAY=5
EXCLUDE_DIRS=()
DEFAULT_RSYNC_OPTS=(-av --info=progress2 --human-readable --bwlimit="$BW_LIMIT" --delete --inplace --no-whole-file)
# ━━━ Remote Health Checks ━━━
# Pre-flight check — aborts if remote rootfs (/) usage is at or above this percentage.
# When remote array is down, rsync writes land on rootfs — fills fast and crashes the server.
ROOTFS_WARN=75
# ━━━ Rsync Profile System ━━━
# Profiles allow per-share rsync behaviour without touching script logic.
# Profile key matched by basename of directory passed to rsync.sh (lowercased).
# Override with --profile=name flag.
#
# IMPORTANT: PROFILE_RSYNC_OPTS does NOT inherit DEFAULT_RSYNC_OPTS.
# List ALL desired options explicitly when defining a profile.
#
# Current profiles:
# arrs_stack — arr databases — lower bandwidth, containers stopped for consistency
# critical-data — auth stack — containers stopped both sides, Authelia delayed start
# gmer4lfe — server-specific appdata — no container stops needed
# important-data — NextCloud + Postgres — NextCloud delayed start after Postgres
# emby — weekly clean sync — both Emby stopped, full mirror, minimal excludes
# called by weekly_sync_maintenance.sh only — do NOT schedule separately
# emby-failover — frequent dirty sync — Emby stays running, WAL excluded, critical data only
# also used for failover writeback on handback
declare -A PROFILE_RSYNC_OPTS=(
[arrs_stack]="-av --info=progress2 --human-readable --bwlimit=$BW_LIMIT --delete --inplace"
[critical-data]="-av --human-readable --bwlimit=$BW_LIMIT --delete"
[gmer4lfe]="-av --info=progress2 --bwlimit=$BW_LIMIT"
[important-data]="-av --human-readable --bwlimit=$BW_LIMIT"
[emby]="-av --human-readable --bwlimit=$BW_LIMIT --delete --inplace --no-whole-file"
[emby-failover]="-av --human-readable --bwlimit=$BW_LIMIT --delete --inplace --no-whole-file"
)
declare -A PROFILE_BW_LIMIT=(
[arrs_stack]=5000
[critical-data]=9500
[gmer4lfe]=8000
[important-data]=9500
[emby]=8000
[emby-failover]=9500
)
declare -A PROFILE_RETRY_COUNT=(
[arrs_stack]=3
[critical-data]=3
[gmer4lfe]=3
[important-data]=3
[emby]=3
[emby-failover]=3
)
declare -A PROFILE_SLEEP=(
[arrs_stack]=300
[critical-data]=300
[gmer4lfe]=300
[important-data]=300
[emby]=300
[emby-failover]=120
)
# Containers stopped on BOTH LOCAL and REMOTE servers before rsync.
# Local stops first — flushes databases cleanly. Remote stops next — prevents writes while receiving.
# Same container names on both servers — consistent naming is required by this ecosystem.
# If a container is not found it is skipped gracefully, not errored.
# SPACE-SEPARATED STRINGS — converted to array at runtime
declare -A PROFILE_CRITICAL_CONTAINER_NAMES=(
[arrs_stack]="Sonarr Lidarr Readarr Radarr Prowlarr Bazarr Pinchflat"
[critical-data]="Mariadb-Authelia Mariadb-Authelia-Secondary Redis-Authelia Redis-Authelia-Secondary Lldap-Gmer4Lfe NginxProxyManager Authelia Authelia-Secondary"
[gmer4lfe]="Organizrv2-Gmer4Lfe UptimeKuma-Gmer4Lfe VaultWarden-Gmer4Lfe"
[important-data]="Postgres-NextCloud NextCloud"
[emby]="Emby"
[emby-failover]=""
)
# SPACE-SEPARATED STRINGS — converted to array at runtime
declare -A PROFILE_DELAYED_CONTAINERS=(
[arrs_stack]=""
[critical-data]="Authelia Authelia-Secondary"
[gmer4lfe]=""
[important-data]="NextCloud"
[emby]=""
[emby-failover]=""
)
declare -A PROFILE_CONTAINER_DELAY=(
[arrs_stack]=5
[critical-data]=15
[gmer4lfe]=5
[important-data]=10
[emby]=5
[emby-failover]=5
)
# SPACE-SEPARATED STRINGS — converted to array at runtime
declare -A PROFILE_EXCLUDE_DIRS=(
[arrs_stack]="logs *.tmp"
[gmer4lfe]="logs *.tmp"
[important-data]="logs *.tmp"
[critical-data]="logs *.tmp *.log nginx/temp nginx/cache __pycache__ notification.txt"
[emby]="logs transcodes cache crash*"
[emby-failover]="logs transcodes cache metadata *.db-wal *.db-shm crash* plugins root"
)
declare -A PROFILE_SKIP_DISK_CHECK=(
[arrs_stack]=true
[critical-data]=true
[gmer4lfe]=true
[important-data]=true
[emby]=true
[emby-failover]=true
)
# ==============================================================================================
# ── FAILOVER ──────────────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# Mutual container failover between two unRAID servers.
# Each server runs Failover/failover.sh independently via array_start.sh.
# All decisions based on two pings: remote reachable + internet reachable.
#
# States: NORMAL | FAILOVER | NO_INTERNET | DARK
#
# DDNS rules — absolute:
# Internet loss → stop own DDNS immediately
# Failover → start remote DDNS first (Tier 1)
# Handback → stop remote DDNS → rsync → start containers → start local DDNS last
#
# Tiers:
# Tier 1 — Immediate — vital services + Live TV
# Tier 2 — configurable delay — productivity services
# Tier 3 — configurable delay — secondary services
# Tier 4 — configurable delay — arrs + downloaders
EXTERNAL_IP="8.8.8.8"
FAILOVER_CHECK_INTERVAL=120
FAILOVER_HANDBACK_STRIKES=2
FAILOVER_STATE_FILE="/boot/config/failover_state.db"
# ━━━ Failover Test ━━━
FAILOVER_TEST_BLOCK_WAIT=150
FAILOVER_TEST_HANDBACK_WAIT=360
# ━━━ DDNS ━━━
HOST1_DDNS_CONTAINERS=(
"Gmer4Lfe.com"
)
HOST2_DDNS_CONTAINERS=(
"Gmer4Lfe.us"
)
# ━━━ Internet Loss ━━━
FAILOVER_HOST1_STOP_ON_NO_NET=(
"Gmer4Lfe.com"
)
FAILOVER_HOST2_STOP_ON_NO_NET=(
"Gmer4Lfe.us"
)
# ━━━ Tiered Container Lists ━━━
# HOST1 runs for HOST2
FAILOVER_HOST1_RUNS_FOR_HOST2_IMMEDIATE=(
"Gmer4Lfe.us"
"VaultWarden-Jayred365"
# "container-placeholder"
)
FAILOVER_HOST1_RUNS_FOR_HOST2_2HR=(
# "container-placeholder"
)
FAILOVER_HOST1_RUNS_FOR_HOST2_6HR=(
# "container-placeholder"
)
FAILOVER_HOST1_RUNS_FOR_HOST2_18HR=(
# "container-placeholder"
)
# HOST2 runs for HOST1
FAILOVER_HOST2_RUNS_FOR_HOST1_IMMEDIATE=(
"Gmer4Lfe.com"
"Emby"
"VaultWarden-Gmer4Lfe"
"Dispatcharr"
"Dispatcharr-Basic"
"Dispatcharr-Iptv-Users"
"ErsatzTV-Emby"
)
FAILOVER_HOST2_RUNS_FOR_HOST1_2HR=(
"Postgres-NextCloud"
"NextCloud"
"PostgreSQL_Immich"
"Immich-Gmer4Lfe"
# "container-placeholder"
)
FAILOVER_HOST2_RUNS_FOR_HOST1_6HR=(
"Gitea"
# "container-placeholder"
)
FAILOVER_HOST2_RUNS_FOR_HOST1_18HR=(
"Sonarr"
"Radarr"
"Lidarr"
"Readarr"
"Prowlarr"
"Bazarr"
"SABnzbd-Gmer4Lfe"
"Qbittorrent-Gmer4Lfe"
"LidaTube"
"Pinchflat"
"ChannelTube"
# "container-placeholder"
)
# ━━━ Tier Delay Settings ━━━
# Minutes before each tier activates. Tier 1 is always immediate.
HOST1_TIER2_DELAY=240
HOST1_TIER3_DELAY=720
HOST1_TIER4_DELAY=1440
HOST2_TIER2_DELAY=240
HOST2_TIER3_DELAY=720
HOST2_TIER4_DELAY=1440
# ━━━ Rsync Writeback Jobs ━━━
# Syncs critical appdata back to primary on handback — containers stopped before this runs.
# Short outages skip writeback — primary state is more reliable than dirty sync data.
# Tier 4 automatically syncs HOST*_DAILY_SYNC_SHARES — add edge cases here only.
HOST1_TIER1_WRITEBACK_DELAY=60
HOST2_TIER1_WRITEBACK_DELAY=60
FAILOVER_HOST1_WRITEBACK_TIER1=(
"/mnt/user/Media_Server/Emby"
)
FAILOVER_HOST1_WRITEBACK_TIER2=(
"/mnt/user/appdata-Failover/Important-Data"
)
FAILOVER_HOST1_WRITEBACK_TIER3=(
# "location-placeholder"
)
FAILOVER_HOST1_WRITEBACK_TIER4=(
"/mnt/user/appdata-Failover/Arrs_Stack"
)
FAILOVER_HOST2_WRITEBACK_TIER1=(
# "/mnt/user/appdata-Failover/Jayred365-Emby"
)
FAILOVER_HOST2_WRITEBACK_TIER2=(
# "/mnt/user/appdata-Failover/Jayred365-Important"
)
FAILOVER_HOST2_WRITEBACK_TIER3=(
# "location-placeholder"
)
FAILOVER_HOST2_WRITEBACK_TIER4=(
"/mnt/user/appdata-Failover/Arrs_Stack"
)
# ==============================================================================================
# ── DOCKER ESSENTIALS ─────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# ━━━ Docker Daily Restart ━━━
# Restarted by docker_daily_restart.sh via daily_sync_maintenance.sh — 1am daily.
DAILY_RESTART_CONTAINERS=(
"NginxProxyManager"
"Authelia"
"Dispatcharr-Iptv-Users"
"Dispatcharr"
"Dispatcharr-Basic"
"ErsatzTV-Emby"
)
# ━━━ Docker Weekly Restart ━━━
# Restarted by docker_weekly_restart.sh via weekly_sync_maintenance.sh — Sunday 2:30am.
WEEKLY_RESTART_CONTAINERS=(
"NextCloud"
"Organizrv2-Gmer4Lfe"
"AdGuard-Home"
"Immich-Gmer4Lfe"
)
# ━━━ Docker Watchdog ━━━
# Continuous two-tier self-healing container monitoring.
# Started by array_start.sh — runs until array stops.
# Re-sources Master.conf each cycle — add/remove containers without restarting watchdog.
# Silent when all healthy — only logs when something needs attention.
#
# Tier 1 — strict monitoring of explicitly configured containers
# Memory hard limits, CPU thresholds, HTTP responsiveness, required container checks
# Tier 2 — global health scan of ALL running containers
# Unhealthy status, OOM kills, crash loops, dead containers, unexpected exits
# Memory hard limits in MB — immediate restart if exceeded
# 20GB=20480 16GB=16384 12GB=12288 8GB=8192 4GB=4096 2GB=2048 1GB=1024
declare -A WATCHDOG_CONTAINERS=(
["Emby"]=16384
["LidaTube"]=6144
["Tdarr"]=6144
["Code-Server"]=1024
)
declare -A HOST1_WATCHDOG_CONTAINER_URLS=(
["Emby"]="http://localhost:8096"
)
declare -A HOST2_WATCHDOG_CONTAINER_URLS=(
["Emby"]="http://localhost:8096"
)
HOST1_WATCHDOG_REQUIRED_CONTAINERS=(
"NginxProxyManager"
"Lldap-Gmer4Lfe"
"Authelia"
"Mariadb-Authelia"
"Redis-Authelia"
"Authelia-Secondary"
"Redis-Authelia-Secondary"
)
HOST2_WATCHDOG_REQUIRED_CONTAINERS=(
"NginxProxyManager"
# add HOST2 required containers here
)
WATCHDOG_STATE_FILE="/tmp/container_watchdog_state.db"
SOFT_CPU_THRESHOLD=80
HARD_CPU_THRESHOLD=85
CPU_FAIL_LIMIT=2
SOFT_MEM_THRESHOLD=80
RESP_FAIL_LIMIT=2
CURL_TIMEOUT=5
DOCKER_WATCHDOG_INTERVAL=900
WATCHDOG_SCAN_ALL=true
WATCHDOG_SCAN_IGNORE=(
# "container-name"
)
WATCHDOG_RESTART_UNHEALTHY=true
WATCHDOG_RESTART_DEAD=true
WATCHDOG_RESTART_CRASHED=true
WATCHDOG_NOTIFY_OOM=true
WATCHDOG_NOTIFY_CRASHLOOP=true
WATCHDOG_CRASH_LIMIT=5
WATCHDOG_STARTUP_GRACE=600
WATCHDOG_CONTAINER_RESTART_LIMIT=3
WATCHDOG_CONTAINER_RESTART_WINDOW=1
WATCHDOG_CONTAINER_RESTART_LOG="/boot/config/container_restart_history.db"
WATCHDOG_BATCH_NOTIFY=true
declare -A WATCHDOG_DEPENDENCIES=(
["Authelia"]="Mariadb-Authelia Redis-Authelia"
["Authelia-Secondary"]="Mariadb-Authelia Redis-Authelia-Secondary"
["NextCloud"]="Postgres-NextCloud"
)
# ━━━ Docker Network Connect ━━━
# Connects containers to extra networks on array start via array_start.sh.
NETWORK_CONNECT_CONTAINERS=(
"memcached"
"Npm-CrowdSec"
)
NETWORK_CONNECT_NETWORKS=(
"high-availability"
)
# ==============================================================================================
# ── UNRAID ESSENTIALS ─────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# ━━━ Reboot ━━━
REBOOT_SLEEP=300
# ━━━ Mover ━━━
MOVER_STOP_TIMEOUT=300
# ━━━ Syslog Filter ━━━
FILTER_FILE="/etc/rsyslog.d/ignore-docker-veth.conf"
# ━━━ PHP-FPM ━━━
PHP_CONF="/etc/php-fpm.d/www.conf"
PHP_MAX_CHILDREN=250
# ━━━ Clear Logs ━━━
LOG_FILES=(/var/log/syslog /var/log/messages /var/log/dmesg)
# ━━━ WebGUI Watchdog ━━━
WEBGUI_URL="http://localhost"
WEBGUI_TIMEOUT=5
WEBGUI_NGINX_WAIT=15
WEBGUI_EMHTTP_WAIT=30
# ==============================================================================================
# ── MEDIA ─────────────────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# ━━━ Media Permissions ━━━
# Applied recursively by media_shares_permissions.sh via MEDIA_MANAGEMENT_JOBS.
PERMISSIONS_MODE="777"
PERMISSIONS_OWNER="nobody:users"
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 ━━━
# Removes junk files from media shares — two profiles: anime and media.
# Called via MEDIA_MANAGEMENT_JOBS. Run manually: Media/media_cleaner.sh anime|media
ANIME_CLEAN_FOLDERS=(
/mnt/user/Anime_Movies
/mnt/user/Anime_Movies-Old
/mnt/user/Anime_Shows
/mnt/user/Anime_Shows-Old
)
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
)
ANIME_FILE_PATTERNS=(
'*.sfv' '*.md5' '*.sha1' '*.txt' '*.url' '*.lnk'
'*.rar' '*.zip' '*.info' '*.torrent' '*.sample*' '*.proof*'
'*sync-conflict*' '*.scr' '*.srr' '*.exe' '*.webp'
'*.log' '*.json'
)
MEDIA_FILE_PATTERNS=(
'*.sfv' '*.md5' '*.sha1' '*.txt' '*.url' '*.lnk'
'*.rar' '*.zip' '*.info' '*.torrent' '*.sample*' '*.proof*'
'*sync-conflict*' '*.scr' '*.srr' '*.exe' '*.webp'
'*.log' '*.json' '*.iso' '*.lrc'
)
# ━━━ Arr Cleanup ━━━
# Orphan file cleanup via Lidarr, Sonarr, and Radarr APIs.
# Compares tracked file paths from API against disk — deletes untracked files older than ORPHAN_AGE.
# detect_hosts() selects correct URL, API key, and root path at runtime.
#
# Protected patterns are NEVER deleted — cover art, metadata, subtitles generated by the arr
# are not included in the tracked file API response but must not be deleted.
#
# API versions: Sonarr v4 → /api/v3/ Radarr v6 → /api/v3/ Lidarr v3 → /api/v1/
# Lidarr runs on HOST1 only.
# ── Lidarr ────────────────────────────────────────────────────────────────────────────────────
HOST1_LIDARR_URL="http://192.168.50.2:8686"
HOST1_LIDARR_API_KEY="b2977e71ef074bc0a0529d9fcce3b2dc"
HOST1_LIDARR_MUSIC_ROOT="/mnt/user/Music-New"
# Container path → host path translation
# Lidarr stores file paths using container paths — script scans host paths
# Add one entry per root folder configured in Lidarr Settings → Media Management → Root Folders
declare -A HOST1_LIDARR_PATH_MAP=(
["/ext-music"]="/mnt/user/Music-New"
)
declare -A HOST2_LIDARR_PATH_MAP=(
# HOST2 does not run Lidarr — fill in if that changes
# ["/ext-music"]="/mnt/user/Music-New"
)
LIDARR_ORPHAN_AGE=7
LIDARR_EXTENSIONS=("flac" "mp3" "m4a" "wav" "aac" "ogg" "opus" "wma")
LIDARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo" "*.lrc")
LIDARR_MAX_DELETE_GB=1 # require --i-know-what-im-doing if deletion exceeds this
LIDARR_MIN_TRACKED_PCT=80 # abort if tracked count drops below this % of last run
LIDARR_TRACKED_COUNT_FILE="/boot/config/lidarr_tracked.count"
# ── Sonarr ────────────────────────────────────────────────────────────────────────────────────
HOST1_SONARR_URL="http://192.168.50.2:8989"
HOST1_SONARR_API_KEY="130decd3db5b4c25afad64864cd03f9f"
HOST1_SONARR_TV_ROOT="/mnt/user/Tv_Shows"
# Container path → host path translation
# Add one entry per root folder configured in Sonarr Settings → Media Management → Root Folders
# 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"
)
HOST2_SONARR_URL="http://localhost:8989"
HOST2_SONARR_API_KEY="your-host2-sonarr-api-key"
HOST2_SONARR_TV_ROOT="/mnt/user/Anime_Shows"
declare -A HOST2_SONARR_PATH_MAP=(
# Fill in when HOST2 is back online
# ["/tv"]="/mnt/user/Anime_Shows"
)
SONARR_ORPHAN_AGE=7
SONARR_EXTENSIONS=("mkv" "mp4" "avi" "m4v" "ts" "wmv" "mov")
SONARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo" "*.srt" "*.sub" "*.ass" "*.ssa")
# ── Radarr ────────────────────────────────────────────────────────────────────────────────────
HOST1_RADARR_URL="http://192.168.50.2:7878"
HOST1_RADARR_API_KEY="d43a3ec6cf1549edb4af0cc63f98b2a9"
HOST1_RADARR_MOVIES_ROOT="/mnt/user/Movies"
# Container path → host path translation
# Add one entry per root folder configured in Radarr Settings → Media Management → Root Folders
# 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"
)
HOST2_RADARR_URL="http://localhost:7878"
HOST2_RADARR_API_KEY="your-host2-radarr-api-key"
HOST2_RADARR_MOVIES_ROOT="/mnt/user/Anime_Movies"
declare -A HOST2_RADARR_PATH_MAP=(
# Fill in when HOST2 is back online
# ["/anime-movies"]="/mnt/user/Anime_Movies"
)
RADARR_ORPHAN_AGE=7
RADARR_EXTENSIONS=("mkv" "mp4" "avi" "m4v" "wmv" "mov")
RADARR_PROTECTED_PATTERNS=("*.jpg" "*.jpeg" "*.png" "*.nfo" "*.srt" "*.sub" "*.ass" "*.ssa")
# ━━━ Arr Failed/Stalled Recovery ━━━
# Auto blocklist + re-search failed imports and stalled downloads.
# Runs every 6 hours — schedule: 0 */6 * * *
#
# Targets four problem types:
# importFailed — downloaded but arr couldn't import
# importPending — downloaded, stuck waiting to import (won't self-resolve)
# error status — serious failure not covered above
# stalled — download stuck with no connections or progress
#
# Items newer than ARR_IMPORT_RECOVERY_AGE are skipped — gives arr time to retry first.
# API versions: Sonarr /api/v3/ — Radarr /api/v3/ — Lidarr /api/v1/
# Lidarr runs on HOST1 only — exits cleanly on HOST2.
ARR_IMPORT_RECOVERY_AGE=6 # hours — skip items newer than this
HOST1_SONARR_RECOVERY=true
HOST1_RADARR_RECOVERY=true
HOST1_LIDARR_RECOVERY=true # HOST1 only
HOST2_SONARR_RECOVERY=true
HOST2_RADARR_RECOVERY=true
# ==============================================================================================
# ── TRANSCODES ────────────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# Session-based storage allocator using filesystem symlink indirection.
# ffmpeg resolves the symlink ONCE at session start — existing sessions are never affected.
#
# How it works:
# ramdisk_setup.sh — creates tmpfs and symlink at array start via array_start.sh
# transcode_management.sh — every 3min, runs cleanup then manager in correct order
# transcode_cleanup.sh — removes old inactive files
# transcode_manager.sh — manages symlink direction based on usage thresholds
#
# ⚠️ Docker mount — must use shared propagation:
# --mount type=bind,source=/mnt/ram-transcode,target=/ext-ram-transcode,bind-propagation=shared
# Standard rprivate bind mounts lock the inode — sessions drift to SSD permanently.
# ━━━ Transcode Manager ━━━
RAMDISK_PATH="/mnt/ramdisk_transcodes"
RAMDISK_SIZE="8G"
TRANSCODE_LINK="/mnt/ram-transcode"
TRANSCODE_SSD="/mnt/cache/Temp_Storage/Emby/Transcodes/"
RAMDISK_WARN_GB=6.8
RAMDISK_LOW_GB=5.5
RAMDISK_SSD_MIN_GB=20
TRANSCODE_MAX_AGE=20
TRANSCODE_ORPHAN_AGE=30
TRANSCODE_FLIP_WARN=3
TRANSCODE_OWNER="nobody:users"
TRANSCODE_CHMOD="755"
# Operating mode — controls symlink direction behaviour
# smart — auto-flips between ramdisk and SSD based on RAMDISK_WARN_GB / RAMDISK_LOW_GB
# hysteresis gap prevents flip-flop — default for production
# ramdisk — always uses ramdisk, never flips to SSD
# warns if RAMDISK_WARN_GB exceeded but holds position
# use during SSD maintenance or when SSD space is low
# ssd — always uses SSD, never flips to ramdisk
# use during ramdisk maintenance or after a ramdisk issue
TRANSCODE_MANAGER_MODE="smart" # smart | ramdisk | ssd
TRANSCODE_DAILY_LOG="/boot/config/transcode_daily.db"
TRANSCODE_LOG_RETENTION=90
# ━━━ Transcode Server Array ━━━
# All media servers sharing the ramdisk transcode space.
# 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.
TRANSCODE_SERVERS=(
"${HOST1_EMBY_CONTAINER}|${HOST1_EMBY_URL}|${HOST1_EMBY_API_KEY}|emby"
# "${HOST2_EMBY_CONTAINER}|${HOST2_EMBY_URL}|${HOST2_EMBY_API_KEY}|emby"
# "Jellyfin|http://localhost:8097|jellyfin-api-key|jellyfin"
# "Plex|http://localhost:32400|plex-token|plex"
)
TRANSCODE_CHECK_EMBY=true
# ==============================================================================================
# ── MONITORS ──────────────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# ━━━ Certificate Monitor ━━━
CERT_MONITOR_DOMAINS=(
"Gmer4Lfe.com"
"Gmer4Lfe.us"
)
CERT_WARN_DAYS=30
CERT_CRIT_DAYS=7
CERT_TIMEOUT=10
# ━━━ Backup Verify ━━━
# Leave empty to use HOST*_DAILY_SYNC_SHARES automatically.
BACKUP_VERIFY_SHARES=(
# leave empty to use daily sync shares automatically
)
BACKUP_VERIFY_SAMPLE=10
BACKUP_VERIFY_MIN_SIZE=1M
# ━━━ SMART Health ━━━
SMART_TEMP_WARN=45
SMART_TEMP_CRIT=55
SMART_IGNORE_DRIVES=(
"sda"
)
# ━━━ ZFS Memory Snapshot ━━━
ZFS_REPORT_LOG="/var/log/zfs-weekly-health.log"
ZFS_REPORT_ARC_WARN_PCT=90
ZFS_REPORT_FREE_WARN_GB=10
ZFS_REPORT_AVAIL_WARN_GB=20
ZFS_REPORT_DOCKER_TOP=10
ZFS_REPORT_IGNORE_POOLS=(
"disk10"
"disk9"
"disk8"
"disk6"
"disk5"
)
# ━━━ Bandwidth Monitor ━━━
# Called by rsync.sh after each sync — bounded write, minimal flash wear.
BANDWIDTH_LOG="/boot/config/bandwidth_history.db"
BANDWIDTH_LOG_RETENTION=90
BANDWIDTH_WARN_GB=50
# ━━━ Health Digest ━━━
# Reads existing state files — no new flash writes.
# Profiles: always | smart | weekly
DIGEST_PROFILE="weekly"
DIGEST_DAY="Sunday"
DIGEST_SMART_ON_WATCHDOG=true
DIGEST_SMART_ON_FAILOVER=true
DIGEST_SMART_ON_CERT_WARN=true
DIGEST_SMART_ON_BANDWIDTH=true
# ━━━ Emby Session Report ━━━
# Weekly Emby usage statistics via API — no persistent writes.
# URL and API key from HOST1/HOST2_EMBY_URL and HOST1/HOST2_EMBY_API_KEY in Host Configuration.
EMBY_REPORT_DAYS=7
EMBY_REPORT_TOP_N=10
# ==============================================================================================
# ── SYSTEM WATCHDOG ───────────────────────────────────────────────────────────────────────────
# ==============================================================================================
# Continuous system health monitoring — last line of defense before a crash.
# Started by array_start.sh — runs until array stops.
# Re-sources Master.conf each cycle — config changes take effect on next cycle.
# Strike system: sustained threshold hits trigger reboot — single spikes ignored.
# Reboot loop protection: shuts down instead if reboot limit hit in rolling window.
# Silent when healthy — logs only when a threshold is triggered.
# ━━━ State Files ━━━
SYS_WATCHDOG_STATE_FILE="/tmp/system_watchdog_state.db" # /tmp resets on reboot ✅
SYS_WATCHDOG_FAILED_FILE="/boot/config/system_watchdog_failed.db" # survives reboots
SYS_WATCHDOG_REBOOT_LOG="/boot/config/system_watchdog_reboots.db" # reboot loop detection
# ━━━ Strike and Reboot Loop Settings ━━━
SYS_WATCHDOG_STRIKE_LIMIT=2
SYSTEM_WATCHDOG_INTERVAL=300 # seconds between cycles (5min default)
SYS_WATCHDOG_REBOOT_LIMIT=3
SYS_WATCHDOG_REBOOT_WINDOW_HRS=12
# ━━━ Thresholds ━━━
SYS_WATCHDOG_ROOTFS_PCT=95
SYS_WATCHDOG_LOG_PCT=95
SYS_WATCHDOG_MEM_GB=4
SYS_WATCHDOG_ARC_PINNED_PCT=98
SYS_WATCHDOG_ARC_RELEASE_PCT=95
SYS_WATCHDOG_LOAD_MULTIPLIER=3
SYS_WATCHDOG_ZOMBIE_LIMIT=50
SYS_WATCHDOG_CPU_TEMP_MAX=95
# ━━━ Check Toggles ━━━
SYS_WATCHDOG_CHECK_ROOTFS=true
SYS_WATCHDOG_CHECK_LOG=true
SYS_WATCHDOG_CHECK_RAM=true
SYS_WATCHDOG_CHECK_ARC=true
SYS_WATCHDOG_CHECK_CPU_TEMP=true
SYS_WATCHDOG_CHECK_LOAD=false # disabled — load spikes during transcoding are normal
SYS_WATCHDOG_CHECK_ZOMBIES=true
SYS_WATCHDOG_CHECK_CONTAINERS=true
SYS_WATCHDOG_CHECK_DOCKER_DAEMON=true
# ━━━ Abort Toggles ━━━
# true = abort reboot if condition active / false = reboot anyway
SYS_WATCHDOG_ABORT_ON_ZFS_UNHEALTHY=true
SYS_WATCHDOG_ABORT_ON_PARITY=false
SYS_WATCHDOG_ABORT_ON_MOVER=false
# ==============================================================================================
# ──────────────────────── End Of User Variables ───────────────────────────────────────────────
# ==============================================================================================