shuttles #2
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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 <db>.* to <user>@'%';
|
||||
|
||||
resource postgresql_grant grant {
|
||||
provider = postgresql.tunnel
|
||||
|
||||
object_type = "database"
|
||||
database = var.tenant_name
|
||||
role = var.tenant_name
|
||||
privileges = ["ALL"]
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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 = ".*"
|
||||
}
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
Loading…
Reference in New Issue