Compare commits
1 Commits
bce43c4a71
...
media-serv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c07f59d021 |
6
main.tf
6
main.tf
@@ -19,10 +19,6 @@ module "services" {
|
||||
source = "./services"
|
||||
}
|
||||
|
||||
locals {
|
||||
volume_host = "${module.system_globals.volume_host}/appdata"
|
||||
}
|
||||
|
||||
module "homelab_cloudflared_tunnel" {
|
||||
source = "./modules/01-networking/cloudflared-tunnel"
|
||||
cloudflare_account_id = module.cloudflare_globals.cloudflare_account_id
|
||||
@@ -44,7 +40,7 @@ module "homelab_caddy_proxy" {
|
||||
cloudflare_zone_id = module.cloudflare_globals.cloudflare_zone_id
|
||||
external_ip = module.cloudflare_globals.external_ip
|
||||
service_definitions = module.services.service_definitions
|
||||
volume_path = local.volume_host
|
||||
volume_path = module.system_globals.volume_host
|
||||
networks = [module.services.homelab_docker_network_name]
|
||||
monitoring = true
|
||||
}
|
||||
|
||||
@@ -63,6 +63,10 @@ locals {
|
||||
])
|
||||
}
|
||||
|
||||
resource "docker_volume" "caddy_config" {
|
||||
name = "${local.container_name}_config"
|
||||
}
|
||||
|
||||
// Create Caddyfile in the volume path
|
||||
resource "local_file" "caddyfile" {
|
||||
content = local.caddyfile_content
|
||||
@@ -111,12 +115,12 @@ module "caddy" {
|
||||
|
||||
ports = [
|
||||
{
|
||||
external = "80"
|
||||
external = "9080"
|
||||
internal = "80"
|
||||
protocol = "tcp"
|
||||
},
|
||||
{
|
||||
external = "443"
|
||||
external = "9443"
|
||||
internal = "443"
|
||||
protocol = "tcp"
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ resource "cloudflare_zero_trust_tunnel_cloudflared_config" "this" {
|
||||
}
|
||||
|
||||
module "dns_records" {
|
||||
source = "../../10-services-generic/cloudflare-dns"
|
||||
source = "../../10-services-generic/cloudflare-dns"
|
||||
zone_id = var.cloudflare_zone_id
|
||||
hostnames = [
|
||||
for rule in local.all_ingress_rules :
|
||||
|
||||
@@ -71,7 +71,7 @@ resource "docker_container" "service_container" {
|
||||
|
||||
# Set the network mode (bridge, host, etc.)
|
||||
network_mode = local.network_mode
|
||||
|
||||
|
||||
# Add host mappings (entries for /etc/hosts)
|
||||
dynamic "host" {
|
||||
for_each = var.host_mappings
|
||||
@@ -139,36 +139,15 @@ resource "docker_container" "service_container" {
|
||||
cpu_shares = var.cpu_shares
|
||||
|
||||
# Other container options
|
||||
dns = var.dns
|
||||
dns_search = var.dns_search
|
||||
hostname = var.hostname
|
||||
domainname = var.domainname
|
||||
user = var.user
|
||||
group_add = var.group_add
|
||||
working_dir = var.working_dir
|
||||
command = var.command
|
||||
entrypoint = var.entrypoint
|
||||
privileged = var.privileged
|
||||
destroy_grace_seconds = var.destroy_grace_seconds
|
||||
|
||||
# Linux capabilities controls
|
||||
dynamic "capabilities" {
|
||||
for_each = length(var.capabilities_add) > 0 || length(var.capabilities_drop) > 0 ? [1] : []
|
||||
content {
|
||||
add = var.capabilities_add
|
||||
drop = var.capabilities_drop
|
||||
}
|
||||
}
|
||||
|
||||
# Device mappings
|
||||
dynamic "devices" {
|
||||
for_each = var.devices
|
||||
content {
|
||||
host_path = devices.value.host_path
|
||||
container_path = devices.value.container_path
|
||||
permissions = devices.value.permissions
|
||||
}
|
||||
}
|
||||
dns = var.dns
|
||||
dns_search = var.dns_search
|
||||
hostname = var.hostname
|
||||
domainname = var.domainname
|
||||
user = var.user
|
||||
working_dir = var.working_dir
|
||||
command = var.command
|
||||
entrypoint = var.entrypoint
|
||||
privileged = var.privileged
|
||||
|
||||
# Set log options
|
||||
log_driver = var.log_driver
|
||||
|
||||
@@ -179,48 +179,12 @@ variable "entrypoint" {
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "group_add" {
|
||||
description = "Additional groups to add to the container"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "privileged" {
|
||||
description = "Run container in privileged mode"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
// Linux capabilities controls
|
||||
variable "capabilities_add" {
|
||||
description = "Linux capabilities to add to the container"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "capabilities_drop" {
|
||||
description = "Linux capabilities to drop from the container"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
// Devices to pass through to container
|
||||
variable "devices" {
|
||||
description = "List of device mappings for the container"
|
||||
type = list(object({
|
||||
host_path = string
|
||||
container_path = string
|
||||
permissions = string
|
||||
}))
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "destroy_grace_seconds" {
|
||||
description = "Grace period in seconds before the container is destroyed"
|
||||
type = number
|
||||
default = 10
|
||||
}
|
||||
|
||||
// Logging options
|
||||
variable "log_driver" {
|
||||
description = "Log driver for the container"
|
||||
@@ -231,8 +195,8 @@ variable "log_driver" {
|
||||
variable "log_opts" {
|
||||
description = "Log driver options"
|
||||
type = map(string)
|
||||
default = {
|
||||
max-size = "10m"
|
||||
max-file = "3"
|
||||
default = {
|
||||
max-size = "10m"
|
||||
max-file = "3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ output "service_definition" {
|
||||
name = local.container_name
|
||||
primary_port = local.exposed_port
|
||||
endpoint = "http://${local.container_name}:${local.exposed_port}"
|
||||
subdomains = local.subdomains
|
||||
publish_via = "tunnel"
|
||||
subdomains = local.subdomains
|
||||
publish_via = "tunnel"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +193,6 @@ output "service_definition" {
|
||||
endpoint = "http://${local.container_name}:${local.affine_internal_port}"
|
||||
subdomains = ["notes"]
|
||||
publish_via = "reverse_proxy"
|
||||
proxied = true
|
||||
proxied = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# Example secrets for the *arr stack (used via terraform-provider-dotenv)
|
||||
# Copy to .env in this same directory and fill in values.
|
||||
|
||||
# Required keys for helpers
|
||||
SONARR_API_KEY=
|
||||
RADARR_API_KEY=
|
||||
LIDARR_API_KEY=
|
||||
QBITTORRENT_USERNAME=
|
||||
QBITTORRENT_PASSWORD=
|
||||
|
||||
# Optional keys for Flaresolverr
|
||||
LOG_LEVEL=info
|
||||
LOG_HTML=false
|
||||
CAPTCHA_SOLVER=none
|
||||
|
||||
# Optional keys for Decluttarr
|
||||
# Uncomment and adjust as needed. Defaults shown here align with the README.
|
||||
#DECLUTTARR_LOG_LEVEL=INFO
|
||||
#DECLUTTARR_TEST_RUN=False
|
||||
#DECLUTTARR_REMOVE_TIMER=10
|
||||
#DECLUTTARR_REMOVE_FAILED=True
|
||||
#DECLUTTARR_REMOVE_FAILED_IMPORTS=True
|
||||
#DECLUTTARR_REMOVE_METADATA_MISSING=True
|
||||
#DECLUTTARR_REMOVE_MISSING_FILES=True
|
||||
#DECLUTTARR_REMOVE_ORPHANS=True
|
||||
#DECLUTTARR_REMOVE_SLOW=True
|
||||
#DECLUTTARR_REMOVE_STALLED=True
|
||||
#DECLUTTARR_REMOVE_UNMONITORED=True
|
||||
#DECLUTTARR_RUN_PERIODIC_RESCANS=
|
||||
#DECLUTTARR_PERMITTED_ATTEMPTS=3
|
||||
#DECLUTTARR_REMOVAL_QBIT_TAG=stalled
|
||||
#DECLUTTARR_MIN_DOWNLOAD_SPEED=100
|
||||
#DECLUTTARR_FAILED_IMPORT_MESSAGE_PATTERNS=
|
||||
#DECLUTTARR_IGNORED_DOWNLOAD_CLIENTS=
|
||||
@@ -1,140 +0,0 @@
|
||||
# *arr Stack Module
|
||||
|
||||
This module deploys the *arr media stack components as Docker containers and provides a service definition for Jellyseerr to be published via your reverse proxy.
|
||||
|
||||
## Overview
|
||||
|
||||
The module includes the following containers:
|
||||
|
||||
- Sonarr (TV)
|
||||
- Radarr (Movies)
|
||||
- Lidarr (Music)
|
||||
- Bazarr (Subtitles)
|
||||
- Prowlarr (Indexers)
|
||||
- Jellyseerr (Requests) — published via reverse proxy
|
||||
- Flaresolverr (optional helper)
|
||||
- Unpackerr (post-processing)
|
||||
- Cleanuparr (cleanup helpers)
|
||||
- Decluttarr (torrent queue cleanup helper)
|
||||
|
||||
All containers share a common `/data` mount (media root) and are attached to the media Docker network. Only Jellyseerr is also attached to the proxy network for reachability by Caddy.
|
||||
|
||||
## Usage
|
||||
|
||||
```hcl
|
||||
module "arr" {
|
||||
source = "./modules/20-services-apps/arr"
|
||||
volume_path = "/srv/appdata/arr" # host path for config directories
|
||||
data_path = "/srv/data" # host media root, mounted as /data in containers
|
||||
downloads_path = "/srv/data/torrents" # host downloads dir, mounted for unpackerr
|
||||
networks = [module.media_docker_network.name]
|
||||
proxy_networks = [module.homelab_docker_network.name] # so Jellyseerr is reachable by Caddy
|
||||
}
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
| Variable | Description | Type | Default |
|
||||
| ------------------ | ------------------------------------------------------------------------------- | -------------- | --------------- |
|
||||
| `volume_path` | Base directory for config volumes for the *arr stack | `string` | - |
|
||||
| `data_path` | Base directory for media/data mounted at `/data` | `string` | - |
|
||||
| `downloads_path` | Directory for downloads mounted at `/data/torrents` (Unpackerr) | `string` | - |
|
||||
| `networks` | Networks to attach all containers to | `list(string)` | `[]` |
|
||||
| `proxy_networks` | Extra networks attached only to published services (Jellyseerr) | `list(string)` | `[]` |
|
||||
| `qbittorrent_host` | Hostname to reach qBittorrent (use `gluetun` when qBittorrent shares Gluetun) | `string` | `"qbittorrent"` |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
| -------------------- | --------------------------------------------------------------------- |
|
||||
| `service_definition` | Service definition for integration with networking modules |
|
||||
|
||||
## Service Definition
|
||||
|
||||
This module outputs a service definition that is used by the networking modules to expose the service.
|
||||
|
||||
```hcl
|
||||
{
|
||||
name = "jellyseerr"
|
||||
primary_port = 5055
|
||||
endpoint = "http://jellyseerr:5055"
|
||||
subdomains = ["req"]
|
||||
publish_via = "reverse_proxy"
|
||||
proxied = false
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables (.env)
|
||||
|
||||
This module reads secrets via the `dotenv` provider from a `.env` file located in this module directory. Use `.env.example` as a template.
|
||||
|
||||
Required/used keys:
|
||||
|
||||
- `SONARR_API_KEY` — for Unpackerr, Cleanuparr, Decluttarr
|
||||
- `RADARR_API_KEY` — for Unpackerr, Cleanuparr, Decluttarr
|
||||
- `LIDARR_API_KEY` — for Decluttarr
|
||||
- `QBITTORRENT_USERNAME` — for Decluttarr
|
||||
- `QBITTORRENT_PASSWORD` — for Cleanuparr and Decluttarr
|
||||
- `LOG_LEVEL` — for Flaresolverr (optional)
|
||||
- `LOG_HTML` — for Flaresolverr (optional)
|
||||
- `CAPTCHA_SOLVER` — for Flaresolverr (optional)
|
||||
|
||||
Optional Decluttarr keys (override defaults as needed):
|
||||
|
||||
- `DECLUTTARR_LOG_LEVEL` (default: `INFO`)
|
||||
- `DECLUTTARR_TEST_RUN` (default: `False`)
|
||||
- `DECLUTTARR_REMOVE_TIMER` (default: `10`)
|
||||
- `DECLUTTARR_REMOVE_FAILED` (default: `True`)
|
||||
- `DECLUTTARR_REMOVE_FAILED_IMPORTS` (default: `True`)
|
||||
- `DECLUTTARR_REMOVE_METADATA_MISSING` (default: `True`)
|
||||
- `DECLUTTARR_REMOVE_MISSING_FILES` (default: `True`)
|
||||
- `DECLUTTARR_REMOVE_ORPHANS` (default: `True`)
|
||||
- `DECLUTTARR_REMOVE_SLOW` (default: `True`)
|
||||
- `DECLUTTARR_REMOVE_STALLED` (default: `True`)
|
||||
- `DECLUTTARR_REMOVE_UNMONITORED` (default: `True`)
|
||||
- `DECLUTTARR_RUN_PERIODIC_RESCANS` (default: empty)
|
||||
- `DECLUTTARR_PERMITTED_ATTEMPTS` (default: `3`)
|
||||
- `DECLUTTARR_REMOVAL_QBIT_TAG` (default: `stalled`)
|
||||
- `DECLUTTARR_MIN_DOWNLOAD_SPEED` (default: `100`)
|
||||
- `DECLUTTARR_FAILED_IMPORT_MESSAGE_PATTERNS` (default: empty)
|
||||
- `DECLUTTARR_IGNORED_DOWNLOAD_CLIENTS` (default: empty)
|
||||
|
||||
Note: `TZ`, `PUID`, `PGID` are injected automatically by the generic docker-service module from `modules/00-globals/system` and do not need to be in this `.env`.
|
||||
|
||||
## Data Persistence
|
||||
|
||||
- Each app stores configuration under `${volume_path}/<app>/...` mounted to `/config` (or as noted by the specific app).
|
||||
- Media library and downloads are accessed under `/data` inside containers, pointing to `data_path` on the host.
|
||||
- Unpackerr mounts `downloads_path` to `/data/torrents`.
|
||||
|
||||
## Networking
|
||||
|
||||
- All containers join `networks` (media network).
|
||||
- Jellyseerr additionally joins `proxy_networks` for reverse proxy reachability.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- No explicit inter-container dependencies are defined. Healthchecks are provided for stable orchestration.
|
||||
- Decluttarr expects to reach `sonarr`, `radarr`, and `lidarr` via internal DNS and qBittorrent at `http://<qbittorrent_host>:8080`. When qBittorrent is routed via Gluetun using `network_mode=container:gluetun`, set `qbittorrent_host = "gluetun"`. Ensure they share the same Docker network.
|
||||
|
||||
## Integration with Networking Modules
|
||||
|
||||
This service is configured to be exposed through the Caddy reverse proxy, set by `publish_via = "reverse_proxy"`.
|
||||
|
||||
## Example Integration in Main Configuration
|
||||
|
||||
```hcl
|
||||
# In services/main.tf
|
||||
module "arr" {
|
||||
source = "${local.module_dir}/20-services-apps/arr"
|
||||
volume_path = "${local.volume_host}/arr"
|
||||
data_path = local.data_host
|
||||
downloads_path = "${local.data_host}/torrents"
|
||||
networks = [module.media_docker_network.name]
|
||||
proxy_networks = [module.homelab_docker_network.name]
|
||||
# If qBittorrent shares Gluetun's network namespace, arr should reach it via 'gluetun'
|
||||
qbittorrent_host = "gluetun"
|
||||
}
|
||||
```
|
||||
|
||||
The service definition is exported by the `services` module as `module.services.service_definitions` and consumed by networking modules in the root `main.tf`.
|
||||
@@ -1,280 +0,0 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
dotenv = { source = "germanbrew/dotenv" }
|
||||
}
|
||||
}
|
||||
|
||||
variable "volume_path" {
|
||||
description = "Base directory for config volumes for *arr stack"
|
||||
type = string
|
||||
}
|
||||
variable "data_path" {
|
||||
description = "Base directory for media/data mounted at /data"
|
||||
type = string
|
||||
}
|
||||
variable "downloads_path" {
|
||||
description = "Directory for downloads mounted at /data/torrents"
|
||||
type = string
|
||||
}
|
||||
variable "networks" {
|
||||
description = "Networks to attach all containers to"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
variable "proxy_networks" {
|
||||
description = "Extra networks to attach only to published services (e.g., Jellyseerr)"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "qbittorrent_host" {
|
||||
description = "Hostname to reach qBittorrent (use 'qbittorrent' normally, 'gluetun' when qBittorrent shares Gluetun network)"
|
||||
type = string
|
||||
default = "qbittorrent"
|
||||
}
|
||||
|
||||
locals {
|
||||
env_file = "${path.module}/.env"
|
||||
monitoring = true
|
||||
|
||||
sonarr_name = "sonarr"
|
||||
radarr_name = "radarr"
|
||||
lidarr_name = "lidarr"
|
||||
bazarr_name = "bazarr"
|
||||
prowlarr_name = "prowlarr"
|
||||
jellyseerr_name = "jellyseerr"
|
||||
flaresolverr_name = "flaresolverr"
|
||||
unpackerr_name = "unpackerr"
|
||||
cleanuparr_name = "cleanuparr"
|
||||
decluttarr_name = "decluttarr"
|
||||
|
||||
sonarr_image = "lscr.io/linuxserver/sonarr"
|
||||
radarr_image = "lscr.io/linuxserver/radarr"
|
||||
lidarr_image = "lscr.io/linuxserver/lidarr"
|
||||
bazarr_image = "lscr.io/linuxserver/bazarr"
|
||||
prowlarr_image = "lscr.io/linuxserver/prowlarr"
|
||||
jellyseerr_image = "ghcr.io/fallenbagel/jellyseerr"
|
||||
flaresolverr_image = "21hsmw/flaresolverr"
|
||||
unpackerr_image = "ghcr.io/unpackerr/unpackerr"
|
||||
cleanuparr_image = "ghcr.io/cleanuparr/cleanuparr"
|
||||
decluttarr_image = "ghcr.io/manimatter/decluttarr"
|
||||
|
||||
sonarr_port = 8989
|
||||
radarr_port = 7878
|
||||
lidarr_port = 8686
|
||||
bazarr_port = 6767
|
||||
prowlarr_port = 9696
|
||||
jellyseerr_port = 5055
|
||||
flaresolverr_port = 8191
|
||||
|
||||
lidarr_healthcheck = { test = ["CMD", "curl", "--fail", "http://127.0.0.1:${local.lidarr_port}/lidarr/ping"], interval = "60s", timeout = "5s", retries = 10 }
|
||||
bazarr_healthcheck = { test = ["CMD", "curl", "--fail", "http://127.0.0.1:${local.bazarr_port}/bazarr/ping"], interval = "60s", timeout = "5s", retries = 10 }
|
||||
jellyseerr_healthcheck = { test = ["CMD", "wget", "http://127.0.0.1:${local.jellyseerr_port}/api/v1/status", "-qO", "/dev/null"], interval = "60s", timeout = "5s", retries = 10 }
|
||||
|
||||
jellyseerr_env = { LOG_LEVEL = "debug" }
|
||||
flaresolverr_env = {
|
||||
LOG_LEVEL = try(provider::dotenv::get_by_key("LOG_LEVEL", local.env_file), "")
|
||||
LOG_HTML = try(provider::dotenv::get_by_key("LOG_HTML", local.env_file), "")
|
||||
CAPTCHA_SOLVER = try(provider::dotenv::get_by_key("CAPTCHA_SOLVER", local.env_file), "")
|
||||
}
|
||||
unpackerr_env = {
|
||||
UN_SONARR_0_URL = "http://${local.sonarr_name}:${local.sonarr_port}/sonarr"
|
||||
UN_SONARR_0_API_KEY = provider::dotenv::get_by_key("SONARR_API_KEY", local.env_file)
|
||||
UN_RADARR_0_URL = "http://${local.radarr_name}:${local.radarr_port}/radarr"
|
||||
UN_RADARR_0_API_KEY = provider::dotenv::get_by_key("RADARR_API_KEY", local.env_file)
|
||||
}
|
||||
cleanuparr_env = {
|
||||
QUEUECLEANER__ENABLED = true
|
||||
QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES = 3
|
||||
QUEUECLEANER__STALLED_MAX_STRIKES = 3
|
||||
QUEUECLEANER__DOWNLOADING_METADATA_MAX_STRIKES = 3
|
||||
QUEUECLEANER__STALLED_RESET_STRIKES_ON_PROGRESS = true
|
||||
TRIGGERS__QUEUECLEANER = "0 0 0/1 * * ?"
|
||||
CONTENTBLOCKER__ENABLED = true
|
||||
CONTENTBLOCKER__IGNORED_DOWNLOADS_PATH = "/usr/ignored.txt"
|
||||
TRIGGERS__CONTENTBLOCKER = "0 0 0/1 * * ?"
|
||||
DOWNLOAD_CLIENT = "qBittorrent"
|
||||
QBITTORRENT__URL = "http://${var.qbittorrent_host}:8080"
|
||||
QBITTORRENT__PASSWORD = provider::dotenv::get_by_key("QBITTORRENT_PASSWORD", local.env_file)
|
||||
SONARR__ENABLED = true
|
||||
SONARR__BLOCK__PATH = "/usr/blacklist.json"
|
||||
SONARR__INSTANCES__0__URL = "http://${local.sonarr_name}:${local.sonarr_port}/sonarr"
|
||||
SONARR__INSTANCES__0__APIKEY = provider::dotenv::get_by_key("SONARR_API_KEY", local.env_file)
|
||||
RADARR__ENABLED = true
|
||||
RADARR__BLOCK__PATH = "/usr/blacklist.json"
|
||||
RADARR__INSTANCES__0__URL = "http://${local.radarr_name}:${local.radarr_port}/radarr"
|
||||
RADARR__INSTANCES__0__APIKEY = provider::dotenv::get_by_key("RADARR_API_KEY", local.env_file)
|
||||
}
|
||||
|
||||
decluttarr_env = {
|
||||
RADARR_URL = "http://${local.radarr_name}:${local.radarr_port}/radarr"
|
||||
RADARR_KEY = provider::dotenv::get_by_key("RADARR_API_KEY", local.env_file)
|
||||
SONARR_URL = "http://${local.sonarr_name}:${local.sonarr_port}/sonarr"
|
||||
SONARR_KEY = provider::dotenv::get_by_key("SONARR_API_KEY", local.env_file)
|
||||
LIDARR_URL = "http://${local.lidarr_name}:${local.lidarr_port}/lidarr"
|
||||
LIDARR_KEY = provider::dotenv::get_by_key("LIDARR_API_KEY", local.env_file)
|
||||
QBITTORRENT_URL = "http://${var.qbittorrent_host}:8080"
|
||||
QBITTORRENT_USERNAME = provider::dotenv::get_by_key("QBITTORRENT_USERNAME", local.env_file)
|
||||
QBITTORRENT_PASSWORD = provider::dotenv::get_by_key("QBITTORRENT_PASSWORD", local.env_file)
|
||||
LOG_LEVEL = try(provider::dotenv::get_by_key("DECLUTTARR_LOG_LEVEL", local.env_file), "")
|
||||
TEST_RUN = try(provider::dotenv::get_by_key("DECLUTTARR_TEST_RUN", local.env_file), "")
|
||||
REMOVE_TIMER = try(provider::dotenv::get_by_key("DECLUTTARR_REMOVE_TIMER", local.env_file), "")
|
||||
REMOVE_FAILED = try(provider::dotenv::get_by_key("DECLUTTARR_REMOVE_FAILED", local.env_file), "")
|
||||
REMOVE_FAILED_IMPORTS = try(provider::dotenv::get_by_key("DECLUTTARR_REMOVE_FAILED_IMPORTS", local.env_file), "")
|
||||
REMOVE_METADATA_MISSING = try(provider::dotenv::get_by_key("DECLUTTARR_REMOVE_METADATA_MISSING", local.env_file), "")
|
||||
REMOVE_MISSING_FILES = try(provider::dotenv::get_by_key("DECLUTTARR_REMOVE_MISSING_FILES", local.env_file), "")
|
||||
REMOVE_ORPHANS = try(provider::dotenv::get_by_key("DECLUTTARR_REMOVE_ORPHANS", local.env_file), "")
|
||||
REMOVE_SLOW = try(provider::dotenv::get_by_key("DECLUTTARR_REMOVE_SLOW", local.env_file), "")
|
||||
REMOVE_STALLED = try(provider::dotenv::get_by_key("DECLUTTARR_REMOVE_STALLED", local.env_file), "")
|
||||
REMOVE_UNMONITORED = try(provider::dotenv::get_by_key("DECLUTTARR_REMOVE_UNMONITORED", local.env_file), "")
|
||||
RUN_PERIODIC_RESCANS = try(provider::dotenv::get_by_key("DECLUTTARR_RUN_PERIODIC_RESCANS", local.env_file), "")
|
||||
PERMITTED_ATTEMPTS = try(provider::dotenv::get_by_key("DECLUTTARR_PERMITTED_ATTEMPTS", local.env_file), "")
|
||||
NO_STALLED_REMOVAL_QBIT_TAG = try(provider::dotenv::get_by_key("DECLUTTARR_REMOVAL_QBIT_TAG", local.env_file), "")
|
||||
MIN_DOWNLOAD_SPEED = try(provider::dotenv::get_by_key("DECLUTTARR_MIN_DOWNLOAD_SPEED", local.env_file), "")
|
||||
FAILED_IMPORT_MESSAGE_PATTERNS = try(provider::dotenv::get_by_key("DECLUTTARR_FAILED_IMPORT_MESSAGE_PATTERNS", local.env_file), "")
|
||||
IGNORED_DOWNLOAD_CLIENTS = try(provider::dotenv::get_by_key("DECLUTTARR_IGNORED_DOWNLOAD_CLIENTS", local.env_file), "")
|
||||
}
|
||||
}
|
||||
|
||||
# Sonarr
|
||||
module "sonarr" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.sonarr_name
|
||||
image = local.sonarr_image
|
||||
volumes = [
|
||||
{ host_path = "${var.volume_path}/sonarr", container_path = "/config", read_only = false },
|
||||
{ host_path = var.data_path, container_path = "/data", read_only = false }
|
||||
]
|
||||
networks = var.networks
|
||||
monitoring = local.monitoring
|
||||
ports = [{ internal = local.sonarr_port, external = local.sonarr_port, protocol = "tcp" }]
|
||||
}
|
||||
|
||||
# Radarr
|
||||
module "radarr" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.radarr_name
|
||||
image = local.radarr_image
|
||||
volumes = [
|
||||
{ host_path = "${var.volume_path}/radarr", container_path = "/config", read_only = false },
|
||||
{ host_path = var.data_path, container_path = "/data", read_only = false }
|
||||
]
|
||||
networks = var.networks
|
||||
monitoring = local.monitoring
|
||||
ports = [{ internal = local.radarr_port, external = local.radarr_port, protocol = "tcp" }]
|
||||
}
|
||||
|
||||
# Lidarr
|
||||
module "lidarr" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.lidarr_name
|
||||
image = local.lidarr_image
|
||||
volumes = [
|
||||
{ host_path = "${var.volume_path}/lidarr", container_path = "/config", read_only = false },
|
||||
{ host_path = var.data_path, container_path = "/data", read_only = false }
|
||||
]
|
||||
networks = var.networks
|
||||
monitoring = local.monitoring
|
||||
healthcheck = local.lidarr_healthcheck
|
||||
ports = [{ internal = local.lidarr_port, external = local.lidarr_port, protocol = "tcp" }]
|
||||
}
|
||||
|
||||
# Bazarr
|
||||
module "bazarr" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.bazarr_name
|
||||
image = local.bazarr_image
|
||||
volumes = [
|
||||
{ host_path = "${var.volume_path}/bazarr/config", container_path = "/config", read_only = false },
|
||||
{ host_path = var.data_path, container_path = "/data", read_only = false }
|
||||
]
|
||||
networks = var.networks
|
||||
monitoring = local.monitoring
|
||||
healthcheck = local.bazarr_healthcheck
|
||||
ports = [{ internal = local.bazarr_port, external = local.bazarr_port, protocol = "tcp" }]
|
||||
}
|
||||
|
||||
# Prowlarr
|
||||
module "prowlarr" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.prowlarr_name
|
||||
image = local.prowlarr_image
|
||||
volumes = [
|
||||
{ host_path = "${var.volume_path}/prowlarr", container_path = "/config", read_only = false }
|
||||
]
|
||||
networks = var.networks
|
||||
monitoring = local.monitoring
|
||||
ports = [{ internal = local.prowlarr_port, external = local.prowlarr_port, protocol = "tcp" }]
|
||||
}
|
||||
|
||||
# Jellyseerr (published via reverse proxy)
|
||||
module "jellyseerr" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.jellyseerr_name
|
||||
image = local.jellyseerr_image
|
||||
volumes = [{ host_path = "${var.volume_path}/jellyseerr", container_path = "/app/config", read_only = false }]
|
||||
env_vars = local.jellyseerr_env
|
||||
networks = concat(var.networks, var.proxy_networks)
|
||||
monitoring = local.monitoring
|
||||
healthcheck = local.jellyseerr_healthcheck
|
||||
ports = [{ internal = local.jellyseerr_port, external = local.jellyseerr_port, protocol = "tcp" }]
|
||||
}
|
||||
|
||||
# Flaresolverr
|
||||
module "flaresolverr" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.flaresolverr_name
|
||||
image = local.flaresolverr_image
|
||||
tag = "nodriver"
|
||||
env_vars = local.flaresolverr_env
|
||||
networks = var.networks
|
||||
monitoring = local.monitoring
|
||||
ports = [{ internal = local.flaresolverr_port, external = local.flaresolverr_port, protocol = "tcp" }]
|
||||
}
|
||||
|
||||
# Unpackerr
|
||||
module "unpackerr" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.unpackerr_name
|
||||
image = local.unpackerr_image
|
||||
env_vars = local.unpackerr_env
|
||||
volumes = [{ host_path = var.downloads_path, container_path = "/data/torrents", read_only = false }]
|
||||
networks = var.networks
|
||||
monitoring = local.monitoring
|
||||
}
|
||||
|
||||
# Cleanuparr
|
||||
module "cleanuparr" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.cleanuparr_name
|
||||
image = local.cleanuparr_image
|
||||
env_vars = local.cleanuparr_env
|
||||
volumes = [
|
||||
{ host_path = "${var.volume_path}/cleanuparr/logs", container_path = "/var/logs", read_only = false },
|
||||
{ host_path = "${var.volume_path}/cleanuparr/ignored.txt", container_path = "/usr/ignored.txt", read_only = false },
|
||||
{ host_path = "${var.volume_path}/cleanuparr/blacklist.json", container_path = "/usr/blacklist.json", read_only = false }
|
||||
]
|
||||
networks = var.networks
|
||||
monitoring = local.monitoring
|
||||
}
|
||||
|
||||
module "decluttarr" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.decluttarr_name
|
||||
image = local.decluttarr_image
|
||||
env_vars = local.decluttarr_env
|
||||
networks = var.networks
|
||||
monitoring = local.monitoring
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "Service definition for Jellyseerr (reverse proxy)"
|
||||
value = {
|
||||
name = local.jellyseerr_name
|
||||
primary_port = local.jellyseerr_port
|
||||
endpoint = "http://${local.jellyseerr_name}:${local.jellyseerr_port}"
|
||||
subdomains = ["req"]
|
||||
publish_via = "reverse_proxy"
|
||||
proxied = true
|
||||
}
|
||||
}
|
||||
@@ -42,13 +42,13 @@ variable "timezone" {
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "calibre-web-automated"
|
||||
calibre_image = "crocodilestick/calibre-web-automated"
|
||||
calibre_tag = var.image_tag
|
||||
monitoring = true
|
||||
env_file = "${path.module}/.env"
|
||||
calibre_internal_port = 8083
|
||||
docker_mods = "lscr.io/linuxserver/mods:universal-calibre-v7.16.0"
|
||||
container_name = "calibre-web-automated"
|
||||
calibre_image = "crocodilestick/calibre-web-automated"
|
||||
calibre_tag = var.image_tag
|
||||
monitoring = true
|
||||
env_file = "${path.module}/.env"
|
||||
calibre_internal_port = 8083
|
||||
docker_mods = "lscr.io/linuxserver/mods:universal-calibre-v7.16.0"
|
||||
|
||||
# Define volumes
|
||||
calibre_volumes = [
|
||||
@@ -99,6 +99,6 @@ output "service_definition" {
|
||||
endpoint = "http://${local.container_name}:${local.calibre_internal_port}"
|
||||
subdomains = ["calibre"]
|
||||
publish_via = "reverse_proxy"
|
||||
proxied = true
|
||||
proxied = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
# Copyparty Module
|
||||
|
||||
This module deploys [copyparty](https://github.com/9001/copyparty), a portable file server.
|
||||
|
||||
## Overview
|
||||
|
||||
The copyparty module:
|
||||
|
||||
- Deploys one Docker container: `copyparty`.
|
||||
- Mounts a volume for configuration and another for the files to be shared.
|
||||
- Provides a service definition for integration with networking modules.
|
||||
|
||||
## Usage
|
||||
|
||||
```hcl
|
||||
module "copyparty" {
|
||||
source = "./modules/20-services-apps/copyparty"
|
||||
fileshare_path = "/path/to/your/fileshare/top/folder"
|
||||
config_path = "/path/to/copyparty/config"
|
||||
networks = ["homelab-network"]
|
||||
}
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
| Variable | Description |
|
||||
| ---------------- | ----------------------------------------------------------- |
|
||||
| `image_tag` | Tag of the copyparty image to use |
|
||||
| `fileshare_path` | Host path for the top folder of the file share |
|
||||
| `config_path` | Host path for copyparty configuration files |
|
||||
| `networks` | List of additional networks to which copyparty should be attached |
|
||||
| `puid` | User ID to run the container as |
|
||||
| `pgid` | Group ID to run the container as |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
| -------------------- | ---------------------------------------------------------- |
|
||||
| `service_definition` | Service definition for integration with networking modules |
|
||||
|
||||
## Service Definition
|
||||
|
||||
This module outputs a service definition that is used by the networking modules to expose the service.
|
||||
|
||||
```hcl
|
||||
{
|
||||
name = "copyparty"
|
||||
primary_port = 3923
|
||||
endpoint = "http://copyparty:3923"
|
||||
subdomains = ["files"]
|
||||
publish_via = "reverse_proxy"
|
||||
}
|
||||
```
|
||||
|
||||
## Data Persistence
|
||||
|
||||
Copyparty uses two volumes:
|
||||
|
||||
1. Configuration: `/cfg` in the container, mapped to `var.config_path` on the host.
|
||||
2. File Share: `/w` in the container, mapped to `var.fileshare_path` on the host.
|
||||
|
||||
## Integration with Networking Modules
|
||||
|
||||
This service is configured to be exposed through the Caddy reverse proxy, set by `publish_via = "reverse_proxy"`.
|
||||
|
||||
## Example Integration in Main Configuration
|
||||
|
||||
```hcl
|
||||
module "copyparty" {
|
||||
source = "./modules/20-services-apps/copyparty"
|
||||
fileshare_path = "/mnt/storage/files"
|
||||
config_path = "${module.system_globals.volume_host}/copyparty/config"
|
||||
networks = [module.services.homelab_docker_network_name]
|
||||
}
|
||||
|
||||
# The service definition is automatically included in the services output
|
||||
module "services" {
|
||||
source = "./modules/services"
|
||||
# ...
|
||||
service_definitions = [
|
||||
module.copyparty.service_definition,
|
||||
# Other service definitions
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -1,89 +0,0 @@
|
||||
variable "image_tag" {
|
||||
description = "The tag for the copyparty container image"
|
||||
type = string
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
variable "fileshare_path" {
|
||||
description = "Path to the top folder of the file share"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "config_path" {
|
||||
description = "Path to the configuration files for copyparty"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "List of networks to which the container should be attached"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "puid" {
|
||||
description = "User ID to run the container as"
|
||||
type = string
|
||||
default = "1000"
|
||||
}
|
||||
|
||||
variable "pgid" {
|
||||
description = "Group ID to run the container as"
|
||||
type = string
|
||||
default = "1000"
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "copyparty"
|
||||
image = "copyparty/ac"
|
||||
tag = var.image_tag
|
||||
monitoring = true
|
||||
internal_port = 3923
|
||||
user = "${var.puid}:${var.pgid}"
|
||||
volumes = [
|
||||
{
|
||||
host_path = var.config_path
|
||||
container_path = "/cfg"
|
||||
read_only = false
|
||||
},
|
||||
{
|
||||
host_path = var.fileshare_path
|
||||
container_path = "/w"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
env_vars = {
|
||||
LD_PRELOAD = "/usr/lib/libmimalloc-secure.so.2"
|
||||
PYTHONUNBUFFERED = "1"
|
||||
}
|
||||
}
|
||||
|
||||
module "copyparty" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.tag
|
||||
user = local.user
|
||||
volumes = local.volumes
|
||||
env_vars = local.env_vars
|
||||
networks = var.networks
|
||||
monitoring = local.monitoring
|
||||
destroy_grace_seconds = 15
|
||||
ports = [
|
||||
{
|
||||
internal = local.internal_port
|
||||
external = local.internal_port
|
||||
protocol = "tcp"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "General service definition with optional ingress configuration"
|
||||
value = {
|
||||
name = local.container_name
|
||||
primary_port = local.internal_port
|
||||
endpoint = "http://${local.container_name}:${local.internal_port}"
|
||||
subdomains = ["drive"]
|
||||
publish_via = "tunnel"
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
# Crawl4AI Configuration
|
||||
PORT=11235
|
||||
|
||||
# API Keys for LLM providers used by Crawl4AI
|
||||
# OpenAI API key for GPT models
|
||||
OPENAI_API_KEY=
|
||||
# DeepSeek API key
|
||||
DEEPSEEK_API_KEY=
|
||||
# Anthropic API key for Claude models
|
||||
ANTHROPIC_API_KEY=
|
||||
# Groq API key
|
||||
GROQ_API_KEY=
|
||||
# Together API key
|
||||
TOGETHER_API_KEY=
|
||||
# Mistral API key
|
||||
MISTRAL_API_KEY=
|
||||
# Google Gemini API token
|
||||
GEMINI_API_TOKEN=
|
||||
@@ -1,91 +0,0 @@
|
||||
# Crawl4AI Module
|
||||
|
||||
This module deploys [Crawl4AI](https://github.com/unclecode/crawl4ai), a web crawling and AI analysis tool, as a Docker container in the homelab environment.
|
||||
|
||||
## Overview
|
||||
|
||||
The Crawl4AI module:
|
||||
|
||||
- Deploys the `unclecode/crawl4ai` Docker container
|
||||
- Configures resource limits and reservations for memory
|
||||
- Provides shared memory access for Chrome/Chromium performance
|
||||
- Supports custom configuration through volume mounting
|
||||
- Provides service definition for integration with networking modules
|
||||
|
||||
## Usage
|
||||
|
||||
```hcl
|
||||
module "crawl4ai" {
|
||||
source = "./modules/20-services-apps/crawl4ai"
|
||||
volume_path = "/path/to/volumes"
|
||||
networks = ["homelab-network"]
|
||||
}
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
| Variable | Description | Type | Default |
|
||||
| --------------------- | ------------------------------------------------- | -------------- | ----------- |
|
||||
| `image_tag` | Tag of the Crawl4AI image to use | `string` | `"latest"` |
|
||||
| `volume_path` | Host path for Crawl4AI data volumes | `string` | - |
|
||||
| `networks` | List of networks to attach the container to | `list(string)` | `[]` |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
| -------------------- | ---------------------------------------------------------- |
|
||||
| `service_definition` | Service definition for integration with networking modules |
|
||||
|
||||
## Service Definition
|
||||
|
||||
This module outputs a service definition that is used by the networking modules to expose the service.
|
||||
|
||||
```hcl
|
||||
{
|
||||
name = "crawl4ai"
|
||||
primary_port = 11235
|
||||
endpoint = "http://crawl4ai:11235"
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Crawl4AI requires API keys for various LLM providers. These are configured through a `.env` file in the module directory. You should create this file based on the provided `.env.example` template:
|
||||
|
||||
- `OPENAI_API_KEY`: OpenAI API key
|
||||
- `DEEPSEEK_API_KEY`: DeepSeek API key
|
||||
- `ANTHROPIC_API_KEY`: Anthropic API key
|
||||
- `GROQ_API_KEY`: Groq API key
|
||||
- `TOGETHER_API_KEY`: Together API key
|
||||
- `MISTRAL_API_KEY`: Mistral API key
|
||||
- `GEMINI_API_TOKEN`: Gemini API token
|
||||
|
||||
## Configuration
|
||||
|
||||
Crawl4AI requires a custom configuration file. This is mounted from `${volume_path}/crawl4ai/config.yml` to `/app/config.yml` in the container.
|
||||
|
||||
## Ports
|
||||
|
||||
Crawl4AI exposes one port, which is mapped to host ports defined in the `.env` file:
|
||||
1. Frontend (port 11235) - The main web interface for accessing games
|
||||
|
||||
## Example Integration in Main Configuration
|
||||
|
||||
```hcl
|
||||
module "crawl4ai" {
|
||||
source = "./modules/20-services-apps/crawl4ai"
|
||||
volume_path = module.system_globals.volume_host
|
||||
networks = [module.services.homelab_docker_network_name]
|
||||
memory_limit = 8192 # 8GB if you need more memory
|
||||
}
|
||||
|
||||
# The service definition is automatically included in the services output
|
||||
module "services" {
|
||||
source = "./modules/services"
|
||||
# ...
|
||||
service_definitions = [
|
||||
module.crawl4ai.service_definition,
|
||||
# Other service definitions
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -1,99 +0,0 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
dotenv = {
|
||||
source = "germanbrew/dotenv"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "image_tag" {
|
||||
description = "Tag of the Crawl4AI image to use"
|
||||
type = string
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
variable "volume_path" {
|
||||
description = "Host path for Crawl4AI data volumes"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "List of networks to which the container should be attached"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "crawl4ai"
|
||||
image = "unclecode/crawl4ai"
|
||||
image_tag = var.image_tag
|
||||
monitoring = true
|
||||
service_port = provider::dotenv::get_by_key("PORT", local.env_file)
|
||||
env_file = "${path.module}/.env"
|
||||
|
||||
# Define volumes
|
||||
default_volumes = [
|
||||
{
|
||||
container_path = "/dev/shm"
|
||||
host_path = "/dev/shm"
|
||||
read_only = false
|
||||
},
|
||||
{
|
||||
container_path = "/app/config.yml"
|
||||
host_path = "${var.volume_path}/config.yml"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
# Define ports
|
||||
ports = [
|
||||
{
|
||||
internal = local.service_port
|
||||
external = local.service_port
|
||||
protocol = "tcp"
|
||||
}
|
||||
]
|
||||
|
||||
# Environment variables
|
||||
env_vars = {
|
||||
OPENAI_API_KEY = provider::dotenv::get_by_key("OPENAI_API_KEY", local.env_file)
|
||||
DEEPSEEK_API_KEY = provider::dotenv::get_by_key("DEEPSEEK_API_KEY", local.env_file)
|
||||
ANTHROPIC_API_KEY = provider::dotenv::get_by_key("ANTHROPIC_API_KEY", local.env_file)
|
||||
GROQ_API_KEY = provider::dotenv::get_by_key("GROQ_API_KEY", local.env_file)
|
||||
TOGETHER_API_KEY = provider::dotenv::get_by_key("TOGETHER_API_KEY", local.env_file)
|
||||
MISTRAL_API_KEY = provider::dotenv::get_by_key("MISTRAL_API_KEY", local.env_file)
|
||||
GEMINI_API_TOKEN = provider::dotenv::get_by_key("GEMINI_API_TOKEN", local.env_file)
|
||||
}
|
||||
|
||||
# Healthcheck configuration
|
||||
healthcheck = {
|
||||
test = ["CMD", "curl", "-f", "http://localhost:${local.service_port}/health"]
|
||||
interval = "30s"
|
||||
timeout = "10s"
|
||||
retries = 3
|
||||
start_period = "40s"
|
||||
}
|
||||
}
|
||||
|
||||
module "crawl4ai" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.image_tag
|
||||
volumes = local.default_volumes
|
||||
ports = local.ports
|
||||
env_vars = local.env_vars
|
||||
networks = var.networks
|
||||
monitoring = local.monitoring
|
||||
healthcheck = local.healthcheck
|
||||
user = "appuser"
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "General service definition with optional ingress configuration"
|
||||
value = {
|
||||
name = local.container_name
|
||||
primary_port = local.service_port
|
||||
endpoint = "http://${local.container_name}:${local.service_port}"
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
# Glance Module
|
||||
|
||||
This module deploys [Glance](https://glanceapp.io/), a dashboard application, as a Docker container in the homelab environment.
|
||||
|
||||
## Overview
|
||||
|
||||
The Glance module:
|
||||
|
||||
- Deploys the `glanceapp/glance` Docker container
|
||||
- Persists configuration to a volume on the host
|
||||
- Provides service definition for integration with networking modules
|
||||
|
||||
## Usage
|
||||
|
||||
```hcl
|
||||
module "glance" {
|
||||
source = "./modules/20-services-apps/glance"
|
||||
volume_path = "/path/to/volumes/glance"
|
||||
networks = ["homelab-network"]
|
||||
}
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
| Variable | Description | Type | Default |
|
||||
| ------------- | ---------------------------------------------------------- | -------------- | ---------- |
|
||||
| `image_tag` | Tag of the Glance image to use | `string` | `"latest"` |
|
||||
| `volume_path` | Host path for Glance data volume | `string` | - |
|
||||
| `networks` | List of networks to which the container should be attached | `list(string)` | - |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
| -------------------- | ---------------------------------------------------------- |
|
||||
| `service_definition` | Service definition for integration with networking modules |
|
||||
|
||||
## Service Definition
|
||||
|
||||
This module outputs a service definition that is used by the networking modules to expose the service.
|
||||
|
||||
```hcl
|
||||
{
|
||||
name = "glance"
|
||||
primary_port = 4921
|
||||
endpoint = "http://glance:4921"
|
||||
subdomains = ["glance"]
|
||||
publish_via = "tunnel" # Only publish through Cloudflare tunnel
|
||||
}
|
||||
```
|
||||
|
||||
## Data Persistence
|
||||
|
||||
Glance stores its configuration in the `/app/config` directory inside the container. This is mapped to a volume on the host at `${volume_path}/config`.
|
||||
|
||||
## Integration with Networking Modules
|
||||
|
||||
This service is configured to be exposed through a Cloudflare tunnel for secure remote access, set by `publish_via = "tunnel"`.
|
||||
|
||||
## Example Integration in Main Configuration
|
||||
|
||||
```hcl
|
||||
module "glance" {
|
||||
source = "./modules/20-services-apps/glance"
|
||||
volume_path = module.system_globals.volume_host
|
||||
networks = [module.services.homelab_docker_network_name]
|
||||
}
|
||||
|
||||
# The service definition is automatically included in the services output
|
||||
module "services" {
|
||||
source = "./modules/services"
|
||||
# ...
|
||||
service_definitions = [
|
||||
module.glance.service_definition,
|
||||
# Other service definitions
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -1,52 +0,0 @@
|
||||
variable "image_tag" {
|
||||
description = "Tag of the Glance image to use"
|
||||
type = string
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
variable "volume_path" {
|
||||
description = "Host path for Glance data volume"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "List of networks to which the container should be attached"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "glance"
|
||||
image = "glanceapp/glance"
|
||||
image_tag = var.image_tag != "" ? var.image_tag : "latest"
|
||||
monitoring = true
|
||||
host_port = 8080
|
||||
subdomains = ["glance"]
|
||||
default_volumes = [
|
||||
{
|
||||
container_path = "/app/config"
|
||||
host_path = "${var.volume_path}/config"
|
||||
read_only = false
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
module "glance" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.image_tag
|
||||
volumes = local.default_volumes
|
||||
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.host_port
|
||||
endpoint = "http://${local.container_name}:${local.host_port}"
|
||||
subdomains = local.subdomains
|
||||
publish_via = "tunnel"
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
# Gluetun VPN (.env example)
|
||||
# Copy to modules/20-services-apps/gluetun/.env and fill in values
|
||||
|
||||
# Provider and VPN type
|
||||
VPN_SERVICE_PROVIDER=mullvad
|
||||
VPN_TYPE=wireguard
|
||||
|
||||
# Wireguard credentials (required)
|
||||
# Generate from Mullvad account: private key and tunnel IP address
|
||||
WIREGUARD_PRIVATE_KEY=
|
||||
# Example: 10.64.0.2/32
|
||||
WIREGUARD_ADDRESSES=
|
||||
|
||||
# Server selection (one of the following is recommended)
|
||||
#SERVER_CITIES="Los Angeles"
|
||||
#SERVER_COUNTRIES="United States"
|
||||
|
||||
# Exact server pinning (optional; supports comma-separated list)
|
||||
# For Jakarta example:
|
||||
#SERVER_HOSTNAMES=id-jpu-wg-001
|
||||
#SERVER_HOSTNAME=id-jpu-wg-001
|
||||
|
||||
# Updater period (optional)
|
||||
#UPDATER_PERIOD=24h
|
||||
|
||||
# Firewall rules (optional)
|
||||
# Allow outbound traffic to RFC1918 subnets so LAN services can be reached
|
||||
# Example: 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
#FIREWALL_OUTBOUND_SUBNETS=
|
||||
|
||||
# If you need to accept inbound connections through Gluetun (e.g., expose qBittorrent UI),
|
||||
# publish the port in Terraform and optionally set FIREWALL_INPUT_PORTS as well
|
||||
#FIREWALL_INPUT_PORTS=8080
|
||||
@@ -1,65 +0,0 @@
|
||||
# Gluetun (Mullvad Wireguard)
|
||||
|
||||
This module runs Gluetun to provide a VPN network stack for other containers.
|
||||
You can route qBittorrent through Gluetun by setting its `network_mode` to `container:gluetun` using the provided toggle in the qBittorrent module.
|
||||
|
||||
- Image: `qmcgaw/gluetun:v3.39.0`
|
||||
- Requires: NET_ADMIN capability and `/dev/net/tun` device
|
||||
- Default: No ports exposed on host. Publish only if you need host access.
|
||||
- Attach Gluetun to the same Docker network as services that should reach apps running through it (e.g., `media-network`).
|
||||
|
||||
## Usage
|
||||
|
||||
Example in `services/main.tf`:
|
||||
|
||||
```hcl
|
||||
module "gluetun" {
|
||||
source = "${local.module_dir}/20-services-apps/gluetun"
|
||||
volume_path = "${local.volume_host}/gluetun"
|
||||
networks = [module.media_docker_network.name]
|
||||
# Optionally expose qBittorrent's Web UI to the host via Gluetun:
|
||||
# ports = [{ internal = 8080, external = 8080, protocol = "tcp" }]
|
||||
}
|
||||
|
||||
module "qbittorrent" {
|
||||
source = "${local.module_dir}/20-services-apps/qbittorrent"
|
||||
volume_path = "${local.volume_host}/qbittorrent"
|
||||
downloads_path = "${local.data_host}/torrents"
|
||||
networks = [module.media_docker_network.name]
|
||||
connect_via_gluetun = true
|
||||
gluetun_container_name = "gluetun"
|
||||
}
|
||||
|
||||
module "arr" {
|
||||
source = "${local.module_dir}/20-services-apps/arr"
|
||||
volume_path = "${local.volume_host}/arr"
|
||||
data_path = local.data_host
|
||||
downloads_path = "${local.data_host}/torrents"
|
||||
networks = [module.media_docker_network.name]
|
||||
proxy_networks = [module.homelab_docker_network.name]
|
||||
qbittorrent_host = "gluetun" # arr containers will reach qBt at http://gluetun:8080
|
||||
}
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
Place a `.env` file in this module directory (`modules/20-services-apps/gluetun/.env`). See `.env.example` for all options. Key variables:
|
||||
|
||||
- VPN_SERVICE_PROVIDER=mullvad
|
||||
- VPN_TYPE=wireguard
|
||||
- WIREGUARD_PRIVATE_KEY=... (required)
|
||||
- WIREGUARD_ADDRESSES=10.64.0.2/32 (example)
|
||||
- SERVER_CITIES=... or SERVER_COUNTRIES=...
|
||||
- SERVER_HOSTNAMES=id-jpu-wg-001 (optional exact server pin; supports comma-separated list)
|
||||
- UPDATER_PERIOD=24h (optional)
|
||||
- FIREWALL_OUTBOUND_SUBNETS=10.0.0.0/8,192.168.0.0/16 (optional; allow containers to reach LAN subnets)
|
||||
- Optional: `FIREWALL_INPUT_PORTS=8080` if you need other containers/LAN to initiate connections to services through Gluetun.
|
||||
|
||||
Notes:
|
||||
- When qBittorrent shares Gluetun's network, other containers should use `http://gluetun:8080`.
|
||||
- To access qBittorrent UI from the host, publish `8080/tcp` on Gluetun via this module's `ports` input or set `FIREWALL_INPUT_PORTS` accordingly.
|
||||
- Do not publish ports on qBittorrent when using Gluetun network mode; publish on Gluetun instead.
|
||||
|
||||
Pinning a specific server:
|
||||
- Set `SERVER_HOSTNAMES=id-jpu-wg-001` to pin to Mullvad Jakarta `id-jpu-wg-001`.
|
||||
- The module also accepts `SERVER_HOSTNAME` for compatibility (falls back to it if `SERVER_HOSTNAMES` is not set).
|
||||
@@ -1,88 +0,0 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
dotenv = { source = "germanbrew/dotenv" }
|
||||
}
|
||||
}
|
||||
|
||||
variable "volume_path" {
|
||||
description = "Base directory for Gluetun state/config mounted at /gluetun"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "Networks to attach Gluetun to"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "ports" {
|
||||
description = "Ports to publish on the Gluetun container (used to reach services connected via network_mode: container:gluetun)"
|
||||
type = list(object({
|
||||
internal = number
|
||||
external = number
|
||||
protocol = string
|
||||
}))
|
||||
// Default to no published ports. Publish only if you need host access.
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "image_tag" {
|
||||
description = "Gluetun image tag"
|
||||
type = string
|
||||
default = "v3.39.0"
|
||||
}
|
||||
|
||||
locals {
|
||||
env_file = "${path.module}/.env"
|
||||
container_name = "gluetun"
|
||||
image = "qmcgaw/gluetun"
|
||||
tag = var.image_tag
|
||||
monitoring = true
|
||||
|
||||
// Gluetun environment
|
||||
env_vars = {
|
||||
VPN_SERVICE_PROVIDER = try(provider::dotenv::get_by_key("VPN_SERVICE_PROVIDER", local.env_file), "mullvad")
|
||||
VPN_TYPE = try(provider::dotenv::get_by_key("VPN_TYPE", local.env_file), "wireguard")
|
||||
WIREGUARD_PRIVATE_KEY = provider::dotenv::get_by_key("WIREGUARD_PRIVATE_KEY", local.env_file)
|
||||
WIREGUARD_ADDRESSES = provider::dotenv::get_by_key("WIREGUARD_ADDRESSES", local.env_file)
|
||||
SERVER_CITIES = try(provider::dotenv::get_by_key("SERVER_CITIES", local.env_file), "")
|
||||
SERVER_COUNTRIES = try(provider::dotenv::get_by_key("SERVER_COUNTRIES", local.env_file), "")
|
||||
SERVER_HOSTNAMES = try(
|
||||
provider::dotenv::get_by_key("SERVER_HOSTNAMES", local.env_file),
|
||||
try(provider::dotenv::get_by_key("SERVER_HOSTNAME", local.env_file), "")
|
||||
)
|
||||
UPDATER_PERIOD = try(provider::dotenv::get_by_key("UPDATER_PERIOD", local.env_file), "")
|
||||
FIREWALL_OUTBOUND_SUBNETS = try(provider::dotenv::get_by_key("FIREWALL_OUTBOUND_SUBNETS", local.env_file), "")
|
||||
}
|
||||
|
||||
volumes = [
|
||||
{
|
||||
host_path = var.volume_path,
|
||||
container_path = "/gluetun",
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
module "gluetun" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.tag
|
||||
env_vars = local.env_vars
|
||||
volumes = local.volumes
|
||||
networks = var.networks
|
||||
monitoring = local.monitoring
|
||||
|
||||
// Grant minimal privileges required by Gluetun
|
||||
capabilities_add = ["NET_ADMIN"]
|
||||
devices = [
|
||||
{
|
||||
host_path = "/dev/net/tun"
|
||||
container_path = "/dev/net/tun"
|
||||
permissions = "rwm"
|
||||
}
|
||||
]
|
||||
|
||||
ports = var.ports
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
# You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables
|
||||
|
||||
###################################################################################
|
||||
# Required database configuration (used by Terraform to configure Postgres & Immich)
|
||||
###################################################################################
|
||||
|
||||
# PostgreSQL username
|
||||
DB_USERNAME=postgres
|
||||
|
||||
# PostgreSQL password (use only A-Za-z0-9 characters)
|
||||
DB_PASSWORD=postgres
|
||||
|
||||
# PostgreSQL database name
|
||||
DB_DATABASE_NAME=immich
|
||||
@@ -1,110 +0,0 @@
|
||||
# Immich Module
|
||||
|
||||
This module deploys [Immich](https://immich.app/), a high-performance self-hosted photo and video backup solution, as Docker containers in the homelab environment.
|
||||
|
||||
## Overview
|
||||
|
||||
The Immich module:
|
||||
|
||||
- Deploys four Docker containers:
|
||||
- `immich-server`: The main Immich API/UI server (port 2283)
|
||||
- `immich-machine-learning`: The ML service for search, faces, and embeddings
|
||||
- `immich-postgres`: Immich-tuned PostgreSQL database
|
||||
- `immich-redis`: Valkey/Redis-compatible cache
|
||||
- Creates a dedicated Docker network (`immich-network`) for inter-container communication
|
||||
- Persists data to volumes on the host
|
||||
- Provides a service definition for integration with networking modules
|
||||
|
||||
## Usage
|
||||
|
||||
```hcl
|
||||
module "immich" {
|
||||
source = "./modules/20-services-apps/immich"
|
||||
appdata_path = "/path/to/appdata/immich"
|
||||
library_path = "/path/to/data/media/photos"
|
||||
networks = ["homelab-network"]
|
||||
}
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
| Variable | Description | Type | Default |
|
||||
| --------------- | -------------------------------------------------------------------------------- | -------------- | ---------- |
|
||||
| `image_tag` | Tag of the Immich images to use (`server` and `machine-learning`) | `string` | `"release"` |
|
||||
| `appdata_path` | Base host path for Immich app data (e.g., PostgreSQL data and internal configs) | `string` | - |
|
||||
| `library_path` | Base host path for user library data and ML cache | `string` | - |
|
||||
| `networks` | List of additional networks to which the server should attach | `list(string)` | `[]` |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
| -------------------- | ---------------------------------------------------------- |
|
||||
| `service_definition` | Service definition for integration with networking modules |
|
||||
|
||||
## Service Definition
|
||||
|
||||
This module outputs a service definition used by networking modules to expose the service.
|
||||
|
||||
```hcl
|
||||
{
|
||||
name = "immich-server"
|
||||
primary_port = 2283
|
||||
endpoint = "http://immich-server:2283"
|
||||
subdomains = ["photos"]
|
||||
publish_via = "reverse_proxy"
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Only the database credentials are expected in a `.env` file in this module directory and are read using the `dotenv` Terraform provider. Everything else is configured directly in Terraform.
|
||||
|
||||
Required in `modules/20-services-apps/immich/.env`:
|
||||
|
||||
- `DB_USERNAME`: PostgreSQL user
|
||||
- `DB_PASSWORD`: PostgreSQL password
|
||||
- `DB_DATABASE_NAME`: Database name
|
||||
|
||||
A ready-to-copy `modules/20-services-apps/immich/.env.example` is provided.
|
||||
|
||||
## Data Persistence
|
||||
|
||||
Immich stores data in the following volumes:
|
||||
|
||||
1. Library storage: `/data` in `immich-server`, mapped to `${library_path}/library` on the host
|
||||
2. ML model cache: `/cache` in `immich-machine-learning`, mapped to `${library_path}/machine-learning/cache` on the host
|
||||
3. PostgreSQL data: `/var/lib/postgresql/data` in `immich-postgres`, mapped to `${appdata_path}/postgres/pgdata` on the host
|
||||
|
||||
## Networking
|
||||
|
||||
The module creates a dedicated Docker network named `immich-network` for communication between Immich components. The Immich server container is also attached to any additional networks specified in the `networks` variable, allowing it to communicate with other services in the homelab.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `immich-server` depends on `immich-postgres` and `immich-redis`
|
||||
- `immich-postgres` and `immich-redis` include healthchecks
|
||||
- The ML service is independent and discovered by the server internally; tuning can be done via the Immich admin UI
|
||||
|
||||
## Integration with Networking Modules
|
||||
|
||||
This service is configured to be exposed through the Caddy reverse proxy, set by `publish_via = "reverse_proxy"`.
|
||||
|
||||
## Example Integration in Main Configuration
|
||||
|
||||
```hcl
|
||||
module "immich" {
|
||||
source = "./modules/20-services-apps/immich"
|
||||
appdata_path = "${module.system_globals.volume_host}/appdata/immich"
|
||||
library_path = "${module.system_globals.volume_host}/data/media/photos"
|
||||
networks = [module.services.homelab_docker_network_name]
|
||||
}
|
||||
|
||||
# The service definition is automatically included in the services output
|
||||
module "services" {
|
||||
source = "./modules/services"
|
||||
# ...
|
||||
service_definitions = [
|
||||
module.immich.service_definition,
|
||||
# Other service definitions
|
||||
]
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
dotenv = {
|
||||
source = "germanbrew/dotenv"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "image_tag" {
|
||||
description = "The tag for the Immich container images (server and machine-learning)"
|
||||
type = string
|
||||
default = "release"
|
||||
}
|
||||
|
||||
variable "appdata_path" {
|
||||
description = "Base directory for Immich app data"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "library_path" {
|
||||
description = "Base directory for Immich library data"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "List of networks to which the Immich server should be attached (in addition to the module network)"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
locals {
|
||||
env_file = "${path.module}/.env"
|
||||
monitoring = true
|
||||
|
||||
# Container names
|
||||
server_name = "immich-server"
|
||||
ml_name = "immich-machine-learning"
|
||||
redis_name = "immich-redis"
|
||||
postgres_name = "immich-postgres"
|
||||
|
||||
# Images and tags
|
||||
server_image = "ghcr.io/immich-app/immich-server"
|
||||
ml_image = "ghcr.io/immich-app/immich-machine-learning"
|
||||
redis_image = "docker.io/valkey/valkey"
|
||||
postgres_image = "ghcr.io/immich-app/postgres"
|
||||
|
||||
server_tag = var.image_tag
|
||||
ml_tag = var.image_tag
|
||||
redis_tag = "8-bookworm"
|
||||
postgres_tag = "14-vectorchord0.4.3-pgvectors0.2.0"
|
||||
|
||||
# Ports
|
||||
server_port = 2283
|
||||
ml_port = 3003
|
||||
|
||||
# Volumes (host paths)
|
||||
server_volumes = [
|
||||
{
|
||||
host_path = "${var.library_path}/data"
|
||||
container_path = "/data"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
ml_volumes = [
|
||||
{
|
||||
host_path = "${var.library_path}/ml/cache"
|
||||
container_path = "/cache"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
postgres_volumes = [
|
||||
{
|
||||
host_path = "${var.appdata_path}/postgres/pgdata"
|
||||
container_path = "/var/lib/postgresql/data"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
# Environment variables for Postgres
|
||||
postgres_env_vars = {
|
||||
POSTGRES_USER = provider::dotenv::get_by_key("DB_USERNAME", local.env_file)
|
||||
POSTGRES_PASSWORD = provider::dotenv::get_by_key("DB_PASSWORD", local.env_file)
|
||||
POSTGRES_DB = provider::dotenv::get_by_key("DB_DATABASE_NAME", local.env_file)
|
||||
POSTGRES_INITDB_ARGS = "--data-checksums"
|
||||
}
|
||||
|
||||
# Environment variables for Immich server
|
||||
server_env_vars = {
|
||||
# Database
|
||||
DB_HOSTNAME = local.postgres_name
|
||||
DB_PORT = "5432"
|
||||
DB_USERNAME = provider::dotenv::get_by_key("DB_USERNAME", local.env_file)
|
||||
DB_PASSWORD = provider::dotenv::get_by_key("DB_PASSWORD", local.env_file)
|
||||
DB_DATABASE_NAME = provider::dotenv::get_by_key("DB_DATABASE_NAME", local.env_file)
|
||||
|
||||
# Redis
|
||||
REDIS_HOSTNAME = local.redis_name
|
||||
REDIS_PORT = "6379"
|
||||
REDIS_DBINDEX = "0"
|
||||
|
||||
# General
|
||||
IMMICH_MEDIA_LOCATION = "/data"
|
||||
}
|
||||
|
||||
# Healthchecks
|
||||
redis_healthcheck = {
|
||||
test = ["CMD", "redis-cli", "ping"]
|
||||
interval = "10s"
|
||||
timeout = "5s"
|
||||
retries = 5
|
||||
start_period = "5s"
|
||||
}
|
||||
|
||||
postgres_healthcheck = {
|
||||
test = ["CMD", "pg_isready", "-U", provider::dotenv::get_by_key("DB_USERNAME", local.env_file), "-d", provider::dotenv::get_by_key("DB_DATABASE_NAME", local.env_file)]
|
||||
interval = "10s"
|
||||
timeout = "5s"
|
||||
retries = 5
|
||||
start_period = "5s"
|
||||
}
|
||||
}
|
||||
|
||||
# Dedicated network for Immich
|
||||
module "immich_network" {
|
||||
source = "../../01-networking/docker-network"
|
||||
name = "immich-network"
|
||||
driver = "bridge"
|
||||
}
|
||||
|
||||
# Valkey (Redis) service
|
||||
module "redis" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.redis_name
|
||||
image = local.redis_image
|
||||
tag = local.redis_tag
|
||||
networks = [module.immich_network.name]
|
||||
monitoring = local.monitoring
|
||||
healthcheck = local.redis_healthcheck
|
||||
}
|
||||
|
||||
# Postgres service (Immich custom image)
|
||||
module "postgres" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.postgres_name
|
||||
image = local.postgres_image
|
||||
tag = local.postgres_tag
|
||||
volumes = local.postgres_volumes
|
||||
env_vars = local.postgres_env_vars
|
||||
networks = [module.immich_network.name]
|
||||
monitoring = local.monitoring
|
||||
healthcheck = local.postgres_healthcheck
|
||||
}
|
||||
|
||||
# Immich Machine Learning service
|
||||
module "machine_learning" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.ml_name
|
||||
image = local.ml_image
|
||||
tag = local.ml_tag
|
||||
volumes = local.ml_volumes
|
||||
networks = [module.immich_network.name]
|
||||
monitoring = local.monitoring
|
||||
}
|
||||
|
||||
# Immich Server service
|
||||
module "immich" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.server_name
|
||||
image = local.server_image
|
||||
tag = local.server_tag
|
||||
ports = [
|
||||
{
|
||||
internal = local.server_port
|
||||
external = local.server_port
|
||||
protocol = "tcp"
|
||||
}
|
||||
]
|
||||
volumes = local.server_volumes
|
||||
env_vars = local.server_env_vars
|
||||
networks = concat([module.immich_network.name], var.networks)
|
||||
monitoring = local.monitoring
|
||||
depends_on = [module.postgres, module.redis]
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "General service definition with optional ingress configuration"
|
||||
value = {
|
||||
name = local.server_name
|
||||
primary_port = local.server_port
|
||||
endpoint = "http://${local.server_name}:${local.server_port}"
|
||||
subdomains = ["photos"]
|
||||
publish_via = "reverse_proxy"
|
||||
proxied = true
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
# Optional values for Jellyfin module
|
||||
# Only needed if you enable JELLYFIN_PublishedServerUrl in main.tf
|
||||
|
||||
HOSTNAME=example.com
|
||||
@@ -1,87 +0,0 @@
|
||||
# Jellyfin Module
|
||||
|
||||
This module deploys Jellyfin as a Docker container and outputs a service definition to be published via your reverse proxy.
|
||||
|
||||
## Overview
|
||||
|
||||
- Container: `jellyfin` (LinuxServer.io)
|
||||
- TCP 8096 for HTTP UI; UDP 7359/1900 for discovery/DLNA
|
||||
- Config and media volumes mapped via variables
|
||||
|
||||
## Usage
|
||||
|
||||
```hcl
|
||||
module "jellyfin" {
|
||||
source = "./modules/20-services-apps/jellyfin"
|
||||
volume_path = "/srv/appdata/jellyfin" # host path for Jellyfin config
|
||||
data_path = "/srv/data" # host media root, mounted as /data
|
||||
networks = [module.media_docker_network.name, module.homelab_docker_network.name]
|
||||
}
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
| Variable | Description | Type | Default |
|
||||
| ------------- | --------------------------------------------- | -------------- | ------- |
|
||||
| `volume_path` | Base directory for Jellyfin config | `string` | - |
|
||||
| `data_path` | Base directory for media data mounted at /data | `string` | - |
|
||||
| `networks` | List of networks to attach | `list(string)` | `[]` |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
| -------------------- | -------------------------------------------- |
|
||||
| `service_definition` | Service definition for integration with networking modules |
|
||||
|
||||
## Service Definition
|
||||
|
||||
This module outputs a service definition that is used by the networking modules to expose the service.
|
||||
|
||||
```hcl
|
||||
{
|
||||
name = "jellyfin"
|
||||
primary_port = 8096
|
||||
endpoint = "http://jellyfin:8096"
|
||||
subdomains = ["stream"]
|
||||
publish_via = "reverse_proxy"
|
||||
proxied = false
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables (.env)
|
||||
|
||||
This module optionally reads `HOSTNAME` from `.env` if you choose to publish a fixed external URL (see commented example in `main.tf`).
|
||||
|
||||
- `HOSTNAME` — your public domain (e.g., example.com). Used only if you enable `JELLYFIN_PublishedServerUrl`.
|
||||
|
||||
Note: `TZ`, `PUID`, and `PGID` are provided automatically by the generic docker-service module via system globals.
|
||||
|
||||
## Data Persistence
|
||||
|
||||
- `/config` -> `${volume_path}`
|
||||
- `/data` -> `${data_path}`
|
||||
|
||||
Ensure the host paths exist and are writable by the container user.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- No explicit inter-container dependencies. Healthcheck ensures readiness.
|
||||
- UDP ports are exposed for discovery/DLNA.
|
||||
|
||||
## Integration with Networking Modules
|
||||
|
||||
This service is configured to be exposed through the Caddy reverse proxy, set by `publish_via = "reverse_proxy"`.
|
||||
|
||||
## Example Integration in Main Configuration
|
||||
|
||||
```hcl
|
||||
# In services/main.tf
|
||||
module "jellyfin" {
|
||||
source = "${local.module_dir}/20-services-apps/jellyfin"
|
||||
volume_path = "${local.volume_host}/jellyfin"
|
||||
data_path = "${local.data_host}/media"
|
||||
networks = [module.media_docker_network.name, module.homelab_docker_network.name]
|
||||
}
|
||||
```
|
||||
|
||||
The service definition is exported by the `services` module as `module.services.service_definitions` and consumed by networking modules in the root `main.tf`.
|
||||
@@ -1,96 +0,0 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
dotenv = { source = "germanbrew/dotenv" }
|
||||
}
|
||||
}
|
||||
|
||||
variable "volume_path" {
|
||||
description = "Base directory for Jellyfin config"
|
||||
type = string
|
||||
}
|
||||
variable "data_path" {
|
||||
description = "Base directory for media data mounted at /data"
|
||||
type = string
|
||||
}
|
||||
variable "networks" {
|
||||
description = "List of networks to attach"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
locals {
|
||||
env_file = "${path.module}/.env"
|
||||
monitoring = true
|
||||
container_name = "jellyfin"
|
||||
image = "lscr.io/linuxserver/jellyfin"
|
||||
tag = "latest"
|
||||
internal_port = 8096
|
||||
|
||||
# UDP ports for DLNA/auto-discovery
|
||||
udp_ports = [
|
||||
{ internal = 7359, external = 7359, protocol = "udp" },
|
||||
{ internal = 1900, external = 1900, protocol = "udp" }
|
||||
]
|
||||
|
||||
volumes = [
|
||||
{
|
||||
host_path = var.volume_path,
|
||||
container_path = "/config",
|
||||
read_only = false
|
||||
},
|
||||
{
|
||||
host_path = var.volume_path,
|
||||
container_path = "/cache",
|
||||
read_only = false
|
||||
},
|
||||
{
|
||||
host_path = var.data_path,
|
||||
container_path = "/data",
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
env_vars = {
|
||||
# If you want to publish external URL, uncomment the following and set HOSTNAME in .env
|
||||
JELLYFIN_PublishedServerUrl = "${provider::dotenv::get_by_key("HOSTNAME", local.env_file)}/jellyfin"
|
||||
}
|
||||
|
||||
# Intel VAAPI/QSV: map the entire /dev/dri directory (per linuxserver/jellyfin docs)
|
||||
devices = [
|
||||
{
|
||||
host_path = "/dev/dri/renderD128",
|
||||
container_path = "/dev/dri/renderD128",
|
||||
permissions = "rwm"
|
||||
},
|
||||
{
|
||||
host_path = "/dev/dri/card0",
|
||||
container_path = "/dev/dri/card0",
|
||||
permissions = "rwm"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
module "jellyfin" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.tag
|
||||
volumes = local.volumes
|
||||
env_vars = local.env_vars
|
||||
networks = var.networks
|
||||
monitoring = local.monitoring
|
||||
ports = local.udp_ports
|
||||
devices = local.devices
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "Service definition for Jellyfin (reverse proxy)"
|
||||
value = {
|
||||
name = local.container_name
|
||||
primary_port = local.internal_port
|
||||
endpoint = "http://${local.container_name}:${local.internal_port}"
|
||||
subdomains = ["stream"]
|
||||
publish_via = "reverse_proxy"
|
||||
proxied = true
|
||||
}
|
||||
}
|
||||
180
modules/20-services-apps/media-server/README.md
Normal file
180
modules/20-services-apps/media-server/README.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# Media Server Module
|
||||
|
||||
This module deploys a complete media server stack using Docker containers in the homelab environment. It includes content management services (Sonarr, Radarr, Readarr), content discovery (Prowlarr, Jellyseerr), download clients (Qbittorrent, Sabnzbd), and media playback (Jellyfin).
|
||||
|
||||
## Overview
|
||||
|
||||
The Media Server module:
|
||||
|
||||
- Deploys multiple Docker containers:
|
||||
- `sonarr`: Series/TV management system
|
||||
- `radarr`: Movie management system
|
||||
- `readarr`: Book management system
|
||||
- `jellyseerr`: Content request and discovery system
|
||||
- `prowlarr`: Indexer management system
|
||||
- `qbittorrent`: Torrent download client with VueTorrent UI
|
||||
- `unpackerr`: Automatic extraction utility
|
||||
- `jellyfin`: Media server for streaming content
|
||||
- `sabnzbd`: Usenet download client
|
||||
- `flaresolverr`: Proxy service to bypass Cloudflare and other protection
|
||||
- `autoheal`: Service for automatic container health restarts
|
||||
- Creates a dedicated Docker network (`media-server`) for container communication
|
||||
- Persists data to volumes on the host
|
||||
- Provides service definitions for integration with networking modules
|
||||
- Exposes Jellyfin, Jellyseerr, and Sabnzbd via reverse proxy
|
||||
|
||||
## Usage
|
||||
|
||||
```hcl
|
||||
module "media_server" {
|
||||
source = "./modules/20-services-apps/media-server"
|
||||
volume_path = "/path/to/app/data"
|
||||
data_root = "/path/to/media/data"
|
||||
download_root = "/path/to/download/data"
|
||||
user_id = "1000"
|
||||
group_id = "1000"
|
||||
timezone = "UTC"
|
||||
hostname = "example.com"
|
||||
sonarr_api_key = "your-sonarr-api-key"
|
||||
radarr_api_key = "your-radarr-api-key"
|
||||
networks = ["homelab-network"]
|
||||
}
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
| Variable | Description | Type | Default |
|
||||
| ---------------- | ------------------------------------------------- | -------------- | -------- |
|
||||
| `volume_path` | Base directory for application data | `string` | - |
|
||||
| `data_root` | Root directory for media data | `string` | - |
|
||||
| `download_root` | Directory for downloads | `string` | - |
|
||||
| `user_id` | User ID for container permissions | `string` | `"1000"` |
|
||||
| `group_id` | Group ID for container permissions | `string` | `"1000"` |
|
||||
| `timezone` | Timezone for the containers | `string` | `"UTC"` |
|
||||
| `hostname` | Hostname for the Jellyfin PublishedServerUrl | `string` | - |
|
||||
| `sonarr_api_key` | API key for Sonarr | `string` | - |
|
||||
| `radarr_api_key` | API key for Radarr | `string` | - |
|
||||
| `networks` | List of networks to which containers are attached | `list(string)` | `[]` |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
| --------------------- | ------------------------------------------------------------------ |
|
||||
| `service_definitions` | Service definitions for Jellyfin, Jellyseerr, and Sabnzbd services |
|
||||
| `network_name` | Name of the media server network |
|
||||
|
||||
## Services
|
||||
|
||||
### Sonarr
|
||||
TV series management tool that integrates with various download clients and indexers to automate obtaining TV episodes.
|
||||
- Port: 8989
|
||||
- Volumes:
|
||||
- Config: `/config` → `${volume_path}/sonarr`
|
||||
- Data: `/data` → `${data_root}`
|
||||
|
||||
### Radarr
|
||||
Movie management tool that integrates with various download clients and indexers to automate obtaining movies.
|
||||
- Port: 7878
|
||||
- Volumes:
|
||||
- Config: `/config` → `${volume_path}/radarr`
|
||||
- Data: `/data` → `${data_root}`
|
||||
|
||||
### Readarr
|
||||
Book management tool that integrates with various download clients and indexers to automate obtaining books.
|
||||
- Port: 8787
|
||||
- Volumes:
|
||||
- Config: `/config` → `${volume_path}/readarr`
|
||||
- Books: `/books` → `${data_root}`
|
||||
|
||||
### Jellyseerr
|
||||
Media request system that integrates with Jellyfin to allow users to request new content.
|
||||
- Port: 5055
|
||||
- Volumes:
|
||||
- Config: `/app/config` → `${volume_path}/jellyseerr`
|
||||
|
||||
### Prowlarr
|
||||
Indexer manager/proxy that integrates with various PVR apps and download clients.
|
||||
- Port: 9696
|
||||
- Volumes:
|
||||
- Config: `/config` → `${volume_path}/prowlarr`
|
||||
|
||||
### FlareSolverr
|
||||
Proxy server to bypass Cloudflare and other protection methods.
|
||||
- Port: 8191
|
||||
|
||||
### QBittorrent
|
||||
Torrent client with VueTorrent web interface.
|
||||
- Port: 8080
|
||||
- Volumes:
|
||||
- Config: `/config` → `${volume_path}/qbittorrent`
|
||||
- Downloads: `/data/torrents` → `${download_root}`
|
||||
|
||||
### Unpackerr
|
||||
Extracts completed downloads automatically for Sonarr, Radarr, and others.
|
||||
- Volumes:
|
||||
- Downloads: `/data/torrents` → `${download_root}`
|
||||
|
||||
### Jellyfin
|
||||
Media server for streaming content to various devices.
|
||||
- Ports:
|
||||
- 8096 (HTTP)
|
||||
- 7359 (UDP)
|
||||
- 1900 (UDP)
|
||||
- Volumes:
|
||||
- Config: `/config` → `${volume_path}/jellyfin`
|
||||
- Data: `/data` → `${data_root}`
|
||||
- Devices:
|
||||
- `/dev/dri/` for hardware acceleration
|
||||
|
||||
### Sabnzbd
|
||||
Usenet download client.
|
||||
- Port: 6789 (external) → 8080 (internal)
|
||||
- Volumes:
|
||||
- Config: `/config` → `${volume_path}/sabnzbd/config`
|
||||
- Downloads: `/downloads` → `${data_root}/usenet/downloads`
|
||||
|
||||
### Autoheal
|
||||
Service that automatically checks for container health and restarts unhealthy containers.
|
||||
- Requires access to Docker socket
|
||||
|
||||
## Networking
|
||||
|
||||
The module creates a dedicated Docker network named `media-server` for communication between the components. Each service container is also attached to any additional networks specified in the `networks` variable, allowing it to communicate with other services in the homelab.
|
||||
|
||||
## Reverse Proxy Integration
|
||||
|
||||
Three services are configured to be exposed through a reverse proxy:
|
||||
|
||||
- **Jellyfin**: Exposed at subdomain `jellyfin`
|
||||
- **Jellyseerr**: Exposed at subdomain `requests`
|
||||
- **Sabnzbd**: Exposed at subdomain `sabnzbd`
|
||||
|
||||
These services have their `publish_via` property set to `"reverse_proxy"` in their service definitions.
|
||||
|
||||
## Example Integration in Main Configuration
|
||||
|
||||
```hcl
|
||||
module "media_server" {
|
||||
source = "./modules/20-services-apps/media-server"
|
||||
volume_path = module.system_globals.volume_host
|
||||
data_root = "${module.system_globals.data_root}/media"
|
||||
download_root = "${module.system_globals.data_root}/downloads"
|
||||
user_id = module.system_globals.user_id
|
||||
group_id = module.system_globals.group_id
|
||||
timezone = module.system_globals.timezone
|
||||
hostname = "example.com"
|
||||
sonarr_api_key = var.sonarr_api_key
|
||||
radarr_api_key = var.radarr_api_key
|
||||
networks = [module.services.homelab_docker_network_name]
|
||||
}
|
||||
|
||||
# The service definitions are automatically included in the services output
|
||||
module "services" {
|
||||
source = "./modules/services"
|
||||
# ...
|
||||
service_definitions = concat(
|
||||
module.media_server.service_definitions,
|
||||
# Other service definitions
|
||||
)
|
||||
}
|
||||
```
|
||||
148
modules/20-services-apps/media-server/main.tf
Normal file
148
modules/20-services-apps/media-server/main.tf
Normal file
@@ -0,0 +1,148 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
dotenv = {
|
||||
source = "germanbrew/dotenv"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
monitoring = true
|
||||
|
||||
# Define common healthcheck settings
|
||||
healthcheck_interval = "30s"
|
||||
healthcheck_retries = 10
|
||||
}
|
||||
|
||||
# Create dedicated network for media server components
|
||||
module "media_server_network" {
|
||||
source = "../../01-networking/docker-network"
|
||||
name = "media-server"
|
||||
subnet = "11.102.0.0/16"
|
||||
driver = "bridge"
|
||||
}
|
||||
|
||||
# Import service modules
|
||||
module "sonarr" {
|
||||
source = "./services/sonarr"
|
||||
|
||||
user_id = var.user_id
|
||||
group_id = var.group_id
|
||||
timezone = var.timezone
|
||||
volume_path = var.volume_path
|
||||
data_root = var.data_root
|
||||
networks = concat([module.media_server_network.name], var.networks)
|
||||
monitoring = local.monitoring
|
||||
}
|
||||
|
||||
module "radarr" {
|
||||
source = "./services/radarr"
|
||||
|
||||
user_id = var.user_id
|
||||
group_id = var.group_id
|
||||
timezone = var.timezone
|
||||
volume_path = var.volume_path
|
||||
data_root = var.data_root
|
||||
networks = concat([module.media_server_network.name], var.networks)
|
||||
monitoring = local.monitoring
|
||||
}
|
||||
|
||||
module "readarr" {
|
||||
source = "./services/readarr"
|
||||
|
||||
user_id = var.user_id
|
||||
group_id = var.group_id
|
||||
timezone = var.timezone
|
||||
volume_path = var.volume_path
|
||||
data_root = var.data_root
|
||||
networks = concat([module.media_server_network.name], var.networks)
|
||||
monitoring = local.monitoring
|
||||
}
|
||||
|
||||
module "jellyseerr" {
|
||||
source = "./services/jellyseerr"
|
||||
|
||||
timezone = var.timezone
|
||||
volume_path = var.volume_path
|
||||
networks = concat([module.media_server_network.name], var.networks)
|
||||
monitoring = local.monitoring
|
||||
}
|
||||
|
||||
module "prowlarr" {
|
||||
source = "./services/prowlarr"
|
||||
|
||||
user_id = var.user_id
|
||||
group_id = var.group_id
|
||||
timezone = var.timezone
|
||||
volume_path = var.volume_path
|
||||
networks = concat([module.media_server_network.name], var.networks)
|
||||
monitoring = local.monitoring
|
||||
}
|
||||
|
||||
module "qbittorrent" {
|
||||
source = "./services/qbittorrent"
|
||||
|
||||
user_id = var.user_id
|
||||
group_id = var.group_id
|
||||
timezone = var.timezone
|
||||
volume_path = var.volume_path
|
||||
download_root = var.download_root
|
||||
networks = concat([module.media_server_network.name], var.networks)
|
||||
monitoring = local.monitoring
|
||||
}
|
||||
|
||||
module "unpackerr" {
|
||||
source = "./services/unpackerr"
|
||||
|
||||
user_id = var.user_id
|
||||
group_id = var.group_id
|
||||
timezone = var.timezone
|
||||
download_root = var.download_root
|
||||
sonarr_api_key = var.sonarr_api_key
|
||||
radarr_api_key = var.radarr_api_key
|
||||
networks = concat([module.media_server_network.name], var.networks)
|
||||
monitoring = local.monitoring
|
||||
}
|
||||
|
||||
module "jellyfin" {
|
||||
source = "./services/jellyfin"
|
||||
|
||||
user_id = var.user_id
|
||||
group_id = var.group_id
|
||||
timezone = var.timezone
|
||||
volume_path = var.volume_path
|
||||
data_root = var.data_root
|
||||
hostname = var.hostname
|
||||
networks = concat([module.media_server_network.name], var.networks)
|
||||
monitoring = local.monitoring
|
||||
}
|
||||
|
||||
module "sabnzbd" {
|
||||
source = "./services/sabnzbd"
|
||||
|
||||
user_id = var.user_id
|
||||
group_id = var.group_id
|
||||
timezone = var.timezone
|
||||
volume_path = var.volume_path
|
||||
data_root = var.data_root
|
||||
networks = concat([module.media_server_network.name], var.networks)
|
||||
monitoring = local.monitoring
|
||||
}
|
||||
|
||||
module "flaresolverr" {
|
||||
source = "./services/flaresolverr"
|
||||
|
||||
timezone = var.timezone
|
||||
log_level = "info"
|
||||
log_html = "false"
|
||||
captcha_solver = "none"
|
||||
networks = concat([module.media_server_network.name], var.networks)
|
||||
monitoring = local.monitoring
|
||||
}
|
||||
|
||||
module "autoheal" {
|
||||
source = "./services/autoheal"
|
||||
|
||||
networks = concat([module.media_server_network.name], var.networks)
|
||||
monitoring = local.monitoring
|
||||
}
|
||||
13
modules/20-services-apps/media-server/outputs.tf
Normal file
13
modules/20-services-apps/media-server/outputs.tf
Normal file
@@ -0,0 +1,13 @@
|
||||
output "service_definitions" {
|
||||
description = "Service definitions for integration with networking modules"
|
||||
value = [
|
||||
module.jellyfin.service_definition,
|
||||
module.jellyseerr.service_definition,
|
||||
module.sabnzbd.service_definition
|
||||
]
|
||||
}
|
||||
|
||||
output "network_name" {
|
||||
description = "Name of the media server network"
|
||||
value = module.media_server_network.name
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
variable "networks" {
|
||||
description = "List of networks to which the container should be attached"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "monitoring" {
|
||||
description = "Enable container monitoring"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "autoheal"
|
||||
image = "willfarrell/autoheal"
|
||||
tag = "latest"
|
||||
|
||||
autoheal_env_vars = {
|
||||
AUTOHEAL_CONTAINER_LABEL = "all"
|
||||
}
|
||||
|
||||
autoheal_volumes = [
|
||||
{
|
||||
host_path = "/var/run/docker.sock"
|
||||
container_path = "/var/run/docker.sock"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
module "autoheal" {
|
||||
source = "../../../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.tag
|
||||
volumes = local.autoheal_volumes
|
||||
env_vars = local.autoheal_env_vars
|
||||
networks = var.networks
|
||||
monitoring = var.monitoring
|
||||
restart_policy = "always"
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "Service definition for autoheal"
|
||||
value = {
|
||||
name = local.container_name
|
||||
endpoint = "http://${local.container_name}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
variable "timezone" {
|
||||
description = "Timezone for the container"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "List of networks to which the container should be attached"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "monitoring" {
|
||||
description = "Enable container monitoring"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "log_level" {
|
||||
description = "Log level for flaresolverr"
|
||||
type = string
|
||||
default = "info"
|
||||
}
|
||||
|
||||
variable "log_html" {
|
||||
description = "Whether to log HTML"
|
||||
type = string
|
||||
default = "false"
|
||||
}
|
||||
|
||||
variable "captcha_solver" {
|
||||
description = "Type of CAPTCHA solver to use"
|
||||
type = string
|
||||
default = "none"
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "flaresolverr"
|
||||
image = "ghcr.io/flaresolverr/flaresolverr"
|
||||
tag = "latest"
|
||||
|
||||
flaresolverr_env_vars = {
|
||||
LOG_LEVEL = var.log_level
|
||||
LOG_HTML = var.log_html
|
||||
CAPTCHA_SOLVER = var.captcha_solver
|
||||
TZ = var.timezone
|
||||
}
|
||||
}
|
||||
|
||||
module "flaresolverr" {
|
||||
source = "../../../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.tag
|
||||
env_vars = local.flaresolverr_env_vars
|
||||
ports = [{
|
||||
internal = 8191
|
||||
external = 8191
|
||||
protocol = "tcp"
|
||||
}]
|
||||
networks = var.networks
|
||||
monitoring = var.monitoring
|
||||
restart_policy = "always"
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "Service definition for flaresolverr"
|
||||
value = {
|
||||
name = local.container_name
|
||||
primary_port = 8191
|
||||
endpoint = "http://${local.container_name}:8191"
|
||||
}
|
||||
}
|
||||
114
modules/20-services-apps/media-server/services/jellyfin/main.tf
Normal file
114
modules/20-services-apps/media-server/services/jellyfin/main.tf
Normal file
@@ -0,0 +1,114 @@
|
||||
variable "user_id" {
|
||||
description = "User ID for container permissions"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "group_id" {
|
||||
description = "Group ID for container permissions"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "timezone" {
|
||||
description = "Timezone for the container"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "volume_path" {
|
||||
description = "Base directory for volumes"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "data_root" {
|
||||
description = "Root directory for media data"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "hostname" {
|
||||
description = "Hostname for the Jellyfin PublishedServerUrl"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "List of networks to which the container should be attached"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "monitoring" {
|
||||
description = "Enable container monitoring"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "jellyfin"
|
||||
image = "jellyfin/jellyfin"
|
||||
tag = "latest"
|
||||
|
||||
internal_ports = [
|
||||
{
|
||||
internal = 8096
|
||||
external = 8096
|
||||
protocol = "tcp"
|
||||
},
|
||||
{
|
||||
internal = 7359
|
||||
external = 7359
|
||||
protocol = "udp"
|
||||
},
|
||||
{
|
||||
internal = 1900
|
||||
external = 1900
|
||||
protocol = "udp"
|
||||
}
|
||||
]
|
||||
|
||||
jellyfin_volumes = [
|
||||
{
|
||||
host_path = "${var.volume_path}/jellyfin"
|
||||
container_path = "/config"
|
||||
read_only = false
|
||||
},
|
||||
{
|
||||
host_path = "${var.data_root}"
|
||||
container_path = "/data"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
jellyfin_devices = [
|
||||
"/dev/dri/:/dev/dri/"
|
||||
]
|
||||
|
||||
jellyfin_env_vars = {
|
||||
PUID = var.user_id
|
||||
PGID = var.group_id
|
||||
TZ = var.timezone
|
||||
JELLYFIN_PublishedServerUrl = "${var.hostname}/jellyfin"
|
||||
}
|
||||
}
|
||||
|
||||
module "jellyfin" {
|
||||
source = "../../../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.tag
|
||||
volumes = local.jellyfin_volumes
|
||||
env_vars = local.jellyfin_env_vars
|
||||
ports = local.internal_ports
|
||||
devices = local.jellyfin_devices
|
||||
networks = var.networks
|
||||
monitoring = var.monitoring
|
||||
restart_policy = "always"
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "Service definition for integration with networking modules"
|
||||
value = {
|
||||
name = local.container_name
|
||||
primary_port = 8096
|
||||
endpoint = "http://${local.container_name}:8096"
|
||||
subdomains = ["jellyfin"]
|
||||
publish_via = "reverse_proxy"
|
||||
proxied = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
variable "timezone" {
|
||||
description = "Timezone for the container"
|
||||
type = string
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
variable "monitoring" {
|
||||
description = "Enable container monitoring"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "jellyseerr"
|
||||
image = "fallenbagel/jellyseerr"
|
||||
tag = "latest"
|
||||
|
||||
jellyseerr_volumes = [
|
||||
{
|
||||
host_path = "${var.volume_path}/jellyseerr"
|
||||
container_path = "/app/config"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
jellyseerr_env_vars = {
|
||||
LOG_LEVEL = "debug"
|
||||
TZ = var.timezone
|
||||
}
|
||||
|
||||
jellyseerr_healthcheck = {
|
||||
test = ["CMD", "wget", "http://127.0.0.1:5055/api/v1/status", "-qO", "/dev/null"]
|
||||
interval = "30s"
|
||||
timeout = "5s"
|
||||
retries = 10
|
||||
start_period = "5s"
|
||||
}
|
||||
}
|
||||
|
||||
module "jellyseerr" {
|
||||
source = "../../../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.tag
|
||||
volumes = local.jellyseerr_volumes
|
||||
env_vars = local.jellyseerr_env_vars
|
||||
healthcheck = local.jellyseerr_healthcheck
|
||||
ports = [{
|
||||
internal = 5055
|
||||
external = 5055
|
||||
protocol = "tcp"
|
||||
}]
|
||||
networks = var.networks
|
||||
monitoring = var.monitoring
|
||||
restart_policy = "always"
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "Service definition for integration with networking modules"
|
||||
value = {
|
||||
name = local.container_name
|
||||
primary_port = 5055
|
||||
endpoint = "http://${local.container_name}:5055"
|
||||
subdomains = ["requests"]
|
||||
publish_via = "reverse_proxy"
|
||||
proxied = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
variable "user_id" {
|
||||
description = "User ID for container permissions"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "group_id" {
|
||||
description = "Group ID for container permissions"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "timezone" {
|
||||
description = "Timezone for the container"
|
||||
type = string
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
variable "monitoring" {
|
||||
description = "Enable container monitoring"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "prowlarr"
|
||||
image = "lscr.io/linuxserver/prowlarr"
|
||||
tag = "latest"
|
||||
|
||||
prowlarr_volumes = [
|
||||
{
|
||||
host_path = "${var.volume_path}/prowlarr"
|
||||
container_path = "/config"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
prowlarr_env_vars = {
|
||||
PUID = var.user_id
|
||||
PGID = var.group_id
|
||||
TZ = var.timezone
|
||||
}
|
||||
|
||||
prowlarr_healthcheck = {
|
||||
test = ["CMD", "curl", "--fail", "http://127.0.0.1:9696/prowlarr/ping"]
|
||||
interval = "30s"
|
||||
timeout = "5s"
|
||||
retries = 10
|
||||
start_period = "5s"
|
||||
}
|
||||
}
|
||||
|
||||
module "prowlarr" {
|
||||
source = "../../../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.tag
|
||||
volumes = local.prowlarr_volumes
|
||||
env_vars = local.prowlarr_env_vars
|
||||
healthcheck = local.prowlarr_healthcheck
|
||||
ports = [{
|
||||
internal = 9696
|
||||
external = 9696
|
||||
protocol = "tcp"
|
||||
}]
|
||||
networks = var.networks
|
||||
monitoring = var.monitoring
|
||||
restart_policy = "always"
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "Service definition for prowlarr"
|
||||
value = {
|
||||
name = local.container_name
|
||||
primary_port = 9696
|
||||
endpoint = "http://${local.container_name}:9696"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
variable "user_id" {
|
||||
description = "User ID for container permissions"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "group_id" {
|
||||
description = "Group ID for container permissions"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "timezone" {
|
||||
description = "Timezone for the container"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "volume_path" {
|
||||
description = "Base directory for volumes"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "download_root" {
|
||||
description = "Directory for downloads"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "List of networks to which the container should be attached"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "monitoring" {
|
||||
description = "Enable container monitoring"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "qbittorrent"
|
||||
image = "lscr.io/linuxserver/qbittorrent"
|
||||
tag = "libtorrentv1"
|
||||
|
||||
qbittorrent_volumes = [
|
||||
{
|
||||
host_path = "${var.volume_path}/qbittorrent"
|
||||
container_path = "/config"
|
||||
read_only = false
|
||||
},
|
||||
{
|
||||
host_path = var.download_root
|
||||
container_path = "/data/torrents"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
qbittorrent_env_vars = {
|
||||
PUID = var.user_id
|
||||
PGID = var.group_id
|
||||
TZ = var.timezone
|
||||
WEBUI_PORT = "8080"
|
||||
DOCKER_MODS = "ghcr.io/gabe565/linuxserver-mod-vuetorrent"
|
||||
}
|
||||
|
||||
qbittorrent_healthcheck = {
|
||||
test = ["CMD", "curl", "--fail", "http://127.0.0.1:8080", "https://google.com"]
|
||||
interval = "30s"
|
||||
timeout = "5s"
|
||||
retries = 10
|
||||
start_period = "5s"
|
||||
}
|
||||
}
|
||||
|
||||
module "qbittorrent" {
|
||||
source = "../../../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.tag
|
||||
volumes = local.qbittorrent_volumes
|
||||
env_vars = local.qbittorrent_env_vars
|
||||
healthcheck = local.qbittorrent_healthcheck
|
||||
ports = [{
|
||||
internal = 8080
|
||||
external = 8080
|
||||
protocol = "tcp"
|
||||
}]
|
||||
networks = var.networks
|
||||
monitoring = var.monitoring
|
||||
restart_policy = "always"
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "Service definition for qbittorrent"
|
||||
value = {
|
||||
name = local.container_name
|
||||
primary_port = 8080
|
||||
endpoint = "http://${local.container_name}:8080"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
variable "user_id" {
|
||||
description = "User ID for container permissions"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "group_id" {
|
||||
description = "Group ID for container permissions"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "timezone" {
|
||||
description = "Timezone for the container"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "volume_path" {
|
||||
description = "Base directory for volumes"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "data_root" {
|
||||
description = "Root directory for media data"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "List of networks to which the container should be attached"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "monitoring" {
|
||||
description = "Enable container monitoring"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "radarr"
|
||||
image = "lscr.io/linuxserver/radarr"
|
||||
tag = "latest"
|
||||
|
||||
radarr_volumes = [
|
||||
{
|
||||
host_path = "${var.volume_path}/radarr"
|
||||
container_path = "/config"
|
||||
read_only = false
|
||||
},
|
||||
{
|
||||
host_path = var.data_root
|
||||
container_path = "/data"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
radarr_env_vars = {
|
||||
PUID = var.user_id
|
||||
PGID = var.group_id
|
||||
TZ = var.timezone
|
||||
}
|
||||
|
||||
radarr_healthcheck = {
|
||||
test = ["CMD", "curl", "--fail", "http://127.0.0.1:7878/radarr/ping"]
|
||||
interval = "30s"
|
||||
timeout = "5s"
|
||||
retries = 10
|
||||
start_period = "5s"
|
||||
}
|
||||
}
|
||||
|
||||
module "radarr" {
|
||||
source = "../../../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.tag
|
||||
volumes = local.radarr_volumes
|
||||
env_vars = local.radarr_env_vars
|
||||
healthcheck = local.radarr_healthcheck
|
||||
ports = [{
|
||||
internal = 7878
|
||||
external = 7878
|
||||
protocol = "tcp"
|
||||
}]
|
||||
networks = var.networks
|
||||
monitoring = var.monitoring
|
||||
restart_policy = "always"
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "Service definition for radarr"
|
||||
value = {
|
||||
name = local.container_name
|
||||
primary_port = 7878
|
||||
endpoint = "http://${local.container_name}:7878"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
variable "user_id" {
|
||||
description = "User ID for container permissions"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "group_id" {
|
||||
description = "Group ID for container permissions"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "timezone" {
|
||||
description = "Timezone for the container"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "volume_path" {
|
||||
description = "Base directory for volumes"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "data_root" {
|
||||
description = "Root directory for media data"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "List of networks to which the container should be attached"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "monitoring" {
|
||||
description = "Enable container monitoring"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "readarr"
|
||||
image = "lscr.io/linuxserver/readarr"
|
||||
tag = "develop"
|
||||
|
||||
readarr_volumes = [
|
||||
{
|
||||
host_path = "${var.volume_path}/readarr"
|
||||
container_path = "/config"
|
||||
read_only = false
|
||||
},
|
||||
{
|
||||
host_path = var.data_root
|
||||
container_path = "/books"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
readarr_env_vars = {
|
||||
PUID = var.user_id
|
||||
PGID = var.group_id
|
||||
TZ = var.timezone
|
||||
}
|
||||
}
|
||||
|
||||
module "readarr" {
|
||||
source = "../../../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.tag
|
||||
volumes = local.readarr_volumes
|
||||
env_vars = local.readarr_env_vars
|
||||
ports = [{
|
||||
internal = 8787
|
||||
external = 8787
|
||||
protocol = "tcp"
|
||||
}]
|
||||
networks = var.networks
|
||||
monitoring = var.monitoring
|
||||
restart_policy = "always"
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "Service definition for readarr"
|
||||
value = {
|
||||
name = local.container_name
|
||||
primary_port = 8787
|
||||
endpoint = "http://${local.container_name}:8787"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
variable "user_id" {
|
||||
description = "User ID for container permissions"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "group_id" {
|
||||
description = "Group ID for container permissions"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "timezone" {
|
||||
description = "Timezone for the container"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "volume_path" {
|
||||
description = "Base directory for volumes"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "data_root" {
|
||||
description = "Root directory for media data"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "List of networks to which the container should be attached"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "monitoring" {
|
||||
description = "Enable container monitoring"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "sabnzbd"
|
||||
image = "lscr.io/linuxserver/sabnzbd"
|
||||
tag = "latest"
|
||||
|
||||
sabnzbd_volumes = [
|
||||
{
|
||||
host_path = "${var.volume_path}/sabnzbd/config"
|
||||
container_path = "/config"
|
||||
read_only = false
|
||||
},
|
||||
{
|
||||
host_path = "${var.data_root}/usenet/downloads"
|
||||
container_path = "/downloads"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
sabnzbd_env_vars = {
|
||||
PUID = var.user_id
|
||||
PGID = var.group_id
|
||||
TZ = var.timezone
|
||||
}
|
||||
}
|
||||
|
||||
module "sabnzbd" {
|
||||
source = "../../../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.tag
|
||||
volumes = local.sabnzbd_volumes
|
||||
env_vars = local.sabnzbd_env_vars
|
||||
ports = [{
|
||||
internal = 8080
|
||||
external = 6789
|
||||
protocol = "tcp"
|
||||
}]
|
||||
networks = var.networks
|
||||
monitoring = var.monitoring
|
||||
restart_policy = "unless-stopped"
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "Service definition for integration with networking modules"
|
||||
value = {
|
||||
name = local.container_name
|
||||
primary_port = 8080
|
||||
endpoint = "http://${local.container_name}:8080"
|
||||
subdomains = ["sabnzbd"]
|
||||
publish_via = "reverse_proxy"
|
||||
proxied = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
variable "user_id" {
|
||||
description = "User ID for container permissions"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "group_id" {
|
||||
description = "Group ID for container permissions"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "timezone" {
|
||||
description = "Timezone for the container"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "volume_path" {
|
||||
description = "Base directory for volumes"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "data_root" {
|
||||
description = "Root directory for media data"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "List of networks to which the container should be attached"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "monitoring" {
|
||||
description = "Enable container monitoring"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "sonarr"
|
||||
image = "lscr.io/linuxserver/sonarr"
|
||||
tag = "latest"
|
||||
|
||||
sonarr_volumes = [
|
||||
{
|
||||
host_path = "${var.volume_path}/sonarr"
|
||||
container_path = "/config"
|
||||
read_only = false
|
||||
},
|
||||
{
|
||||
host_path = var.data_root
|
||||
container_path = "/data"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
sonarr_env_vars = {
|
||||
PUID = var.user_id
|
||||
PGID = var.group_id
|
||||
TZ = var.timezone
|
||||
}
|
||||
|
||||
sonarr_healthcheck = {
|
||||
test = ["CMD", "curl", "--fail", "http://127.0.0.1:8989/sonarr/ping"]
|
||||
interval = "30s"
|
||||
timeout = "5s"
|
||||
retries = 10
|
||||
start_period = "5s"
|
||||
}
|
||||
}
|
||||
|
||||
module "sonarr" {
|
||||
source = "../../../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.tag
|
||||
volumes = local.sonarr_volumes
|
||||
env_vars = local.sonarr_env_vars
|
||||
healthcheck = local.sonarr_healthcheck
|
||||
ports = [{
|
||||
internal = 8989
|
||||
external = 8989
|
||||
protocol = "tcp"
|
||||
}]
|
||||
networks = var.networks
|
||||
monitoring = var.monitoring
|
||||
restart_policy = "always"
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "Service definition for sonarr"
|
||||
value = {
|
||||
name = local.container_name
|
||||
primary_port = 8989
|
||||
endpoint = "http://${local.container_name}:8989"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
variable "user_id" {
|
||||
description = "User ID for container permissions"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "group_id" {
|
||||
description = "Group ID for container permissions"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "timezone" {
|
||||
description = "Timezone for the container"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "download_root" {
|
||||
description = "Directory for downloads"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "sonarr_api_key" {
|
||||
description = "API key for Sonarr"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "radarr_api_key" {
|
||||
description = "API key for Radarr"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "List of networks to which the container should be attached"
|
||||
type = list(string)
|
||||
}
|
||||
|
||||
variable "monitoring" {
|
||||
description = "Enable container monitoring"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "unpackerr"
|
||||
image = "golift/unpackerr"
|
||||
tag = "latest"
|
||||
|
||||
unpackerr_volumes = [
|
||||
{
|
||||
host_path = var.download_root
|
||||
container_path = "/data/torrents"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
unpackerr_env_vars = {
|
||||
TZ = var.timezone
|
||||
UN_SONARR_0_URL = "http://sonarr:8989/sonarr"
|
||||
UN_SONARR_0_API_KEY = var.sonarr_api_key
|
||||
UN_RADARR_0_URL = "http://radarr:7878/radarr"
|
||||
UN_RADARR_0_API_KEY = var.radarr_api_key
|
||||
}
|
||||
|
||||
unpackerr_security_opts = [
|
||||
"no-new-privileges:true"
|
||||
]
|
||||
}
|
||||
|
||||
module "unpackerr" {
|
||||
source = "../../../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.tag
|
||||
volumes = local.unpackerr_volumes
|
||||
env_vars = local.unpackerr_env_vars
|
||||
security_opts = local.unpackerr_security_opts
|
||||
networks = var.networks
|
||||
monitoring = var.monitoring
|
||||
restart_policy = "always"
|
||||
user = "${var.user_id}:${var.group_id}"
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "Service definition for unpackerr"
|
||||
value = {
|
||||
name = local.container_name
|
||||
endpoint = "http://${local.container_name}"
|
||||
}
|
||||
}
|
||||
55
modules/20-services-apps/media-server/variables.tf
Normal file
55
modules/20-services-apps/media-server/variables.tf
Normal file
@@ -0,0 +1,55 @@
|
||||
variable "volume_path" {
|
||||
description = "Base directory for volumes (APP_DATA)"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "data_root" {
|
||||
description = "Root directory for media data (DATA_ROOT)"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "download_root" {
|
||||
description = "Directory for downloads (DOWNLOAD_ROOT)"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "user_id" {
|
||||
description = "User ID for container permissions"
|
||||
type = string
|
||||
default = "1000"
|
||||
}
|
||||
|
||||
variable "group_id" {
|
||||
description = "Group ID for container permissions"
|
||||
type = string
|
||||
default = "1000"
|
||||
}
|
||||
|
||||
variable "timezone" {
|
||||
description = "Timezone for the containers"
|
||||
type = string
|
||||
default = "UTC"
|
||||
}
|
||||
|
||||
variable "hostname" {
|
||||
description = "Hostname for the Jellyfin PublishedServerUrl"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "sonarr_api_key" {
|
||||
description = "API key for Sonarr"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "radarr_api_key" {
|
||||
description = "API key for Radarr"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "List of additional networks to which containers should be attached"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
@@ -8,7 +8,3 @@ N8N_PORT=5678
|
||||
N8N_PROTOCOL=http
|
||||
WEBHOOK_URL=https://n8n.yourdomain.com/
|
||||
NODE_FUNCTION_ALLOW_EXTERNAL=*
|
||||
|
||||
# MCP
|
||||
N8N_MCP_AUTH_TOKEN=
|
||||
N8N_API_KEY=
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
# n8n Module
|
||||
|
||||
This module deploys [n8n](https://n8n.io/), a workflow automation tool, along with its dependencies and the [n8n-mcp](https://github.com/czlonkowski/n8n-mcp) community node manager, as Docker containers in the homelab environment.
|
||||
This module deploys [n8n](https://n8n.io/), a workflow automation tool for technical people, as Docker containers in the homelab environment.
|
||||
|
||||
## Overview
|
||||
|
||||
The n8n module:
|
||||
|
||||
- Deploys four Docker containers:
|
||||
- Deploys two Docker containers:
|
||||
- `n8n`: The main workflow automation server
|
||||
- `n8n-postgres`: A PostgreSQL database backend
|
||||
- `n8n-redis`: A Redis instance for queuing
|
||||
- `n8n-mcp`: A community node management tool for n8n
|
||||
- Creates a dedicated Docker network (`n8n-network`) for container communication
|
||||
- Persists data to volumes on the host
|
||||
- Provides service definitions for integration with networking modules
|
||||
- Provides service definition for integration with networking modules
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -30,21 +28,18 @@ module "n8n" {
|
||||
| Variable | Description | Type | Default |
|
||||
| ------------- | ---------------------------------------------------------- | -------------- | ---------- |
|
||||
| `image_tag` | Tag of the n8n image to use | `string` | `"latest"` |
|
||||
| `volume_path` | Host path for n8n, Postgres, Redis, and n8n-mcp data volumes | `string` | - |
|
||||
| `volume_path` | Host path for n8n and Postgres data volumes | `string` | - |
|
||||
| `networks` | List of additional networks to which n8n should be attached | `list(string)` | `[]` |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
| ---------------------------- | ---------------------------------------------------------- |
|
||||
| `service_definition` | Service definition for the n8n container |
|
||||
| `n8n_mcp_service_definition` | Service definition for the n8n-mcp container |
|
||||
| Output | Description |
|
||||
| -------------------- | ---------------------------------------------------------- |
|
||||
| `service_definition` | Service definition for integration with networking modules |
|
||||
|
||||
## Service Definitions
|
||||
## Service Definition
|
||||
|
||||
This module outputs two service definitions that are used by the networking modules to expose the services.
|
||||
|
||||
### n8n
|
||||
This module outputs a service definition that is used by the networking modules to expose the service.
|
||||
|
||||
```hcl
|
||||
{
|
||||
@@ -52,63 +47,45 @@ This module outputs two service definitions that are used by the networking modu
|
||||
primary_port = 5678
|
||||
endpoint = "http://n8n:5678"
|
||||
subdomains = ["n8n"]
|
||||
publish_via = "tunnel"
|
||||
}
|
||||
```
|
||||
|
||||
### n8n-mcp
|
||||
|
||||
```hcl
|
||||
{
|
||||
name = "n8n-mcp"
|
||||
primary_port = 3000
|
||||
endpoint = "http://n8n-mcp:3000"
|
||||
subdomains = ["n8n-mcp"]
|
||||
publish_via = "tunnel"
|
||||
publish_via = "tunnel" # Only publish through Cloudflare tunnel
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The services require several environment variables to function properly. These are stored in a `.env` file in the module directory and read using the `dotenv` Terraform provider:
|
||||
n8n requires several environment variables to function properly. These are stored in a `.env` file in the module directory and read using the `dotenv` Terraform provider:
|
||||
|
||||
- **Database configuration (`n8n-postgres`)**:
|
||||
- Database configuration:
|
||||
- `POSTGRES_USER`: Root PostgreSQL user
|
||||
- `POSTGRES_PASSWORD`: Root PostgreSQL password
|
||||
- `POSTGRES_DB`: Database name for n8n
|
||||
- `POSTGRES_NON_ROOT_USER`: Non-root user for n8n to connect with
|
||||
- `POSTGRES_NON_ROOT_PASSWORD`: Password for the non-root user
|
||||
|
||||
- **n8n configuration (`n8n`)**:
|
||||
- n8n configuration:
|
||||
- `N8N_HOST`: Host for n8n to use
|
||||
- `N8N_PORT`: Port for n8n to use
|
||||
- `N8N_PROTOCOL`: Protocol for n8n (http or https)
|
||||
- `WEBHOOK_URL`: URL for webhooks
|
||||
- `NODE_FUNCTION_ALLOW_EXTERNAL`: Whether to allow external function calls
|
||||
|
||||
- **n8n-mcp configuration (`n8n-mcp`)**:
|
||||
- `N8N_MCP_AUTH_TOKEN`: Authentication token for n8n-mcp.
|
||||
- `N8N_API_KEY`: n8n API key for n8n-mcp to interact with the n8n instance.
|
||||
|
||||
## Data Persistence
|
||||
|
||||
The services store data in several volumes:
|
||||
n8n stores its data in two main volumes:
|
||||
|
||||
1. **n8n application data**: `/home/node/.n8n` in the container, mapped to `${volume_path}/n8n_storage/_data` on the host
|
||||
2. **PostgreSQL data**: `/var/lib/postgresql/data` in the container, mapped to `${volume_path}/db_storage/_data` on the host
|
||||
3. **Redis data**: `/data` in the container, mapped to `${volume_path}/redis_data` on the host
|
||||
4. **n8n-mcp data**: `/app/data` in the container, mapped to `${volume_path}/n8n_mcp_storage/_data` on the host
|
||||
1. n8n application data: `/home/node/.n8n` in the container, mapped to `${volume_path}/n8n_storage/_data` on the host
|
||||
2. PostgreSQL data: `/var/lib/postgresql/data` in the container, mapped to `${volume_path}/db_storage/_data` on the host
|
||||
|
||||
Additionally, an initialization script is mounted to the PostgreSQL container:
|
||||
- `/docker-entrypoint-initdb.d/init-data.sh` in the container, from `${volume_path}/init-data.sh` on the host
|
||||
|
||||
## Networking
|
||||
|
||||
The module creates a dedicated Docker network named `n8n-network` for communication between all containers. The `n8n` and `n8n-mcp` containers are also attached to any additional networks specified in the `networks` variable, allowing them to communicate with other services in the homelab.
|
||||
The module creates a dedicated Docker network named `n8n-network` for communication between the n8n and PostgreSQL containers. The n8n container is also attached to any additional networks specified in the `networks` variable, allowing it to communicate with other services in the homelab.
|
||||
|
||||
## Integration with Networking Modules
|
||||
|
||||
The services are configured to be exposed through a Cloudflare tunnel for secure remote access, set by `publish_via = "tunnel"`.
|
||||
This service is configured to be exposed through a Cloudflare tunnel for secure remote access, set by `publish_via = "tunnel"`.
|
||||
|
||||
## Example Integration in Main Configuration
|
||||
|
||||
@@ -119,41 +96,13 @@ module "n8n" {
|
||||
networks = [module.services.homelab_docker_network_name]
|
||||
}
|
||||
|
||||
# The service definitions are automatically included in the services output
|
||||
# The service definition is automatically included in the services output
|
||||
module "services" {
|
||||
source = "./modules/services"
|
||||
# ...
|
||||
service_definitions = [
|
||||
module.n8n.service_definition,
|
||||
module.n8n.n8n_mcp_service_definition,
|
||||
# Other service definitions
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Using n8n-mcp with your IDE
|
||||
|
||||
To connect your IDE to the `n8n-mcp` server, you can use the following configuration in your IDE's settings. This allows the IDE to use the n8n instance as a tool provider.
|
||||
|
||||
Make sure to replace `<domain>` with your actual domain and populate the `AUTH_TOKEN` with the value of `N8N_MCP_AUTH_TOKEN` from your `.env` file.
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"n8n-mcp": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"mcp-remote",
|
||||
"https://n8n-mcp.<domain>/mcp",
|
||||
"--header",
|
||||
"Authorization: Bearer ${AUTH_TOKEN}",
|
||||
"--transport",
|
||||
"http-only"
|
||||
],
|
||||
"env": {
|
||||
"AUTH_TOKEN": "..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -23,24 +23,17 @@ variable "networks" {
|
||||
default = []
|
||||
}
|
||||
|
||||
module "system_globals" {
|
||||
source = "../../00-globals/system"
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "n8n"
|
||||
database_name = "n8n-postgres"
|
||||
redis_name = "n8n-redis"
|
||||
n8n_image = "docker.n8n.io/n8nio/n8n"
|
||||
database_image = "postgres"
|
||||
redis_image = "redis"
|
||||
n8n_tag = var.image_tag != "" ? var.image_tag : "latest"
|
||||
database_tag = "16"
|
||||
redis_tag = "7-alpine"
|
||||
monitoring = true
|
||||
env_file = "${path.module}/.env"
|
||||
container_name = "n8n"
|
||||
database_name = "n8n-postgres"
|
||||
n8n_image = "docker.n8n.io/n8nio/n8n"
|
||||
database_image = "postgres"
|
||||
n8n_tag = var.image_tag != "" ? var.image_tag : "latest"
|
||||
database_tag = "16"
|
||||
monitoring = true
|
||||
env_file = "${path.module}/.env"
|
||||
n8n_internal_port = 5678
|
||||
|
||||
|
||||
# Define volumes
|
||||
n8n_volumes = [
|
||||
{
|
||||
@@ -49,7 +42,7 @@ locals {
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
database_volumes = [
|
||||
{
|
||||
host_path = "${var.volume_path}/db_storage/_data"
|
||||
@@ -62,34 +55,29 @@ locals {
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
# Environment variables for the database
|
||||
database_env_vars = {
|
||||
POSTGRES_USER = provider::dotenv::get_by_key("POSTGRES_USER", local.env_file)
|
||||
POSTGRES_PASSWORD = provider::dotenv::get_by_key("POSTGRES_PASSWORD", local.env_file)
|
||||
POSTGRES_DB = provider::dotenv::get_by_key("POSTGRES_DB", local.env_file)
|
||||
POSTGRES_NON_ROOT_USER = provider::dotenv::get_by_key("POSTGRES_NON_ROOT_USER", local.env_file)
|
||||
POSTGRES_USER = provider::dotenv::get_by_key("POSTGRES_USER", local.env_file)
|
||||
POSTGRES_PASSWORD = provider::dotenv::get_by_key("POSTGRES_PASSWORD", local.env_file)
|
||||
POSTGRES_DB = provider::dotenv::get_by_key("POSTGRES_DB", local.env_file)
|
||||
POSTGRES_NON_ROOT_USER = provider::dotenv::get_by_key("POSTGRES_NON_ROOT_USER", local.env_file)
|
||||
POSTGRES_NON_ROOT_PASSWORD = provider::dotenv::get_by_key("POSTGRES_NON_ROOT_PASSWORD", local.env_file)
|
||||
}
|
||||
|
||||
|
||||
# Environment variables for n8n
|
||||
n8n_env_vars = {
|
||||
DB_TYPE = "postgresdb"
|
||||
DB_POSTGRESDB_HOST = local.database_name
|
||||
DB_POSTGRESDB_PORT = 5432
|
||||
DB_POSTGRESDB_DATABASE = provider::dotenv::get_by_key("POSTGRES_DB", local.env_file)
|
||||
DB_POSTGRESDB_USER = provider::dotenv::get_by_key("POSTGRES_NON_ROOT_USER", local.env_file)
|
||||
DB_POSTGRESDB_PASSWORD = provider::dotenv::get_by_key("POSTGRES_NON_ROOT_PASSWORD", local.env_file)
|
||||
N8N_HOST = provider::dotenv::get_by_key("N8N_HOST", local.env_file)
|
||||
N8N_PORT = provider::dotenv::get_by_key("N8N_PORT", local.env_file)
|
||||
N8N_PROTOCOL = provider::dotenv::get_by_key("N8N_PROTOCOL", local.env_file)
|
||||
WEBHOOK_URL = provider::dotenv::get_by_key("WEBHOOK_URL", local.env_file)
|
||||
NODE_FUNCTION_ALLOW_EXTERNAL = provider::dotenv::get_by_key("NODE_FUNCTION_ALLOW_EXTERNAL", local.env_file)
|
||||
GENERIC_TIMEZONE = module.system_globals.timezone
|
||||
QUEUE_BULL_REDIS_HOST = local.redis_name
|
||||
QUEUE_BULL_REDIS_PORT = 6379
|
||||
QUEUE_BULL_REDIS_USERNAME = "redis"
|
||||
QUEUE_BULL_REDIS_PASSWORD = "redis"
|
||||
DB_TYPE = "postgresdb"
|
||||
DB_POSTGRESDB_HOST = local.database_name
|
||||
DB_POSTGRESDB_PORT = 5432
|
||||
DB_POSTGRESDB_DATABASE = provider::dotenv::get_by_key("POSTGRES_DB", local.env_file)
|
||||
DB_POSTGRESDB_USER = provider::dotenv::get_by_key("POSTGRES_NON_ROOT_USER", local.env_file)
|
||||
DB_POSTGRESDB_PASSWORD = provider::dotenv::get_by_key("POSTGRES_NON_ROOT_PASSWORD", local.env_file)
|
||||
N8N_HOST = provider::dotenv::get_by_key("N8N_HOST", local.env_file)
|
||||
N8N_PORT = provider::dotenv::get_by_key("N8N_PORT", local.env_file)
|
||||
N8N_PROTOCOL = provider::dotenv::get_by_key("N8N_PROTOCOL", local.env_file)
|
||||
WEBHOOK_URL = provider::dotenv::get_by_key("WEBHOOK_URL", local.env_file)
|
||||
NODE_FUNCTION_ALLOW_EXTERNAL = provider::dotenv::get_by_key("NODE_FUNCTION_ALLOW_EXTERNAL", local.env_file)
|
||||
}
|
||||
|
||||
# Healthcheck configuration for the database
|
||||
@@ -100,66 +88,12 @@ locals {
|
||||
retries = 10
|
||||
start_period = "10s"
|
||||
}
|
||||
|
||||
# Healthcheck configuration for Redis
|
||||
redis_healthcheck = {
|
||||
test = ["CMD-SHELL", "redis-cli ping"]
|
||||
interval = "5s"
|
||||
timeout = "5s"
|
||||
retries = 10
|
||||
start_period = "10s"
|
||||
}
|
||||
|
||||
# Define Redis volume
|
||||
redis_volumes = [
|
||||
{
|
||||
host_path = "${var.volume_path}/redis_data"
|
||||
container_path = "/data"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
n8n_mcp_container_name = "n8n-mcp"
|
||||
n8n_mcp_image = "ghcr.io/czlonkowski/n8n-mcp"
|
||||
n8n_mcp_tag = "latest"
|
||||
n8n_mcp_internal_port = 3000
|
||||
|
||||
n8n_mcp_volumes = [
|
||||
{
|
||||
host_path = "${var.volume_path}/n8n_mcp_storage/_data"
|
||||
container_path = "/app/data"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
n8n_mcp_env_vars = {
|
||||
MCP_MODE = "http"
|
||||
USE_FIXED_HTTP = "true"
|
||||
AUTH_TOKEN = provider::dotenv::get_by_key("N8N_MCP_AUTH_TOKEN", local.env_file)
|
||||
N8N_API_URL = "http://${local.container_name}:${local.n8n_internal_port}"
|
||||
N8N_API_KEY = provider::dotenv::get_by_key("N8N_API_KEY", local.env_file)
|
||||
NODE_ENV = "production"
|
||||
LOG_LEVEL = "info"
|
||||
PORT = local.n8n_mcp_internal_port
|
||||
NODE_DB_PATH = "/app/data/nodes.db"
|
||||
REBUILD_ON_START = "false"
|
||||
GENERIC_TIMEZONE = module.system_globals.timezone
|
||||
}
|
||||
|
||||
n8n_mcp_healthcheck = {
|
||||
test = ["CMD", "curl", "-f", "http://127.0.0.1:${local.n8n_mcp_internal_port}/health"]
|
||||
interval = "30s"
|
||||
timeout = "10s"
|
||||
retries = 3
|
||||
start_period = "40s"
|
||||
}
|
||||
}
|
||||
|
||||
module "n8n_network" {
|
||||
source = "../../01-networking/docker-network"
|
||||
name = "n8n-network"
|
||||
driver = "bridge"
|
||||
subnet = "172.24.0.0/16"
|
||||
}
|
||||
|
||||
# Create the PostgreSQL container
|
||||
@@ -169,31 +103,12 @@ module "postgres" {
|
||||
image = local.database_image
|
||||
tag = local.database_tag
|
||||
volumes = local.database_volumes
|
||||
user = "1000:1000"
|
||||
env_vars = local.database_env_vars
|
||||
networks = [module.n8n_network.name]
|
||||
monitoring = local.monitoring
|
||||
healthcheck = local.database_healthcheck
|
||||
}
|
||||
|
||||
# Create the Redis container
|
||||
module "redis" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.redis_name
|
||||
image = local.redis_image
|
||||
tag = local.redis_tag
|
||||
volumes = local.redis_volumes
|
||||
user = "1000:1000"
|
||||
env_vars = {
|
||||
REDIS_USERNAME = "redis"
|
||||
REDIS_PASSWORD = "redis"
|
||||
}
|
||||
networks = [module.n8n_network.name]
|
||||
monitoring = local.monitoring
|
||||
command = ["redis-server", "--requirepass", "redis", "--user", "redis", "on", ">redis", "~*", "+@all"]
|
||||
healthcheck = local.redis_healthcheck
|
||||
}
|
||||
|
||||
# Create the n8n container
|
||||
module "n8n" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
@@ -201,26 +116,10 @@ module "n8n" {
|
||||
image = local.n8n_image
|
||||
tag = local.n8n_tag
|
||||
volumes = local.n8n_volumes
|
||||
user = "1000:1000"
|
||||
env_vars = local.n8n_env_vars
|
||||
networks = concat([module.n8n_network.name], var.networks)
|
||||
monitoring = local.monitoring
|
||||
depends_on = [module.postgres, module.redis]
|
||||
}
|
||||
|
||||
# Create the n8n-mcp container
|
||||
module "n8n_mcp" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.n8n_mcp_container_name
|
||||
image = local.n8n_mcp_image
|
||||
tag = local.n8n_mcp_tag
|
||||
volumes = local.n8n_mcp_volumes
|
||||
user = "1000:1000"
|
||||
env_vars = local.n8n_mcp_env_vars
|
||||
networks = concat([module.n8n_network.name], var.networks)
|
||||
monitoring = local.monitoring
|
||||
healthcheck = local.n8n_mcp_healthcheck
|
||||
depends_on = [module.n8n]
|
||||
depends_on = [module.postgres]
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
@@ -233,14 +132,3 @@ output "service_definition" {
|
||||
publish_via = "tunnel"
|
||||
}
|
||||
}
|
||||
|
||||
output "n8n_mcp_service_definition" {
|
||||
description = "General service definition with optional ingress configuration for n8n-mcp"
|
||||
value = {
|
||||
name = local.n8n_mcp_container_name
|
||||
primary_port = local.n8n_mcp_internal_port
|
||||
endpoint = "http://${local.n8n_mcp_container_name}:${local.n8n_mcp_internal_port}"
|
||||
subdomains = ["n8n-mcp"]
|
||||
publish_via = "tunnel"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# Database Configuration
|
||||
DB_USERNAME=postgres
|
||||
DB_PASSWORD=change_this_password
|
||||
DB_DATABASE=root_db
|
||||
@@ -1,106 +0,0 @@
|
||||
# NocoDB Module
|
||||
|
||||
This module deploys [NocoDB](https://www.nocodb.com/), an open-source no-code database platform that transforms PostgreSQL into a smart spreadsheet interface, as Docker containers in the homelab environment.
|
||||
|
||||
## Overview
|
||||
|
||||
The NocoDB module:
|
||||
|
||||
- Deploys two Docker containers:
|
||||
- `nocodb`: The main NocoDB application server
|
||||
- `nocodb-postgres`: A PostgreSQL database backend
|
||||
- Creates a dedicated Docker network (`nocodb-network`) for container communication
|
||||
- Persists data to volumes on the host
|
||||
- Provides service definition for integration with networking modules
|
||||
|
||||
## Usage
|
||||
|
||||
```hcl
|
||||
module "nocodb" {
|
||||
source = "./modules/20-services-apps/nocodb"
|
||||
volume_path = "/path/to/volumes"
|
||||
networks = ["homelab-network"]
|
||||
postgres_user = "postgres"
|
||||
postgres_password = "your_secure_password"
|
||||
postgres_db = "root_db"
|
||||
}
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
| Variable | Description | Type | Default |
|
||||
| ------------------- | -------------------------------------------------------------- | -------------- | ---------------------- |
|
||||
| `image_tag` | Tag of the NocoDB image to use | `string` | `"latest"` |
|
||||
| `postgres_image_tag`| Tag of the PostgreSQL image to use | `string` | `"16.6"` |
|
||||
| `volume_path` | Host path for NocoDB and database data volumes | `string` | - |
|
||||
| `networks` | List of networks to which NocoDB should be attached | `list(string)` | `[]` |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
| -------------------- | ---------------------------------------------------------- |
|
||||
| `service_definition` | Service definition for integration with networking modules |
|
||||
|
||||
## Service Definition
|
||||
|
||||
This module outputs a service definition that is used by the networking modules to expose the service.
|
||||
|
||||
```hcl
|
||||
{
|
||||
name = "nocodb"
|
||||
primary_port = 8080
|
||||
endpoint = "http://nocodb:8080"
|
||||
subdomains = ["nocodb"]
|
||||
publish_via = "tunnel" # Only publish through Cloudflare tunnel
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
NocoDB requires several environment variables to function properly. These are stored in a `.env` file in the module directory and read using the `dotenv` Terraform provider:
|
||||
|
||||
- Database configuration:
|
||||
|
||||
- `DB_USERNAME`: PostgreSQL user
|
||||
- `DB_PASSWORD`: PostgreSQL password
|
||||
- `DB_DATABASE`: Database name (defaults to "root_db")
|
||||
|
||||
## Data Persistence
|
||||
|
||||
NocoDB stores its data in two main volumes:
|
||||
|
||||
1. NocoDB application data: `/usr/app/data` in the container, mapped to `${volume_path}/nocodb/data` on the host
|
||||
2. PostgreSQL data: `/var/lib/postgresql/data` in the container, mapped to `${volume_path}/nocodb/postgres/data` on the host
|
||||
|
||||
## Networking
|
||||
|
||||
The module creates a dedicated Docker network named `nocodb-network` for communication between the NocoDB components. The NocoDB server container is also attached to any additional networks specified in the `networks` variable, allowing it to communicate with other services in the homelab.
|
||||
|
||||
## Dependencies
|
||||
|
||||
The NocoDB container depends on PostgreSQL, which includes a healthcheck to ensure it's ready before NocoDB starts.
|
||||
|
||||
## Integration with Networking Modules
|
||||
|
||||
This service is configured to be exposed through a Cloudflare tunnel for secure remote access, set by `publish_via = "tunnel"`.
|
||||
|
||||
## Example Integration in Main Configuration
|
||||
|
||||
```hcl
|
||||
module "nocodb" {
|
||||
source = "./modules/20-services-apps/nocodb"
|
||||
volume_path = module.system_globals.volume_host
|
||||
networks = [module.services.homelab_docker_network_name]
|
||||
postgres_password = "your_secure_password"
|
||||
}
|
||||
|
||||
# The service definition is automatically included in the services output
|
||||
module "services" {
|
||||
source = "./modules/services"
|
||||
# ...
|
||||
service_definitions = [
|
||||
module.nocodb.service_definition,
|
||||
# Other service definitions
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -1,129 +0,0 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
dotenv = {
|
||||
source = "germanbrew/dotenv"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "image_tag" {
|
||||
description = "Tag of the NocoDB image to use"
|
||||
type = string
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
variable "postgres_image_tag" {
|
||||
description = "Tag of the Postgres image to use"
|
||||
type = string
|
||||
default = "16.6"
|
||||
}
|
||||
|
||||
variable "volume_path" {
|
||||
description = "Host path for NocoDB data volumes"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "List of networks to which the containers should be attached"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "nocodb"
|
||||
postgres_name = "nocodb-postgres"
|
||||
nocodb_image = "nocodb/nocodb"
|
||||
postgres_image = "postgres"
|
||||
nocodb_tag = var.image_tag
|
||||
postgres_tag = var.postgres_image_tag
|
||||
monitoring = true
|
||||
nocodb_internal_port = 8080
|
||||
env_file = "${path.module}/.env"
|
||||
postgres_user = provider::dotenv::get_by_key("DB_USERNAME", local.env_file)
|
||||
postgres_password = provider::dotenv::get_by_key("DB_PASSWORD", local.env_file)
|
||||
postgres_db = provider::dotenv::get_by_key("DB_DATABASE", local.env_file)
|
||||
|
||||
# Define volumes
|
||||
nocodb_volumes = [
|
||||
{
|
||||
host_path = "${var.volume_path}/data"
|
||||
container_path = "/usr/app/data"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
postgres_volumes = [
|
||||
{
|
||||
host_path = "${var.volume_path}/postgres/data"
|
||||
container_path = "/var/lib/postgresql/data"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
# Environment variables for postgres
|
||||
postgres_env_vars = {
|
||||
POSTGRES_USER = local.postgres_user
|
||||
POSTGRES_PASSWORD = local.postgres_password
|
||||
POSTGRES_DB = local.postgres_db
|
||||
POSTGRES_INITDB_ARGS = "--data-checksums"
|
||||
POSTGRES_HOST_AUTH_METHOD = "trust"
|
||||
}
|
||||
|
||||
# Environment variables for NocoDB
|
||||
nocodb_env_vars = {
|
||||
NC_DB = "pg://${local.postgres_name}:5432?u=${local.postgres_user}&p=${local.postgres_password}&d=${local.postgres_db}"
|
||||
}
|
||||
|
||||
# Healthcheck configuration for Postgres
|
||||
postgres_healthcheck = {
|
||||
test = ["CMD", "pg_isready", "-U", local.postgres_user, "-d", local.postgres_db]
|
||||
interval = "10s"
|
||||
timeout = "2s"
|
||||
retries = 10
|
||||
start_period = "5s"
|
||||
}
|
||||
}
|
||||
|
||||
module "nocodb_network" {
|
||||
source = "../../01-networking/docker-network"
|
||||
name = "nocodb-network"
|
||||
subnet = "11.101.0.0/16"
|
||||
driver = "bridge"
|
||||
}
|
||||
|
||||
# Create the PostgreSQL container
|
||||
module "postgres" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.postgres_name
|
||||
image = local.postgres_image
|
||||
tag = local.postgres_tag
|
||||
volumes = local.postgres_volumes
|
||||
env_vars = local.postgres_env_vars
|
||||
networks = [module.nocodb_network.name]
|
||||
monitoring = local.monitoring
|
||||
healthcheck = local.postgres_healthcheck
|
||||
}
|
||||
|
||||
# Create the NocoDB container
|
||||
module "nocodb" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.nocodb_image
|
||||
tag = local.nocodb_tag
|
||||
volumes = local.nocodb_volumes
|
||||
env_vars = local.nocodb_env_vars
|
||||
networks = concat([module.nocodb_network.name], var.networks)
|
||||
monitoring = local.monitoring
|
||||
depends_on = [module.postgres]
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "General service definition with optional ingress configuration"
|
||||
value = {
|
||||
name = local.container_name
|
||||
primary_port = local.nocodb_internal_port
|
||||
endpoint = "http://${local.container_name}:${local.nocodb_internal_port}"
|
||||
subdomains = ["db"]
|
||||
publish_via = "tunnel"
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
# Portainer Module
|
||||
|
||||
This module deploys [Portainer](https://www.portainer.io/), a lightweight management UI that allows you to easily manage your different Docker environments.
|
||||
|
||||
## Overview
|
||||
|
||||
The Portainer module:
|
||||
|
||||
- Deploys one Docker container: `portainer`.
|
||||
- Mounts the Docker socket to allow Portainer to manage the Docker environment.
|
||||
- Persists Portainer data to a volume on the host.
|
||||
- Provides a service definition for integration with networking modules.
|
||||
|
||||
## Usage
|
||||
|
||||
```hcl
|
||||
module "portainer" {
|
||||
source = "./modules/20-services-apps/portainer"
|
||||
volume_path = "/path/to/volumes/portainer"
|
||||
networks = ["homelab-network"]
|
||||
}
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
| Variable | Description |
|
||||
| ------------- | ---------------------------------------------------------------- |
|
||||
| `image_tag` | Tag of the Portainer image to use |
|
||||
| `volume_path` | Host path for Portainer data volume |
|
||||
| `networks` | List of additional networks to which Portainer should be attached |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
| -------------------- | ---------------------------------------------------------- |
|
||||
| `service_definition` | Service definition for integration with networking modules |
|
||||
|
||||
## Service Definition
|
||||
|
||||
This module outputs a service definition that is used by the networking modules to expose the service.
|
||||
|
||||
```hcl
|
||||
{
|
||||
name = "portainer"
|
||||
primary_port = 9000
|
||||
endpoint = "http://portainer:9000"
|
||||
subdomains = ["portainer"]
|
||||
publish_via = "reverse_proxy"
|
||||
}
|
||||
```
|
||||
|
||||
## Data Persistence
|
||||
|
||||
Portainer stores its data in a single volume:
|
||||
|
||||
1. Portainer data: `/data` in the container, mapped to `${volume_path}/data` on the host.
|
||||
|
||||
It also mounts the Docker socket from `/var/run/docker.sock` on the host to `/var/run/docker.sock` in the container to manage Docker.
|
||||
|
||||
## Example Integration in Main Configuration
|
||||
|
||||
```hcl
|
||||
module "portainer" {
|
||||
source = "./modules/20-services-apps/portainer"
|
||||
volume_path = "${module.system_globals.volume_host}/portainer"
|
||||
networks = [module.services.homelab_docker_network_name]
|
||||
}
|
||||
|
||||
# The service definition is automatically included in the services output
|
||||
module "services" {
|
||||
source = "./modules/services"
|
||||
# ...
|
||||
service_definitions = [
|
||||
module.portainer.service_definition,
|
||||
# Other service definitions
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -1,67 +0,0 @@
|
||||
|
||||
variable "image_tag" {
|
||||
description = "The tag for the portainer 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 = "portainer"
|
||||
image = "portainer/portainer-ce"
|
||||
tag = var.image_tag
|
||||
monitoring = true
|
||||
internal_port = 9000
|
||||
exposed_port = 9000
|
||||
|
||||
# Define volumes
|
||||
volumes = [
|
||||
{
|
||||
host_path = "/var/run/docker.sock"
|
||||
container_path = "/var/run/docker.sock"
|
||||
read_only = false
|
||||
},
|
||||
{
|
||||
host_path = "${var.volume_path}/data"
|
||||
container_path = "/data"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Create the portainer container
|
||||
module "portainer" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.tag
|
||||
volumes = local.volumes
|
||||
networks = var.networks
|
||||
monitoring = local.monitoring
|
||||
ports = [
|
||||
{
|
||||
internal = local.internal_port
|
||||
external = local.exposed_port
|
||||
protocol = "tcp"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "General service definition with optional ingress configuration"
|
||||
value = {
|
||||
name = local.container_name
|
||||
primary_port = local.internal_port
|
||||
endpoint = "http://${local.container_name}:${local.internal_port}"
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
# qBittorrent Module
|
||||
|
||||
This module deploys qBittorrent as a Docker container with the Vuetorrent UI mod.
|
||||
|
||||
## Overview
|
||||
|
||||
- Container: `qbittorrent` (LinuxServer.io) with `vuetorrent` mod
|
||||
- Web UI on TCP 8080
|
||||
- Mounts `/config` and `/data/torrents`
|
||||
|
||||
## Usage
|
||||
|
||||
Without Gluetun:
|
||||
|
||||
```hcl
|
||||
module "qbittorrent" {
|
||||
source = "./modules/20-services-apps/qbittorrent"
|
||||
volume_path = "/srv/appdata/qbittorrent"
|
||||
downloads_path = "/srv/data/torrents"
|
||||
networks = [module.media_docker_network.name]
|
||||
}
|
||||
```
|
||||
|
||||
With Gluetun (recommended for privacy):
|
||||
|
||||
```hcl
|
||||
module "gluetun" {
|
||||
source = "./modules/20-services-apps/gluetun"
|
||||
volume_path = "/srv/appdata/gluetun"
|
||||
networks = [module.media_docker_network.name]
|
||||
# Optional: expose qBittorrent UI to the host via Gluetun
|
||||
# ports = [{ internal = 8080, external = 8080, protocol = "tcp" }]
|
||||
}
|
||||
|
||||
module "qbittorrent" {
|
||||
source = "./modules/20-services-apps/qbittorrent"
|
||||
volume_path = "/srv/appdata/qbittorrent"
|
||||
downloads_path = "/srv/data/torrents"
|
||||
networks = [module.media_docker_network.name]
|
||||
connect_via_gluetun = true
|
||||
gluetun_container_name = "gluetun"
|
||||
}
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
| Variable | Description | Type | Default |
|
||||
| ----------------------- | --------------------------------------------------------------- | -------------- | ----------- |
|
||||
| `volume_path` | Base directory for qBittorrent config | `string` | - |
|
||||
| `downloads_path` | Directory for downloads mounted at /data/torrents | `string` | - |
|
||||
| `networks` | Networks to attach (ignored when `connect_via_gluetun` is true) | `list(string)` | `[]` |
|
||||
| `connect_via_gluetun` | Route qBittorrent through Gluetun (network_mode=container:gluetun) | `bool` | `false` |
|
||||
| `gluetun_container_name`| Container name of the Gluetun instance | `string` | `"gluetun"` |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
| -------------------- | ----------------------------------- |
|
||||
| `service_definition` | Service definition for integration with networking modules |
|
||||
|
||||
## Service Definition
|
||||
|
||||
This module outputs a service definition that is used by the networking modules. qBittorrent is not published externally.
|
||||
|
||||
```hcl
|
||||
{
|
||||
name = "qbittorrent"
|
||||
primary_port = 8080
|
||||
endpoint = "http://qbittorrent:8080"
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- Defaults:
|
||||
- `WEBUI_PORT=8080`
|
||||
- `DOCKER_MODS=ghcr.io/gabe565/linuxserver-mod-vuetorrent`
|
||||
- `TZ`, `PUID`, and `PGID` are injected by the generic docker-service module from system globals.
|
||||
|
||||
## Data Persistence
|
||||
|
||||
- `/config` -> `${volume_path}`
|
||||
- `/data/torrents` -> `${downloads_path}`
|
||||
- Ensure host paths exist and permissions align with the container user.
|
||||
|
||||
## Networking
|
||||
|
||||
- When `connect_via_gluetun = false`:
|
||||
- Container attaches to `networks` and exposes its Web UI internally at `http://qbittorrent:8080`.
|
||||
- When `connect_via_gluetun = true`:
|
||||
- Container runs with `network_mode = container:<gluetun_container_name>`.
|
||||
- Do not publish ports on qBittorrent. If you need host access, publish `8080/tcp` on the Gluetun module instead.
|
||||
- Other containers should reach the Web UI at `http://gluetun:8080` when on the same Docker network as Gluetun.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- No explicit inter-container dependencies. Healthcheck ensures readiness.
|
||||
|
||||
## Integration with Networking Modules
|
||||
|
||||
This service is not published externally. Its service definition is included in the aggregated `module.services.service_definitions` for internal discovery and potential future use by networking modules.
|
||||
|
||||
## Example Integration in Main Configuration
|
||||
|
||||
```hcl
|
||||
# In services/main.tf
|
||||
module "qbittorrent" {
|
||||
source = "${local.module_dir}/20-services-apps/qbittorrent"
|
||||
volume_path = "${local.volume_host}/qbittorrent"
|
||||
downloads_path = "${local.data_host}/torrents"
|
||||
networks = [module.media_docker_network.name]
|
||||
connect_via_gluetun = true
|
||||
gluetun_container_name = "gluetun"
|
||||
}
|
||||
```
|
||||
|
||||
The service definition is exported by the `services` module as `module.services.service_definitions` and consumed by networking modules in the root `main.tf`.
|
||||
@@ -1,86 +0,0 @@
|
||||
variable "volume_path" {
|
||||
description = "Base directory for qBittorrent config"
|
||||
type = string
|
||||
}
|
||||
variable "downloads_path" {
|
||||
description = "Directory for downloads mounted at /data/torrents"
|
||||
type = string
|
||||
}
|
||||
variable "networks" {
|
||||
description = "List of networks to attach"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
// When true, run qBittorrent through Gluetun by sharing its network namespace
|
||||
variable "connect_via_gluetun" {
|
||||
description = "Route qBittorrent through Gluetun (network_mode=container:gluetun)"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "gluetun_container_name" {
|
||||
description = "Container name of the Gluetun instance to share network with"
|
||||
type = string
|
||||
default = "gluetun"
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "qbittorrent"
|
||||
image = "lscr.io/linuxserver/qbittorrent"
|
||||
tag = "libtorrentv1"
|
||||
monitoring = true
|
||||
internal_port = 8080
|
||||
|
||||
use_gluetun = var.connect_via_gluetun
|
||||
gluetun_name = var.gluetun_container_name
|
||||
network_mode = local.use_gluetun ? "container:${local.gluetun_name}" : "bridge"
|
||||
|
||||
env_vars = {
|
||||
WEBUI_PORT = "8080"
|
||||
DOCKER_MODS = "ghcr.io/gabe565/linuxserver-mod-vuetorrent"
|
||||
}
|
||||
|
||||
volumes = [
|
||||
{
|
||||
host_path = var.volume_path,
|
||||
container_path = "/config",
|
||||
read_only = false
|
||||
},
|
||||
{
|
||||
host_path = var.downloads_path,
|
||||
container_path = "/data/torrents",
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
healthcheck = {
|
||||
test = ["CMD", "curl", "--fail", "http://127.0.0.1:8080", "https://google.com"]
|
||||
interval = "60s"
|
||||
timeout = "5s"
|
||||
retries = 10
|
||||
}
|
||||
}
|
||||
|
||||
module "qbittorrent" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.tag
|
||||
env_vars = local.env_vars
|
||||
volumes = local.volumes
|
||||
network_mode = local.network_mode
|
||||
networks = local.use_gluetun ? [] : var.networks
|
||||
monitoring = local.monitoring
|
||||
healthcheck = local.healthcheck
|
||||
ports = local.use_gluetun ? [] : [{ internal = local.internal_port, external = local.internal_port, protocol = "tcp" }]
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "Service definition for qBittorrent (not published)"
|
||||
value = {
|
||||
name = local.container_name
|
||||
primary_port = local.internal_port
|
||||
endpoint = "http://${local.container_name}:${local.internal_port}"
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
# Sabnzbd Module
|
||||
|
||||
This module deploys Sabnzbd as a Docker container and outputs a non-published service definition.
|
||||
|
||||
## Overview
|
||||
|
||||
- Container: `sabnzbd` (LinuxServer.io)
|
||||
- Web UI on TCP 8080
|
||||
- Mounts `/config` and `/downloads`
|
||||
|
||||
## Usage
|
||||
|
||||
```hcl
|
||||
module "sabnzbd" {
|
||||
source = "./modules/20-services-apps/sabnzbd"
|
||||
volume_path = "/srv/appdata/sabnzbd" # host path for app config
|
||||
downloads_path = "/srv/data/usenet" # host path for usenet downloads
|
||||
networks = [module.media_docker_network.name, module.homelab_docker_network.name]
|
||||
}
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
| Variable | Description | Type | Default |
|
||||
| ---------------- | ------------------------------------------- | -------------- | ------- |
|
||||
| `volume_path` | Base directory for Sabnzbd config | `string` | - |
|
||||
| `downloads_path` | Directory for downloads mounted at /downloads | `string` | - |
|
||||
| `networks` | List of networks to attach | `list(string)` | `[]` |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
| -------------------- | ------------------------------ |
|
||||
| `service_definition` | Service definition for integration with networking modules |
|
||||
|
||||
## Service Definition
|
||||
|
||||
This module outputs a service definition that is used by the networking modules. Sabnzbd is not published externally.
|
||||
|
||||
```hcl
|
||||
{
|
||||
name = "sabnzbd"
|
||||
primary_port = 8080
|
||||
endpoint = "http://sabnzbd:8080"
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `TZ`, `PUID`, and `PGID` are injected automatically via system globals in the generic docker-service module.
|
||||
|
||||
## Data Persistence
|
||||
|
||||
- `/config` -> `${volume_path}`
|
||||
- `/downloads` -> `${downloads_path}`
|
||||
- Ensure host paths exist and permissions align with the container user.
|
||||
|
||||
## Networking
|
||||
|
||||
- Attaches to `networks` (typically media and homelab). Not published externally; accessible internally.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- No explicit inter-container dependencies. Healthcheck ensures readiness.
|
||||
|
||||
## Example Integration in Main Configuration
|
||||
|
||||
```hcl
|
||||
# In services/main.tf
|
||||
module "sabnzbd" {
|
||||
source = "${local.module_dir}/20-services-apps/sabnzbd"
|
||||
volume_path = "${local.volume_host}/sabnzbd"
|
||||
downloads_path = "${local.data_host}/usenet"
|
||||
networks = [module.media_docker_network.name, module.homelab_docker_network.name]
|
||||
}
|
||||
```
|
||||
|
||||
The service definition is exported by the `services` module as `module.services.service_definitions` and consumed by networking modules in the root `main.tf`.
|
||||
@@ -1,69 +0,0 @@
|
||||
variable "volume_path" {
|
||||
description = "Base directory for Sabnzbd config"
|
||||
type = string
|
||||
}
|
||||
variable "downloads_path" {
|
||||
description = "Directory for downloads mounted at /downloads"
|
||||
type = string
|
||||
}
|
||||
variable "networks" {
|
||||
description = "List of networks to attach"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "sabnzbd"
|
||||
image = "lscr.io/linuxserver/sabnzbd"
|
||||
tag = "latest"
|
||||
monitoring = true
|
||||
internal_port = 8080
|
||||
|
||||
env_vars = {
|
||||
# Add typical env like PUID/PGID/TZ if desired via the generic module interface
|
||||
}
|
||||
|
||||
volumes = [
|
||||
{
|
||||
host_path = var.volume_path,
|
||||
container_path = "/config",
|
||||
read_only = false
|
||||
},
|
||||
{
|
||||
host_path = var.downloads_path,
|
||||
container_path = "/data/usenet/downloads",
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
healthcheck = {
|
||||
test = ["CMD", "curl", "--fail", "http://127.0.0.1:8080"]
|
||||
interval = "60s"
|
||||
timeout = "5s"
|
||||
retries = 10
|
||||
}
|
||||
}
|
||||
|
||||
module "sabnzbd" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.image
|
||||
tag = local.tag
|
||||
env_vars = local.env_vars
|
||||
volumes = local.volumes
|
||||
networks = var.networks
|
||||
monitoring = local.monitoring
|
||||
healthcheck = local.healthcheck
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "Service definition for Sabnzbd (not published)"
|
||||
value = {
|
||||
name = local.container_name
|
||||
primary_port = local.internal_port
|
||||
endpoint = "http://${local.container_name}:${local.internal_port}"
|
||||
subdomains = ["sabnzbd"]
|
||||
publish_via = "tunnel"
|
||||
proxied = true
|
||||
}
|
||||
}
|
||||
109
services/main.tf
Executable file → Normal file
109
services/main.tf
Executable file → Normal file
@@ -1,8 +1,6 @@
|
||||
locals {
|
||||
module_dir = "../modules"
|
||||
root_volume = module.system_globals.volume_host
|
||||
volume_host = "${module.system_globals.volume_host}/appdata"
|
||||
data_host = "${module.system_globals.volume_host}/data"
|
||||
volume_host = module.system_globals.volume_host
|
||||
}
|
||||
|
||||
module "system_globals" {
|
||||
@@ -20,16 +18,6 @@ module "homelab_docker_network" {
|
||||
subnet = "10.100.0.0/16"
|
||||
}
|
||||
|
||||
// Docker network used for media services
|
||||
module "media_docker_network" {
|
||||
source = "${local.module_dir}/01-networking/docker-network"
|
||||
|
||||
name = "media-network"
|
||||
driver = "bridge"
|
||||
attachable = true
|
||||
subnet = "10.110.0.0/16"
|
||||
}
|
||||
|
||||
module "actualbudget" {
|
||||
source = "${local.module_dir}/20-services-apps/actualbudget"
|
||||
volume_path = "${local.volume_host}/actual"
|
||||
@@ -42,73 +30,15 @@ module "affine" {
|
||||
networks = [module.homelab_docker_network.name]
|
||||
}
|
||||
|
||||
module "arr" {
|
||||
source = "${local.module_dir}/20-services-apps/arr"
|
||||
volume_path = "${local.volume_host}/arr"
|
||||
data_path = local.data_host
|
||||
downloads_path = "${local.data_host}/torrents"
|
||||
networks = [module.media_docker_network.name]
|
||||
proxy_networks = [module.homelab_docker_network.name]
|
||||
qbittorrent_host = "gluetun"
|
||||
}
|
||||
|
||||
module "calibre" {
|
||||
source = "${local.module_dir}/20-services-apps/calibre"
|
||||
volume_path = "${local.volume_host}/calibre"
|
||||
networks = [module.homelab_docker_network.name]
|
||||
}
|
||||
|
||||
module "copyparty" {
|
||||
source = "${local.module_dir}/20-services-apps/copyparty"
|
||||
fileshare_path = local.root_volume
|
||||
config_path = "${local.volume_host}/copyparty"
|
||||
networks = [module.homelab_docker_network.name]
|
||||
}
|
||||
|
||||
module "crawl4ai" {
|
||||
source = "${local.module_dir}/20-services-apps/crawl4ai"
|
||||
volume_path = "${local.volume_host}/crawl4ai"
|
||||
networks = [module.homelab_docker_network.name]
|
||||
}
|
||||
|
||||
module "emulatorjs" {
|
||||
source = "${local.module_dir}/20-services-apps/emulatorjs"
|
||||
volume_path = "${local.volume_host}/emulatorjs"
|
||||
image_tag = "1.9.2"
|
||||
}
|
||||
|
||||
module "glance" {
|
||||
source = "${local.module_dir}/20-services-apps/glance"
|
||||
volume_path = "${local.volume_host}/glance"
|
||||
networks = [module.homelab_docker_network.name]
|
||||
}
|
||||
|
||||
module "gluetun" {
|
||||
source = "${local.module_dir}/20-services-apps/gluetun"
|
||||
volume_path = "${local.volume_host}/gluetun"
|
||||
networks = [module.media_docker_network.name]
|
||||
ports = [
|
||||
# Expose qBittorrent UI to the host
|
||||
{
|
||||
internal = 8080
|
||||
external = 8080
|
||||
protocol = "tcp"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
module "immich" {
|
||||
source = "${local.module_dir}/20-services-apps/immich"
|
||||
appdata_path = "${local.volume_host}/immich"
|
||||
library_path = "${local.data_host}/media/photos"
|
||||
networks = [module.homelab_docker_network.name]
|
||||
}
|
||||
|
||||
module "jellyfin" {
|
||||
source = "${local.module_dir}/20-services-apps/jellyfin"
|
||||
volume_path = "${local.volume_host}/jellyfin"
|
||||
data_path = "${local.data_host}"
|
||||
networks = [module.media_docker_network.name, module.homelab_docker_network.name]
|
||||
}
|
||||
|
||||
module "linkwarden" {
|
||||
@@ -117,30 +47,12 @@ module "linkwarden" {
|
||||
networks = [module.homelab_docker_network.name]
|
||||
}
|
||||
|
||||
module "n8n" {
|
||||
source = "${local.module_dir}/20-services-apps/n8n"
|
||||
volume_path = "${local.volume_host}/n8n"
|
||||
networks = [module.homelab_docker_network.name]
|
||||
}
|
||||
|
||||
module "nocodb" {
|
||||
source = "${local.module_dir}/20-services-apps/nocodb"
|
||||
volume_path = "${local.volume_host}/nocodb"
|
||||
networks = [module.homelab_docker_network.name]
|
||||
}
|
||||
|
||||
module "ntfy" {
|
||||
source = "${local.module_dir}/20-services-apps/ntfy"
|
||||
volume_path = "${local.volume_host}/ntfy"
|
||||
networks = [module.homelab_docker_network.name]
|
||||
}
|
||||
|
||||
module "portainer" {
|
||||
source = "${local.module_dir}/20-services-apps/portainer"
|
||||
volume_path = "${local.volume_host}/portainer"
|
||||
networks = [module.homelab_docker_network.name]
|
||||
}
|
||||
|
||||
module "pterodactyl_panel" {
|
||||
source = "${local.module_dir}/20-services-apps/pterodactyl/panel"
|
||||
volume_path = "${local.volume_host}/pterodactyl/panel"
|
||||
@@ -153,21 +65,10 @@ module "pterodactyl_wings" {
|
||||
networks = [module.homelab_docker_network.name]
|
||||
}
|
||||
|
||||
module "qbittorrent" {
|
||||
source = "${local.module_dir}/20-services-apps/qbittorrent"
|
||||
volume_path = "${local.volume_host}/qbittorrent"
|
||||
downloads_path = "${local.data_host}/torrents"
|
||||
networks = [module.media_docker_network.name]
|
||||
connect_via_gluetun = true
|
||||
gluetun_container_name = "gluetun"
|
||||
depends_on = [module.gluetun]
|
||||
}
|
||||
|
||||
module "sabnzbd" {
|
||||
source = "${local.module_dir}/20-services-apps/sabnzbd"
|
||||
volume_path = "${local.volume_host}/sabnzbd"
|
||||
downloads_path = "${local.data_host}/usenet/downloads"
|
||||
networks = [module.media_docker_network.name, module.homelab_docker_network.name]
|
||||
module "n8n" {
|
||||
source = "${local.module_dir}/20-services-apps/n8n"
|
||||
volume_path = "${local.volume_host}/n8n"
|
||||
networks = [module.homelab_docker_network.name]
|
||||
}
|
||||
|
||||
module "searxng" {
|
||||
|
||||
13
services/outputs.tf
Executable file → Normal file
13
services/outputs.tf
Executable file → Normal file
@@ -6,24 +6,13 @@ output "service_definitions" {
|
||||
value = [
|
||||
module.actualbudget.service_definition,
|
||||
module.affine.service_definition,
|
||||
module.arr.service_definition,
|
||||
module.calibre.service_definition,
|
||||
module.copyparty.service_definition,
|
||||
module.crawl4ai.service_definition,
|
||||
module.emulatorjs.service_definition,
|
||||
module.glance.service_definition,
|
||||
module.immich.service_definition,
|
||||
module.jellyfin.service_definition,
|
||||
module.linkwarden.service_definition,
|
||||
module.n8n.service_definition,
|
||||
module.n8n.n8n_mcp_service_definition,
|
||||
module.nocodb.service_definition,
|
||||
module.ntfy.service_definition,
|
||||
module.portainer.service_definition,
|
||||
module.pterodactyl_wings.service_definition,
|
||||
module.pterodactyl_panel.service_definition,
|
||||
module.qbittorrent.service_definition,
|
||||
module.sabnzbd.service_definition,
|
||||
module.n8n.service_definition,
|
||||
module.searxng.service_definition
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user