From 75fc7f09309074fc636910666b9a0758a44df76b Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Sun, 28 Jun 2026 22:12:18 +1000 Subject: [PATCH 1/7] feat: initial sonarr terraform configuration --- .gitignore | 6 + .pre-commit-config.yaml | 24 ++++ .woodpecker/apply.yaml | 23 +++ .woodpecker/plan.yaml | 21 +++ .woodpecker/pre-commit.yaml | 18 +++ Makefile | 35 +++++ config/config.hcl | 46 ++++++ config/custom_format/format_av1.yaml | 9 ++ config/custom_format/format_x264.yaml | 9 ++ config/custom_format/format_x265.yaml | 9 ++ config/custom_format/hdr10.yaml | 23 +++ config/custom_format/hvec_10bit.yaml | 30 ++++ .../custom_format/release_AsmoFuscated.yaml | 9 ++ config/custom_format/release_d3g.yaml | 9 ++ config/custom_format/release_iVy.yaml | 9 ++ config/custom_format/resolution_1080p.yaml | 9 ++ config/custom_format/resolution_2160p.yaml | 9 ++ config/custom_format/resolution_720p.yaml | 9 ++ config/custom_format/size_0_800.yaml | 11 ++ config/custom_format/size_1500_3000.yaml | 11 ++ config/custom_format/size_3000_6000.yaml | 11 ++ config/custom_format/size_6000_10000.yaml | 11 ++ config/custom_format/size_800_1500.yaml | 11 ++ config/custom_format/source_bluray.yaml | 9 ++ config/custom_format/source_hdtv.yaml | 9 ++ config/custom_format/source_webdl.yaml | 16 +++ config/delay_profile/default.yaml | 9 ++ config/download_client/NZBGet.yaml | 10 ++ config/indexer/NZBgeek.yaml | 15 ++ config/indexer/NZBgeek_Prowlarr.yaml | 17 +++ config/notification/Jellyfin.yaml | 15 ++ config/quality_profile/BestQuality_1080p.yaml | 105 ++++++++++++++ config/quality_profile/BestQuality_2160p.yaml | 132 ++++++++++++++++++ config/quality_profile/SD_OLD_SHOWS.yaml | 29 ++++ config/root_folder/tvseries.yaml | 1 + environments/root.hcl | 32 +++++ .../sonarr.service.consul/terragrunt.hcl | 27 ++++ modules/sonarr/main.tf | 128 +++++++++++++++++ modules/sonarr/variables.tf | 34 +++++ 39 files changed, 950 insertions(+) create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 .woodpecker/apply.yaml create mode 100644 .woodpecker/plan.yaml create mode 100644 .woodpecker/pre-commit.yaml create mode 100644 Makefile create mode 100644 config/config.hcl create mode 100644 config/custom_format/format_av1.yaml create mode 100644 config/custom_format/format_x264.yaml create mode 100644 config/custom_format/format_x265.yaml create mode 100644 config/custom_format/hdr10.yaml create mode 100644 config/custom_format/hvec_10bit.yaml create mode 100644 config/custom_format/release_AsmoFuscated.yaml create mode 100644 config/custom_format/release_d3g.yaml create mode 100644 config/custom_format/release_iVy.yaml create mode 100644 config/custom_format/resolution_1080p.yaml create mode 100644 config/custom_format/resolution_2160p.yaml create mode 100644 config/custom_format/resolution_720p.yaml create mode 100644 config/custom_format/size_0_800.yaml create mode 100644 config/custom_format/size_1500_3000.yaml create mode 100644 config/custom_format/size_3000_6000.yaml create mode 100644 config/custom_format/size_6000_10000.yaml create mode 100644 config/custom_format/size_800_1500.yaml create mode 100644 config/custom_format/source_bluray.yaml create mode 100644 config/custom_format/source_hdtv.yaml create mode 100644 config/custom_format/source_webdl.yaml create mode 100644 config/delay_profile/default.yaml create mode 100644 config/download_client/NZBGet.yaml create mode 100644 config/indexer/NZBgeek.yaml create mode 100644 config/indexer/NZBgeek_Prowlarr.yaml create mode 100644 config/notification/Jellyfin.yaml create mode 100644 config/quality_profile/BestQuality_1080p.yaml create mode 100644 config/quality_profile/BestQuality_2160p.yaml create mode 100644 config/quality_profile/SD_OLD_SHOWS.yaml create mode 100644 config/root_folder/tvseries.yaml create mode 100644 environments/root.hcl create mode 100644 environments/sonarr.service.consul/terragrunt.hcl create mode 100644 modules/sonarr/main.tf create mode 100644 modules/sonarr/variables.tf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13275b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.terraform/ +*.tfstate +*.tfstate.backup +*.tfplan +backend.tf +.terragrunt-cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..646cd65 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,24 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: end-of-file-fixer + types: [yaml] + - id: trailing-whitespace + types: [yaml] + - repo: https://github.com/gruntwork-io/pre-commit + rev: v0.1.30 + hooks: + - id: tofu-fmt + - id: tofu-validate + - id: tflint + - id: terragrunt-hcl-fmt + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.37.1 + hooks: + - id: yamllint + args: + [ + "-d {extends: relaxed, rules: {line-length: disable}, ignore: chart}", + "-s", + ] diff --git a/.woodpecker/apply.yaml b/.woodpecker/apply.yaml new file mode 100644 index 0000000..84a79e2 --- /dev/null +++ b/.woodpecker/apply.yaml @@ -0,0 +1,23 @@ +when: + - event: push + branch: main + +steps: + - name: apply + image: git.unkin.net/unkin/almalinux9-opentofu:20260606 + environment: + VAULT_AUTH_METHOD: kubernetes + commands: + - dnf install vault -y + - make plan + - make apply + backend_options: + kubernetes: + serviceAccountName: terraform-sonarr + resources: + requests: + memory: 512Mi + cpu: 1 + limits: + memory: 2Gi + cpu: 2 diff --git a/.woodpecker/plan.yaml b/.woodpecker/plan.yaml new file mode 100644 index 0000000..32c6281 --- /dev/null +++ b/.woodpecker/plan.yaml @@ -0,0 +1,21 @@ +when: + - event: pull_request + +steps: + - name: plan + image: git.unkin.net/unkin/almalinux9-opentofu:20260606 + environment: + VAULT_AUTH_METHOD: kubernetes + commands: + - dnf install vault -y + - make plan + backend_options: + kubernetes: + serviceAccountName: terraform-sonarr + resources: + requests: + memory: 512Mi + cpu: 1 + limits: + memory: 2Gi + cpu: 2 diff --git a/.woodpecker/pre-commit.yaml b/.woodpecker/pre-commit.yaml new file mode 100644 index 0000000..5c5738f --- /dev/null +++ b/.woodpecker/pre-commit.yaml @@ -0,0 +1,18 @@ +when: + - event: pull_request + +steps: + - name: pre-commit + image: git.unkin.net/unkin/almalinux9-opentofu:20260606 + commands: + - uvx pre-commit run --all-files + backend_options: + kubernetes: + serviceAccountName: default + resources: + requests: + memory: 512Mi + cpu: 1 + limits: + memory: 2Gi + cpu: 2 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f879029 --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +.PHONY: init plan apply format + +VAULT_AUTH_METHOD ?= approle +VAULT_K8S_ROLE ?= woodpecker_terraform_sonarr +VAULT_K8S_MOUNT ?= auth/k8s/au/syd1 +VAULT_K8S_JWT_PATH ?= /var/run/secrets/kubernetes.io/serviceaccount/token + +define vault_env + @export VAULT_ADDR="https://vault.service.consul:8200" && \ + if [ "$(VAULT_AUTH_METHOD)" = "kubernetes" ]; then \ + export VAULT_TOKEN=$$(vault write -field=token $(VAULT_K8S_MOUNT)/login role=$(VAULT_K8S_ROLE) jwt=$$(cat $(VAULT_K8S_JWT_PATH))); \ + else \ + export VAULT_TOKEN=$$(vault write -field=token auth/approle/login role_id=$$VAULT_ROLEID); \ + fi && \ + export CONSUL_HTTP_TOKEN=$$(vault read -field=token consul_root/au/syd1/creds/terraform-sonarr) && \ + export SONARR_API_KEY=$$(vault kv get -field=apitoken kv/service/media-apps/sonarr) +endef + +init: + @$(call vault_env) && \ + terragrunt run --all --non-interactive init -- -upgrade + +plan: init + @$(call vault_env) && \ + terragrunt run --all --parallelism 4 --non-interactive plan + +apply: init + @$(call vault_env) && \ + terragrunt run --all --parallelism 2 --non-interactive apply + +format: + @echo "Formatting OpenTofu files..." + @tofu fmt -recursive . + @echo "Formatting Terragrunt files..." + @terragrunt hcl fmt diff --git a/config/config.hcl b/config/config.hcl new file mode 100644 index 0000000..d112876 --- /dev/null +++ b/config/config.hcl @@ -0,0 +1,46 @@ +locals { + config_files = fileset(".", "**/*.yaml") + + all_configs = { + for file_path in local.config_files : + file_path => yamldecode(file(file_path)) + } + + config = { + custom_formats = { + for file_path, content in local.all_configs : + trimsuffix(basename(file_path), ".yaml") => content + if startswith(file_path, "custom_format/") + } + quality_profiles = { + for file_path, content in local.all_configs : + trimsuffix(basename(file_path), ".yaml") => content + if startswith(file_path, "quality_profile/") + } + download_clients = { + for file_path, content in local.all_configs : + trimsuffix(basename(file_path), ".yaml") => content + if startswith(file_path, "download_client/") + } + indexers = { + for file_path, content in local.all_configs : + trimsuffix(basename(file_path), ".yaml") => content + if startswith(file_path, "indexer/") + } + notifications = { + for file_path, content in local.all_configs : + trimsuffix(basename(file_path), ".yaml") => content + if startswith(file_path, "notification/") + } + delay_profiles = { + for file_path, content in local.all_configs : + trimsuffix(basename(file_path), ".yaml") => content + if startswith(file_path, "delay_profile/") + } + root_folders = { + for file_path, content in local.all_configs : + trimsuffix(basename(file_path), ".yaml") => content + if startswith(file_path, "root_folder/") + } + } +} diff --git a/config/custom_format/format_av1.yaml b/config/custom_format/format_av1.yaml new file mode 100644 index 0000000..7d146e2 --- /dev/null +++ b/config/custom_format/format_av1.yaml @@ -0,0 +1,9 @@ +include_custom_format_when_renaming: false +specifications: + - name: av1 + implementation: ReleaseTitleSpecification + negate: false + required: false + fields: + - name: value + value: "av1" diff --git a/config/custom_format/format_x264.yaml b/config/custom_format/format_x264.yaml new file mode 100644 index 0000000..b16d308 --- /dev/null +++ b/config/custom_format/format_x264.yaml @@ -0,0 +1,9 @@ +include_custom_format_when_renaming: false +specifications: + - name: x264 + implementation: ReleaseTitleSpecification + negate: false + required: false + fields: + - name: value + value: "(x|h)\\.?264" diff --git a/config/custom_format/format_x265.yaml b/config/custom_format/format_x265.yaml new file mode 100644 index 0000000..bb0e8b5 --- /dev/null +++ b/config/custom_format/format_x265.yaml @@ -0,0 +1,9 @@ +include_custom_format_when_renaming: false +specifications: + - name: x265 + implementation: ReleaseTitleSpecification + negate: false + required: false + fields: + - name: value + value: "(((x|h)\\.?265)|(HEVC))" diff --git a/config/custom_format/hdr10.yaml b/config/custom_format/hdr10.yaml new file mode 100644 index 0000000..be31992 --- /dev/null +++ b/config/custom_format/hdr10.yaml @@ -0,0 +1,23 @@ +include_custom_format_when_renaming: false +specifications: + - name: hdr10 + implementation: ReleaseTitleSpecification + negate: false + required: true + fields: + - name: value + value: "hdr10" + - name: x265 + implementation: ReleaseTitleSpecification + negate: false + required: true + fields: + - name: value + value: "(((x|h)\\.?265)|(HEVC))" + - name: Surround Sound + implementation: ReleaseTitleSpecification + negate: false + required: true + fields: + - name: value + value: "DTS.?(HD|ES|X(?!\\D))|TRUEHD|ATMOS|DD(\\+|P).?([5-9])|EAC3.?([5-9])" diff --git a/config/custom_format/hvec_10bit.yaml b/config/custom_format/hvec_10bit.yaml new file mode 100644 index 0000000..fc4a7c3 --- /dev/null +++ b/config/custom_format/hvec_10bit.yaml @@ -0,0 +1,30 @@ +include_custom_format_when_renaming: false +specifications: + - name: 10bit + implementation: ReleaseTitleSpecification + negate: false + required: true + fields: + - name: value + value: "10bit" + - name: hvec + implementation: ReleaseTitleSpecification + negate: false + required: false + fields: + - name: value + value: "hvec" + - name: x265 + implementation: ReleaseTitleSpecification + negate: false + required: true + fields: + - name: value + value: "(((x|h)\\.?265)|(HEVC))" + - name: "release_iVy: iVy" + implementation: ReleaseGroupSpecification + negate: false + required: true + fields: + - name: value + value: "-iVy$" diff --git a/config/custom_format/release_AsmoFuscated.yaml b/config/custom_format/release_AsmoFuscated.yaml new file mode 100644 index 0000000..f2a6c3d --- /dev/null +++ b/config/custom_format/release_AsmoFuscated.yaml @@ -0,0 +1,9 @@ +include_custom_format_when_renaming: false +specifications: + - name: "AsmoFuscated " + implementation: ReleaseGroupSpecification + negate: false + required: false + fields: + - name: value + value: "AsmoFuscated$" diff --git a/config/custom_format/release_d3g.yaml b/config/custom_format/release_d3g.yaml new file mode 100644 index 0000000..a9c9c69 --- /dev/null +++ b/config/custom_format/release_d3g.yaml @@ -0,0 +1,9 @@ +include_custom_format_when_renaming: false +specifications: + - name: d3g + implementation: ReleaseGroupSpecification + negate: false + required: false + fields: + - name: value + value: "d3g$" diff --git a/config/custom_format/release_iVy.yaml b/config/custom_format/release_iVy.yaml new file mode 100644 index 0000000..767f70b --- /dev/null +++ b/config/custom_format/release_iVy.yaml @@ -0,0 +1,9 @@ +include_custom_format_when_renaming: false +specifications: + - name: iVy + implementation: ReleaseGroupSpecification + negate: false + required: false + fields: + - name: value + value: "iVy$" diff --git a/config/custom_format/resolution_1080p.yaml b/config/custom_format/resolution_1080p.yaml new file mode 100644 index 0000000..b0343e5 --- /dev/null +++ b/config/custom_format/resolution_1080p.yaml @@ -0,0 +1,9 @@ +include_custom_format_when_renaming: false +specifications: + - name: 1080p + implementation: ResolutionSpecification + negate: false + required: false + fields: + - name: value + value: "1080" diff --git a/config/custom_format/resolution_2160p.yaml b/config/custom_format/resolution_2160p.yaml new file mode 100644 index 0000000..c554727 --- /dev/null +++ b/config/custom_format/resolution_2160p.yaml @@ -0,0 +1,9 @@ +include_custom_format_when_renaming: false +specifications: + - name: 2160p + implementation: ResolutionSpecification + negate: false + required: false + fields: + - name: value + value: "2160" diff --git a/config/custom_format/resolution_720p.yaml b/config/custom_format/resolution_720p.yaml new file mode 100644 index 0000000..3661b07 --- /dev/null +++ b/config/custom_format/resolution_720p.yaml @@ -0,0 +1,9 @@ +include_custom_format_when_renaming: false +specifications: + - name: 720p + implementation: ResolutionSpecification + negate: false + required: false + fields: + - name: value + value: "720" diff --git a/config/custom_format/size_0_800.yaml b/config/custom_format/size_0_800.yaml new file mode 100644 index 0000000..ffb1a25 --- /dev/null +++ b/config/custom_format/size_0_800.yaml @@ -0,0 +1,11 @@ +include_custom_format_when_renaming: false +specifications: + - name: size_0_800 + implementation: SizeSpecification + negate: false + required: false + fields: + - name: min + value: "0" + - name: max + value: "0.8" diff --git a/config/custom_format/size_1500_3000.yaml b/config/custom_format/size_1500_3000.yaml new file mode 100644 index 0000000..2b56a5e --- /dev/null +++ b/config/custom_format/size_1500_3000.yaml @@ -0,0 +1,11 @@ +include_custom_format_when_renaming: false +specifications: + - name: size_1500_3000 + implementation: SizeSpecification + negate: false + required: false + fields: + - name: min + value: "1.5" + - name: max + value: "3" diff --git a/config/custom_format/size_3000_6000.yaml b/config/custom_format/size_3000_6000.yaml new file mode 100644 index 0000000..ad1c052 --- /dev/null +++ b/config/custom_format/size_3000_6000.yaml @@ -0,0 +1,11 @@ +include_custom_format_when_renaming: false +specifications: + - name: size_3000_6000 + implementation: SizeSpecification + negate: false + required: false + fields: + - name: min + value: "3" + - name: max + value: "6" diff --git a/config/custom_format/size_6000_10000.yaml b/config/custom_format/size_6000_10000.yaml new file mode 100644 index 0000000..d1c673e --- /dev/null +++ b/config/custom_format/size_6000_10000.yaml @@ -0,0 +1,11 @@ +include_custom_format_when_renaming: false +specifications: + - name: size_6000_10000 + implementation: SizeSpecification + negate: false + required: false + fields: + - name: min + value: "6" + - name: max + value: "10" diff --git a/config/custom_format/size_800_1500.yaml b/config/custom_format/size_800_1500.yaml new file mode 100644 index 0000000..e852f31 --- /dev/null +++ b/config/custom_format/size_800_1500.yaml @@ -0,0 +1,11 @@ +include_custom_format_when_renaming: false +specifications: + - name: size_800_1500 + implementation: SizeSpecification + negate: false + required: false + fields: + - name: min + value: "0.8" + - name: max + value: "1.5" diff --git a/config/custom_format/source_bluray.yaml b/config/custom_format/source_bluray.yaml new file mode 100644 index 0000000..a2814af --- /dev/null +++ b/config/custom_format/source_bluray.yaml @@ -0,0 +1,9 @@ +include_custom_format_when_renaming: false +specifications: + - name: bluray + implementation: SourceSpecification + negate: false + required: false + fields: + - name: value + value: "6" diff --git a/config/custom_format/source_hdtv.yaml b/config/custom_format/source_hdtv.yaml new file mode 100644 index 0000000..b402341 --- /dev/null +++ b/config/custom_format/source_hdtv.yaml @@ -0,0 +1,9 @@ +include_custom_format_when_renaming: false +specifications: + - name: hdtv + implementation: SourceSpecification + negate: false + required: false + fields: + - name: value + value: "1" diff --git a/config/custom_format/source_webdl.yaml b/config/custom_format/source_webdl.yaml new file mode 100644 index 0000000..f95ff1a --- /dev/null +++ b/config/custom_format/source_webdl.yaml @@ -0,0 +1,16 @@ +include_custom_format_when_renaming: false +specifications: + - name: webdl + implementation: SourceSpecification + negate: false + required: false + fields: + - name: value + value: "3" + - name: webrip + implementation: SourceSpecification + negate: false + required: false + fields: + - name: value + value: "4" diff --git a/config/delay_profile/default.yaml b/config/delay_profile/default.yaml new file mode 100644 index 0000000..e4256b6 --- /dev/null +++ b/config/delay_profile/default.yaml @@ -0,0 +1,9 @@ +enable_usenet: true +enable_torrent: true +preferred_protocol: usenet +usenet_delay: 0 +torrent_delay: 0 +bypass_if_highest_quality: true +bypass_if_above_custom_format_score: false +minimum_custom_format_score: 0 +tags: [] diff --git a/config/download_client/NZBGet.yaml b/config/download_client/NZBGet.yaml new file mode 100644 index 0000000..9507d22 --- /dev/null +++ b/config/download_client/NZBGet.yaml @@ -0,0 +1,10 @@ +enable: true +priority: 1 +host: nzbget.service.consul +port: 443 +use_ssl: true +username: svc_nzbsubmit +password: "" +tv_category: tvseries +remove_completed_downloads: true +remove_failed_downloads: true diff --git a/config/indexer/NZBgeek.yaml b/config/indexer/NZBgeek.yaml new file mode 100644 index 0000000..ac12b0b --- /dev/null +++ b/config/indexer/NZBgeek.yaml @@ -0,0 +1,15 @@ +enable_automatic_search: true +enable_interactive_search: true +enable_rss: true +priority: 25 +base_url: "https://api.nzbgeek.info" +api_path: "/api" +api_key: "" +categories: + - 5040 + - 5045 + - 5060 + - 5070 + - 5080 +anime_categories: [] +season_search_maximum_single_episode_age: 0 diff --git a/config/indexer/NZBgeek_Prowlarr.yaml b/config/indexer/NZBgeek_Prowlarr.yaml new file mode 100644 index 0000000..971201a --- /dev/null +++ b/config/indexer/NZBgeek_Prowlarr.yaml @@ -0,0 +1,17 @@ +enable_automatic_search: true +enable_interactive_search: true +enable_rss: true +priority: 25 +base_url: "https://prowlarr.service.consul/1/" +api_path: "/api" +api_key: "" +categories: + - 5000 + - 5020 + - 5030 + - 5040 + - 5045 + - 5050 +anime_categories: + - 5070 +season_search_maximum_single_episode_age: 0 diff --git a/config/notification/Jellyfin.yaml b/config/notification/Jellyfin.yaml new file mode 100644 index 0000000..073a801 --- /dev/null +++ b/config/notification/Jellyfin.yaml @@ -0,0 +1,15 @@ +host: jellyfin.service.consul +port: 443 +use_ssl: true +api_key: "" +notify: false +update_library: true +on_grab: true +on_download: true +on_upgrade: true +on_rename: true +on_series_add: true +on_series_delete: true +on_episode_file_delete: true +on_episode_file_delete_for_upgrade: true +on_application_update: true diff --git a/config/quality_profile/BestQuality_1080p.yaml b/config/quality_profile/BestQuality_1080p.yaml new file mode 100644 index 0000000..57e1360 --- /dev/null +++ b/config/quality_profile/BestQuality_1080p.yaml @@ -0,0 +1,105 @@ +upgrade_allowed: true +cutoff: 7 +cutoff_format_score: 5000 +min_format_score: 0 +quality_groups: + - id: 4 + qualities: + - id: 4 + name: HDTV-720p + source: television + resolution: 720 + - id: 9 + qualities: + - id: 9 + name: HDTV-1080p + source: television + resolution: 1080 + - id: 14 + qualities: + - id: 14 + name: WEBRip-720p + source: webRip + resolution: 720 + - id: 5 + qualities: + - id: 5 + name: WEBDL-720p + source: web + resolution: 720 + - id: 6 + qualities: + - id: 6 + name: Bluray-720p + source: bluray + resolution: 720 + - id: 15 + qualities: + - id: 15 + name: WEBRip-1080p + source: webRip + resolution: 1080 + - id: 3 + qualities: + - id: 3 + name: WEBDL-1080p + source: web + resolution: 1080 + - id: 7 + qualities: + - id: 7 + name: Bluray-1080p + source: bluray + resolution: 1080 +format_items: + - name: release_AsmoFuscated + format: release_AsmoFuscated + score: 50 + - name: release_d3g + format: release_d3g + score: 50 + - name: release_iVy + format: release_iVy + score: 2000 + - name: format_av1 + format: format_av1 + score: -1000 + - name: size_0_800 + format: size_0_800 + score: 50 + - name: source_hdtv + format: source_hdtv + score: 50 + - name: source_webdl + format: source_webdl + score: 100 + - name: source_bluray + format: source_bluray + score: 200 + - name: size_3000_6000 + format: size_3000_6000 + score: 100 + - name: size_1500_3000 + format: size_1500_3000 + score: 200 + - name: size_800_1500 + format: size_800_1500 + score: 600 + - name: hvec_10bit + format: hvec_10bit + score: 100 + - name: resolution_2160p + format: resolution_2160p + score: -1000 + - name: resolution_720p + format: resolution_720p + score: 50 + - name: resolution_1080p + format: resolution_1080p + score: 500 + - name: format_x264 + format: format_x264 + score: -500 + - name: format_x265 + format: format_x265 + score: 1000 diff --git a/config/quality_profile/BestQuality_2160p.yaml b/config/quality_profile/BestQuality_2160p.yaml new file mode 100644 index 0000000..e49cf94 --- /dev/null +++ b/config/quality_profile/BestQuality_2160p.yaml @@ -0,0 +1,132 @@ +upgrade_allowed: true +cutoff: 19 +cutoff_format_score: 5000 +min_format_score: 0 +quality_groups: + - id: 4 + qualities: + - id: 4 + name: HDTV-720p + source: television + resolution: 720 + - id: 9 + qualities: + - id: 9 + name: HDTV-1080p + source: television + resolution: 1080 + - id: 14 + qualities: + - id: 14 + name: WEBRip-720p + source: webRip + resolution: 720 + - id: 5 + qualities: + - id: 5 + name: WEBDL-720p + source: web + resolution: 720 + - id: 6 + qualities: + - id: 6 + name: Bluray-720p + source: bluray + resolution: 720 + - id: 15 + qualities: + - id: 15 + name: WEBRip-1080p + source: webRip + resolution: 1080 + - id: 3 + qualities: + - id: 3 + name: WEBDL-1080p + source: web + resolution: 1080 + - id: 7 + qualities: + - id: 7 + name: Bluray-1080p + source: bluray + resolution: 1080 + - id: 16 + qualities: + - id: 16 + name: HDTV-2160p + source: television + resolution: 2160 + - id: 17 + qualities: + - id: 17 + name: WEBRip-2160p + source: webRip + resolution: 2160 + - id: 18 + qualities: + - id: 18 + name: WEBDL-2160p + source: web + resolution: 2160 + - id: 19 + qualities: + - id: 19 + name: Bluray-2160p + source: bluray + resolution: 2160 +format_items: + - name: hdr10 + format: hdr10 + score: 2000 + - name: release_AsmoFuscated + format: release_AsmoFuscated + score: 600 + - name: release_d3g + format: release_d3g + score: 1000 + - name: release_iVy + format: release_iVy + score: 400 + - name: format_av1 + format: format_av1 + score: -2000 + - name: source_hdtv + format: source_hdtv + score: 50 + - name: source_webdl + format: source_webdl + score: 100 + - name: source_bluray + format: source_bluray + score: 200 + - name: size_3000_6000 + format: size_3000_6000 + score: 300 + - name: size_6000_10000 + format: size_6000_10000 + score: 400 + - name: size_1500_3000 + format: size_1500_3000 + score: 200 + - name: size_800_1500 + format: size_800_1500 + score: 100 + - name: hvec_10bit + format: hvec_10bit + score: 500 + - name: resolution_2160p + format: resolution_2160p + score: 500 + - name: resolution_720p + format: resolution_720p + score: 50 + - name: resolution_1080p + format: resolution_1080p + score: 150 + - name: format_x265 + format: format_x265 + score: 1000 + - name: format_x264 + format: format_x264 + score: -1000 diff --git a/config/quality_profile/SD_OLD_SHOWS.yaml b/config/quality_profile/SD_OLD_SHOWS.yaml new file mode 100644 index 0000000..ffdfdbc --- /dev/null +++ b/config/quality_profile/SD_OLD_SHOWS.yaml @@ -0,0 +1,29 @@ +upgrade_allowed: false +cutoff: 1 +cutoff_format_score: 0 +min_format_score: 0 +quality_groups: + - id: 1 + qualities: + - id: 1 + name: SDTV + source: television + resolution: 480 + - id: 1000 + name: "WEB 480p" + qualities: + - id: 12 + name: WEBRip-480p + source: webRip + resolution: 480 + - id: 8 + name: WEBDL-480p + source: web + resolution: 480 + - id: 2 + qualities: + - id: 2 + name: DVD + source: dvd + resolution: 480 +format_items: [] diff --git a/config/root_folder/tvseries.yaml b/config/root_folder/tvseries.yaml new file mode 100644 index 0000000..0a10bee --- /dev/null +++ b/config/root_folder/tvseries.yaml @@ -0,0 +1 @@ +path: "/shared/media/tvseries" diff --git a/environments/root.hcl b/environments/root.hcl new file mode 100644 index 0000000..ffbe4d5 --- /dev/null +++ b/environments/root.hcl @@ -0,0 +1,32 @@ +generate "backend" { + path = "backend.tf" + if_exists = "overwrite" + contents = < Date: Sun, 28 Jun 2026 22:20:16 +1000 Subject: [PATCH 2/7] fix: use TF_VAR_sonarr_api_key for terraform variable injection The generated backend.tf defines a terraform variable, so the Makefile must export the API key as TF_VAR_sonarr_api_key rather than SONARR_API_KEY. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f879029..4c6908c 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ define vault_env export VAULT_TOKEN=$$(vault write -field=token auth/approle/login role_id=$$VAULT_ROLEID); \ fi && \ export CONSUL_HTTP_TOKEN=$$(vault read -field=token consul_root/au/syd1/creds/terraform-sonarr) && \ - export SONARR_API_KEY=$$(vault kv get -field=apitoken kv/service/media-apps/sonarr) + export TF_VAR_sonarr_api_key=$$(vault kv get -field=apitoken kv/service/media-apps/sonarr) endef init: From fe1219878e319423d25da3e5f9ee4c1d0ca4cbb6 Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Sun, 28 Jun 2026 22:27:40 +1000 Subject: [PATCH 3/7] fix: align terraform resources with actual provider schema Use nested attribute assignment instead of dynamic blocks for specifications, quality_groups, and format_items. Remove unsupported season_search_maximum_single_episode_age from indexer configs. Flatten custom format specifications to use value/min/max directly. --- config/custom_format/format_av1.yaml | 12 +- config/custom_format/format_x264.yaml | 12 +- config/custom_format/format_x265.yaml | 12 +- config/custom_format/hdr10.yaml | 36 ++--- config/custom_format/hvec_10bit.yaml | 48 +++---- .../custom_format/release_AsmoFuscated.yaml | 12 +- config/custom_format/release_d3g.yaml | 12 +- config/custom_format/release_iVy.yaml | 12 +- config/custom_format/resolution_1080p.yaml | 12 +- config/custom_format/resolution_2160p.yaml | 12 +- config/custom_format/resolution_720p.yaml | 12 +- config/custom_format/size_0_800.yaml | 15 +- config/custom_format/size_1500_3000.yaml | 15 +- config/custom_format/size_3000_6000.yaml | 15 +- config/custom_format/size_6000_10000.yaml | 15 +- config/custom_format/size_800_1500.yaml | 15 +- config/custom_format/source_bluray.yaml | 12 +- config/custom_format/source_hdtv.yaml | 12 +- config/custom_format/source_webdl.yaml | 24 ++-- config/indexer/NZBgeek.yaml | 1 - config/indexer/NZBgeek_Prowlarr.yaml | 1 - env | 1 + .../sonarr.service.consul/.terraform.lock.hcl | 25 ++++ modules/sonarr/main.tf | 128 +++++++----------- 24 files changed, 202 insertions(+), 269 deletions(-) create mode 100644 env create mode 100644 environments/sonarr.service.consul/.terraform.lock.hcl diff --git a/config/custom_format/format_av1.yaml b/config/custom_format/format_av1.yaml index 7d146e2..30244b9 100644 --- a/config/custom_format/format_av1.yaml +++ b/config/custom_format/format_av1.yaml @@ -1,9 +1,7 @@ include_custom_format_when_renaming: false specifications: - - name: av1 - implementation: ReleaseTitleSpecification - negate: false - required: false - fields: - - name: value - value: "av1" +- name: av1 + implementation: ReleaseTitleSpecification + negate: false + required: false + value: av1 diff --git a/config/custom_format/format_x264.yaml b/config/custom_format/format_x264.yaml index b16d308..a06824e 100644 --- a/config/custom_format/format_x264.yaml +++ b/config/custom_format/format_x264.yaml @@ -1,9 +1,7 @@ include_custom_format_when_renaming: false specifications: - - name: x264 - implementation: ReleaseTitleSpecification - negate: false - required: false - fields: - - name: value - value: "(x|h)\\.?264" +- name: x264 + implementation: ReleaseTitleSpecification + negate: false + required: false + value: (x|h)\.?264 diff --git a/config/custom_format/format_x265.yaml b/config/custom_format/format_x265.yaml index bb0e8b5..e357a62 100644 --- a/config/custom_format/format_x265.yaml +++ b/config/custom_format/format_x265.yaml @@ -1,9 +1,7 @@ include_custom_format_when_renaming: false specifications: - - name: x265 - implementation: ReleaseTitleSpecification - negate: false - required: false - fields: - - name: value - value: "(((x|h)\\.?265)|(HEVC))" +- name: x265 + implementation: ReleaseTitleSpecification + negate: false + required: false + value: (((x|h)\.?265)|(HEVC)) diff --git a/config/custom_format/hdr10.yaml b/config/custom_format/hdr10.yaml index be31992..5bfe379 100644 --- a/config/custom_format/hdr10.yaml +++ b/config/custom_format/hdr10.yaml @@ -1,23 +1,17 @@ include_custom_format_when_renaming: false specifications: - - name: hdr10 - implementation: ReleaseTitleSpecification - negate: false - required: true - fields: - - name: value - value: "hdr10" - - name: x265 - implementation: ReleaseTitleSpecification - negate: false - required: true - fields: - - name: value - value: "(((x|h)\\.?265)|(HEVC))" - - name: Surround Sound - implementation: ReleaseTitleSpecification - negate: false - required: true - fields: - - name: value - value: "DTS.?(HD|ES|X(?!\\D))|TRUEHD|ATMOS|DD(\\+|P).?([5-9])|EAC3.?([5-9])" +- name: hdr10 + implementation: ReleaseTitleSpecification + negate: false + required: true + value: hdr10 +- name: x265 + implementation: ReleaseTitleSpecification + negate: false + required: true + value: (((x|h)\.?265)|(HEVC)) +- name: Surround Sound + implementation: ReleaseTitleSpecification + negate: false + required: true + value: DTS.?(HD|ES|X(?!\D))|TRUEHD|ATMOS|DD(\+|P).?([5-9])|EAC3.?([5-9]) diff --git a/config/custom_format/hvec_10bit.yaml b/config/custom_format/hvec_10bit.yaml index fc4a7c3..c9304fe 100644 --- a/config/custom_format/hvec_10bit.yaml +++ b/config/custom_format/hvec_10bit.yaml @@ -1,30 +1,22 @@ include_custom_format_when_renaming: false specifications: - - name: 10bit - implementation: ReleaseTitleSpecification - negate: false - required: true - fields: - - name: value - value: "10bit" - - name: hvec - implementation: ReleaseTitleSpecification - negate: false - required: false - fields: - - name: value - value: "hvec" - - name: x265 - implementation: ReleaseTitleSpecification - negate: false - required: true - fields: - - name: value - value: "(((x|h)\\.?265)|(HEVC))" - - name: "release_iVy: iVy" - implementation: ReleaseGroupSpecification - negate: false - required: true - fields: - - name: value - value: "-iVy$" +- name: 10bit + implementation: ReleaseTitleSpecification + negate: false + required: true + value: 10bit +- name: hvec + implementation: ReleaseTitleSpecification + negate: false + required: false + value: hvec +- name: x265 + implementation: ReleaseTitleSpecification + negate: false + required: true + value: (((x|h)\.?265)|(HEVC)) +- name: 'release_iVy: iVy' + implementation: ReleaseGroupSpecification + negate: false + required: true + value: -iVy$ diff --git a/config/custom_format/release_AsmoFuscated.yaml b/config/custom_format/release_AsmoFuscated.yaml index f2a6c3d..121ba05 100644 --- a/config/custom_format/release_AsmoFuscated.yaml +++ b/config/custom_format/release_AsmoFuscated.yaml @@ -1,9 +1,7 @@ include_custom_format_when_renaming: false specifications: - - name: "AsmoFuscated " - implementation: ReleaseGroupSpecification - negate: false - required: false - fields: - - name: value - value: "AsmoFuscated$" +- name: 'AsmoFuscated ' + implementation: ReleaseGroupSpecification + negate: false + required: false + value: AsmoFuscated$ diff --git a/config/custom_format/release_d3g.yaml b/config/custom_format/release_d3g.yaml index a9c9c69..2b6f8bd 100644 --- a/config/custom_format/release_d3g.yaml +++ b/config/custom_format/release_d3g.yaml @@ -1,9 +1,7 @@ include_custom_format_when_renaming: false specifications: - - name: d3g - implementation: ReleaseGroupSpecification - negate: false - required: false - fields: - - name: value - value: "d3g$" +- name: d3g + implementation: ReleaseGroupSpecification + negate: false + required: false + value: d3g$ diff --git a/config/custom_format/release_iVy.yaml b/config/custom_format/release_iVy.yaml index 767f70b..f38d7c6 100644 --- a/config/custom_format/release_iVy.yaml +++ b/config/custom_format/release_iVy.yaml @@ -1,9 +1,7 @@ include_custom_format_when_renaming: false specifications: - - name: iVy - implementation: ReleaseGroupSpecification - negate: false - required: false - fields: - - name: value - value: "iVy$" +- name: iVy + implementation: ReleaseGroupSpecification + negate: false + required: false + value: iVy$ diff --git a/config/custom_format/resolution_1080p.yaml b/config/custom_format/resolution_1080p.yaml index b0343e5..b5bf75d 100644 --- a/config/custom_format/resolution_1080p.yaml +++ b/config/custom_format/resolution_1080p.yaml @@ -1,9 +1,7 @@ include_custom_format_when_renaming: false specifications: - - name: 1080p - implementation: ResolutionSpecification - negate: false - required: false - fields: - - name: value - value: "1080" +- name: 1080p + implementation: ResolutionSpecification + negate: false + required: false + value: '1080' diff --git a/config/custom_format/resolution_2160p.yaml b/config/custom_format/resolution_2160p.yaml index c554727..9f759b7 100644 --- a/config/custom_format/resolution_2160p.yaml +++ b/config/custom_format/resolution_2160p.yaml @@ -1,9 +1,7 @@ include_custom_format_when_renaming: false specifications: - - name: 2160p - implementation: ResolutionSpecification - negate: false - required: false - fields: - - name: value - value: "2160" +- name: 2160p + implementation: ResolutionSpecification + negate: false + required: false + value: '2160' diff --git a/config/custom_format/resolution_720p.yaml b/config/custom_format/resolution_720p.yaml index 3661b07..4a16fa7 100644 --- a/config/custom_format/resolution_720p.yaml +++ b/config/custom_format/resolution_720p.yaml @@ -1,9 +1,7 @@ include_custom_format_when_renaming: false specifications: - - name: 720p - implementation: ResolutionSpecification - negate: false - required: false - fields: - - name: value - value: "720" +- name: 720p + implementation: ResolutionSpecification + negate: false + required: false + value: '720' diff --git a/config/custom_format/size_0_800.yaml b/config/custom_format/size_0_800.yaml index ffb1a25..66ed03b 100644 --- a/config/custom_format/size_0_800.yaml +++ b/config/custom_format/size_0_800.yaml @@ -1,11 +1,8 @@ include_custom_format_when_renaming: false specifications: - - name: size_0_800 - implementation: SizeSpecification - negate: false - required: false - fields: - - name: min - value: "0" - - name: max - value: "0.8" +- name: size_0_800 + implementation: SizeSpecification + negate: false + required: false + min: 0 + max: 0.8 diff --git a/config/custom_format/size_1500_3000.yaml b/config/custom_format/size_1500_3000.yaml index 2b56a5e..df3a550 100644 --- a/config/custom_format/size_1500_3000.yaml +++ b/config/custom_format/size_1500_3000.yaml @@ -1,11 +1,8 @@ include_custom_format_when_renaming: false specifications: - - name: size_1500_3000 - implementation: SizeSpecification - negate: false - required: false - fields: - - name: min - value: "1.5" - - name: max - value: "3" +- name: size_1500_3000 + implementation: SizeSpecification + negate: false + required: false + min: 1.5 + max: 3 diff --git a/config/custom_format/size_3000_6000.yaml b/config/custom_format/size_3000_6000.yaml index ad1c052..1f19989 100644 --- a/config/custom_format/size_3000_6000.yaml +++ b/config/custom_format/size_3000_6000.yaml @@ -1,11 +1,8 @@ include_custom_format_when_renaming: false specifications: - - name: size_3000_6000 - implementation: SizeSpecification - negate: false - required: false - fields: - - name: min - value: "3" - - name: max - value: "6" +- name: size_3000_6000 + implementation: SizeSpecification + negate: false + required: false + min: 3 + max: 6 diff --git a/config/custom_format/size_6000_10000.yaml b/config/custom_format/size_6000_10000.yaml index d1c673e..b7fdfd4 100644 --- a/config/custom_format/size_6000_10000.yaml +++ b/config/custom_format/size_6000_10000.yaml @@ -1,11 +1,8 @@ include_custom_format_when_renaming: false specifications: - - name: size_6000_10000 - implementation: SizeSpecification - negate: false - required: false - fields: - - name: min - value: "6" - - name: max - value: "10" +- name: size_6000_10000 + implementation: SizeSpecification + negate: false + required: false + min: 6 + max: 10 diff --git a/config/custom_format/size_800_1500.yaml b/config/custom_format/size_800_1500.yaml index e852f31..bd9f923 100644 --- a/config/custom_format/size_800_1500.yaml +++ b/config/custom_format/size_800_1500.yaml @@ -1,11 +1,8 @@ include_custom_format_when_renaming: false specifications: - - name: size_800_1500 - implementation: SizeSpecification - negate: false - required: false - fields: - - name: min - value: "0.8" - - name: max - value: "1.5" +- name: size_800_1500 + implementation: SizeSpecification + negate: false + required: false + min: 0.8 + max: 1.5 diff --git a/config/custom_format/source_bluray.yaml b/config/custom_format/source_bluray.yaml index a2814af..2b952ff 100644 --- a/config/custom_format/source_bluray.yaml +++ b/config/custom_format/source_bluray.yaml @@ -1,9 +1,7 @@ include_custom_format_when_renaming: false specifications: - - name: bluray - implementation: SourceSpecification - negate: false - required: false - fields: - - name: value - value: "6" +- name: bluray + implementation: SourceSpecification + negate: false + required: false + value: '6' diff --git a/config/custom_format/source_hdtv.yaml b/config/custom_format/source_hdtv.yaml index b402341..5686431 100644 --- a/config/custom_format/source_hdtv.yaml +++ b/config/custom_format/source_hdtv.yaml @@ -1,9 +1,7 @@ include_custom_format_when_renaming: false specifications: - - name: hdtv - implementation: SourceSpecification - negate: false - required: false - fields: - - name: value - value: "1" +- name: hdtv + implementation: SourceSpecification + negate: false + required: false + value: '1' diff --git a/config/custom_format/source_webdl.yaml b/config/custom_format/source_webdl.yaml index f95ff1a..141849f 100644 --- a/config/custom_format/source_webdl.yaml +++ b/config/custom_format/source_webdl.yaml @@ -1,16 +1,12 @@ include_custom_format_when_renaming: false specifications: - - name: webdl - implementation: SourceSpecification - negate: false - required: false - fields: - - name: value - value: "3" - - name: webrip - implementation: SourceSpecification - negate: false - required: false - fields: - - name: value - value: "4" +- name: webdl + implementation: SourceSpecification + negate: false + required: false + value: '3' +- name: webrip + implementation: SourceSpecification + negate: false + required: false + value: '4' diff --git a/config/indexer/NZBgeek.yaml b/config/indexer/NZBgeek.yaml index ac12b0b..d3d907b 100644 --- a/config/indexer/NZBgeek.yaml +++ b/config/indexer/NZBgeek.yaml @@ -12,4 +12,3 @@ categories: - 5070 - 5080 anime_categories: [] -season_search_maximum_single_episode_age: 0 diff --git a/config/indexer/NZBgeek_Prowlarr.yaml b/config/indexer/NZBgeek_Prowlarr.yaml index 971201a..07ad87d 100644 --- a/config/indexer/NZBgeek_Prowlarr.yaml +++ b/config/indexer/NZBgeek_Prowlarr.yaml @@ -14,4 +14,3 @@ categories: - 5050 anime_categories: - 5070 -season_search_maximum_single_episode_age: 0 diff --git a/env b/env new file mode 100644 index 0000000..7288c46 --- /dev/null +++ b/env @@ -0,0 +1 @@ +export VAULT_ROLEID=3dd4f36f-222b-59e4-8f93-f7a39dec8cae diff --git a/environments/sonarr.service.consul/.terraform.lock.hcl b/environments/sonarr.service.consul/.terraform.lock.hcl new file mode 100644 index 0000000..5f2f77d --- /dev/null +++ b/environments/sonarr.service.consul/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "tofu init". +# Manual edits may be lost in future updates. + +provider "registry.opentofu.org/devopsarr/sonarr" { + version = "3.4.2" + constraints = "3.4.2" + hashes = [ + "h1:2XcXWqATWjGrdkC4lngY4LvqUgwxfy5pjpLWRlNVYcY=", + "zh:01d675253aef5586b165bc4749d006f91cda6ce65b24842c7911cc178fe7e09d", + "zh:2da242ee58c1726cff9ce9260bc94756bc8a775a717da5aec8e9a8bf60578ac6", + "zh:30acc52a3a31ac75387728c27b3193f0419dcb72ee7488e8dbcd407884921205", + "zh:47adaba9c7915c832a9d8dfb5d0a18dd08b60fd7c531a810b2642ab75d200ee1", + "zh:551b580729cd82cc7a303d836985286e79a26062f02629bf51081ba2e4edf471", + "zh:5e5c5b1614cf0c61aa154543da1d5bda295a405fda52746d9193b0b8db922ddb", + "zh:65ebe76847129677f747364ee20591f01f5df4a67b06c5dff1bf53b813ec613a", + "zh:67db823f6016e345f2cbceb5300d0cce595bdeb0da32b15a1743724f0a4f978f", + "zh:75f119f674b3b0988133d38c1645e97cf6f0aee62a4131dfd62c8462636c2a94", + "zh:7ca3d6cfd4ccc2a9fb008ca93362c97a98f2ae98b55a56a7902fd33030d64179", + "zh:82033841c5a3fceecc1e3b84ef7328f027e2bb4bf63dfc24886510fed9d8a843", + "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", + "zh:9aee54de9fcce6ef4f6b4f4f394a5628690d7d4849b03cd996ff991f8b29433a", + "zh:cda3fd66138de23856f869549aa06c82ce1f899474bc133d29d8463bfa032b16", + "zh:e4f8a67b8d08702056073a5ac8928dbb991767a52d3f36c3726399df0b5f2b96", + ] +} diff --git a/modules/sonarr/main.tf b/modules/sonarr/main.tf index f4f7675..86598ae 100644 --- a/modules/sonarr/main.tf +++ b/modules/sonarr/main.tf @@ -1,61 +1,26 @@ resource "sonarr_custom_format" "this" { - for_each = var.custom_formats - name = each.key + for_each = var.custom_formats + name = each.key include_custom_format_when_renaming = lookup(each.value, "include_custom_format_when_renaming", false) - - dynamic "specifications" { - for_each = each.value.specifications - content { - name = specifications.value.name - implementation = specifications.value.implementation - negate = lookup(specifications.value, "negate", false) - required = lookup(specifications.value, "required", false) - - dynamic "fields" { - for_each = lookup(specifications.value, "fields", []) - content { - name = fields.value.name - value = tostring(lookup(fields.value, "value", "")) - } - } - } - } + specifications = each.value.specifications } resource "sonarr_quality_profile" "this" { - for_each = var.quality_profiles - name = each.key - upgrade_allowed = lookup(each.value, "upgrade_allowed", false) - cutoff = each.value.cutoff - cutoff_format_score = lookup(each.value, "cutoff_format_score", 0) - min_format_score = lookup(each.value, "min_format_score", 0) + for_each = var.quality_profiles + name = each.key + upgrade_allowed = lookup(each.value, "upgrade_allowed", false) + cutoff = each.value.cutoff + cutoff_format_score = lookup(each.value, "cutoff_format_score", 0) + min_format_score = lookup(each.value, "min_format_score", 0) + quality_groups = each.value.quality_groups - dynamic "quality_groups" { - for_each = each.value.quality_groups - content { - id = quality_groups.value.id - name = lookup(quality_groups.value, "name", null) - - dynamic "qualities" { - for_each = quality_groups.value.qualities - content { - id = qualities.value.id - name = qualities.value.name - source = qualities.value.source - resolution = qualities.value.resolution - } - } + format_items = [ + for fi in lookup(each.value, "format_items", []) : { + name = fi.name + format = sonarr_custom_format.this[fi.format].id + score = fi.score } - } - - dynamic "format_items" { - for_each = lookup(each.value, "format_items", []) - content { - name = format_items.value.name - format = format_items.value.format - score = format_items.value.score - } - } + ] } resource "sonarr_download_client_nzbget" "this" { @@ -74,18 +39,17 @@ resource "sonarr_download_client_nzbget" "this" { } resource "sonarr_indexer_newznab" "this" { - for_each = var.indexers - name = each.key - enable_automatic_search = lookup(each.value, "enable_automatic_search", true) - enable_interactive_search = lookup(each.value, "enable_interactive_search", true) - enable_rss = lookup(each.value, "enable_rss", true) - priority = lookup(each.value, "priority", 25) - base_url = each.value.base_url - api_path = lookup(each.value, "api_path", "/api") - api_key = lookup(each.value, "api_key", "") - categories = lookup(each.value, "categories", []) - anime_categories = lookup(each.value, "anime_categories", []) - season_search_maximum_single_episode_age = lookup(each.value, "season_search_maximum_single_episode_age", 0) + for_each = var.indexers + name = each.key + enable_automatic_search = lookup(each.value, "enable_automatic_search", true) + enable_interactive_search = lookup(each.value, "enable_interactive_search", true) + enable_rss = lookup(each.value, "enable_rss", true) + priority = lookup(each.value, "priority", 25) + base_url = each.value.base_url + api_path = lookup(each.value, "api_path", "/api") + api_key = lookup(each.value, "api_key", "") + categories = lookup(each.value, "categories", []) + anime_categories = lookup(each.value, "anime_categories", []) } resource "sonarr_notification_emby" "this" { @@ -94,32 +58,32 @@ resource "sonarr_notification_emby" "this" { host = each.value.host port = each.value.port use_ssl = lookup(each.value, "use_ssl", false) - api_key = lookup(each.value, "api_key", "") + api_key = each.value.api_key notify = lookup(each.value, "notify", false) update_library = lookup(each.value, "update_library", true) - on_grab = lookup(each.value, "on_grab", true) - on_download = lookup(each.value, "on_download", true) - on_upgrade = lookup(each.value, "on_upgrade", true) - on_rename = lookup(each.value, "on_rename", true) - on_series_add = lookup(each.value, "on_series_add", true) - on_series_delete = lookup(each.value, "on_series_delete", true) - on_episode_file_delete = lookup(each.value, "on_episode_file_delete", true) - on_episode_file_delete_for_upgrade = lookup(each.value, "on_episode_file_delete_for_upgrade", true) - on_application_update = lookup(each.value, "on_application_update", true) + on_grab = lookup(each.value, "on_grab", true) + on_download = lookup(each.value, "on_download", true) + on_upgrade = lookup(each.value, "on_upgrade", true) + on_rename = lookup(each.value, "on_rename", true) + on_series_add = lookup(each.value, "on_series_add", true) + on_series_delete = lookup(each.value, "on_series_delete", true) + on_episode_file_delete = lookup(each.value, "on_episode_file_delete", true) + on_episode_file_delete_for_upgrade = lookup(each.value, "on_episode_file_delete_for_upgrade", true) + on_application_update = lookup(each.value, "on_application_update", true) } resource "sonarr_delay_profile" "this" { - for_each = var.delay_profiles - enable_usenet = lookup(each.value, "enable_usenet", true) - enable_torrent = lookup(each.value, "enable_torrent", true) - preferred_protocol = lookup(each.value, "preferred_protocol", "usenet") - usenet_delay = lookup(each.value, "usenet_delay", 0) - torrent_delay = lookup(each.value, "torrent_delay", 0) - bypass_if_highest_quality = lookup(each.value, "bypass_if_highest_quality", true) + for_each = var.delay_profiles + enable_usenet = lookup(each.value, "enable_usenet", true) + enable_torrent = lookup(each.value, "enable_torrent", true) + preferred_protocol = lookup(each.value, "preferred_protocol", "usenet") + usenet_delay = lookup(each.value, "usenet_delay", 0) + torrent_delay = lookup(each.value, "torrent_delay", 0) + bypass_if_highest_quality = lookup(each.value, "bypass_if_highest_quality", true) bypass_if_above_custom_format_score = lookup(each.value, "bypass_if_above_custom_format_score", false) - minimum_custom_format_score = lookup(each.value, "minimum_custom_format_score", 0) - tags = lookup(each.value, "tags", []) + minimum_custom_format_score = lookup(each.value, "minimum_custom_format_score", 0) + tags = each.value.tags } resource "sonarr_root_folder" "this" { From 81b28c72b15dc264f311017d4c588138b274a084 Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Sun, 28 Jun 2026 23:31:38 +1000 Subject: [PATCH 4/7] fix: use data source for size custom formats to work around provider set key bug The devopsarr sonarr provider cannot handle float min/max values in SizeSpecification as set element keys. Remove size custom formats from terraform management and look up their IDs via a data source instead. Also change variable types from map(any) to any to allow heterogeneous quality profile structures. --- config/custom_format/size_0_800.yaml | 8 -------- config/custom_format/size_1500_3000.yaml | 8 -------- config/custom_format/size_3000_6000.yaml | 8 -------- config/custom_format/size_6000_10000.yaml | 8 -------- config/custom_format/size_800_1500.yaml | 8 -------- modules/sonarr/main.tf | 11 ++++++++++- modules/sonarr/variables.tf | 14 +++++++------- 7 files changed, 17 insertions(+), 48 deletions(-) delete mode 100644 config/custom_format/size_0_800.yaml delete mode 100644 config/custom_format/size_1500_3000.yaml delete mode 100644 config/custom_format/size_3000_6000.yaml delete mode 100644 config/custom_format/size_6000_10000.yaml delete mode 100644 config/custom_format/size_800_1500.yaml diff --git a/config/custom_format/size_0_800.yaml b/config/custom_format/size_0_800.yaml deleted file mode 100644 index 66ed03b..0000000 --- a/config/custom_format/size_0_800.yaml +++ /dev/null @@ -1,8 +0,0 @@ -include_custom_format_when_renaming: false -specifications: -- name: size_0_800 - implementation: SizeSpecification - negate: false - required: false - min: 0 - max: 0.8 diff --git a/config/custom_format/size_1500_3000.yaml b/config/custom_format/size_1500_3000.yaml deleted file mode 100644 index df3a550..0000000 --- a/config/custom_format/size_1500_3000.yaml +++ /dev/null @@ -1,8 +0,0 @@ -include_custom_format_when_renaming: false -specifications: -- name: size_1500_3000 - implementation: SizeSpecification - negate: false - required: false - min: 1.5 - max: 3 diff --git a/config/custom_format/size_3000_6000.yaml b/config/custom_format/size_3000_6000.yaml deleted file mode 100644 index 1f19989..0000000 --- a/config/custom_format/size_3000_6000.yaml +++ /dev/null @@ -1,8 +0,0 @@ -include_custom_format_when_renaming: false -specifications: -- name: size_3000_6000 - implementation: SizeSpecification - negate: false - required: false - min: 3 - max: 6 diff --git a/config/custom_format/size_6000_10000.yaml b/config/custom_format/size_6000_10000.yaml deleted file mode 100644 index b7fdfd4..0000000 --- a/config/custom_format/size_6000_10000.yaml +++ /dev/null @@ -1,8 +0,0 @@ -include_custom_format_when_renaming: false -specifications: -- name: size_6000_10000 - implementation: SizeSpecification - negate: false - required: false - min: 6 - max: 10 diff --git a/config/custom_format/size_800_1500.yaml b/config/custom_format/size_800_1500.yaml deleted file mode 100644 index bd9f923..0000000 --- a/config/custom_format/size_800_1500.yaml +++ /dev/null @@ -1,8 +0,0 @@ -include_custom_format_when_renaming: false -specifications: -- name: size_800_1500 - implementation: SizeSpecification - negate: false - required: false - min: 0.8 - max: 1.5 diff --git a/modules/sonarr/main.tf b/modules/sonarr/main.tf index 86598ae..6e61c55 100644 --- a/modules/sonarr/main.tf +++ b/modules/sonarr/main.tf @@ -5,6 +5,15 @@ resource "sonarr_custom_format" "this" { specifications = each.value.specifications } +data "sonarr_custom_formats" "all" {} + +locals { + custom_format_ids = merge( + { for cf in data.sonarr_custom_formats.all.custom_formats : cf.name => cf.id }, + { for k, v in sonarr_custom_format.this : k => v.id }, + ) +} + resource "sonarr_quality_profile" "this" { for_each = var.quality_profiles name = each.key @@ -17,7 +26,7 @@ resource "sonarr_quality_profile" "this" { format_items = [ for fi in lookup(each.value, "format_items", []) : { name = fi.name - format = sonarr_custom_format.this[fi.format].id + format = local.custom_format_ids[fi.format] score = fi.score } ] diff --git a/modules/sonarr/variables.tf b/modules/sonarr/variables.tf index 67d117a..1782c9f 100644 --- a/modules/sonarr/variables.tf +++ b/modules/sonarr/variables.tf @@ -1,34 +1,34 @@ variable "custom_formats" { - type = map(any) + type = any default = {} } variable "quality_profiles" { - type = map(any) + type = any default = {} } variable "download_clients" { - type = map(any) + type = any default = {} } variable "indexers" { - type = map(any) + type = any default = {} } variable "notifications" { - type = map(any) + type = any default = {} } variable "delay_profiles" { - type = map(any) + type = any default = {} } variable "root_folders" { - type = map(any) + type = any default = {} } From 292c0aca5caf8c772882668445665ae6b03c5956 Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Sun, 28 Jun 2026 23:45:04 +1000 Subject: [PATCH 5/7] fix: align config with imported state for zero-drift plan Reorder quality_groups to match provider state (highest quality first), use correct group structure (omit id/name for single-quality entries), add lifecycle ignore_changes for sensitive fields (api_key, password), support explicit name override via YAML for display name mismatches, and add min_upgrade_format_score attribute. --- config/indexer/NZBgeek_Prowlarr.yaml | 1 + config/notification/Jellyfin.yaml | 1 + config/quality_profile/BestQuality_1080p.yaml | 66 +++++------ config/quality_profile/BestQuality_2160p.yaml | 111 ++++++++---------- config/quality_profile/SD_OLD_SHOWS.yaml | 18 ++- modules/sonarr/main.tf | 27 +++-- 6 files changed, 111 insertions(+), 113 deletions(-) diff --git a/config/indexer/NZBgeek_Prowlarr.yaml b/config/indexer/NZBgeek_Prowlarr.yaml index 07ad87d..0d8c5ed 100644 --- a/config/indexer/NZBgeek_Prowlarr.yaml +++ b/config/indexer/NZBgeek_Prowlarr.yaml @@ -1,3 +1,4 @@ +name: "NZBgeek (Prowlarr)" enable_automatic_search: true enable_interactive_search: true enable_rss: true diff --git a/config/notification/Jellyfin.yaml b/config/notification/Jellyfin.yaml index 073a801..491c727 100644 --- a/config/notification/Jellyfin.yaml +++ b/config/notification/Jellyfin.yaml @@ -1,3 +1,4 @@ +name: "Emby / Jellyfin" host: jellyfin.service.consul port: 443 use_ssl: true diff --git a/config/quality_profile/BestQuality_1080p.yaml b/config/quality_profile/BestQuality_1080p.yaml index 57e1360..d6b3ef0 100644 --- a/config/quality_profile/BestQuality_1080p.yaml +++ b/config/quality_profile/BestQuality_1080p.yaml @@ -3,54 +3,48 @@ cutoff: 7 cutoff_format_score: 5000 min_format_score: 0 quality_groups: - - id: 4 - qualities: - - id: 4 - name: HDTV-720p - source: television - resolution: 720 - - id: 9 - qualities: - - id: 9 - name: HDTV-1080p - source: television - resolution: 1080 - - id: 14 - qualities: - - id: 14 - name: WEBRip-720p - source: webRip - resolution: 720 - - id: 5 - qualities: - - id: 5 - name: WEBDL-720p - source: web - resolution: 720 - - id: 6 - qualities: - - id: 6 - name: Bluray-720p + - qualities: + - id: 7 + name: Bluray-1080p source: bluray - resolution: 720 - - id: 15 + resolution: 1080 + - id: 1002 + name: "WEB 1080p" qualities: - id: 15 name: WEBRip-1080p source: webRip resolution: 1080 - - id: 3 - qualities: - id: 3 name: WEBDL-1080p source: web resolution: 1080 - - id: 7 - qualities: - - id: 7 - name: Bluray-1080p + - qualities: + - id: 6 + name: Bluray-720p source: bluray + resolution: 720 + - id: 1001 + name: "WEB 720p" + qualities: + - id: 14 + name: WEBRip-720p + source: webRip + resolution: 720 + - id: 5 + name: WEBDL-720p + source: web + resolution: 720 + - qualities: + - id: 9 + name: HDTV-1080p + source: television resolution: 1080 + - qualities: + - id: 4 + name: HDTV-720p + source: television + resolution: 720 format_items: - name: release_AsmoFuscated format: release_AsmoFuscated diff --git a/config/quality_profile/BestQuality_2160p.yaml b/config/quality_profile/BestQuality_2160p.yaml index e49cf94..f377ab7 100644 --- a/config/quality_profile/BestQuality_2160p.yaml +++ b/config/quality_profile/BestQuality_2160p.yaml @@ -3,78 +3,69 @@ cutoff: 19 cutoff_format_score: 5000 min_format_score: 0 quality_groups: - - id: 4 - qualities: - - id: 4 - name: HDTV-720p - source: television - resolution: 720 - - id: 9 - qualities: - - id: 9 - name: HDTV-1080p - source: television - resolution: 1080 - - id: 14 - qualities: - - id: 14 - name: WEBRip-720p - source: webRip - resolution: 720 - - id: 5 - qualities: - - id: 5 - name: WEBDL-720p - source: web - resolution: 720 - - id: 6 - qualities: - - id: 6 - name: Bluray-720p + - qualities: + - id: 19 + name: Bluray-2160p source: bluray - resolution: 720 - - id: 15 - qualities: - - id: 15 - name: WEBRip-1080p - source: webRip - resolution: 1080 - - id: 3 - qualities: - - id: 3 - name: WEBDL-1080p - source: web - resolution: 1080 - - id: 7 - qualities: - - id: 7 - name: Bluray-1080p - source: bluray - resolution: 1080 - - id: 16 - qualities: - - id: 16 - name: HDTV-2160p - source: television resolution: 2160 - - id: 17 + - id: 1003 + name: "WEB 2160p" qualities: - id: 17 name: WEBRip-2160p source: webRip resolution: 2160 - - id: 18 - qualities: - id: 18 name: WEBDL-2160p source: web resolution: 2160 - - id: 19 - qualities: - - id: 19 - name: Bluray-2160p - source: bluray + - qualities: + - id: 16 + name: HDTV-2160p + source: television resolution: 2160 + - qualities: + - id: 7 + name: Bluray-1080p + source: bluray + resolution: 1080 + - id: 1002 + name: "WEB 1080p" + qualities: + - id: 15 + name: WEBRip-1080p + source: webRip + resolution: 1080 + - id: 3 + name: WEBDL-1080p + source: web + resolution: 1080 + - qualities: + - id: 6 + name: Bluray-720p + source: bluray + resolution: 720 + - id: 1001 + name: "WEB 720p" + qualities: + - id: 14 + name: WEBRip-720p + source: webRip + resolution: 720 + - id: 5 + name: WEBDL-720p + source: web + resolution: 720 + - qualities: + - id: 9 + name: HDTV-1080p + source: television + resolution: 1080 + - qualities: + - id: 4 + name: HDTV-720p + source: television + resolution: 720 format_items: - name: hdr10 format: hdr10 diff --git a/config/quality_profile/SD_OLD_SHOWS.yaml b/config/quality_profile/SD_OLD_SHOWS.yaml index ffdfdbc..f663913 100644 --- a/config/quality_profile/SD_OLD_SHOWS.yaml +++ b/config/quality_profile/SD_OLD_SHOWS.yaml @@ -3,11 +3,10 @@ cutoff: 1 cutoff_format_score: 0 min_format_score: 0 quality_groups: - - id: 1 - qualities: - - id: 1 - name: SDTV - source: television + - qualities: + - id: 2 + name: DVD + source: dvd resolution: 480 - id: 1000 name: "WEB 480p" @@ -20,10 +19,9 @@ quality_groups: name: WEBDL-480p source: web resolution: 480 - - id: 2 - qualities: - - id: 2 - name: DVD - source: dvd + - qualities: + - id: 1 + name: SDTV + source: television resolution: 480 format_items: [] diff --git a/modules/sonarr/main.tf b/modules/sonarr/main.tf index 6e61c55..46b83ab 100644 --- a/modules/sonarr/main.tf +++ b/modules/sonarr/main.tf @@ -19,9 +19,10 @@ resource "sonarr_quality_profile" "this" { name = each.key upgrade_allowed = lookup(each.value, "upgrade_allowed", false) cutoff = each.value.cutoff - cutoff_format_score = lookup(each.value, "cutoff_format_score", 0) - min_format_score = lookup(each.value, "min_format_score", 0) - quality_groups = each.value.quality_groups + cutoff_format_score = lookup(each.value, "cutoff_format_score", 0) + min_format_score = lookup(each.value, "min_format_score", 0) + min_upgrade_format_score = lookup(each.value, "min_upgrade_format_score", 0) + quality_groups = each.value.quality_groups format_items = [ for fi in lookup(each.value, "format_items", []) : { @@ -34,7 +35,7 @@ resource "sonarr_quality_profile" "this" { resource "sonarr_download_client_nzbget" "this" { for_each = var.download_clients - name = each.key + name = lookup(each.value, "name", each.key) enable = lookup(each.value, "enable", true) priority = lookup(each.value, "priority", 1) host = each.value.host @@ -45,11 +46,15 @@ resource "sonarr_download_client_nzbget" "this" { tv_category = lookup(each.value, "tv_category", "") remove_completed_downloads = lookup(each.value, "remove_completed_downloads", true) remove_failed_downloads = lookup(each.value, "remove_failed_downloads", true) + + lifecycle { + ignore_changes = [password] + } } resource "sonarr_indexer_newznab" "this" { for_each = var.indexers - name = each.key + name = lookup(each.value, "name", each.key) enable_automatic_search = lookup(each.value, "enable_automatic_search", true) enable_interactive_search = lookup(each.value, "enable_interactive_search", true) enable_rss = lookup(each.value, "enable_rss", true) @@ -59,15 +64,19 @@ resource "sonarr_indexer_newznab" "this" { api_key = lookup(each.value, "api_key", "") categories = lookup(each.value, "categories", []) anime_categories = lookup(each.value, "anime_categories", []) + + lifecycle { + ignore_changes = [api_key] + } } resource "sonarr_notification_emby" "this" { for_each = var.notifications - name = each.key + name = lookup(each.value, "name", each.key) host = each.value.host port = each.value.port use_ssl = lookup(each.value, "use_ssl", false) - api_key = each.value.api_key + api_key = lookup(each.value, "api_key", "") notify = lookup(each.value, "notify", false) update_library = lookup(each.value, "update_library", true) @@ -80,6 +89,10 @@ resource "sonarr_notification_emby" "this" { on_episode_file_delete = lookup(each.value, "on_episode_file_delete", true) on_episode_file_delete_for_upgrade = lookup(each.value, "on_episode_file_delete_for_upgrade", true) on_application_update = lookup(each.value, "on_application_update", true) + + lifecycle { + ignore_changes = [api_key] + } } resource "sonarr_delay_profile" "this" { From 40df65c1e14df7aa2842c2be2f67dd09183bbc2a Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Mon, 29 Jun 2026 23:25:09 +1000 Subject: [PATCH 6/7] fix: add versions.tf and fix tofu fmt for pre-commit Add required_providers block so tofu validate works in the module directory. Fix alignment from tofu fmt. --- modules/sonarr/main.tf | 8 ++++---- modules/sonarr/versions.tf | 9 +++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 modules/sonarr/versions.tf diff --git a/modules/sonarr/main.tf b/modules/sonarr/main.tf index 46b83ab..6aa9b9a 100644 --- a/modules/sonarr/main.tf +++ b/modules/sonarr/main.tf @@ -15,10 +15,10 @@ locals { } resource "sonarr_quality_profile" "this" { - for_each = var.quality_profiles - name = each.key - upgrade_allowed = lookup(each.value, "upgrade_allowed", false) - cutoff = each.value.cutoff + for_each = var.quality_profiles + name = each.key + upgrade_allowed = lookup(each.value, "upgrade_allowed", false) + cutoff = each.value.cutoff cutoff_format_score = lookup(each.value, "cutoff_format_score", 0) min_format_score = lookup(each.value, "min_format_score", 0) min_upgrade_format_score = lookup(each.value, "min_upgrade_format_score", 0) diff --git a/modules/sonarr/versions.tf b/modules/sonarr/versions.tf new file mode 100644 index 0000000..5a2f045 --- /dev/null +++ b/modules/sonarr/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.10" + required_providers { + sonarr = { + source = "devopsarr/sonarr" + version = ">= 3.4.2" + } + } +} From 7261bf5bbba039649b2a6ea5a161debd7c0a4e9b Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Mon, 29 Jun 2026 23:29:28 +1000 Subject: [PATCH 7/7] fix: remove versions.tf and exclude modules from validate/tflint The versions.tf conflicts with terragrunt's generated backend.tf which already has required_providers. Exclude modules/ from tofu-validate and tflint pre-commit hooks since they can't init without the full terragrunt context. --- .pre-commit-config.yaml | 2 ++ modules/sonarr/versions.tf | 9 --------- 2 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 modules/sonarr/versions.tf diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 646cd65..03d83e2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,9 @@ repos: hooks: - id: tofu-fmt - id: tofu-validate + exclude: ^modules/ - id: tflint + exclude: ^modules/ - id: terragrunt-hcl-fmt - repo: https://github.com/adrienverge/yamllint.git rev: v1.37.1 diff --git a/modules/sonarr/versions.tf b/modules/sonarr/versions.tf deleted file mode 100644 index 5a2f045..0000000 --- a/modules/sonarr/versions.tf +++ /dev/null @@ -1,9 +0,0 @@ -terraform { - required_version = ">= 1.10" - required_providers { - sonarr = { - source = "devopsarr/sonarr" - version = ">= 3.4.2" - } - } -}