diff --git a/common.nix b/common.nix index 1ecca88..6343c0d 100644 --- a/common.nix +++ b/common.nix @@ -29,6 +29,7 @@ ./services/prowlarr.nix ./services/jellyfin.nix ./services/bazarr.nix + ./services/bazarr-sync.nix ./services/cloudflare-ddns.nix ./services/authelia.nix ./services/homepage.nix diff --git a/services/bazarr-sync.nix b/services/bazarr-sync.nix new file mode 100644 index 0000000..0f3bf28 --- /dev/null +++ b/services/bazarr-sync.nix @@ -0,0 +1,184 @@ +# 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 ]; + + # 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/data/config/config.ini" ]; then + BAZARR_KEY=$(grep -oP '(?<=apikey = ).*' /var/lib/bazarr/data/config/config.ini || 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") { + + # 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; + }; + }; + }; +}