Favorites on any server propagate to all others; never unmarks.
Covers MusicArtist, MusicAlbum, Movie, Series (Audio tracks future).
Provider map extended to include FAV_TYPES alongside SYNC_TYPES,
with Series/Episode TVDB IDs namespaced to avoid collisions.
Emby now runs continuously on both hosts — play_state_sync via API
handles watch state reconciliation, making the 30-min dirty rsync
redundant. Handback runs play_state_sync --wait before DNS cutover
so users land on current state after a failover event.
- Emby removed from FALLBACK_HOST1_TIER1 (always running)
- Emby dirty sync removed from HOST1_CRITICAL_SYNC_SHARES
- Emby writeback removed from FALLBACK_HOST1_WRITEBACK_TIER1
- play_state_sync gains --wait flag (uses lock wait mode for handback)
- Tier 1 emby-fallback special case removed from handback writeback loop
- play_state_sync --wait added as Step 7 in handback, before DNS cutover
Single mixed Movie,Episode,Audio query caused Jellyfin to reorder items unpredictably,
pushing most episodes past the page limit and leaving PROV_LOOKUP empty — every
Emby→Jellyfin push silently failed with 'item not found'. Also removes the
ExcludeLocationTypes=Virtual param which caused the same corruption in JF.
All state/data file paths in scripts and PHP now resolve via STATE_DIR / DATA_DIR /
PERSISTENT_CONF_CACHE instead of hardcoded /boot/config/ or /tmp/ paths, so the
ecosystem works in both internal and appdata storage modes.
PHP layer (watchdog.php, partnership.php, fallback.php, monitor.php, snapshot.php,
config.php): all state reads switched to STATE_DIR constant; remote state reads use
the new vv_remote_state_cmd() helper which resolves the remote's SCRIPTS_DIR via
their varaverk.cfg before building the path.
conf_sync.sh: fixed SCRIPTS_ROOT → SCRIPTS_DIR bug on MY_CONF path; added
_remote_scripts_dir() to resolve partner's SCRIPTS_DIR before SCP pull.
fallback.php page: added controls card (PARTNERSHIP_ENABLED, FALLBACK_ENABLED,
FALLBACK_RSYNC_ENABLED toggles), status grid, and settings card.
README and Manual updated for System_Essentials, Watchdogs, Fallback, Rsync,
Media, Monitors, Orchestrators, Partnership: added new scripts (conf_sync,
conf_cache_save/restore, conf_cache_watchdog, play_state_sync, start_webhook_listener,
upgrade_webhook_handler), corrected all stale /boot/config/ state file paths to
$STATE_DIR/$DATA_DIR, noted webgui/php_fpm/mover/user_scripts scripts moved to
Plugin/unraid/System_Essentials, fixed start_webhook_listener.sh header (Node.js,
not PHP -S).
Adds require_partnership() to common.sh — exits cleanly when PARTNERSHIP_ENABLED=false.
Removes FALLBACK_PARTNERSHIP_REQUIRED toggle — partnership is now always required,
not optional. Cross-server scripts (rsync, conf sync, fallback, arr sync, play state,
backup verify) all call require_partnership after detect_hosts.
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.
Fix discrepancies found by auditing docs against headers:
- stability_watchdog.sh: wrong variable name (STRIKES → STRIKE_LIMIT) and wrong default (2hr → 12hr) in header
- Watchdogs manual: REBOOT_WINDOW_HRS example value was 2, should be 12
- Transcodes README: transcode_cleanup/manager table said "Every 3 min", should be "Every 7 minutes"
- Tools manual: fallback_state_reset.sh still marked "not yet built" — rewrite section to reflect current script
- Tools manual: claude_startup --setup flag doesn't exist; modes were inverted — fix and add --launch
- Tools README + manual: add docker_prune_images.sh (existed but undocumented)
- Tools manual: add play_state_sync.sh --full to flag reference
- Orchestrators README: arrs_failed_stalled_recovery runs via intermediate_sync (every 4hr), not standalone every 6hr
- Plugin README: add build.sh to scripts table
- Partnership README + manual: add partnership_transfer.sh and onboard_cancel.sh to script tables and flag reference
Behavior fixes:
- docker_daily_restart.sh: implement RESTART_VERIFY_WAIT sleep before
verify_running() — variable was logged as config but never applied
- cert_monitor.sh: --log now shows expiry date and days remaining for
healthy domains in the per-domain summary; problems always show details
Header corrections:
- downloaders_reset.sh: fix frequency contradiction (15 min → 30 min)
- lidarr_cleanup.sh: Emby scan triggers on actual deletions, not every run
- ssh_setup.sh: document --local-only flag in USAGE section
- zfs_memory_snapshot.sh: clarify dry-run skips log write in PURPOSE
- ramdisk_stop.sh / ramdisk_setup.sh: document TRANSCODE_STATE_FILE var
- smart_long_test.sh: remove SMART_TEMP_WARN/CRIT — not used in code
OS version reads now go through platform_get_os_version() and
platform_os_version_probe_cmd() instead of grepping /etc/unraid-version directly.
STATE_DIR fallbacks to /boot/config removed — STATE_DIR is always set by
load_config.sh and the fallback encoded a platform-specific path.
Setup DB path references use platform_setup_db_path() instead of the
VARAVERK_SETUP_FILE/-/boot/config compound fallback.
DOCKER_APPDATA_BASE default removed from arr_sync.sh — the adapter sets it.
All bash scripts are now platform-neutral. Unraid-specific paths, commands,
and service checks moved to Plugin/unraid/adapter.sh. Core scripts call
platform_*() functions exclusively — no direct OS paths in runtime logic.
New adapter functions: platform_storage_path, platform_webui_install_path,
platform_scripts_dir_probe_cmd, platform_setup_db_path, platform_storage_healthy,
platform_is_service_enabled, platform_get_temp_thresholds, platform_disk_states_path,
platform_rebuild_container, platform_push_conf, platform_push_setup_state,
platform_get_templates_dir, platform_send_os_notification.
Partnership services stack (Emby/Jellyfin/Seerr/SeerrFin) added as third
onboarding stack alongside auth and arr stacks.
Remote functions (_remote_arr_up, _remote_library, _remote_defaults,
_remote_add, _delete_remote_item) now check ${node_id}_<ARR>_API_KEY
from the environment first. When found (conf cache populated by
conf_sync.sh + load_config.sh sourcing /tmp/.vv/), they call the arr
API directly over Tailscale — no SSH connection needed.
SSH fallback (grep config.xml + localhost API) remains for the first
boot before conf_sync has run or when the partner is offline.
config_xml is no longer passed between functions — computed internally
in the SSH path only. Call sites in _sync_arr and blocklist-add updated
accordingly.
common.sh gains:
- docker_cmd() + verify_running() + retry_docker() — removed from all 3 Docker_Essentials
scripts where they were byte-for-byte duplicates
- emby_api(endpoint, [timeout=30]) — removed from 6 Media/Tools scripts that each defined
their own _emby_api() with the same curl/parse/error pattern; call sites renamed emby_api
- format_duration() extended with days/hours branch (was capped at minutes+seconds)
- notify() comment: scripts do not need to preflight the notify script via validate_unraid_cmd
Tailscale deduplication:
- arr_sync.sh: _resolve_node_ip() and inline block in _delete_remote_item() both replaced
with resolve_tailscale_ip() from common.sh
- git_pull_execute.sh: inline tailscale ip -4 replaced with resolve_tailscale_ip() (adds
the tailscale status fallback that was missing)
play_state_sync: fix TVDB episode lookup for Jellyfin 10.x — AnyProviderIdEquals
returns the entire library for TVDB queries; switch to season+episode search with
ProviderIds.Tvdb validation to find the correct episode. Also fix pkey extraction
that was pulling s7e2 instead of the TVDB ID from tvdb:ep:5618559:s7e2.
docker_watchdog: add automatic manual-stop detection — containers stopped cleanly
(exit 0/143) are tracked in docker_watchdog_manual_stop.db and skipped until
restarted, removing the need to add manually-stopped containers to the exclusion
list. Auto-clears when the container is seen running again.
docker_daily_restart: remove bare `local` declarations outside a function that
were printing an error for every container restarted.
Discover all HOST*_TRANSCODE_SERVERS from every configured host.
For remote hosts, rewrite localhost/127.0.0.1 in the URL to their
Tailscale IP (via resolve_tailscale_ip). Unreachable hosts are
warned and skipped — partial sync continues for reachable servers.
Adding a new host needs no script changes — just HOST*_TRANSCODE_SERVERS
in the new host conf.
play_state_sync.sh:
- Add detect_hosts() call so TRANSCODE_SERVERS alias is populated
- Switch MEDIA_SERVERS → TRANSCODE_SERVERS (correct array name from host*.conf)
- Fix malformed jq fallback in combine step (was {"} now handled with if/else)
- Skip placeholder API keys in _add_server
load_config.sh:
- Replace 'for x in $(ls glob)' with 'while read < <(printf glob | sort)'
so paths with spaces (e.g. dev workspace) don't get word-split
Syncs played/resume state bidirectionally across all configured Emby
and Jellyfin servers. Newest LastPlayedDate wins; users matched by
name (case-insensitive); unmatched users skipped cleanly.
Matching: IMDb/TMDB for movies, TVDB+s/e for episodes, MusicBrainz
track ID for audio. Also syncs resume positions for partial plays.
Config: PLAY_SYNC_ENABLED, PLAY_SYNC_DAYS (default 90), PLAY_SYNC_TYPES.
Modes: --dry-run, --status, --full (ignore day window), --log.
Wired into DAILY_MAINTENANCE_SCRIPTS after arr cleanup.
Auto Scroll now pauses automatically when user scrolls up and resumes
when they reach the bottom — no separate Scroll Lock needed.
Lidarr discovery no longer hard-skips single-seed candidates; they
score low on breadth but can still reach threshold on merit.
arr_sync.sh bidirectional union replaced the old split-ownership model.
Old: HOST1 owns Movies/TV/Music, HOST2 owns Anime — each arr manages
different shares to avoid conflicts.
New: arr_sync.sh keeps all arr databases in union. Either server can
download to any share at any time. The union is the source of truth.
Auth stack references left intact — HOST1 still owns auth config
(one-way sync to HOST2). Only media library ownership language updated.
Files changed:
README.md — server block + share annotations
Fallback/README-Fallback.md — role line + Split Source of Truth section
Orchestrators/README-Orchestrators — daily sync share comments
Rsync/Manual-Rsync.md — DEFAULT_RSYNC_OPTS comment block
Media/README-Media.md — HOST AWARENESS section
Remove setup banners from all 8 scripts that had them (discovery scripts
use comment-only setup sections). Move per-item/detail echoes to log in
arr_sync, lidarr_missing_art, lidarr/radarr/sonarr_cleanup (scan progress,
API query status, root/orphan config lines), and per-title lines in
radarr_tmdb_removed / sonarr_tvdb_removed. Fix log→echo on clean-status
summary lines in arrs_failed_stalled_recovery, media_cleaner, and both
removed-title scripts. Add output tiers section to Manual-Media.md.
22 scripts used docker without verifying it's present. Consistent with the
pattern already established in Docker_Essentials scripts. Added after acquire_lock
and before detect_hosts in each script's setup section.
arr_sync.sh had acquire_lock documented in OPERATIONAL SAFEGUARDS header but the
call was never present in the code — two sync instances could run simultaneously.
bulk_permissions_repair.sh and container_data_export.sh had no lock at all.
All three are now consistent with every other script in Media/ and Tools/.
Script headers (6 files):
- Add OPERATIONAL SAFEGUARDS section to all discovery + emby sync scripts
- Add STATE FILES section to all 3 discovery scripts
- Add CONFIGURATION section to emby_to_*_sync.sh
- Rename USAGE → RUNTIME MODES in emby_to_*_sync.sh (doc standard)
- Fix SONARR_DISCOVERY_MONITOR_MODE default doc: "future" → "all"
Media/README-Media.md:
- Remove "WIP prototype" label; describe all 3 discovery scripts
- Add discovery scripts to scripts table and HOW THE SCRIPTS RELATE diagram
- Add weekly discovery schedule block to EXECUTION ORDER section
- Update intro to mention behavior-driven discovery
Media/Manual-Media.md:
- Add configuration reference sections for all 3 discovery scripts
- Add flag reference sections for all 3 discovery scripts
Tools/README-Tools.md:
- Add "Library Sync Bootstrap" category and emby_to_*_sync.sh to scripts table
- Add emby sync scripts to HOW THE SCRIPTS RELATE diagram
Tools/Manual-Tools.md:
- Update emby sync notes: search commands now fire immediately after add
README.md:
- Update Media folder description to include discovery
- Update weekly schedule to show discovery scripts
- Update README table entry for Media
future only monitors seasons not yet aired — shows with all existing
seasons would add as unmonitored with nothing to search. all monitors
everything so SeriesSearch actually finds episodes to grab.
- New: Media/playback_aware_sonarr_discovery.sh — two-stage TV show
discovery using Emby episode play history → TMDB TV recommendations.
Multi-user aware: diversity score (0-50) is primary seed driver so one
user binge-watching alone cannot dominate. Per-user episode cap (default 8)
limits single-user volume contribution. TMDB→TVDB via external_ids for
reliable Sonarr lookup. monitor=future by default.
- Add to WEEKLY_MAINTENANCE_SCRIPTS; Sonarr discovery config block in
master.conf (threshold=52, lookback=14d, max_adds=3, user_ep_cap=8).
- Fix: all arr add scripts were not triggering searches after adding.
Added _radarr_command/_lidarr_command/_sonarr_command helpers and fire
MoviesSearch/ArtistSearch/SeriesSearch immediately after each successful
add. Affects: playback_aware_radarr_discovery.sh, emby_to_lidarr_sync.sh,
emby_to_radarr_sync.sh, emby_to_sonarr_sync.sh.
- Stage 1 rewritten to use Emby activity log (playback.stop events)
with ItemId batch-fetching instead of per-library UserData sort;
SortBy=DatePlayed requires UserId context and errored server-wide
- Scoring simplified: recency (0-50) + play frequency across all
users (0-50); CommunityRating is not exposed by Emby Items API
so TMDB rating scoring moved entirely to Stage 2
- Batch IDs in groups of 100 — 1500+ unique item IDs in a 30-day
window exceeded GET URL limits on a single request
- Threshold lowered to 50 (from 60); with diverse-genre seeds,
recommendations rarely appear across multiple seeds so breadth
score stays at 10/40 — threshold 50 yields ~5 adds per run at
good quality (rating 7.0+, 1k+ votes)
- HOST1_TMDB_API_KEY moved to sit under Radarr section in
master_host1.conf where it logically belongs
- playback_aware_radarr_discovery.sh: new two-stage movie discovery
using recently watched Emby movies as seeds and TMDB recommendations
as the similarity engine; scores on recency + rating + vote count;
adds HOST*_TMDB_API_KEY alias to detect_hosts() in common.sh
- emby_to_sonarr_sync.sh: restrict to SONARR_EMBY_LIBRARIES allowlist
(Anime, Kids Shows, Stand-Up Comedy, TV shows) — excludes Youtube
and Recorded Sports libraries; add garbage title filter for multi-
season folder names (S01-S03) and bare bracket tags ([Prof])
- emby_to_radarr_sync.sh: add _is_episode_title filter to block anime
episode files stored in Movies library (underscore/dot patterns,
fansub bracket format, episode markers, codec metadata strings)
- emby_to_lidarr_sync.sh: add U+FFFD Unicode replacement character
filter alongside the existing ASCII ? filter for encoding corruption
- master.conf: RADARR_DISCOVERY_* settings, SONARR/RADARR_EMBY_LIBRARIES
allowlists, Radarr discovery added to WEEKLY_MAINTENANCE_SCRIPTS