Add Nextcloud deployment and configuration

- Introduced Nextcloud module with Kubernetes resources including deployment, service, ingress, and cron job.
- Configured Apache settings for Nextcloud with SSL support and compression.
- Created secrets for mail and admin credentials.
- Added necessary environment variables for Nextcloud configuration.
- Removed unused Scaleway access and secret keys from secrets and variables.
- Updated CoreDNS module to support custom host overrides.
- Implemented backup bucket policies and lifecycle rules for Scaleway object storage.
- Enhanced IAM policies and applications for backup and email services.
- Added outputs for backup bucket and API keys.
This commit is contained in:
2025-06-09 23:49:43 +02:00
parent cac269f160
commit 826959c903
41 changed files with 1186 additions and 219 deletions
Executable
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
POD_NAME=$(kubectl get pods -n nextcloud -l app=nextcloud -o jsonpath="{.items[0].metadata.name}")
kubectl -n nextcloud exec -it $POD_NAME -- /bin/su -s /bin/bash www-data -c "/var/www/html/occ $*"
+14
View File
@@ -15,6 +15,7 @@ module "dns" {
source = "../../modules/apps/coredns"
forward_dns_servers = local.dns_nameservers
override_hosts = local.override_hosts
zones = local.zones
}
@@ -23,3 +24,16 @@ module "reverse_proxy" {
services = local.reverse_proxy_services
}
module "nextcloud" {
source = "../../modules/apps/nextcloud"
fqdn = local.nextcloud_fqdn
persistent_folder = local.persistent_folder
app_config = local.nextcloud_config
db_credentials_are_secrets = true
tls_enabled = true
issuer = local.internal_issuer
}
+43
View File
@@ -16,6 +16,13 @@ locals {
"185.253.5.0"
]
override_hosts = [
{
name = "cloud.alexpires.me"
ip = "10.19.4.136"
},
]
zones = [
{
fqdn = "${local.lab_domain}"
@@ -88,4 +95,40 @@ locals {
}
}
scaleway_smtp_host = "smtp.tem.scw.cloud"
nextcloud_fqdn = "cloud.alexpires.me"
nextcloud_db_passwd = var.nextcloud_db_passwd
nextcloud_db_root_password = var.nextcloud_db_root_password
nextcloud_db_path = "${local.persistent_folder}/mysql/nextcloud"
backup_folder = "/mnt/usb/backups/nextcloud"
nextcloud_config = {
mail_from = "cloud"
mail_domain = "alexpires.me"
smtp_user = var.scaleway_project_id
smtp_password = local.mail_app_api_secret_key
smtp_host = local.scaleway_smtp_host
admin_password = var.nextcloud_admin_password
db_host = module.nextcloud_db.host
db_name = module.nextcloud_db.db_name
db_user = module.nextcloud_db.app_crendentials_name
db_password = module.nextcloud_db.app_crendentials_name
}
backup_retention_days = 3
backup_cron_schedule = "0 2 * * *" # Daily at 2 AM
backup_bucket_name = data.terraform_remote_state.scaleway.outputs.backup_bucket_name
backup_app_api_secret_key = data.terraform_remote_state.scaleway.outputs.backup_app_api_secret_key
backup_app_api_access_key = data.terraform_remote_state.scaleway.outputs.backup_app_api_access_key
mail_app_api_secret_key = data.terraform_remote_state.scaleway.outputs.mail_app_api_secret_key
}
data "terraform_remote_state" "scaleway" {
backend = "s3"
config = {
bucket = "a13labs.infra.eu"
key = "scaleway.core.infra.tfstate"
region = "eu-west-1"
}
}
+17
View File
@@ -0,0 +1,17 @@
module "nextcloud_db" {
source = "../../modules/db/mariadb"
namespace = module.nextcloud.namespace
app_name = "nextcloud"
app_password = local.nextcloud_db_passwd
root_password = local.nextcloud_db_root_password
retention_days = local.backup_retention_days
cron_schedule = local.backup_cron_schedule
database_folder = local.nextcloud_db_path
backup_folder = local.backup_folder
tls_enabled = true
issuer = local.internal_issuer
}
+16
View File
@@ -1 +1,17 @@
variable "nextcloud_db_passwd" {
description = "Password for the Nextcloud database"
type = string
sensitive = true
}
variable "nextcloud_db_root_password" {
description = "Root password for the Nextcloud database"
type = string
sensitive = true
}
variable "nextcloud_admin_password" {
description = "Password for the Nextcloud admin user"
type = string
sensitive = true
}
+4
View File
@@ -1,2 +1,6 @@
AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY
AWS_SECRET_ACCESS_KEY=$AWS_SECRET_KEY
TF_VAR_nextcloud_db_passwd=$MYSQL_NEXTCLOUD_PASSWORD
TF_VAR_nextcloud_db_root_password=$MYSQL_ROOT_PASSWORD
TF_VAR_nextcloud_admin_password=$NEXTCLOUD_ADMIN_PASSWORD
TF_VAR_scaleway_project_id=$SCALEWAY_PROJECT_ID
+4 -1
View File
@@ -1 +1,4 @@
variable "scaleway_project_id" {
description = "Scaleway Project Id"
type = string
}
+6 -6
View File
@@ -46,7 +46,7 @@ module "mail" {
relay_smtp_host = local.scaleway_smtp_host
relay_smtp_port = 465
relay_smtp_username = var.scaleway_project_id
relay_smtp_password = scaleway_iam_api_key.mail_app_api.secret_key
relay_smtp_password = local.mail_app_api_secret_key
fetch_emails_schedule = local.fetch_emails_schedule
@@ -84,7 +84,7 @@ module "gitea" {
smtp_host = local.scaleway_smtp_host
smtp_port = local.scaleway_smtp_port
smtp_user = var.scaleway_project_id
smtp_passwd = scaleway_iam_api_key.mail_app_api.secret_key
smtp_passwd = local.mail_app_api_secret_key
secret_key = var.gitea_secret_key
internal_token = var.gitea_internal_token
disable_ssh = false
@@ -108,11 +108,11 @@ module "sftpgo" {
smtp_host = local.scaleway_smtp_host
smtp_port = local.scaleway_smtp_port
smtp_username = var.scaleway_project_id
smtp_password = scaleway_iam_api_key.mail_app_api.secret_key
smtp_password = local.mail_app_api_secret_key
from_email = "sftpgo@${local.primary_domain}"
access_key = scaleway_iam_api_key.sftpgo_app_api.access_key
secret_key = scaleway_iam_api_key.sftpgo_app_api.secret_key
access_key = local.sftpgo_app_api_access_key
secret_key = local.sftpgo_app_api_secret_key
}
module "trivy" {
@@ -124,7 +124,7 @@ module "trivy" {
smtp_host = local.scaleway_smtp_host
smtp_port = local.scaleway_smtp_port
smtp_username = var.scaleway_project_id
smtp_password = scaleway_iam_api_key.mail_app_api.secret_key
smtp_password = local.mail_app_api_secret_key
from_email = "trivy@${local.primary_domain}"
to_email = "c.alexandre.pires@${local.primary_domain}"
}
+9 -9
View File
@@ -7,9 +7,9 @@ module "mail_backup" {
scaleway_organization_id = var.scaleway_organization_id
scaleway_project_id = var.scaleway_organization_id
access_key = scaleway_iam_api_key.backup_app_api.access_key
secret_key = scaleway_iam_api_key.backup_app_api.secret_key
bucket_name = scaleway_object_bucket.backup.name
access_key = local.backup_app_api_access_key
secret_key = local.backup_app_api_secret_key
bucket_name = local.backup_bucket_name
retention_days = local.local_backup_retention_days
cron_schedule = local.mail_files_backup_cron_schedule
@@ -26,9 +26,9 @@ module "gitea_backup" {
scaleway_organization_id = var.scaleway_organization_id
scaleway_project_id = var.scaleway_organization_id
access_key = scaleway_iam_api_key.backup_app_api.access_key
secret_key = scaleway_iam_api_key.backup_app_api.secret_key
bucket_name = scaleway_object_bucket.backup.name
access_key = local.backup_app_api_access_key
secret_key = local.backup_app_api_secret_key
bucket_name = local.backup_bucket_name
retention_days = local.local_backup_retention_days
cron_schedule = local.gitea_files_backup_cron_schedule
@@ -46,9 +46,9 @@ module "sftpgo_backup" {
scaleway_organization_id = var.scaleway_organization_id
scaleway_project_id = var.scaleway_organization_id
access_key = scaleway_iam_api_key.backup_app_api.access_key
secret_key = scaleway_iam_api_key.backup_app_api.secret_key
bucket_name = scaleway_object_bucket.backup.name
access_key = local.backup_app_api_access_key
secret_key = local.backup_app_api_secret_key
bucket_name = local.backup_bucket_name
retention_days = local.local_backup_retention_days
cron_schedule = local.sftpgo_files_backup_cron_schedule
-130
View File
@@ -1,130 +0,0 @@
resource "random_string" "bucket_suffix" {
length = 8
lower = true
upper = false
special = false
}
resource "scaleway_object_bucket" "backup" {
name = "a13labs-backup-${random_string.bucket_suffix.result}"
project_id = var.scaleway_project_id
region = "fr-par"
lifecycle_rule {
id = "gitea-clean"
prefix = "gitea-backup/"
enabled = true
expiration {
days = local.cloud_backup_retention_days
}
}
lifecycle_rule {
id = "wordpress-clean"
prefix = "wordpress-backup/"
enabled = true
expiration {
days = local.cloud_backup_retention_days
}
}
lifecycle_rule {
id = "mail-clean"
prefix = "mail-backup/"
enabled = true
expiration {
days = local.cloud_backup_retention_days
}
}
}
resource "scaleway_object_bucket_policy" "backup" {
bucket = scaleway_object_bucket.backup.name
policy = jsonencode({
Version = "2023-04-17",
Id = "AllowAccess",
Statement = [
{
Effect = "Allow"
Action = [
"s3:*",
]
Principal = {
SCW = "user_id:${var.scaleway_user_id}"
}
Resource = [
scaleway_object_bucket.backup.name,
"${scaleway_object_bucket.backup.name}/*",
]
},
{
Effect = "Allow"
Action = [
"s3:*",
]
Principal = {
SCW = "application_id:${scaleway_iam_application.backup_app.id}"
}
Resource = [
scaleway_object_bucket.backup.name,
"${scaleway_object_bucket.backup.name}/*",
],
Condition = {
IpAddress = {
"aws:SourceIp" = local.instance_ips
}
},
},
]
})
}
resource "scaleway_object_bucket" "sftpgo" {
name = "a13labs-sftpgo-${random_string.bucket_suffix.result}"
project_id = var.scaleway_project_id
region = "fr-par"
}
resource "scaleway_object_bucket_policy" "sftpgo" {
bucket = scaleway_object_bucket.sftpgo.name
policy = jsonencode({
Version = "2023-04-17",
Id = "AllowAccess",
Statement = [
{
Effect = "Allow"
Action = [
"s3:*",
]
Principal = {
SCW = "user_id:${var.scaleway_user_id}"
}
Resource = [
scaleway_object_bucket.sftpgo.name,
"${scaleway_object_bucket.sftpgo.name}/*",
]
},
{
Effect = "Allow"
Action = [
"s3:*",
]
Principal = {
SCW = "application_id:${scaleway_iam_application.sftpgo_app.id}"
}
Resource = [
scaleway_object_bucket.sftpgo.name,
"${scaleway_object_bucket.sftpgo.name}/*",
],
Condition = {
IpAddress = {
"aws:SourceIp" = local.instance_ips
}
},
},
]
})
}
+29 -26
View File
@@ -44,7 +44,6 @@ locals {
# backup
local_backup_retention_days = 2
cloud_backup_retention_days = 3
mail_backup_path = "${local.persistent_folder}/backup.mail"
gitea_backup_path = "${local.persistent_folder}/backup.gitea"
sftpgo_backup_path = "${local.persistent_folder}/backup.sftpgo"
@@ -108,43 +107,47 @@ locals {
"Connection" = "upgrade"
}
},
# {
# private = true
# path = "/tools/gitea"
# upstream = "http://10.19.4.136:8000"
# rewrite = "^/tools/gitea/(.*)$ /$1 break"
# },
{
path = "/"
},
]
},
"nextcloud" = {
http_port = 8081
https_port = 8444
custom_ingress_annotations = {
"nginx.ingress.kubernetes.io/proxy-body-size" = "4G"
"nginx.ingress.kubernetes.io/proxy-request-buffering" = "on"
"nginx.ingress.kubernetes.io/proxy-buffer-size" = "32k"
"nginx.ingress.kubernetes.io/proxy-buffers-number" = "16"
}
tls = true
custom_snippet = file("${path.module}/resources/nextcloud.conf")
resolver = "10.19.4.136"
fqdn = ["cloud.alexpires.me"]
upstream = "https://cloud.alexpires.me"
locations = [
{
path = "/"
},
]
},
}
# VPS configuration
instance_ips = [
"${local.vps_1.ip}/32",
]
vps_1 = {
id = data.terraform_remote_state.contabo.outputs.instances[0].id
name = data.terraform_remote_state.contabo.outputs.instances[0].name
ip = data.terraform_remote_state.contabo.outputs.instances[0].ip_config[0].v4[0].ip
}
backup_bucket_name = data.terraform_remote_state.scaleway.outputs.backup_bucket_name
backup_app_api_secret_key = data.terraform_remote_state.scaleway.outputs.backup_app_api_secret_key
backup_app_api_access_key = data.terraform_remote_state.scaleway.outputs.backup_app_api_access_key
mail_app_api_secret_key = data.terraform_remote_state.scaleway.outputs.mail_app_api_secret_key
sftpgo_app_api_access_key = data.terraform_remote_state.scaleway.outputs.sftpgo_app_api_access_key
sftpgo_app_api_secret_key = data.terraform_remote_state.scaleway.outputs.sftpgo_app_api_secret_key
}
data "scaleway_account_project" "default" {
project_id = var.scaleway_project_id
organization_id = var.scaleway_organization_id
}
data "terraform_remote_state" "contabo" {
data "terraform_remote_state" "scaleway" {
backend = "s3"
config = {
bucket = "a13labs.infra.eu"
key = "contabo.core.infra.tfstate"
key = "scaleway.core.infra.tfstate"
region = "eu-west-1"
}
}
-11
View File
@@ -21,10 +21,6 @@ terraform {
source = "hashicorp/kubernetes"
version = "2.36.0"
}
scaleway = {
source = "scaleway/scaleway"
version = "~>2.13"
}
}
}
@@ -34,11 +30,4 @@ provider "kubernetes" {
insecure = true
}
provider "scaleway" {
zone = "fr-par-2"
region = "fr-par"
access_key = var.scaleway_access_key
secret_key = var.scaleway_secret_key
organization_id = var.scaleway_organization_id
}
@@ -0,0 +1,32 @@
server_tokens off;
proxy_hide_header X-Powered-By;
location = /.well-known/webfinger {
return 301 $scheme://$host/index.php/.well-known/webfinger;
}
location = /.well-known/nodeinfo {
return 301 $scheme://$host/index.php/.well-known/nodeinfo;
}
location = /.well-known/carddav {
return 301 $scheme://$host/remote.php/dav;
}
location = /.well-known/caldav {
return 301 $scheme://$host/remote.php/dav;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/ {
deny all;
}
location ~ ^/(?:autotest|occ|issue|indie|db_|console) {
deny all;
}
-12
View File
@@ -11,18 +11,6 @@ variable "gitea_mysql_password" {
sensitive = true
}
variable "scaleway_access_key" {
description = "Scaleway Access key"
type = string
sensitive = true
}
variable "scaleway_secret_key" {
description = "Scaleway Secret key"
type = string
sensitive = true
}
variable "wireguard_private_key" {
description = "Wireguard public key"
type = string
-3
View File
@@ -1,9 +1,6 @@
AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY
AWS_SECRET_ACCESS_KEY=$AWS_SECRET_KEY
TF_VAR_scaleway_access_key=$SCALEWAY_ACCESS_KEY
TF_VAR_scaleway_secret_key=$SCALEWAY_SECRET_KEY
TF_VAR_scaleway_project_id=$SCALEWAY_PROJECT_ID
TF_VAR_scaleway_user_id=$SCALEWAY_USER_ID
TF_VAR_scaleway_organization_id=$SCALEWAY_ORGANIZATION_ID
TF_VAR_mysql_root_password=$MYSQL_ROOT_PASSWORD
TF_VAR_wordpress_mysql_password=$MYSQL_WORDPRESS_PASSWORD
-5
View File
@@ -1,8 +1,3 @@
variable "scaleway_user_id" {
description = "Scaleway User Id"
type = string
}
variable "scaleway_project_id" {
description = "Scaleway Project Id"
type = string
+122
View File
@@ -43,3 +43,125 @@ resource "scaleway_object_bucket_policy" "policy" {
]
})
}
resource "random_string" "bucket_suffix" {
length = 8
lower = true
upper = false
special = false
}
resource "scaleway_object_bucket" "backup" {
name = "a13labs-backup-${random_string.bucket_suffix.result}"
project_id = var.scaleway_project_id
region = "fr-par"
lifecycle_rule {
id = "gitea-clean"
prefix = "gitea-backup/"
enabled = true
expiration {
days = local.cloud_backup_retention_days
}
}
lifecycle_rule {
id = "mail-clean"
prefix = "mail-backup/"
enabled = true
expiration {
days = local.cloud_backup_retention_days
}
}
}
resource "scaleway_object_bucket_policy" "backup" {
bucket = scaleway_object_bucket.backup.name
policy = jsonencode({
Version = "2023-04-17",
Id = "AllowAccess",
Statement = [
{
Effect = "Allow"
Action = [
"s3:*",
]
Principal = {
SCW = "user_id:${var.scaleway_user_id}"
}
Resource = [
scaleway_object_bucket.backup.name,
"${scaleway_object_bucket.backup.name}/*",
]
},
{
Effect = "Allow"
Action = [
"s3:*",
]
Principal = {
SCW = "application_id:${scaleway_iam_application.backup_app.id}"
}
Resource = [
scaleway_object_bucket.backup.name,
"${scaleway_object_bucket.backup.name}/*",
],
Condition = {
IpAddress = {
"aws:SourceIp" = local.instance_ips
}
},
},
]
})
}
resource "scaleway_object_bucket" "sftpgo" {
name = "a13labs-sftpgo-${random_string.bucket_suffix.result}"
project_id = var.scaleway_project_id
region = "fr-par"
}
resource "scaleway_object_bucket_policy" "sftpgo" {
bucket = scaleway_object_bucket.sftpgo.name
policy = jsonencode({
Version = "2023-04-17",
Id = "AllowAccess",
Statement = [
{
Effect = "Allow"
Action = [
"s3:*",
]
Principal = {
SCW = "user_id:${var.scaleway_user_id}"
}
Resource = [
scaleway_object_bucket.sftpgo.name,
"${scaleway_object_bucket.sftpgo.name}/*",
]
},
{
Effect = "Allow"
Action = [
"s3:*",
]
Principal = {
SCW = "application_id:${scaleway_iam_application.sftpgo_app.id}"
}
Resource = [
scaleway_object_bucket.sftpgo.name,
"${scaleway_object_bucket.sftpgo.name}/*",
],
Condition = {
IpAddress = {
"aws:SourceIp" = local.instance_ips
}
},
},
]
})
}
+6
View File
@@ -3,6 +3,8 @@ locals {
"${local.vps_1.ip}/32",
]
cloud_backup_retention_days = 3
vps_1 = {
id = data.terraform_remote_state.contabo.outputs.instances[0].id
name = data.terraform_remote_state.contabo.outputs.instances[0].name
@@ -19,3 +21,7 @@ data "terraform_remote_state" "contabo" {
}
}
data "scaleway_account_project" "default" {
project_id = var.scaleway_project_id
organization_id = var.scaleway_organization_id
}
@@ -2,14 +2,6 @@ resource "scaleway_iam_application" "backup_app" {
name = "k8s_backup_app"
}
resource "scaleway_iam_application" "mail_app" {
name = "mail_app"
}
resource "scaleway_iam_application" "sftpgo_app" {
name = "sftpgo_app"
}
resource "scaleway_iam_policy" "object_read_write_backup" {
name = "K8s backup app policy"
description = "Gives k8s backup job access to object storage in project"
@@ -26,6 +18,10 @@ resource "scaleway_iam_policy" "object_read_write_backup" {
}
}
resource "scaleway_iam_application" "mail_app" {
name = "mail_app"
}
resource "scaleway_iam_policy" "email_full_access" {
name = "Email full access policy"
description = "Gives access to transactional email in project"
@@ -39,6 +35,9 @@ resource "scaleway_iam_policy" "email_full_access" {
}
}
resource "scaleway_iam_application" "sftpgo_app" {
name = "sftpgo_app"
}
resource "scaleway_iam_policy" "object_read_write_sftpgo" {
name = "SFtpGo app policy"
+34
View File
@@ -0,0 +1,34 @@
output "backup_bucket_name" {
description = "The name of the backup bucket."
value = scaleway_object_bucket.backup.name
}
output "backup_app_api_secret_key" {
description = "The secret key for the backup API."
value = scaleway_iam_api_key.backup_app_api.secret_key
sensitive = true
}
output "backup_app_api_access_key" {
description = "The access key for the backup API."
value = scaleway_iam_api_key.backup_app_api.access_key
sensitive = true
}
output "mail_app_api_secret_key" {
description = "The secret key for the mail app API."
value = scaleway_iam_api_key.mail_app_api.secret_key
sensitive = true
}
output "sftpgo_app_api_access_key" {
description = "The access key for the SFTPGo app API."
value = scaleway_iam_api_key.sftpgo_app_api.access_key
sensitive = true
}
output "sftpgo_app_api_secret_key" {
description = "The secret key for the SFTPGo app API."
value = scaleway_iam_api_key.sftpgo_app_api.secret_key
sensitive = true
}
+2
View File
@@ -2,6 +2,8 @@
locals {
configfile = templatefile("${path.module}/resources/Corefile.tpl", {
zones = var.zones
override_hosts = length(var.override_hosts) > 0
hosts = var.override_hosts
forward_dns_servers = join(" ", var.forward_dns_servers)
})
configfile_checksum = md5(local.configfile)
@@ -1,4 +1,12 @@
.:53 {
%{ if override_hosts ~}
hosts {
%{ for h in hosts ~}
${h.ip} ${h.name}
%{ endfor ~}
fallthrough
}
%{ endif ~}
errors
health {
lameduck 5s
@@ -10,6 +10,15 @@ variable "forward_dns_servers" {
default = ["8.8.8.8", "8.8.4.4"]
}
variable "override_hosts" {
description = "Override hosts"
type = list(object({
ip = string
name = string
}))
default = []
}
variable "zones" {
description = "Zones to be managed by coredns"
type = list(object({
@@ -0,0 +1,90 @@
locals {
persistent_folder = var.persistent_folder
database_credentials = {
MYSQL_USER = {
key = "username"
name = var.app_config.db_user
}
MYSQL_PASSWORD = {
key = "password"
name = var.app_config.db_password
}
}
nextcloud_env_vars = merge({
# Database configuration
MYSQL_DATABASE = var.app_config.db_name
MYSQL_HOST = var.app_config.db_host
# Email configuration
SMTP_HOST = var.app_config.smtp_host
SMTP_SECURE = var.app_config.smtp_secure
SMTP_PORT = var.app_config.smtp_port
MAIL_FROM_ADDRESS = var.app_config.mail_from
MAIL_DOMAIN = var.app_config.mail_domain
SMTP_AUTHTYPE = "PLAIN"
# Redis configuration
REDIS_HOST = "redis"
# General
NEXTCLOUD_TRUSTED_DOMAINS = var.fqdn
TRUSTED_PROXIES = join(" ", ["10.1.0.0/16"])
OVERWRITEHOST = var.fqdn
OVERWRITEPROTOCOL = "https"
OVERWRITECLIURL = "https://${var.fqdn}"
# PHP
PHP_MEMORY_LIMIT = "512M"
}, var.db_credentials_are_secrets ? {} : local.database_credentials)
nextcloud_secrets = merge({
SMTP_NAME = {
key = "username"
name = kubernetes_secret.nextcloud_mail_credentials.metadata[0].name
}
SMTP_PASSWORD = {
key = "password"
name = kubernetes_secret.nextcloud_mail_credentials.metadata[0].name
}
NEXTCLOUD_ADMIN_USER = {
key = "username"
name = kubernetes_secret.nextcloud_admin_credentials.metadata[0].name
}
NEXTCLOUD_ADMIN_PASSWORD = {
key = "password"
name = kubernetes_secret.nextcloud_admin_credentials.metadata[0].name
}
}, var.db_credentials_are_secrets ? local.database_credentials : {})
nextcloud_data = "${local.persistent_folder}/nextcloud"
apache_confs = {
for f in fileset("${path.module}/resources/apache2/conf-enabled/", "*.conf") :
"${f}" => file("${path.module}/resources/apache2/conf-enabled//${f}")
}
apache_mods_load = {
for f in fileset("${path.module}/resources/apache2/mods-enabled/", "*.cload") :
"${f}" => file("${path.module}/resources/apache2/mods-enabled//${f}")
}
apache_mods_conf = {
for f in fileset("${path.module}/resources/apache2/mods-enabled/", "*.conf") :
"${f}" => file("${path.module}/resources/apache2/mods-enabled//${f}")
}
apache_sites = {
for f in fileset("${path.module}/resources/apache2/sites-enabled/", "*.conf") :
"${f}" => file("${path.module}/resources/apache2/sites-enabled//${f}")
}
apache_confs_checksum = sha256(jsonencode(local.apache_confs))
apache_sites_checksum = sha256(jsonencode(local.apache_sites))
apache_mods_load_checksum = sha256(jsonencode(local.apache_mods_load))
apache_mods_conf_checksum = sha256(jsonencode(local.apache_mods_conf))
}
+54
View File
@@ -0,0 +1,54 @@
resource "kubernetes_cron_job_v1" "nextcloud_cron_apache" {
depends_on = [
kubernetes_deployment.nextcloud_apache
]
metadata {
namespace = kubernetes_namespace.nextcloud.metadata[0].name
name = "cronjob-trigger"
}
spec {
schedule = "*/15 * * * *"
concurrency_policy = "Forbid"
failed_jobs_history_limit = 2
successful_jobs_history_limit = 2
job_template {
metadata {
labels = {
app = "cronjob-trigger"
}
}
spec {
backoff_limit = 1
parallelism = 1
completions = 1
active_deadline_seconds = 300
template {
metadata {
labels = {
app = "cronjob-trigger"
}
}
spec {
container {
name = "nextcloud"
image = "nextcloud:${var.tag}"
command = ["curl"]
args = ["--fail", "-L", "http://http/cron.php"]
security_context {
allow_privilege_escalation = false
}
}
restart_policy = "Never"
termination_grace_period_seconds = 30
}
}
}
}
}
lifecycle {
replace_triggered_by = [
kubernetes_deployment.nextcloud_apache
]
}
}
@@ -0,0 +1,67 @@
resource "kubernetes_service" "nextcloud_apache" {
metadata {
name = "http"
namespace = kubernetes_namespace.nextcloud.metadata[0].name
}
spec {
port {
port = var.tls_enabled ? 443 : 80
target_port = var.tls_enabled ? 443 : 80
}
selector = {
app = "nextcloud"
}
cluster_ip = "None"
}
}
resource "kubernetes_ingress_v1" "nextcloud_ingress" {
depends_on = [
kubernetes_deployment.nextcloud_apache
]
metadata {
name = "ingress"
namespace = kubernetes_namespace.nextcloud.metadata[0].name
annotations = merge({
"kubernetes.io/ingress.class" = "public"
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
"kubernetes.io/tls-acme" = "true"
"nginx.ingress.kubernetes.io/ssl-redirect" = "true"
"nginx.ingress.kubernetes.io/proxy-body-size" = "4G"
"nginx.ingress.kubernetes.io/proxy-request-buffering" = "on"
"nginx.ingress.kubernetes.io/proxy-buffer-size" = "32k"
"nginx.ingress.kubernetes.io/proxy-buffers-number" = "16"
"nginx.ingress.kubernetes.io/server-snippet" = file("${path.module}/resources/nextcloud-server-snippet.conf")
},
var.tls_enabled ? {
"nginx.org/ssl-services" = "http"
"nginx.org/ssl-redirect" = "true"
"nginx.org/redirect-to-https" = "true"
"nginx.ingress.kubernetes.io/backend-protocol" = "HTTPS"
} : {}
)
}
spec {
tls {
hosts = [var.fqdn]
secret_name = "nextcloud-tls"
}
rule {
host = var.fqdn
http {
path {
backend {
service {
name = "http"
port {
number = var.tls_enabled ? 443 : 80
}
}
}
}
}
}
}
}
+285
View File
@@ -0,0 +1,285 @@
resource "kubernetes_namespace" "nextcloud" {
metadata {
annotations = {
name = "nextcloud"
}
labels = {
name = "nextcloud"
}
name = "nextcloud"
}
}
resource "kubernetes_manifest" "internal_cert" {
count = var.tls_enabled ? 1 : 0
manifest = {
apiVersion = "cert-manager.io/v1"
kind = "Certificate"
metadata = {
name = "internal-cert"
namespace = kubernetes_namespace.nextcloud.metadata[0].name
}
spec = {
secretName = "internal-cert"
issuerRef = {
name = var.issuer
kind = "ClusterIssuer"
}
commonName = var.fqdn
dnsNames = [var.fqdn]
}
}
}
resource "kubernetes_config_map" "apache_confs" {
metadata {
name = "nextcloud-apache-confs"
namespace = kubernetes_namespace.nextcloud.metadata[0].name
}
data = local.apache_confs
}
resource "kubernetes_config_map" "apache_mods_load" {
metadata {
name = "nextcloud-apache-mods-load"
namespace = kubernetes_namespace.nextcloud.metadata[0].name
}
data = local.apache_mods_load
}
resource "kubernetes_config_map" "apache_mods_conf" {
metadata {
name = "nextcloud-apache-mods-enabled"
namespace = kubernetes_namespace.nextcloud.metadata[0].name
}
data = local.apache_mods_conf
}
resource "kubernetes_config_map" "apache_sites" {
metadata {
name = "nextcloud-apache-sites"
namespace = kubernetes_namespace.nextcloud.metadata[0].name
}
data = local.apache_sites
}
resource "kubernetes_service_account" "nextcloud_service_account" {
metadata {
name = "nextcloud-app-sa"
namespace = kubernetes_namespace.nextcloud.metadata[0].name
}
automount_service_account_token = false
}
resource "kubernetes_deployment" "nextcloud_apache" {
depends_on = [
kubernetes_deployment.nextcloud_redis
]
metadata {
name = "nextcloud-app"
namespace = kubernetes_namespace.nextcloud.metadata[0].name
}
spec {
selector {
match_labels = {
app = "nextcloud"
}
}
strategy {
type = "Recreate"
}
template {
metadata {
labels = {
app = "nextcloud"
}
annotations = {
"checksum/apache_confs" = local.apache_confs_checksum
"checksum/apache_sites" = local.apache_sites_checksum
"checksum/apache_mods_load" = local.apache_mods_load_checksum
"checksum/apache_mods_conf" = local.apache_mods_conf_checksum
}
}
spec {
service_account_name = kubernetes_service_account.nextcloud_service_account.metadata[0].name
container {
name = "nextcloud"
image = "nextcloud:${var.tag}"
image_pull_policy = "Always"
resources {
requests = {
memory = "200Mi"
}
}
readiness_probe {
http_get {
port = "http"
path = "/status.php"
}
initial_delay_seconds = 30
period_seconds = 10
success_threshold = 1
failure_threshold = 3
}
liveness_probe {
tcp_socket {
port = "http"
}
initial_delay_seconds = 5
period_seconds = 10
success_threshold = 1
failure_threshold = 3
}
dynamic "env" {
for_each = local.nextcloud_env_vars
content {
name = env.key
value = env.value
}
}
security_context {
allow_privilege_escalation = false
}
dynamic "env" {
for_each = local.nextcloud_secrets
content {
name = env.key
value_from {
secret_key_ref {
key = env.value.key
name = env.value.name
}
}
}
}
port {
container_port = 80
name = "http"
}
dynamic "port" {
for_each = var.tls_enabled ? [1] : []
content {
container_port = 443
name = "https"
}
}
volume_mount {
mount_path = "/var/www/html"
name = "html-data"
}
dynamic "volume_mount" {
for_each = local.apache_confs
content {
mount_path = "/etc/apache2/conf-enabled/${volume_mount.key}"
name = "apache-confs"
sub_path = volume_mount.key
read_only = true
}
}
dynamic "volume_mount" {
for_each = local.apache_sites
content {
mount_path = "/etc/apache2/sites-enabled/${volume_mount.key}"
name = "apache-sites"
sub_path = volume_mount.key
read_only = true
}
}
dynamic "volume_mount" {
for_each = local.apache_mods_load
content {
mount_path = "/etc/apache2/mods-enabled/${volume_mount.key}"
name = "apache-mods-load"
sub_path = volume_mount.key
read_only = true
}
}
dynamic "volume_mount" {
for_each = local.apache_mods_conf
content {
mount_path = "/etc/apache2/mods-enabled/${volume_mount.key}"
name = "apache-mods-conf"
sub_path = volume_mount.key
read_only = true
}
}
dynamic "volume_mount" {
for_each = var.tls_enabled ? [1] : []
content {
mount_path = "/etc/ssl/certs/private"
name = "internal-cert"
read_only = true
}
}
}
volume {
name = "html-data"
host_path {
path = local.nextcloud_data
}
}
volume {
name = "apache-confs"
config_map {
name = kubernetes_config_map.apache_confs.metadata[0].name
}
}
volume {
name = "apache-sites"
config_map {
name = kubernetes_config_map.apache_sites.metadata[0].name
}
}
volume {
name = "apache-mods-load"
config_map {
name = kubernetes_config_map.apache_mods_load.metadata[0].name
}
}
volume {
name = "apache-mods-conf"
config_map {
name = kubernetes_config_map.apache_mods_conf.metadata[0].name
}
}
dynamic "volume" {
for_each = var.tls_enabled ? [1] : []
content {
name = "internal-cert"
secret {
secret_name = kubernetes_manifest.internal_cert[0].manifest["metadata"]["name"]
items {
key = "tls.crt"
path = "tls.crt"
}
items {
key = "tls.key"
path = "tls.key"
}
}
}
}
}
}
}
lifecycle {
replace_triggered_by = [
kubernetes_deployment.nextcloud_redis
]
}
}
@@ -0,0 +1,4 @@
output "namespace" {
description = "value of namespace"
value = kubernetes_namespace.nextcloud.metadata[0].name
}
@@ -0,0 +1,9 @@
terraform {
required_version = "~>1.8"
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = "2.36.0"
}
}
}
+76
View File
@@ -0,0 +1,76 @@
resource "kubernetes_service" "nextcloud_redis" {
metadata {
name = "redis"
namespace = kubernetes_namespace.nextcloud.metadata[0].name
}
spec {
port {
port = 6379
name = "redis"
}
selector = {
app = "redis"
}
cluster_ip = "None"
}
}
resource "kubernetes_deployment" "nextcloud_redis" {
metadata {
name = "redis"
namespace = kubernetes_namespace.nextcloud.metadata[0].name
}
spec {
selector {
match_labels = {
app = "redis"
}
}
strategy {
type = "RollingUpdate"
}
template {
metadata {
labels = {
app = "redis"
}
}
spec {
container {
name = "redis"
image = "redis:latest"
security_context {
allow_privilege_escalation = false
}
readiness_probe {
tcp_socket {
port = "redis"
}
initial_delay_seconds = 10
period_seconds = 15
success_threshold = 1
failure_threshold = 3
}
liveness_probe {
tcp_socket {
port = "redis"
}
initial_delay_seconds = 10
period_seconds = 15
success_threshold = 1
failure_threshold = 3
}
port {
container_port = 6379
name = "redis"
protocol = "TCP"
}
}
}
}
}
}
@@ -0,0 +1,14 @@
<IfModule mod_deflate.c>
# Enable compression for the following content types
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE text/xml
</IfModule>
# Do not compress images
SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp|woff|woff2)$ no-gzip
@@ -0,0 +1 @@
MaxRequestWorkers 10
@@ -0,0 +1,86 @@
LoadModule socache_shmcb_module /usr/lib/apache2/modules/mod_socache_shmcb.so
LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so
# Pseudo Random Number Generator (PRNG):
# Configure one or more sources to seed the PRNG of the SSL library.
# The seed data should be of good random quality.
# WARNING! On some platforms /dev/random blocks if not enough entropy
# is available. This means you then cannot use the /dev/random device
# because it would lead to very long connection times (as long as
# it requires to make more entropy available). But usually those
# platforms additionally provide a /dev/urandom device which doesn't
# block. So, if available, use this one instead. Read the mod_ssl User
# Manual for more details.
#
SSLRandomSeed startup builtin
SSLRandomSeed startup file:/dev/urandom 512
SSLRandomSeed connect builtin
SSLRandomSeed connect file:/dev/urandom 512
##
## SSL Global Context
##
## All SSL configuration in this context applies both to
## the main server and all SSL-enabled virtual hosts.
##
#
# Some MIME-types for downloading Certificates and CRLs
#
AddType application/x-x509-ca-cert .crt
AddType application/x-pkcs7-crl .crl
# Pass Phrase Dialog:
# Configure the pass phrase gathering process.
# The filtering dialog program (`builtin' is a internal
# terminal dialog) has to provide the pass phrase on stdout.
SSLPassPhraseDialog exec:/usr/share/apache2/ask-for-passphrase
# Inter-Process Session Cache:
# Configure the SSL Session Cache: First the mechanism
# to use and second the expiring timeout (in seconds).
# (The mechanism dbm has known memory leaks and should not be used).
#SSLSessionCache dbm:${APACHE_RUN_DIR}/ssl_scache
SSLSessionCache shmcb:${APACHE_RUN_DIR}/ssl_scache(512000)
SSLSessionCacheTimeout 300
# Semaphore:
# Configure the path to the mutual exclusion semaphore the
# SSL engine uses internally for inter-process synchronization.
# (Disabled by default, the global Mutex directive consolidates by default
# this)
#Mutex file:${APACHE_LOCK_DIR}/ssl_mutex ssl-cache
# SSL Cipher Suite:
# List the ciphers that the client is permitted to negotiate. See the
# ciphers(1) man page from the openssl package for list of all available
# options.
# Enable only secure ciphers:
SSLCipherSuite HIGH:!aNULL
# SSL server cipher order preference:
# Use server priorities for cipher algorithm choice.
# Clients may prefer lower grade encryption. You should enable this
# option if you want to enforce stronger encryption, and can afford
# the CPU cost, and did not override SSLCipherSuite in a way that puts
# insecure ciphers first.
# Default: Off
#SSLHonorCipherOrder on
# The protocols to enable.
# Available values: all, SSLv3, TLSv1, TLSv1.1, TLSv1.2
# SSL v2 is no longer supported
SSLProtocol all -SSLv3
# Allow insecure renegotiation with clients which do not yet support the
# secure renegotiation protocol. Default: Off
#SSLInsecureRenegotiation on
# Whether to forbid non-SNI clients to access name based virtual hosts.
# Default: Off
#SSLStrictSNIVHostCheck On
# Warning: Session Tickets require regular reloading of the server!
# Make sure you do this (e.g. via logrotate) before changing this setting!
SSLSessionTickets off
@@ -0,0 +1,11 @@
<VirtualHost *:443>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLEngine on
SSLCertificateFile /etc/ssl/certs/private/tls.crt
SSLCertificateKeyFile /etc/ssl/certs/private/tls.key
</VirtualHost>
@@ -0,0 +1,32 @@
server_tokens off;
proxy_hide_header X-Powered-By;
location = /.well-known/webfinger {
return 301 $scheme://$host/index.php/.well-known/webfinger;
}
location = /.well-known/nodeinfo {
return 301 $scheme://$host/index.php/.well-known/nodeinfo;
}
location = /.well-known/carddav {
return 301 $scheme://$host/remote.php/dav;
}
location = /.well-known/caldav {
return 301 $scheme://$host/remote.php/dav;
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/ {
deny all;
}
location ~ ^/(?:autotest|occ|issue|indie|db_|console) {
deny all;
}
@@ -0,0 +1,24 @@
resource "kubernetes_secret" "nextcloud_mail_credentials" {
metadata {
name = "nextcloud-mail-credentials"
namespace = kubernetes_namespace.nextcloud.metadata[0].name
}
data = {
username = var.app_config.smtp_user
password = var.app_config.smtp_password
}
type = "Opaque"
}
resource "kubernetes_secret" "nextcloud_admin_credentials" {
metadata {
name = "nextcloud-admin-credentials"
namespace = kubernetes_namespace.nextcloud.metadata[0].name
}
data = {
username = var.app_config.admin_username
password = var.app_config.admin_password
}
type = "Opaque"
}
@@ -0,0 +1,52 @@
variable "persistent_folder" {
description = "The path to the persistent folder"
type = string
}
variable "tag" {
description = "The version of the websites to install"
type = string
default = "30-apache"
}
variable "fqdn" {
description = "The fully qualified domain name for the Rustdesk server"
type = string
}
variable "tls_enabled" {
description = "Enable TLS for the SFTPGo server"
type = bool
default = true
}
variable "issuer" {
description = "The issuer for the certificate"
type = string
default = "internal-ca"
}
variable "db_credentials_are_secrets" {
description = "set to true if the database credentials are secrets"
type = bool
default = false
}
variable "app_config" {
description = "The configuration for the SFTPGo server"
type = object({
mail_from = string
mail_domain = string
smtp_user = string
smtp_password = string
smtp_host = string
admin_password = string
db_host = string
db_name = string
db_user = string
db_password = string
smtp_port = optional(number, 465)
smtp_secure = optional(string, "ssl")
admin_username = optional(string, "admin")
})
}
@@ -136,12 +136,14 @@ resource "kubernetes_ingress_v1" "reverse-proxy_ingress" {
"kubernetes.io/ingress.class" = "public"
"cert-manager.io/cluster-issuer" = "letsencrypt-prod"
},
each.value.custom_ingress_annotations,
each.value.tls ? {
"nginx.org/ssl-services" = format("reverse-proxy-%s", replace(each.key, ".", "-"))
"nginx.org/ssl-redirect" = "true"
"nginx.org/redirect-to-https" = "true"
"nginx.ingress.kubernetes.io/backend-protocol" = "HTTPS"
} : {})
} : {}
)
}
spec {
tls {
@@ -29,13 +29,15 @@ variable "issuer_name" {
variable "services" {
description = "A list of services to be deployed"
type = map(object({
fqdn = list(string)
upstream = optional(string, "")
resolver = optional(string, "")
default_headers = optional(map(string), {}),
http_port = optional(number, 80)
https_port = optional(number, 443)
tls = optional(bool, false)
fqdn = list(string)
custom_ingress_annotations = optional(map(string), {})
annotations = optional(map(string), {})
upstream = optional(string, "")
resolver = optional(string, "")
default_headers = optional(map(string), {}),
http_port = optional(number, 80)
https_port = optional(number, 443)
tls = optional(bool, false)
locations = optional(list(object({
path = string,
headers = optional(map(string), {}),
+4
View File
@@ -13,3 +13,7 @@ output "root_crendentials_name" {
output "app_crendentials_name" {
value = kubernetes_secret.app_password.metadata[0].name
}
output "db_name" {
value = var.app_name
}