274 lines
11 KiB
Bash
Executable File
274 lines
11 KiB
Bash
Executable File
#!/bin/bash
|
|
# ==============================================================================================
|
|
# ========================= Upgrade Webhook Setup =============================================
|
|
# ==============================================================================================
|
|
#
|
|
# PURPOSE
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Registers the Varaverk upgrade webhook in Sonarr, Radarr, and Lidarr via
|
|
# their notification APIs. Idempotent — skips any arr that already has it.
|
|
#
|
|
# Run once after first install, or any time you add a new arr or host.
|
|
# The listener (start_webhook_listener.sh) must be running before arrs will
|
|
# actually deliver events, but this script can register the connection first.
|
|
#
|
|
# ==============================================================================================
|
|
# OPERATIONAL MODEL
|
|
# ==============================================================================================
|
|
#
|
|
# 1. Generate WEBHOOK_SECRET in master.conf if empty
|
|
# 2. Register webhook in each local arr (Sonarr / Radarr / Lidarr)
|
|
# 3. SSH to remote host and run itself there (unless --local-only)
|
|
#
|
|
# ==============================================================================================
|
|
# DESIGN PRINCIPLES
|
|
# ==============================================================================================
|
|
#
|
|
# Idempotent Registration
|
|
# Skips any arr that already has the webhook registered. Safe to re-run after
|
|
# adding a new arr or after a conf change without creating duplicate entries.
|
|
#
|
|
# Self-Propagating
|
|
# SSHes to the remote and runs itself with --local-only — one execution
|
|
# configures both servers without a separate remote step.
|
|
#
|
|
# ==============================================================================================
|
|
# OPERATIONAL SAFEGUARDS
|
|
# ==============================================================================================
|
|
#
|
|
# Idempotent — skips arrs that already have the webhook registered
|
|
# Secret auto-gen — WEBHOOK_SECRET generated if empty; never left blank
|
|
# --local-only — used internally for SSH; prevents infinite recursion
|
|
# --dry-run mode — shows what would be registered without making API calls
|
|
# acquire_lock — prevents concurrent registration runs
|
|
#
|
|
# ==============================================================================================
|
|
# CONFIGURATION
|
|
# ==============================================================================================
|
|
#
|
|
# master.conf
|
|
#
|
|
# WEBHOOK_PORT
|
|
# Port the listener binds and the registered webhook URL points at. 0 disables the
|
|
# listener entirely (start_webhook_listener.sh exits early), so registering against
|
|
# a port of 0 would produce URLs nothing is serving.
|
|
#
|
|
# WEBHOOK_SECRET
|
|
# Shared secret embedded in the registered URL as ?key=. Generated here on first run
|
|
# and written back into master.conf — the write-back is verified, because the arrs
|
|
# are registered with this value and a failed persist would leave them holding a
|
|
# secret this host does not have.
|
|
#
|
|
# host*.conf (aliased by detect_hosts())
|
|
#
|
|
# SONARR_URL / SONARR_API_KEY
|
|
# RADARR_URL / RADARR_API_KEY
|
|
# LIDARR_URL / LIDARR_API_KEY
|
|
# Each arr the webhook is registered in. An arr with no URL or key configured on
|
|
# this host is skipped rather than failing the run.
|
|
#
|
|
# SSH_KEY
|
|
# Used when propagating the same secret to the partner via OVERRIDE_SECRET.
|
|
#
|
|
# ==============================================================================================
|
|
# RUNTIME MODES
|
|
# ==============================================================================================
|
|
#
|
|
# webhook_setup.sh
|
|
# Configure local arrs, then SSH to remote and configure remote arrs.
|
|
#
|
|
# webhook_setup.sh --local-only
|
|
# Local arrs only (used internally when called via SSH on the remote).
|
|
#
|
|
# webhook_setup.sh --dry-run
|
|
# Show what would be registered without making any changes.
|
|
#
|
|
# webhook_setup.sh --log
|
|
# Verbose output.
|
|
#
|
|
# ==============================================================================================
|
|
|
|
set -uo pipefail
|
|
|
|
LOCAL_ONLY=false
|
|
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--local-only) LOCAL_ONLY=true ;;
|
|
esac
|
|
done
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
ECOSYSTEM_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
MASTER_CONF="$ECOSYSTEM_ROOT/Configurations/master.conf"
|
|
|
|
source "$ECOSYSTEM_ROOT/load_config.sh"
|
|
parse_args "$@"
|
|
|
|
# Writes the generated secret into master.conf via sed -i and SSHes to the partner.
|
|
if [[ "$EUID" -ne 0 ]]; then
|
|
error "Must be run as root"
|
|
exit 1
|
|
fi
|
|
|
|
acquire_lock
|
|
detect_hosts
|
|
|
|
WEBHOOK_NAME="Varaverk Upgrade"
|
|
|
|
# ── Resolve or generate the secret ──────────────────────────────────────────
|
|
# OVERRIDE_SECRET env var is set when called recursively via SSH from the
|
|
# primary host, so both ends use the same secret.
|
|
#
|
|
# Every write-back below is verified by re-reading master.conf. The secret gets baked into
|
|
# the webhook URL registered in each arr — if the sed silently fails to match, the arrs end
|
|
# up holding a secret this host does not have, and the listener rejects every delivery.
|
|
_persist_secret() {
|
|
local secret="$1"
|
|
sed -i "s/WEBHOOK_SECRET=\"\"/WEBHOOK_SECRET=\"$secret\"/" "$MASTER_CONF"
|
|
if ! grep -q "WEBHOOK_SECRET=\"$secret\"" "$MASTER_CONF" 2>/dev/null; then
|
|
error "Could not persist WEBHOOK_SECRET to $MASTER_CONF"
|
|
error "Registering the arrs now would leave them with a secret this host does not have"
|
|
notify "Webhook setup aborted on $(hostname) — could not persist WEBHOOK_SECRET" \
|
|
"Webhook Setup" "warning"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
if [[ -n "${OVERRIDE_SECRET:-}" ]]; then
|
|
if [[ -z "${WEBHOOK_SECRET:-}" ]]; then
|
|
_persist_secret "$OVERRIDE_SECRET"
|
|
fi
|
|
WEBHOOK_SECRET="$OVERRIDE_SECRET"
|
|
fi
|
|
|
|
if [[ -z "${WEBHOOK_SECRET:-}" ]]; then
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
WEBHOOK_SECRET="<would-generate>"
|
|
else
|
|
if ! command -v openssl >/dev/null 2>&1; then
|
|
error "openssl not found — cannot generate WEBHOOK_SECRET"
|
|
exit 1
|
|
fi
|
|
GENERATED=$(openssl rand -hex 32)
|
|
_persist_secret "$GENERATED"
|
|
WEBHOOK_SECRET="$GENERATED"
|
|
echo "Generated WEBHOOK_SECRET — saved to master.conf"
|
|
fi
|
|
fi
|
|
|
|
LOCAL_IP=$(hostname -I | awk '{print $1}')
|
|
WEBHOOK_URL="http://${LOCAL_IP}:${WEBHOOK_PORT}/webhook?key=${WEBHOOK_SECRET}"
|
|
|
|
# ── Helper: register webhook in one arr ─────────────────────────────────────
|
|
_register() {
|
|
local label="$1" base_url="$2" api_key="$3" api_ver="$4"
|
|
local import_field="$5" # onDownload (Sonarr/Radarr) or onReleaseImport (Lidarr)
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
echo " [$label] Would register → $WEBHOOK_URL"
|
|
return 0
|
|
fi
|
|
|
|
# Check if already registered by name
|
|
local existing
|
|
existing=$(curl -sf --max-time 5 \
|
|
-H "X-Api-Key: $api_key" \
|
|
"$base_url/api/$api_ver/notification" 2>/dev/null) || {
|
|
echo " [$label] Cannot reach arr — skipping"
|
|
return 1
|
|
}
|
|
|
|
if echo "$existing" | grep -q "\"name\":[[:space:]]*\"${WEBHOOK_NAME}\""; then
|
|
echo " [$label] Already registered — skipping"
|
|
return 0
|
|
fi
|
|
|
|
local payload
|
|
payload=$(printf '{
|
|
"name": "%s",
|
|
"implementation": "Webhook",
|
|
"configContract": "WebhookSettings",
|
|
"onGrab": false,
|
|
"%s": true,
|
|
"onUpgrade": true,
|
|
"onRename": false,
|
|
"onHealthIssue": false,
|
|
"includeHealthWarnings": false,
|
|
"onApplicationUpdate": false,
|
|
"tags": [],
|
|
"fields": [
|
|
{"name": "url", "value": "%s"},
|
|
{"name": "method", "value": 1},
|
|
{"name": "username", "value": ""},
|
|
{"name": "password", "value": ""}
|
|
]
|
|
}' "$WEBHOOK_NAME" "$import_field" "$WEBHOOK_URL")
|
|
|
|
local http_code
|
|
http_code=$(curl -sf --max-time 5 -o /dev/null -w '%{http_code}' \
|
|
-X POST \
|
|
-H "X-Api-Key: $api_key" \
|
|
-H 'Content-Type: application/json' \
|
|
-d "$payload" \
|
|
"$base_url/api/$api_ver/notification" 2>/dev/null)
|
|
|
|
if [[ "$http_code" == "201" || "$http_code" == "200" ]]; then
|
|
echo " [$label] Registered ✅ → $WEBHOOK_URL"
|
|
else
|
|
echo " [$label] Failed (HTTP ${http_code:-timeout})"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# ── Local arrs ───────────────────────────────────────────────────────────────
|
|
echo ""
|
|
echo "━━━ Upgrade Webhook Setup — $MY_ID ($LOCAL_SERVER_NAME) ━━━"
|
|
echo " LAN IP : $LOCAL_IP"
|
|
echo " Port : $WEBHOOK_PORT"
|
|
echo " URL : $WEBHOOK_URL"
|
|
echo ""
|
|
|
|
[[ -n "${SONARR_URL:-}" && -n "${SONARR_API_KEY:-}" ]] && \
|
|
_register "Sonarr" "$SONARR_URL" "$SONARR_API_KEY" "v3" "onDownload"
|
|
|
|
[[ -n "${RADARR_URL:-}" && -n "${RADARR_API_KEY:-}" ]] && \
|
|
_register "Radarr" "$RADARR_URL" "$RADARR_API_KEY" "v3" "onDownload"
|
|
|
|
[[ -n "${LIDARR_URL:-}" && -n "${LIDARR_API_KEY:-}" ]] && \
|
|
_register "Lidarr" "$LIDARR_URL" "$LIDARR_API_KEY" "v1" "onReleaseImport"
|
|
|
|
# ── Remote host ───────────────────────────────────────────────────────────────
|
|
if [[ "$LOCAL_ONLY" == false && -n "${REMOTE_ID:-}" ]]; then
|
|
echo ""
|
|
echo "━━━ Configuring remote: $REMOTE_SERVER_NAME ━━━"
|
|
|
|
remote_ip=$(resolve_tailscale_ip "$REMOTE_SERVER_NAME") || {
|
|
echo " Cannot resolve Tailscale IP for $REMOTE_SERVER_NAME — skipping"
|
|
echo " Run manually on that host: Tools/webhook_setup.sh --local-only"
|
|
echo ""
|
|
exit 0
|
|
}
|
|
|
|
remote_flags="--local-only"
|
|
[[ "$DRY_RUN" == true ]] && remote_flags="$remote_flags --dry-run"
|
|
|
|
# The remote's own SCRIPTS_DIR, read from its varaverk.cfg — never the flash plugin path.
|
|
# That was hardcoded here, so a partner in appdata storage mode got
|
|
# "bash: .../Tools/webhook_setup.sh: No such file or directory" and the generic
|
|
# "check SSH and that Varaverk is installed" message, which pointed at two things that
|
|
# were both fine. HOST2 has run in appdata mode since it was installed.
|
|
SSH_TIMEOUT="${SSH_TIMEOUT:-10}"
|
|
remote_sd=$(resolve_remote_scripts_dir "$remote_ip" "$SSH_KEY" "no")
|
|
remote_script="${remote_sd}/Tools/webhook_setup.sh"
|
|
|
|
ssh -i "$SSH_KEY" -o ConnectTimeout=10 -o BatchMode=yes -o StrictHostKeyChecking=no \
|
|
root@"$remote_ip" \
|
|
"[ -f '$remote_script' ] || { echo missing; exit 127; }
|
|
OVERRIDE_SECRET='$WEBHOOK_SECRET' bash '$remote_script' $remote_flags" \
|
|
|| echo " Remote setup failed — no webhook_setup.sh at $remote_script on $REMOTE_SERVER_NAME, or SSH refused"
|
|
fi
|
|
|
|
echo ""
|
|
echo "Done."
|