Move media stack to tofu

This commit is contained in:
Antonin Ruan
2026-02-09 23:10:53 +01:00
parent f20172f6a4
commit c258a1d09e
17 changed files with 1211 additions and 502 deletions

168
media/bazarr.tf Normal file
View File

@@ -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
}
}
}
}
}
}

64
media/flaresolverr.tf Normal file
View File

@@ -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
}
}
}
}
}
}

180
media/jellyfin.tf Normal file
View File

@@ -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
}
}
}
}
}
}

24
media/main.tf Normal file
View File

@@ -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"
}
}

131
media/prowlarr.tf Normal file
View File

@@ -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
}
}
}
}
}
}

148
media/qbittorrent.tf Normal file
View File

@@ -0,0 +1,148 @@
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"
}
resources {
limits = {
"memory" = "1Gi"
}
}
}
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
}
}
}
}
}
}

152
media/radarr.tf Normal file
View File

@@ -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
}
}
}
}
}
}

152
media/sonarr.tf Normal file
View File

@@ -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
}
}
}
}
}
}

49
media/terraform.tfvars Normal file
View File

@@ -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"
}

140
media/wizarr.tf Normal file
View File

@@ -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, 5690)
})
}
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
}
}
}
}
}
}