From d3cb741e44b9bb8422c2a31c77ddd9fef451bab4 Mon Sep 17 00:00:00 2001
From: Pascal Bleser
Date: Tue, 14 Oct 2025 11:35:25 +0200
Subject: [PATCH] groupware: try an alternative way to configure Stalwart
dynamically in the devtools Docker Compose setup, by using separate files and
${STALWART_AUTH_DIRECTORY} to name to file to mount
---
.../opencloud_full/config/stalwart/README.md | 21 ++++
.../config/stalwart/idmldap.toml | 110 ++++++++++++++++++
.../opencloud_full/config/stalwart/ldap.toml | 110 ++++++++++++++++++
.../deployments/opencloud_full/stalwart.yml | 2 +-
4 files changed, 242 insertions(+), 1 deletion(-)
create mode 100644 devtools/deployments/opencloud_full/config/stalwart/README.md
create mode 100644 devtools/deployments/opencloud_full/config/stalwart/idmldap.toml
create mode 100644 devtools/deployments/opencloud_full/config/stalwart/ldap.toml
diff --git a/devtools/deployments/opencloud_full/config/stalwart/README.md b/devtools/deployments/opencloud_full/config/stalwart/README.md
new file mode 100644
index 0000000000..df25fb0e05
--- /dev/null
+++ b/devtools/deployments/opencloud_full/config/stalwart/README.md
@@ -0,0 +1,21 @@
+# Stalwart Configuration
+
+The mechanics are currently to mount a different configuration file depending on the environment, as we support two scenarios that are described in [`services/groupware/DEVELOPER.md`](../../../../../services/groupware/DEVELOPER.md):
+
+ * «production» setup, with OpenLDAP and Keycloak containers
+ * «homelab» setup, with the built-in IDM (LDAP) and IDP that run as part of the `opencloud` container
+
+The Docker Compose setup (in [`stalwart.yml`](../../stalwart.yml)) mounts either [`idmldap.toml`](./idmldap.toml) or [`ldap.toml`](./ldap.toml) depending on how the variable `STALWART_AUTH_DIRECTORY` is set, which is either `idmldap` for the homelab setup, or `ldap` for the production setup.
+
+This is thus all done automatically, but whenever changes are performed to Stalwart configuration files, they must be reflected across those two files, to keep them in sync, as the only entry that should differ is this one:
+
+```ruby
+storage.directory = "ldap"
+```
+
+or this:
+
+```ruby
+storage.directory = "idmldap"
+```
+
diff --git a/devtools/deployments/opencloud_full/config/stalwart/idmldap.toml b/devtools/deployments/opencloud_full/config/stalwart/idmldap.toml
new file mode 100644
index 0000000000..eac1294b87
--- /dev/null
+++ b/devtools/deployments/opencloud_full/config/stalwart/idmldap.toml
@@ -0,0 +1,110 @@
+authentication.fallback-admin.secret = "$6$4qPYDVhaUHkKcY7s$bB6qhcukb9oFNYRIvaDZgbwxrMa2RvF5dumCjkBFdX19lSNqrgKltf3aPrFMuQQKkZpK2YNuQ83hB1B3NiWzj."
+authentication.fallback-admin.user = "mailadmin"
+authentication.master.secret = "$6$4qPYDVhaUHkKcY7s$bB6qhcukb9oFNYRIvaDZgbwxrMa2RvF5dumCjkBFdX19lSNqrgKltf3aPrFMuQQKkZpK2YNuQ83hB1B3NiWzj."
+authentication.master.user = "master"
+directory.idmldap.attributes.class = "objectClass"
+directory.idmldap.attributes.description = "displayName"
+directory.idmldap.attributes.email = "mail"
+directory.idmldap.attributes.groups = "memberOf"
+directory.idmldap.attributes.name = "uid"
+directory.idmldap.attributes.secret = "userPassword"
+directory.idmldap.base-dn = "o=libregraph-idm"
+directory.idmldap.bind.auth.method = "default"
+directory.idmldap.bind.dn = "uid=reva,ou=sysusers,o=libregraph-idm"
+directory.idmldap.bind.secret = "admin"
+directory.idmldap.cache.size = 1048576
+directory.idmldap.cache.ttl.negative = "10m"
+directory.idmldap.cache.ttl.positive = "1h"
+directory.idmldap.filter.email = "(&(|(objectClass=person)(objectClass=groupOfNames))(mail=?))"
+directory.idmldap.filter.name = "(&(|(objectClass=person)(objectClass=groupOfNames))(uid=?))"
+directory.idmldap.timeout = "15s"
+directory.idmldap.tls.allow-invalid-certs = true
+directory.idmldap.tls.enable = true
+directory.idmldap.type = "ldap"
+directory.idmldap.url = "ldaps://opencloud:9235"
+directory.keycloak.auth.method = "user-token"
+directory.keycloak.cache.size = 1048576
+directory.keycloak.cache.ttl.negative = "10m"
+directory.keycloak.cache.ttl.positive = "1h"
+directory.keycloak.endpoint.method = "introspect"
+directory.keycloak.endpoint.url = "http://keycloak:8080/realms/openCloud/protocol/openid-connect/userinfo"
+directory.keycloak.fields.email = "email"
+directory.keycloak.fields.full-name = "name"
+directory.keycloak.fields.username = "preferred_username"
+directory.keycloak.timeout = "15s"
+directory.keycloak.type = "oidc"
+directory.ldap.attributes.class = "objectClass"
+directory.ldap.attributes.description = "displayName"
+directory.ldap.attributes.email = "mail"
+directory.ldap.attributes.email-alias = "mailAlias"
+directory.ldap.attributes.groups = "memberOf"
+directory.ldap.attributes.name = "uid"
+directory.ldap.attributes.secret = "userPassword"
+directory.ldap.attributes.secret-changed = "pwdChangedTime"
+directory.ldap.base-dn = "dc=opencloud,dc=eu"
+directory.ldap.bind.auth.dn = "cn=?,ou=users,dc=opencloud,dc=eu"
+directory.ldap.bind.auth.enable = true
+directory.ldap.bind.auth.search = true
+directory.ldap.bind.dn = "cn=admin,dc=opencloud,dc=eu"
+directory.ldap.bind.secret = "admin"
+directory.ldap.cache.ttl.negative = "10m"
+directory.ldap.cache.ttl.positive = "1h"
+directory.ldap.filter.email = "(&(|(objectClass=person)(objectClass=groupOfNames))(|(uid=?)(mail=?)(mailAlias=?)(cn=?)))"
+directory.ldap.filter.name = "(&(|(objectClass=person)(objectClass=groupOfNames))(|(uid=?)(cn=?)))"
+directory.ldap.timeout = "5s"
+directory.ldap.tls.allow-invalid-certs = true
+directory.ldap.tls.enable = true
+directory.ldap.type = "ldap"
+directory.ldap.url = "ldap://ldap-server:1389"
+http.allowed-endpoint = 200
+http.hsts = true
+http.permissive-cors = false
+http.url = "'https://' + config_get('server.hostname')"
+http.use-x-forwarded = true
+metrics.prometheus.auth.secret = "secret"
+metrics.prometheus.auth.username = "metrics"
+metrics.prometheus.enable = true
+server.listener.http.bind = "0.0.0.0:8080"
+server.listener.http.protocol = "http"
+server.listener.https.bind = "0.0.0.0:443"
+server.listener.https.protocol = "http"
+server.listener.https.tls.implicit = true
+server.listener.imap.bind = "0.0.0.0:143"
+server.listener.imap.protocol = "imap"
+server.listener.imaptls.bind = "0.0.0.0:993"
+server.listener.imaptls.protocol = "imap"
+server.listener.imaptls.tls.implicit = true
+server.listener.pop3.bind = "0.0.0.0:110"
+server.listener.pop3.protocol = "pop3"
+server.listener.pop3s.bind = "0.0.0.0:995"
+server.listener.pop3s.protocol = "pop3"
+server.listener.pop3s.tls.implicit = true
+server.listener.sieve.bind = "0.0.0.0:4190"
+server.listener.sieve.protocol = "managesieve"
+server.listener.smtp.bind = "0.0.0.0:25"
+server.listener.smtp.protocol = "smtp"
+server.listener.submission.bind = "0.0.0.0:587"
+server.listener.submission.protocol = "smtp"
+server.listener.submissions.bind = "0.0.0.0:465"
+server.listener.submissions.protocol = "smtp"
+server.listener.submissions.tls.implicit = true
+server.max-connections = 8192
+server.socket.backlog = 1024
+server.socket.nodelay = true
+server.socket.reuse-addr = true
+server.socket.reuse-port = true
+storage.blob = "rocksdb"
+storage.data = "rocksdb"
+storage.directory = "idmldap"
+storage.fts = "rocksdb"
+storage.lookup = "rocksdb"
+store.rocksdb.compression = "lz4"
+store.rocksdb.path = "/opt/stalwart/data"
+store.rocksdb.type = "rocksdb"
+tracer.console.ansi = true
+tracer.console.buffered = true
+tracer.console.enable = true
+tracer.console.level = "trace"
+tracer.console.lossy = false
+tracer.console.multiline = false
+tracer.console.type = "stdout"
diff --git a/devtools/deployments/opencloud_full/config/stalwart/ldap.toml b/devtools/deployments/opencloud_full/config/stalwart/ldap.toml
new file mode 100644
index 0000000000..8b2c465296
--- /dev/null
+++ b/devtools/deployments/opencloud_full/config/stalwart/ldap.toml
@@ -0,0 +1,110 @@
+authentication.fallback-admin.secret = "$6$4qPYDVhaUHkKcY7s$bB6qhcukb9oFNYRIvaDZgbwxrMa2RvF5dumCjkBFdX19lSNqrgKltf3aPrFMuQQKkZpK2YNuQ83hB1B3NiWzj."
+authentication.fallback-admin.user = "mailadmin"
+authentication.master.secret = "$6$4qPYDVhaUHkKcY7s$bB6qhcukb9oFNYRIvaDZgbwxrMa2RvF5dumCjkBFdX19lSNqrgKltf3aPrFMuQQKkZpK2YNuQ83hB1B3NiWzj."
+authentication.master.user = "master"
+directory.idmldap.attributes.class = "objectClass"
+directory.idmldap.attributes.description = "displayName"
+directory.idmldap.attributes.email = "mail"
+directory.idmldap.attributes.groups = "memberOf"
+directory.idmldap.attributes.name = "uid"
+directory.idmldap.attributes.secret = "userPassword"
+directory.idmldap.base-dn = "o=libregraph-idm"
+directory.idmldap.bind.auth.method = "default"
+directory.idmldap.bind.dn = "uid=reva,ou=sysusers,o=libregraph-idm"
+directory.idmldap.bind.secret = "admin"
+directory.idmldap.cache.size = 1048576
+directory.idmldap.cache.ttl.negative = "10m"
+directory.idmldap.cache.ttl.positive = "1h"
+directory.idmldap.filter.email = "(&(|(objectClass=person)(objectClass=groupOfNames))(mail=?))"
+directory.idmldap.filter.name = "(&(|(objectClass=person)(objectClass=groupOfNames))(uid=?))"
+directory.idmldap.timeout = "15s"
+directory.idmldap.tls.allow-invalid-certs = true
+directory.idmldap.tls.enable = true
+directory.idmldap.type = "ldap"
+directory.idmldap.url = "ldaps://opencloud:9235"
+directory.keycloak.auth.method = "user-token"
+directory.keycloak.cache.size = 1048576
+directory.keycloak.cache.ttl.negative = "10m"
+directory.keycloak.cache.ttl.positive = "1h"
+directory.keycloak.endpoint.method = "introspect"
+directory.keycloak.endpoint.url = "http://keycloak:8080/realms/openCloud/protocol/openid-connect/userinfo"
+directory.keycloak.fields.email = "email"
+directory.keycloak.fields.full-name = "name"
+directory.keycloak.fields.username = "preferred_username"
+directory.keycloak.timeout = "15s"
+directory.keycloak.type = "oidc"
+directory.ldap.attributes.class = "objectClass"
+directory.ldap.attributes.description = "displayName"
+directory.ldap.attributes.email = "mail"
+directory.ldap.attributes.email-alias = "mailAlias"
+directory.ldap.attributes.groups = "memberOf"
+directory.ldap.attributes.name = "uid"
+directory.ldap.attributes.secret = "userPassword"
+directory.ldap.attributes.secret-changed = "pwdChangedTime"
+directory.ldap.base-dn = "dc=opencloud,dc=eu"
+directory.ldap.bind.auth.dn = "cn=?,ou=users,dc=opencloud,dc=eu"
+directory.ldap.bind.auth.enable = true
+directory.ldap.bind.auth.search = true
+directory.ldap.bind.dn = "cn=admin,dc=opencloud,dc=eu"
+directory.ldap.bind.secret = "admin"
+directory.ldap.cache.ttl.negative = "10m"
+directory.ldap.cache.ttl.positive = "1h"
+directory.ldap.filter.email = "(&(|(objectClass=person)(objectClass=groupOfNames))(|(uid=?)(mail=?)(mailAlias=?)(cn=?)))"
+directory.ldap.filter.name = "(&(|(objectClass=person)(objectClass=groupOfNames))(|(uid=?)(cn=?)))"
+directory.ldap.timeout = "5s"
+directory.ldap.tls.allow-invalid-certs = true
+directory.ldap.tls.enable = true
+directory.ldap.type = "ldap"
+directory.ldap.url = "ldap://ldap-server:1389"
+http.allowed-endpoint = 200
+http.hsts = true
+http.permissive-cors = false
+http.url = "'https://' + config_get('server.hostname')"
+http.use-x-forwarded = true
+metrics.prometheus.auth.secret = "secret"
+metrics.prometheus.auth.username = "metrics"
+metrics.prometheus.enable = true
+server.listener.http.bind = "0.0.0.0:8080"
+server.listener.http.protocol = "http"
+server.listener.https.bind = "0.0.0.0:443"
+server.listener.https.protocol = "http"
+server.listener.https.tls.implicit = true
+server.listener.imap.bind = "0.0.0.0:143"
+server.listener.imap.protocol = "imap"
+server.listener.imaptls.bind = "0.0.0.0:993"
+server.listener.imaptls.protocol = "imap"
+server.listener.imaptls.tls.implicit = true
+server.listener.pop3.bind = "0.0.0.0:110"
+server.listener.pop3.protocol = "pop3"
+server.listener.pop3s.bind = "0.0.0.0:995"
+server.listener.pop3s.protocol = "pop3"
+server.listener.pop3s.tls.implicit = true
+server.listener.sieve.bind = "0.0.0.0:4190"
+server.listener.sieve.protocol = "managesieve"
+server.listener.smtp.bind = "0.0.0.0:25"
+server.listener.smtp.protocol = "smtp"
+server.listener.submission.bind = "0.0.0.0:587"
+server.listener.submission.protocol = "smtp"
+server.listener.submissions.bind = "0.0.0.0:465"
+server.listener.submissions.protocol = "smtp"
+server.listener.submissions.tls.implicit = true
+server.max-connections = 8192
+server.socket.backlog = 1024
+server.socket.nodelay = true
+server.socket.reuse-addr = true
+server.socket.reuse-port = true
+storage.blob = "rocksdb"
+storage.data = "rocksdb"
+storage.directory = "ldap"
+storage.fts = "rocksdb"
+storage.lookup = "rocksdb"
+store.rocksdb.compression = "lz4"
+store.rocksdb.path = "/opt/stalwart/data"
+store.rocksdb.type = "rocksdb"
+tracer.console.ansi = true
+tracer.console.buffered = true
+tracer.console.enable = true
+tracer.console.level = "trace"
+tracer.console.lossy = false
+tracer.console.multiline = false
+tracer.console.type = "stdout"
diff --git a/devtools/deployments/opencloud_full/stalwart.yml b/devtools/deployments/opencloud_full/stalwart.yml
index e2e5ccd336..d247925491 100644
--- a/devtools/deployments/opencloud_full/stalwart.yml
+++ b/devtools/deployments/opencloud_full/stalwart.yml
@@ -17,7 +17,7 @@ services:
- "127.0.0.1:1465:465"
volumes:
- /etc/localtime:/etc/localtime:ro
- - ./config/stalwart:/opt/stalwart/etc
+ - "./config/stalwart/${STALWART_AUTH_DIRECTORY:-idmldap}.toml:/opt/stalwart/etc/config.toml"
- stalwart-data:/opt/stalwart/data
environment:
STALWART_AUTH_DIRECTORY: "${STALWART_AUTH_DIRECTORY:-idmldap}"