From a114a5002e400183201ada3443c166c716fe303c Mon Sep 17 00:00:00 2001 From: Thomas Rijpstra Date: Thu, 25 Jun 2026 09:54:44 +0200 Subject: [PATCH] feat(quadlets): add new service modules for the mars ship Add quadlet modules for affine, airsonic-advanced, beets, deeptutor, documentdb, forgejo, gonic, mopidy, navidrome, opensign, plane, postgres (with tenant sub-module), qdrant, rabbitmq (with tenant sub-module), tmail-web, and zot, all built on the quadlet-app/network patterns. --- quadlets/modules/affine/main.tf | 82 ++++++ quadlets/modules/airsonic-advanced/main.tf | 60 +++++ quadlets/modules/beets/main.tf | 59 +++++ quadlets/modules/deeptutor/main.tf | 98 +++++++ quadlets/modules/documentdb/main.tf | 59 +++++ quadlets/modules/forgejo/main.tf | 117 +++++++++ quadlets/modules/gonic/main.tf | 60 +++++ quadlets/modules/mopidy/main.tf | 59 +++++ quadlets/modules/navidrome/main.tf | 58 +++++ quadlets/modules/opensign/main.tf | 111 ++++++++ quadlets/modules/plane/admin/main.tf | 58 +++++ quadlets/modules/plane/api/main.tf | 182 +++++++++++++ quadlets/modules/plane/beat-worker/main.tf | 112 ++++++++ quadlets/modules/plane/live/main.tf | 60 +++++ quadlets/modules/plane/main.tf | 239 ++++++++++++++++++ quadlets/modules/plane/migrator/main.tf | 112 ++++++++ quadlets/modules/plane/space/main.tf | 60 +++++ quadlets/modules/plane/web/main.tf | 64 +++++ quadlets/modules/plane/worker/main.tf | 113 +++++++++ quadlets/modules/postgres/main.tf | 48 ++++ quadlets/modules/postgres/tenant/README.md | 132 ++++++++++ quadlets/modules/postgres/tenant/main.tf | 81 ++++++ quadlets/modules/postgres/tenant/outputs.tf | 27 ++ quadlets/modules/postgres/tenant/variables.tf | 44 ++++ quadlets/modules/qdrant/main.tf | 63 +++++ quadlets/modules/rabbitmq/main.tf | 60 +++++ quadlets/modules/rabbitmq/tenant/README.md | 180 +++++++++++++ quadlets/modules/rabbitmq/tenant/main.tf | 71 ++++++ quadlets/modules/rabbitmq/tenant/outputs.tf | 47 ++++ quadlets/modules/rabbitmq/tenant/variables.tf | 44 ++++ quadlets/modules/tmail-web/main.tf | 97 +++++++ quadlets/modules/zot/main.tf | 58 +++++ 32 files changed, 2715 insertions(+) create mode 100644 quadlets/modules/affine/main.tf create mode 100644 quadlets/modules/airsonic-advanced/main.tf create mode 100644 quadlets/modules/beets/main.tf create mode 100644 quadlets/modules/deeptutor/main.tf create mode 100644 quadlets/modules/documentdb/main.tf create mode 100644 quadlets/modules/forgejo/main.tf create mode 100644 quadlets/modules/gonic/main.tf create mode 100644 quadlets/modules/mopidy/main.tf create mode 100644 quadlets/modules/navidrome/main.tf create mode 100644 quadlets/modules/opensign/main.tf create mode 100644 quadlets/modules/plane/admin/main.tf create mode 100644 quadlets/modules/plane/api/main.tf create mode 100644 quadlets/modules/plane/beat-worker/main.tf create mode 100644 quadlets/modules/plane/live/main.tf create mode 100644 quadlets/modules/plane/main.tf create mode 100644 quadlets/modules/plane/migrator/main.tf create mode 100644 quadlets/modules/plane/space/main.tf create mode 100644 quadlets/modules/plane/web/main.tf create mode 100644 quadlets/modules/plane/worker/main.tf create mode 100644 quadlets/modules/postgres/main.tf create mode 100644 quadlets/modules/postgres/tenant/README.md create mode 100644 quadlets/modules/postgres/tenant/main.tf create mode 100644 quadlets/modules/postgres/tenant/outputs.tf create mode 100644 quadlets/modules/postgres/tenant/variables.tf create mode 100644 quadlets/modules/qdrant/main.tf create mode 100644 quadlets/modules/rabbitmq/main.tf create mode 100644 quadlets/modules/rabbitmq/tenant/README.md create mode 100644 quadlets/modules/rabbitmq/tenant/main.tf create mode 100644 quadlets/modules/rabbitmq/tenant/outputs.tf create mode 100644 quadlets/modules/rabbitmq/tenant/variables.tf create mode 100644 quadlets/modules/tmail-web/main.tf create mode 100644 quadlets/modules/zot/main.tf diff --git a/quadlets/modules/affine/main.tf b/quadlets/modules/affine/main.tf new file mode 100644 index 0000000..ebd97a9 --- /dev/null +++ b/quadlets/modules/affine/main.tf @@ -0,0 +1,82 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +variable "postgres_password" { + type = string + sensitive = true +} + +module "psql_db" { + source = "../postgres/tenant" + + tenant_name = "affine" + admin_username = "postgres" + admin_password = var.postgres_password + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path +} + +module "affine" { + source = "../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + restart_policy = "never" + + app_name = "affine" + image = "ghcr.io/toeverything/affine:stable" + ports = ["3020:3010"] + volumes = [ + "/opt/storage/data/affine/storage:/root/.affine/storage:Z", + "/opt/storage/data/affine/config:/root/.affine/config:Z" + ] + environment = { + DATABASE_URL = "postgresql://affine:${module.psql_db.password}@systemd-postgres:5432/affine" + REDIS_SERVER_HOST = "systemd-valkey" + AFFINE_INDEXER_ENABLED = "false" + } + command = [ + "sh", + "-c", + "\"node ./scripts/self-host-predeploy.js && node ./dist/main.js\"" + ] + healthcmd = "curl -f http://localhost:3010 || exit 1" + + haproxy_services = [ + { + name = "affine" + domain = "affine.${var.server_domain}" + port = "3020" + host = "127.0.0.1" + tls = false + } + ] + + depends_on_services = ["postgres.service", "valkey.service"] +} + +output "app_urls" { + value = module.affine.app_urls +} + +output "installed" { + value = true + depends_on = [module.affine.installed] +} diff --git a/quadlets/modules/airsonic-advanced/main.tf b/quadlets/modules/airsonic-advanced/main.tf new file mode 100644 index 0000000..2070869 --- /dev/null +++ b/quadlets/modules/airsonic-advanced/main.tf @@ -0,0 +1,60 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +module "airsonic-advanced" { + source = "../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + app_name = "airsonic-advanced" + image = "lscr.io/linuxserver/airsonic-advanced:latest" + ports = ["4040:4040"] + volumes = [ + "/opt/storage/data/airsonic-advanced:/config:Z", + "/opt/storage/media/music:/music:Z", + ] + + environment = { + PUID="1001" + PGID="1001" + TZ="Etc/UTC" + #JAVA_OPTS="-Dserver.address=127.0.0.1" + } + + haproxy_services = [ + { + name = "airsonic-advanced" + domain = "airsonic.${var.server_domain}" + port = "4040" + host = "127.0.0.1" + tls = false + }, + ] + +} + +output "app_urls" { + value = module.airsonic-advanced.app_urls +} + +output "installed" { + value = true + depends_on = [module.airsonic-advanced.installed] +} diff --git a/quadlets/modules/beets/main.tf b/quadlets/modules/beets/main.tf new file mode 100644 index 0000000..e33fee6 --- /dev/null +++ b/quadlets/modules/beets/main.tf @@ -0,0 +1,59 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +module "beets" { + source = "../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + app_name = "beets" + image = "lscr.io/linuxserver/beets:latest" + ports = ["8337:8337"] + volumes = [ + "/opt/storage/data/beets/config:/config:Z", + "/opt/storage/media/music:/music:z", + "/opt/storage/media/downloads:/downloads:z" + ] + + environment = { + PUID = "1001" + PGID = "1001" + TZ = "Europe/Amsterdam" + } + + haproxy_services = [ + { + name = "beets" + domain = "beets.${var.server_domain}" + port = "8337" + host = "127.0.0.1" + tls = false + } + ] +} + +output "app_urls" { + value = module.beets.app_urls +} + +output "installed" { + value = true + depends_on = [module.beets.installed] +} \ No newline at end of file diff --git a/quadlets/modules/deeptutor/main.tf b/quadlets/modules/deeptutor/main.tf new file mode 100644 index 0000000..ab0a565 --- /dev/null +++ b/quadlets/modules/deeptutor/main.tf @@ -0,0 +1,98 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +module "deeptutor" { + source = "../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + app_name = "deeptutor" + image = "ghcr.io/hkuds/deeptutor:latest" + ports = ["8001:8001", "3782:3782"] + volumes = [ + "/opt/storage/data/deeptutor/config:/app/config:Z", + "/opt/storage/data/deeptutor/user:/app/data/user:Z", + "/opt/storage/data/deeptutor/knowledge_bases:/app/data/knowledge_bases:Z", + ] + + environment = { + # LLM Configuration (Required - set these in terraform.tfvars) + LLM_BINDING = "openai" + LLM_MODEL = "deepseek/deepseek-v3.2" + LLM_BINDING_API_KEY = "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" + LLM_BINDING_HOST = "https://openrouter.ai/api/v1" + DISABLE_SSL_VERIFY = "false" + + # Embedding Configuration (Required for Knowledge Base) + EMBEDDING_BINDING = "openai" + EMBEDDING_MODEL = "text-embedding-3-large" + EMBEDDING_BINDING_API_KEY = "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" + EMBEDDING_BINDING_HOST = "https://openrouter.ai/api/v1" + EMBEDDING_DIM = "3072" + EMBEDDING_MAX_TOKENS = "8192" + + # TTS Configuration (Optional) + TTS_MODEL = "" + TTS_API_KEY = "" + TTS_URL = "" + TTS_VOICE = "alloy" + + # Web Search Configuration (Optional) + PERPLEXITY_API_KEY = "" + + # Logging Configuration + RAG_TOOL_MODULE_LOG_LEVEL = "INFO" + + # Service Ports + BACKEND_PORT = "8001" + FRONTEND_PORT = "3782" + + # External API URL (for browser access if different from localhost) + NEXT_PUBLIC_API_BASE_EXTERNAL = "https://deeptutor-api.${var.server_domain}" + } + + healthcmd = "curl -f http://localhost:8001/ || exit 1" + + haproxy_services = [ + { + name = "deeptutor-frontend" + domain = "deeptutor.${var.server_domain}" + port = "3782" + host = "127.0.0.1" + tls = false + }, + { + name = "deeptutor-api" + domain = "deeptutor-api.${var.server_domain}" + port = "8001" + host = "127.0.0.1" + tls = false + } + ] +} + +output "app_urls" { + value = module.deeptutor.app_urls +} + +output "installed" { + value = true + depends_on = [module.deeptutor.installed] +} diff --git a/quadlets/modules/documentdb/main.tf b/quadlets/modules/documentdb/main.tf new file mode 100644 index 0000000..29dab78 --- /dev/null +++ b/quadlets/modules/documentdb/main.tf @@ -0,0 +1,59 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +resource "random_password" "username" { + length = 32 + special = false +} + +resource "random_password" "password" { + length = 32 + special = false +} + +module "redis" { + source = "../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + app_name = "documentdb" + image = "ghcr.io/documentdb/documentdb/documentdb-local:latest" + ports = ["27017:27017"] + volumes = ["/opt/storage/data/documentdb:/data:Z"] + environment = { + USERNAME = random_password.username.result + PASSWORD = random_password.password.result + } +} + +output "app_urls" { + value = module.redis.app_urls +} + +output "installed" { + value = true + depends_on = [module.redis.installed] +} + +output "username" { + value = random_password.username.result + sensitive = true +} + +output "password" { + value = random_password.password.result + sensitive = true +} \ No newline at end of file diff --git a/quadlets/modules/forgejo/main.tf b/quadlets/modules/forgejo/main.tf new file mode 100644 index 0000000..148d8fb --- /dev/null +++ b/quadlets/modules/forgejo/main.tf @@ -0,0 +1,117 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +variable "postgres_password" { + type = string + sensitive = true +} + +resource "random_password" "forgejo_secret_key" { + length = 64 + special = false +} + +resource "random_password" "forgejo_internal_token" { + length = 64 + special = false +} + +resource "random_password" "forgejo_jwt_secret" { + length = 32 + special = false +} + +module "psql_db" { + source = "../postgres/tenant" + + tenant_name = "forgejo" + admin_username = "postgres" + admin_password = var.postgres_password + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path +} + +module "forgejo" { + source = "../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + app_name = "forgejo" + image = "codeberg.org/forgejo/forgejo:13.0" + ports = ["3000:3000", "2222:22"] + volumes = [ + "/opt/storage/data/forgejo:/data:Z", + "/etc/localtime:/etc/localtime:ro" + ] + + environment = { + USER_UID = "1000" + USER_GID = "1000" + FORGEJO__database__DB_TYPE = "postgres" + FORGEJO__database__HOST = "systemd-postgres:5432" + FORGEJO__database__NAME = "forgejo" + FORGEJO__database__USER = module.psql_db.username + FORGEJO__database__PASSWD = module.psql_db.password + FORGEJO__server__DOMAIN = "forgejo.${var.server_domain}" + FORGEJO__server__ROOT_URL = "https://forgejo.${var.server_domain}/" + FORGEJO__server__SSH_DOMAIN = "forgejo.${var.server_domain}" + FORGEJO__server__SSH_PORT = "2222" + FORGEJO__security__INSTALL_LOCK = "true" + FORGEJO__security__SECRET_KEY = random_password.forgejo_secret_key.result + FORGEJO__security__INTERNAL_TOKEN = random_password.forgejo_internal_token.result + FORGEJO__oauth2__JWT_SECRET = random_password.forgejo_jwt_secret.result + } + + healthcmd = "curl -f http://localhost:3000/api/v1/version || exit 1" + + haproxy_services = [ + { + name = "forgejo" + domain = "forgejo.${var.server_domain}" + port = "3000" + host = "127.0.0.1" + tls = false + } + ] +} + +output "app_urls" { + value = module.forgejo.app_urls +} + +output "installed" { + value = true + depends_on = [module.forgejo.installed] +} + +output "secret_key" { + value = random_password.forgejo_secret_key.result + sensitive = true +} + +output "internal_token" { + value = random_password.forgejo_internal_token.result + sensitive = true +} + +output "jwt_secret" { + value = random_password.forgejo_jwt_secret.result + sensitive = true +} diff --git a/quadlets/modules/gonic/main.tf b/quadlets/modules/gonic/main.tf new file mode 100644 index 0000000..d134848 --- /dev/null +++ b/quadlets/modules/gonic/main.tf @@ -0,0 +1,60 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +module "gonic" { + source = "../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + app_name = "gonic" + image = "docker.io/sentriz/gonic:latest" + ports = ["4747:80"] + volumes = [ + "/opt/storage/data/gonic:/config:Z", + "/opt/storage/media/music:/music:Z", + "/opt/storage/media/podcasts:/podcasts:Z", + "/opt/storage/media/playlists:/playlists:Z", + "/opt/storage/cache/gonic:/cache:Z", + ] + + environment = { + TZ="Etc/UTC" + } + + haproxy_services = [ + { + name = "gonic" + domain = "gonic.${var.server_domain}" + port = "4747" + host = "127.0.0.1" + tls = false + }, + ] + +} + +output "app_urls" { + value = module.gonic.app_urls +} + +output "installed" { + value = true + depends_on = [module.gonic.installed] +} diff --git a/quadlets/modules/mopidy/main.tf b/quadlets/modules/mopidy/main.tf new file mode 100644 index 0000000..cbf98fd --- /dev/null +++ b/quadlets/modules/mopidy/main.tf @@ -0,0 +1,59 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +module "mopidy" { + source = "../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + app_name = "mopidy" + image = "docker.io/ivdata/mopidy" + ports = ["6600:6600", "6680:6680"] + volumes = [ + "/opt/storage/data/mopidy:/var/lib/mopidy:Z", + "/opt/storage/data/mopidy/mopidy.conf:/home/mopidy/.config/mopidy/mopidy.conf:Z", + "/opt/storage/media/music:/media/music:ro,Z", + ] + + # environment = { + # SPOTIFY_CLIENT_ID="ff4a141c-cb52-4569-9848-c0468da66bd1" + # SPOTIFY_CLIENT_SECRET="VVXVtoiY8DanFLl9BnpHvLygXCchboHoEewoALTHIaA=" + # } + + haproxy_services = [ + { + name = "mopidy" + domain = "mopidy.${var.server_domain}" + port = "6680" + host = "127.0.0.1" + tls = false + }, + ] + +} + +output "app_urls" { + value = module.mopidy.app_urls +} + +output "installed" { + value = true + depends_on = [module.mopidy.installed] +} diff --git a/quadlets/modules/navidrome/main.tf b/quadlets/modules/navidrome/main.tf new file mode 100644 index 0000000..c1a1174 --- /dev/null +++ b/quadlets/modules/navidrome/main.tf @@ -0,0 +1,58 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +module "navidrome" { + source = "../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + app_name = "navidrome" + image = "docker.io/deluan/navidrome:latest" + ports = ["4533:4533"] + volumes = [ + "/opt/storage/data/navidrome:/data:Z", + "/opt/storage/media/music:/music:ro,Z", + ] + + environment = { + ND_LOGLEVEL="info" + ND_ENABLEINSIGHTSCOLLECTOR="false" + } + + haproxy_services = [ + { + name = "navidrome" + domain = "navidrome.${var.server_domain}" + port = "4533" + host = "127.0.0.1" + tls = false + }, + ] + +} + +output "app_urls" { + value = module.navidrome.app_urls +} + +output "installed" { + value = true + depends_on = [module.navidrome.installed] +} diff --git a/quadlets/modules/opensign/main.tf b/quadlets/modules/opensign/main.tf new file mode 100644 index 0000000..9197625 --- /dev/null +++ b/quadlets/modules/opensign/main.tf @@ -0,0 +1,111 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +# Generate random keys for esign +resource "random_password" "app_id" { + length = 12 + special = false +} + +resource "random_password" "master_key" { + length = 32 + special = true +} + +module "esign" { + source = "../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + app_name = "esign" + image = "docker.io/esign/esignserver:main" + ports = ["8180:8080"] + volumes = ["/opt/storage/data/esign:/usr/src/app/files:Z"] + environment = { + # Node environment + NODE_ENV = "production" + + # App identifiers + APP_ID = random_password.app_id.result + REACT_APP_APPID = random_password.app_id.result + appName = "Four Lights - eSign" + MASTER_KEY = random_password.master_key.result + + # Frontend config + PUBLIC_URL = "https://esign.${var.server_domain}" + REACT_APP_SERVERURL = "https://esign.${var.server_domain}/api/app" + GENERATE_SOURCEMAP = "false" + + # Backend API config + SERVER_URL = "https://esign.${var.server_domain}/api/app" + PARSE_MOUNT = "/app" + + # MongoDB connection settings + MONGODB_URI = "mongodb://systemd-documentdb:27017/esign" + + # MinIO/S3 storage configuration + DO_SPACE = "esign" + DO_ENDPOINT = "systemd-minio:9000" + DO_BASEURL = "https://esign.${var.server_domain}/minio" + DO_ACCESS_KEY_ID = "esign" + DO_SECRET_ACCESS_KEY = "esign" + DO_REGION = "us-east-1" + + # Email config (commented out - configure as needed) + # SMTP_ENABLE = "" + # SMTP_HOST = "" + # SMTP_PORT = "" + # SMTP_USER_EMAIL = "" + # SMTP_PASS = "" + + # Document signing (optional) + PFX_BASE64 = "" + PASS_PHRASE = "" + } + + haproxy_services = [ + { + name = "esign" + domain = "esign.${var.server_domain}" + port = "8180" + host = "127.0.0.1" + tls = false + } + ] +} + +output "app_urls" { + value = module.esign.app_urls +} + +output "app_id" { + value = random_password.app_id.result + sensitive = true +} + +output "master_key" { + value = random_password.master_key.result + sensitive = true +} + +output "installed" { + value = true + depends_on = [module.esign.installed] +} diff --git a/quadlets/modules/plane/admin/main.tf b/quadlets/modules/plane/admin/main.tf new file mode 100644 index 0000000..12aa075 --- /dev/null +++ b/quadlets/modules/plane/admin/main.tf @@ -0,0 +1,58 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +module "admin" { + source = "../../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + app_name = "plane-admin" + image = "docker.io/makeplane/plane-admin:v1.0.0" + ports = ["3010:3000"] + environment = { + # API settings + NEXT_PUBLIC_API_BASE_URL = "https://plane.${var.server_domain}/api" + + # Web settings + NEXT_PUBLIC_WEB_URL = "https://plane.${var.server_domain}/god-mode" + + # Environment + NODE_ENV = "production" + } + + haproxy_services = [ + { + name = "plane-admin" + domain = "plane.${var.server_domain}/god-mode/" + port = "3010" + host = "127.0.0.1" + tls = false + } + ] +} + +output "app_urls" { + value = module.admin.app_urls +} + +output "installed" { + value = true + depends_on = [module.admin.installed] +} \ No newline at end of file diff --git a/quadlets/modules/plane/api/main.tf b/quadlets/modules/plane/api/main.tf new file mode 100644 index 0000000..6551e0c --- /dev/null +++ b/quadlets/modules/plane/api/main.tf @@ -0,0 +1,182 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +# Accept credentials from parent Plane module +variable "db_password" { + type = string + sensitive = true +} + +variable "redis_password" { + type = string + sensitive = true +} + +variable "rabbitmq_password" { + type = string + sensitive = true +} + +variable "minio_access_key" { + type = string + sensitive = true +} + +variable "minio_secret_key" { + type = string + sensitive = true +} + +variable "secret_key" { + type = string + sensitive = true +} + +module "api" { + source = "../../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + restart_policy = "never" + + app_name = "plane-api" + image = "docker.io/makeplane/plane-backend:v1.0.0" + ports = ["3011:8000"] + volumes = [ + "/opt/storage/data/plane-api:/var/www/plane:Z" + ] + environment = { + # Database settings + DATABASE_URL = "postgresql://plane:${var.db_password}@systemd-postgres:5432/plane" + POSTGRES_USER = "plane" + POSTGRES_PASSWORD = var.db_password + POSTGRES_DB = "plane" + + # Redis settings + REDIS_URL = "redis://:${var.redis_password}@systemd-valkey:6379/0" + REDIS_HOST = "systemd-valkey" + REDIS_PORT = "6379" + + # RabbitMQ settings + RABBITMQ_URL = "amqp://plane:${var.rabbitmq_password}@systemd-rabbitmq:5672/plane" + RABBITMQ_HOST = "systemd-rabbitmq" + RABBITMQ_PORT = "5672" + RABBITMQ_USER = "plane" + RABBITMQ_PASSWORD = var.rabbitmq_password + RABBITMQ_VHOST = "plane" + + # MinIO/S3 settings + AWS_S3_ENDPOINT_URL = "http://systemd-minio:9000" + AWS_ACCESS_KEY_ID = var.minio_access_key + AWS_SECRET_ACCESS_KEY = var.minio_secret_key + AWS_S3_BUCKET_NAME = "plane" + AWS_REGION = "us-east-1" + USE_MINIO = "1" + + # Application settings + SECRET_KEY = var.secret_key + DEBUG = "0" + ENVIRONMENT = "production" + + # Web settings + WEB_URL = "https://plane.${var.server_domain}/api/" + ADMIN_BASE_URL = "https://plane.${var.server_domain}/god-mode/" + SPACE_BASE_URL = "https://plane.${var.server_domain}/spaces/" + APP_BASE_URL = "https://plane.${var.server_domain}/" + LIVE_BASE_URL = "https://plane.${var.server_domain}/live/" + + # File upload settings + FILE_SIZE_LIMIT = "5242880" + + # Rate limiting + API_KEY_RATE_LIMIT = "60/minute" + + # Workers + GUNICORN_WORKERS=3 + + # Email settings (optional - can be configured later) + # EMAIL_HOST = "" + # EMAIL_PORT = "" + # EMAIL_HOST_USER = "" + # EMAIL_HOST_PASSWORD = "" + # EMAIL_USE_TLS = "1" + + # OpenAI/GPT settings (optional) + # OPENAI_API_KEY = "" + # GPT_ENGINE = "gpt-3.5-turbo" + } + command = ["./bin/docker-entrypoint-api.sh"] + + haproxy_services = [ + { + name = "plane-api" + domain = "plane.${var.server_domain}/api/" + port = "3011" + host = "127.0.0.1" + tls = false + }, + { + name = "plane-auth" + domain = "plane.${var.server_domain}/auth/" + port = "3011" + host = "127.0.0.1" + tls = false + } + ] +} + +output "app_urls" { + value = module.api.app_urls +} + +output "installed" { + value = true + depends_on = [module.api.installed] +} + +output "db_password" { + value = var.db_password + sensitive = true +} + +output "redis_password" { + value = var.redis_password + sensitive = true +} + +output "rabbitmq_password" { + value = var.rabbitmq_password + sensitive = true +} + +output "minio_access_key" { + value = var.minio_access_key + sensitive = true +} + +output "minio_secret_key" { + value = var.minio_secret_key + sensitive = true +} + +output "secret_key" { + value = var.secret_key + sensitive = true +} \ No newline at end of file diff --git a/quadlets/modules/plane/beat-worker/main.tf b/quadlets/modules/plane/beat-worker/main.tf new file mode 100644 index 0000000..473844a --- /dev/null +++ b/quadlets/modules/plane/beat-worker/main.tf @@ -0,0 +1,112 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +variable "db_password" { + type = string + sensitive = true +} + +variable "redis_password" { + type = string + sensitive = true +} + +variable "rabbitmq_password" { + type = string + sensitive = true +} + +variable "minio_access_key" { + type = string + sensitive = true +} + +variable "minio_secret_key" { + type = string + sensitive = true +} + +variable "secret_key" { + type = string + sensitive = true +} + +module "beat-worker" { + source = "../../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + restart_policy = "never" + + app_name = "plane-beat-worker" + image = "docker.io/makeplane/plane-backend:v1.0.0" + volumes = [ + "/opt/storage/data/plane-beat-worker:/var/www/plane:Z" + ] + environment = { + # Database settings + DATABASE_URL = "postgresql://plane:${var.db_password}@systemd-postgres:5432/plane" + POSTGRES_USER = "plane" + POSTGRES_PASSWORD = var.db_password + POSTGRES_DB = "plane" + + # Redis settings + REDIS_URL = "redis://:${var.redis_password}@systemd-valkey:6379/0" + REDIS_HOST = "systemd-valkey" + REDIS_PORT = "6379" + + # RabbitMQ settings + RABBITMQ_URL = "amqp://plane:${var.rabbitmq_password}@systemd-rabbitmq:5672/plane" + RABBITMQ_HOST = "systemd-rabbitmq" + RABBITMQ_PORT = "5672" + RABBITMQ_USER = "plane" + RABBITMQ_PASSWORD = var.rabbitmq_password + RABBITMQ_VHOST = "plane" + + # MinIO/S3 settings + AWS_S3_ENDPOINT_URL = "http://systemd-minio:9000" + AWS_ACCESS_KEY_ID = var.minio_access_key + AWS_SECRET_ACCESS_KEY = var.minio_secret_key + AWS_S3_BUCKET_NAME = "plane-uploads" + AWS_REGION = "us-east-1" + USE_MINIO = "1" + + # Application settings + SECRET_KEY = var.secret_key + DEBUG = "0" + ENVIRONMENT = "production" + + # Web settings + WEB_URL = "https://plane.${var.server_domain}" + API_BASE_URL = "https://plane.${var.server_domain}/api" + + # File upload settings + FILE_SIZE_LIMIT = "5242880" + + # Rate limiting + API_KEY_RATE_LIMIT = "60/minute" + } + command = ["./bin/docker-entrypoint-beat.sh"] +} + +output "installed" { + value = true + depends_on = [module.beat-worker.installed] +} \ No newline at end of file diff --git a/quadlets/modules/plane/live/main.tf b/quadlets/modules/plane/live/main.tf new file mode 100644 index 0000000..270296d --- /dev/null +++ b/quadlets/modules/plane/live/main.tf @@ -0,0 +1,60 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +module "live" { + source = "../../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + app_name = "plane-live" + image = "docker.io/makeplane/plane-live:v1.0.0" + ports = ["3012:3000"] + environment = { + # API settings + NEXT_PUBLIC_API_BASE_URL = "https://plane.${var.server_domain}/api/" + + # Web settings + NEXT_PUBLIC_WEB_URL = "https://plane.${var.server_domain}/live/" + + # Environment + NODE_ENV = "production" + } + + restart_policy = "never" + + haproxy_services = [ + { + name = "plane-live" + domain = "plane.${var.server_domain}/live/" + port = "3012" + host = "127.0.0.1" + tls = false + } + ] +} + +output "app_urls" { + value = module.live.app_urls +} + +output "installed" { + value = true + depends_on = [module.live.installed] +} \ No newline at end of file diff --git a/quadlets/modules/plane/main.tf b/quadlets/modules/plane/main.tf new file mode 100644 index 0000000..23f970c --- /dev/null +++ b/quadlets/modules/plane/main.tf @@ -0,0 +1,239 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +# Credentials from existing services +variable "postgres_password" { + type = string + sensitive = true +} + +variable "minio_server" { + type = string + sensitive = true +} + +variable "minio_access_key" { + type = string + sensitive = true +} + +variable "minio_secret_key" { + type = string + sensitive = true +} + +variable "rabbitmq_username" { + type = string + sensitive = true +} + +variable "rabbitmq_password" { + type = string + sensitive = true +} + +# Generate Plane-specific credentials +resource "random_password" "redis_password" { + length = 32 + special = false +} + +resource "random_password" "secret_key" { + length = 64 + special = true +} + +module "psql_db" { + source = "../postgres/tenant" + + tenant_name = "plane" + admin_username = "postgres" + admin_password = var.postgres_password + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path +} + +module "aqmp_db" { + source = "../rabbitmq/tenant" + + tenant_name = "plane" + admin_username = var.rabbitmq_username + admin_password = var.rabbitmq_password + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path +} + +module "minio" { + source = "../minio/tenant" + + name = "plane" + server = var.minio_server + access_key = var.minio_access_key + secret_key = var.minio_secret_key + tls = true +} + +# Deploy the core API service first +module "api" { + source = "./api" + wait_on = var.wait_on + server_ip = var.server_ip + server_domain = var.server_domain + ssh_private_key_path = var.ssh_private_key_path + + # Use credentials from existing services + redis_password = "" #random_password.redis_password.result + db_password = module.psql_db.password + rabbitmq_password = module.aqmp_db.password + + minio_access_key = module.minio.access_key + minio_secret_key = module.minio.secret_key + secret_key = random_password.secret_key.result +} + +# Run database migrations after API is ready +module "migrator" { + source = "./migrator" + wait_on = module.api.installed + server_ip = var.server_ip + server_domain = var.server_domain + ssh_private_key_path = var.ssh_private_key_path + + # Use credentials from API module + db_password = module.api.db_password + redis_password = module.api.redis_password + rabbitmq_password = module.api.rabbitmq_password + minio_access_key = module.minio.access_key + minio_secret_key = module.minio.secret_key + secret_key = module.api.secret_key +} + +# Start background workers after migrations +module "worker" { + source = "./worker" + wait_on = module.migrator.installed + server_ip = var.server_ip + server_domain = var.server_domain + ssh_private_key_path = var.ssh_private_key_path + + # Use credentials from API module + db_password = module.api.db_password + redis_password = module.api.redis_password + rabbitmq_password = module.api.rabbitmq_password + minio_access_key = module.minio.access_key + minio_secret_key = module.minio.secret_key + secret_key = module.api.secret_key +} + +module "beat-worker" { + source = "./beat-worker" + wait_on = module.migrator.installed + server_ip = var.server_ip + server_domain = var.server_domain + ssh_private_key_path = var.ssh_private_key_path + + # Use credentials from API module + db_password = module.api.db_password + redis_password = module.api.redis_password + rabbitmq_password = module.api.rabbitmq_password + minio_access_key = module.minio.access_key + minio_secret_key = module.minio.secret_key + secret_key = module.api.secret_key +} + +# Start web interfaces after API is ready +module "web" { + source = "./web" + wait_on = module.api.installed + server_ip = var.server_ip + server_domain = var.server_domain + ssh_private_key_path = var.ssh_private_key_path +} + +module "admin" { + source = "./admin" + wait_on = module.api.installed + server_ip = var.server_ip + server_domain = var.server_domain + ssh_private_key_path = var.ssh_private_key_path +} + +module "space" { + source = "./space" + wait_on = module.api.installed + server_ip = var.server_ip + server_domain = var.server_domain + ssh_private_key_path = var.ssh_private_key_path +} + +module "live" { + source = "./live" + wait_on = module.api.installed + server_ip = var.server_ip + server_domain = var.server_domain + ssh_private_key_path = var.ssh_private_key_path +} + +# Aggregate all app URLs +locals { + app_urls = { + web = module.web.app_urls + admin = module.admin.app_urls + space = module.space.app_urls + live = module.live.app_urls + api = module.api.app_urls + } +} + +output "app_urls" { + value = local.app_urls + description = "All Plane application URLs" +} + +output "installed" { + value = true + depends_on = [ + module.api.installed, + module.migrator.installed, + module.worker.installed, + module.beat-worker.installed, + module.web.installed, + module.admin.installed, + module.space.installed, + module.live.installed + ] + description = "Whether all Plane services are installed" +} + +output "credentials" { + value = { + db_password = module.api.db_password + redis_password = module.api.redis_password + minio_access_key = module.api.minio_access_key + minio_secret_key = module.api.minio_secret_key + rabbitmq_password = module.api.rabbitmq_password + secret_key = module.api.secret_key + } + sensitive = true + description = "All Plane service credentials" +} + +output "main_url" { + value = "https://plane.${var.server_domain}" + description = "Main Plane application URL" +} \ No newline at end of file diff --git a/quadlets/modules/plane/migrator/main.tf b/quadlets/modules/plane/migrator/main.tf new file mode 100644 index 0000000..a93c218 --- /dev/null +++ b/quadlets/modules/plane/migrator/main.tf @@ -0,0 +1,112 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +variable "db_password" { + type = string + sensitive = true +} + +variable "redis_password" { + type = string + sensitive = true +} + +variable "rabbitmq_password" { + type = string + sensitive = true +} + +variable "minio_access_key" { + type = string + sensitive = true +} + +variable "minio_secret_key" { + type = string + sensitive = true +} + +variable "secret_key" { + type = string + sensitive = true +} + +module "migrator" { + source = "../../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + restart_policy = "never" + + app_name = "plane-migrator" + image = "docker.io/makeplane/plane-backend:v1.0.0" + volumes = [ + "/opt/storage/data/plane-migrator:/var/www/plane:Z" + ] + environment = { + # Database settings + DATABASE_URL = "postgresql://plane:${var.db_password}@systemd-postgres:5432/plane" + POSTGRES_USER = "plane" + POSTGRES_PASSWORD = var.db_password + POSTGRES_DB = "plane" + + # Redis settings + REDIS_URL = "redis://:${var.redis_password}@systemd-valkey:6379/0" + REDIS_HOST = "systemd-valkey" + REDIS_PORT = "6379" + + # RabbitMQ settings + RABBITMQ_URL = "amqp://plane:${var.rabbitmq_password}@systemd-rabbitmq:5672/plane" + RABBITMQ_HOST = "systemd-rabbitmq" + RABBITMQ_PORT = "5672" + RABBITMQ_USER = "plane" + RABBITMQ_PASSWORD = var.rabbitmq_password + RABBITMQ_VHOST = "plane" + + # MinIO/S3 settings + AWS_S3_ENDPOINT_URL = "http://systemd-minio:9000" + AWS_ACCESS_KEY_ID = var.minio_access_key + AWS_SECRET_ACCESS_KEY = var.minio_secret_key + AWS_S3_BUCKET_NAME = "plane-uploads" + AWS_REGION = "us-east-1" + USE_MINIO = "1" + + # Application settings + SECRET_KEY = var.secret_key + DEBUG = "0" + ENVIRONMENT = "production" + + # Web settings + WEB_URL = "https://plane.${var.server_domain}" + API_BASE_URL = "https://plane.${var.server_domain}/api" + + # File upload settings + FILE_SIZE_LIMIT = "5242880" + + # Rate limiting + API_KEY_RATE_LIMIT = "60/minute" + } + command = ["./bin/docker-entrypoint-migrator.sh"] +} + +output "installed" { + value = true + depends_on = [module.migrator.installed] +} \ No newline at end of file diff --git a/quadlets/modules/plane/space/main.tf b/quadlets/modules/plane/space/main.tf new file mode 100644 index 0000000..51f57e8 --- /dev/null +++ b/quadlets/modules/plane/space/main.tf @@ -0,0 +1,60 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +module "space" { + source = "../../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + restart_policy = "never" + + app_name = "plane-space" + image = "docker.io/makeplane/plane-space:v1.0.0" + ports = ["3013:3000"] + environment = { + # API settings + NEXT_PUBLIC_API_BASE_URL = "https://plane.${var.server_domain}/api" + + # Web settings + NEXT_PUBLIC_WEB_URL = "https://plane.${var.server_domain}/spaces" + + # Environment + NODE_ENV = "production" + } + + haproxy_services = [ + { + name = "plane-space" + domain = "plane.${var.server_domain}/spaces/" + port = "3013" + host = "127.0.0.1" + tls = false + } + ] +} + +output "app_urls" { + value = module.space.app_urls +} + +output "installed" { + value = true + depends_on = [module.space.installed] +} \ No newline at end of file diff --git a/quadlets/modules/plane/web/main.tf b/quadlets/modules/plane/web/main.tf new file mode 100644 index 0000000..a072683 --- /dev/null +++ b/quadlets/modules/plane/web/main.tf @@ -0,0 +1,64 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +module "web" { + source = "../../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + restart_policy = "never" + + app_name = "plane-web" + image = "docker.io/makeplane/plane-frontend:v1.0.0" + ports = ["3014:3000"] + environment = { + # API settings + NEXT_PUBLIC_API_BASE_URL = "https://plane.${var.server_domain}/api" + + # Web settings + NEXT_PUBLIC_WEB_BASE_URL = "https://plane.${var.server_domain}" + + NEXT_PUBLIC_ADMIN_BASE_URL = "https://plane.${var.server_domain}/god-mode" + NEXT_PUBLIC_SPACE_BASE_URL = "https://plane.${var.server_domain}/spaces" + NEXT_PUBLIC_LIVE_BASE_URL = "https://plane.${var.server_domain}/live" + + # Environment + NODE_ENV = "production" + } + + haproxy_services = [ + { + name = "plane-web" + domain = "plane.${var.server_domain}" + port = "3014" + host = "127.0.0.1" + tls = false + } + ] +} + +output "app_urls" { + value = module.web.app_urls +} + +output "installed" { + value = true + depends_on = [module.web.installed] +} \ No newline at end of file diff --git a/quadlets/modules/plane/worker/main.tf b/quadlets/modules/plane/worker/main.tf new file mode 100644 index 0000000..1cc7dba --- /dev/null +++ b/quadlets/modules/plane/worker/main.tf @@ -0,0 +1,113 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +variable "db_password" { + type = string + sensitive = true +} + +variable "redis_password" { + type = string + sensitive = true +} + +variable "rabbitmq_password" { + type = string + sensitive = true +} + +variable "minio_access_key" { + type = string + sensitive = true +} + +variable "minio_secret_key" { + type = string + sensitive = true +} + +variable "secret_key" { + type = string + sensitive = true +} + +module "worker" { + source = "../../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + restart_policy = "never" + + app_name = "plane-worker" + image = "docker.io/makeplane/plane-backend:v1.0.0" + volumes = [ + "/opt/storage/data/plane-worker:/var/www/plane:Z" + ] + environment = { + # Database settings + DATABASE_URL = "postgresql://plane:${var.db_password}@systemd-postgres:5432/plane" + POSTGRES_USER = "plane" + POSTGRES_PASSWORD = var.db_password + POSTGRES_DB = "plane" + PGDATA = "/var/lib/postgresql/data" + + # Redis settings + REDIS_URL = "redis://:${var.redis_password}@systemd-valkey:6379/0" + REDIS_HOST = "systemd-valkey" + REDIS_PORT = "6379" + + # RabbitMQ settings + RABBITMQ_URL = "amqp://plane:${var.rabbitmq_password}@systemd-rabbitmq:5672/plane" + RABBITMQ_HOST = "systemd-rabbitmq" + RABBITMQ_PORT = "5672" + RABBITMQ_USER = "plane" + RABBITMQ_PASSWORD = var.rabbitmq_password + RABBITMQ_VHOST = "plane" + + # MinIO/S3 settings + AWS_S3_ENDPOINT_URL = "http://systemd-minio:9000" + AWS_ACCESS_KEY_ID = var.minio_access_key + AWS_SECRET_ACCESS_KEY = var.minio_secret_key + AWS_S3_BUCKET_NAME = "plane-uploads" + AWS_REGION = "us-east-1" + USE_MINIO = "1" + + # Application settings + SECRET_KEY = var.secret_key + DEBUG = "0" + ENVIRONMENT = "production" + + # Web settings + WEB_URL = "https://plane.${var.server_domain}" + API_BASE_URL = "https://plane.${var.server_domain}/api" + + # File upload settings + FILE_SIZE_LIMIT = "5242880" + + # Rate limiting + API_KEY_RATE_LIMIT = "60/minute" + } + command = ["./bin/docker-entrypoint-worker.sh"] +} + +output "installed" { + value = true + depends_on = [module.worker.installed] +} \ No newline at end of file diff --git a/quadlets/modules/postgres/main.tf b/quadlets/modules/postgres/main.tf new file mode 100644 index 0000000..4178b3a --- /dev/null +++ b/quadlets/modules/postgres/main.tf @@ -0,0 +1,48 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +resource "random_password" "password" { + length = 32 + special = false +} + +module "postgres" { + source = "../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + app_name = "postgres" + image = "docker.io/postgres:17" + ports = ["5432:5432"] + volumes = ["/opt/storage/data/postgres:/var/lib/postgresql/data:Z"] + environment = { + POSTGRES_PASSWORD = random_password.password.result + } +} + +output "app_urls" { + value = module.postgres.app_urls +} + +output "installed" { + value = true + depends_on = [module.postgres.installed] +} + +output "password" { + value = random_password.password.result + sensitive = true +} \ No newline at end of file diff --git a/quadlets/modules/postgres/tenant/README.md b/quadlets/modules/postgres/tenant/README.md new file mode 100644 index 0000000..bcb8e85 --- /dev/null +++ b/quadlets/modules/postgres/tenant/README.md @@ -0,0 +1,132 @@ +# PostgreSQL Tenant Module + +This module creates a PostgreSQL database and user (tenant) on a VPS-hosted PostgreSQL instance using SSH tunneling for secure access. + +## Features + +- Creates SSH tunnel to VPS-hosted PostgreSQL +- Creates isolated database and user for tenant +- Generates secure random password +- Outputs connection details for application use +- Supports explicit dependencies via `wait_on` + +## Requirements + +- SSH access to VPS with key-based authentication +- PostgreSQL admin credentials +- `terraform-ssh-tunnel` module (automatically downloaded) +- `cyrilgdn/postgresql` provider (automatically configured) + +## Usage + +### Basic Example + +```hcl +module "app_tenant" { + source = "./quadlets/modules/postgres/tenant" + + tenant_name = "myapp" + admin_username = "postgres" + admin_password = var.postgres_admin_password + server_ip = "203.0.113.10" + ssh_user = "fourlights" + + # Wait for PostgreSQL service to be ready + wait_on = module.postgres.installed +} + +# Use the connection details +output "app_db_connection" { + value = { + host = module.app_tenant.host + port = module.app_tenant.port + database = module.app_tenant.database + username = module.app_tenant.username + password = module.app_tenant.password + } + sensitive = true +} +``` + +### With Custom Port + +```hcl +module "custom_tenant" { + source = "./quadlets/modules/postgres/tenant" + + tenant_name = "custom_app" + admin_username = "postgres" + admin_password = var.postgres_admin_password + server_ip = "203.0.113.10" + target_port = 5433 # Custom PostgreSQL port + + wait_on = module.postgres.installed +} +``` + +### Multiple Tenants + +```hcl +module "tenant_1" { + source = "./quadlets/modules/postgres/tenant" + + tenant_name = "app1" + admin_username = "postgres" + admin_password = var.postgres_admin_password + server_ip = var.vps_ip + + wait_on = module.postgres.installed +} + +module "tenant_2" { + source = "./quadlets/modules/postgres/tenant" + + tenant_name = "app2" + admin_username = "postgres" + admin_password = var.postgres_admin_password + server_ip = var.vps_ip + + wait_on = module.postgres.installed +} +``` + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|----------| +| tenant_name | Name of the tenant (used for database name and username) | string | - | yes | +| admin_username | PostgreSQL admin username | string | "postgres" | no | +| admin_password | PostgreSQL admin password | string | - | yes | +| server_ip | VPS server IP address | string | - | yes | +| ssh_user | SSH user for connecting to VPS | string | "fourlights" | no | +| ssh_private_key_path | Path to SSH private key | string | "~/.ssh/id_rsa" | no | +| target_port | PostgreSQL port on VPS | number | 5432 | no | +| wait_on | Resources to wait on before creating tenant | any | null | no | + +## Outputs + +| Name | Description | Sensitive | +|------|-------------|-----------| +| host | PostgreSQL connection host (via SSH tunnel) | no | +| port | PostgreSQL connection port (via SSH tunnel) | no | +| database | Database name | no | +| username | Database username | no | +| password | Database password | yes | +| connection_string | Full PostgreSQL connection string | yes | +| installed | Installation complete flag | no | + +## How It Works + +1. **SSH Tunnel**: The module uses `terraform-ssh-tunnel` to create a secure SSH tunnel from your local machine to the VPS PostgreSQL instance +2. **Provider Configuration**: The PostgreSQL provider is configured to connect through the tunnel +3. **Resource Creation**: Creates a PostgreSQL role (user) and database with appropriate permissions +4. **Password Generation**: Generates a cryptographically secure random password +5. **Outputs**: Provides connection details for your application to use + +## Notes + +- The SSH tunnel is created dynamically when Terraform needs to connect +- Ensure your SSH agent is running and has the appropriate key loaded +- The database and user will have the same name as `tenant_name` +- The user is granted full privileges on their database +- Password is stored in Terraform state (sensitive) \ No newline at end of file diff --git a/quadlets/modules/postgres/tenant/main.tf b/quadlets/modules/postgres/tenant/main.tf new file mode 100644 index 0000000..aa2546f --- /dev/null +++ b/quadlets/modules/postgres/tenant/main.tf @@ -0,0 +1,81 @@ +terraform { + required_providers { + random = { + source = "hashicorp/random" + version = "~> 3.6" + } + postgresql = { + source = "cyrilgdn/postgresql" + version = "~> 1.22" + } + } +} + +# Generate random password for the tenant user +resource "random_password" "tenant_password" { + length = 32 + special = false + min_upper = 2 + min_lower = 2 + min_numeric = 2 +} + +# Create SSH tunnel to PostgreSQL on VPS +module "db_tunnel" { + source = "flaupretre/tunnel/ssh" + version = "2.3.0" + + target_host = "127.0.0.1" + target_port = var.target_port + + gateway_host = var.server_ip + gateway_user = var.ssh_user + gateway_port = 22 +} + +provider postgresql { + alias = "tunnel" + host = module.db_tunnel.host + port = module.db_tunnel.port + username = var.admin_username + password = var.admin_password + database = "postgres" + sslmode = "disable" + superuser = false +} + + +#---- Database + +resource postgresql_database db { + provider = postgresql.tunnel + + name = var.tenant_name + owner = var.tenant_name + + encoding = "UTF8" + lc_collate = "C" + lc_ctype = "C" +} + +#---- DB user/role + +resource postgresql_role role { + provider = postgresql.tunnel + + name = var.tenant_name + login = true + password = random_password.tenant_password.result +} + +#---- Grant +# SQL: grant all on .* to @'%'; + +resource postgresql_grant grant { + provider = postgresql.tunnel + + object_type = "database" + database = var.tenant_name + role = var.tenant_name + privileges = ["ALL"] +} \ No newline at end of file diff --git a/quadlets/modules/postgres/tenant/outputs.tf b/quadlets/modules/postgres/tenant/outputs.tf new file mode 100644 index 0000000..bf4864a --- /dev/null +++ b/quadlets/modules/postgres/tenant/outputs.tf @@ -0,0 +1,27 @@ +output "database" { + description = "Database name" + value = var.tenant_name +} + +output "username" { + description = "Database username" + value = var.tenant_name +} + +output "password" { + description = "Database password" + value = random_password.tenant_password.result + sensitive = true +} + +output "connection_string" { + description = "PostgreSQL connection string" + value = "postgresql://${var.tenant_name}:${random_password.tenant_password.result}@localhost:${var.target_port}/${var.tenant_name}" + sensitive = true +} + +output "installed" { + description = "Installation complete flag" + value = true + depends_on = [postgresql_role.role, postgresql_grant.grant, postgresql_database.db, random_password.tenant_password] +} \ No newline at end of file diff --git a/quadlets/modules/postgres/tenant/variables.tf b/quadlets/modules/postgres/tenant/variables.tf new file mode 100644 index 0000000..ebf3135 --- /dev/null +++ b/quadlets/modules/postgres/tenant/variables.tf @@ -0,0 +1,44 @@ +variable "tenant_name" { + description = "Name of the tenant (used for database name and username)" + type = string +} + +variable "admin_username" { + description = "PostgreSQL admin username" + type = string + default = "postgres" +} + +variable "admin_password" { + description = "PostgreSQL admin password" + type = string + sensitive = true +} + +variable "server_ip" { + description = "VPS server IP address" + type = string +} + +variable "ssh_user" { + description = "SSH user for connecting to VPS" + type = string + default = "fourlights" +} + +variable "ssh_private_key_path" { + description = "Path to SSH private key for authentication" + type = string +} + +variable "target_port" { + description = "PostgreSQL port on VPS" + type = number + default = 5432 +} + +variable "wait_on" { + description = "Resources to wait on before creating tenant" + type = any + default = true +} \ No newline at end of file diff --git a/quadlets/modules/qdrant/main.tf b/quadlets/modules/qdrant/main.tf new file mode 100644 index 0000000..fbe89d6 --- /dev/null +++ b/quadlets/modules/qdrant/main.tf @@ -0,0 +1,63 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" {} + +resource "random_password" "api_key" { + length = 64 + special = false +} + +module "qdrant" { + source = "../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + app_name = "qdrant" + image = "docker.io/qdrant/qdrant" + ports = ["6333:6333", "6334:6334"] + volumes = ["/opt/storage/data/qdrant:/qdrant/storage:z"] + + environment = { + "QDRANT__SERVICE__API_KEY" = random_password.api_key.result + } + + haproxy_services = [ + { + name = "qdrant" + domain = "qdrant.${var.server_domain}" + port = "6333" + host = "127.0.0.1" + tls = false + #grpc_port = "6334" + }, + ] + +} + +output "app_urls" { + value = module.qdrant.app_urls +} + +output "installed" { + value = true + depends_on = [module.qdrant.installed] +} + +output "api_key" { + value = random_password.api_key.result + sensitive = true +} \ No newline at end of file diff --git a/quadlets/modules/rabbitmq/main.tf b/quadlets/modules/rabbitmq/main.tf new file mode 100644 index 0000000..1aa2cb6 --- /dev/null +++ b/quadlets/modules/rabbitmq/main.tf @@ -0,0 +1,60 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +resource "random_password" "username" { + length = 32 + special = false +} + +resource "random_password" "password" { + length = 32 + special = false +} + +module "rabbitmq" { + source = "../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + app_name = "rabbitmq" + image = "docker.io/rabbitmq:management" + ports = ["5672:5672", "15672:15672"] + volumes = ["/opt/storage/data/rabbitmq:/var/lib/rabbitmq:Z"] + + environment = { + RABBITMQ_DEFAULT_USER = random_password.username.result + RABBITMQ_DEFAULT_PASS = random_password.password.result + } +} + +output "app_urls" { + value = module.rabbitmq.app_urls +} + +output "installed" { + value = true + depends_on = [module.rabbitmq.installed] +} + +output "username" { + value = random_password.username.result + sensitive = true +} + +output "password" { + value = random_password.password.result + sensitive = true +} \ No newline at end of file diff --git a/quadlets/modules/rabbitmq/tenant/README.md b/quadlets/modules/rabbitmq/tenant/README.md new file mode 100644 index 0000000..946fbb1 --- /dev/null +++ b/quadlets/modules/rabbitmq/tenant/README.md @@ -0,0 +1,180 @@ +# RabbitMQ Tenant Module + +This module creates a RabbitMQ virtual host (vhost) and user (tenant) on a VPS-hosted RabbitMQ instance using SSH tunneling for secure access to the Management API. + +## Features + +- Creates SSH tunnel to VPS-hosted RabbitMQ Management API +- Creates isolated virtual host and user for tenant +- Configures full permissions for user on their vhost +- Generates secure random password +- Outputs connection details for application use +- Supports explicit dependencies via `wait_on` + +## Requirements + +- SSH access to VPS with key-based authentication +- RabbitMQ admin credentials +- RabbitMQ Management plugin enabled +- `terraform-ssh-tunnel` module (automatically downloaded) +- `cyrilgdn/rabbitmq` provider (automatically configured) + +## Usage + +### Basic Example + +```hcl +module "app_tenant" { + source = "./quadlets/modules/rabbitmq/tenant" + + tenant_name = "myapp" + admin_username = var.rabbitmq_admin_username + admin_password = var.rabbitmq_admin_password + server_ip = "203.0.113.10" + ssh_user = "fourlights" + + # Wait for RabbitMQ service to be ready + wait_on = module.rabbitmq.installed +} + +# Use the connection details +output "app_mq_connection" { + value = { + host = module.app_tenant.host + port = module.app_tenant.port + vhost = module.app_tenant.vhost + username = module.app_tenant.username + password = module.app_tenant.password + } + sensitive = true +} +``` + +### With Custom Management Port + +```hcl +module "custom_tenant" { + source = "./quadlets/modules/rabbitmq/tenant" + + tenant_name = "custom_app" + admin_username = var.rabbitmq_admin_username + admin_password = var.rabbitmq_admin_password + server_ip = "203.0.113.10" + target_port = 25672 # Custom Management API port + + wait_on = module.rabbitmq.installed +} +``` + +### Multiple Tenants + +```hcl +module "tenant_1" { + source = "./quadlets/modules/rabbitmq/tenant" + + tenant_name = "app1" + admin_username = var.rabbitmq_admin_username + admin_password = var.rabbitmq_admin_password + server_ip = var.vps_ip + + wait_on = module.rabbitmq.installed +} + +module "tenant_2" { + source = "./quadlets/modules/rabbitmq/tenant" + + tenant_name = "app2" + admin_username = var.rabbitmq_admin_username + admin_password = var.rabbitmq_admin_password + server_ip = var.vps_ip + + wait_on = module.rabbitmq.installed +} +``` + +### Using Connection String + +```hcl +module "app_tenant" { + source = "./quadlets/modules/rabbitmq/tenant" + + tenant_name = "myapp" + admin_username = var.rabbitmq_admin_username + admin_password = var.rabbitmq_admin_password + server_ip = var.vps_ip + + wait_on = module.rabbitmq.installed +} + +# Use in application configuration +resource "kubernetes_secret" "rabbitmq_credentials" { + metadata { + name = "rabbitmq-connection" + } + + data = { + connection_string = module.app_tenant.connection_string + } +} +``` + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|----------| +| tenant_name | Name of the tenant (used for vhost name and username) | string | - | yes | +| admin_username | RabbitMQ admin username | string | - | yes | +| admin_password | RabbitMQ admin password | string | - | yes | +| server_ip | VPS server IP address | string | - | yes | +| ssh_user | SSH user for connecting to VPS | string | "fourlights" | no | +| ssh_private_key_path | Path to SSH private key | string | "~/.ssh/id_rsa" | no | +| target_port | RabbitMQ Management API port on VPS | number | 15672 | no | +| wait_on | Resources to wait on before creating tenant | any | null | no | + +## Outputs + +| Name | Description | Sensitive | +|------|-------------|-----------| +| host | RabbitMQ connection host (VPS IP for AMQP) | no | +| port | RabbitMQ AMQP connection port (5672) | no | +| management_host | RabbitMQ Management API host (via SSH tunnel) | no | +| management_port | RabbitMQ Management API port (via SSH tunnel) | no | +| vhost | Virtual host name | no | +| username | RabbitMQ username | no | +| password | RabbitMQ password | yes | +| connection_string | Full AMQP connection string | yes | +| installed | Installation complete flag | no | + +## How It Works + +1. **SSH Tunnel**: The module uses `terraform-ssh-tunnel` to create a secure SSH tunnel from your local machine to the VPS RabbitMQ Management API (port 15672) +2. **Provider Configuration**: The RabbitMQ provider is configured to connect through the tunnel +3. **Resource Creation**: Creates a RabbitMQ virtual host, user, and permissions +4. **Password Generation**: Generates a cryptographically secure random password +5. **Outputs**: Provides connection details for your application to use + +## Connection Details + +- **Management API**: Accessed via SSH tunnel (for Terraform operations) +- **AMQP Protocol**: Direct connection to VPS on port 5672 (for application connections) +- **Virtual Host**: Isolated messaging environment for the tenant +- **Permissions**: User has full configure, write, and read permissions on their vhost + +## Notes + +- The SSH tunnel is only used for management operations (creating vhost/user/permissions) +- Applications connect directly to the VPS AMQP port (5672), not through the tunnel +- The virtual host and user will have the same name as `tenant_name` +- User is granted `configure=".*"`, `write=".*"`, `read=".*"` on their vhost +- Password is stored in Terraform state (sensitive) +- Ensure RabbitMQ Management plugin is enabled on the VPS instance + +## Permissions Explained + +The tenant user receives the following permissions on their vhost: + +- **configure**: Can declare exchanges, queues, and bindings +- **write**: Can publish messages +- **read**: Can consume messages + +All permissions use the `.*` regex pattern, granting full access within the vhost. \ No newline at end of file diff --git a/quadlets/modules/rabbitmq/tenant/main.tf b/quadlets/modules/rabbitmq/tenant/main.tf new file mode 100644 index 0000000..7894eb7 --- /dev/null +++ b/quadlets/modules/rabbitmq/tenant/main.tf @@ -0,0 +1,71 @@ +terraform { + required_providers { + random = { + source = "hashicorp/random" + version = "~> 3.6" + } + rabbitmq = { + source = "cyrilgdn/rabbitmq" + version = "~> 1.8" + } + } +} + +# Generate random password for the tenant user +resource "random_password" "tenant_password" { + length = 32 + special = false + min_upper = 2 + min_lower = 2 + min_numeric = 2 +} + +# Create SSH tunnel to RabbitMQ Management API on VPS +module "ssh_tunnel" { + source = "flaupretre/tunnel/ssh" + version = "2.2.1" + + target_host = "127.0.0.1" + target_port = var.target_port + + gateway_host = var.server_ip + gateway_user = var.ssh_user + + # Tunnel will be created when needed + timeout = "30s" +} + +# Configure RabbitMQ provider to use the SSH tunnel +provider "rabbitmq" { + endpoint = "http://${module.ssh_tunnel.host}:${module.ssh_tunnel.port}" + username = var.admin_username + password = var.admin_password +} + +# Create the virtual host +resource "rabbitmq_vhost" "tenant_vhost" { + depends_on = [var.wait_on] + + name = var.tenant_name +} + +# Create the user +resource "rabbitmq_user" "tenant_user" { + depends_on = [var.wait_on] + + name = var.tenant_name + password = random_password.tenant_password.result + tags = [] +} + +# Grant permissions to the user on the vhost +resource "rabbitmq_permissions" "tenant_permissions" { + user = rabbitmq_user.tenant_user.name + vhost = rabbitmq_vhost.tenant_vhost.name + + permissions { + configure = ".*" + write = ".*" + read = ".*" + } +} \ No newline at end of file diff --git a/quadlets/modules/rabbitmq/tenant/outputs.tf b/quadlets/modules/rabbitmq/tenant/outputs.tf new file mode 100644 index 0000000..69e6213 --- /dev/null +++ b/quadlets/modules/rabbitmq/tenant/outputs.tf @@ -0,0 +1,47 @@ +output "host" { + description = "RabbitMQ connection host (via SSH tunnel)" + value = var.server_ip +} + +output "port" { + description = "RabbitMQ AMQP connection port" + value = 5672 +} + +output "management_host" { + description = "RabbitMQ Management API host (via SSH tunnel)" + value = module.ssh_tunnel.host +} + +output "management_port" { + description = "RabbitMQ Management API port (via SSH tunnel)" + value = module.ssh_tunnel.port +} + +output "vhost" { + description = "Virtual host name" + value = rabbitmq_vhost.tenant_vhost.name +} + +output "username" { + description = "RabbitMQ username" + value = rabbitmq_user.tenant_user.name +} + +output "password" { + description = "RabbitMQ password" + value = random_password.tenant_password.result + sensitive = true +} + +output "connection_string" { + description = "RabbitMQ AMQP connection string" + value = "amqp://${rabbitmq_user.tenant_user.name}:${random_password.tenant_password.result}@${var.server_ip}:5672/${rabbitmq_vhost.tenant_vhost.name}" + sensitive = true +} + +output "installed" { + description = "Installation complete flag" + value = true + depends_on = [rabbitmq_vhost.tenant_vhost, rabbitmq_user.tenant_user, rabbitmq_permissions.tenant_permissions] +} \ No newline at end of file diff --git a/quadlets/modules/rabbitmq/tenant/variables.tf b/quadlets/modules/rabbitmq/tenant/variables.tf new file mode 100644 index 0000000..d2e6cfc --- /dev/null +++ b/quadlets/modules/rabbitmq/tenant/variables.tf @@ -0,0 +1,44 @@ +variable "tenant_name" { + description = "Name of the tenant (used for vhost name and username)" + type = string +} + +variable "admin_username" { + description = "RabbitMQ admin username" + type = string +} + +variable "admin_password" { + description = "RabbitMQ admin password" + type = string + sensitive = true +} + +variable "server_ip" { + description = "VPS server IP address" + type = string +} + +variable "ssh_user" { + description = "SSH user for connecting to VPS" + type = string + default = "fourlights" +} + +variable "ssh_private_key_path" { + description = "Path to SSH private key for authentication" + type = string + default = "~/.ssh/id_rsa" +} + +variable "target_port" { + description = "RabbitMQ Management API port on VPS" + type = number + default = 15672 +} + +variable "wait_on" { + description = "Resources to wait on before creating tenant" + type = any + default = null +} \ No newline at end of file diff --git a/quadlets/modules/tmail-web/main.tf b/quadlets/modules/tmail-web/main.tf new file mode 100644 index 0000000..2ac0748 --- /dev/null +++ b/quadlets/modules/tmail-web/main.tf @@ -0,0 +1,97 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +variable "server_url" { + type = string + default = "https://mail.binarysunset.dev" +} + +# tmail-web reads its configuration from a file at +# /usr/share/nginx/html/assets/env.file inside the container. Setting +# SERVER_URL as a container environment variable does NOT work (see +# https://github.com/linagora/tmail-flutter/issues/4240); the file must +# be injected. We write it to the host and bind-mount it into the +# container. +locals { + env_file_host_path = "/opt/storage/data/tmail-web/env.file" + env_file_container_path = "/usr/share/nginx/html/assets/env.file" + env_file_content = "SERVER_URL=${var.server_url}\n" +} + +resource "null_resource" "tmail_env_file" { + triggers = { + content = local.env_file_content + server_ip = var.server_ip + host_path = local.env_file_host_path + } + + provisioner "remote-exec" { + inline = [ + "mkdir -p $(dirname ${local.env_file_host_path})", + "cat > ${local.env_file_host_path} << 'EOF'", + local.env_file_content, + "EOF", + ] + + connection { + type = "ssh" + host = var.server_ip + user = "fourlights" + agent = true + agent_identity = var.ssh_private_key_path + } + } +} + +module "tmail-web" { + source = "../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + app_name = "tmail-web" + image = "docker.io/linagora/tmail-web:latest" + ports = ["8080:80"] + volumes = ["${local.env_file_host_path}:${local.env_file_container_path}:ro"] + + environment = {} + + haproxy_services = [ + { + name = "tmail-web" + domain = "mail.${var.server_domain}" + port = "8080" + host = "127.0.0.1" + tls = false + } + ] + + healthcmd = "curl -f http://localhost:80 || exit 1" + + depends_on = [null_resource.tmail_env_file] +} + +output "app_urls" { + value = module.tmail-web.app_urls +} + +output "installed" { + value = true + depends_on = [module.tmail-web.installed] +} diff --git a/quadlets/modules/zot/main.tf b/quadlets/modules/zot/main.tf new file mode 100644 index 0000000..b86b182 --- /dev/null +++ b/quadlets/modules/zot/main.tf @@ -0,0 +1,58 @@ +variable "wait_on" { + type = any + description = "Resources to wait on" + default = true +} + +variable "server_ip" { + type = string +} + +variable "ssh_private_key_path" { + type = string +} + +variable "server_domain" { + type = string +} + +module "zot" { + source = "../quadlet-app" + wait_on = var.wait_on + + server_ip = var.server_ip + ssh_private_key_path = var.ssh_private_key_path + + app_name = "zot" + image = "ghcr.io/project-zot/zot:latest" + ports = ["5000:5000"] + volumes = [ + "/opt/storage/data/zot/config.json:/etc/zot/config.json:Z", + "/opt/storage/data/zot:/var/lib/registry:Z", + ] + + environment = { + ND_LOGLEVEL="info" + ND_ENABLEINSIGHTSCOLLECTOR="false" + } + + haproxy_services = [ + { + name = "registry" + domain = "registry.${var.server_domain}" + port = "5000" + host = "127.0.0.1" + tls = false + }, + ] + +} + +output "app_urls" { + value = module.zot.app_urls +} + +output "installed" { + value = true + depends_on = [module.zot.installed] +}