Add docker actions, arr profile enforcer, monitor caching, and web file symlink

Web files now served via symlink to the git repo so git pull changes survive
reboots without rebuilding the txz. Also includes: docker pull/rebuild/restart
with live log streaming, arr_profile_enforcer for Sonarr/Radarr quality
profiles, monitor page cache fix (background writer now in cron), and
ARR_KIDS/SONARR/RADARR profile name vars in master.conf.
This commit is contained in:
Gmer4Lfe
2026-06-19 11:09:40 -04:00
parent ac2986b141
commit f42ecc8464
15 changed files with 657 additions and 62 deletions
+194
View File
@@ -0,0 +1,194 @@
#!/bin/bash
# ==============================================================================================
# ========================= Arr Profile Enforcer ==============================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Ensures every series/movie in Sonarr and Radarr is on the correct quality
# profile based on its root folder. Safe to re-run — only touches items whose
# current profile is wrong.
#
# RULES
# ─────────────────────────────────────────────────────────────────────────────
# Root folder path contains "kids" OR "anime" → kids profile
# All other root folders → default profile
#
# Profile names are looked up by name from the API at runtime, so profile IDs
# do not need to be hardcoded and work across hosts.
#
# ── USAGE ────────────────────────────────────────────────────────────────────
# arr_profile_enforcer.sh [--dry-run] [--sonarr-only] [--radarr-only]
#
# ── CONFIGURATION ────────────────────────────────────────────────────────────
# master.conf
# ARR_KIDS_PROFILE_NAME — profile name for kids/anime (default: "Kids shows")
# ARR_SONARR_DEFAULT_PROFILE — default Sonarr profile name (default: "Any")
# ARR_RADARR_DEFAULT_PROFILE — default Radarr profile name (default: "Any (mine)")
#
# ==============================================================================================
DRY_RUN=false
RUN_SONARR=true
RUN_RADARR=true
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=true ;;
--sonarr-only) RUN_RADARR=false ;;
--radarr-only) RUN_SONARR=false ;;
esac
done
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../load_config.sh"
detect_hosts
KIDS_PROFILE_NAME="${ARR_KIDS_PROFILE_NAME:-Kids shows}"
SONARR_DEFAULT="${ARR_SONARR_DEFAULT_PROFILE:-Any}"
RADARR_DEFAULT="${ARR_RADARR_DEFAULT_PROFILE:-Any (mine)}"
# ── Helpers ──────────────────────────────────────────────────────────────────
_arr_get() {
local url="$1" key="$2" endpoint="$3"
curl -sf --max-time 30 -H "X-Api-Key: $key" "$url/api/v3/$endpoint"
}
_arr_put() {
local url="$1" key="$2" endpoint="$3" body="$4"
curl -sf --max-time 60 -X PUT \
-H "X-Api-Key: $key" \
-H "Content-Type: application/json" \
-d "$body" \
"$url/api/v3/$endpoint"
}
_profile_id_by_name() {
local profiles_json="$1" name="$2"
php -r '
$profiles = json_decode(file_get_contents("php://stdin"), true);
$name = $argv[1];
foreach ($profiles as $p) {
if (strcasecmp($p["name"], $name) === 0) { echo $p["id"]; exit; }
}
exit(1);
' "$name" <<< "$profiles_json"
}
_is_kids_path() {
local path="$1"
local dir
dir=$(basename "$(dirname "$path")")
[[ "$dir" == *kids* || "$dir" == *anime* ]]
}
# ── Core enforcer ─────────────────────────────────────────────────────────────
# _enforce <label> <url> <api_key> <items_endpoint> <id_field> <editor_endpoint>
# <kids_profile_id> <default_profile_id>
_enforce() {
local label="$1" url="$2" key="$3" items_ep="$4"
local id_field="$5" editor_ep="$6"
local kids_id="$7" default_id="$8"
local items
items=$(_arr_get "$url" "$key" "$items_ep") || {
error "[$label] Cannot reach $url"
return 1
}
local to_kids=() to_default=()
while IFS='|' read -r id profile_id path; do
if _is_kids_path "$path"; then
[[ "$profile_id" -ne "$kids_id" ]] && to_kids+=("$id")
else
[[ "$profile_id" -ne "$default_id" ]] && to_default+=("$id")
fi
done < <(php -r '
$items = json_decode(file_get_contents("php://stdin"), true);
foreach ($items as $r) echo $r["id"]."|".$r["qualityProfileId"]."|".$r["path"]."\n";
' <<< "$items")
local total=$(( ${#to_kids[@]} + ${#to_default[@]} ))
log "arr_profile_enforcer" "[$label] ${#to_kids[@]}$KIDS_PROFILE_NAME | ${#to_default[@]} → default | $total to fix"
if [[ "$DRY_RUN" == true ]]; then
echo " [$label] DRY RUN — would set $KIDS_PROFILE_NAME on ${#to_kids[@]}, default on ${#to_default[@]}"
return 0
fi
_bulk_update() {
local profile_id="$1" prof_label="$2"
shift 2
local ids=("$@")
[[ ${#ids[@]} -eq 0 ]] && return 0
local ids_json
ids_json=$(printf '%s,' "${ids[@]}")
ids_json="[${ids_json%,}]"
local result
result=$(_arr_put "$url" "$key" "$editor_ep" \
"{\"${id_field}\":${ids_json},\"qualityProfileId\":${profile_id}}") || {
error "[$label] Bulk update failed for $prof_label"
return 1
}
local updated
updated=$(php -r 'echo count(json_decode(file_get_contents("php://stdin"), true));' <<< "$result")
log "arr_profile_enforcer" "[$label] Set $prof_label on $updated items"
}
_bulk_update "$kids_id" "$KIDS_PROFILE_NAME" "${to_kids[@]+"${to_kids[@]}"}"
_bulk_update "$default_id" "default" "${to_default[@]+"${to_default[@]}"}"
}
# ── Sonarr ────────────────────────────────────────────────────────────────────
if [[ "$RUN_SONARR" == true ]]; then
require_var SONARR_URL
require_var SONARR_API_KEY
SONARR_PROFILES=$(_arr_get "$SONARR_URL" "$SONARR_API_KEY" "qualityprofile") || {
error "Cannot reach Sonarr at $SONARR_URL"
exit 1
}
SONARR_KIDS_ID=$(_profile_id_by_name "$SONARR_PROFILES" "$KIDS_PROFILE_NAME") || {
error "Sonarr profile not found: '$KIDS_PROFILE_NAME'"
exit 1
}
SONARR_DEFAULT_ID=$(_profile_id_by_name "$SONARR_PROFILES" "$SONARR_DEFAULT") || {
error "Sonarr profile not found: '$SONARR_DEFAULT'"
exit 1
}
_enforce "Sonarr" "$SONARR_URL" "$SONARR_API_KEY" \
"series" "seriesIds" "series/editor" \
"$SONARR_KIDS_ID" "$SONARR_DEFAULT_ID"
fi
# ── Radarr ────────────────────────────────────────────────────────────────────
if [[ "$RUN_RADARR" == true ]]; then
require_var RADARR_URL
require_var RADARR_API_KEY
RADARR_PROFILES=$(_arr_get "$RADARR_URL" "$RADARR_API_KEY" "qualityprofile") || {
error "Cannot reach Radarr at $RADARR_URL"
exit 1
}
RADARR_KIDS_ID=$(_profile_id_by_name "$RADARR_PROFILES" "$KIDS_PROFILE_NAME") || {
error "Radarr profile not found: '$KIDS_PROFILE_NAME'"
exit 1
}
RADARR_DEFAULT_ID=$(_profile_id_by_name "$RADARR_PROFILES" "$RADARR_DEFAULT") || {
error "Radarr profile not found: '$RADARR_DEFAULT'"
exit 1
}
_enforce "Radarr" "$RADARR_URL" "$RADARR_API_KEY" \
"movie" "movieIds" "movie/editor" \
"$RADARR_KIDS_ID" "$RADARR_DEFAULT_ID"
fi
log "arr_profile_enforcer" "Done"