#!/bin/bash
# ==============================================================================================
# ======================= Upgrade Webhook Listener (continuous) ================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Starts a standalone Node.js HTTP server that receives Sonarr/Radarr/Lidarr
# OnUpgrade webhooks and dispatches upgrade_webhook_handler.sh.
#
# Runs outside Unraid nginx — no session auth required. The shared secret in
# the webhook URL is the only gate. Arrs on this host call:
#
#   http://<HOST_LAN_IP>:<WEBHOOK_PORT>/webhook?key=<WEBHOOK_SECRET>
#
# Runs as a continuous script started by array_started.sh. Execs node which
# replaces this process — the PID stays the same for array_started.sh's check.
#
# Uses Node.js instead of php -S: php -S on Unraid PHP 8.4 silently drops
# POST request bodies, making webhook payloads arrive empty.
#
# If WEBHOOK_SECRET is empty in master.conf: generates and saves one, then starts.
# If WEBHOOK_PORT is 0: exits cleanly (disables the listener).
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
#   1. Check WEBHOOK_PORT — exit cleanly if 0 (listener disabled)
#   2. acquire_lock "continuous" — exit cleanly if a healthy instance is already running
#   3. Check WEBHOOK_SECRET — generate and persist one if empty
#   4. exec node webhook_listener.js — replaces this process; PID stays the same
#
# exec is intentional: array_started.sh tracks the PID of this script to check
# whether the listener is running. exec preserves that PID across the hand-off
# to node.
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Node.js Over php -S
#   php -S on Unraid PHP 8.4 silently drops POST request bodies — webhooks arrive
#   empty and the handler has no payload to act on. Node.js handles POST bodies
#   correctly and has no equivalent silent-drop behaviour.
#
# Runs Outside nginx
#   The listener binds directly to WEBHOOK_PORT — no nginx proxy, no session auth.
#   The shared secret in the URL query string is the only gate. This keeps the
#   webhook path independent of the auth stack.
#
# exec Preserves PID
#   The script execs into node rather than forking it. array_started.sh stores the
#   PID of this script to check liveness — exec ensures that PID continues to
#   refer to the running node process after the hand-off.
#
# Continuous-Mode Lock, Not Just PID Tracking
#   array_started.sh only checks whether ITS launch attempt is still alive after
#   1s — it has no idea a previous instance might already be listening (e.g. an
#   array stop/start that didn't kill the old node process). Without its own
#   guard, a relaunch would exec straight into node, hit EADDRINUSE on the port,
#   exit 1 almost immediately, and array_started.sh would log a false failure
#   for a listener that was actually still healthy. acquire_lock "continuous"
#   detects the live instance and exits 0 instead.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Enforcement
#   Writes /var/log/varaverk and persists the generated secret into master.conf.
#
# WEBHOOK_PORT=0 Gate
#   Exits cleanly before any setup if the listener is disabled.
#
# node Presence Check
#   node is exec'd at the end of this script. Checking up front fails with a clear
#   reason at array start rather than an exec error buried in the log after setup.
#
# openssl Presence Check
#   Checked before attempting to generate a secret, on the path that needs it.
#
# Lock Acquisition
#   acquire_lock "continuous" exits 0 cleanly if a healthy instance is already
#   listening, instead of relaunching into an EADDRINUSE port conflict. An array
#   stop/start without a full reboot leaves the old node process alive, and without
#   this array_started.sh would log a false failure.
#
# Secret Auto-Generation
#   WEBHOOK_SECRET generated via openssl rand if empty, and persisted to master.conf
#   so restarts reuse it.
#
# Secret Persistence Verification
#   The write-back is confirmed by re-reading master.conf. If it did not land, the
#   secret would exist only in this process and be regenerated on the next start,
#   silently invalidating the key already registered in the arrs — so this aborts
#   loudly rather than starting with a secret that will not survive a restart.
#
# Shared Secret Gate
#   The webhook URL must include ?key=<WEBHOOK_SECRET>; requests without a valid key
#   are rejected by the Node.js server.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
#
#   WEBHOOK_PORT    — port the listener binds to; 0 = disabled
#   WEBHOOK_SECRET  — shared secret for URL auth; auto-generated if empty
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# start_webhook_listener.sh
#     Started automatically at array start via ARRAY_START_SCRIPTS.
#     Exits immediately if WEBHOOK_PORT=0.
#
# To stop:
#     pkill -f webhook_listener.js
#
# ==============================================================================================

set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ECOSYSTEM_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

source "$ECOSYSTEM_ROOT/load_config.sh"

if [[ "$EUID" -ne 0 ]]; then
    error "Must be run as root"
    exit 1
fi

[[ "${WEBHOOK_PORT:-0}" -eq 0 ]] && {
    echo "[webhook] WEBHOOK_PORT=0 — listener disabled"
    exit 0
}

# node is exec'd at the end of this script — checking here fails with a clear reason at
# array start instead of an exec error buried in the log after all the setup has run.
if ! command -v node >/dev/null 2>&1; then
    error "node not found — required to run webhook_listener.js"
    notify "Webhook listener failed to start on $(hostname) — node not installed" \
        "Webhook Listener" "warning"
    exit 1
fi

# Skip gracefully if a healthy instance is already listening — otherwise a
# restart that doesn't kill the old node process (array stop/start without a
# full reboot) hits EADDRINUSE and array_started.sh logs a false failure.
acquire_lock "continuous"

# ── Auto-generate secret if not yet set ─────────────────────────────────────
if [[ -z "${WEBHOOK_SECRET:-}" ]]; then
    if ! command -v openssl >/dev/null 2>&1; then
        error "openssl not found — cannot generate WEBHOOK_SECRET"
        notify "Webhook listener failed to start on $(hostname) — openssl not installed" \
            "Webhook Listener" "warning"
        exit 1
    fi

    GENERATED=$(openssl rand -hex 32)
    MASTER_CONF="$ECOSYSTEM_ROOT/Configurations/master.conf"
    sed -i "s/WEBHOOK_SECRET=\"\"/WEBHOOK_SECRET=\"$GENERATED\"/" "$MASTER_CONF"
    WEBHOOK_SECRET="$GENERATED"

    # If the sed didn't match, the secret only exists in this process. The listener would
    # come up, then regenerate a different secret on the next start — silently invalidating
    # the key already registered in the arrs. Fail loudly instead.
    if ! grep -q "WEBHOOK_SECRET=\"$GENERATED\"" "$MASTER_CONF" 2>/dev/null; then
        error "Generated WEBHOOK_SECRET but could not persist it to $MASTER_CONF"
        error "Set WEBHOOK_SECRET manually — a non-persisted secret changes on every restart"
        notify "Webhook secret not persisted on $(hostname) — set WEBHOOK_SECRET manually" \
            "Webhook Listener" "warning"
        exit 1
    fi

    echo "[webhook] Generated WEBHOOK_SECRET — run Tools/webhook_setup.sh to register in arrs"
fi

mkdir -p /var/log/varaverk

exec node "$ECOSYSTEM_ROOT/Arrs_Stack/webhook_listener.js" \
    "$WEBHOOK_PORT" "$WEBHOOK_SECRET" "$ECOSYSTEM_ROOT" \
    >> /var/log/varaverk/upgrade_webhook.log 2>&1
