Bring script headers onto the template and close safeguard gaps
Headers claimed protections the code never had, and several destructive paths had no guard against a collapsed config value.
This commit is contained in:
+72
-11
@@ -18,7 +18,7 @@
|
||||
# Audio → MusicBrainz Track ID
|
||||
#
|
||||
# ==============================================================================================
|
||||
# SYNC LOGIC
|
||||
# OPERATIONAL MODEL
|
||||
# ==============================================================================================
|
||||
#
|
||||
# For each matched item across ≥2 servers:
|
||||
@@ -55,21 +55,71 @@
|
||||
# OPERATIONAL SAFEGUARDS
|
||||
# ==============================================================================================
|
||||
#
|
||||
# PLAY_SYNC_ENABLED gate — exits cleanly when disabled; no partial runs
|
||||
# PARTNERSHIP gate — skips remote sync when partnership is inactive
|
||||
# Per-server reachability — unreachable servers are skipped individually;
|
||||
# one offline server does not abort the entire sync
|
||||
# User match required — a user missing from a server is skipped for that
|
||||
# server; no cross-account state pollution
|
||||
# acquire_lock — prevents concurrent runs from racing on the same
|
||||
# items during the 30-minute critical window
|
||||
# Root Enforcement
|
||||
# The probe fingerprint is written under STATE_DIR, which is not user-writable.
|
||||
# Without root the fingerprint silently fails to persist and the change probe
|
||||
# never suppresses anything.
|
||||
#
|
||||
# jq Dependency Check
|
||||
# Exits if jq is missing. All API response parsing and the epoch comparisons
|
||||
# depend on it — without jq every comparison would silently evaluate empty.
|
||||
#
|
||||
# PLAY_SYNC_ENABLED Gate
|
||||
# Exits cleanly when disabled; no partial runs.
|
||||
#
|
||||
# PLAY_SYNC_REMOTE Gate
|
||||
# When false, only this host's own servers are synced. Remote hosts are skipped
|
||||
# before any network call is attempted.
|
||||
#
|
||||
# Partnership Gate
|
||||
# Remote hosts are skipped when PARTNERSHIP_ENABLED=false. Local Emby↔Jellyfin
|
||||
# sync still runs — a dormant partnership does not disable local work.
|
||||
#
|
||||
# Tailscale Resolution Guard
|
||||
# A remote host whose Tailscale IP cannot be resolved is skipped rather than
|
||||
# contacted at its literal localhost URL, which would otherwise point the sync
|
||||
# at this host's own server and cross-contaminate state.
|
||||
#
|
||||
# Placeholder Credential Guard
|
||||
# Servers whose API key is empty or still a placeholder are dropped from the
|
||||
# list before any request is made.
|
||||
#
|
||||
# Per-Server Reachability
|
||||
# Unreachable servers are skipped individually; one offline server does not
|
||||
# abort the entire sync.
|
||||
#
|
||||
# User Match Required
|
||||
# A user missing from a server is skipped for that server. State is never
|
||||
# written to an unrelated account that happens to exist there.
|
||||
#
|
||||
# Forward-Only Writes
|
||||
# The sync only pushes state forward — it never clears a Played flag or resets
|
||||
# a resume position. The worst outcome of a bad comparison is a no-op, not
|
||||
# erased watch history.
|
||||
#
|
||||
# Lock Acquisition
|
||||
# acquire_lock prevents concurrent runs racing on the same items during the
|
||||
# 30 minute critical window. --wait switches from skip to wait for manual runs.
|
||||
#
|
||||
# Probe Staleness Ceiling
|
||||
# PLAY_SYNC_PROBE_MAX_AGE_HOURS forces a full comparison regardless of the
|
||||
# hash. Fetches happen every run either way, so the probe can only skip
|
||||
# per-item processing — it can never cause a change to be missed outright.
|
||||
#
|
||||
# Dry Run Support
|
||||
# --dry-run performs all comparisons and writes no state.
|
||||
#
|
||||
# ==============================================================================================
|
||||
# CONFIGURATION (host*.conf, aliased by detect_hosts)
|
||||
# CONFIGURATION
|
||||
# ==============================================================================================
|
||||
#
|
||||
# host*.conf
|
||||
#
|
||||
# HOST*_TRANSCODE_SERVERS "Name|URL|APIKey|type" entries per host (emby/jellyfin)
|
||||
# All hosts are discovered automatically — no extra config needed.
|
||||
# Read directly for every HOST[0-9]+ defined — this script
|
||||
# deliberately does NOT call detect_hosts(), because it needs
|
||||
# every host's servers, not just this one's. Self is identified
|
||||
# by comparing HOST* values against hostname -s.
|
||||
# Remote host URLs have localhost rewritten to their Tailscale IP.
|
||||
#
|
||||
# master.conf
|
||||
@@ -103,6 +153,10 @@
|
||||
# play_state_sync.sh --full
|
||||
# Bypass the change probe — always run the full comparison.
|
||||
#
|
||||
# play_state_sync.sh --wait
|
||||
# Wait for an in-progress run to finish instead of exiting. For manual runs
|
||||
# that would otherwise be skipped by the every-30-minute scheduled pass.
|
||||
#
|
||||
# play_state_sync.sh --log
|
||||
# Verbose output — show each item comparison.
|
||||
#
|
||||
@@ -129,6 +183,13 @@ parse_args "${_FILTERED[@]}"
|
||||
# ==============================================================================================
|
||||
# ━━━ Setup ━━━
|
||||
# ==============================================================================================
|
||||
# The probe fingerprint lives under STATE_DIR — without root it silently fails to persist
|
||||
# and the change probe can never suppress a run.
|
||||
if [[ "$EUID" -ne 0 ]]; then
|
||||
error "Must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
[[ "${PLAY_SYNC_ENABLED:-true}" != "true" ]] && echo "Play state sync disabled" && exit 0
|
||||
|
||||
SYNC_TYPES="${PLAY_SYNC_TYPES:-Movie,Episode}"
|
||||
|
||||
Reference in New Issue
Block a user