refactor: simplify project structure
This commit is contained in:
11
.env.example
Normal file
11
.env.example
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# System
|
||||||
|
TIMEZONE="Australia/Brisbane"
|
||||||
|
DATA_DIR="/mnt/appdata"
|
||||||
|
PUID="1000"
|
||||||
|
PGID="1000"
|
||||||
|
|
||||||
|
# Cloudflare
|
||||||
|
CLOUDFLARE_API_TOKEN="your-cloudflare-api-token"
|
||||||
|
CLOUDFLARE_ACCOUNT_ID="your-cloudflare-account-id"
|
||||||
|
CLOUDFLARE_ZONE_ID="your-cloudflare-zone-id"
|
||||||
|
DOMAIN="yourdomain.com"
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ override.tf
|
|||||||
override.tf.json
|
override.tf.json
|
||||||
.terraformrc
|
.terraformrc
|
||||||
terraform.rc
|
terraform.rc
|
||||||
|
.env
|
||||||
|
|||||||
23
.terraform.lock.hcl
generated
23
.terraform.lock.hcl
generated
@@ -24,6 +24,29 @@ provider "registry.opentofu.org/cloudflare/cloudflare" {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
provider "registry.opentofu.org/germanbrew/dotenv" {
|
||||||
|
version = "1.2.5"
|
||||||
|
constraints = "1.2.5"
|
||||||
|
hashes = [
|
||||||
|
"h1:aUUdKCjUPHBgapuUb36pa8BUhWscSPpt5q1/JKNODsc=",
|
||||||
|
"zh:01d2c432515ef0ceffc321473a87c7571aaf068c31c36bed203c3450828e5ab5",
|
||||||
|
"zh:026dec6dcc688cfb6011e71e7c16219af02cc5acfa6ef4e6f803972c85b57eda",
|
||||||
|
"zh:31a63b727b5a5aea529bea1e557fbe04067c05c032db77afab61c2db0328dbf2",
|
||||||
|
"zh:3c53fb73bed50012019bdc83bc0502926a80c60e4c8f8fdade11e3a705baee32",
|
||||||
|
"zh:420b26f57d16fa8750da0e9a45b32846f39efc909de60b5e3b6e23596ab0ff15",
|
||||||
|
"zh:4e559274f355c79c9c5367b55f26d2a054d585502978dae15b715732d8717772",
|
||||||
|
"zh:84ab9b4024d53edbd67c83dc56a9af41089148d125e7dd1dab04ee402a8880e8",
|
||||||
|
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
||||||
|
"zh:8f17d9cf82f08e5a6d451d80fec912aebf8ce8a0c880609666613b21de3393ad",
|
||||||
|
"zh:9f3c17e254c0f2a0eae5b53103976763241e049afcada375cd84af95aa012c18",
|
||||||
|
"zh:a3fffa256fe3eed985831f3ea90826ac7feab869f433a60c912a80d8d41783e8",
|
||||||
|
"zh:c6f6aa8249aaaefbf00b6c4c1eadb1166e469a07d36e738e4a3106b87e1b7340",
|
||||||
|
"zh:d2e89f25111485fb97d75b165bdfe06b47467e1ec62aedc884c9d30b3c138196",
|
||||||
|
"zh:d588bcb33fab3991fdd27d9d0a8a842244ecc45a32aa2b32ec12fc631c5dd5fb",
|
||||||
|
"zh:e78ce7620cfa89de13b93e55ba2bb51d68bac410b2f6fbea77d57ba9252f0f80",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
provider "registry.opentofu.org/hashicorp/random" {
|
provider "registry.opentofu.org/hashicorp/random" {
|
||||||
version = "3.5.1"
|
version = "3.5.1"
|
||||||
constraints = "~> 3.5.1"
|
constraints = "~> 3.5.1"
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
# Homelab Environments
|
|
||||||
|
|
||||||
This directory contains environment-specific configurations that help organize your infrastructure modules into logical groupings.
|
|
||||||
|
|
||||||
Each subdirectory represents a category or environment that can be applied independently or together with others.
|
|
||||||
|
|
||||||
```
|
|
||||||
/environments/
|
|
||||||
├── core/ # Essential infrastructure (tunnel, monitoring)
|
|
||||||
├── services/ # Application services (ActualBudget, EmulatorJS)
|
|
||||||
└── network/ # (Future) Network configs
|
|
||||||
```
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
// Core infrastructure components
|
|
||||||
// These are the foundational services that other services depend on
|
|
||||||
|
|
||||||
locals {
|
|
||||||
module_dir = "../../modules"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Core monitoring and maintenance service
|
|
||||||
module "watchtower" {
|
|
||||||
source = "${local.module_dir}/20-services-apps/watchtower"
|
|
||||||
|
|
||||||
timezone = var.timezone
|
|
||||||
poll_interval = 86400
|
|
||||||
cleanup = true
|
|
||||||
enable_notifications = var.watchtower_enable_notifications
|
|
||||||
notification_url = var.watchtower_notification_url
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
// Generic
|
|
||||||
variable "timezone" {
|
|
||||||
description = "Timezone for the system"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watchtower
|
|
||||||
variable "watchtower_enable_notifications" {
|
|
||||||
description = "Enable Watchtower update notifications"
|
|
||||||
type = bool
|
|
||||||
default = false
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "watchtower_notification_url" {
|
|
||||||
description = "Webhook URL for Watchtower notifications (Discord, Slack, etc.)"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
// Network environment
|
|
||||||
// Contains configurations for network infrastructure
|
|
||||||
|
|
||||||
locals {
|
|
||||||
module_dir = "../../modules"
|
|
||||||
}
|
|
||||||
|
|
||||||
module "cloudflare_globals" {
|
|
||||||
source = "${local.module_dir}/00-globals/cloudflare"
|
|
||||||
|
|
||||||
cloudflare_api_token = var.cloudflare_api_token
|
|
||||||
cloudflare_account_id = var.cloudflare_account_id
|
|
||||||
cloudflare_zone_id = var.cloudflare_zone_id
|
|
||||||
domain = var.domain
|
|
||||||
}
|
|
||||||
|
|
||||||
module "homelab_docker_network" {
|
|
||||||
source = "${local.module_dir}/01-networking/docker-network"
|
|
||||||
|
|
||||||
name = "homelab-network"
|
|
||||||
driver = "bridge"
|
|
||||||
attachable = true
|
|
||||||
subnet = "10.100.0.0/16"
|
|
||||||
}
|
|
||||||
|
|
||||||
module "homelab_cloudflared_tunnel" {
|
|
||||||
source = "${local.module_dir}/01-networking/cloudflared-tunnel"
|
|
||||||
|
|
||||||
cloudflare_account_id = module.cloudflare_globals.cloudflare_account_id
|
|
||||||
cloudflare_zone_id = module.cloudflare_globals.cloudflare_zone_id
|
|
||||||
|
|
||||||
tunnel_name = "homelab"
|
|
||||||
container_name = "cloudflared-homelab"
|
|
||||||
|
|
||||||
ingress_rules = [
|
|
||||||
{
|
|
||||||
hostname = "budget.${var.domain}"
|
|
||||||
service = "http://actualbudget:5006"
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
networks = [module.homelab_docker_network.name]
|
|
||||||
|
|
||||||
monitoring = true
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
output "cloudflare_account_id" {
|
|
||||||
description = "Cloudflare account ID"
|
|
||||||
value = module.cloudflare_globals.cloudflare_account_id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "cloudflare_zone_id" {
|
|
||||||
description = "Cloudflare zone ID"
|
|
||||||
value = module.cloudflare_globals.cloudflare_zone_id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "domain" {
|
|
||||||
description = "Base domain name"
|
|
||||||
value = module.cloudflare_globals.domain
|
|
||||||
}
|
|
||||||
|
|
||||||
// Docker network outputs
|
|
||||||
output "homelab_docker_network_name" {
|
|
||||||
description = "Name of the Docker network"
|
|
||||||
value = module.homelab_docker_network.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tunnel outputs
|
|
||||||
output "homelab_cloudflared_tunnel_id" {
|
|
||||||
description = "ID of the Cloudflare tunnel"
|
|
||||||
value = module.homelab_cloudflared_tunnel.tunnel_id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "homelab_cloudflared_tunnel_name" {
|
|
||||||
description = "Name of the Cloudflare tunnel"
|
|
||||||
value = module.homelab_cloudflared_tunnel.tunnel_name
|
|
||||||
}
|
|
||||||
|
|
||||||
output "homelab_cloudflared_tunnel_cname_target" {
|
|
||||||
description = "CNAME target for the tunnel"
|
|
||||||
value = module.homelab_cloudflared_tunnel.cname_target
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
|
|
||||||
variable "cloudflare_api_token" {
|
|
||||||
description = "API token for Cloudflare with the necessary permissions"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "cloudflare_account_id" {
|
|
||||||
description = "Cloudflare account ID"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "cloudflare_zone_id" {
|
|
||||||
description = "Cloudflare zone ID for the domain"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "domain" {
|
|
||||||
description = "Base domain name (e.g., example.com)"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
// Application services environment
|
|
||||||
// Contains configurations for all application services
|
|
||||||
|
|
||||||
// Import global Terraform settings
|
|
||||||
terraform {
|
|
||||||
# Include backend configuration if needed
|
|
||||||
# backend "local" { ... }
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
module_dir = "../../modules"
|
|
||||||
}
|
|
||||||
|
|
||||||
module "actualbudget" {
|
|
||||||
source = "${local.module_dir}/20-services-apps/actualbudget"
|
|
||||||
|
|
||||||
container_name = "actualbudget"
|
|
||||||
timezone = var.timezone
|
|
||||||
data_volume_path = "${var.data_dir}/actual/data"
|
|
||||||
port = var.actualbudget_port
|
|
||||||
networks = var.default_networks
|
|
||||||
}
|
|
||||||
|
|
||||||
module "emulatorjs" {
|
|
||||||
source = "${local.module_dir}/20-services-apps/emulatorjs"
|
|
||||||
|
|
||||||
container_name = "emulatorjs"
|
|
||||||
timezone = var.timezone
|
|
||||||
puid = var.puid
|
|
||||||
pgid = var.pgid
|
|
||||||
config_volume_path = "${var.data_dir}/emulatorjs/config"
|
|
||||||
data_volume_path = "${var.data_dir}/emulatorjs/data"
|
|
||||||
frontend_port = var.emulatorjs_frontend_port
|
|
||||||
config_port = var.emulatorjs_config_port
|
|
||||||
backend_port = var.emulatorjs_backend_port
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
// Services environment outputs
|
|
||||||
|
|
||||||
// ActualBudget
|
|
||||||
output "actualbudget_container_name" {
|
|
||||||
description = "The name of the ActualBudget container"
|
|
||||||
value = module.actualbudget.container_name
|
|
||||||
}
|
|
||||||
|
|
||||||
output "actualbudget_container_id" {
|
|
||||||
description = "The ID of the ActualBudget container"
|
|
||||||
value = module.actualbudget.container_id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "actualbudget_local_url" {
|
|
||||||
description = "The local URL to access ActualBudget"
|
|
||||||
value = module.actualbudget.local_url
|
|
||||||
}
|
|
||||||
|
|
||||||
// EmulatorJS
|
|
||||||
output "emulatorjs_container_name" {
|
|
||||||
description = "The name of the EmulatorJS container"
|
|
||||||
value = module.emulatorjs.container_name
|
|
||||||
}
|
|
||||||
|
|
||||||
output "emulatorjs_container_id" {
|
|
||||||
description = "The ID of the EmulatorJS container"
|
|
||||||
value = module.emulatorjs.container_id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "emulatorjs_frontend_url" {
|
|
||||||
description = "The frontend URL for EmulatorJS"
|
|
||||||
value = module.emulatorjs.frontend_url
|
|
||||||
}
|
|
||||||
|
|
||||||
output "emulatorjs_config_url" {
|
|
||||||
description = "The configuration URL for EmulatorJS"
|
|
||||||
value = module.emulatorjs.config_url
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// Variables for the services environment
|
|
||||||
|
|
||||||
// Generic
|
|
||||||
variable "timezone" {
|
|
||||||
description = "Timezone for the system"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "puid" {
|
|
||||||
description = "User ID for the container"
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "pgid" {
|
|
||||||
description = "Group ID for the container"
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "data_dir" {
|
|
||||||
description = "Base directory for data volumes"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "default_networks" {
|
|
||||||
description = "List of networks to which the container should be attached"
|
|
||||||
type = list(string)
|
|
||||||
default = []
|
|
||||||
}
|
|
||||||
|
|
||||||
// ActualBudget
|
|
||||||
variable "actualbudget_port" {
|
|
||||||
description = "External port for the ActualBudget server"
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
// EmulatorJS
|
|
||||||
variable "emulatorjs_frontend_port" {
|
|
||||||
description = "External port for the EmulatorJS frontend"
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "emulatorjs_config_port" {
|
|
||||||
description = "External port for the EmulatorJS configuration interface"
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "emulatorjs_backend_port" {
|
|
||||||
description = "External port for the EmulatorJS backend"
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
59
main.tf
59
main.tf
@@ -1,49 +1,24 @@
|
|||||||
// Root module that orchestrates all environments
|
module "cloudflare_globals" {
|
||||||
// This unified approach keeps a single entry point while organizing by function
|
source = "./modules/00-globals/cloudflare"
|
||||||
|
|
||||||
// Network infrastructure
|
|
||||||
module "network" {
|
|
||||||
source = "./environments/network"
|
|
||||||
|
|
||||||
// Cloudflare variables
|
|
||||||
cloudflare_api_token = var.cloudflare_api_token
|
|
||||||
cloudflare_account_id = var.cloudflare_account_id
|
|
||||||
cloudflare_zone_id = var.cloudflare_zone_id
|
|
||||||
domain = var.domain
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Core infrastructure (monitoring, globals)
|
module "watchtower" {
|
||||||
module "core" {
|
source = "./modules/20-services-apps/watchtower"
|
||||||
source = "./environments/core"
|
|
||||||
|
|
||||||
depends_on = [module.network]
|
|
||||||
|
|
||||||
timezone = var.timezone
|
|
||||||
|
|
||||||
// Watchtower variables
|
|
||||||
watchtower_enable_notifications = var.watchtower_enable_notifications
|
|
||||||
watchtower_notification_url = var.watchtower_notification_url
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Application services
|
// Application services
|
||||||
module "services" {
|
module "services" {
|
||||||
source = "./environments/services"
|
source = "./services"
|
||||||
|
}
|
||||||
depends_on = [module.core, module.network]
|
|
||||||
|
module "homelab_cloudflared_tunnel" {
|
||||||
timezone = var.timezone
|
source = "./modules/01-networking/cloudflared-tunnel"
|
||||||
puid = var.puid
|
cloudflare_account_id = module.cloudflare_globals.cloudflare_account_id
|
||||||
pgid = var.pgid
|
cloudflare_zone_id = module.cloudflare_globals.cloudflare_zone_id
|
||||||
data_dir = var.data_dir
|
domain = module.cloudflare_globals.domain
|
||||||
|
tunnel_name = "homelab"
|
||||||
// ActualBudget variables
|
container_name = "cloudflared-homelab"
|
||||||
actualbudget_port = var.actualbudget_port
|
service_definitions = module.services.service_definitions
|
||||||
|
networks = [module.services.homelab_docker_network_name]
|
||||||
// EmulatorJS variables
|
monitoring = true
|
||||||
emulatorjs_frontend_port = var.emulatorjs_frontend_port
|
|
||||||
emulatorjs_config_port = var.emulatorjs_config_port
|
|
||||||
emulatorjs_backend_port = var.emulatorjs_backend_port
|
|
||||||
|
|
||||||
// Docker network variables
|
|
||||||
default_networks = [module.network.homelab_docker_network_name]
|
|
||||||
}
|
}
|
||||||
|
|||||||
32
modules/00-globals/cloudflare/main.tf
Normal file
32
modules/00-globals/cloudflare/main.tf
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
dotenv = {
|
||||||
|
source = "germanbrew/dotenv"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data "dotenv_sensitive" "cloudflare_credentials" {}
|
||||||
|
data "dotenv" "cloudflare_config" {}
|
||||||
|
|
||||||
|
// Outputs
|
||||||
|
output "cloudflare_account_id" {
|
||||||
|
description = "Cloudflare account ID"
|
||||||
|
value = data.dotenv.cloudflare_config.entries.CLOUDFLARE_ACCOUNT_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
output "cloudflare_zone_id" {
|
||||||
|
description = "Cloudflare zone ID"
|
||||||
|
value = data.dotenv.cloudflare_config.entries.CLOUDFLARE_ZONE_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
output "domain" {
|
||||||
|
description = "Base domain name"
|
||||||
|
value = data.dotenv.cloudflare_config.entries.DOMAIN
|
||||||
|
}
|
||||||
|
|
||||||
|
output "cloudflare_api_token" {
|
||||||
|
description = "API token for Cloudflare"
|
||||||
|
value = data.dotenv_sensitive.cloudflare_credentials.entries.CLOUDFLARE_API_TOKEN
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
output "cloudflare_account_id" {
|
|
||||||
description = "Cloudflare account ID"
|
|
||||||
value = var.cloudflare_account_id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "cloudflare_zone_id" {
|
|
||||||
description = "Cloudflare zone ID"
|
|
||||||
value = var.cloudflare_zone_id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "domain" {
|
|
||||||
description = "Base domain name"
|
|
||||||
value = var.domain
|
|
||||||
}
|
|
||||||
|
|
||||||
output "cloudflare_api_token" {
|
|
||||||
description = "API token for Cloudflare"
|
|
||||||
value = var.cloudflare_api_token
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
variable "cloudflare_api_token" {
|
|
||||||
description = "API token for Cloudflare with tunnel, DNS, and zone management permissions"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "cloudflare_account_id" {
|
|
||||||
description = "Cloudflare account ID"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "cloudflare_zone_id" {
|
|
||||||
description = "Cloudflare zone ID for your domain"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "domain" {
|
|
||||||
description = "Base domain name (e.g., example.com)"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
30
modules/00-globals/system/main.tf
Normal file
30
modules/00-globals/system/main.tf
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
dotenv = {
|
||||||
|
source = "germanbrew/dotenv"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data "dotenv" "system_config" {}
|
||||||
|
|
||||||
|
// Outputs
|
||||||
|
output "timezone" {
|
||||||
|
description = "System timezone"
|
||||||
|
value = data.dotenv.system_config.entries.TIMEZONE
|
||||||
|
}
|
||||||
|
|
||||||
|
output "data_dir" {
|
||||||
|
description = "Base directory for data volumes"
|
||||||
|
value = data.dotenv.system_config.entries.DATA_DIR
|
||||||
|
}
|
||||||
|
|
||||||
|
output "puid" {
|
||||||
|
description = "PUID for Docker containers"
|
||||||
|
value = data.dotenv.system_config.entries.PUID
|
||||||
|
}
|
||||||
|
|
||||||
|
output "pgid" {
|
||||||
|
description = "PGID for Docker containers"
|
||||||
|
value = data.dotenv.system_config.entries.PGID
|
||||||
|
}
|
||||||
@@ -5,11 +5,9 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
cloudflare = {
|
cloudflare = {
|
||||||
source = "cloudflare/cloudflare"
|
source = "cloudflare/cloudflare"
|
||||||
version = "~> 4.0"
|
|
||||||
}
|
}
|
||||||
random = {
|
random = {
|
||||||
source = "hashicorp/random"
|
source = "hashicorp/random"
|
||||||
version = "~> 3.5.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,7 +26,23 @@ resource "cloudflare_zero_trust_tunnel_cloudflared" "this" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
all_ingress_rules = [for rule in var.ingress_rules : rule if rule != null]
|
// Transform service definitions into ingress rules format, only for services with ingress_enabled
|
||||||
|
service_ingress_rules = flatten([
|
||||||
|
for service in var.service_definitions :
|
||||||
|
// Only process services with hostnames AND where ingress is enabled (or default to true for backward compatibility)
|
||||||
|
(length(service.hostnames) > 0) ? [
|
||||||
|
for hostname in service.hostnames : {
|
||||||
|
hostname = "${hostname}.${var.domain}"
|
||||||
|
service = service.endpoint
|
||||||
|
}
|
||||||
|
] : []
|
||||||
|
])
|
||||||
|
|
||||||
|
// Combine manual ingress rules and service-generated ones
|
||||||
|
all_ingress_rules = concat(
|
||||||
|
[for rule in var.ingress_rules : rule if rule != null],
|
||||||
|
local.service_ingress_rules
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure tunnel routing
|
// Configure tunnel routing
|
||||||
@@ -55,7 +69,10 @@ resource "cloudflare_zero_trust_tunnel_cloudflared_config" "this" {
|
|||||||
|
|
||||||
// Create DNS record for each service
|
// Create DNS record for each service
|
||||||
resource "cloudflare_record" "service" {
|
resource "cloudflare_record" "service" {
|
||||||
for_each = { for rule in var.ingress_rules : rule.hostname => rule }
|
for_each = {
|
||||||
|
for rule in local.all_ingress_rules : rule.hostname => rule
|
||||||
|
if rule.hostname != null && rule.hostname != ""
|
||||||
|
}
|
||||||
|
|
||||||
zone_id = var.cloudflare_zone_id
|
zone_id = var.cloudflare_zone_id
|
||||||
name = split(".", each.value.hostname)[0] // Extract subdomain
|
name = split(".", each.value.hostname)[0] // Extract subdomain
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// Variables for the Cloudflare tunnel module
|
// Variables for the Cloudflare tunnel module
|
||||||
|
|
||||||
variable "cloudflare_account_id" {
|
variable "cloudflare_account_id" {
|
||||||
description = "Cloudflare account ID"
|
description = "Cloudflare account ID"
|
||||||
type = string
|
type = string
|
||||||
@@ -10,6 +9,11 @@ variable "cloudflare_zone_id" {
|
|||||||
type = string
|
type = string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "domain" {
|
||||||
|
description = "The domain name to use for creating DNS records"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
variable "container_name" {
|
variable "container_name" {
|
||||||
description = "Name of the Cloudflare tunnel container"
|
description = "Name of the Cloudflare tunnel container"
|
||||||
type = string
|
type = string
|
||||||
@@ -35,7 +39,7 @@ variable "tunnel_secret" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
variable "ingress_rules" {
|
variable "ingress_rules" {
|
||||||
description = "List of ingress rules for services to be exposed through the tunnel"
|
description = "List of ingress rules to configure manually"
|
||||||
type = list(object({
|
type = list(object({
|
||||||
hostname = string
|
hostname = string
|
||||||
service = string
|
service = string
|
||||||
@@ -43,6 +47,17 @@ variable "ingress_rules" {
|
|||||||
default = []
|
default = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "service_definitions" {
|
||||||
|
description = "List of service definitions containing name, endpoints and hostname configuration"
|
||||||
|
type = list(object({
|
||||||
|
name = string
|
||||||
|
primary_port = number
|
||||||
|
endpoint = string
|
||||||
|
hostnames = optional(list(string), [])
|
||||||
|
}))
|
||||||
|
default = []
|
||||||
|
}
|
||||||
|
|
||||||
variable "monitoring" {
|
variable "monitoring" {
|
||||||
description = "Enable monitoring via Watchtower"
|
description = "Enable monitoring via Watchtower"
|
||||||
type = bool
|
type = bool
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
// Docker Network Module
|
|
||||||
// This module creates a Docker network for container communication
|
|
||||||
|
|
||||||
terraform {
|
terraform {
|
||||||
required_providers {
|
required_providers {
|
||||||
docker = {
|
docker = {
|
||||||
source = "kreuzwerker/docker"
|
source = "kreuzwerker/docker"
|
||||||
version = "~> 3.6.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// Outputs for Docker Network module
|
// Outputs for Docker Network module
|
||||||
|
|
||||||
output "network_id" {
|
output "network_id" {
|
||||||
description = "The ID of the Docker network"
|
description = "The ID of the Docker network"
|
||||||
value = docker_network.this.id
|
value = docker_network.this.id
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
docker = {
|
docker = {
|
||||||
source = "kreuzwerker/docker"
|
source = "kreuzwerker/docker"
|
||||||
version = ">= 3.0.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
// Generic Docker service module
|
// Generic Docker service module
|
||||||
// Creates and manages a Docker container with configurable options
|
// Creates and manages a Docker container with configurable options
|
||||||
|
module "system_globals" {
|
||||||
|
source = "../../00-globals/system"
|
||||||
|
}
|
||||||
|
|
||||||
terraform {
|
terraform {
|
||||||
required_providers {
|
required_providers {
|
||||||
docker = {
|
docker = {
|
||||||
source = "kreuzwerker/docker"
|
source = "kreuzwerker/docker"
|
||||||
version = ">= 3.0.0"
|
}
|
||||||
|
dotenv = {
|
||||||
|
source = "germanbrew/dotenv"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,6 +20,14 @@ locals {
|
|||||||
container_name = var.container_name
|
container_name = var.container_name
|
||||||
image_name = "${var.image}:${var.tag}"
|
image_name = "${var.image}:${var.tag}"
|
||||||
|
|
||||||
|
default_env_vars = {
|
||||||
|
TZ = module.system_globals.timezone
|
||||||
|
PUID = module.system_globals.puid
|
||||||
|
PGID = module.system_globals.pgid
|
||||||
|
}
|
||||||
|
|
||||||
|
env_vars = merge(var.env_vars, local.default_env_vars)
|
||||||
|
|
||||||
// Prepare ports configuration
|
// Prepare ports configuration
|
||||||
ports_config = [
|
ports_config = [
|
||||||
for port in var.ports : {
|
for port in var.ports : {
|
||||||
@@ -88,7 +101,7 @@ resource "docker_container" "service_container" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Configure environment variables - map to array of strings
|
# Configure environment variables - map to array of strings
|
||||||
env = [for k, v in var.env_vars : "${k}=${v}"]
|
env = [for k, v in local.env_vars : "${k}=${v}"]
|
||||||
|
|
||||||
# Set container labels
|
# Set container labels
|
||||||
dynamic "labels" {
|
dynamic "labels" {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ variable "keep_image_locally" {
|
|||||||
variable "restart_policy" {
|
variable "restart_policy" {
|
||||||
description = "Docker restart policy (no, always, unless-stopped, on-failure)"
|
description = "Docker restart policy (no, always, unless-stopped, on-failure)"
|
||||||
type = string
|
type = string
|
||||||
default = "unless-stopped"
|
default = "always"
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "network_mode" {
|
variable "network_mode" {
|
||||||
|
|||||||
@@ -1,20 +1,30 @@
|
|||||||
// ActualBudget module for budgeting
|
variable "image_tag" {
|
||||||
// This module configures an ActualBudget container with the specified volumes
|
description = "Tag of the ActualBudget image to use"
|
||||||
|
type = string
|
||||||
|
default = "latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "volume_path" {
|
||||||
|
description = "Host path for ActualBudget data volume"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "networks" {
|
||||||
|
description = "List of networks to which the container should be attached"
|
||||||
|
type = list(string)
|
||||||
|
}
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
container_name = var.container_name != "" ? var.container_name : "actualbudget"
|
container_name = "actualbudget"
|
||||||
|
image = "actualbudget/actual-server"
|
||||||
image_tag = var.image_tag != "" ? var.image_tag : "latest"
|
image_tag = var.image_tag != "" ? var.image_tag : "latest"
|
||||||
|
monitoring = true
|
||||||
default_env_vars = {
|
exposed_port = 5006
|
||||||
TZ = var.timezone
|
hostnames = ["budget"]
|
||||||
PUID = var.puid
|
|
||||||
PGID = var.pgid
|
|
||||||
}
|
|
||||||
|
|
||||||
default_volumes = [
|
default_volumes = [
|
||||||
{
|
{
|
||||||
container_path = "/data"
|
container_path = "/data"
|
||||||
host_path = var.data_volume_path
|
host_path = "${var.volume_path}/data"
|
||||||
read_only = false
|
read_only = false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -22,28 +32,20 @@ locals {
|
|||||||
|
|
||||||
module "actualbudget" {
|
module "actualbudget" {
|
||||||
source = "../../10-services-generic/docker-service"
|
source = "../../10-services-generic/docker-service"
|
||||||
|
container_name = local.container_name
|
||||||
container_name = var.container_name
|
image = local.image
|
||||||
image = "actualbudget/actual-server"
|
tag = local.image_tag
|
||||||
tag = var.image_tag
|
|
||||||
|
|
||||||
// Environment variables
|
|
||||||
env_vars = local.default_env_vars
|
|
||||||
|
|
||||||
// Port mapping
|
|
||||||
ports = [
|
|
||||||
{
|
|
||||||
internal = 5006
|
|
||||||
external = var.port
|
|
||||||
protocol = "tcp"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
// Volume mapping
|
|
||||||
volumes = local.default_volumes
|
volumes = local.default_volumes
|
||||||
|
|
||||||
// Enable monitoring for the container via Watchtower
|
|
||||||
monitoring = var.monitoring
|
|
||||||
|
|
||||||
networks = var.networks
|
networks = var.networks
|
||||||
|
monitoring = local.monitoring
|
||||||
|
}
|
||||||
|
|
||||||
|
output "service_definition" {
|
||||||
|
description = "General service definition with optional ingress configuration"
|
||||||
|
value = {
|
||||||
|
name = local.container_name
|
||||||
|
primary_port = local.exposed_port
|
||||||
|
endpoint = "http://${local.container_name}:${local.exposed_port}"
|
||||||
|
hostnames = local.hostnames
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
output "container_name" {
|
|
||||||
description = "The name of the ActualBudget container"
|
|
||||||
value = module.actualbudget.container_name
|
|
||||||
}
|
|
||||||
|
|
||||||
output "container_id" {
|
|
||||||
description = "The ID of the ActualBudget container"
|
|
||||||
value = module.actualbudget.container_id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "image_id" {
|
|
||||||
description = "The ID of the ActualBudget image"
|
|
||||||
value = module.actualbudget.image_id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "ip_address" {
|
|
||||||
description = "The IP address of the ActualBudget container"
|
|
||||||
value = module.actualbudget.ip_address
|
|
||||||
}
|
|
||||||
|
|
||||||
output "local_url" {
|
|
||||||
description = "The local URL to access the ActualBudget interface"
|
|
||||||
value = "http://localhost:${var.port}"
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
variable "container_name" {
|
|
||||||
description = "Name of the ActualBudget container"
|
|
||||||
type = string
|
|
||||||
default = "actualbudget"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "timezone" {
|
|
||||||
description = "Timezone for the container"
|
|
||||||
type = string
|
|
||||||
default = "UTC"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "image_tag" {
|
|
||||||
description = "Tag of the ActualBudget image to use"
|
|
||||||
type = string
|
|
||||||
default = "latest"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "port" {
|
|
||||||
description = "External port for ActualBudget server"
|
|
||||||
type = number
|
|
||||||
default = 5006
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "data_volume_path" {
|
|
||||||
description = "Host path for ActualBudget data volume"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "puid" {
|
|
||||||
description = "User ID for the container"
|
|
||||||
type = number
|
|
||||||
default = 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "pgid" {
|
|
||||||
description = "Group ID for the container"
|
|
||||||
type = number
|
|
||||||
default = 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "monitoring" {
|
|
||||||
description = "Enable monitoring for the container via Watchtower"
|
|
||||||
type = bool
|
|
||||||
default = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "networks" {
|
|
||||||
description = "List of networks to which the container should be attached"
|
|
||||||
type = list(string)
|
|
||||||
default = []
|
|
||||||
}
|
|
||||||
3
modules/20-services-apps/emulatorjs/.env.example
Normal file
3
modules/20-services-apps/emulatorjs/.env.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
EMULATORJS_FRONTEND_PORT=5823
|
||||||
|
EMULATORJS_CONFIG_PORT=5824
|
||||||
|
EMULATORJS_BACKEND_PORT=5825
|
||||||
@@ -1,70 +1,78 @@
|
|||||||
// EmulatorJS module for retro game emulation
|
terraform {
|
||||||
// This module configures an EmulatorJS container with the specified volumes
|
required_providers {
|
||||||
|
dotenv = {
|
||||||
locals {
|
source = "germanbrew/dotenv"
|
||||||
container_name = var.container_name != "" ? var.container_name : "emulatorjs"
|
|
||||||
image_tag = var.image_tag != "" ? var.image_tag : "latest"
|
|
||||||
|
|
||||||
default_env_vars = {
|
|
||||||
TZ = var.timezone
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge default env vars with any additional ones provided
|
|
||||||
env_vars = merge(local.default_env_vars, var.additional_env_vars)
|
|
||||||
|
|
||||||
// Default volumes for EmulatorJS
|
|
||||||
default_volumes = [
|
|
||||||
{
|
|
||||||
host_path = var.config_volume_path
|
|
||||||
container_path = "/config"
|
|
||||||
read_only = false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
host_path = var.data_volume_path
|
|
||||||
container_path = "/data"
|
|
||||||
read_only = false
|
|
||||||
}
|
}
|
||||||
]
|
|
||||||
|
|
||||||
// Merge default volumes with any additional ones provided
|
|
||||||
volumes = concat(local.default_volumes, var.additional_volumes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the generic docker-service module to deploy EmulatorJS
|
variable "image_tag" {
|
||||||
module "emulatorjs" {
|
description = "The tag for the EmulatorJS container image"
|
||||||
source = "../../10-services-generic/docker-service"
|
type = string
|
||||||
|
default = "latest"
|
||||||
|
}
|
||||||
|
|
||||||
container_name = local.container_name
|
variable "volume_path" {
|
||||||
|
description = "Base directory for volumes"
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
container_name = "emulatorjs"
|
||||||
image = "linuxserver/emulatorjs"
|
image = "linuxserver/emulatorjs"
|
||||||
tag = local.image_tag
|
image_tag = var.image_tag != "" ? var.image_tag : "latest"
|
||||||
|
monitoring = true
|
||||||
restart_policy = var.restart_policy
|
env_file = "${path.module}/.env"
|
||||||
network_mode = "bridge"
|
frontend_port = provider::dotenv::get_by_key("EMULATORJS_FRONTEND_PORT", local.env_file)
|
||||||
|
config_port = provider::dotenv::get_by_key("EMULATORJS_CONFIG_PORT", local.env_file)
|
||||||
env_vars = local.env_vars
|
backend_port = provider::dotenv::get_by_key("EMULATORJS_BACKEND_PORT", local.env_file)
|
||||||
volumes = local.volumes
|
|
||||||
|
|
||||||
labels = var.labels
|
|
||||||
|
|
||||||
// Default ports for EmulatorJS
|
|
||||||
ports = [
|
ports = [
|
||||||
{
|
{
|
||||||
internal = 3000
|
internal = 3000
|
||||||
external = var.config_port
|
external = local.config_port
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
internal = 80
|
internal = 80
|
||||||
external = var.frontend_port
|
external = local.frontend_port
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
internal = 4001
|
internal = 4001
|
||||||
external = var.backend_port
|
external = local.backend_port
|
||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
volumes = [
|
||||||
// Enable monitoring for the container via Watchtower
|
{
|
||||||
monitoring = var.monitoring
|
host_path = "${var.volume_path}/config"
|
||||||
|
container_path = "/config"
|
||||||
|
read_only = false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
host_path = "${var.volume_path}/data"
|
||||||
|
container_path = "/data"
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
module "emulatorjs" {
|
||||||
|
source = "../../10-services-generic/docker-service"
|
||||||
|
|
||||||
|
container_name = local.container_name
|
||||||
|
image = local.image
|
||||||
|
tag = local.image_tag
|
||||||
|
volumes = local.volumes
|
||||||
|
ports = local.ports
|
||||||
|
monitoring = local.monitoring
|
||||||
|
}
|
||||||
|
|
||||||
|
output "service_definition" {
|
||||||
|
description = "General service definition with optional ingress configuration"
|
||||||
|
value = {
|
||||||
|
name = module.emulatorjs.container_name
|
||||||
|
primary_port = local.frontend_port
|
||||||
|
endpoint = "http://${module.emulatorjs.container_name}:${local.frontend_port}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
// Outputs for the EmulatorJS module
|
|
||||||
|
|
||||||
output "container_name" {
|
|
||||||
description = "Name of the created EmulatorJS container"
|
|
||||||
value = module.emulatorjs.container_name
|
|
||||||
}
|
|
||||||
|
|
||||||
output "container_id" {
|
|
||||||
description = "ID of the created EmulatorJS container"
|
|
||||||
value = module.emulatorjs.container_id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "image_id" {
|
|
||||||
description = "ID of the EmulatorJS image used"
|
|
||||||
value = module.emulatorjs.image_id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "frontend_url" {
|
|
||||||
description = "URL to access the EmulatorJS frontend interface"
|
|
||||||
value = "http://localhost:${var.frontend_port}"
|
|
||||||
}
|
|
||||||
|
|
||||||
output "config_url" {
|
|
||||||
description = "URL to access the EmulatorJS web configuration interface"
|
|
||||||
value = "http://localhost:${var.config_port}"
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
variable "container_name" {
|
|
||||||
description = "Name for the EmulatorJS container"
|
|
||||||
type = string
|
|
||||||
default = "emulatorjs"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "image_tag" {
|
|
||||||
description = "The tag for the EmulatorJS container image"
|
|
||||||
type = string
|
|
||||||
default = "latest"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "restart_policy" {
|
|
||||||
description = "Restart policy for the container"
|
|
||||||
type = string
|
|
||||||
default = "unless-stopped"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "timezone" {
|
|
||||||
description = "Timezone for the container"
|
|
||||||
type = string
|
|
||||||
default = "Etc/UTC"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "puid" {
|
|
||||||
description = "User ID the container will run as"
|
|
||||||
type = number
|
|
||||||
default = 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "pgid" {
|
|
||||||
description = "Group ID the container will run as"
|
|
||||||
type = number
|
|
||||||
default = 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "config_volume_path" {
|
|
||||||
description = "Host path for the EmulatorJS config directory"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "data_volume_path" {
|
|
||||||
description = "Host path for the EmulatorJS data directory"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "frontend_port" {
|
|
||||||
description = "External port for the EmulatorJS frontend"
|
|
||||||
type = number
|
|
||||||
default = 3000
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "config_port" {
|
|
||||||
description = "External port for the EmulatorJS configuration interface"
|
|
||||||
type = number
|
|
||||||
default = 8080
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "backend_port" {
|
|
||||||
description = "External port for the EmulatorJS backend"
|
|
||||||
type = number
|
|
||||||
default = 4001
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "additional_env_vars" {
|
|
||||||
description = "Additional environment variables for EmulatorJS"
|
|
||||||
type = map(string)
|
|
||||||
default = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "additional_volumes" {
|
|
||||||
description = "Additional volumes to mount in the container"
|
|
||||||
type = list(object({
|
|
||||||
host_path = string
|
|
||||||
container_path = string
|
|
||||||
read_only = bool
|
|
||||||
}))
|
|
||||||
default = []
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "labels" {
|
|
||||||
description = "Labels to set on the container"
|
|
||||||
type = map(string)
|
|
||||||
default = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "monitoring" {
|
|
||||||
description = "Enable monitoring for the container via Watchtower"
|
|
||||||
type = bool
|
|
||||||
default = true
|
|
||||||
}
|
|
||||||
7
modules/20-services-apps/watchtower/.env.example
Normal file
7
modules/20-services-apps/watchtower/.env.example
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
WATCHTOWER_CLEANUP=true
|
||||||
|
WATCHTOWER_POLL_INTERVAL=86400
|
||||||
|
WATCHTOWER_INCLUDE_STOPPED=false
|
||||||
|
WATCHTOWER_REVIVE_STOPPED=false
|
||||||
|
WATCHTOWER_ROLLING_RESTART=true
|
||||||
|
WATCHTOWER_NOTIFICATIONS=shoutrrr
|
||||||
|
WATCHTOWER_NOTIFICATION_URL=discord://token@webhook_id
|
||||||
@@ -1,58 +1,48 @@
|
|||||||
// Watchtower module for automatic Docker container updates
|
terraform {
|
||||||
// This module configures a Watchtower container that monitors and updates other containers
|
required_providers {
|
||||||
|
dotenv = {
|
||||||
|
source = "germanbrew/dotenv"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "image_tag" {
|
||||||
|
description = "The tag for the Watchtower container image"
|
||||||
|
type = string
|
||||||
|
default = "latest"
|
||||||
|
}
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
container_name = var.container_name != "" ? var.container_name : "watchtower"
|
container_name = "watchtower"
|
||||||
|
image = "containrrr/watchtower"
|
||||||
image_tag = var.image_tag != "" ? var.image_tag : "latest"
|
image_tag = var.image_tag != "" ? var.image_tag : "latest"
|
||||||
|
env_file = "${path.module}/.env"
|
||||||
|
|
||||||
default_env_vars = {
|
env_vars = {
|
||||||
TZ = var.timezone
|
WATCHTOWER_CLEANUP = provider::dotenv::get_by_key("WATCHTOWER_CLEANUP", local.env_file)
|
||||||
WATCHTOWER_CLEANUP = var.cleanup
|
WATCHTOWER_POLL_INTERVAL = provider::dotenv::get_by_key("WATCHTOWER_POLL_INTERVAL", local.env_file)
|
||||||
WATCHTOWER_POLL_INTERVAL = var.poll_interval
|
WATCHTOWER_INCLUDE_STOPPED = false
|
||||||
WATCHTOWER_INCLUDE_STOPPED = var.include_stopped
|
WATCHTOWER_REVIVE_STOPPED = false
|
||||||
WATCHTOWER_REVIVE_STOPPED = var.revive_stopped
|
WATCHTOWER_ROLLING_RESTART = true
|
||||||
WATCHTOWER_ROLLING_RESTART = var.rolling_restart
|
WATCHTOWER_NOTIFICATION_URL = provider::dotenv::get_by_key("WATCHTOWER_NOTIFICATION_URL", local.env_file)
|
||||||
WATCHTOWER_NOTIFICATION_URL = var.notification_url
|
WATCHTOWER_NOTIFICATIONS = provider::dotenv::get_by_key("WATCHTOWER_NOTIFICATIONS", local.env_file)
|
||||||
WATCHTOWER_NOTIFICATIONS = var.enable_notifications ? "shoutrrr" : ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge default env vars with any additional ones provided
|
volumes = [
|
||||||
env_vars = merge(local.default_env_vars, var.additional_env_vars)
|
|
||||||
|
|
||||||
// Default volumes for Docker socket access
|
|
||||||
default_volumes = [
|
|
||||||
{
|
{
|
||||||
host_path = "/var/run/docker.sock"
|
host_path = "/var/run/docker.sock"
|
||||||
container_path = "/var/run/docker.sock"
|
container_path = "/var/run/docker.sock"
|
||||||
read_only = true
|
read_only = true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
// Merge default volumes with any additional ones provided
|
|
||||||
volumes = concat(local.default_volumes, var.additional_volumes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the generic docker-service module to deploy Watchtower
|
|
||||||
module "watchtower" {
|
module "watchtower" {
|
||||||
source = "../../10-services-generic/docker-service"
|
source = "../../10-services-generic/docker-service"
|
||||||
|
|
||||||
container_name = local.container_name
|
container_name = local.container_name
|
||||||
image = "containrrr/watchtower"
|
image = local.image
|
||||||
tag = local.image_tag
|
tag = local.image_tag
|
||||||
|
|
||||||
restart_policy = var.restart_policy
|
|
||||||
network_mode = "bridge"
|
|
||||||
|
|
||||||
env_vars = local.env_vars
|
env_vars = local.env_vars
|
||||||
volumes = local.volumes
|
volumes = local.volumes
|
||||||
|
|
||||||
labels = var.labels
|
|
||||||
|
|
||||||
// Watchtower doesn't typically expose ports but we'll include the option
|
|
||||||
ports = var.ports
|
|
||||||
|
|
||||||
// Add monitoring label if enabled
|
|
||||||
monitoring = var.monitoring
|
|
||||||
|
|
||||||
depends_on = []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
output "container_name" {
|
|
||||||
description = "Name of the created Watchtower container"
|
|
||||||
value = module.watchtower.container_name
|
|
||||||
}
|
|
||||||
|
|
||||||
output "container_id" {
|
|
||||||
description = "ID of the created Watchtower container"
|
|
||||||
value = module.watchtower.container_id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "image_id" {
|
|
||||||
description = "ID of the Watchtower image used"
|
|
||||||
value = module.watchtower.image_id
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
variable "container_name" {
|
|
||||||
description = "Name for the Watchtower container"
|
|
||||||
type = string
|
|
||||||
default = "watchtower"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "image_tag" {
|
|
||||||
description = "The tag for the Watchtower container image"
|
|
||||||
type = string
|
|
||||||
default = "latest"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "restart_policy" {
|
|
||||||
description = "Restart policy for the container"
|
|
||||||
type = string
|
|
||||||
default = "unless-stopped"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "timezone" {
|
|
||||||
description = "Timezone for the container"
|
|
||||||
type = string
|
|
||||||
default = "Etc/UTC"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "cleanup" {
|
|
||||||
description = "Remove old images after updating"
|
|
||||||
type = bool
|
|
||||||
default = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "poll_interval" {
|
|
||||||
description = "Poll interval (in seconds) for checking for updates"
|
|
||||||
type = number
|
|
||||||
default = 86400 // Default: check once per day
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "include_stopped" {
|
|
||||||
description = "Include stopped containers when checking for updates"
|
|
||||||
type = bool
|
|
||||||
default = false
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "revive_stopped" {
|
|
||||||
description = "Restart stopped containers after updating"
|
|
||||||
type = bool
|
|
||||||
default = false
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "rolling_restart" {
|
|
||||||
description = "Restart containers one by one instead of all at once"
|
|
||||||
type = bool
|
|
||||||
default = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "notification_url" {
|
|
||||||
description = "URL for sending update notifications via shoutrrr"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "enable_notifications" {
|
|
||||||
description = "Enable shoutrrr notifications"
|
|
||||||
type = bool
|
|
||||||
default = false
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "additional_env_vars" {
|
|
||||||
description = "Additional environment variables for Watchtower"
|
|
||||||
type = map(string)
|
|
||||||
default = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "additional_volumes" {
|
|
||||||
description = "Additional volumes to mount in the container"
|
|
||||||
type = list(object({
|
|
||||||
host_path = string
|
|
||||||
container_path = string
|
|
||||||
read_only = bool
|
|
||||||
}))
|
|
||||||
default = []
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "labels" {
|
|
||||||
description = "Labels to set on the container"
|
|
||||||
type = map(string)
|
|
||||||
default = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "ports" {
|
|
||||||
description = "Ports to expose (Watchtower typically doesn't need ports exposed)"
|
|
||||||
type = list(object({
|
|
||||||
internal = number
|
|
||||||
external = number
|
|
||||||
protocol = string
|
|
||||||
}))
|
|
||||||
default = []
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "monitoring" {
|
|
||||||
description = "Enable monitoring for the container"
|
|
||||||
type = bool
|
|
||||||
default = true
|
|
||||||
}
|
|
||||||
40
outputs.tf
40
outputs.tf
@@ -1,33 +1,9 @@
|
|||||||
// Root outputs that expose important information from each environment
|
output "services" {
|
||||||
|
description = "Service definitions for all services"
|
||||||
// Network environment outputs
|
value = [
|
||||||
output "cloudflare_domain" {
|
for service in module.services.service_definitions : {
|
||||||
description = "Base domain for the homelab"
|
name = service.name
|
||||||
value = module.network.domain
|
endpoint = contains(keys(service), "hostnames") ? "${service.hostnames[0]}.${module.cloudflare_globals.domain}" : service.endpoint
|
||||||
}
|
}
|
||||||
|
]
|
||||||
output "homelab_cloudflared_tunnel_name" {
|
|
||||||
description = "Name of the Cloudflare tunnel"
|
|
||||||
value = module.network.homelab_cloudflared_tunnel_name
|
|
||||||
}
|
|
||||||
|
|
||||||
output "homelab_cloudflared_tunnel_cname_target" {
|
|
||||||
description = "CNAME target for the Cloudflare tunnel"
|
|
||||||
value = module.network.homelab_cloudflared_tunnel_cname_target
|
|
||||||
}
|
|
||||||
|
|
||||||
// Service URLs
|
|
||||||
output "actualbudget_local_url" {
|
|
||||||
description = "Local URL for accessing ActualBudget"
|
|
||||||
value = module.services.actualbudget_local_url
|
|
||||||
}
|
|
||||||
|
|
||||||
output "emulatorjs_frontend_url" {
|
|
||||||
description = "URL for the EmulatorJS frontend"
|
|
||||||
value = module.services.emulatorjs_frontend_url
|
|
||||||
}
|
|
||||||
|
|
||||||
output "emulatorjs_config_url" {
|
|
||||||
description = "URL for the EmulatorJS configuration"
|
|
||||||
value = module.services.emulatorjs_config_url
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,13 @@ terraform {
|
|||||||
source = "hashicorp/random"
|
source = "hashicorp/random"
|
||||||
version = "~> 3.5.1"
|
version = "~> 3.5.1"
|
||||||
}
|
}
|
||||||
|
dotenv = {
|
||||||
|
source = "germanbrew/dotenv"
|
||||||
|
version = "1.2.5"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
provider "cloudflare" {
|
provider "cloudflare" {
|
||||||
api_token = var.cloudflare_api_token
|
api_token = module.cloudflare_globals.cloudflare_api_token
|
||||||
}
|
}
|
||||||
|
|||||||
30
services/main.tf
Normal file
30
services/main.tf
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
locals {
|
||||||
|
module_dir = "../modules"
|
||||||
|
data_dir = module.system_globals.data_dir
|
||||||
|
}
|
||||||
|
|
||||||
|
module "system_globals" {
|
||||||
|
source = "${local.module_dir}/00-globals/system"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Docker network used for modules that needs to be exposed to the internet
|
||||||
|
// using Cloudflared
|
||||||
|
module "homelab_docker_network" {
|
||||||
|
source = "${local.module_dir}/01-networking/docker-network"
|
||||||
|
|
||||||
|
name = "homelab-network"
|
||||||
|
driver = "bridge"
|
||||||
|
attachable = true
|
||||||
|
subnet = "10.100.0.0/16"
|
||||||
|
}
|
||||||
|
|
||||||
|
module "actualbudget" {
|
||||||
|
source = "${local.module_dir}/20-services-apps/actualbudget"
|
||||||
|
volume_path = "${local.data_dir}/actual"
|
||||||
|
networks = [module.homelab_docker_network.name]
|
||||||
|
}
|
||||||
|
|
||||||
|
module "emulatorjs" {
|
||||||
|
source = "${local.module_dir}/20-services-apps/emulatorjs"
|
||||||
|
volume_path = "${local.data_dir}/emulatorjs"
|
||||||
|
}
|
||||||
15
services/outputs.tf
Normal file
15
services/outputs.tf
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// Services environment outputs
|
||||||
|
|
||||||
|
// Consolidated service definitions for networking
|
||||||
|
output "service_definitions" {
|
||||||
|
description = "Service definitions for all services"
|
||||||
|
value = [
|
||||||
|
module.actualbudget.service_definition,
|
||||||
|
module.emulatorjs.service_definition
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "homelab_docker_network_name" {
|
||||||
|
description = "The name of the Docker network"
|
||||||
|
value = module.homelab_docker_network.name
|
||||||
|
}
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# Example terraform.tfvars file
|
|
||||||
# Copy to terraform.tfvars and fill with your values
|
|
||||||
|
|
||||||
# Generic
|
|
||||||
timezone = "Australia/Sydney"
|
|
||||||
puid = 1000
|
|
||||||
pgid = 1000
|
|
||||||
data_dir = "/srv/docker_data"
|
|
||||||
|
|
||||||
# Watchtower
|
|
||||||
watchtower_enable_notifications = false
|
|
||||||
# watchtower_notification_url = "discord://token@webhookId"
|
|
||||||
|
|
||||||
# EmulatorJS
|
|
||||||
emulatorjs_frontend_port = 5823
|
|
||||||
emulatorjs_config_port = 5824
|
|
||||||
emulatorjs_backend_port = 5825
|
|
||||||
|
|
||||||
# ActualBudget
|
|
||||||
actualbudget_port = 5006
|
|
||||||
|
|
||||||
# Cloudflare
|
|
||||||
cloudflare_api_token = "your-cloudflare-api-token" # API token with required permissions
|
|
||||||
cloudflare_account_id = "your-cloudflare-account-id" # Found in Cloudflare dashboard URL
|
|
||||||
cloudflare_zone_id = "your-cloudflare-zone-id" # Found in the domain overview page
|
|
||||||
domain = "yourdomain.com" # Your domain on Cloudflare
|
|
||||||
79
variables.tf
79
variables.tf
@@ -1,79 +0,0 @@
|
|||||||
// Generic
|
|
||||||
variable "timezone" {
|
|
||||||
description = "Timezone for the system"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "puid" {
|
|
||||||
description = "User ID for the container"
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "pgid" {
|
|
||||||
description = "Group ID for the container"
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "data_dir" {
|
|
||||||
description = "Base directory for data volumes"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watchtower
|
|
||||||
variable "watchtower_enable_notifications" {
|
|
||||||
description = "Enable Watchtower update notifications"
|
|
||||||
type = bool
|
|
||||||
default = false
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "watchtower_notification_url" {
|
|
||||||
description = "Webhook URL for Watchtower notifications (Discord, Slack, etc.)"
|
|
||||||
type = string
|
|
||||||
sensitive = true // This flags the variable as sensitive in logs and outputs
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// EmulatorJS
|
|
||||||
variable "emulatorjs_frontend_port" {
|
|
||||||
description = "External port for the EmulatorJS frontend"
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "emulatorjs_config_port" {
|
|
||||||
description = "External port for the EmulatorJS configuration interface"
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "emulatorjs_backend_port" {
|
|
||||||
description = "External port for the EmulatorJS backend"
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
// ActualBudget
|
|
||||||
variable "actualbudget_port" {
|
|
||||||
description = "External port for the ActualBudget server"
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cloudflare
|
|
||||||
variable "cloudflare_api_token" {
|
|
||||||
description = "API token for Cloudflare with tunnel, DNS, and zone management permissions"
|
|
||||||
type = string
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "cloudflare_account_id" {
|
|
||||||
description = "Cloudflare account ID"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "cloudflare_zone_id" {
|
|
||||||
description = "Cloudflare zone ID for your domain"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "domain" {
|
|
||||||
description = "Base domain name (e.g., example.com)"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user