diff --git a/stoat/livekit.tf b/stoat/livekit.tf new file mode 100644 index 0000000..fc96537 --- /dev/null +++ b/stoat/livekit.tf @@ -0,0 +1,158 @@ +variable "livekit" { + type = object({ + app_name = optional(string, "livekit") + api_key = string + image = string + version = string + http_port = optional(number, 7880) + tcp_port = optional(number, 7881) + udp_port = optional(number, 7882) + subdomain = optional(string, "livekit") + }) +} + +resource "random_password" "livekit_api_secret" { + length = 44 + special = false +} + +resource "kubernetes_config_map_v1" "livekit" { + metadata { + name = var.livekit.app_name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + data = { + "livekit.yml" = templatefile("${path.module}/templates/livekit.yml.tftpl", + { + domain = var.domain + voice_ingress_url = "http://${var.stoat.voice_ingress.name}:8500/worldwide" + livekit = { + http_port = var.livekit.http_port + tcp_port = var.livekit.tcp_port + api_key = var.livekit.api_key + api_secret = random_password.livekit_api_secret.result + subdomain = var.livekit.subdomain + } + redis = { + host = "${var.redis.app_name}" + port = 6379 + } + }) + } +} + +resource "kubernetes_service_v1" "livekit" { + metadata { + name = "${var.livekit.app_name}-service" + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + selector = { + app = var.livekit.app_name + } + + type = "NodePort" + port { + name = "http" + port = var.livekit.http_port + target_port = var.livekit.http_port + } + port { + name = "tcp" + port = var.livekit.tcp_port + target_port = var.livekit.tcp_port + } + port { + name = "udp" + port = var.livekit.udp_port + target_port = var.livekit.udp_port + } + } +} + +resource "kubernetes_ingress_v1" "livekit" { + metadata { + name = "${var.livekit.app_name}-service" + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + + annotations = { + "cert-manager.io/cluster-issuer" = "letsencrypt" + } + } + + spec { + tls { + hosts = [ + "${var.livekit.subdomain}.${var.domain}" + ] + secret_name = "livekit-tls" + } + + rule { + host = "${var.livekit.subdomain}.${var.domain}" + http { + path { + path = "/" + path_type = "Prefix" + backend { + service { + name = kubernetes_service_v1.livekit.metadata[0].name + port { + number = var.livekit.http_port + } + } + } + } + } + } + } +} + +resource "kubernetes_deployment_v1" "livekit" { + metadata { + name = var.livekit.app_name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.livekit.app_name + } + } + + template { + metadata { + labels = { + "app" = var.livekit.app_name + } + } + + spec { + host_network = true + dns_policy = "ClusterFirstWithHostNet" + container { + name = var.livekit.app_name + image = "${var.livekit.image}:${var.livekit.version}" + # command = [ "/bin/sh", "-c", "--", "while true; do sleep 5; done;"] + args = ["--config", "/etc/livekit.yml"] + volume_mount { + name = "livekit-config" + mount_path = "/etc/livekit.yml" + sub_path = "livekit.yml" + } + } + + volume { + name = "livekit-config" + config_map { + name = kubernetes_config_map_v1.livekit.metadata[0].name + } + } + } + } + } +} diff --git a/stoat/main.tf b/stoat/main.tf new file mode 100644 index 0000000..92d9194 --- /dev/null +++ b/stoat/main.tf @@ -0,0 +1,874 @@ +provider "kubernetes" { + config_path = "~/.kube/config" +} + +variable "domain" { + type = string +} + +variable "smtp" { + type = object({ + host = string + username = string + password = string + from = string + }) +} + +variable "stoat" { + type = object({ + subdomain = string + api = object({ + name = optional(string, "stoat-api") + port = optional(number, 14702) + image = string + version = string + }) + events = object({ + name = optional(string, "stoat-events") + port = optional(number, 14703) + image = string + version = string + }) + autumn = object({ + name = optional(string, "stoat-autumn") + port = optional(number, 14704) + image = string + version = string + }) + january = object({ + name = optional(string, "stoat-january") + port = optional(number, 14705) + image = string + version = string + }) + gifbox = object({ + name = optional(string, "stoat-gifbox") + port = optional(number, 14706) + image = string + version = string + }) + pushd = object({ + name = optional(string, "stoat-pushd") + image = string + version = string + }) + crond = object({ + name = optional(string, "stoat-crond") + image = string + version = string + }) + voice_ingress = object({ + name = optional(string, "stoat-voice-ingress") + port = optional(number, 8500) + image = string + version = string + }) + web = object({ + name = optional(string, "stoat-webapp") + port = optional(number, 80) + image = string + version = string + }) + }) +} + +resource "kubernetes_namespace_v1" "stoat" { + metadata { + name = "stoat" + } +} + +resource "kubernetes_config_map_v1" "Revolt" { + metadata { + name = "revolt" + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + data = { + "Revolt.toml" = templatefile("${path.module}/templates/Revolt.toml.tftpl", + { + domain = var.domain + hostname = "${var.stoat.subdomain}.${var.domain}" + smtp = var.smtp + livekit_api_key = var.livekit.api_key + livekit_secret_key = random_password.livekit_api_secret.result + minio_host = var.minio.app_name + minio_user = var.minio.user + minio_pass = random_password.minio.result + mongo_host = var.mongo.app_name + rabbit_host = var.rabbit.app_name + rabbit_port = var.rabbit.port + rabbit_user = var.rabbit.user + rabbit_passwd = random_password.rabbit.result + redis_host = var.redis.app_name + }) + "Caddyfile" = templatefile("${path.module}/templates/Caddyfile.tftpl", + { + hostname = "${var.stoat.subdomain}.${var.domain}" + stoat = var.stoat + }) + } +} + +resource "kubernetes_config_map_v1" "env_web" { + metadata { + name = "env-web" + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + data = { + NGINX_HOST = "chat.ruan.fr" + } +} + +resource "kubernetes_ingress_v1" "stoat" { + metadata { + name = "stoat" + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + + annotations = { + "cert-manager.io/cluster-issuer" = "letsencrypt" + } + } + + spec { + tls { + hosts = [ + "${var.stoat.subdomain}.${var.domain}", + "api.${var.stoat.subdomain}.${var.domain}", + "file.${var.stoat.subdomain}.${var.domain}", + "proxy.${var.stoat.subdomain}.${var.domain}", + "events.${var.stoat.subdomain}.${var.domain}", + "gifbox.${var.stoat.subdomain}.${var.domain}", + ] + secret_name = "stoat-tls" + } + + rule { + host = "${var.stoat.subdomain}.${var.domain}" + http { + path { + path = "/" + path_type = "Prefix" + backend { + service { + name = kubernetes_service_v1.stoat_caddy.metadata[0].name + port { + number = 80 + } + } + } + } + } + } + rule { + host = "api.${var.stoat.subdomain}.${var.domain}" + http { + path { + path = "/" + path_type = "Prefix" + backend { + service { + name = kubernetes_service_v1.stoat_api.metadata[0].name + port { + number = var.stoat.api.port + } + } + } + } + } + } + rule { + host = "events.${var.stoat.subdomain}.${var.domain}" + http { + path { + path = "/" + path_type = "Exact" + backend { + service { + name = kubernetes_service_v1.stoat_events.metadata[0].name + port { + number = var.stoat.events.port + } + } + } + } + } + } + rule { + host = "file.${var.stoat.subdomain}.${var.domain}" + http { + path { + path = "/" + path_type = "Prefix" + backend { + service { + name = kubernetes_service_v1.stoat_autumn.metadata[0].name + port { + number = var.stoat.autumn.port + } + } + } + } + } + } + rule { + host = "proxy.${var.stoat.subdomain}.${var.domain}" + http { + path { + path = "/" + path_type = "Prefix" + backend { + service { + name = kubernetes_service_v1.stoat_january.metadata[0].name + port { + number = var.stoat.january.port + } + } + } + } + } + } + rule { + host = "gifbox.${var.stoat.subdomain}.${var.domain}" + http { + path { + path = "/" + path_type = "Prefix" + backend { + service { + name = kubernetes_service_v1.stoat_gifbox.metadata[0].name + port { + number = var.stoat.gifbox.port + } + } + } + } + } + } + } +} + +resource "kubernetes_service_v1" "stoat_caddy" { + metadata { + name = "caddy" + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + selector = { + app = "caddy" + } + + port { + port = 80 + target_port = 80 + } + } +} + +resource "kubernetes_deployment_v1" "stoat_caddy" { + metadata { + name = "caddy" + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + replicas = 1 + selector { + match_labels = { + app = "caddy" + } + } + + template { + metadata { + labels = { + "app" = "caddy" + } + } + + spec { + container { + name = "caddy" + image = "docker.io/caddy" + + port { + container_port = 80 + } + + env_from { + config_map_ref { + name = kubernetes_config_map_v1.env_web.metadata[0].name + optional = false + } + } + + volume_mount { + name = "revolt" + mount_path = "/etc/caddy/Caddyfile" + sub_path = "Caddyfile" + } + } + + volume { + name = "revolt" + config_map { + name = kubernetes_config_map_v1.Revolt.metadata[0].name + optional = false + } + } + } + } + } +} + +resource "kubernetes_service_v1" "stoat_api" { + metadata { + name = var.stoat.api.name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + selector = { + app = var.stoat.api.name + } + + port { + port = var.stoat.api.port + target_port = var.stoat.api.port + } + } +} + +resource "kubernetes_deployment_v1" "stoat_api" { + metadata { + name = var.stoat.api.name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.stoat.api.name + } + } + + template { + metadata { + labels = { + "app" = var.stoat.api.name + } + } + + spec { + container { + name = var.stoat.api.name + image = "${var.stoat.api.image}:${var.stoat.api.version}" + + port { + container_port = var.stoat.api.port + } + + volume_mount { + name = "revolt-toml" + mount_path = "/Revolt.toml" + sub_path = "Revolt.toml" + } + } + + volume { + name = "revolt-toml" + config_map { + name = kubernetes_config_map_v1.Revolt.metadata[0].name + optional = false + } + } + } + } + } +} + +resource "kubernetes_service_v1" "stoat_events" { + metadata { + name = var.stoat.events.name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + selector = { + app = var.stoat.events.name + } + + port { + port = var.stoat.events.port + target_port = var.stoat.events.port + } + } +} + +resource "kubernetes_deployment_v1" "stoat_events" { + metadata { + name = var.stoat.events.name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.stoat.events.name + } + } + + template { + metadata { + labels = { + "app" = var.stoat.events.name + } + } + + spec { + container { + name = var.stoat.events.name + image = "${var.stoat.events.image}:${var.stoat.events.version}" + + port { + container_port = var.stoat.events.port + } + + volume_mount { + name = "revolt-toml" + mount_path = "/Revolt.toml" + sub_path = "Revolt.toml" + } + } + + volume { + name = "revolt-toml" + config_map { + name = kubernetes_config_map_v1.Revolt.metadata[0].name + optional = false + } + } + } + } + } +} + +resource "kubernetes_service_v1" "stoat_autumn" { + metadata { + name = var.stoat.autumn.name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + selector = { + app = var.stoat.autumn.name + } + + port { + port = var.stoat.autumn.port + target_port = var.stoat.autumn.port + } + } +} + +resource "kubernetes_deployment_v1" "stoat_autumn" { + metadata { + name = var.stoat.autumn.name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.stoat.autumn.name + } + } + + template { + metadata { + labels = { + "app" = var.stoat.autumn.name + } + } + + spec { + container { + name = var.stoat.autumn.name + image = "${var.stoat.autumn.image}:${var.stoat.autumn.version}" + + port { + container_port = var.stoat.autumn.port + } + + volume_mount { + name = "revolt-toml" + mount_path = "/Revolt.toml" + sub_path = "Revolt.toml" + } + } + + volume { + name = "revolt-toml" + config_map { + name = kubernetes_config_map_v1.Revolt.metadata[0].name + optional = false + } + } + } + } + } +} + +resource "kubernetes_service_v1" "stoat_january" { + metadata { + name = var.stoat.january.name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + selector = { + app = var.stoat.january.name + } + + port { + port = var.stoat.january.port + target_port = var.stoat.january.port + } + } +} + +resource "kubernetes_deployment_v1" "stoat_january" { + metadata { + name = var.stoat.january.name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.stoat.january.name + } + } + + template { + metadata { + labels = { + "app" = var.stoat.january.name + } + } + + spec { + container { + name = var.stoat.january.name + image = "${var.stoat.january.image}:${var.stoat.january.version}" + + port { + container_port = var.stoat.january.port + } + + volume_mount { + name = "revolt-toml" + mount_path = "/Revolt.toml" + sub_path = "Revolt.toml" + } + } + + volume { + name = "revolt-toml" + config_map { + name = kubernetes_config_map_v1.Revolt.metadata[0].name + optional = false + } + } + } + } + } +} + +resource "kubernetes_service_v1" "stoat_gifbox" { + metadata { + name = var.stoat.gifbox.name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + selector = { + app = var.stoat.gifbox.name + } + + port { + port = var.stoat.gifbox.port + target_port = var.stoat.gifbox.port + } + } +} + +resource "kubernetes_deployment_v1" "stoat_gifbox" { + metadata { + name = var.stoat.gifbox.name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.stoat.gifbox.name + } + } + + template { + metadata { + labels = { + "app" = var.stoat.gifbox.name + } + } + + spec { + container { + name = var.stoat.gifbox.name + image = "${var.stoat.gifbox.image}:${var.stoat.gifbox.version}" + + port { + container_port = var.stoat.gifbox.port + } + + volume_mount { + name = "revolt-toml" + mount_path = "/Revolt.toml" + sub_path = "Revolt.toml" + } + } + + volume { + name = "revolt-toml" + config_map { + name = kubernetes_config_map_v1.Revolt.metadata[0].name + optional = false + } + } + } + } + } +} + +resource "kubernetes_deployment_v1" "stoat_pushd" { + metadata { + name = var.stoat.pushd.name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.stoat.pushd.name + } + } + + template { + metadata { + labels = { + "app" = var.stoat.pushd.name + } + } + + spec { + container { + name = var.stoat.pushd.name + image = "${var.stoat.pushd.image}:${var.stoat.pushd.version}" + + volume_mount { + name = "revolt-toml" + mount_path = "/Revolt.toml" + sub_path = "Revolt.toml" + } + } + + volume { + name = "revolt-toml" + config_map { + name = kubernetes_config_map_v1.Revolt.metadata[0].name + optional = false + } + } + } + } + } +} + +resource "kubernetes_deployment_v1" "stoat_crond" { + metadata { + name = var.stoat.crond.name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.stoat.crond.name + } + } + + template { + metadata { + labels = { + "app" = var.stoat.crond.name + } + } + + spec { + container { + name = var.stoat.crond.name + image = "${var.stoat.crond.image}:${var.stoat.crond.version}" + + volume_mount { + name = "revolt-toml" + mount_path = "/Revolt.toml" + sub_path = "Revolt.toml" + } + } + + volume { + name = "revolt-toml" + config_map { + name = kubernetes_config_map_v1.Revolt.metadata[0].name + optional = false + } + } + } + } + } +} + +resource "kubernetes_service_v1" "stoat_voice_ingress" { + metadata { + name = var.stoat.voice_ingress.name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + selector = { + app = var.stoat.voice_ingress.name + } + + port { + port = var.stoat.voice_ingress.port + target_port = var.stoat.voice_ingress.port + } + } +} + +resource "kubernetes_deployment_v1" "stoat_voice_ingress" { + metadata { + name = var.stoat.voice_ingress.name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.stoat.voice_ingress.name + } + } + + template { + metadata { + labels = { + "app" = var.stoat.voice_ingress.name + } + } + + spec { + container { + name = var.stoat.voice_ingress.name + image = "${var.stoat.voice_ingress.image}:${var.stoat.voice_ingress.version}" + + volume_mount { + name = "revolt-toml" + mount_path = "/Revolt.toml" + sub_path = "Revolt.toml" + } + } + + volume { + name = "revolt-toml" + config_map { + name = kubernetes_config_map_v1.Revolt.metadata[0].name + optional = false + } + } + } + } + } +} + +resource "kubernetes_service_v1" "stoat_web" { + metadata { + name = var.stoat.web.name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + selector = { + app = var.stoat.web.name + } + + port { + port = var.stoat.web.port + target_port = var.stoat.web.port + } + } +} + +resource "kubernetes_deployment_v1" "stoat_web" { + metadata { + name = var.stoat.web.name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.stoat.web.name + } + } + + template { + metadata { + labels = { + "app" = var.stoat.web.name + } + } + + spec { + container { + name = var.stoat.web.name + image = "${var.stoat.web.image}:${var.stoat.web.version}" + image_pull_policy = "Always" + + port { + container_port = var.stoat.web.port + } + + env_from { + config_map_ref { + name = kubernetes_config_map_v1.env_web.metadata[0].name + optional = false + } + } + } + } + } + } +} diff --git a/stoat/minio.tf b/stoat/minio.tf new file mode 100644 index 0000000..9847d6c --- /dev/null +++ b/stoat/minio.tf @@ -0,0 +1,180 @@ +variable "minio" { + type = object({ + app_name = optional(string, "minio") + image = string + version = string + user = string + port = optional(number, 9000) + }) +} + +resource "random_password" "minio" { + length = 16 +} + +resource "kubernetes_service_v1" "minio" { + metadata { + name = var.minio.app_name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + selector = { + app = var.minio.app_name + } + port { + port = var.minio.port + target_port = var.minio.port + } + } +} + +resource "kubernetes_config_map_v1" "minio_env" { + metadata { + name = "${var.minio.app_name}-env" + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + data = { + MINIO_ROOT_USER = var.minio.user + MINIO_ROOT_PASSWORD = random_password.minio.result + MINIO_DOMAIN = "minio" + } +} + +resource "kubernetes_config_map_v1" "minio_creds" { + metadata { + name = "${var.minio.app_name}-creds" + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + data = { + "creds.json" = templatefile("${path.module}/templates/minio_creds.json.tftpl", + { + minio_host = var.minio.app_name + minio_user = var.minio.user + minio_pass = random_password.minio.result + }) + } +} + +resource "kubernetes_deployment_v1" "minio" { + metadata { + name = var.minio.app_name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.minio.app_name + } + } + + template { + metadata { + labels = { + "app" = var.minio.app_name + } + } + + spec { + container { + name = var.minio.app_name + image = "${var.minio.image}:${var.minio.version}" + command = ["minio", "server", "/data"] + + port { + container_port = var.minio.port + } + + env_from { + config_map_ref { + name = kubernetes_config_map_v1.minio_env.metadata[0].name + optional = false + } + } + + volume_mount { + name = "minio-data" + mount_path = "/data" + } + } + + container { + name = "minio-cli" + image = "minio/mc" + command = ["/bin/sh", "-c", "--", "while true; do sleep 10; done"] + + volume_mount { + name = "minio-creds" + mount_path = "/creds.json" + sub_path = "creds.json" + } + } + + volume { + name = "minio-data" + nfs { + path = "/srv/nfs/minio" + server = "10.42.0.1" + read_only = false + } + } + + volume { + name = "minio-creds" + config_map { + name = kubernetes_config_map_v1.minio_creds.metadata[0].name + optional = false + } + } + } + } + } +} +resource "kubernetes_deployment_v1" "minio_cli" { + metadata { + name = "minio-cli" + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + replicas = 1 + selector { + match_labels = { + app = "minio-cli" + } + } + + template { + metadata { + labels = { + app = "minio-cli" + } + } + + spec { + container { + name = "minio-cli" + image = "minio/mc" + command = ["/bin/sh", "-c", "--", "while true; do sleep 10; done"] + + volume_mount { + name = "minio-creds" + mount_path = "/creds.json" + sub_path = "creds.json" + } + } + volume { + name = "minio-creds" + config_map { + name = kubernetes_config_map_v1.minio_creds.metadata[0].name + optional = false + } + } + } + } + } +} diff --git a/stoat/mongo.tf b/stoat/mongo.tf new file mode 100644 index 0000000..3611296 --- /dev/null +++ b/stoat/mongo.tf @@ -0,0 +1,89 @@ +variable "mongo" { + type = object({ + app_name = optional(string, "mongo") + image = string + version = string + }) +} + +resource "kubernetes_service_v1" "mongo" { + metadata { + name = var.mongo.app_name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + selector = { + app = var.mongo.app_name + } + port { + port = 27017 + target_port = 27017 + } + } +} + +resource "kubernetes_persistent_volume_claim_v1" "mongo" { + metadata { + name = var.mongo.app_name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + storage_class_name = "local-path" + access_modes = ["ReadWriteOnce"] + + resources { + requests = { + storage = "5Gi" + } + } + } +} + +resource "kubernetes_deployment_v1" "mongo" { + metadata { + name = var.mongo.app_name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.mongo.app_name + } + } + + template { + metadata { + labels = { + "app" = var.mongo.app_name + } + } + + spec { + container { + name = var.mongo.app_name + image = "${var.mongo.image}:${var.mongo.version}" + + port { + container_port = 27017 + } + + volume_mount { + name = "mongo-data" + mount_path = "/data/db" + } + } + + volume { + name = "mongo-data" + persistent_volume_claim { + claim_name = var.mongo.app_name + } + } + } + } + } +} diff --git a/stoat/rabbit.tf b/stoat/rabbit.tf new file mode 100644 index 0000000..cce545b --- /dev/null +++ b/stoat/rabbit.tf @@ -0,0 +1,114 @@ +variable "rabbit" { + type = object({ + app_name = optional(string, "rabbit") + image = string + version = string + user = optional(string, "rabbit") + port = optional(number, 5672) + }) +} + +resource "random_password" "rabbit" { + length = 16 +} + +resource "kubernetes_service_v1" "rabbit" { + metadata { + name = var.rabbit.app_name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + selector = { + app = var.rabbit.app_name + } + port { + port = var.rabbit.port + target_port = var.rabbit.port + } + } +} + +resource "kubernetes_persistent_volume_claim_v1" "rabbit-data" { + metadata { + name = var.rabbit.app_name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + storage_class_name = "local-path" + access_modes = ["ReadWriteOnce"] + + resources { + requests = { + storage = "1Gi" + } + } + } +} + +resource "kubernetes_config_map_v1" "rabbit_env" { + metadata { + name = "${var.rabbit.app_name}-env" + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + data = { + RABBITMQ_DEFAULT_USER = var.rabbit.user + RABBITMQ_DEFAULT_PASS = random_password.rabbit.result + } +} + +resource "kubernetes_deployment_v1" "rabbit" { + metadata { + name = var.rabbit.app_name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.rabbit.app_name + } + } + + template { + metadata { + labels = { + "app" = var.rabbit.app_name + } + } + + spec { + container { + name = var.rabbit.app_name + image = "${var.rabbit.image}:${var.rabbit.version}" + + port { + container_port = var.rabbit.port + } + + env_from { + config_map_ref { + name = kubernetes_config_map_v1.rabbit_env.metadata[0].name + optional = false + } + } + + volume_mount { + name = "rabbit-data" + mount_path = "/var/lib/rabbitmq" + } + } + + volume { + name = "rabbit-data" + persistent_volume_claim { + claim_name = kubernetes_persistent_volume_claim_v1.rabbit-data.metadata[0].name + } + } + } + } + } +} diff --git a/stoat/redis.tf b/stoat/redis.tf new file mode 100644 index 0000000..e838bd0 --- /dev/null +++ b/stoat/redis.tf @@ -0,0 +1,59 @@ +variable "redis" { + type = object({ + app_name = optional(string, "redis") + image = string + version = string + }) +} + +resource "kubernetes_service_v1" "redis" { + metadata { + name = var.redis.app_name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + selector = { + app = var.redis.app_name + } + port { + port = 6379 + target_port = 6379 + } + } +} + +resource "kubernetes_deployment_v1" "redis" { + metadata { + name = var.redis.app_name + namespace = kubernetes_namespace_v1.stoat.metadata[0].name + } + + spec { + replicas = 1 + selector { + match_labels = { + app = var.redis.app_name + } + } + + template { + metadata { + labels = { + "app" = var.redis.app_name + } + } + + spec { + container { + name = var.redis.app_name + image = "${var.redis.image}:${var.redis.version}" + + port { + container_port = 6379 + } + } + } + } + } +} diff --git a/stoat/templates/Caddyfile.tftpl b/stoat/templates/Caddyfile.tftpl new file mode 100644 index 0000000..93ef4cc --- /dev/null +++ b/stoat/templates/Caddyfile.tftpl @@ -0,0 +1,38 @@ +:80 { + route /api* { + uri strip_prefix /api + reverse_proxy http://${stoat.api.name}:14702 { + header_down Location "^/" "/api/" + } + } + + route /ws { + uri strip_prefix /ws + reverse_proxy http://${stoat.events.name}:14703 { + header_down Location "^/" "/ws/" + } + } + + route /autumn* { + uri strip_prefix /autumn + reverse_proxy http://${stoat.autumn.name}:14704 { + header_down Location "^/" "/autumn/" + } + } + + route /january* { + uri strip_prefix /january + reverse_proxy http://${stoat.january.name}:14705 { + header_down Location "^/" "/january/" + } + } + + route /gifbox* { + uri strip_prefix /gifbox + reverse_proxy http://${stoat.gifbox.name}:14706 { + header_down Location "^/" "/gifbox/" + } + } + + reverse_proxy http://${stoat.web.name}:80 +} diff --git a/stoat/templates/Revolt.toml.tftpl b/stoat/templates/Revolt.toml.tftpl new file mode 100644 index 0000000..34f81e9 --- /dev/null +++ b/stoat/templates/Revolt.toml.tftpl @@ -0,0 +1,289 @@ +production = true + +[database] +# MongoDB connection URL +# Defaults to the container name specified in self-hosted +mongodb = "mongodb://${mongo_host}" +# Redis connection URL +# Defaults to the container name specified in self-hosted +redis = "redis://${redis_host}/" + +[hosts] +# Web locations of various services +# Defaults assume all services are reverse-proxied +# See https://github.com/revoltchat/self-hosted/blob/master/Caddyfile +# +# Remember to change these to https/wss where appropriate in production! +app = "https://${hostname}" +api = "https://api.${hostname}/" +events = "wss://events.${hostname}/" +autumn = "https://file.${hostname}/" +january = "https://proxy.${hostname}/" + +[hosts.livekit] +worldwide = "wss://livekit.${domain}" + +[rabbit] +host = "${rabbit_host}" +port = ${rabbit_port} +username = "${rabbit_user}" +password = "${rabbit_passwd}" + +[api] + +[api.livekit] + +[api.livekit.nodes.worldwide] +url = "https://livekit.${domain}" +lat = 0.0 +lon = 0.0 +key = "${livekit_api_key}" +secret = "${livekit_secret_key}" + +[api.registration] +# Whether an invite should be required for registration +# See https://github.com/revoltchat/self-hosted#making-your-instance-invite-only +invite_only = true + +[api.smtp] +# Email server configuration for verification +# Defaults to no email verification (host field is empty) +host = "${smtp.host}" +username = "${smtp.username}" +password = "${smtp.password}" +from_address = "${smtp.from}" +# reply_to = "noreply@example.com" +port = 465 +use_tls = true + + +[api.security] +# Authifier Shield API key +authifier_shield_key = "" +# Legacy voice server management token +voso_legacy_token = "" +# Whether services are behind the Cloudflare network +trust_cloudflare = false +# easypwned endpoint +easypwned = "" + +[api.security.captcha] +# hCaptcha configuration +hcaptcha_key = "" +hcaptcha_sitekey = "" + +[api.workers] +# Maximum concurrent connections (to proxy server) +max_concurrent_connections = 50 + +[api.users] + + +[pushd] +# this changes the names of the queues to not overlap +# prod/beta if they happen to be on the same exchange/instance. +# Usually they have to be, so that messages sent from one or the other get sent to everyone +production = true + +# Changes how many users are processed in each chunk when resolving role/everyone mentions. +# Increasing this will resolve mentions faster, but will consume more memory while resolving. +mass_mention_chunk_size = 200 + +# none of these should need changing +exchange = "revolt.notifications" +message_queue = "notifications.origin.message" +mass_mention_queue = "notifications.origin.mass_mention" # handles messages that contain role or everyone mentions +fr_accepted_queue = "notifications.ingest.fr_accepted" # friend request accepted +fr_received_queue = "notifications.ingest.fr_received" # friend request received +generic_queue = "notifications.ingest.generic" # generic messages (title + body) +ack_queue = "notifications.process.ack" # updates badges for apple devices + + +[pushd.vapid] +queue = "notifications.outbound.vapid" +private_key = "LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUU3WHpRZElsQk9TSW4yaXV4eUdFa2ExTGNhZzhKMEEzL0lFRjhXMklJWGZvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFUTBWUkxtUklsS1Y5bW1FcUM4R3Z3aEFpQzdHb1I3TzNlTzVIOXlvVytYTzVFQzcwbHNHYwp5ZjFrYTlzK3hWZEEzdk4yYmNvRmkrT3BGUG9uY0pWRjV3PT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo" +public_key = "BENFUS5kSJSlfZphKgvBr8IQIguxqEezt3juR_cqFvlzuRAu9JbBnMn9ZGvbPsVXQN7zdm3KBYvjqRT6J3CVRec" + +[pushd.fcm] +queue = "notifications.outbound.fcm" +key_type = "" +project_id = "" +private_key_id = "" +private_key = "" +client_email = "" +client_id = "" +auth_uri = "" +token_uri = "" +auth_provider_x509_cert_url = "" +client_x509_cert_url = "" + +[pushd.apn] +sandbox = false +queue = "notifications.outbound.apn" +pkcs8 = "" +key_id = "" +team_id = "" + + +[files] +# Encryption key for stored files +# Generate your own key using `openssl -base64 32` +encryption_key = "regG4PGKT1sZm6fONrmNq/YrNvKoN1ivzRbmDLi72ns=" +# Quality used for lossy WebP previews (set to 100 for lossless) +webp_quality = 80.0 +# Mime types that cannot be uploaded or served +# +# Example for Windows executables and Android installation files: +# ["application/vnd.microsoft.portable-executable", "application/vnd.android.package-archive"] +blocked_mime_types = [] +# ClamAV service +# hostname:port +clamd_host = "" +# Mime types that should be virus scanned +# +# Leave empty to scan all file types +scan_mime_types = [ + "application/vnd.microsoft.portable-executable", + "application/vnd.android.package-archive", + "application/zip", +] + +[files.limit] +# Minimum file size (in bytes) +min_file_size = 1 +# Minimum image resolution +min_resolution = [1, 1] +# Maximum MP of images +max_mega_pixels = 40 +# Maximum pixel side of an image +max_pixel_side = 10_000 + +[files.preview] +# Maximum image resolution +attachments = [1280, 1280] +avatars = [128, 128] +backgrounds = [1280, 720] +icons = [128, 128] +banners = [480, 480] +emojis = [128, 128] + +[files.s3] +# Configuration for S3 +# Defaults included for MinIO + self-hosted setup +# +# Backblaze B2: +# - endpoint is listed on the "Buckets" page +# - path_style_buckets is set to true +# - region is `eu-central-003` string from endpoint URL +# - access_key_id is keyID generated on the "Application Keys" page +# - secret_access_key is token generated on the "Application Keys" page +# - default_bucket matches the name of the bucket you've created + +# S3 protocol endpoint +endpoint = "http://${minio_host}:9000" +# Whether to use path-style buckets +# Generally true, except for MinIO +path_style_buckets = true +# S3 region name +region = "minio" +# S3 protocol key ID +access_key_id = "${minio_user}" +# S3 protocol access key +secret_access_key = "${minio_pass}" +default_bucket = "revolt-uploads" + + +[features] +# Feature gate options +webhooks_enabled = false +# Enable push notifications for mass pings (everyone, online, roles) +# When false this will still ping in-client but will not send notifications from pushd +mass_mentions_send_notifications = true +# Can role/everyone pings be used at all +mass_mentions_enabled = true + +[features.limits] + +[features.limits.global] +group_size = 100 +message_embeds = 5 +message_replies = 5 +message_reactions = 20 +server_emoji = 100 +server_roles = 200 +server_channels = 200 + +# How many hours since creation a user is considered new +new_user_hours = 72 + +# Maximum permissible body size in bytes for uploads +# (should be greater than any one file upload limit) +body_limit_size = 20_000_000 + +[features.limits.new_user] +# Limits imposed on new users + +# Number of outgoing friend requests permitted at any time +outgoing_friend_requests = 5 + +# Maximum number of owned bots +bots = 2 + +# Message content length +message_length = 2000 + +# Number of attachments that can be included +message_attachments = 5 + +# Maximum number of servers the user can create/join +servers = 50 + +[features.limits.new_user.file_upload_size_limit] +# Maximum file size limits (in bytes) +attachments = 20_000_000 +avatars = 4_000_000 +backgrounds = 6_000_000 +icons = 2_500_000 +banners = 6_000_000 +emojis = 500_000 + +[features.limits.default] +# Limits imposed on users by default + +# Number of outgoing friend requests permitted at any time +outgoing_friend_requests = 10 + +# Maximum number of owned bots +bots = 5 + +# Message content length +message_length = 2000 + +# Number of attachments that can be included +message_attachments = 5 + +# Maximum number of servers the user can create/join +servers = 100 + +[features.limits.default.file_upload_size_limit] +# Maximum file size limits (in bytes) +attachments = 20_000_000 +avatars = 4_000_000 +backgrounds = 6_000_000 +icons = 2_500_000 +banners = 6_000_000 +emojis = 500_000 + +[features.advanced] +# The max amount of messages the rabbitmq provider/db mention adder job will delay for before forcing handling of a channel. +# default: 5 +process_message_delay_limit = 5 + +[sentry] +# Configuration for Sentry error reporting +api = "" +events = "" +files = "" +proxy = "" +pushd = "" +crond = "" diff --git a/stoat/templates/livekit.yml.tftpl b/stoat/templates/livekit.yml.tftpl new file mode 100644 index 0000000..d49b614 --- /dev/null +++ b/stoat/templates/livekit.yml.tftpl @@ -0,0 +1,17 @@ +port: ${livekit.http_port} +log_level: debug +rtc: + tcp_port: ${livekit.tcp_port} + port_range_start: 50000 + port_range_end: 60000 + use_external_ip: false +redis: + address: ${redis.host}:${redis.port} + username: "" + password: "" +webhook: + api_key: ${livekit.api_key} + urls: + - "${voice_ingress_url}" +keys: + ${livekit.api_key}: ${livekit.api_secret} diff --git a/stoat/templates/minio_creds.json.tftpl b/stoat/templates/minio_creds.json.tftpl new file mode 100644 index 0000000..0e1dbb9 --- /dev/null +++ b/stoat/templates/minio_creds.json.tftpl @@ -0,0 +1,7 @@ +{ + "url": "http://${minio_host}:9000", + "accessKey": "${minio_user}", + "secretKey": "${minio_pass}", + "api": "s3v4", + "path": "auto" + } \ No newline at end of file diff --git a/stoat/terraform.tfvars b/stoat/terraform.tfvars new file mode 100644 index 0000000..3ff7cfa --- /dev/null +++ b/stoat/terraform.tfvars @@ -0,0 +1,76 @@ +domain = "ruan.fr" + +livekit = { + image = "livekit/livekit-server" + version = "latest" + api_key = "APIK9RyTCqWEzwc" +} + +minio = { + image = "docker.io/minio/minio" + version = "latest" + user = "minio" +} + +mongo = { + image = "docker.io/mongo" + version = "latest" +} + +rabbit = { + image = "docker.io/rabbitmq" + version = "latest" + user = "rabbit" +} + +redis = { + image = "docker.io/eqalpha/keydb" + version = "latest" +} + +smtp = { + host = "smtp.tem.scaleway.com" + username = "0405a864-c52f-4a58-9dff-05560e22978b" + password = "" + from = "noreply@ruan.fr" +} + +stoat = { + subdomain = "chat" + api = { + image = "ghcr.io/stoatchat/api" + version = "v0.11.0" + } + events = { + image = "ghcr.io/stoatchat/events" + version = "v0.11.0" + } + autumn = { + image = "ghcr.io/stoatchat/file-server" + version = "v0.11.0" + } + january = { + image = "ghcr.io/stoatchat/proxy" + version = "v0.11.0" + } + gifbox = { + image = "ghcr.io/stoatchat/gifbox" + version = "v0.11.0" + } + crond = { + image = "ghcr.io/stoatchat/crond" + version = "v0.11.0" + } + pushd = { + image = "ghcr.io/stoatchat/pushd" + version = "v0.11.0" + } + voice_ingress = { + image = "ghcr.io/stoatchat/voice-ingress" + version = "v0.11.0" + } + web = { + image = "antoninruan/stoat-client" + version = "v0.1.2" + } +}