cleanup category handling

This commit is contained in:
2026-03-27 22:46:45 -07:00
parent 7f395bd9b3
commit 35c6d1b821
2 changed files with 77 additions and 24 deletions

View File

@@ -112,7 +112,13 @@ let
syncCategories = lib.mkOption {
type = lib.types.listOf lib.types.int;
default = [ ];
description = "List of sync category IDs for the application.";
description = ''
List of Newznab category IDs to sync for this application.
When empty (default), categories are auto-detected at runtime
by querying Prowlarr's indexer/categories API endpoint and
collecting all IDs under the parent category matching the
implementation type (e.g. Sonarr -> TV, Radarr -> Movies).
'';
example = [
5000
5010
@@ -291,6 +297,44 @@ let
};
};
# Map Servarr implementation names to their Newznab parent category names.
# Used to auto-detect syncCategories from the Prowlarr API when not explicitly set.
implementationCategoryMap = {
Sonarr = "TV";
Radarr = "Movies";
Lidarr = "Audio";
Readarr = "Books";
Whisparr = "XXX";
};
# Emit shell code that sets SYNC_CATEGORIES to a JSON array of category IDs.
# When the user provides explicit IDs, use those. Otherwise, query the Prowlarr
# /indexer/categories endpoint and collect the parent + all subcategory IDs for
# the implementation's Newznab category.
mkResolveSyncCategories =
app:
let
hasExplicit = app.syncCategories != [ ];
categoryName = implementationCategoryMap.${app.implementation} or null;
in
if hasExplicit then
"SYNC_CATEGORIES=${lib.escapeShellArg (builtins.toJSON app.syncCategories)}"
else if categoryName != null then
''
echo "Auto-detecting sync categories for ${app.implementation}..."
ALL_CATEGORIES=$(${curl} -sf "$BASE_URL/indexer/categories" -H "X-Api-Key: $API_KEY")
SYNC_CATEGORIES=$(echo "$ALL_CATEGORIES" | ${jq} --arg name ${lib.escapeShellArg categoryName} \
'[.[] | select(.name == $name) | .id, .subCategories[].id]')
if [ "$SYNC_CATEGORIES" = "[]" ] || [ -z "$SYNC_CATEGORIES" ]; then
echo "Warning: could not auto-detect categories for '${categoryName}', using empty list" >&2
SYNC_CATEGORIES='[]'
else
echo "Resolved sync categories: $SYNC_CATEGORIES"
fi
''
else
"SYNC_CATEGORIES='[]' ";
curl = "${pkgs.curl}/bin/curl";
jq = "${pkgs.jq}/bin/jq";
grep = "${pkgs.gnugrep}/bin/grep";
@@ -353,6 +397,7 @@ let
echo "Synced app '${app.name}' already exists, skipping"
else
echo "Adding synced app '${app.name}'..."
${mkResolveSyncCategories app}
PAYLOAD=$(${jq} -n \
--arg name ${lib.escapeShellArg app.name} \
--arg implementation ${lib.escapeShellArg app.implementation} \
@@ -361,7 +406,7 @@ let
--arg prowlarrUrl ${lib.escapeShellArg app.prowlarrUrl} \
--arg baseUrl ${lib.escapeShellArg app.baseUrl} \
--arg apiKey "$TARGET_API_KEY" \
--argjson syncCategories ${builtins.toJSON app.syncCategories} \
--argjson syncCategories "$SYNC_CATEGORIES" \
'{
name: $name,
implementation: $implementation,

View File

@@ -182,16 +182,7 @@ pkgs.testers.runNixOSTest {
prowlarrUrl = "http://localhost:9696";
baseUrl = "http://localhost:8989";
apiKeyFrom = "/var/lib/sonarr/.config/NzbDrone/config.xml";
syncCategories = [
5000
5010
5020
5030
5040
5045
5050
5090
];
# syncCategories omitted — auto-detected from Prowlarr API
serviceName = "sonarr";
}
{
@@ -201,18 +192,7 @@ pkgs.testers.runNixOSTest {
prowlarrUrl = "http://localhost:9696";
baseUrl = "http://localhost:7878";
apiKeyFrom = "/var/lib/radarr/.config/Radarr/config.xml";
syncCategories = [
2000
2010
2020
2030
2040
2045
2050
2060
2070
2080
];
# syncCategories omitted — auto-detected from Prowlarr API
serviceName = "radarr";
}
];
@@ -325,6 +305,34 @@ pkgs.testers.runNixOSTest {
"jq -e '.[] | select(.name == \"Radarr\")'"
)
# Verify auto-detected syncCategories are non-empty for Sonarr (TV: 5xxx)
machine.succeed(
"API_KEY=$(grep -oP '(?<=<ApiKey>)[^<]+' /var/lib/prowlarr/config.xml) && "
"curl -sf http://localhost:9696/api/v1/applications -H \"X-Api-Key: $API_KEY\" | "
"jq -e '.[] | select(.name == \"Sonarr\") | .fields[] | select(.name == \"syncCategories\") | .value | length > 0'"
)
# Verify auto-detected syncCategories contain parent TV category (5000)
machine.succeed(
"API_KEY=$(grep -oP '(?<=<ApiKey>)[^<]+' /var/lib/prowlarr/config.xml) && "
"curl -sf http://localhost:9696/api/v1/applications -H \"X-Api-Key: $API_KEY\" | "
"jq -e '.[] | select(.name == \"Sonarr\") | .fields[] | select(.name == \"syncCategories\") | .value | map(select(. == 5000)) | length > 0'"
)
# Verify auto-detected syncCategories are non-empty for Radarr (Movies: 2xxx)
machine.succeed(
"API_KEY=$(grep -oP '(?<=<ApiKey>)[^<]+' /var/lib/prowlarr/config.xml) && "
"curl -sf http://localhost:9696/api/v1/applications -H \"X-Api-Key: $API_KEY\" | "
"jq -e '.[] | select(.name == \"Radarr\") | .fields[] | select(.name == \"syncCategories\") | .value | length > 0'"
)
# Verify auto-detected syncCategories contain parent Movies category (2000)
machine.succeed(
"API_KEY=$(grep -oP '(?<=<ApiKey>)[^<]+' /var/lib/prowlarr/config.xml) && "
"curl -sf http://localhost:9696/api/v1/applications -H \"X-Api-Key: $API_KEY\" | "
"jq -e '.[] | select(.name == \"Radarr\") | .fields[] | select(.name == \"syncCategories\") | .value | map(select(. == 2000)) | length > 0'"
)
# Idempotency test: restart init services and verify no duplicate entries
machine.succeed("systemctl restart sonarr-init.service")
machine.succeed("systemctl restart radarr-init.service")