From 0456a9dd2d373cb535effb6ef056a8ea091f9354 Mon Sep 17 00:00:00 2001
From: Matthew Baggett <matthew@baggett.me>
Date: Mon, 3 Mar 2025 19:04:44 +0100
Subject: [PATCH] Seafile stub

---
 products/seafile/inputs.tf    | 63 ++++++++++++++++++++++++
 products/seafile/memcached.tf |  7 +++
 products/seafile/mysql.tf     | 11 +++++
 products/seafile/network.tf   |  5 ++
 products/seafile/seafile.tf   | 90 +++++++++++++++++++++++++++++++++++
 products/seafile/terraform.tf | 13 +++++
 6 files changed, 189 insertions(+)
 create mode 100644 products/seafile/inputs.tf
 create mode 100644 products/seafile/memcached.tf
 create mode 100644 products/seafile/mysql.tf
 create mode 100644 products/seafile/network.tf
 create mode 100644 products/seafile/seafile.tf
 create mode 100644 products/seafile/terraform.tf

diff --git a/products/seafile/inputs.tf b/products/seafile/inputs.tf
new file mode 100644
index 0000000..4b5b06a
--- /dev/null
+++ b/products/seafile/inputs.tf
@@ -0,0 +1,63 @@
+variable "enable" {
+  type        = bool
+  description = "Whether to enable the service."
+  default     = true
+}
+variable "seafile_version" {
+  type        = string
+  default     = "11.0.13"
+  description = "The version of the docker image to use for the Seafile service."
+}
+# Pass-thru variables
+variable "stack_name" {
+  type    = string
+  default = "seafile"
+}
+variable "service_name" {
+  default     = "seafile"
+  type        = string
+  description = "The name of the service to create."
+}
+variable "networks" {
+  type = list(object({
+    name = string
+    id   = string
+  }))
+  default     = []
+  description = "A list of network names to attach the service to."
+}
+variable "ports" {
+  type = list(object({
+    host         = optional(number, null)
+    container    = number
+    protocol     = optional(string, "tcp")
+    publish_mode = optional(string, "ingress")
+  }))
+  default     = []
+  description = "A map of port mappings to expose on the host. The key is the host port, and the value is the container port."
+}
+variable "mysql_ports" {
+  type = list(object({
+    host         = optional(number, null)
+    container    = number
+    protocol     = optional(string, "tcp")
+    publish_mode = optional(string, "ingress")
+  }))
+  default     = []
+  description = "A map of port mappings to expose on the host. The key is the host port, and the value is the container port."
+}
+variable "mounts" {
+  type        = map(string)
+  default     = {}
+  description = "A map of host paths to container paths to mount. The key is the host path, and the value is the container path."
+}
+variable "placement_constraints" {
+  default     = []
+  type        = list(string)
+  description = "Docker Swarm placement constraints"
+}
+variable "data_persist_path" {
+  default     = null
+  description = "Path on host machine to persist data. Leaving this blank will provision an ephemeral volume."
+  type        = string
+}
diff --git a/products/seafile/memcached.tf b/products/seafile/memcached.tf
new file mode 100644
index 0000000..64ad5e1
--- /dev/null
+++ b/products/seafile/memcached.tf
@@ -0,0 +1,7 @@
+module "memcached" {
+  source                = "../memcached"
+  enable                = var.enable
+  stack_name            = var.stack_name
+  networks              = [module.network]
+  placement_constraints = var.placement_constraints
+}
\ No newline at end of file
diff --git a/products/seafile/mysql.tf b/products/seafile/mysql.tf
new file mode 100644
index 0000000..dc41df5
--- /dev/null
+++ b/products/seafile/mysql.tf
@@ -0,0 +1,11 @@
+module "mysql" {
+  source                = "../mysql"
+  enable                = var.enable
+  stack_name            = var.stack_name
+  database              = "seafile"
+  username              = "seafile"
+  networks              = [module.network]
+  data_persist_path     = "${var.data_persist_path}/mysql"
+  placement_constraints = var.placement_constraints
+  ports                 = var.mysql_ports
+}
\ No newline at end of file
diff --git a/products/seafile/network.tf b/products/seafile/network.tf
new file mode 100644
index 0000000..9d7b29a
--- /dev/null
+++ b/products/seafile/network.tf
@@ -0,0 +1,5 @@
+module "network" {
+  source       = "../../docker/network"
+  stack_name   = var.stack_name
+  network_name = "nextcloud"
+}
\ No newline at end of file
diff --git a/products/seafile/seafile.tf b/products/seafile/seafile.tf
new file mode 100644
index 0000000..51569ea
--- /dev/null
+++ b/products/seafile/seafile.tf
@@ -0,0 +1,90 @@
+variable "domain" {
+  type        = string
+  description = "The domain to use for the traefik configuration."
+}
+module "seafile" {
+  depends_on            = [module.memcached, module.mysql, module.network]
+  source                = "../../docker/service"
+  enable                = var.enable
+  stack_name            = var.stack_name
+  image                 = "h44z/seafile-ce:${var.seafile_version}"
+  placement_constraints = var.placement_constraints
+  service_name          = var.service_name
+  networks              = concat([module.network.network], var.networks, )
+  mounts = {
+    "${var.data_persist_path}/seafile" = "/seafile"
+    "${var.data_persist_path}/logs"    = "/opt/seafile/logs"
+  }
+  labels = {
+    "traefik.enable"         = "true"
+    "traefik.docker.network" = "proxy-net"
+    # HTTP Router Seafile/Seahub
+    "traefik.http.routers.seafile.rule"                      = "(Host(`seafile.${var.domain}`))"
+    "traefik.http.routers.seafile.entrypoints"               = "websecure"
+    "traefik.http.routers.seafile.tls"                       = "true"
+    "traefik.http.routers.seafile.tls.certresolver"          = "letsencryptresolver"
+    "traefik.http.routers.seafile.service"                   = "seafile"
+    "traefik.http.routers.seafile.middlewares"               = "sec-headers"
+    "traefik.http.services.seafile.loadbalancer.server.port" = "8000"
+    # HTTP Router Seafdav
+    "traefik.http.routers.seafile-dav.rule"                      = "Host(`seafile.${var.domain}`) && PathPrefix(`/seafdav`)"
+    "traefik.http.routers.seafile-dav.entrypoints"               = "websecure"
+    "traefik.http.routers.seafile-dav.tls"                       = "true"
+    "traefik.http.routers.seafile-dav.tls.certresolver"          = "letsencryptresolver"
+    "traefik.http.routers.seafile-dav.service"                   = "seafile-dav"
+    "traefik.http.services.seafile-dav.loadbalancer.server.port" = "8080"
+    # HTTP Router Seafhttp
+    "traefik.http.routers.seafile-http.rule"                      = "Host(`seafile.${var.domain}`) && PathPrefix(`/seafhttp`)"
+    "traefik.http.routers.seafile-http.entrypoints"               = "websecure"
+    "traefik.http.routers.seafile-http.tls"                       = "true"
+    "traefik.http.routers.seafile-http.tls.certresolver"          = "letsencryptresolver"
+    "traefik.http.routers.seafile-http.middlewares"               = "seafile-strip"
+    "traefik.http.routers.seafile-http.service"                   = "seafile-http"
+    "traefik.http.services.seafile-http.loadbalancer.server.port" = "8082"
+    # Middlewares 
+    "traefik.http.middlewares.seafile-strip.stripprefix.prefixes"       = "/seafhttp"
+    "traefik.http.middlewares.sec-headers.headers.sslredirect"          = "true"
+    "traefik.http.middlewares.sec-headers.headers.browserXssFilter"     = "true"
+    "traefik.http.middlewares.sec-headers.headers.contentTypeNosniff"   = "true"
+    "traefik.http.middlewares.sec-headers.headers.forceSTSHeader"       = "true"
+    "traefik.http.middlewares.sec-headers.headers.stsIncludeSubdomains" = "true"
+    "traefik.http.middlewares.sec-headers.headers.stsPreload"           = "true"
+    "traefik.http.middlewares.sec-headers.headers.referrerPolicy"       = "same-origin"
+  }
+  environment_variables = {
+    # Base settings
+    TIME_ZONE = "Europe/Amsterdam"
+
+    # Database settings, remove this section to use a sqlite database.
+    # You can either specify a root password (MYSQL_ROOT_PASSWORD), or use your exsting database tables.
+    # Also specifying MYSQL_USER_HOST only makes sense if MYSQL_ROOT_PASSWORD is given, otherwise no new MySQL user will be created.
+    # To use an external database, simply remove the MySQL service from the docker-compose.yml.
+    MYSQL_SERVER        = module.mysql.service_name
+    MYSQL_USER          = module.mysql.username
+    MYSQL_USER_PASSWORD = module.mysql.password
+    MYSQL_PORT          = 3306
+
+    # General Seafile Settings
+    SEAFILE_VERSION  = var.seafile_version
+    SEAFILE_NAME     = "Seafile"
+    SEAFILE_ADDRESS  = var.domain
+    SEAFILE_ADMIN    = "admin@${var.domain}"
+    SEAFILE_ADMIN_PW = "changeme"
+
+    # OnlyOffice Settings
+    ONLYOFFICE_JWT_SECRET = "Supers3cr3t" // @todo generate a key instead
+
+    # Optional Seafile Settings
+    LDAP_IGNORE_CERT_CHECK = true
+
+    # Traefik (Reverse Proxy) Settings
+    DOMAINNAME = var.domain
+
+    # All other settings can be edited in the conf dir (/seafile/conf) once the container started up!
+
+    # runmode, default = run
+    #MODE=maintenance
+  }
+  converge_enable = false // @todo: Fix healthcheck and change this.
+}
+
diff --git a/products/seafile/terraform.tf b/products/seafile/terraform.tf
new file mode 100644
index 0000000..d2a0d07
--- /dev/null
+++ b/products/seafile/terraform.tf
@@ -0,0 +1,13 @@
+terraform {
+  required_version = "~> 1.6"
+  required_providers {
+    docker = {
+      source  = "kreuzwerker/docker"
+      version = "~> 3.0"
+    }
+    random = {
+      source  = "hashicorp/random"
+      version = "~> 3.0"
+    }
+  }
+}