From 0ea2271823e1a0035f27b2c24937b3ae9dce42c6 Mon Sep 17 00:00:00 2001 From: Nathan Coad Date: Mon, 9 Feb 2026 21:04:36 +1100 Subject: [PATCH] Harden xTeVe update flow to prevent mapping deactivation --- root/cronjob.sh | 106 +++++++++++++++++++++++++++++----- root/defaults/sample_cron.txt | 4 +- root/etc/crontabs/root | 2 +- sample.env | 6 ++ 4 files changed, 99 insertions(+), 19 deletions(-) diff --git a/root/cronjob.sh b/root/cronjob.sh index f74240b..180b1a7 100755 --- a/root/cronjob.sh +++ b/root/cronjob.sh @@ -9,6 +9,69 @@ function prepend() { exec 1> >(prepend "[cronjob.sh] ") +LOCK_DIR=/tmp/xteve/.cronjob.lock +mkdir -p /tmp/xteve + +if ! mkdir "$LOCK_DIR" 2>/dev/null; then + echo "Another cronjob instance is already running, exiting." + exit 0 +fi + +function cleanup() { + rmdir "$LOCK_DIR" >/dev/null 2>&1 || true +} +trap cleanup EXIT + +function post_xteve_cmd() { + local cmd="$1" + local retries="${2:-5}" + local retry_sleep="${3:-2}" + local response="" + + for ((attempt=1; attempt<=retries; attempt++)); do + response=$(curl -sS --max-time 120 -X POST -d "{\"cmd\":\"${cmd}\"}" "http://127.0.0.1:${XTEVE_PORT}/api/" 2>&1) + if echo "$response" | grep -q '"status"[[:space:]]*:[[:space:]]*true'; then + echo "xTeVe API ${cmd}: accepted" + return 0 + fi + + echo "xTeVe API ${cmd}: attempt ${attempt}/${retries} failed (${response})" + sleep "$retry_sleep" + done + + echo "xTeVe API ${cmd}: failed after ${retries} attempts" + return 1 +} + +function xteve_internal_update_due_now() { + if [ "${skip_xteve_api_when_internal_update:-yes}" != "yes" ]; then + return 1 + fi + + if [ ! -f /xteve/settings.json ]; then + return 1 + fi + + local now_hhmm + now_hhmm=$(date +"%H%M") + + local update_values + update_values=$(tr -d '\n' < /xteve/settings.json | sed -n 's/.*"update"[[:space:]]*:[[:space:]]*\[\([^]]*\)\].*/\1/p' | tr -d ' "') + if [ -z "$update_values" ]; then + return 1 + fi + + IFS=',' read -r -a updates <<< "$update_values" + for update_time in "${updates[@]}"; do + if [ "$update_time" = "$now_hhmm" ]; then + echo "xTeVe internal update is scheduled for ${now_hhmm}; skipping wrapper API update to avoid overlap." + return 0 + fi + done + + return 1 +} + echo "Running scripts..." ### Generate playlist and XML data from Lazystream @@ -94,13 +157,23 @@ sleep 1 # update xteve via API if [ "$use_xTeveAPI" = "yes" ]; then - echo "Updating xTeVe..." - curl -s -X POST -d '{"cmd":"update.m3u"}' http://127.0.0.1:$XTEVE_PORT/api/ - # sleep 1 - curl -s -X POST -d '{"cmd":"update.xmltv"}' http://127.0.0.1:$XTEVE_PORT/api/ - sleep 1 - curl -s -X POST -d '{"cmd":"update.xepg"}' http://127.0.0.1:$XTEVE_PORT/api/ - sleep 30 + if xteve_internal_update_due_now; then + echo "Skipping xTeVe API update in cronjob." + else + echo "Updating xTeVe..." + if post_xteve_cmd "update.m3u"; then + sleep "${xteve_m3u_settle_seconds:-2}" + if post_xteve_cmd "update.xmltv"; then + sleep "${xteve_xmltv_settle_seconds:-20}" + post_xteve_cmd "update.xepg" + sleep "${xteve_xepg_settle_seconds:-2}" + else + echo "Skipping update.xepg because update.xmltv failed." + fi + else + echo "Skipping remaining xTeVe API updates because update.m3u failed." + fi + fi fi # update Emby via API @@ -116,18 +189,19 @@ fi # update Plex via API if [ "$use_plexAPI" = "yes" ]; then - - # get protocol - proto="$(echo $plexUpdateURL | grep :// | sed -e's,^\(.*://\).*,\1,g')" - # remove the protocol - url="$(echo ${plexUpdateURL/$proto/})" - # extract the host - plexHostPort="$(echo ${url/} | cut -d/ -f1)" - echo "Updating Plex..." - if [ -z "$plexUpdateURL" ]; then + if [ -z "${plexUpdateURL//[[:space:]]/}" ]; then echo "no Plex credentials provided" + elif ! echo "$plexUpdateURL" | grep -Eq '^https?://[^[:space:]]+$'; then + echo "invalid plexUpdateURL, expected a plain http(s) URL. Skipping Plex update." else + # get protocol + proto="$(echo $plexUpdateURL | grep :// | sed -e's,^\(.*://\).*,\1,g')" + # remove the protocol + url="$(echo ${plexUpdateURL/$proto/})" + # extract the host + plexHostPort="$(echo ${url/} | cut -d/ -f1)" + curl --location --request POST "$plexUpdateURL" -H "authority: $plexHostPort" -H "content-length: 0" -H "pragma: no-cache" -H "cache-control: no-cache" -H "sec-ch-ua: 'Google Chrome';v='95', 'Chromium';v='95', ';Not A Brand';v='99'" -H "accept: text/plain, */*; q=0.01" -H "x-requested-with: XMLHttpRequest" -H "accept-language: en" -H "sec-ch-ua-mobile: ?0" -H "user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36" -H "sec-ch-ua-platform: 'macOS'" -H "origin: http://$plexHostPort" -H "sec-fetch-site: same-origin" -H "sec-fetch-mode: cors" -H "sec-fetch-dest: empty" -H "referer: http://$plexHostPort/web/index.html" sleep 1 fi diff --git a/root/defaults/sample_cron.txt b/root/defaults/sample_cron.txt index eef278b..1f03d7e 100644 --- a/root/defaults/sample_cron.txt +++ b/root/defaults/sample_cron.txt @@ -1,4 +1,4 @@ -printf '0 * * * * /cronjob.sh' > /etc/crontabs/root +printf '5 * * * * /cronjob.sh' > /etc/crontabs/root ### Remove this line and everything beneath it ### ### Edit the above cron expression, rename this file to cron.txt, and restart the container. -### If no cron.txt is found the cronjob will run every hour \ No newline at end of file +### If no cron.txt is found the cronjob will run every hour diff --git a/root/etc/crontabs/root b/root/etc/crontabs/root index 8b97ee3..8e7ada9 100644 --- a/root/etc/crontabs/root +++ b/root/etc/crontabs/root @@ -1 +1 @@ -0 * * * * /cronjob.sh \ No newline at end of file +5 * * * * /cronjob.sh diff --git a/sample.env b/sample.env index 7025a16..44e3fcc 100644 --- a/sample.env +++ b/sample.env @@ -7,6 +7,12 @@ PGID=1000 ### Xteve Config XTEVE_PORT=34400 use_xTeveAPI=yes +# Skip wrapper-triggered xTeVe updates if the current time matches xTeVe's internal "update" schedule. +skip_xteve_api_when_internal_update=yes +# Wait times between wrapper xTeVe update stages. XMLTV delay helps avoid mapping against in-progress downloads. +#xteve_m3u_settle_seconds=2 +#xteve_xmltv_settle_seconds=20 +#xteve_xepg_settle_seconds=2 ### Lazystream Config # If trim_xmltv is set to yes, both NHL and MLB wont output 100 placeholder channels and