Skip to content

Commit b037e89

Browse files
pbleser-octammi-23
andauthored
Groupware: upgrade Stalwart to 0.16 (#2714)
* chore: Upgrade to Stalwart 0.16 * Stalwart 0.16 introduces a different way of configuring the service, requiring to first bootstrap in recovery mode, then using the stalwart-cli (which is distributed separately) to load a NJSON file that contains the configuration objects, and then restarting Stalwart itself but in regular mode * the TOML configuration file is replaced by a small JSON file in dev/docker/stalwart/stalwart-config.json, and the JSON configuration snapshot dev/docker/stalwart/idmldap.json * the docker-compose.yml has been modified to - have the stalwart container check for a .initialized file in the mounted data volume and if that file does not exist, it loops endlessly until it is there, sleeping 1s in between checks; when that file has been created by the stalwart-import container; and when that happens, the stalwart process is stopped and restarted in regular mode - have an additional container stalwart-import that also mounts the stalwart-data volume, and if the .initialized file does not exist there yet, waits for the stalwart process to be ready (checking for its healthz endpoint), and when that happens, it imports the configuration snapshot using stalwart-cli, and then creates the .initialized file * since stalwart-cli is currently not distributed as a docker container, we create one on-the-fly using an inline dockerfile in the docker-compose.yml * changed the built-in IDM LDAP's authentication password as Stalwart is configured to expect that to be 'admin' instead of 'some-ldap-reva-password', which will make porting changes between this Docker Compose project and the opencloud_full one that is used for developing OpenCloud backend services a lot easier * added OC_LDAP_CACERT to point to the LDAP certificate file * added GROUPWARE_JMAP_BASE_URL, GROUPWARE_JMAP_MASTER_USERNAME and GROUPWARE_JMAP_MASTER_PASSWORD environment variables to opencloud containers * wip: fixing dev-stack * fix: adjust jmap login * wip: added idmldam.json to prettierignore * chore: prettier formatting + ignore stalwart NDJSON snapshot * fix LDAP configuration for the 'description' (full name) of users in Stalwart: must use displayName instead of description * chore: moved stalwart scripts * wip: working with rolling and dev * fix: adjust formatting * wip: fixing timing issue * wip: prettier formating * stalwart-snapshot-passwords: import from node: instead of using require() * add a README.md to explain how to update the Stalwart configuration * fixed typos --------- Co-authored-by: tammi-23 <t.thamm@opencloud.eu>
1 parent 88ace2d commit b037e89

8 files changed

Lines changed: 229 additions & 125 deletions

File tree

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
dev/docker/opencloud/csp.yaml
2+
dev/docker/stalwart/idmldap.json
23
packages/design-system/src/styles/
34
packages/web-client/src/graph/generated/
45
tests/woodpecker/csp.yaml

dev/docker/stalwart/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Stalwart in the Web Development Environment
2+
3+
## Performing Configuration Changes
4+
5+
Configuration changes can be made by pointing a browser to <https://stalwart.opencloud.test/admin/> and authenticating as `admin` with the password `secret`
6+
7+
After changes have been made there, the configuration database needs to be dumped into the NDJSON file `./idmldap.json`
8+
9+
To do so, one needs to have [`stalwart-cli`](https://github.com/stalwartlabs/cli#install) installed locally on the host, and then run this:
10+
11+
```bash
12+
./stalwart-snapshot --quiet | ./stalwart-snapshot-passwords > ./idmldap.json
13+
```

dev/docker/stalwart/config.toml

Lines changed: 0 additions & 114 deletions
This file was deleted.

dev/docker/stalwart/idmldap.json

Lines changed: 25 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"@type": "RocksDb",
3+
"path": "/var/lib/stalwart/",
4+
"blobSize": 16834,
5+
"bufferSize": 134217728,
6+
"poolWorkers": null
7+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/bin/bash
2+
# Snapshots the current configuration of a Stalwart >= 0.16 server.
3+
# Requires having stalwart-cli installed.
4+
set -euo pipefail
5+
exec stalwart-cli -k --url 'https://stalwart.opencloud.test' --user admin --password secret snapshot \
6+
--include-secrets \
7+
Tenant \
8+
Domain \
9+
Directory \
10+
Authentication \
11+
DkimSignature \
12+
AcmeProvider \
13+
Certificate \
14+
DnsServer \
15+
Role \
16+
Authentication \
17+
Account \
18+
NetworkListener \
19+
Tracer \
20+
Sharing \
21+
SystemSettings \
22+
DataRetention \
23+
BlobStore \
24+
InMemoryStore \
25+
SearchStore \
26+
--allow-unresolved PublicKey \
27+
"$@"
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env node
2+
// Processes a Stalwart snapshot JSON file to perform the following:
3+
// - replace masked passwords with their correct value
4+
// - replace the defaultHostname system setting
5+
6+
import * as fs from 'node:fs'
7+
import readline from 'node:readline'
8+
9+
const args = process.argv.slice(2)
10+
const inputs = args.length > 0 ? args.map((file) => fs.createReadStream(file)) : [process.stdin]
11+
12+
async function processLines() {
13+
for (const stream of inputs) {
14+
const rl = readline.createInterface({
15+
input: stream,
16+
crlfDelay: Infinity
17+
})
18+
19+
for await (const line of rl) {
20+
const trimmed = line.trim()
21+
if (!trimmed) continue
22+
23+
const item = JSON.parse(trimmed)
24+
const t = item['@type']
25+
const o = item['object']
26+
27+
if (t === 'create' && o === 'Account') {
28+
const value = item['value'] || {}
29+
for (const [id, account] of Object.entries(value)) {
30+
const name = account['name']
31+
if (name === 'master' || name === 'admin') {
32+
const credentials = account['credentials'] || {}
33+
for (const [cid, creds] of Object.entries(credentials)) {
34+
if (creds['@type'] === 'Password') {
35+
creds['secret'] = 'supersecret1234'
36+
}
37+
}
38+
}
39+
}
40+
}
41+
42+
if (t === 'create' && o === 'Directory') {
43+
const value = item['value'] || {}
44+
for (const [id, dir] of Object.entries(value)) {
45+
if (dir['@type'] === 'Ldap') {
46+
if (dir['bindSecret']) {
47+
dir['bindSecret']['secret'] = 'admin'
48+
}
49+
}
50+
}
51+
}
52+
53+
if (t === 'update' && o === 'SystemSettings') {
54+
if (item['value']) {
55+
item['value']['defaultHostname'] = 'stalwart.opencloud.test'
56+
}
57+
}
58+
59+
console.log(JSON.stringify(item))
60+
}
61+
}
62+
}
63+
64+
processLines()

0 commit comments

Comments
 (0)