# services/bazarr-sync.nix — Subtitle sync via bazarr-sync. # # Two systemd timers: # bazarr-sync — hourly, targets only movies/shows added in the last # 2 hours (queries Radarr/Sonarr APIs for recent items) # bazarr-sync-full — weekly full-library sync (every Sunday 04:00) # # Both use the same bazarr-sync container image under podman with --network host # so it can reach Bazarr on localhost:6767. { config, lib, pkgs, ... }: let deps = lib.makeBinPath [ pkgs.curl pkgs.jq pkgs.gnused pkgs.gnugrep pkgs.coreutils pkgs.podman pkgs.yq-go ]; # Shared preamble: extract API keys and write config.yaml preamble = '' set -euo pipefail PATH="${deps}:$PATH" BASE="http://127.0.0.1" CONFIG_DIR="/var/lib/bazarr-sync" CONFIG_FILE="$CONFIG_DIR/config.yaml" # --- Extract API keys (same pattern as arr-interconnect) --- extract_arr_key() { if [ -f "$1" ]; then sed -n 's/.*\(.*\)<\/ApiKey>.*/\1/p' "$1" fi } SONARR_KEY=$(extract_arr_key "/var/lib/sonarr/config.xml") RADARR_KEY=$(extract_arr_key "/var/lib/radarr/config.xml") BAZARR_KEY="" if [ -f "/var/lib/bazarr/config/config.yaml" ]; then BAZARR_KEY=$(yq '.auth.apikey' /var/lib/bazarr/config/config.yaml || true) fi if [ -z "$BAZARR_KEY" ]; then echo "ERROR: Could not extract Bazarr API key" exit 1 fi # --- Write config.yaml for bazarr-sync --- cat > "$CONFIG_FILE" <= $cutoff) | .id] | map(tostring) | join(",")') || true if [ -n "$RADARR_IDS" ]; then echo " Found recently added movies: $RADARR_IDS" else echo " No recently added movies" fi fi # --- Query Sonarr for series with recently imported episodes --- SONARR_IDS="" if [ -n "$SONARR_KEY" ]; then echo "Querying Sonarr for recently imported episodes since $CUTOFF..." SONARR_IDS=$(curl -sf -H "X-Api-Key: $SONARR_KEY" \ "$BASE:8989/api/v3/history?pageSize=200&sortKey=date&sortDirection=descending" | \ jq -r --arg cutoff "$CUTOFF" \ '[.records[] | select(.date >= $cutoff and .eventType == "downloadFolderImported") | .seriesId] | unique | map(tostring) | join(",")') || true if [ -n "$SONARR_IDS" ]; then echo " Found series with recent imports: $SONARR_IDS" else echo " No recently imported episodes" fi fi # --- Exit early if nothing to do --- if [ -z "$RADARR_IDS" ] && [ -z "$SONARR_IDS" ]; then echo "Nothing new to sync. Exiting." exit 0 fi # --- Run bazarr-sync for recent movies --- if [ -n "$RADARR_IDS" ]; then echo "Syncing subtitles for movies: $RADARR_IDS" run_sync movies --radarr-id "$RADARR_IDS" || echo "WARNING: Movie sync failed" fi # --- Run bazarr-sync for recent shows --- if [ -n "$SONARR_IDS" ]; then echo "Syncing subtitles for shows: $SONARR_IDS" run_sync shows --sonarr-id "$SONARR_IDS" || echo "WARNING: Show sync failed" fi echo "Sync complete." ''; # Weekly: full library sync fullScript = pkgs.writeShellScript "bazarr-sync-full" '' ${preamble} echo "Starting full library subtitle sync..." echo "Syncing all movies..." run_sync movies || echo "WARNING: Full movie sync failed" echo "Syncing all shows..." run_sync shows || echo "WARNING: Full show sync failed" echo "Full sync complete." ''; serviceAfter = [ "bazarr.service" "sonarr.service" "radarr.service" "network-online.target" ]; in { config = lib.mkIf (config.networking.hostName == "FredOS-Mediaserver") { # Ensure podman is available with a valid container policy virtualisation.podman.enable = true; # Persistent directory for the generated config.yaml systemd.tmpfiles.rules = [ "d /var/lib/bazarr-sync 0700 root root -" ]; # --- Hourly: recent additions only --- systemd.services.bazarr-sync = { description = "Sync subtitles for recently added media via bazarr-sync"; after = serviceAfter; wants = [ "network-online.target" ]; serviceConfig = { Type = "oneshot"; ExecStart = recentScript; }; }; systemd.timers.bazarr-sync = { description = "Run bazarr-sync hourly for recent additions"; wantedBy = [ "timers.target" ]; timerConfig = { OnCalendar = "hourly"; RandomizedDelaySec = "5m"; Persistent = true; }; }; # --- Weekly: full library sync --- systemd.services.bazarr-sync-full = { description = "Full library subtitle sync via bazarr-sync"; after = serviceAfter; wants = [ "network-online.target" ]; serviceConfig = { Type = "oneshot"; ExecStart = fullScript; }; }; systemd.timers.bazarr-sync-full = { description = "Run full bazarr-sync weekly (Sunday 04:00)"; wantedBy = [ "timers.target" ]; timerConfig = { OnCalendar = "Sun *-*-* 04:00:00"; RandomizedDelaySec = "15m"; Persistent = true; }; }; }; }