#!/bin/bash # ============================================================================================== # ================================= Array Start Orchestrator =================================== # ============================================================================================== # # PURPOSE # ───────────────────────────────────────────────────────────────────────────── # Single entry point for array start — fired by the Varaverk plugin's # disks_mounted event hook (Plugin/unraid/event/disks_mounted/array_start_jobs). # Launches everything configured in ARRAY_START_SCRIPTS in master.conf. # This script exits after launching all scripts — the event hook sees it complete normally. # # ============================================================================================== # OPERATIONAL MODEL # ============================================================================================== # # Configured in master.conf ARRAY_START_SCRIPTS — no changes to this script ever needed. # Current order (order matters — see below): # # ONE-SHOT (run and exit naturally): # System_Essentials/unraid_api_key_renew.sh — re-register Varaverk API key at boot # System_Essentials/inotify_tuning.sh — raise inotify limits before containers start # System_Essentials/docker_syslog_filter.sh — suppress veth log noise before logs fill # System_Essentials/php_fpm_max_children.sh — WebGUI performance tuning # Transcodes/ramdisk_setup.sh — create tmpfs + symlink before Emby starts # Docker_Essentials/docker_network_connect.sh — ensure networks + container connections # # CONTINUOUS (run until array stops): # Fallback/fallback.sh — mutual fallback monitor # # NOTE: watchdogs (docker, system, stability) are NOT launched here. # They run via watchdog_orchestrator.sh every 15 min (cron), not as daemons. # # Script is launched in background with bash script.sh & # After 1 second: if PID still alive → continuous (running in background) # if PID dead + exit 0 → one-shot completed successfully # if PID dead + exit N → failure # # ============================================================================================== # DESIGN PRINCIPLES # ============================================================================================== # # Order Is Load-Bearing # inotify limits must be raised before containers start — containers that # start with low limits keep them. Ramdisk must exist before Emby starts. # Docker networks must be connected before watchdogs check container states. # fallback.sh goes last — it needs everything else stable to make decisions. # # Configuration Owns the List # ARRAY_START_SCRIPTS in master.conf is the only place scripts are added or # removed. This orchestrator never needs to be edited to change what runs — # one-shot vs continuous behaviour is auto-detected from the PID after launch. # # ============================================================================================== # OPERATIONAL SAFEGUARDS # ============================================================================================== # # Root Enforcement # Every script launched here requires root. # # Lock Acquisition # acquire_lock prevents duplicate array start launches. Unraid can fire the array # start hook more than once, and a second pass would re-launch continuous scripts # that are already running. # # Host Detection # detect_hosts() sets MY_ID for notifications. # # Empty Job List Guard # Exits with an error and a notification if ARRAY_START_SCRIPTS is empty. An empty # list would silently bring the array up with no ramdisk, no network setup, no # watchdogs and no fallback — while reporting a clean start. # # Executable Auto-Fix # Non-executable scripts are chmod +x'd before launch. A permission bit lost to a # git checkout or a file copy should not silently disable a boot-time component. # # Full Path on Failure # Failures report the exact resolved path, so a missing script is immediately # distinguishable from a script that ran and failed. # # Failure Notification # Any script that fails to launch raises a notification — array start is unattended, # so a silent failure here would only surface much later as a missing service. # # ============================================================================================== # CONFIGURATION # ============================================================================================== # # master.conf # # ARRAY_START_SCRIPTS — ordered list of scripts to launch at array start # # ============================================================================================== # RUNTIME MODES # ============================================================================================== # # array_started.sh # Normal launch — called by Varaverk disks_mounted event hook. # # array_started.sh --dry-run # Show what would be launched without launching. # # array_started.sh --status # Show configured scripts and their current state. # # array_started.sh --log # Verbose output per script. # # ============================================================================================== SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ECOSYSTEM_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" source "$ECOSYSTEM_ROOT/load_config.sh" parse_args "$@" # ============================================================================================== # ━━━ Setup ━━━ # ============================================================================================== if [[ "$EUID" -ne 0 ]]; then error "Must be run as root" exit 1 fi acquire_lock detect_hosts # An unconfigured job list would run nothing and still report "0/0 passed" — indistinguishable # from a healthy run. Fail loudly instead of silently doing no work. if [[ ${#ARRAY_START_SCRIPTS[@]} -eq 0 ]]; then error "ARRAY_START_SCRIPTS is empty — no array start scripts will run" error "Check ARRAY_START_SCRIPTS in master.conf" notify "array start scripts skipped on $(hostname) ($MY_ID) — ARRAY_START_SCRIPTS is empty" \ "$(basename "$0" .sh)" "warning" exit 1 fi [[ "$DRY_RUN" == true ]] && warn "DRY RUN — scripts will not be launched" # ============================================================================================== # ━━━ Status ━━━ # ============================================================================================== if [[ "$SHOW_STATUS" == true ]]; then echo "" echo "━━━━━ $ICON_SUMMARY ARRAY START STATUS ━━━━━" echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)" echo "$ICON_GEAR Scripts: ${#ARRAY_START_SCRIPTS[@]} configured" echo "" for relative_path in "${ARRAY_START_SCRIPTS[@]}"; do [[ -z "$relative_path" ]] && continue script_path="$ECOSYSTEM_ROOT/$relative_path" script_name=$(basename "$script_path") if [[ ! -f "$script_path" ]]; then echo " $ICON_ERROR $script_name — FILE NOT FOUND" echo " $script_path" continue fi [[ ! -x "$script_path" ]] && flag=" (not executable — will auto-fix)" || flag="" # Check if currently running if pgrep -f "$script_path" >/dev/null 2>&1; then RUN_PID=$(pgrep -f "$script_path" | head -1) echo " $ICON_RUNNING $script_name — RUNNING (PID $RUN_PID)${flag}" else echo " $ICON_NOT_RUNNING $script_name — not running${flag}" fi done echo "━━━━━━━━━━━━━━━━━━━━━━━" exit 0 fi # ============================================================================================== # ━━━ Launch Scripts ━━━ # ============================================================================================== if [[ ${#ARRAY_START_SCRIPTS[@]} -eq 0 ]]; then notify "Array started on $LOCAL_SERVER_NAME ($MY_ID) but ARRAY_START_SCRIPTS is empty — boot sequence skipped. Check master.conf." \ "Array Start" "alert" error "ARRAY_START_SCRIPTS is empty — check master.conf" exit 1 fi echo "" echo "━━━ $ICON_GEAR Array Start — $MY_ID — $(date '+%Y-%m-%d %H:%M:%S') ━━━" log "Ecosystem root: $ECOSYSTEM_ROOT" log "Launching ${#ARRAY_START_SCRIPTS[@]} script(s)..." echo "" START=$(date +%s) JOB_PASS=() JOB_FAIL=() for relative_path in "${ARRAY_START_SCRIPTS[@]}"; do [[ -z "$relative_path" ]] && continue SCRIPT_PATH="$ECOSYSTEM_ROOT/$relative_path" SCRIPT_NAME=$(basename "$SCRIPT_PATH") # File existence check if [[ ! -f "$SCRIPT_PATH" ]]; then error "$SCRIPT_NAME — not found" error " Expected: $SCRIPT_PATH" JOB_FAIL+=("$SCRIPT_NAME") continue fi # Auto-fix permissions — chmod +x if needed if [[ ! -x "$SCRIPT_PATH" ]]; then warn "$SCRIPT_NAME — not executable, fixing..." chmod +x "$SCRIPT_PATH" || { error "$SCRIPT_NAME — chmod +x failed" JOB_FAIL+=("$SCRIPT_NAME") continue } fi if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — would launch: $SCRIPT_NAME" JOB_PASS+=("$SCRIPT_NAME") continue fi log "$ICON_START Launching $SCRIPT_NAME..." bash "$SCRIPT_PATH" & PID=$! # Brief settle — 1s enough to detect immediate failures sleep 1 if kill -0 "$PID" 2>/dev/null; then # Still running → continuous script log "$SCRIPT_NAME — running (PID $PID) ✅" JOB_PASS+=("$SCRIPT_NAME") else # Exited — check if one-shot success or failure wait "$PID" EXIT_CODE=$? if [[ "$EXIT_CODE" -eq 0 ]]; then echo "$SCRIPT_NAME — completed (one-shot) ✅" JOB_PASS+=("$SCRIPT_NAME") else error "$SCRIPT_NAME — exited with code $EXIT_CODE" error " Path: $SCRIPT_PATH" JOB_FAIL+=("$SCRIPT_NAME") fi fi done END=$(date +%s) # ============================================================================================== # ━━━ Summary ━━━ # ============================================================================================== echo "" echo "━━━━━ $ICON_SUMMARY ARRAY START SUMMARY ━━━━━" echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)" echo "$ICON_SUCCESS Launched: ${#JOB_PASS[@]}" [[ ${#JOB_FAIL[@]} -gt 0 ]] && echo "$ICON_ERROR Failed: ${#JOB_FAIL[@]} — ${JOB_FAIL[*]}" echo "$ICON_TIME Duration: $(format_duration $(( END - START )))" echo "" if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — no scripts launched" elif [[ ${#JOB_FAIL[@]} -gt 0 ]]; then warn "Status: ${#JOB_FAIL[@]} script(s) failed — ${JOB_FAIL[*]}" notify "Array start on $(hostname) ($MY_ID) — ${#JOB_FAIL[@]} script(s) failed: ${JOB_FAIL[*]}" \ "Array Start" "warning" else echo "$ICON_DONE Status: all ${#JOB_PASS[@]} script(s) launched ✅" fi echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" [[ ${#JOB_FAIL[@]} -gt 0 ]] && exit 1 exit 0