diff --git a/modules/20-services-apps/pterodactyl/panel/.env.example b/modules/20-services-apps/pterodactyl/panel/.env.example new file mode 100644 index 0000000..1c64ad4 --- /dev/null +++ b/modules/20-services-apps/pterodactyl/panel/.env.example @@ -0,0 +1,18 @@ +# Pterodactyl Panel Environment Settings + +# Database Configuration +MYSQL_PASSWORD=secure_database_password_here +MYSQL_ROOT_PASSWORD=secure_root_password_here +MYSQL_DATABASE=pterodactyl +MYSQL_USER=pterodactyl + +# Panel Configuration +APP_URL=https://panel.yourdomain.com +APP_TIMEZONE=Australia/Brisbane +APP_SERVICE_AUTHOR=email@example.com +APP_CORS_ALLOWED_ORIGINS=https://panel.yourdomain.com +TRUSTED_PROXIES="*" # Set this to your proxy IP + +# Optional: Let's Encrypt Settings +# Uncomment and set to your email to use Let's Encrypt +# LE_EMAIL=admin@yourdomain.com diff --git a/modules/20-services-apps/pterodactyl/panel/main.tf b/modules/20-services-apps/pterodactyl/panel/main.tf new file mode 100644 index 0000000..fc4809b --- /dev/null +++ b/modules/20-services-apps/pterodactyl/panel/main.tf @@ -0,0 +1,163 @@ +terraform { + required_providers { + dotenv = { + source = "germanbrew/dotenv" + } + } +} + +module "smtp" { + source = "../../../00-globals/smtp" +} + +variable "image_tag" { + description = "The tag for the Pterodactyl Panel container image" + type = string + default = "latest" +} + +variable "volume_path" { + description = "Base directory for volumes" + type = string +} + +variable "networks" { + description = "List of networks to which the container should be attached" + type = list(string) + default = [] +} + +locals { + container_name = "pterodactyl-panel" + database_name = "pterodactyl-db" + cache_name = "pterodactyl-cache" + panel_image = "ghcr.io/pterodactyl/panel" + database_image = "mariadb" + cache_image = "redis" + panel_tag = var.image_tag != "" ? var.image_tag : "latest" + database_tag = "10.5" + cache_tag = "alpine" + monitoring = true + env_file = "${path.module}/.env" + + # Volume paths + panel_volumes = [ + { + host_path = "${var.volume_path}/var" + container_path = "/app/var" + read_only = false + }, + { + host_path = "${var.volume_path}/nginx" + container_path = "/etc/nginx/http.d" + read_only = false + }, + { + host_path = "${var.volume_path}/certs" + container_path = "/etc/letsencrypt" + read_only = false + }, + { + host_path = "${var.volume_path}/logs" + container_path = "/app/storage/logs" + read_only = false + } + ] + + database_volumes = [ + { + host_path = "${var.volume_path}/database" + container_path = "/var/lib/mysql" + read_only = false + } + ] + + # Environment variables + panel_env_vars = { + APP_URL = provider::dotenv::get_by_key("APP_URL", local.env_file) + APP_TIMEZONE = provider::dotenv::get_by_key("APP_TIMEZONE", local.env_file) + APP_SERVICE_AUTHOR = provider::dotenv::get_by_key("APP_SERVICE_AUTHOR", local.env_file) + APP_CORS_ALLOWED_ORIGINS = provider::dotenv::get_by_key("APP_CORS_ALLOWED_ORIGINS", local.env_file) + TRUSTED_PROXIES = provider::dotenv::get_by_key("TRUSTED_PROXIES", local.env_file) + MAIL_FROM = module.smtp.mail_from + MAIL_DRIVER = "smtp" + MAIL_HOST = module.smtp.mail_host + MAIL_PORT = module.smtp.mail_port + MAIL_USERNAME = module.smtp.mail_username + MAIL_PASSWORD = module.smtp.mail_password + MAIL_ENCRYPTION = "false" + DB_PASSWORD = provider::dotenv::get_by_key("MYSQL_PASSWORD", local.env_file) + APP_ENV = "production" + APP_ENVIRONMENT_ONLY = "false" + CACHE_DRIVER = "redis" + SESSION_DRIVER = "redis" + QUEUE_DRIVER = "redis" + REDIS_HOST = local.cache_name + DB_HOST = local.database_name + DB_PORT = "3306" + DB_DATABASE = provider::dotenv::get_by_key("MYSQL_DATABASE", local.env_file) + DB_USERNAME = provider::dotenv::get_by_key("MYSQL_USER", local.env_file) + } + + database_env_vars = { + MYSQL_PASSWORD = provider::dotenv::get_by_key("MYSQL_PASSWORD", local.env_file) + MYSQL_ROOT_PASSWORD = provider::dotenv::get_by_key("MYSQL_ROOT_PASSWORD", local.env_file) + MYSQL_DATABASE = provider::dotenv::get_by_key("MYSQL_DATABASE", local.env_file) + MYSQL_USER = provider::dotenv::get_by_key("MYSQL_USER", local.env_file) + } +} + +# Create a dedicated network for Pterodactyl +module "pterodactyl_network" { + source = "../../../01-networking/docker-network" + name = "ptero-panel" + driver = "bridge" + subnet = "172.20.0.0/16" + attachable = true +} + +# Database container +module "database" { + source = "../../../10-services-generic/docker-service" + container_name = local.database_name + image = local.database_image + tag = local.database_tag + volumes = local.database_volumes + env_vars = local.database_env_vars + networks = concat([module.pterodactyl_network.name], var.networks) + command = ["--default-authentication-plugin=mysql_native_password"] + monitoring = local.monitoring +} + +# Cache container +module "cache" { + source = "../../../10-services-generic/docker-service" + container_name = local.cache_name + image = local.cache_image + tag = local.cache_tag + networks = concat([module.pterodactyl_network.name], var.networks) + monitoring = local.monitoring +} + +# Panel container +module "panel" { + source = "../../../10-services-generic/docker-service" + container_name = local.container_name + image = local.panel_image + tag = local.panel_tag + volumes = local.panel_volumes + env_vars = local.panel_env_vars + networks = concat([module.pterodactyl_network.name], var.networks) + monitoring = local.monitoring + depends_on = [module.database, module.cache] +} + +output "service_definition" { + description = "General service definition with optional ingress configuration" + value = { + name = local.container_name + primary_port = 80 + endpoint = "http://${local.container_name}:80" + subdomains = ["gameservers"] + } +} diff --git a/modules/20-services-apps/pterodactyl/wings/main.tf b/modules/20-services-apps/pterodactyl/wings/main.tf new file mode 100644 index 0000000..f1f066b --- /dev/null +++ b/modules/20-services-apps/pterodactyl/wings/main.tf @@ -0,0 +1,115 @@ +terraform { + required_providers { + dotenv = { + source = "germanbrew/dotenv" + } + } +} + +variable "image_tag" { + description = "The tag for the Pterodactyl Wings container image" + type = string + default = "v1.11.3" +} + +variable "volume_path" { + description = "Base directory for volumes" + type = string +} + +variable "networks" { + description = "List of networks to which the container should be attached" + type = list(string) + default = [] +} + +locals { + container_name = "pterodactyl-wings" + image = "ghcr.io/pterodactyl/wings" + image_tag = var.image_tag != "" ? var.image_tag : "v1.11.3" + monitoring = false + env_file = "${path.module}/.env" + subdomains = ["wings"] + + # Volumes configuration + volumes = [ + { + host_path = "/var/run/docker.sock" + container_path = "/var/run/docker.sock" + read_only = false + }, + { + host_path = "/var/lib/docker/containers/" + container_path = "/var/lib/docker/containers/" + read_only = false + }, + { + host_path = "/etc/ssl/certs" + container_path = "/etc/ssl/certs" + read_only = true + }, + { + host_path = "${var.volume_path}/etc" + container_path = "/etc/pterodactyl/" + read_only = false + }, + { + host_path = "${var.volume_path}/var/lib" + container_path = "/var/lib/pterodactyl/" + read_only = false + }, + { + host_path = "${var.volume_path}/var/log" + container_path = "/var/log/pterodactyl/" + read_only = false + }, + { + host_path = "${var.volume_path}/tmp" + container_path = "/tmp/pterodactyl/" + read_only = false + }, + ] + + # Environment variables + env_vars = { + TZ = "Australia/Brisbane" + WINGS_UID = 988 + WINGS_GID = 988 + WINGS_USERNAME = "pterodactyl" + } +} + +# Create a custom Docker network for wings +module "wings_network" { + source = "../../../01-networking/docker-network" + + name = "ptero-wings" + driver = "bridge" + attachable = true + subnet = "172.32.0.0/16" + options = { + "com.docker.network.bridge.name" = "ptero-wings" + } +} + +module "wings" { + source = "../../../10-services-generic/docker-service" + container_name = local.container_name + image = local.image + tag = local.image_tag + volumes = local.volumes + env_vars = local.env_vars + networks = concat([module.wings_network.name], var.networks) + monitoring = local.monitoring + privileged = true +} + +output "service_definition" { + description = "General service definition with optional ingress configuration" + value = { + name = local.container_name + primary_port = 443 + endpoint = "http://${local.container_name}:443" + subdomains = local.subdomains + } +} diff --git a/services/main.tf b/services/main.tf index 60fc0c6..59aef35 100644 --- a/services/main.tf +++ b/services/main.tf @@ -1,5 +1,5 @@ locals { - module_dir = "../modules" + module_dir = "../modules" volume_host = module.system_globals.volume_host } @@ -40,4 +40,15 @@ module "ntfy" { volume_path = "${local.volume_host}/ntfy" networks = [module.homelab_docker_network.name] } - \ No newline at end of file + +module "pterodactyl_panel" { + source = "${local.module_dir}/20-services-apps/pterodactyl/panel" + volume_path = "${local.volume_host}/pterodactyl/panel" + networks = [module.homelab_docker_network.name] +} + +module "pterodactyl_wings" { + source = "${local.module_dir}/20-services-apps/pterodactyl/wings" + volume_path = "${local.volume_host}/pterodactyl/wings" + networks = [module.homelab_docker_network.name] +} diff --git a/services/outputs.tf b/services/outputs.tf index ac6bec6..3209f8e 100644 --- a/services/outputs.tf +++ b/services/outputs.tf @@ -7,7 +7,9 @@ output "service_definitions" { module.actualbudget.service_definition, module.emulatorjs.service_definition, module.linkwarden.service_definition, - module.ntfy.service_definition + module.ntfy.service_definition, + module.pterodactyl_wings.service_definition, + module.pterodactyl_panel.service_definition ] }