From a341749cb094c44c968d0d11e1f78118bd01328e Mon Sep 17 00:00:00 2001 From: Antonin Ruan Date: Mon, 9 Feb 2026 23:10:53 +0100 Subject: [PATCH] Move media stack to tofu --- .gitignore | 4 +- k8s/flaresolverr.yaml.in | 58 ------------- k8s/jellyfin.yaml.in | 104 ---------------------- k8s/prowlarr.yaml.in | 77 ----------------- k8s/qbitorrent.yaml.in | 84 ------------------ k8s/radarr.yaml.in | 89 ------------------- k8s/sonarr.yaml.in | 89 ------------------- media/bazarr.tf | 168 ++++++++++++++++++++++++++++++++++++ media/flaresolverr.tf | 64 ++++++++++++++ media/jellyfin.tf | 180 +++++++++++++++++++++++++++++++++++++++ media/main.tf | 24 ++++++ media/prowlarr.tf | 131 ++++++++++++++++++++++++++++ media/qbittorrent.tf | 143 +++++++++++++++++++++++++++++++ media/radarr.tf | 152 +++++++++++++++++++++++++++++++++ media/sonarr.tf | 152 +++++++++++++++++++++++++++++++++ media/terraform.tfvars | 49 +++++++++++ media/wizarr.tf | 140 ++++++++++++++++++++++++++++++ 17 files changed, 1206 insertions(+), 502 deletions(-) delete mode 100644 k8s/flaresolverr.yaml.in delete mode 100644 k8s/jellyfin.yaml.in delete mode 100644 k8s/prowlarr.yaml.in delete mode 100644 k8s/qbitorrent.yaml.in delete mode 100644 k8s/radarr.yaml.in delete mode 100644 k8s/sonarr.yaml.in create mode 100644 media/bazarr.tf create mode 100644 media/flaresolverr.tf create mode 100644 media/jellyfin.tf create mode 100644 media/main.tf create mode 100644 media/prowlarr.tf create mode 100644 media/qbittorrent.tf create mode 100644 media/radarr.tf create mode 100644 media/sonarr.tf create mode 100644 media/terraform.tfvars create mode 100644 media/wizarr.tf diff --git a/.gitignore b/.gitignore index a539470..6db75ba 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -config.yaml \ No newline at end of file +config.yaml +*/**/.terraform* +*/**/*.tfstate* \ No newline at end of file diff --git a/k8s/flaresolverr.yaml.in b/k8s/flaresolverr.yaml.in deleted file mode 100644 index 50f0b65..0000000 --- a/k8s/flaresolverr.yaml.in +++ /dev/null @@ -1,58 +0,0 @@ ---- -apiVersion: v1 -kind: Service -metadata: - name: flarsolverr -spec: - selector: - app: flarsolverr - ports: - - name: web - port: 8191 -#--- -#apiVersion: networking.k8s.io/v1 -#kind: Ingress -#metadata: -# name: flarsolverr -# annotations: -# spec.ingressClassName: "nginx" -# cert-manager.io/cluster-issuer: "letsencrypt" -#spec: -# tls: -# - hosts: -# - flarsolverr.{{.domain}} -# secretName: flarsolverr-tls -# rules: -# - host: flarsolverr.{{.domain}} -# http: -# paths: -# - path: / -# pathType: Prefix -# backend: -# service: -# name: flarsolverr -# port: -# number: 9696 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: flarsolverr - labels: - app: flarsolverr -spec: - replicas: 1 - selector: - matchLabels: - app: flarsolverr - template: - metadata: - labels: - app: flarsolverr - spec: - containers: - - name: flarsolverr - image: ghcr.io/flaresolverr/flaresolverr:latest - imagePullPolicy: IfNotPresent - ports: - - containerPort: 8191 diff --git a/k8s/jellyfin.yaml.in b/k8s/jellyfin.yaml.in deleted file mode 100644 index 4b770bd..0000000 --- a/k8s/jellyfin.yaml.in +++ /dev/null @@ -1,104 +0,0 @@ ---- -apiVersion: v1 -kind: Service -metadata: - name: jellyfin -spec: - selector: - app: jellyfin - ports: - - name: web - port: 8096 - - name: client-discovery - port: 7359 ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: jellyfin - annotations: - spec.ingressClassName: "nginx" - cert-manager.io/cluster-issuer: "letsencrypt" -spec: - tls: - - hosts: - - media.{{.domain}} - secretName: jellyfin-tls - rules: - - host: media.{{.domain}} - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: jellyfin - port: - number: 8096 ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: jellyfin-cache -spec: - storageClassName: local-path - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 20Gi ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: jellyfin-config -spec: - storageClassName: local-path - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: jellyfin - labels: - app: jellyfin -spec: - replicas: 1 - selector: - matchLabels: - app: jellyfin - template: - metadata: - labels: - app: jellyfin - spec: - containers: - - name: jellyfin - image: jellyfin/jellyfin:latest - imagePullPolicy: IfNotPresent - ports: - - containerPort: 8096 - - containerPort: 7359 - volumeMounts: - - name: jellyfin-data - mountPath: /media - - name: jellyfin-config - mountPath: /config - - name: jellyfin-cache - mountPath: /cache - volumes: - - name: jellyfin-data - nfs: - path: "/srv/nfs/jellyfin" - server: "10.42.0.1" - readOnly: false - - name: jellyfin-config - persistentVolumeClaim: - claimName: jellyfin-config - - name: jellyfin-cache - persistentVolumeClaim: - claimName: jellyfin-cache diff --git a/k8s/prowlarr.yaml.in b/k8s/prowlarr.yaml.in deleted file mode 100644 index ae72eaf..0000000 --- a/k8s/prowlarr.yaml.in +++ /dev/null @@ -1,77 +0,0 @@ ---- -apiVersion: v1 -kind: Service -metadata: - name: prowlarr -spec: - selector: - app: prowlarr - ports: - - name: web - port: 9696 ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: prowlarr - annotations: - spec.ingressClassName: "nginx" - cert-manager.io/cluster-issuer: "letsencrypt" -spec: - tls: - - hosts: - - prowlarr.{{.domain}} - secretName: prowlarr-tls - rules: - - host: prowlarr.{{.domain}} - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: prowlarr - port: - number: 9696 ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: prowlarr-config -spec: - storageClassName: local-path - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: prowlarr - labels: - app: prowlarr -spec: - replicas: 1 - selector: - matchLabels: - app: prowlarr - template: - metadata: - labels: - app: prowlarr - spec: - containers: - - name: prowlarr - image: lscr.io/linuxserver/prowlarr:latest - imagePullPolicy: IfNotPresent - ports: - - containerPort: 8989 - volumeMounts: - - name: prowlarr-config - mountPath: /config - volumes: - - name: prowlarr-config - persistentVolumeClaim: - claimName: prowlarr-config diff --git a/k8s/qbitorrent.yaml.in b/k8s/qbitorrent.yaml.in deleted file mode 100644 index 92dc65a..0000000 --- a/k8s/qbitorrent.yaml.in +++ /dev/null @@ -1,84 +0,0 @@ ---- -apiVersion: v1 -kind: Service -metadata: - name: qbitorrent -spec: - selector: - app: qbitorrent - ports: - - name: web - port: 8080 ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: qbitorrent - annotations: - spec.ingressClassName: "nginx" - cert-manager.io/cluster-issuer: "letsencrypt" -spec: - tls: - - hosts: - - torrent.{{.domain}} - secretName: qbitorrent-tls - rules: - - host: torrent.{{.domain}} - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: qbitorrent - port: - number: 8080 ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: qbitorrent-config -spec: - storageClassName: local-path - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: qbitorrent - labels: - app: qbitorrent -spec: - replicas: 1 - selector: - matchLabels: - app: qbitorrent - template: - metadata: - labels: - app: qbitorrent - spec: - containers: - - name: qbitorrent - image: lscr.io/linuxserver/qbittorrent:latest - imagePullPolicy: IfNotPresent - ports: - - containerPort: 8080 - volumeMounts: - - name: qbitorrent-data - mountPath: /downloads - - name: qbitorrent-config - mountPath: /config - volumes: - - name: qbitorrent-data - nfs: - path: "/srv/nfs/torrent" - server: "10.42.0.1" - readOnly: false - - name: qbitorrent-config - persistentVolumeClaim: - claimName: qbitorrent-config diff --git a/k8s/radarr.yaml.in b/k8s/radarr.yaml.in deleted file mode 100644 index c849c12..0000000 --- a/k8s/radarr.yaml.in +++ /dev/null @@ -1,89 +0,0 @@ ---- -apiVersion: v1 -kind: Service -metadata: - name: radarr -spec: - selector: - app: radarr - ports: - - name: web - port: 7878 ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: radarr - annotations: - spec.ingressClassName: "nginx" - cert-manager.io/cluster-issuer: "letsencrypt" -spec: - tls: - - hosts: - - radarr.{{.domain}} - secretName: radarr-tls - rules: - - host: radarr.{{.domain}} - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: radarr - port: - number: 7878 ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: radarr-config -spec: - storageClassName: local-path - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: radarr - labels: - app: radarr -spec: - replicas: 1 - selector: - matchLabels: - app: radarr - template: - metadata: - labels: - app: radarr - spec: - containers: - - name: radarr - image: lscr.io/linuxserver/radarr:latest - imagePullPolicy: IfNotPresent - ports: - - containerPort: 8989 - volumeMounts: - - name: radarr-data - mountPath: /data - - name: radarr-config - mountPath: /config - env: - - name: PUID - value: "0" - - name: PGID - value: "0" - volumes: - - name: radarr-data - nfs: - path: "/srv/nfs" - server: "10.42.0.1" - readOnly: false - - name: radarr-config - persistentVolumeClaim: - claimName: radarr-config diff --git a/k8s/sonarr.yaml.in b/k8s/sonarr.yaml.in deleted file mode 100644 index 21396f7..0000000 --- a/k8s/sonarr.yaml.in +++ /dev/null @@ -1,89 +0,0 @@ ---- -apiVersion: v1 -kind: Service -metadata: - name: sonarr -spec: - selector: - app: sonarr - ports: - - name: web - port: 8989 ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: sonarr - annotations: - spec.ingressClassName: "nginx" - cert-manager.io/cluster-issuer: "letsencrypt" -spec: - tls: - - hosts: - - sonarr.{{.domain}} - secretName: sonarr-tls - rules: - - host: sonarr.{{.domain}} - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: sonarr - port: - number: 8989 ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: sonarr-config -spec: - storageClassName: local-path - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: sonarr - labels: - app: sonarr -spec: - replicas: 1 - selector: - matchLabels: - app: sonarr - template: - metadata: - labels: - app: sonarr - spec: - containers: - - name: sonarr - image: lscr.io/linuxserver/sonarr:latest - imagePullPolicy: IfNotPresent - ports: - - containerPort: 8989 - volumeMounts: - - name: sonarr-data - mountPath: /data - - name: sonarr-config - mountPath: /config - env: - - name: PUID - value: "0" - - name: PGID - value: "0" - volumes: - - name: sonarr-data - nfs: - path: "/srv/nfs" - server: "10.42.0.1" - readOnly: false - - name: sonarr-config - persistentVolumeClaim: - claimName: sonarr-config diff --git a/media/bazarr.tf b/media/bazarr.tf new file mode 100644 index 0000000..a1b04f1 --- /dev/null +++ b/media/bazarr.tf @@ -0,0 +1,168 @@ +variable "bazarr" { + type = object({ + app_name = optional(string, "bazarr") + image = string + version = optional(string, "latest") + subdomain = optional(string, "bazarr") + port = optional(number, 6767) + }) +} + +resource "kubernetes_service_v1" "bazarr" { + metadata { + name = var.bazarr.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + } + + spec { + selector = { + app = var.bazarr.app_name + } + port { + port = var.bazarr.port + target_port = var.bazarr.port + } + } +} + +resource "kubernetes_ingress_v1" "bazarr" { + metadata { + name = var.bazarr.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + + annotations = { + "cert-manager.io/cluster-issuer" = "letsencrypt" + } + } + + spec { + tls { + hosts = ["${var.bazarr.subdomain}.${var.domain}"] + secret_name = "${var.bazarr.app_name}-tls" + } + + rule { + host = "${var.bazarr.subdomain}.${var.domain}" + http { + path { + path = "/" + path_type = "Prefix" + backend { + service { + name = kubernetes_service_v1.bazarr.metadata[0].name + port { + number = var.bazarr.port + } + } + } + } + } + } + } +} + +resource "kubernetes_persistent_volume_claim_v1" "bazarr-config" { + metadata { + name = "${var.bazarr.app_name}-config" + namespace = kubernetes_namespace_v1.media.metadata[0].name + } + + spec { + storage_class_name = "local-path" + access_modes = ["ReadWriteOnce"] + + resources { + requests = { + storage = "1Gi" + } + } + } +} + +resource "kubernetes_deployment_v1" "bazarr" { + metadata { + name = var.bazarr.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + labels = { + app = var.bazarr.app_name + } + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.bazarr.app_name + } + } + + template { + metadata { + labels = { + "app" = var.bazarr.app_name + } + } + + spec { + container { + name = var.bazarr.app_name + image = "${var.bazarr.image}:${var.bazarr.version}" + image_pull_policy = "Always" + + port { + container_port = var.bazarr.port + } + + env { + name = "PUID" + value = 0 + } + env { + name = "GUID" + value = 0 + } + env { + name = "TZ" + value = "Europe/Paris" + } + + volume_mount { + name = "bazarr-config" + mount_path = "/config" + } + volume_mount { + name = "movies" + mount_path = "/movies" + } + volume_mount { + name = "shows" + mount_path = "/tv" + } + } + + volume { + name = "bazarr-config" + persistent_volume_claim { + claim_name = kubernetes_persistent_volume_claim_v1.bazarr-config.metadata[0].name + } + } + volume { + name = "movies" + nfs { + path = "${var.media_storage.root_path}/${var.media_storage.movies_path}" + server = var.media_storage.server_ip + read_only = false + } + } + volume { + name = "shows" + nfs { + path = "${var.media_storage.root_path}/${var.media_storage.shows_path}" + server = var.media_storage.server_ip + read_only = false + } + } + } + } + } +} diff --git a/media/flaresolverr.tf b/media/flaresolverr.tf new file mode 100644 index 0000000..658fe82 --- /dev/null +++ b/media/flaresolverr.tf @@ -0,0 +1,64 @@ +variable "flaresolverr" { + type = object({ + app_name = optional(string, "flarsolverr") + image = string + version = optional(string, "latest") + port = optional(number, 8191) + }) +} + +resource "kubernetes_service_v1" "flarsolverr" { + metadata { + name = "${var.flaresolverr.app_name}" + namespace = kubernetes_namespace_v1.media.metadata[0].name + } + + spec { + selector = { + app = "${var.flaresolverr.app_name}" + } + port { + port = var.flaresolverr.port + target_port = var.flaresolverr.port + } + } +} + +resource "kubernetes_deployment_v1" "flarsolverr" { + metadata { + name = "${var.flaresolverr.app_name}" + namespace = kubernetes_namespace_v1.media.metadata[0].name + labels = { + app = "${var.flaresolverr.app_name}" + } + } + + spec { + replicas = 1 + selector { + match_labels = { + app = "${var.flaresolverr.app_name}" + } + } + + template { + metadata { + labels = { + "app" = "${var.flaresolverr.app_name}" + } + } + + spec { + container { + name = "${var.flaresolverr.app_name}" + image = "${var.flaresolverr.image}:${var.flaresolverr.version}" + image_pull_policy = "Always" + + port { + container_port = var.flaresolverr.port + } + } + } + } + } +} diff --git a/media/jellyfin.tf b/media/jellyfin.tf new file mode 100644 index 0000000..b6b7970 --- /dev/null +++ b/media/jellyfin.tf @@ -0,0 +1,180 @@ +variable "jellyfin" { + type = object({ + app_name = optional(string, "jellyfin") + image = string + version = optional(string, "latest") + subdomain = optional(string, "jellyfin") + port = optional(number, 8096) + }) +} + +resource "kubernetes_service_v1" "jellyfin" { + metadata { + name = var.jellyfin.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + } + + spec { + selector = { + app = var.jellyfin.app_name + } + port { + name = "web" + port = var.jellyfin.port + target_port = var.jellyfin.port + } + port { + name = "client-discovery" + port = 7359 + target_port = 7359 + } + } +} + +resource "kubernetes_ingress_v1" "jellyfin" { + metadata { + name = var.jellyfin.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + + annotations = { + "cert-manager.io/cluster-issuer" = "letsencrypt" + } + } + + spec { + tls { + hosts = ["${var.jellyfin.subdomain}.${var.domain}"] + secret_name = "${var.jellyfin.app_name}-tls" + } + + rule { + host = "${var.jellyfin.subdomain}.${var.domain}" + http { + path { + path = "/" + path_type = "Prefix" + backend { + service { + name = kubernetes_service_v1.jellyfin.metadata[0].name + port { + number = var.jellyfin.port + } + } + } + } + } + } + } +} + +resource "kubernetes_persistent_volume_claim_v1" "jellyfin-config" { + metadata { + name = "${var.jellyfin.app_name}-config" + namespace = kubernetes_namespace_v1.media.metadata[0].name + } + + spec { + storage_class_name = "local-path" + access_modes = ["ReadWriteOnce"] + + resources { + requests = { + storage = "1Gi" + } + } + } +} + +resource "kubernetes_persistent_volume_claim_v1" "jellyfin-cache" { + metadata { + name = "${var.jellyfin.app_name}-cache" + namespace = kubernetes_namespace_v1.media.metadata[0].name + } + + spec { + storage_class_name = "local-path" + access_modes = ["ReadWriteOnce"] + + resources { + requests = { + storage = "1Gi" + } + } + } +} + +resource "kubernetes_deployment_v1" "jellyfin" { + metadata { + name = var.jellyfin.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + labels = { + app = var.jellyfin.app_name + } + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.jellyfin.app_name + } + } + + template { + metadata { + labels = { + "app" = var.jellyfin.app_name + } + } + + spec { + container { + name = var.jellyfin.app_name + image = "${var.jellyfin.image}:${var.jellyfin.version}" + image_pull_policy = "Always" + + port { + container_port = var.jellyfin.port + } + port { + container_port = 7359 + } + + volume_mount { + name = "jellyfin-config" + mount_path = "/config" + } + volume_mount { + name = "jellyfin-cache" + mount_path = "/cache" + } + volume_mount { + name = "jellyfin-data" + mount_path = "/media" + } + } + + volume { + name = "jellyfin-config" + persistent_volume_claim { + claim_name = kubernetes_persistent_volume_claim_v1.jellyfin-config.metadata[0].name + } + } + volume { + name = "jellyfin-cache" + persistent_volume_claim { + claim_name = kubernetes_persistent_volume_claim_v1.jellyfin-cache.metadata[0].name + } + } + volume { + name = "jellyfin-data" + nfs { + path = "${var.media_storage.root_path}/${var.media_storage.jellyfin_path}" + server = var.media_storage.server_ip + read_only = false + } + } + } + } + } +} diff --git a/media/main.tf b/media/main.tf new file mode 100644 index 0000000..8cdc94e --- /dev/null +++ b/media/main.tf @@ -0,0 +1,24 @@ +provider "kubernetes" { + config_path="~/.kube/config" +} + +variable "domain" { + type = string +} + +variable "media_storage" { + type = object({ + server_ip = string + root_path = string + torrent_path = string + jellyfin_path = string + movies_path = string + shows_path = string + }) +} + +resource "kubernetes_namespace_v1" "media" { + metadata { + name = "media" + } +} diff --git a/media/prowlarr.tf b/media/prowlarr.tf new file mode 100644 index 0000000..ad86964 --- /dev/null +++ b/media/prowlarr.tf @@ -0,0 +1,131 @@ +variable "prowlarr" { + type = object({ + app_name = optional(string, "prowlarr") + image = string + version = optional(string, "latest") + subdomain = optional(string, "prowlarr") + port = optional(number, 9696) + }) +} + +resource "kubernetes_service_v1" "prowlarr" { + metadata { + name = "${var.prowlarr.app_name}" + namespace = kubernetes_namespace_v1.media.metadata[0].name + } + + spec { + selector = { + app = "${var.prowlarr.app_name}" + } + port { + port = var.prowlarr.port + target_port = var.prowlarr.port + } + } +} + +resource "kubernetes_ingress_v1" "prowlarr" { + metadata { + name = "${var.prowlarr.app_name}" + namespace = kubernetes_namespace_v1.media.metadata[0].name + + annotations = { + "cert-manager.io/cluster-issuer" = "letsencrypt" + } + } + + spec { + tls { + hosts = ["${var.prowlarr.subdomain}.${var.domain}"] + secret_name = "${var.prowlarr.app_name}-tls" + } + + rule { + host = "${var.prowlarr.subdomain}.${var.domain}" + http { + path { + path = "/" + path_type = "Prefix" + backend { + service { + name = kubernetes_service_v1.prowlarr.metadata[0].name + port { + number = var.prowlarr.port + } + } + } + } + } + } + } +} + +resource "kubernetes_persistent_volume_claim_v1" "prowlarr-config" { + metadata { + name = "${var.prowlarr.app_name}-config" + namespace = kubernetes_namespace_v1.media.metadata[0].name + } + + spec { + storage_class_name = "local-path" + access_modes = ["ReadWriteOnce"] + + resources { + requests = { + storage = "1Gi" + } + } + } +} + +resource "kubernetes_deployment_v1" "prowlarr" { + metadata { + name = "${var.prowlarr.app_name}" + namespace = kubernetes_namespace_v1.media.metadata[0].name + labels = { + app = "${var.prowlarr.app_name}" + } + } + + spec { + replicas = 1 + selector { + match_labels = { + app = "${var.prowlarr.app_name}" + } + } + + template { + metadata { + labels = { + "app" = "${var.prowlarr.app_name}" + } + } + + spec { + container { + name = "${var.prowlarr.app_name}" + image = "${var.prowlarr.image}:${var.prowlarr.version}" + image_pull_policy = "Always" + + port { + container_port = var.prowlarr.port + } + + volume_mount { + name = "prowlarr-config" + mount_path = "/config" + } + } + + volume { + name = "prowlarr-config" + persistent_volume_claim { + claim_name = kubernetes_persistent_volume_claim_v1.prowlarr-config.metadata[0].name + } + } + } + } + } +} diff --git a/media/qbittorrent.tf b/media/qbittorrent.tf new file mode 100644 index 0000000..2c2bad1 --- /dev/null +++ b/media/qbittorrent.tf @@ -0,0 +1,143 @@ +variable "qbittorrent" { + type = object({ + app_name = optional(string, "qbittorrent") + image = string + version = optional(string, "latest") + subdomain = optional(string, "qbittorrent") + port = optional(number, 8080) + }) +} + +resource "kubernetes_service_v1" "qbittorrent" { + metadata { + name = var.qbittorrent.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + } + + spec { + selector = { + app = var.qbittorrent.app_name + } + port { + port = var.qbittorrent.port + target_port = var.qbittorrent.port + } + } +} + +resource "kubernetes_ingress_v1" "qbittorrent" { + metadata { + name = var.qbittorrent.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + + annotations = { + "cert-manager.io/cluster-issuer" = "letsencrypt" + } + } + + spec { + tls { + hosts = ["${var.qbittorrent.subdomain}.${var.domain}"] + secret_name = "${var.qbittorrent.app_name}-tls" + } + + rule { + host = "${var.qbittorrent.subdomain}.${var.domain}" + http { + path { + path = "/" + path_type = "Prefix" + backend { + service { + name = kubernetes_service_v1.qbittorrent.metadata[0].name + port { + number = var.qbittorrent.port + } + } + } + } + } + } + } +} + +resource "kubernetes_persistent_volume_claim_v1" "qbittorrent-config" { + metadata { + name = "${var.qbittorrent.app_name}-config" + namespace = kubernetes_namespace_v1.media.metadata[0].name + } + + spec { + storage_class_name = "local-path" + access_modes = ["ReadWriteOnce"] + + resources { + requests = { + storage = "1Gi" + } + } + } +} + +resource "kubernetes_deployment_v1" "qbittorrent" { + metadata { + name = var.qbittorrent.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + labels = { + app = var.qbittorrent.app_name + } + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.qbittorrent.app_name + } + } + + template { + metadata { + labels = { + "app" = var.qbittorrent.app_name + } + } + + spec { + container { + name = var.qbittorrent.app_name + image = "${var.qbittorrent.image}:${var.qbittorrent.version}" + image_pull_policy = "Always" + + port { + container_port = var.qbittorrent.port + } + + volume_mount { + name = "qbittorrent-config" + mount_path = "/config" + } + volume_mount { + name = "qbittorrent-data" + mount_path = "/downloads" + } + } + + volume { + name = "qbittorrent-config" + persistent_volume_claim { + claim_name = kubernetes_persistent_volume_claim_v1.qbittorrent-config.metadata[0].name + } + } + volume { + name = "qbittorrent-data" + nfs { + path = "${var.media_storage.root_path}/${var.media_storage.torrent_path}" + server = var.media_storage.server_ip + read_only = false + } + } + } + } + } +} diff --git a/media/radarr.tf b/media/radarr.tf new file mode 100644 index 0000000..5a79ec8 --- /dev/null +++ b/media/radarr.tf @@ -0,0 +1,152 @@ +variable "radarr" { + type = object({ + app_name = optional(string, "radarr") + image = string + version = optional(string, "latest") + subdomain = optional(string, "radarr") + port = optional(number, 7878) + }) +} + +resource "kubernetes_service_v1" "radarr" { + metadata { + name = var.radarr.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + } + + spec { + selector = { + app = var.radarr.app_name + } + port { + port = var.radarr.port + target_port = var.radarr.port + } + } +} + +resource "kubernetes_ingress_v1" "radarr" { + metadata { + name = var.radarr.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + + annotations = { + "cert-manager.io/cluster-issuer" = "letsencrypt" + } + } + + spec { + tls { + hosts = ["${var.radarr.subdomain}.${var.domain}"] + secret_name = "${var.radarr.app_name}-tls" + } + + rule { + host = "${var.radarr.subdomain}.${var.domain}" + http { + path { + path = "/" + path_type = "Prefix" + backend { + service { + name = kubernetes_service_v1.radarr.metadata[0].name + port { + number = var.radarr.port + } + } + } + } + } + } + } +} + +resource "kubernetes_persistent_volume_claim_v1" "radarr-config" { + metadata { + name = "${var.radarr.app_name}-config" + namespace = kubernetes_namespace_v1.media.metadata[0].name + } + + spec { + storage_class_name = "local-path" + access_modes = ["ReadWriteOnce"] + + resources { + requests = { + storage = "1Gi" + } + } + } +} + +resource "kubernetes_deployment_v1" "radarr" { + metadata { + name = var.radarr.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + labels = { + app = var.radarr.app_name + } + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.radarr.app_name + } + } + + template { + metadata { + labels = { + "app" = var.radarr.app_name + } + } + + spec { + container { + name = var.radarr.app_name + image = "${var.radarr.image}:${var.radarr.version}" + image_pull_policy = "Always" + + port { + container_port = var.radarr.port + } + + env { + name = "PUID" + value = 0 + } + env { + name = "GUID" + value = 0 + } + + volume_mount { + name = "radarr-config" + mount_path = "/config" + } + volume_mount { + name = "radarr-data" + mount_path = "/data" + } + } + + volume { + name = "radarr-config" + persistent_volume_claim { + claim_name = kubernetes_persistent_volume_claim_v1.radarr-config.metadata[0].name + } + } + volume { + name = "radarr-data" + nfs { + path = "${var.media_storage.root_path}" + server = var.media_storage.server_ip + read_only = false + } + } + } + } + } +} diff --git a/media/sonarr.tf b/media/sonarr.tf new file mode 100644 index 0000000..8019b01 --- /dev/null +++ b/media/sonarr.tf @@ -0,0 +1,152 @@ +variable "sonarr" { + type = object({ + app_name = optional(string, "sonarr") + image = string + version = optional(string, "latest") + subdomain = optional(string, "sonarr") + port = optional(number, 8989) + }) +} + +resource "kubernetes_service_v1" "sonarr" { + metadata { + name = var.sonarr.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + } + + spec { + selector = { + app = var.sonarr.app_name + } + port { + port = var.sonarr.port + target_port = var.sonarr.port + } + } +} + +resource "kubernetes_ingress_v1" "sonarr" { + metadata { + name = var.sonarr.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + + annotations = { + "cert-manager.io/cluster-issuer" = "letsencrypt" + } + } + + spec { + tls { + hosts = ["${var.sonarr.subdomain}.${var.domain}"] + secret_name = "${var.sonarr.app_name}-tls" + } + + rule { + host = "${var.sonarr.subdomain}.${var.domain}" + http { + path { + path = "/" + path_type = "Prefix" + backend { + service { + name = kubernetes_service_v1.sonarr.metadata[0].name + port { + number = var.sonarr.port + } + } + } + } + } + } + } +} + +resource "kubernetes_persistent_volume_claim_v1" "sonarr-config" { + metadata { + name = "${var.sonarr.app_name}-config" + namespace = kubernetes_namespace_v1.media.metadata[0].name + } + + spec { + storage_class_name = "local-path" + access_modes = ["ReadWriteOnce"] + + resources { + requests = { + storage = "1Gi" + } + } + } +} + +resource "kubernetes_deployment_v1" "sonarr" { + metadata { + name = var.sonarr.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + labels = { + app = var.sonarr.app_name + } + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.sonarr.app_name + } + } + + template { + metadata { + labels = { + "app" = var.sonarr.app_name + } + } + + spec { + container { + name = var.sonarr.app_name + image = "${var.sonarr.image}:${var.sonarr.version}" + image_pull_policy = "Always" + + port { + container_port = var.sonarr.port + } + + env { + name = "PUID" + value = 0 + } + env { + name = "GUID" + value = 0 + } + + volume_mount { + name = "sonarr-config" + mount_path = "/config" + } + volume_mount { + name = "sonarr-data" + mount_path = "/data" + } + } + + volume { + name = "sonarr-config" + persistent_volume_claim { + claim_name = kubernetes_persistent_volume_claim_v1.sonarr-config.metadata[0].name + } + } + volume { + name = "sonarr-data" + nfs { + path = "${var.media_storage.root_path}" + server = var.media_storage.server_ip + read_only = false + } + } + } + } + } +} diff --git a/media/terraform.tfvars b/media/terraform.tfvars new file mode 100644 index 0000000..48289cd --- /dev/null +++ b/media/terraform.tfvars @@ -0,0 +1,49 @@ +domain = "ruan.fr" + +media_storage = { + server_ip = "10.42.0.1" + root_path = "/srv/nfs" + torrent_path = "torrent" + jellyfin_path = "jellyfin" + movies_path = "jellyfin/movies" + shows_path = "jellyfin/shows" +} + +bazarr = { + subdomain = "bazarr" + image = "lscr.io/linuxserver/bazarr" +} + +flaresolverr = { + image = "ghcr.io/flaresolverr/flaresolverr" +} + +jellyfin = { + subdomain = "media" + image = "jellyfin/jellyfin" +} + +prowlarr = { + subdomain = "prowlarr" + image = "lscr.io/linuxserver/prowlarr" +} + +qbittorrent = { + subdomain = "torrent" + image = "lscr.io/linuxserver/qbittorrent" +} + +radarr = { + subdomain = "radarr" + image = "lscr.io/linuxserver/radarr" +} + +sonarr = { + subdomain = "sonarr" + image = "lscr.io/linuxserver/sonarr" +} + +wizarr = { + subdomain = "wizarr" + image = "ghcr.io/wizarrrr/wizarr" +} diff --git a/media/wizarr.tf b/media/wizarr.tf new file mode 100644 index 0000000..fb2d80c --- /dev/null +++ b/media/wizarr.tf @@ -0,0 +1,140 @@ +variable "wizarr" { + type = object({ + app_name = optional(string, "wizarr") + image = string + version = optional(string, "latest") + subdomain = optional(string, "wizarr") + port = optional(number, 5960) + }) +} + +resource "kubernetes_service_v1" "wizarr" { + metadata { + name = var.wizarr.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + } + + spec { + selector = { + app = var.wizarr.app_name + } + port { + port = var.wizarr.port + target_port = var.wizarr.port + } + } +} + +resource "kubernetes_ingress_v1" "wizarr" { + metadata { + name = var.wizarr.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + + annotations = { + "cert-manager.io/cluster-issuer" = "letsencrypt" + } + } + + spec { + tls { + hosts = ["${var.wizarr.subdomain}.${var.domain}"] + secret_name = "${var.wizarr.app_name}-tls" + } + + rule { + host = "${var.wizarr.subdomain}.${var.domain}" + http { + path { + path = "/" + path_type = "Prefix" + backend { + service { + name = kubernetes_service_v1.wizarr.metadata[0].name + port { + number = var.wizarr.port + } + } + } + } + } + } + } +} + +resource "kubernetes_persistent_volume_claim_v1" "wizarr-data" { + metadata { + name = "${var.wizarr.app_name}-data" + namespace = kubernetes_namespace_v1.media.metadata[0].name + } + + spec { + storage_class_name = "local-path" + access_modes = ["ReadWriteOnce"] + + resources { + requests = { + storage = "1Gi" + } + } + } +} + +resource "kubernetes_deployment_v1" "wizarr" { + metadata { + name = var.wizarr.app_name + namespace = kubernetes_namespace_v1.media.metadata[0].name + labels = { + app = var.wizarr.app_name + } + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.wizarr.app_name + } + } + + template { + metadata { + labels = { + "app" = var.wizarr.app_name + } + } + + spec { + container { + name = var.wizarr.app_name + image = "${var.wizarr.image}:${var.wizarr.version}" + image_pull_policy = "Always" + + port { + container_port = var.wizarr.port + } + + env { + name = "DISABLE_BUILTIN_AUTH" + value = "false" + } + env { + name = "TZ" + value = "Europe/Paris" + } + + volume_mount { + name = "wizarr-data" + mount_path = "/data" + } + } + + volume { + name = "wizarr-data" + persistent_volume_claim { + claim_name = kubernetes_persistent_volume_claim_v1.wizarr-data.metadata[0].name + } + } + } + } + } +}