From 98144177f846c4e695ddf1c835f85ea4e21cf8df Mon Sep 17 00:00:00 2001
From: Pascal Bleser
Date: Mon, 8 Jun 2026 20:29:34 +0200
Subject: [PATCH] groupware: simplify JMAP integration tests with Stalwart 0.16
* don't run the Stalwart container in recovery mode first, just run it
in "proper" mode, import the configuration and use it like that,
which only requires skipping the "destroy" steps
* add code documentation and logging
* replace the text templating of the Stalwart snapshot by doing the
same as the formatting script was doing, but directly in Go, removing
an additional step to perform when modifying a Stalwart
configuration to use in the integration tests
---
pkg/jmap/export_integration_test.go | 613 ++++++++++++------------
pkg/jmap/export_prefixed_writer_test.go | 62 +++
2 files changed, 371 insertions(+), 304 deletions(-)
create mode 100644 pkg/jmap/export_prefixed_writer_test.go
diff --git a/pkg/jmap/export_integration_test.go b/pkg/jmap/export_integration_test.go
index b1b72ef3b3..cddae266e0 100644
--- a/pkg/jmap/export_integration_test.go
+++ b/pkg/jmap/export_integration_test.go
@@ -20,10 +20,8 @@ import (
"regexp"
"runtime"
"slices"
- "strconv"
"strings"
"testing"
- "text/template"
"time"
"github.com/google/go-cmp/cmp"
@@ -46,9 +44,32 @@ import (
)
const (
- EnableTypes = false
+ // When enabled, creates a new temporary Docker network to run the Stalwart and
+ // CLI containers in, which might be necessary in some environments for both
+ // containers to see each other.
+ // On Linux, this is not needed ("works for me"), but that might not be the
+ // case on all platforms, as it pertains to Docker networking and how hostname
+ // resolution works, which is why we keep the code and this option configurable.
+ useNetwork = false
- // Wireshark = "/usr/bin/wireshark"
+ // When enabled, integration tests first start the Stalwart container in recovery mode,
+ // then import the configuration (which includes deleting existing configuration objects first),
+ // then stops that container, and starts it again in non-recovery mode, reusing the same
+ // data volume.
+ // When disabled, it skips using the recovery container, which is faster and simpler,
+ // and works for now, but it might not be the case in the future, which is why we keep
+ // the code and this option configurable.
+ useRecoveryContainer = false
+
+ // When enabled, expects all JMAP objects to have a @type attribute, which should stay disabled
+ // since they're optional in the specification at the time of writing, but might change in the
+ // future.
+ enableTypes = false
+
+ // When not empty and set to a fully-qualified path to a wireshark binary (e.g. "/usr/bin/wireshark"),
+ // a Wireshark process is started to listen in on the traffic that is exchanged with the Stalwart
+ // container, when deep debugging is required and when the Stalwart tracing of inbound and outbound JMAP
+ // messages is not sufficient.
Wireshark = ""
)
@@ -74,6 +95,8 @@ func mkuser(name string, description string, alias string) User {
}
var (
+ // A list of users that we create before running any tests.
+ // Tests can randomly pick one or more of them.
users = [...]User{
mkuser("cdrummer", "Camina Drummer", "camina.drummer@opa.org"),
mkuser("aburton", "Amos Burton", "amos.burton@earth.gov"),
@@ -88,11 +111,24 @@ var (
)
const (
- stalwartImage = "ghcr.io/stalwartlabs/stalwart:v0.16.7-alpine"
- httpPort = "8080"
- imapsPort = "993"
- jsonConfigTemplate = `{"@type":"RocksDb","path":"/var/lib/stalwart/","blobSize":16834,"bufferSize":134217728,"poolWorkers":null}`
- cliDockerfile = `
+ StalwartVersion = "0.16.7"
+ StalwartCliVersion = "1.0.8"
+
+ stalwartImageTemplate = "ghcr.io/stalwartlabs/stalwart:v%s-alpine"
+ httpPort = "8080"
+ imapsPort = "993"
+
+ // Bootstrap configuration file for Stalwart >= 0.16, only points to the storage.
+ stalwartConfig = `{"@type":"RocksDb","path":"/var/lib/stalwart/","blobSize":16834,"bufferSize":134217728,"poolWorkers":null}`
+
+ // Keep this in sync with the "path" in the Stalwart configuration template above.
+ stalwartStoragePath = "/var/lib/stalwart"
+
+ // Where Stalwart expects its bootstrapping configuration file to be.
+ stalwartConfigPath = "/etc/stalwart/config.json"
+
+ // Dockerfile to create a container with the CLI that is used to import the configuration for Stalwart >= 0.16.
+ cliDockerfile = `
FROM alpine:latest
ARG VERSION="1.0.8"
ARG ARCH="x86_64"
@@ -102,181 +138,63 @@ CMD ["/usr/local/bin/stalwart-cli"]
)
var (
- dumpTemplate = []string{
- `{"@type":"destroy","object":"Tracer"}`,
- `{"@type":"destroy","object":"NetworkListener"}`,
- `{"@type":"destroy","object":"DnsServer"}`,
- `{"@type":"destroy","object":"Certificate"}`,
- `{"@type":"destroy","object":"Tenant"}`,
- `{"@type":"destroy","object":"Role"}`,
- `{"@type":"destroy","object":"AcmeProvider"}`,
- `{"@type":"destroy","object":"Directory"}`,
- `{"@type":"destroy","object":"Domain"}`,
- `{"@type":"destroy","object":"Account"}`,
- `{"@type":"destroy","object":"DkimSignature"}`,
- `{"@type":"create","object":"Tracer","value":{"tracer-iugg9zgwaaaa":{"@type":"Stdout","eventsPolicy":"exclude","enable":true,"multiline":false,"level":"trace","lossy":false,"buffered":true,"ansi":false,"events":{"store.data-write":true,"eval.result":true,"store.cache-hit":true,"store.cache-miss":true,"store.cache-stale":true,"store.cache-update":true,"store.data-iterate":true,"spam.rules-updated":true,"resource.download-external":true,"task-manager.task-acquired":true,"task-manager.scheduler-started":true,"task-manager.manager-started":true}}}}`,
- `{"@type":"create","object":"NetworkListener","value":{"networklistener-iughfwjyahqb":{"socketNoDelay":true,"socketReusePort":true,"tlsDisableCipherSuites":{},"socketReuseAddress":true,"name":"http","bind":{"[::]:8080":true},"socketTosV4":null,"socketTtl":null,"maxConnections":8192,"socketBacklog":1024,"socketReceiveBufferSize":null,"overrideProxyTrustedNetworks":{},"socketSendBufferSize":null,"tlsIgnoreClientOrder":true,"tlsImplicit":false,"tlsTimeout":60000,"useTls":true,"tlsDisableProtocols":{},"protocol":"http"},"networklistener-iughfwjyahab":{"socketNoDelay":true,"socketReusePort":true,"tlsDisableCipherSuites":{},"socketReuseAddress":true,"name":"https","bind":{"[::]:443":true},"socketTosV4":null,"socketTtl":null,"maxConnections":8192,"socketBacklog":1024,"socketReceiveBufferSize":null,"overrideProxyTrustedNetworks":{},"socketSendBufferSize":null,"tlsIgnoreClientOrder":true,"tlsImplicit":true,"tlsTimeout":60000,"useTls":true,"tlsDisableProtocols":{},"protocol":"http"},"networklistener-iughfwjyagqb":{"socketNoDelay":true,"socketReusePort":true,"tlsDisableCipherSuites":{},"socketReuseAddress":true,"name":"sieve","bind":{"[::]:4190":true},"socketTosV4":null,"socketTtl":null,"maxConnections":8192,"socketBacklog":1024,"socketReceiveBufferSize":null,"overrideProxyTrustedNetworks":{},"socketSendBufferSize":null,"tlsIgnoreClientOrder":true,"tlsImplicit":false,"tlsTimeout":60000,"useTls":true,"tlsDisableProtocols":{},"protocol":"manageSieve"},"networklistener-iughfwjyagab":{"socketNoDelay":true,"socketReusePort":true,"tlsDisableCipherSuites":{},"socketReuseAddress":true,"name":"pop3s","bind":{"[::]:995":true},"socketTosV4":null,"socketTtl":null,"maxConnections":8192,"socketBacklog":1024,"socketReceiveBufferSize":null,"overrideProxyTrustedNetworks":{},"socketSendBufferSize":null,"tlsIgnoreClientOrder":true,"tlsImplicit":true,"tlsTimeout":60000,"useTls":true,"tlsDisableProtocols":{},"protocol":"pop3"},"networklistener-iughfwjyafqb":{"socketNoDelay":true,"socketReusePort":true,"tlsDisableCipherSuites":{},"socketReuseAddress":true,"name":"imaps","bind":{"[::]:993":true},"socketTosV4":null,"socketTtl":null,"maxConnections":8192,"socketBacklog":1024,"socketReceiveBufferSize":null,"overrideProxyTrustedNetworks":{},"socketSendBufferSize":null,"tlsIgnoreClientOrder":true,"tlsImplicit":true,"tlsTimeout":60000,"useTls":true,"tlsDisableProtocols":{},"protocol":"imap"},"networklistener-iughfwjwafab":{"socketNoDelay":true,"socketReusePort":true,"tlsDisableCipherSuites":{},"socketReuseAddress":true,"name":"submissions","bind":{"[::]:465":true},"socketTosV4":null,"socketTtl":null,"maxConnections":8192,"socketBacklog":1024,"socketReceiveBufferSize":null,"overrideProxyTrustedNetworks":{},"socketSendBufferSize":null,"tlsIgnoreClientOrder":true,"tlsImplicit":true,"tlsTimeout":60000,"useTls":true,"tlsDisableProtocols":{},"protocol":"smtp"},"networklistener-iughfwjwaeqb":{"socketNoDelay":true,"socketReusePort":true,"tlsDisableCipherSuites":{},"socketReuseAddress":true,"name":"smtp","bind":{"[::]:25":true},"socketTosV4":null,"socketTtl":null,"maxConnections":8192,"socketBacklog":1024,"socketReceiveBufferSize":null,"overrideProxyTrustedNetworks":{},"socketSendBufferSize":null,"tlsIgnoreClientOrder":true,"tlsImplicit":false,"tlsTimeout":60000,"useTls":true,"tlsDisableProtocols":{},"protocol":"smtp"}}}`,
- `{"@type":"create","object":"Role","value":{"role-e":{"disabledPermissions":{},"memberTenantId":null,"enabledPermissions":{"authenticate":true,"authenticateWithAlias":true,"interactAi":true,"impersonate":true,"unlimitedRequests":true,"unlimitedUploads":true,"fetchAnyBlob":true,"oAuthClientRegistration":true,"oAuthClientOverride":true,"liveTracing":true,"liveMetrics":true,"liveDeliveryTest":true,"sysAccountGet":true,"sysAccountCreate":true,"sysAccountUpdate":true,"sysAccountDestroy":true,"sysAccountQuery":true,"sysAccountPasswordGet":true,"sysAccountPasswordUpdate":true,"sysAccountSettingsGet":true,"sysAccountSettingsUpdate":true,"sysAcmeProviderGet":true,"sysAcmeProviderCreate":true,"sysAcmeProviderUpdate":true,"sysAcmeProviderDestroy":true,"sysAcmeProviderQuery":true,"actionReloadSettings":true,"actionReloadTlsCertificates":true,"actionReloadLookupStores":true,"actionReloadBlockedIps":true,"actionUpdateApps":true,"actionTroubleshootDmarc":true,"actionClassifySpam":true,"actionInvalidateCaches":true,"actionInvalidateNegativeCaches":true,"actionPauseMtaQueue":true,"actionResumeMtaQueue":true,"sysActionGet":true,"sysActionCreate":true,"sysActionUpdate":true,"sysActionDestroy":true,"sysActionQuery":true,"sysAddressBookGet":true,"sysAddressBookUpdate":true,"sysAiModelGet":true,"sysAiModelCreate":true,"sysAiModelUpdate":true,"sysAiModelDestroy":true,"sysAiModelQuery":true,"sysAlertGet":true,"sysAlertCreate":true,"sysAlertUpdate":true,"sysAlertDestroy":true,"sysAlertQuery":true,"sysAllowedIpGet":true,"sysAllowedIpCreate":true,"sysAllowedIpUpdate":true,"sysAllowedIpDestroy":true,"sysAllowedIpQuery":true,"sysApiKeyGet":true,"sysApiKeyCreate":true,"sysApiKeyUpdate":true,"sysApiKeyDestroy":true,"sysApiKeyQuery":true,"sysAppPasswordGet":true,"sysAppPasswordCreate":true,"sysAppPasswordUpdate":true,"sysAppPasswordDestroy":true,"sysAppPasswordQuery":true,"sysApplicationGet":true,"sysApplicationCreate":true,"sysApplicationUpdate":true,"sysApplicationDestroy":true,"sysApplicationQuery":true,"sysArchivedItemGet":true,"sysArchivedItemCreate":true,"sysArchivedItemUpdate":true,"sysArchivedItemDestroy":true,"sysArchivedItemQuery":true,"sysArfExternalReportGet":true,"sysArfExternalReportCreate":true,"sysArfExternalReportUpdate":true,"sysArfExternalReportDestroy":true,"sysArfExternalReportQuery":true,"sysAsnGet":true,"sysAsnUpdate":true,"sysAuthenticationGet":true,"sysAuthenticationUpdate":true,"sysBlobStoreGet":true,"sysBlobStoreUpdate":true,"sysBlockedIpGet":true,"sysBlockedIpCreate":true,"sysBlockedIpUpdate":true,"sysBlockedIpDestroy":true,"sysBlockedIpQuery":true,"sysBootstrapGet":true,"sysBootstrapUpdate":true,"sysCacheGet":true,"sysCacheUpdate":true,"sysCalendarGet":true,"sysCalendarUpdate":true,"sysCalendarAlarmGet":true,"sysCalendarAlarmUpdate":true,"sysCalendarSchedulingGet":true,"sysCalendarSchedulingUpdate":true,"sysCertificateGet":true,"sysCertificateCreate":true,"sysCertificateUpdate":true,"sysCertificateDestroy":true,"sysCertificateQuery":true,"sysClusterNodeGet":true,"sysClusterNodeCreate":true,"sysClusterNodeUpdate":true,"sysClusterNodeDestroy":true,"sysClusterNodeQuery":true,"sysClusterRoleGet":true,"sysClusterRoleCreate":true,"sysClusterRoleUpdate":true,"sysClusterRoleDestroy":true,"sysClusterRoleQuery":true,"sysCoordinatorGet":true,"sysCoordinatorUpdate":true,"sysDataRetentionGet":true,"sysDataRetentionUpdate":true,"sysDataStoreGet":true,"sysDataStoreUpdate":true,"sysDirectoryGet":true,"sysDirectoryCreate":true,"sysDirectoryUpdate":true,"sysDirectoryDestroy":true,"sysDirectoryQuery":true,"sysDkimReportSettingsGet":true,"sysDkimReportSettingsUpdate":true,"sysDkimSignatureGet":true,"sysDkimSignatureCreate":true,"sysDkimSignatureUpdate":true,"sysDkimSignatureDestroy":true,"sysDkimSignatureQuery":true,"sysDmarcExternalReportGet":true,"sysDmarcExternalReportCreate":true,"sysDmarcExternalReportUpdate":true,"sysDmarcExternalReportDestroy":true,"sysDmarcExternalReportQuery":true,"sysDmarcInternalReportGet":true,"sysDmarcInternalReportCreate":true,"sysDmarcInternalReportUpdate":true,"sysDmarcInternalReportDestroy":true,"sysDmarcInternalReportQuery":true,"sysDmarcReportSettingsGet":true,"sysDmarcReportSettingsUpdate":true,"sysDnsResolverGet":true,"sysDnsResolverUpdate":true,"sysDnsServerGet":true,"sysDnsServerCreate":true,"sysDnsServerUpdate":true,"sysDnsServerDestroy":true,"sysDnsServerQuery":true,"sysDomainGet":true,"sysDomainCreate":true,"sysDomainUpdate":true,"sysDomainDestroy":true,"sysDomainQuery":true,"sysDsnReportSettingsGet":true,"sysDsnReportSettingsUpdate":true,"sysEmailGet":true,"sysEmailUpdate":true,"sysEnterpriseGet":true,"sysEnterpriseUpdate":true,"sysEventTracingLevelGet":true,"sysEventTracingLevelCreate":true,"sysEventTracingLevelUpdate":true,"sysEventTracingLevelDestroy":true,"sysEventTracingLevelQuery":true,"sysFileStorageGet":true,"sysFileStorageUpdate":true,"sysHttpGet":true,"sysHttpUpdate":true,"sysHttpFormGet":true,"sysHttpFormUpdate":true,"sysHttpLookupGet":true,"sysHttpLookupCreate":true,"sysHttpLookupUpdate":true,"sysHttpLookupDestroy":true,"sysHttpLookupQuery":true,"sysImapGet":true,"sysImapUpdate":true,"sysInMemoryStoreGet":true,"sysInMemoryStoreUpdate":true,"sysJmapGet":true,"sysJmapUpdate":true,"sysLogGet":true,"sysLogCreate":true,"sysLogUpdate":true,"sysLogDestroy":true,"sysLogQuery":true,"sysMailingListGet":true,"sysMailingListCreate":true,"sysMailingListUpdate":true,"sysMailingListDestroy":true,"sysMailingListQuery":true,"sysMaskedEmailGet":true,"sysMaskedEmailCreate":true,"sysMaskedEmailUpdate":true,"sysMaskedEmailDestroy":true,"sysMaskedEmailQuery":true,"sysMemoryLookupKeyGet":true,"sysMemoryLookupKeyCreate":true,"sysMemoryLookupKeyUpdate":true,"sysMemoryLookupKeyDestroy":true,"sysMemoryLookupKeyQuery":true,"sysMemoryLookupKeyValueGet":true,"sysMemoryLookupKeyValueCreate":true,"sysMemoryLookupKeyValueUpdate":true,"sysMemoryLookupKeyValueDestroy":true,"sysMemoryLookupKeyValueQuery":true,"sysMetricGet":true,"sysMetricCreate":true,"sysMetricUpdate":true,"sysMetricDestroy":true,"sysMetricQuery":true,"sysMetricsGet":true,"sysMetricsUpdate":true,"sysMetricsStoreGet":true,"sysMetricsStoreUpdate":true,"sysMtaConnectionStrategyGet":true,"sysMtaConnectionStrategyCreate":true,"sysMtaConnectionStrategyUpdate":true,"sysMtaConnectionStrategyDestroy":true,"sysMtaConnectionStrategyQuery":true,"sysMtaDeliveryScheduleGet":true,"sysMtaDeliveryScheduleCreate":true,"sysMtaDeliveryScheduleUpdate":true,"sysMtaDeliveryScheduleDestroy":true,"sysMtaDeliveryScheduleQuery":true,"sysMtaExtensionsGet":true,"sysMtaExtensionsUpdate":true,"sysMtaHookGet":true,"sysMtaHookCreate":true,"sysMtaHookUpdate":true,"sysMtaHookDestroy":true,"sysMtaHookQuery":true,"sysMtaInboundSessionGet":true,"sysMtaInboundSessionUpdate":true,"sysMtaInboundThrottleGet":true,"sysMtaInboundThrottleCreate":true,"sysMtaInboundThrottleUpdate":true,"sysMtaInboundThrottleDestroy":true,"sysMtaInboundThrottleQuery":true,"sysMtaMilterGet":true,"sysMtaMilterCreate":true,"sysMtaMilterUpdate":true,"sysMtaMilterDestroy":true,"sysMtaMilterQuery":true,"sysMtaOutboundStrategyGet":true,"sysMtaOutboundStrategyUpdate":true,"sysMtaOutboundThrottleGet":true,"sysMtaOutboundThrottleCreate":true,"sysMtaOutboundThrottleUpdate":true,"sysMtaOutboundThrottleDestroy":true,"sysMtaOutboundThrottleQuery":true,"sysMtaQueueQuotaGet":true,"sysMtaQueueQuotaCreate":true,"sysMtaQueueQuotaUpdate":true,"sysMtaQueueQuotaDestroy":true,"sysMtaQueueQuotaQuery":true,"sysMtaRouteGet":true,"sysMtaRouteCreate":true,"sysMtaRouteUpdate":true,"sysMtaRouteDestroy":true,"sysMtaRouteQuery":true,"sysMtaStageAuthGet":true,"sysMtaStageAuthUpdate":true,"sysMtaStageConnectGet":true,"sysMtaStageConnectUpdate":true,"sysMtaStageDataGet":true,"sysMtaStageDataUpdate":true,"sysMtaStageEhloGet":true,"sysMtaStageEhloUpdate":true,"sysMtaStageMailGet":true,"sysMtaStageMailUpdate":true,"sysMtaStageRcptGet":true,"sysMtaStageRcptUpdate":true,"sysMtaStsGet":true,"sysMtaStsUpdate":true,"sysMtaTlsStrategyGet":true,"sysMtaTlsStrategyCreate":true,"sysMtaTlsStrategyUpdate":true,"sysMtaTlsStrategyDestroy":true,"sysMtaTlsStrategyQuery":true,"sysMtaVirtualQueueGet":true,"sysMtaVirtualQueueCreate":true,"sysMtaVirtualQueueUpdate":true,"sysMtaVirtualQueueDestroy":true,"sysMtaVirtualQueueQuery":true,"sysNetworkListenerGet":true,"sysNetworkListenerCreate":true,"sysNetworkListenerUpdate":true,"sysNetworkListenerDestroy":true,"sysNetworkListenerQuery":true,"sysOAuthClientGet":true,"sysOAuthClientCreate":true,"sysOAuthClientUpdate":true,"sysOAuthClientDestroy":true,"sysOAuthClientQuery":true,"sysOidcProviderGet":true,"sysOidcProviderUpdate":true,"sysPublicKeyGet":true,"sysPublicKeyCreate":true,"sysPublicKeyUpdate":true,"sysPublicKeyDestroy":true,"sysPublicKeyQuery":true,"sysQueuedMessageGet":true,"sysQueuedMessageCreate":true,"sysQueuedMessageUpdate":true,"sysQueuedMessageDestroy":true,"sysQueuedMessageQuery":true,"sysReportSettingsGet":true,"sysReportSettingsUpdate":true,"sysRoleGet":true,"sysRoleCreate":true,"sysRoleUpdate":true,"sysRoleDestroy":true,"sysRoleQuery":true,"sysSearchGet":true,"sysSearchUpdate":true,"sysSearchStoreGet":true,"sysSearchStoreUpdate":true,"sysSecurityGet":true,"sysSecurityUpdate":true,"sysSenderAuthGet":true,"sysSenderAuthUpdate":true,"sysSharingGet":true,"sysSharingUpdate":true,"sysSieveSystemInterpreterGet":true,"sysSieveSystemInterpreterUpdate":true,"sysSieveSystemScriptGet":true,"sysSieveSystemScriptCreate":true,"sysSieveSystemScriptUpdate":true,"sysSieveSystemScriptDestroy":true,"sysSieveSystemScriptQuery":true,"sysSieveUserInterpreterGet":true,"sysSieveUserInterpreterUpdate":true,"sysSieveUserScriptGet":true,"sysSieveUserScriptCreate":true,"sysSieveUserScriptUpdate":true,"sysSieveUserScriptDestroy":true,"sysSieveUserScriptQuery":true,"sysSpamClassifierGet":true,"sysSpamClassifierUpdate":true,"sysSpamDnsblServerGet":true,"sysSpamDnsblServerCreate":true,"sysSpamDnsblServerUpdate":true,"sysSpamDnsblServerDestroy":true,"sysSpamDnsblServerQuery":true,"sysSpamDnsblSettingsGet":true,"sysSpamDnsblSettingsUpdate":true,"sysSpamFileExtensionGet":true,"sysSpamFileExtensionCreate":true,"sysSpamFileExtensionUpdate":true,"sysSpamFileExtensionDestroy":true,"sysSpamFileExtensionQuery":true,"sysSpamLlmGet":true,"sysSpamLlmUpdate":true,"sysSpamPyzorGet":true,"sysSpamPyzorUpdate":true,"sysSpamRuleGet":true,"sysSpamRuleCreate":true,"sysSpamRuleUpdate":true,"sysSpamRuleDestroy":true,"sysSpamRuleQuery":true,"sysSpamSettingsGet":true,"sysSpamSettingsUpdate":true,"sysSpamTagGet":true,"sysSpamTagCreate":true,"sysSpamTagUpdate":true,"sysSpamTagDestroy":true,"sysSpamTagQuery":true,"sysSpamTrainingSampleGet":true,"sysSpamTrainingSampleCreate":true,"sysSpamTrainingSampleUpdate":true,"sysSpamTrainingSampleDestroy":true,"sysSpamTrainingSampleQuery":true,"sysSpfReportSettingsGet":true,"sysSpfReportSettingsUpdate":true,"sysStoreLookupGet":true,"sysStoreLookupCreate":true,"sysStoreLookupUpdate":true,"sysStoreLookupDestroy":true,"sysStoreLookupQuery":true,"sysSystemSettingsGet":true,"sysSystemSettingsUpdate":true,"taskIndexDocument":true,"taskUnindexDocument":true,"taskIndexTrace":true,"taskCalendarAlarmEmail":true,"taskCalendarAlarmNotification":true,"taskCalendarItipMessage":true,"taskMergeThreads":true,"taskDmarcReport":true,"taskTlsReport":true,"taskRestoreArchivedItem":true,"taskDestroyAccount":true,"taskAccountMaintenance":true,"taskTenantMaintenance":true,"taskStoreMaintenance":true,"taskSpamFilterMaintenance":true,"taskAcmeRenewal":true,"taskDkimManagement":true,"taskDnsManagement":true,"sysTaskGet":true,"sysTaskCreate":true,"sysTaskUpdate":true,"sysTaskDestroy":true,"sysTaskQuery":true,"sysTaskManagerGet":true,"sysTaskManagerUpdate":true,"sysTenantGet":true,"sysTenantCreate":true,"sysTenantUpdate":true,"sysTenantDestroy":true,"sysTenantQuery":true,"sysTlsExternalReportGet":true,"sysTlsExternalReportCreate":true,"sysTlsExternalReportUpdate":true,"sysTlsExternalReportDestroy":true,"sysTlsExternalReportQuery":true,"sysTlsInternalReportGet":true,"sysTlsInternalReportCreate":true,"sysTlsInternalReportUpdate":true,"sysTlsInternalReportDestroy":true,"sysTlsInternalReportQuery":true,"sysTlsReportSettingsGet":true,"sysTlsReportSettingsUpdate":true,"sysTraceGet":true,"sysTraceCreate":true,"sysTraceUpdate":true,"sysTraceDestroy":true,"sysTraceQuery":true,"sysTracerGet":true,"sysTracerCreate":true,"sysTracerUpdate":true,"sysTracerDestroy":true,"sysTracerQuery":true,"sysTracingStoreGet":true,"sysTracingStoreUpdate":true,"sysWebDavGet":true,"sysWebDavUpdate":true,"sysWebHookGet":true,"sysWebHookCreate":true,"sysWebHookUpdate":true,"sysWebHookDestroy":true,"sysWebHookQuery":true},"description":"System Administrator","roleIds":{}},"role-d":{"disabledPermissions":{},"memberTenantId":null,"enabledPermissions":{"authenticate":true,"authenticateWithAlias":true,"interactAi":true,"fetchAnyBlob":true,"liveDeliveryTest":true,"sysAccountGet":true,"sysAccountCreate":true,"sysAccountUpdate":true,"sysAccountDestroy":true,"sysAccountQuery":true,"sysAcmeProviderGet":true,"sysAcmeProviderCreate":true,"sysAcmeProviderUpdate":true,"sysAcmeProviderDestroy":true,"sysAcmeProviderQuery":true,"sysDkimSignatureGet":true,"sysDkimSignatureCreate":true,"sysDkimSignatureUpdate":true,"sysDkimSignatureDestroy":true,"sysDkimSignatureQuery":true,"sysDnsServerGet":true,"sysDnsServerCreate":true,"sysDnsServerUpdate":true,"sysDnsServerDestroy":true,"sysDnsServerQuery":true,"sysDomainGet":true,"sysDomainCreate":true,"sysDomainUpdate":true,"sysDomainDestroy":true,"sysDomainQuery":true,"sysMailingListGet":true,"sysMailingListCreate":true,"sysMailingListUpdate":true,"sysMailingListDestroy":true,"sysMailingListQuery":true,"sysOAuthClientGet":true,"sysOAuthClientCreate":true,"sysOAuthClientUpdate":true,"sysOAuthClientDestroy":true,"sysOAuthClientQuery":true,"sysQueuedMessageGet":true,"sysQueuedMessageCreate":true,"sysQueuedMessageUpdate":true,"sysQueuedMessageDestroy":true,"sysQueuedMessageQuery":true,"sysRoleGet":true,"sysRoleCreate":true,"sysRoleUpdate":true,"sysRoleDestroy":true,"sysRoleQuery":true},"description":"Tenant Administrator","roleIds":{}},"role-c":{"disabledPermissions":{},"memberTenantId":null,"enabledPermissions":{"emailSend":true,"emailReceive":true,"calendarAlarmsSend":true,"calendarSchedulingSend":true,"calendarSchedulingReceive":true,"jmapPushSubscriptionGet":true,"jmapPushSubscriptionCreate":true,"jmapPushSubscriptionUpdate":true,"jmapPushSubscriptionDestroy":true,"jmapMailboxGet":true,"jmapMailboxChanges":true,"jmapMailboxQuery":true,"jmapMailboxQueryChanges":true,"jmapMailboxCreate":true,"jmapMailboxUpdate":true,"jmapMailboxDestroy":true,"jmapThreadGet":true,"jmapThreadChanges":true,"jmapEmailGet":true,"jmapEmailChanges":true,"jmapEmailQuery":true,"jmapEmailQueryChanges":true,"jmapEmailCreate":true,"jmapEmailUpdate":true,"jmapEmailDestroy":true,"jmapEmailCopy":true,"jmapEmailImport":true,"jmapEmailParse":true,"jmapSearchSnippetGet":true,"jmapIdentityGet":true,"jmapIdentityChanges":true,"jmapIdentityCreate":true,"jmapIdentityUpdate":true,"jmapIdentityDestroy":true,"jmapEmailSubmissionGet":true,"jmapEmailSubmissionChanges":true,"jmapEmailSubmissionQuery":true,"jmapEmailSubmissionQueryChanges":true,"jmapEmailSubmissionCreate":true,"jmapEmailSubmissionUpdate":true,"jmapEmailSubmissionDestroy":true,"jmapVacationResponseGet":true,"jmapVacationResponseCreate":true,"jmapVacationResponseUpdate":true,"jmapVacationResponseDestroy":true,"jmapSieveScriptGet":true,"jmapSieveScriptQuery":true,"jmapSieveScriptValidate":true,"jmapSieveScriptCreate":true,"jmapSieveScriptUpdate":true,"jmapSieveScriptDestroy":true,"jmapPrincipalGet":true,"jmapPrincipalQuery":true,"jmapPrincipalChanges":true,"jmapPrincipalQueryChanges":true,"jmapPrincipalGetAvailability":true,"jmapPrincipalCreate":true,"jmapPrincipalUpdate":true,"jmapPrincipalDestroy":true,"jmapQuotaGet":true,"jmapQuotaChanges":true,"jmapQuotaQuery":true,"jmapQuotaQueryChanges":true,"jmapBlobGet":true,"jmapBlobCopy":true,"jmapBlobLookup":true,"jmapBlobUpload":true,"jmapAddressBookGet":true,"jmapAddressBookChanges":true,"jmapAddressBookCreate":true,"jmapAddressBookUpdate":true,"jmapAddressBookDestroy":true,"jmapContactCardGet":true,"jmapContactCardChanges":true,"jmapContactCardQuery":true,"jmapContactCardQueryChanges":true,"jmapContactCardCreate":true,"jmapContactCardUpdate":true,"jmapContactCardDestroy":true,"jmapContactCardCopy":true,"jmapContactCardParse":true,"jmapFileNodeGet":true,"jmapFileNodeChanges":true,"jmapFileNodeQuery":true,"jmapFileNodeQueryChanges":true,"jmapFileNodeCreate":true,"jmapFileNodeUpdate":true,"jmapFileNodeDestroy":true,"jmapShareNotificationGet":true,"jmapShareNotificationChanges":true,"jmapShareNotificationQuery":true,"jmapShareNotificationQueryChanges":true,"jmapShareNotificationCreate":true,"jmapShareNotificationUpdate":true,"jmapShareNotificationDestroy":true,"jmapCalendarGet":true,"jmapCalendarChanges":true,"jmapCalendarCreate":true,"jmapCalendarUpdate":true,"jmapCalendarDestroy":true,"jmapCalendarEventGet":true,"jmapCalendarEventChanges":true,"jmapCalendarEventQuery":true,"jmapCalendarEventQueryChanges":true,"jmapCalendarEventCreate":true,"jmapCalendarEventUpdate":true,"jmapCalendarEventDestroy":true,"jmapCalendarEventCopy":true,"jmapCalendarEventParse":true,"jmapCalendarEventNotificationGet":true,"jmapCalendarEventNotificationChanges":true,"jmapCalendarEventNotificationQuery":true,"jmapCalendarEventNotificationQueryChanges":true,"jmapCalendarEventNotificationCreate":true,"jmapCalendarEventNotificationUpdate":true,"jmapCalendarEventNotificationDestroy":true,"jmapParticipantIdentityGet":true,"jmapParticipantIdentityChanges":true,"jmapParticipantIdentityCreate":true,"jmapParticipantIdentityUpdate":true,"jmapParticipantIdentityDestroy":true,"jmapCoreEcho":true,"imapAuthenticate":true,"imapAclGet":true,"imapAclSet":true,"imapMyRights":true,"imapListRights":true,"imapAppend":true,"imapCapability":true,"imapId":true,"imapCopy":true,"imapMove":true,"imapCreate":true,"imapDelete":true,"imapEnable":true,"imapExpunge":true,"imapFetch":true,"imapIdle":true,"imapList":true,"imapLsub":true,"imapNamespace":true,"imapRename":true,"imapSearch":true,"imapSort":true,"imapSelect":true,"imapExamine":true,"imapStatus":true,"imapStore":true,"imapSubscribe":true,"imapThread":true,"pop3Authenticate":true,"pop3List":true,"pop3Uidl":true,"pop3Stat":true,"pop3Retr":true,"pop3Dele":true,"sieveAuthenticate":true,"sieveListScripts":true,"sieveSetActive":true,"sieveGetScript":true,"sievePutScript":true,"sieveDeleteScript":true,"sieveRenameScript":true,"sieveCheckScript":true,"sieveHaveSpace":true,"davSyncCollection":true,"davExpandProperty":true,"davPrincipalAcl":true,"davPrincipalList":true,"davPrincipalMatch":true,"davPrincipalSearch":true,"davPrincipalSearchPropSet":true,"davFilePropFind":true,"davFilePropPatch":true,"davFileGet":true,"davFileMkCol":true,"davFileDelete":true,"davFilePut":true,"davFileCopy":true,"davFileMove":true,"davFileLock":true,"davFileAcl":true,"davCardPropFind":true,"davCardPropPatch":true,"davCardGet":true,"davCardMkCol":true,"davCardDelete":true,"davCardPut":true,"davCardCopy":true,"davCardMove":true,"davCardLock":true,"davCardAcl":true,"davCardQuery":true,"davCardMultiGet":true,"davCalPropFind":true,"davCalPropPatch":true,"davCalGet":true,"davCalMkCol":true,"davCalDelete":true,"davCalPut":true,"davCalCopy":true,"davCalMove":true,"davCalLock":true,"davCalAcl":true,"davCalQuery":true,"davCalMultiGet":true,"davCalFreeBusyQuery":true,"sysAccountSettingsGet":true,"sysAccountSettingsUpdate":true,"sysArchivedItemGet":true,"sysArchivedItemCreate":true,"sysArchivedItemUpdate":true,"sysArchivedItemDestroy":true,"sysArchivedItemQuery":true,"sysMaskedEmailGet":true,"sysMaskedEmailCreate":true,"sysMaskedEmailUpdate":true,"sysMaskedEmailDestroy":true,"sysMaskedEmailQuery":true,"sysPublicKeyGet":true,"sysPublicKeyCreate":true,"sysPublicKeyUpdate":true,"sysPublicKeyDestroy":true,"sysPublicKeyQuery":true,"sysSpamTrainingSampleGet":true,"sysSpamTrainingSampleUpdate":true,"sysSpamTrainingSampleDestroy":true,"sysSpamTrainingSampleQuery":true,"jmapFileNodeCopy":true},"description":"Group","roleIds":{}},"role-b":{"disabledPermissions":{},"memberTenantId":null,"enabledPermissions":{"authenticate":true,"authenticateWithAlias":true,"interactAi":true,"emailSend":true,"emailReceive":true,"calendarAlarmsSend":true,"calendarSchedulingSend":true,"calendarSchedulingReceive":true,"jmapPushSubscriptionGet":true,"jmapPushSubscriptionCreate":true,"jmapPushSubscriptionUpdate":true,"jmapPushSubscriptionDestroy":true,"jmapMailboxGet":true,"jmapMailboxChanges":true,"jmapMailboxQuery":true,"jmapMailboxQueryChanges":true,"jmapMailboxCreate":true,"jmapMailboxUpdate":true,"jmapMailboxDestroy":true,"jmapThreadGet":true,"jmapThreadChanges":true,"jmapEmailGet":true,"jmapEmailChanges":true,"jmapEmailQuery":true,"jmapEmailQueryChanges":true,"jmapEmailCreate":true,"jmapEmailUpdate":true,"jmapEmailDestroy":true,"jmapEmailCopy":true,"jmapEmailImport":true,"jmapEmailParse":true,"jmapSearchSnippetGet":true,"jmapIdentityGet":true,"jmapIdentityChanges":true,"jmapIdentityCreate":true,"jmapIdentityUpdate":true,"jmapIdentityDestroy":true,"jmapEmailSubmissionGet":true,"jmapEmailSubmissionChanges":true,"jmapEmailSubmissionQuery":true,"jmapEmailSubmissionQueryChanges":true,"jmapEmailSubmissionCreate":true,"jmapEmailSubmissionUpdate":true,"jmapEmailSubmissionDestroy":true,"jmapVacationResponseGet":true,"jmapVacationResponseCreate":true,"jmapVacationResponseUpdate":true,"jmapVacationResponseDestroy":true,"jmapSieveScriptGet":true,"jmapSieveScriptQuery":true,"jmapSieveScriptValidate":true,"jmapSieveScriptCreate":true,"jmapSieveScriptUpdate":true,"jmapSieveScriptDestroy":true,"jmapPrincipalGet":true,"jmapPrincipalQuery":true,"jmapPrincipalChanges":true,"jmapPrincipalQueryChanges":true,"jmapPrincipalGetAvailability":true,"jmapPrincipalCreate":true,"jmapPrincipalUpdate":true,"jmapPrincipalDestroy":true,"jmapQuotaGet":true,"jmapQuotaChanges":true,"jmapQuotaQuery":true,"jmapQuotaQueryChanges":true,"jmapBlobGet":true,"jmapBlobCopy":true,"jmapBlobLookup":true,"jmapBlobUpload":true,"jmapAddressBookGet":true,"jmapAddressBookChanges":true,"jmapAddressBookCreate":true,"jmapAddressBookUpdate":true,"jmapAddressBookDestroy":true,"jmapContactCardGet":true,"jmapContactCardChanges":true,"jmapContactCardQuery":true,"jmapContactCardQueryChanges":true,"jmapContactCardCreate":true,"jmapContactCardUpdate":true,"jmapContactCardDestroy":true,"jmapContactCardCopy":true,"jmapContactCardParse":true,"jmapFileNodeGet":true,"jmapFileNodeChanges":true,"jmapFileNodeQuery":true,"jmapFileNodeQueryChanges":true,"jmapFileNodeCreate":true,"jmapFileNodeUpdate":true,"jmapFileNodeDestroy":true,"jmapShareNotificationGet":true,"jmapShareNotificationChanges":true,"jmapShareNotificationQuery":true,"jmapShareNotificationQueryChanges":true,"jmapShareNotificationCreate":true,"jmapShareNotificationUpdate":true,"jmapShareNotificationDestroy":true,"jmapCalendarGet":true,"jmapCalendarChanges":true,"jmapCalendarCreate":true,"jmapCalendarUpdate":true,"jmapCalendarDestroy":true,"jmapCalendarEventGet":true,"jmapCalendarEventChanges":true,"jmapCalendarEventQuery":true,"jmapCalendarEventQueryChanges":true,"jmapCalendarEventCreate":true,"jmapCalendarEventUpdate":true,"jmapCalendarEventDestroy":true,"jmapCalendarEventCopy":true,"jmapCalendarEventParse":true,"jmapCalendarEventNotificationGet":true,"jmapCalendarEventNotificationChanges":true,"jmapCalendarEventNotificationQuery":true,"jmapCalendarEventNotificationQueryChanges":true,"jmapCalendarEventNotificationCreate":true,"jmapCalendarEventNotificationUpdate":true,"jmapCalendarEventNotificationDestroy":true,"jmapParticipantIdentityGet":true,"jmapParticipantIdentityChanges":true,"jmapParticipantIdentityCreate":true,"jmapParticipantIdentityUpdate":true,"jmapParticipantIdentityDestroy":true,"jmapCoreEcho":true,"imapAuthenticate":true,"imapAclGet":true,"imapAclSet":true,"imapMyRights":true,"imapListRights":true,"imapAppend":true,"imapCapability":true,"imapId":true,"imapCopy":true,"imapMove":true,"imapCreate":true,"imapDelete":true,"imapEnable":true,"imapExpunge":true,"imapFetch":true,"imapIdle":true,"imapList":true,"imapLsub":true,"imapNamespace":true,"imapRename":true,"imapSearch":true,"imapSort":true,"imapSelect":true,"imapExamine":true,"imapStatus":true,"imapStore":true,"imapSubscribe":true,"imapThread":true,"pop3Authenticate":true,"pop3List":true,"pop3Uidl":true,"pop3Stat":true,"pop3Retr":true,"pop3Dele":true,"sieveAuthenticate":true,"sieveListScripts":true,"sieveSetActive":true,"sieveGetScript":true,"sievePutScript":true,"sieveDeleteScript":true,"sieveRenameScript":true,"sieveCheckScript":true,"sieveHaveSpace":true,"davSyncCollection":true,"davExpandProperty":true,"davPrincipalAcl":true,"davPrincipalList":true,"davPrincipalMatch":true,"davPrincipalSearch":true,"davPrincipalSearchPropSet":true,"davFilePropFind":true,"davFilePropPatch":true,"davFileGet":true,"davFileMkCol":true,"davFileDelete":true,"davFilePut":true,"davFileCopy":true,"davFileMove":true,"davFileLock":true,"davFileAcl":true,"davCardPropFind":true,"davCardPropPatch":true,"davCardGet":true,"davCardMkCol":true,"davCardDelete":true,"davCardPut":true,"davCardCopy":true,"davCardMove":true,"davCardLock":true,"davCardAcl":true,"davCardQuery":true,"davCardMultiGet":true,"davCalPropFind":true,"davCalPropPatch":true,"davCalGet":true,"davCalMkCol":true,"davCalDelete":true,"davCalPut":true,"davCalCopy":true,"davCalMove":true,"davCalLock":true,"davCalAcl":true,"davCalQuery":true,"davCalMultiGet":true,"davCalFreeBusyQuery":true,"sysAccountPasswordGet":true,"sysAccountPasswordUpdate":true,"sysAccountSettingsGet":true,"sysAccountSettingsUpdate":true,"sysApiKeyGet":true,"sysApiKeyCreate":true,"sysApiKeyUpdate":true,"sysApiKeyDestroy":true,"sysApiKeyQuery":true,"sysAppPasswordGet":true,"sysAppPasswordCreate":true,"sysAppPasswordUpdate":true,"sysAppPasswordDestroy":true,"sysAppPasswordQuery":true,"sysArchivedItemGet":true,"sysArchivedItemCreate":true,"sysArchivedItemUpdate":true,"sysArchivedItemDestroy":true,"sysArchivedItemQuery":true,"sysMaskedEmailGet":true,"sysMaskedEmailCreate":true,"sysMaskedEmailUpdate":true,"sysMaskedEmailDestroy":true,"sysMaskedEmailQuery":true,"sysPublicKeyGet":true,"sysPublicKeyCreate":true,"sysPublicKeyUpdate":true,"sysPublicKeyDestroy":true,"sysPublicKeyQuery":true,"sysSpamTrainingSampleGet":true,"sysSpamTrainingSampleUpdate":true,"sysSpamTrainingSampleDestroy":true,"sysSpamTrainingSampleQuery":true,"jmapFileNodeCopy":true},"description":"User","roleIds":{}}}}`,
- `{
- "@type": "create",
- "object": "Domain",
- "value": {
- "domain-b": {
- "aliases": {},
- "catchAllAddress": null,
- "certificateManagement": {
- "@type": "Manual"
- },
- "dkimManagement": {
- "@type": "Manual"
- },
- "subAddressing": {
- "@type": "Enabled"
- },
- "logo": null,
- "isEnabled": true,
- "memberTenantId": null,
- "description": null,
- "name": "<<.domain>>",
- "directoryId": null,
- "allowRelaying": false,
- "dnsManagement": {
- "@type": "Manual"
- },
- "reportAddressUri": "mailto:postmaster"
- }
- }
- }`,
- `{
- "@type": "create",
- "object": "Account",
- "value": {
- "account-d": {
- "@type": "User",
- "name": "<<.masterusername>>",
- "memberGroupIds": {},
- "locale": "en_US",
- "quotas": {},
- "aliases": {},
- "domainId": "#domain-b",
- "memberTenantId": null,
- "permissions": {
- "@type": "Merge",
- "enabledPermissions": {
- "impersonate": true
- },
- "disabledPermissions": {}
- },
- "encryptionAtRest": {
- "@type": "Disabled"
- },
- "credentials": {
- "0": {
- "@type": "Password",
- "expiresAt": null,
- "secret": "<<.masterpassword>>",
- "allowedIps": {}
- }
- },
- "description": "Master",
- "timeZone": null,
- "roles": {
- "@type": "User"
- }
- },
- "account-b": {
- "@type": "User",
- "name": "<<.adminusername>>",
- "memberGroupIds": {},
- "locale": "en_US",
- "quotas": {},
- "aliases": {},
- "domainId": "#domain-b",
- "memberTenantId": null,
- "permissions": {
- "@type": "Inherit"
- },
- "encryptionAtRest": {
- "@type": "Disabled"
- },
- "credentials": {
- "0": {
- "@type": "Password",
- "expiresAt": null,
- "secret": "<<.adminpassword>>",
- "allowedIps": {}
- }
- },
- "description": "System administrator",
- "timeZone": null,
- "roles": {
- "@type": "Admin"
- }
- }
- }
- }`,
- `{"@type":"update","object":"Authentication","value":{"defaultGroupRoleIds":{"#role-c":true},"defaultAdminRoleIds":{"#role-e":true,"#role-b":true},"defaultUserRoleIds":{"#role-b":true},"maxAppPasswords":5,"defaultTenantRoleIds":{"#role-d":true,"#role-b":true},"directoryId":null,"passwordMinLength":1,"passwordDefaultExpiry":null,"passwordHashAlgorithm":"argon2id","passwordMinStrength":"zero","passwordMaxLength":128,"maxApiKeys":5}}`,
- `{"@type":"update","object":"Sharing","value":{"allowDirectoryQueries":<<.dirquery>>,"maxShares":10}}`,
- `{
- "@type": "update",
- "object": "SystemSettings",
- "value": {
- "defaultHostname": "<<.hostname>>",
- "threadPoolSize": null,
- "defaultCertificateId": null,
- "proxyTrustedNetworks": {},
- "defaultDomainId": "#domain-b",
- "services": {
- "caldav": {
- "hostname": null,
- "cleartext": false
- },
- "carddav": {
- "hostname": null,
- "cleartext": false
- },
- "imap": {
- "hostname": null,
- "cleartext": false
- },
- "jmap": {
- "hostname": null,
- "cleartext": false
- },
- "managesieve": {
- "hostname": null,
- "cleartext": false
- },
- "pop3": {
- "hostname": null,
- "cleartext": false
- },
- "smtp": {
- "hostname": null,
- "cleartext": false
- },
- "webdav": {
- "hostname": null,
- "cleartext": false
- }
- },
- "mailExchangers": {
- "0": {
- "priority": 10,
- "hostname": null
- }
- },
- "maxConnections": 8192,
- "providerInfo": {}
- }
- }`,
- `{"@type":"update","object":"DataRetention","value":{"archiveDeletedAccountsFor":null,"blobCleanupSchedule":{"@type":"Daily","hour":4,"minute":0},"expungeSchedule":{"@type":"Daily","hour":0,"minute":0},"dataCleanupSchedule":{"@type":"Daily","hour":2,"minute":0},"expungeTrashAfter":2592000000,"holdMtaReportsFor":2592000000,"metricsCollectionInterval":{"@type":"Hourly","minute":0},"holdMetricsFor":7776000000,"holdTracesFor":2592000000,"expungeShareNotifyAfter":2592000000,"expungeSchedulingInboxAfter":2592000000,"maxChangesHistory":10000,"expungeSubmissionsAfter":259200000,"archiveDeletedItemsFor":null}}`,
- `{"@type":"update","object":"BlobStore","value":{"@type":"Default"}}`,
- `{"@type":"update","object":"InMemoryStore","value":{"@type":"Default"}}`,
- `{"@type":"update","object":"SearchStore","value":{"@type":"Default"}}`,
- }
+ // Stalwart configuration to import using the CLI.
+ //
+ // The snapshot is obtained from a running Stalwart container using the following command:
+ //
+ // # First, copy the bootstrap configuration from the `stalwartConfig` variable above into a file:
+ // echo '{"@type":"RocksDb","path":"/var/lib/stalwart/","blobSize":16834,"bufferSize":134217728,"poolWorkers":null}' > config.json
+ //
+ // # Next, run Stalwart in recovery mode, mounting that configuration
+ // docker run -ti --rm -p 8080:8080 \
+ // -v ./config.json:/etc/stalwart/config.json \
+ // -e STALWART_RECOVERY_MODE=1 -e STALWART_RECOVERY_ADMIN=admin:secret \
+ // "stalwartlabs/stalwart:v0.16.7-alpine"
+ //
+ // # Point your browser to http://localhost:8080/admin and make the necessary
+ // # configuration change after authenticating as username "admin" with the password "secret"
+ // xdg-open http://localhost:8080/admin
+ //
+ // # When done modifying the configuration, use the `stalwart-cli` command-line tool to
+ // # create a snapshot JSON file, as follows:
+ // stalwart-cli --url http://localhost:8080 --user admin --password secret snapshot --output ./snapshot.json \
+ // Tenant Domain Directory DkimSignature AcmeProvider Certificate DnsServer Role Authentication Account \
+ // NetworkListener Tracer Sharing SystemSettings DataRetention BlobStore InMemoryStore SearchStore \
+ // --include-secrets --allow-unresolved PublicKey
+ //
+ // # Lastly, copy/paste the content of `./snapshot.json` into the following variable as-is:
+ dumpTemplate = `
+{"@type":"destroy","object":"Tracer"}
+{"@type":"destroy","object":"NetworkListener"}
+{"@type":"destroy","object":"DnsServer"}
+{"@type":"destroy","object":"Certificate"}
+{"@type":"destroy","object":"Tenant"}
+{"@type":"destroy","object":"Role"}
+{"@type":"destroy","object":"AcmeProvider"}
+{"@type":"destroy","object":"Directory"}
+{"@type":"destroy","object":"Domain"}
+{"@type":"destroy","object":"Account"}
+{"@type":"destroy","object":"DkimSignature"}
+{"@type":"create","object":"Tracer","value":{"tracer-iugg9zgwaaaa":{"@type":"Stdout","eventsPolicy":"exclude","ansi":false,"lossy":false,"multiline":false,"events":{},"buffered":true,"enable":true,"level":"trace"}}}
+{"@type":"create","object":"NetworkListener","value":{"networklistener-iughfwjyahqb":{"maxConnections":8192,"socketSendBufferSize":null,"protocol":"http","tlsIgnoreClientOrder":true,"socketReuseAddress":true,"socketBacklog":1024,"name":"http","socketNoDelay":true,"socketTosV4":null,"tlsDisableCipherSuites":{},"tlsTimeout":60000,"overrideProxyTrustedNetworks":{},"socketReusePort":true,"useTls":true,"socketReceiveBufferSize":null,"socketTtl":null,"tlsImplicit":false,"bind":{"[::]:8080":true},"tlsDisableProtocols":{}},"networklistener-iughfwjyahab":{"maxConnections":8192,"socketSendBufferSize":null,"protocol":"http","tlsIgnoreClientOrder":true,"socketReuseAddress":true,"socketBacklog":1024,"name":"https","socketNoDelay":true,"socketTosV4":null,"tlsDisableCipherSuites":{},"tlsTimeout":60000,"overrideProxyTrustedNetworks":{},"socketReusePort":true,"useTls":true,"socketReceiveBufferSize":null,"socketTtl":null,"tlsImplicit":true,"bind":{"[::]:443":true},"tlsDisableProtocols":{}},"networklistener-iughfwjyagqb":{"maxConnections":8192,"socketSendBufferSize":null,"protocol":"manageSieve","tlsIgnoreClientOrder":true,"socketReuseAddress":true,"socketBacklog":1024,"name":"sieve","socketNoDelay":true,"socketTosV4":null,"tlsDisableCipherSuites":{},"tlsTimeout":60000,"overrideProxyTrustedNetworks":{},"socketReusePort":true,"useTls":true,"socketReceiveBufferSize":null,"socketTtl":null,"tlsImplicit":false,"bind":{"[::]:4190":true},"tlsDisableProtocols":{}},"networklistener-iughfwjyagab":{"maxConnections":8192,"socketSendBufferSize":null,"protocol":"pop3","tlsIgnoreClientOrder":true,"socketReuseAddress":true,"socketBacklog":1024,"name":"pop3s","socketNoDelay":true,"socketTosV4":null,"tlsDisableCipherSuites":{},"tlsTimeout":60000,"overrideProxyTrustedNetworks":{},"socketReusePort":true,"useTls":true,"socketReceiveBufferSize":null,"socketTtl":null,"tlsImplicit":true,"bind":{"[::]:995":true},"tlsDisableProtocols":{}},"networklistener-iughfwjyafqb":{"maxConnections":8192,"socketSendBufferSize":null,"protocol":"imap","tlsIgnoreClientOrder":true,"socketReuseAddress":true,"socketBacklog":1024,"name":"imaps","socketNoDelay":true,"socketTosV4":null,"tlsDisableCipherSuites":{},"tlsTimeout":60000,"overrideProxyTrustedNetworks":{},"socketReusePort":true,"useTls":true,"socketReceiveBufferSize":null,"socketTtl":null,"tlsImplicit":true,"bind":{"[::]:993":true},"tlsDisableProtocols":{}},"networklistener-iughfwjwafab":{"maxConnections":8192,"socketSendBufferSize":null,"protocol":"smtp","tlsIgnoreClientOrder":true,"socketReuseAddress":true,"socketBacklog":1024,"name":"submissions","socketNoDelay":true,"socketTosV4":null,"tlsDisableCipherSuites":{},"tlsTimeout":60000,"overrideProxyTrustedNetworks":{},"socketReusePort":true,"useTls":true,"socketReceiveBufferSize":null,"socketTtl":null,"tlsImplicit":true,"bind":{"[::]:465":true},"tlsDisableProtocols":{}},"networklistener-iughfwjwaeqb":{"maxConnections":8192,"socketSendBufferSize":null,"protocol":"smtp","tlsIgnoreClientOrder":true,"socketReuseAddress":true,"socketBacklog":1024,"name":"smtp","socketNoDelay":true,"socketTosV4":null,"tlsDisableCipherSuites":{},"tlsTimeout":60000,"overrideProxyTrustedNetworks":{},"socketReusePort":true,"useTls":true,"socketReceiveBufferSize":null,"socketTtl":null,"tlsImplicit":false,"bind":{"[::]:25":true},"tlsDisableProtocols":{}}}}
+{"@type":"create","object":"Role","value":{"role-e":{"memberTenantId":null,"roleIds":{},"description":"System Administrator","enabledPermissions":{"authenticate":true,"authenticateWithAlias":true,"interactAi":true,"impersonate":true,"unlimitedRequests":true,"unlimitedUploads":true,"fetchAnyBlob":true,"oAuthClientRegistration":true,"oAuthClientOverride":true,"liveTracing":true,"liveMetrics":true,"liveDeliveryTest":true,"sysAccountGet":true,"sysAccountCreate":true,"sysAccountUpdate":true,"sysAccountDestroy":true,"sysAccountQuery":true,"sysAccountPasswordGet":true,"sysAccountPasswordUpdate":true,"sysAccountSettingsGet":true,"sysAccountSettingsUpdate":true,"sysAcmeProviderGet":true,"sysAcmeProviderCreate":true,"sysAcmeProviderUpdate":true,"sysAcmeProviderDestroy":true,"sysAcmeProviderQuery":true,"actionReloadSettings":true,"actionReloadTlsCertificates":true,"actionReloadLookupStores":true,"actionReloadBlockedIps":true,"actionUpdateApps":true,"actionTroubleshootDmarc":true,"actionClassifySpam":true,"actionInvalidateCaches":true,"actionInvalidateNegativeCaches":true,"actionPauseMtaQueue":true,"actionResumeMtaQueue":true,"sysActionGet":true,"sysActionCreate":true,"sysActionUpdate":true,"sysActionDestroy":true,"sysActionQuery":true,"sysAddressBookGet":true,"sysAddressBookUpdate":true,"sysAiModelGet":true,"sysAiModelCreate":true,"sysAiModelUpdate":true,"sysAiModelDestroy":true,"sysAiModelQuery":true,"sysAlertGet":true,"sysAlertCreate":true,"sysAlertUpdate":true,"sysAlertDestroy":true,"sysAlertQuery":true,"sysAllowedIpGet":true,"sysAllowedIpCreate":true,"sysAllowedIpUpdate":true,"sysAllowedIpDestroy":true,"sysAllowedIpQuery":true,"sysApiKeyGet":true,"sysApiKeyCreate":true,"sysApiKeyUpdate":true,"sysApiKeyDestroy":true,"sysApiKeyQuery":true,"sysAppPasswordGet":true,"sysAppPasswordCreate":true,"sysAppPasswordUpdate":true,"sysAppPasswordDestroy":true,"sysAppPasswordQuery":true,"sysApplicationGet":true,"sysApplicationCreate":true,"sysApplicationUpdate":true,"sysApplicationDestroy":true,"sysApplicationQuery":true,"sysArchivedItemGet":true,"sysArchivedItemCreate":true,"sysArchivedItemUpdate":true,"sysArchivedItemDestroy":true,"sysArchivedItemQuery":true,"sysArfExternalReportGet":true,"sysArfExternalReportCreate":true,"sysArfExternalReportUpdate":true,"sysArfExternalReportDestroy":true,"sysArfExternalReportQuery":true,"sysAsnGet":true,"sysAsnUpdate":true,"sysAuthenticationGet":true,"sysAuthenticationUpdate":true,"sysBlobStoreGet":true,"sysBlobStoreUpdate":true,"sysBlockedIpGet":true,"sysBlockedIpCreate":true,"sysBlockedIpUpdate":true,"sysBlockedIpDestroy":true,"sysBlockedIpQuery":true,"sysBootstrapGet":true,"sysBootstrapUpdate":true,"sysCacheGet":true,"sysCacheUpdate":true,"sysCalendarGet":true,"sysCalendarUpdate":true,"sysCalendarAlarmGet":true,"sysCalendarAlarmUpdate":true,"sysCalendarSchedulingGet":true,"sysCalendarSchedulingUpdate":true,"sysCertificateGet":true,"sysCertificateCreate":true,"sysCertificateUpdate":true,"sysCertificateDestroy":true,"sysCertificateQuery":true,"sysClusterNodeGet":true,"sysClusterNodeCreate":true,"sysClusterNodeUpdate":true,"sysClusterNodeDestroy":true,"sysClusterNodeQuery":true,"sysClusterRoleGet":true,"sysClusterRoleCreate":true,"sysClusterRoleUpdate":true,"sysClusterRoleDestroy":true,"sysClusterRoleQuery":true,"sysCoordinatorGet":true,"sysCoordinatorUpdate":true,"sysDataRetentionGet":true,"sysDataRetentionUpdate":true,"sysDataStoreGet":true,"sysDataStoreUpdate":true,"sysDirectoryGet":true,"sysDirectoryCreate":true,"sysDirectoryUpdate":true,"sysDirectoryDestroy":true,"sysDirectoryQuery":true,"sysDkimReportSettingsGet":true,"sysDkimReportSettingsUpdate":true,"sysDkimSignatureGet":true,"sysDkimSignatureCreate":true,"sysDkimSignatureUpdate":true,"sysDkimSignatureDestroy":true,"sysDkimSignatureQuery":true,"sysDmarcExternalReportGet":true,"sysDmarcExternalReportCreate":true,"sysDmarcExternalReportUpdate":true,"sysDmarcExternalReportDestroy":true,"sysDmarcExternalReportQuery":true,"sysDmarcInternalReportGet":true,"sysDmarcInternalReportCreate":true,"sysDmarcInternalReportUpdate":true,"sysDmarcInternalReportDestroy":true,"sysDmarcInternalReportQuery":true,"sysDmarcReportSettingsGet":true,"sysDmarcReportSettingsUpdate":true,"sysDnsResolverGet":true,"sysDnsResolverUpdate":true,"sysDnsServerGet":true,"sysDnsServerCreate":true,"sysDnsServerUpdate":true,"sysDnsServerDestroy":true,"sysDnsServerQuery":true,"sysDomainGet":true,"sysDomainCreate":true,"sysDomainUpdate":true,"sysDomainDestroy":true,"sysDomainQuery":true,"sysDsnReportSettingsGet":true,"sysDsnReportSettingsUpdate":true,"sysEmailGet":true,"sysEmailUpdate":true,"sysEnterpriseGet":true,"sysEnterpriseUpdate":true,"sysEventTracingLevelGet":true,"sysEventTracingLevelCreate":true,"sysEventTracingLevelUpdate":true,"sysEventTracingLevelDestroy":true,"sysEventTracingLevelQuery":true,"sysFileStorageGet":true,"sysFileStorageUpdate":true,"sysHttpGet":true,"sysHttpUpdate":true,"sysHttpFormGet":true,"sysHttpFormUpdate":true,"sysHttpLookupGet":true,"sysHttpLookupCreate":true,"sysHttpLookupUpdate":true,"sysHttpLookupDestroy":true,"sysHttpLookupQuery":true,"sysImapGet":true,"sysImapUpdate":true,"sysInMemoryStoreGet":true,"sysInMemoryStoreUpdate":true,"sysJmapGet":true,"sysJmapUpdate":true,"sysLogGet":true,"sysLogCreate":true,"sysLogUpdate":true,"sysLogDestroy":true,"sysLogQuery":true,"sysMailingListGet":true,"sysMailingListCreate":true,"sysMailingListUpdate":true,"sysMailingListDestroy":true,"sysMailingListQuery":true,"sysMaskedEmailGet":true,"sysMaskedEmailCreate":true,"sysMaskedEmailUpdate":true,"sysMaskedEmailDestroy":true,"sysMaskedEmailQuery":true,"sysMemoryLookupKeyGet":true,"sysMemoryLookupKeyCreate":true,"sysMemoryLookupKeyUpdate":true,"sysMemoryLookupKeyDestroy":true,"sysMemoryLookupKeyQuery":true,"sysMemoryLookupKeyValueGet":true,"sysMemoryLookupKeyValueCreate":true,"sysMemoryLookupKeyValueUpdate":true,"sysMemoryLookupKeyValueDestroy":true,"sysMemoryLookupKeyValueQuery":true,"sysMetricGet":true,"sysMetricCreate":true,"sysMetricUpdate":true,"sysMetricDestroy":true,"sysMetricQuery":true,"sysMetricsGet":true,"sysMetricsUpdate":true,"sysMetricsStoreGet":true,"sysMetricsStoreUpdate":true,"sysMtaConnectionStrategyGet":true,"sysMtaConnectionStrategyCreate":true,"sysMtaConnectionStrategyUpdate":true,"sysMtaConnectionStrategyDestroy":true,"sysMtaConnectionStrategyQuery":true,"sysMtaDeliveryScheduleGet":true,"sysMtaDeliveryScheduleCreate":true,"sysMtaDeliveryScheduleUpdate":true,"sysMtaDeliveryScheduleDestroy":true,"sysMtaDeliveryScheduleQuery":true,"sysMtaExtensionsGet":true,"sysMtaExtensionsUpdate":true,"sysMtaHookGet":true,"sysMtaHookCreate":true,"sysMtaHookUpdate":true,"sysMtaHookDestroy":true,"sysMtaHookQuery":true,"sysMtaInboundSessionGet":true,"sysMtaInboundSessionUpdate":true,"sysMtaInboundThrottleGet":true,"sysMtaInboundThrottleCreate":true,"sysMtaInboundThrottleUpdate":true,"sysMtaInboundThrottleDestroy":true,"sysMtaInboundThrottleQuery":true,"sysMtaMilterGet":true,"sysMtaMilterCreate":true,"sysMtaMilterUpdate":true,"sysMtaMilterDestroy":true,"sysMtaMilterQuery":true,"sysMtaOutboundStrategyGet":true,"sysMtaOutboundStrategyUpdate":true,"sysMtaOutboundThrottleGet":true,"sysMtaOutboundThrottleCreate":true,"sysMtaOutboundThrottleUpdate":true,"sysMtaOutboundThrottleDestroy":true,"sysMtaOutboundThrottleQuery":true,"sysMtaQueueQuotaGet":true,"sysMtaQueueQuotaCreate":true,"sysMtaQueueQuotaUpdate":true,"sysMtaQueueQuotaDestroy":true,"sysMtaQueueQuotaQuery":true,"sysMtaRouteGet":true,"sysMtaRouteCreate":true,"sysMtaRouteUpdate":true,"sysMtaRouteDestroy":true,"sysMtaRouteQuery":true,"sysMtaStageAuthGet":true,"sysMtaStageAuthUpdate":true,"sysMtaStageConnectGet":true,"sysMtaStageConnectUpdate":true,"sysMtaStageDataGet":true,"sysMtaStageDataUpdate":true,"sysMtaStageEhloGet":true,"sysMtaStageEhloUpdate":true,"sysMtaStageMailGet":true,"sysMtaStageMailUpdate":true,"sysMtaStageRcptGet":true,"sysMtaStageRcptUpdate":true,"sysMtaStsGet":true,"sysMtaStsUpdate":true,"sysMtaTlsStrategyGet":true,"sysMtaTlsStrategyCreate":true,"sysMtaTlsStrategyUpdate":true,"sysMtaTlsStrategyDestroy":true,"sysMtaTlsStrategyQuery":true,"sysMtaVirtualQueueGet":true,"sysMtaVirtualQueueCreate":true,"sysMtaVirtualQueueUpdate":true,"sysMtaVirtualQueueDestroy":true,"sysMtaVirtualQueueQuery":true,"sysNetworkListenerGet":true,"sysNetworkListenerCreate":true,"sysNetworkListenerUpdate":true,"sysNetworkListenerDestroy":true,"sysNetworkListenerQuery":true,"sysOAuthClientGet":true,"sysOAuthClientCreate":true,"sysOAuthClientUpdate":true,"sysOAuthClientDestroy":true,"sysOAuthClientQuery":true,"sysOidcProviderGet":true,"sysOidcProviderUpdate":true,"sysPublicKeyGet":true,"sysPublicKeyCreate":true,"sysPublicKeyUpdate":true,"sysPublicKeyDestroy":true,"sysPublicKeyQuery":true,"sysQueuedMessageGet":true,"sysQueuedMessageCreate":true,"sysQueuedMessageUpdate":true,"sysQueuedMessageDestroy":true,"sysQueuedMessageQuery":true,"sysReportSettingsGet":true,"sysReportSettingsUpdate":true,"sysRoleGet":true,"sysRoleCreate":true,"sysRoleUpdate":true,"sysRoleDestroy":true,"sysRoleQuery":true,"sysSearchGet":true,"sysSearchUpdate":true,"sysSearchStoreGet":true,"sysSearchStoreUpdate":true,"sysSecurityGet":true,"sysSecurityUpdate":true,"sysSenderAuthGet":true,"sysSenderAuthUpdate":true,"sysSharingGet":true,"sysSharingUpdate":true,"sysSieveSystemInterpreterGet":true,"sysSieveSystemInterpreterUpdate":true,"sysSieveSystemScriptGet":true,"sysSieveSystemScriptCreate":true,"sysSieveSystemScriptUpdate":true,"sysSieveSystemScriptDestroy":true,"sysSieveSystemScriptQuery":true,"sysSieveUserInterpreterGet":true,"sysSieveUserInterpreterUpdate":true,"sysSieveUserScriptGet":true,"sysSieveUserScriptCreate":true,"sysSieveUserScriptUpdate":true,"sysSieveUserScriptDestroy":true,"sysSieveUserScriptQuery":true,"sysSpamClassifierGet":true,"sysSpamClassifierUpdate":true,"sysSpamDnsblServerGet":true,"sysSpamDnsblServerCreate":true,"sysSpamDnsblServerUpdate":true,"sysSpamDnsblServerDestroy":true,"sysSpamDnsblServerQuery":true,"sysSpamDnsblSettingsGet":true,"sysSpamDnsblSettingsUpdate":true,"sysSpamFileExtensionGet":true,"sysSpamFileExtensionCreate":true,"sysSpamFileExtensionUpdate":true,"sysSpamFileExtensionDestroy":true,"sysSpamFileExtensionQuery":true,"sysSpamLlmGet":true,"sysSpamLlmUpdate":true,"sysSpamPyzorGet":true,"sysSpamPyzorUpdate":true,"sysSpamRuleGet":true,"sysSpamRuleCreate":true,"sysSpamRuleUpdate":true,"sysSpamRuleDestroy":true,"sysSpamRuleQuery":true,"sysSpamSettingsGet":true,"sysSpamSettingsUpdate":true,"sysSpamTagGet":true,"sysSpamTagCreate":true,"sysSpamTagUpdate":true,"sysSpamTagDestroy":true,"sysSpamTagQuery":true,"sysSpamTrainingSampleGet":true,"sysSpamTrainingSampleCreate":true,"sysSpamTrainingSampleUpdate":true,"sysSpamTrainingSampleDestroy":true,"sysSpamTrainingSampleQuery":true,"sysSpfReportSettingsGet":true,"sysSpfReportSettingsUpdate":true,"sysStoreLookupGet":true,"sysStoreLookupCreate":true,"sysStoreLookupUpdate":true,"sysStoreLookupDestroy":true,"sysStoreLookupQuery":true,"sysSystemSettingsGet":true,"sysSystemSettingsUpdate":true,"taskIndexDocument":true,"taskUnindexDocument":true,"taskIndexTrace":true,"taskCalendarAlarmEmail":true,"taskCalendarAlarmNotification":true,"taskCalendarItipMessage":true,"taskMergeThreads":true,"taskDmarcReport":true,"taskTlsReport":true,"taskRestoreArchivedItem":true,"taskDestroyAccount":true,"taskAccountMaintenance":true,"taskTenantMaintenance":true,"taskStoreMaintenance":true,"taskSpamFilterMaintenance":true,"taskAcmeRenewal":true,"taskDkimManagement":true,"taskDnsManagement":true,"sysTaskGet":true,"sysTaskCreate":true,"sysTaskUpdate":true,"sysTaskDestroy":true,"sysTaskQuery":true,"sysTaskManagerGet":true,"sysTaskManagerUpdate":true,"sysTenantGet":true,"sysTenantCreate":true,"sysTenantUpdate":true,"sysTenantDestroy":true,"sysTenantQuery":true,"sysTlsExternalReportGet":true,"sysTlsExternalReportCreate":true,"sysTlsExternalReportUpdate":true,"sysTlsExternalReportDestroy":true,"sysTlsExternalReportQuery":true,"sysTlsInternalReportGet":true,"sysTlsInternalReportCreate":true,"sysTlsInternalReportUpdate":true,"sysTlsInternalReportDestroy":true,"sysTlsInternalReportQuery":true,"sysTlsReportSettingsGet":true,"sysTlsReportSettingsUpdate":true,"sysTraceGet":true,"sysTraceCreate":true,"sysTraceUpdate":true,"sysTraceDestroy":true,"sysTraceQuery":true,"sysTracerGet":true,"sysTracerCreate":true,"sysTracerUpdate":true,"sysTracerDestroy":true,"sysTracerQuery":true,"sysTracingStoreGet":true,"sysTracingStoreUpdate":true,"sysWebDavGet":true,"sysWebDavUpdate":true,"sysWebHookGet":true,"sysWebHookCreate":true,"sysWebHookUpdate":true,"sysWebHookDestroy":true,"sysWebHookQuery":true},"disabledPermissions":{}},"role-d":{"memberTenantId":null,"roleIds":{},"description":"Tenant Administrator","enabledPermissions":{"authenticate":true,"authenticateWithAlias":true,"interactAi":true,"fetchAnyBlob":true,"liveDeliveryTest":true,"sysAccountGet":true,"sysAccountCreate":true,"sysAccountUpdate":true,"sysAccountDestroy":true,"sysAccountQuery":true,"sysAcmeProviderGet":true,"sysAcmeProviderCreate":true,"sysAcmeProviderUpdate":true,"sysAcmeProviderDestroy":true,"sysAcmeProviderQuery":true,"sysDkimSignatureGet":true,"sysDkimSignatureCreate":true,"sysDkimSignatureUpdate":true,"sysDkimSignatureDestroy":true,"sysDkimSignatureQuery":true,"sysDnsServerGet":true,"sysDnsServerCreate":true,"sysDnsServerUpdate":true,"sysDnsServerDestroy":true,"sysDnsServerQuery":true,"sysDomainGet":true,"sysDomainCreate":true,"sysDomainUpdate":true,"sysDomainDestroy":true,"sysDomainQuery":true,"sysMailingListGet":true,"sysMailingListCreate":true,"sysMailingListUpdate":true,"sysMailingListDestroy":true,"sysMailingListQuery":true,"sysOAuthClientGet":true,"sysOAuthClientCreate":true,"sysOAuthClientUpdate":true,"sysOAuthClientDestroy":true,"sysOAuthClientQuery":true,"sysQueuedMessageGet":true,"sysQueuedMessageCreate":true,"sysQueuedMessageUpdate":true,"sysQueuedMessageDestroy":true,"sysQueuedMessageQuery":true,"sysRoleGet":true,"sysRoleCreate":true,"sysRoleUpdate":true,"sysRoleDestroy":true,"sysRoleQuery":true},"disabledPermissions":{}},"role-c":{"memberTenantId":null,"roleIds":{},"description":"Group","enabledPermissions":{"emailSend":true,"emailReceive":true,"calendarAlarmsSend":true,"calendarSchedulingSend":true,"calendarSchedulingReceive":true,"jmapPushSubscriptionGet":true,"jmapPushSubscriptionCreate":true,"jmapPushSubscriptionUpdate":true,"jmapPushSubscriptionDestroy":true,"jmapMailboxGet":true,"jmapMailboxChanges":true,"jmapMailboxQuery":true,"jmapMailboxQueryChanges":true,"jmapMailboxCreate":true,"jmapMailboxUpdate":true,"jmapMailboxDestroy":true,"jmapThreadGet":true,"jmapThreadChanges":true,"jmapEmailGet":true,"jmapEmailChanges":true,"jmapEmailQuery":true,"jmapEmailQueryChanges":true,"jmapEmailCreate":true,"jmapEmailUpdate":true,"jmapEmailDestroy":true,"jmapEmailCopy":true,"jmapEmailImport":true,"jmapEmailParse":true,"jmapSearchSnippetGet":true,"jmapIdentityGet":true,"jmapIdentityChanges":true,"jmapIdentityCreate":true,"jmapIdentityUpdate":true,"jmapIdentityDestroy":true,"jmapEmailSubmissionGet":true,"jmapEmailSubmissionChanges":true,"jmapEmailSubmissionQuery":true,"jmapEmailSubmissionQueryChanges":true,"jmapEmailSubmissionCreate":true,"jmapEmailSubmissionUpdate":true,"jmapEmailSubmissionDestroy":true,"jmapVacationResponseGet":true,"jmapVacationResponseCreate":true,"jmapVacationResponseUpdate":true,"jmapVacationResponseDestroy":true,"jmapSieveScriptGet":true,"jmapSieveScriptQuery":true,"jmapSieveScriptValidate":true,"jmapSieveScriptCreate":true,"jmapSieveScriptUpdate":true,"jmapSieveScriptDestroy":true,"jmapPrincipalGet":true,"jmapPrincipalQuery":true,"jmapPrincipalChanges":true,"jmapPrincipalQueryChanges":true,"jmapPrincipalGetAvailability":true,"jmapPrincipalCreate":true,"jmapPrincipalUpdate":true,"jmapPrincipalDestroy":true,"jmapQuotaGet":true,"jmapQuotaChanges":true,"jmapQuotaQuery":true,"jmapQuotaQueryChanges":true,"jmapBlobGet":true,"jmapBlobCopy":true,"jmapBlobLookup":true,"jmapBlobUpload":true,"jmapAddressBookGet":true,"jmapAddressBookChanges":true,"jmapAddressBookCreate":true,"jmapAddressBookUpdate":true,"jmapAddressBookDestroy":true,"jmapContactCardGet":true,"jmapContactCardChanges":true,"jmapContactCardQuery":true,"jmapContactCardQueryChanges":true,"jmapContactCardCreate":true,"jmapContactCardUpdate":true,"jmapContactCardDestroy":true,"jmapContactCardCopy":true,"jmapContactCardParse":true,"jmapFileNodeGet":true,"jmapFileNodeChanges":true,"jmapFileNodeQuery":true,"jmapFileNodeQueryChanges":true,"jmapFileNodeCreate":true,"jmapFileNodeUpdate":true,"jmapFileNodeDestroy":true,"jmapShareNotificationGet":true,"jmapShareNotificationChanges":true,"jmapShareNotificationQuery":true,"jmapShareNotificationQueryChanges":true,"jmapShareNotificationCreate":true,"jmapShareNotificationUpdate":true,"jmapShareNotificationDestroy":true,"jmapCalendarGet":true,"jmapCalendarChanges":true,"jmapCalendarCreate":true,"jmapCalendarUpdate":true,"jmapCalendarDestroy":true,"jmapCalendarEventGet":true,"jmapCalendarEventChanges":true,"jmapCalendarEventQuery":true,"jmapCalendarEventQueryChanges":true,"jmapCalendarEventCreate":true,"jmapCalendarEventUpdate":true,"jmapCalendarEventDestroy":true,"jmapCalendarEventCopy":true,"jmapCalendarEventParse":true,"jmapCalendarEventNotificationGet":true,"jmapCalendarEventNotificationChanges":true,"jmapCalendarEventNotificationQuery":true,"jmapCalendarEventNotificationQueryChanges":true,"jmapCalendarEventNotificationCreate":true,"jmapCalendarEventNotificationUpdate":true,"jmapCalendarEventNotificationDestroy":true,"jmapParticipantIdentityGet":true,"jmapParticipantIdentityChanges":true,"jmapParticipantIdentityCreate":true,"jmapParticipantIdentityUpdate":true,"jmapParticipantIdentityDestroy":true,"jmapCoreEcho":true,"imapAuthenticate":true,"imapAclGet":true,"imapAclSet":true,"imapMyRights":true,"imapListRights":true,"imapAppend":true,"imapCapability":true,"imapId":true,"imapCopy":true,"imapMove":true,"imapCreate":true,"imapDelete":true,"imapEnable":true,"imapExpunge":true,"imapFetch":true,"imapIdle":true,"imapList":true,"imapLsub":true,"imapNamespace":true,"imapRename":true,"imapSearch":true,"imapSort":true,"imapSelect":true,"imapExamine":true,"imapStatus":true,"imapStore":true,"imapSubscribe":true,"imapThread":true,"pop3Authenticate":true,"pop3List":true,"pop3Uidl":true,"pop3Stat":true,"pop3Retr":true,"pop3Dele":true,"sieveAuthenticate":true,"sieveListScripts":true,"sieveSetActive":true,"sieveGetScript":true,"sievePutScript":true,"sieveDeleteScript":true,"sieveRenameScript":true,"sieveCheckScript":true,"sieveHaveSpace":true,"davSyncCollection":true,"davExpandProperty":true,"davPrincipalAcl":true,"davPrincipalList":true,"davPrincipalMatch":true,"davPrincipalSearch":true,"davPrincipalSearchPropSet":true,"davFilePropFind":true,"davFilePropPatch":true,"davFileGet":true,"davFileMkCol":true,"davFileDelete":true,"davFilePut":true,"davFileCopy":true,"davFileMove":true,"davFileLock":true,"davFileAcl":true,"davCardPropFind":true,"davCardPropPatch":true,"davCardGet":true,"davCardMkCol":true,"davCardDelete":true,"davCardPut":true,"davCardCopy":true,"davCardMove":true,"davCardLock":true,"davCardAcl":true,"davCardQuery":true,"davCardMultiGet":true,"davCalPropFind":true,"davCalPropPatch":true,"davCalGet":true,"davCalMkCol":true,"davCalDelete":true,"davCalPut":true,"davCalCopy":true,"davCalMove":true,"davCalLock":true,"davCalAcl":true,"davCalQuery":true,"davCalMultiGet":true,"davCalFreeBusyQuery":true,"sysAccountSettingsGet":true,"sysAccountSettingsUpdate":true,"sysArchivedItemGet":true,"sysArchivedItemCreate":true,"sysArchivedItemUpdate":true,"sysArchivedItemDestroy":true,"sysArchivedItemQuery":true,"sysMaskedEmailGet":true,"sysMaskedEmailCreate":true,"sysMaskedEmailUpdate":true,"sysMaskedEmailDestroy":true,"sysMaskedEmailQuery":true,"sysPublicKeyGet":true,"sysPublicKeyCreate":true,"sysPublicKeyUpdate":true,"sysPublicKeyDestroy":true,"sysPublicKeyQuery":true,"sysSpamTrainingSampleGet":true,"sysSpamTrainingSampleUpdate":true,"sysSpamTrainingSampleDestroy":true,"sysSpamTrainingSampleQuery":true,"jmapFileNodeCopy":true},"disabledPermissions":{}},"role-b":{"memberTenantId":null,"roleIds":{},"description":"User","enabledPermissions":{"authenticate":true,"authenticateWithAlias":true,"interactAi":true,"emailSend":true,"emailReceive":true,"calendarAlarmsSend":true,"calendarSchedulingSend":true,"calendarSchedulingReceive":true,"jmapPushSubscriptionGet":true,"jmapPushSubscriptionCreate":true,"jmapPushSubscriptionUpdate":true,"jmapPushSubscriptionDestroy":true,"jmapMailboxGet":true,"jmapMailboxChanges":true,"jmapMailboxQuery":true,"jmapMailboxQueryChanges":true,"jmapMailboxCreate":true,"jmapMailboxUpdate":true,"jmapMailboxDestroy":true,"jmapThreadGet":true,"jmapThreadChanges":true,"jmapEmailGet":true,"jmapEmailChanges":true,"jmapEmailQuery":true,"jmapEmailQueryChanges":true,"jmapEmailCreate":true,"jmapEmailUpdate":true,"jmapEmailDestroy":true,"jmapEmailCopy":true,"jmapEmailImport":true,"jmapEmailParse":true,"jmapSearchSnippetGet":true,"jmapIdentityGet":true,"jmapIdentityChanges":true,"jmapIdentityCreate":true,"jmapIdentityUpdate":true,"jmapIdentityDestroy":true,"jmapEmailSubmissionGet":true,"jmapEmailSubmissionChanges":true,"jmapEmailSubmissionQuery":true,"jmapEmailSubmissionQueryChanges":true,"jmapEmailSubmissionCreate":true,"jmapEmailSubmissionUpdate":true,"jmapEmailSubmissionDestroy":true,"jmapVacationResponseGet":true,"jmapVacationResponseCreate":true,"jmapVacationResponseUpdate":true,"jmapVacationResponseDestroy":true,"jmapSieveScriptGet":true,"jmapSieveScriptQuery":true,"jmapSieveScriptValidate":true,"jmapSieveScriptCreate":true,"jmapSieveScriptUpdate":true,"jmapSieveScriptDestroy":true,"jmapPrincipalGet":true,"jmapPrincipalQuery":true,"jmapPrincipalChanges":true,"jmapPrincipalQueryChanges":true,"jmapPrincipalGetAvailability":true,"jmapPrincipalCreate":true,"jmapPrincipalUpdate":true,"jmapPrincipalDestroy":true,"jmapQuotaGet":true,"jmapQuotaChanges":true,"jmapQuotaQuery":true,"jmapQuotaQueryChanges":true,"jmapBlobGet":true,"jmapBlobCopy":true,"jmapBlobLookup":true,"jmapBlobUpload":true,"jmapAddressBookGet":true,"jmapAddressBookChanges":true,"jmapAddressBookCreate":true,"jmapAddressBookUpdate":true,"jmapAddressBookDestroy":true,"jmapContactCardGet":true,"jmapContactCardChanges":true,"jmapContactCardQuery":true,"jmapContactCardQueryChanges":true,"jmapContactCardCreate":true,"jmapContactCardUpdate":true,"jmapContactCardDestroy":true,"jmapContactCardCopy":true,"jmapContactCardParse":true,"jmapFileNodeGet":true,"jmapFileNodeChanges":true,"jmapFileNodeQuery":true,"jmapFileNodeQueryChanges":true,"jmapFileNodeCreate":true,"jmapFileNodeUpdate":true,"jmapFileNodeDestroy":true,"jmapShareNotificationGet":true,"jmapShareNotificationChanges":true,"jmapShareNotificationQuery":true,"jmapShareNotificationQueryChanges":true,"jmapShareNotificationCreate":true,"jmapShareNotificationUpdate":true,"jmapShareNotificationDestroy":true,"jmapCalendarGet":true,"jmapCalendarChanges":true,"jmapCalendarCreate":true,"jmapCalendarUpdate":true,"jmapCalendarDestroy":true,"jmapCalendarEventGet":true,"jmapCalendarEventChanges":true,"jmapCalendarEventQuery":true,"jmapCalendarEventQueryChanges":true,"jmapCalendarEventCreate":true,"jmapCalendarEventUpdate":true,"jmapCalendarEventDestroy":true,"jmapCalendarEventCopy":true,"jmapCalendarEventParse":true,"jmapCalendarEventNotificationGet":true,"jmapCalendarEventNotificationChanges":true,"jmapCalendarEventNotificationQuery":true,"jmapCalendarEventNotificationQueryChanges":true,"jmapCalendarEventNotificationCreate":true,"jmapCalendarEventNotificationUpdate":true,"jmapCalendarEventNotificationDestroy":true,"jmapParticipantIdentityGet":true,"jmapParticipantIdentityChanges":true,"jmapParticipantIdentityCreate":true,"jmapParticipantIdentityUpdate":true,"jmapParticipantIdentityDestroy":true,"jmapCoreEcho":true,"imapAuthenticate":true,"imapAclGet":true,"imapAclSet":true,"imapMyRights":true,"imapListRights":true,"imapAppend":true,"imapCapability":true,"imapId":true,"imapCopy":true,"imapMove":true,"imapCreate":true,"imapDelete":true,"imapEnable":true,"imapExpunge":true,"imapFetch":true,"imapIdle":true,"imapList":true,"imapLsub":true,"imapNamespace":true,"imapRename":true,"imapSearch":true,"imapSort":true,"imapSelect":true,"imapExamine":true,"imapStatus":true,"imapStore":true,"imapSubscribe":true,"imapThread":true,"pop3Authenticate":true,"pop3List":true,"pop3Uidl":true,"pop3Stat":true,"pop3Retr":true,"pop3Dele":true,"sieveAuthenticate":true,"sieveListScripts":true,"sieveSetActive":true,"sieveGetScript":true,"sievePutScript":true,"sieveDeleteScript":true,"sieveRenameScript":true,"sieveCheckScript":true,"sieveHaveSpace":true,"davSyncCollection":true,"davExpandProperty":true,"davPrincipalAcl":true,"davPrincipalList":true,"davPrincipalMatch":true,"davPrincipalSearch":true,"davPrincipalSearchPropSet":true,"davFilePropFind":true,"davFilePropPatch":true,"davFileGet":true,"davFileMkCol":true,"davFileDelete":true,"davFilePut":true,"davFileCopy":true,"davFileMove":true,"davFileLock":true,"davFileAcl":true,"davCardPropFind":true,"davCardPropPatch":true,"davCardGet":true,"davCardMkCol":true,"davCardDelete":true,"davCardPut":true,"davCardCopy":true,"davCardMove":true,"davCardLock":true,"davCardAcl":true,"davCardQuery":true,"davCardMultiGet":true,"davCalPropFind":true,"davCalPropPatch":true,"davCalGet":true,"davCalMkCol":true,"davCalDelete":true,"davCalPut":true,"davCalCopy":true,"davCalMove":true,"davCalLock":true,"davCalAcl":true,"davCalQuery":true,"davCalMultiGet":true,"davCalFreeBusyQuery":true,"sysAccountPasswordGet":true,"sysAccountPasswordUpdate":true,"sysAccountSettingsGet":true,"sysAccountSettingsUpdate":true,"sysApiKeyGet":true,"sysApiKeyCreate":true,"sysApiKeyUpdate":true,"sysApiKeyDestroy":true,"sysApiKeyQuery":true,"sysAppPasswordGet":true,"sysAppPasswordCreate":true,"sysAppPasswordUpdate":true,"sysAppPasswordDestroy":true,"sysAppPasswordQuery":true,"sysArchivedItemGet":true,"sysArchivedItemCreate":true,"sysArchivedItemUpdate":true,"sysArchivedItemDestroy":true,"sysArchivedItemQuery":true,"sysMaskedEmailGet":true,"sysMaskedEmailCreate":true,"sysMaskedEmailUpdate":true,"sysMaskedEmailDestroy":true,"sysMaskedEmailQuery":true,"sysPublicKeyGet":true,"sysPublicKeyCreate":true,"sysPublicKeyUpdate":true,"sysPublicKeyDestroy":true,"sysPublicKeyQuery":true,"sysSpamTrainingSampleGet":true,"sysSpamTrainingSampleUpdate":true,"sysSpamTrainingSampleDestroy":true,"sysSpamTrainingSampleQuery":true,"jmapFileNodeCopy":true},"disabledPermissions":{}}}}
+{"@type":"create","object":"Domain","value":{"domain-b":{"dkimManagement":{"@type":"Manual"},"aliases":{},"catchAllAddress":null,"isEnabled":true,"description":null,"logo":null,"name":"example.org","reportAddressUri":"mailto:postmaster","subAddressing":{"@type":"Enabled"},"memberTenantId":null,"certificateManagement":{"@type":"Manual"},"directoryId":null,"dnsManagement":{"@type":"Manual"},"allowRelaying":false}}}
+{"@type":"create","object":"Account","value":{"account-d":{"@type":"User","credentials":{"0":{"@type":"Password","expiresAt":null,"secret":"****","allowedIps":{}}},"description":"Master","encryptionAtRest":{"@type":"Disabled"},"quotas":{},"memberGroupIds":{},"name":"master","memberTenantId":null,"timeZone":null,"locale":"en_US","aliases":{},"roles":{"@type":"User"},"domainId":"#domain-b","permissions":{"@type":"Merge","enabledPermissions":{"impersonate":true},"disabledPermissions":{}}},"account-b":{"@type":"User","credentials":{"0":{"@type":"Password","expiresAt":null,"secret":"****","allowedIps":{}}},"description":"System administrator","encryptionAtRest":{"@type":"Disabled"},"quotas":{},"memberGroupIds":{},"name":"admin","memberTenantId":null,"timeZone":null,"locale":"en_US","aliases":{},"roles":{"@type":"Admin"},"domainId":"#domain-b","permissions":{"@type":"Inherit"}}}}
+{"@type":"update","object":"Sharing","value":{"allowDirectoryQueries":false,"maxShares":10}}
+{"@type":"update","object":"SystemSettings","value":{"maxConnections":8192,"services":{"caldav":{"cleartext":false,"hostname":null},"carddav":{"cleartext":false,"hostname":null},"imap":{"cleartext":false,"hostname":null},"jmap":{"cleartext":false,"hostname":null},"managesieve":{"cleartext":false,"hostname":null},"pop3":{"cleartext":false,"hostname":null},"smtp":{"cleartext":false,"hostname":null},"webdav":{"cleartext":false,"hostname":null}},"defaultCertificateId":null,"providerInfo":{},"threadPoolSize":null,"defaultHostname":"localhost","mailExchangers":{"0":{"hostname":null,"priority":10}},"defaultDomainId":"#domain-b","proxyTrustedNetworks":{}}}
+{"@type":"update","object":"DataRetention","value":{"archiveDeletedItemsFor":null,"holdTracesFor":2592000000,"dataCleanupSchedule":{"@type":"Daily","minute":0,"hour":2},"expungeSchedulingInboxAfter":2592000000,"holdMetricsFor":7776000000,"metricsCollectionInterval":{"@type":"Hourly","minute":0},"expungeSchedule":{"@type":"Daily","minute":0,"hour":0},"expungeSubmissionsAfter":259200000,"holdMtaReportsFor":2592000000,"archiveDeletedAccountsFor":null,"expungeTrashAfter":2592000000,"maxChangesHistory":10000,"expungeShareNotifyAfter":2592000000,"blobCleanupSchedule":{"@type":"Daily","minute":0,"hour":4}}}
+{"@type":"update","object":"BlobStore","value":{"@type":"Default"}}
+{"@type":"update","object":"InMemoryStore","value":{"@type":"Default"}}
+{"@type":"update","object":"SearchStore","value":{"@type":"Default"}}
+`
)
+type importItem struct {
+ Type string `json:"@type"`
+ Object string `json:"object"`
+ Value map[string]any `json:"value"`
+}
+
func skip(t *testing.T) bool {
if os.Getenv("CI") == "woodpecker" {
t.Skip("Skipping tests because CI==woodpecker")
@@ -329,8 +247,8 @@ func (s *StalwartTest) Context(session *Session) Context {
}
func (s *StalwartTest) Session(username string) *Session {
- session, jerr := s.client.FetchSession(s.ctx, s.sessionUrl, username, s.logger)
- require.NoError(s.t, jerr)
+ session, err := s.client.FetchSession(s.ctx, s.sessionUrl, username, s.logger)
+ require.NoError(s.t, err, "failed to authenticate user '%s' and/or retrieve their JMAP session using the URL '%s'", username, s.sessionUrl.String())
require.NotNil(s.t, session.Capabilities.Mail)
require.NotNil(s.t, session.Capabilities.Calendars)
require.NotNil(s.t, session.Capabilities.Contacts)
@@ -339,6 +257,7 @@ func (s *StalwartTest) Session(username string) *Session {
// will know its name to be a random Docker container identifier, or
// "localhost" as we defined the hostname in the Stalwart configuration,
// and we also need to overwrite the port number as its not mapped
+
session.JmapUrl.Host = s.jmapBaseUrl.Host
session.JmapUrl.Scheme = "http" // replace https with http
session.WebsocketUrl.Host = s.jmapBaseUrl.Host
@@ -382,7 +301,6 @@ type printingLogConsumer struct {
prefix string
}
-// 2026/06/05 16:18:36 STALWART: 2026-06-05T14:18:36Z INFO Shutting down Stalwart Server (server.shutdown) causedBy = "SIGTERM"
var printingLogConsumerRegex = regexp.MustCompile(`^(\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ)\s+(\S+)\s+(.+)$`)
func (c *printingLogConsumer) Accept(l testcontainers.Log) {
@@ -394,13 +312,13 @@ func (c *printingLogConsumer) Accept(l testcontainers.Log) {
}
}
-func withDirectoryQueries(allowDirectoryQueries bool) func(map[string]any) {
- return func(m map[string]any) {
- m["dirquery"] = strconv.FormatBool(allowDirectoryQueries)
+func withDirectoryQueries(allowDirectoryQueries bool) func(*importSettings) {
+ return func(settings *importSettings) {
+ settings.allowDirectoryQueries = allowDirectoryQueries
}
}
-func applySnapshot(ctx context.Context, net *testcontainers.DockerNetwork, uri, user, password string, content *strings.Reader) ([]string, error) {
+func applySnapshot(t *testing.T, ctx context.Context, net *testcontainers.DockerNetwork, uri, user, password string, content *strings.Reader) ([]string, error) {
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
hdr := &tar.Header{
@@ -432,12 +350,19 @@ func applySnapshot(ctx context.Context, net *testcontainers.DockerNetwork, uri,
return nil, fmt.Errorf("unsupported architecture: '%s'", runtime.GOARCH)
}
+ cliVersion := StalwartCliVersion
+ buildOutput := []string{}
+ buildLogger := NewLogLineWriter(func(s string) { log.Printf("DOCKER-BUILD: %s", s) }, &buildOutput)
opts := []testcontainers.ContainerCustomizer{
testcontainers.WithDockerfile(testcontainers.FromDockerfile{
ContextArchive: bytes.NewReader(buf.Bytes()),
+ Repo: "stalwart-cli",
+ Tag: cliVersion,
KeepImage: true, // speeds up subsequent test runs by using the Docker cache
+ BuildLogWriter: buildLogger,
BuildArgs: map[string]*string{
- "ARCH": &cliArch,
+ "ARCH": &cliArch,
+ "VERSION": &cliVersion,
},
}),
testcontainers.WithCmdArgs("/usr/local/bin/stalwart-cli", "apply", "--json", "--no-color", "--file", "/snapshot.json"),
@@ -464,6 +389,8 @@ func applySnapshot(ctx context.Context, net *testcontainers.DockerNetwork, uri,
return nil, err
}
+ t.Logf("Container build output:\n%s", strings.Join(buildOutput, "\n"))
+
rc := 0
s, stateErr := container.State(ctx)
if stateErr == nil {
@@ -483,6 +410,115 @@ func applySnapshot(ctx context.Context, net *testcontainers.DockerNetwork, uri,
return output, nil
}
+type importSettings struct {
+ adminUsername string
+ adminPassword string
+ masterUsername string
+ masterPassword string
+ hostname string
+ httpPort string
+ imapsPort string
+ allowDirectoryQueries bool
+ domain string
+}
+
+func importConfig(t *testing.T, container *testcontainers.DockerContainer, ctx context.Context, net *testcontainers.DockerNetwork,
+ host string, username string, password string, settings importSettings, skipDestroy bool,
+) ([]string, error) {
+ uri := ""
+ if net != nil {
+ uri = (&url.URL{Scheme: "http", Host: host + ":" + httpPort, Path: "/"}).String()
+ } else {
+ if ir, err := container.Inspect(ctx); err != nil {
+ return nil, err
+ } else {
+ id := ir.Config.Hostname
+ for _, network := range ir.NetworkSettings.Networks {
+ id = network.IPAddress.String()
+ }
+ uri = (&url.URL{Scheme: "http", Host: id + ":" + httpPort, Path: "/"}).String()
+ }
+ }
+
+ snapshot := []string{}
+ {
+ for line := range structs.FilterSeq(structs.MapSeq(strings.Lines(dumpTemplate), strings.TrimSpace), func(s string) bool { return len(s) > 0 }) {
+ var item importItem
+ {
+ buf := bytes.NewBufferString(line)
+ if err := json.Unmarshal(buf.Bytes(), &item); err != nil {
+ return nil, err
+ }
+ }
+
+ if skipDestroy {
+ if item.Type == "destroy" {
+ continue
+ }
+ if item.Type == "create" && item.Object == "NetworkListener" {
+ continue
+ }
+ }
+
+ switch item.Type {
+ case "create":
+ switch item.Object {
+ case "Account":
+ for id, account := range item.Value {
+ account := account.(map[string]any)
+ name := account["name"]
+ switch name {
+ case "master":
+ account["name"] = settings.masterUsername
+ credsMap := account["credentials"].(map[string]any)
+ for cid, creds := range credsMap {
+ creds := creds.(map[string]any)
+ if creds["@type"] == "Password" {
+ creds["secret"] = settings.masterPassword
+ }
+ credsMap[cid] = creds
+ }
+ case "admin":
+ account["name"] = settings.adminUsername
+ credsMap := account["credentials"].(map[string]any)
+ for cid, creds := range credsMap {
+ creds := creds.(map[string]any)
+ if creds["@type"] == "Password" {
+ creds["secret"] = settings.adminPassword
+ }
+ credsMap[cid] = creds
+ }
+ }
+ item.Value[id] = account
+ }
+ case "Domain":
+ for id, domain := range item.Value {
+ domain := domain.(map[string]any)
+ domain["name"] = settings.domain
+ item.Value[id] = domain
+ }
+ }
+ case "update":
+ switch item.Object {
+ case "SystemSettings":
+ item.Value["defaultHostname"] = settings.hostname
+ case "Sharing":
+ item.Value["allowDirectoryQueries"] = settings.allowDirectoryQueries
+ }
+ }
+
+ b, err := json.Marshal(item)
+ if err != nil {
+ return nil, err
+ }
+ snapshot = append(snapshot, strings.TrimSpace(string(b)))
+ }
+ }
+ text := strings.Join(snapshot, "\n")
+ t.Logf("Importing this config:\n%s", text)
+ return applySnapshot(t, ctx, net, uri, username, password, strings.NewReader(text))
+}
+
func postJmap(ctx context.Context, h http.Client, url string, username string, password string, body map[string]any) (map[string]any, error) {
bb, err := json.Marshal(body)
if err != nil {
@@ -651,9 +687,7 @@ func (h *ContextPasswordAuthHttpJmapClientAuthenticator) AuthenticateWS(ctx cont
return nil
}
-const useNetwork = false
-
-func newStalwartTest(t *testing.T, options ...func(map[string]any)) (*StalwartTest, error) { //NOSONAR
+func newStalwartTest(t *testing.T, options ...func(*importSettings)) (*StalwartTest, error) { //NOSONAR
//ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
ctx := t.Context()
cancel := func() {}
@@ -686,23 +720,19 @@ func newStalwartTest(t *testing.T, options ...func(map[string]any)) (*StalwartTe
}
hostname := "127.0.0.1"
- settings := map[string]any{
- "hostname": hostname,
- "masterusername": masterUsername,
- "masterpassword": masterPassword, //Hash,
- "adminusername": adminUsername,
- "adminpassword": adminPassword,
- "httpPort": httpPort,
- "httpsPort": 10443,
- "popsPort": 10995,
- "imapsPort": imapsPort,
- "submissionsPort": 10465,
- "smtpPort": 10025,
- "dirquery": "false",
- "domain": "example.org",
+ settings := importSettings{
+ adminUsername: adminUsername,
+ adminPassword: adminPassword,
+ masterUsername: masterUsername,
+ masterPassword: masterPassword,
+ hostname: hostname,
+ httpPort: httpPort,
+ imapsPort: imapsPort,
+ allowDirectoryQueries: false,
+ domain: "example.org",
}
for _, option := range options {
- option(settings)
+ option(&settings)
}
var net *testcontainers.DockerNetwork
@@ -719,121 +749,82 @@ func newStalwartTest(t *testing.T, options ...func(map[string]any)) (*StalwartTe
}
}
+ // the strategy to wait for the container to be ready: prod the JMAP well-known URI until we get a 200 OK
httpWait := wait.ForHTTP("/.well-known/jmap")
httpWait.Port = dockernetwork.MustParsePort(httpPort)
- configBuf := bytes.NewBufferString("")
- template.Must(template.New("config").Delims("<<", ">>").Parse(jsonConfigTemplate)).Execute(configBuf, settings)
- config := configBuf.String()
- {
- var recovery *testcontainers.DockerContainer
- {
- opts := []testcontainers.ContainerCustomizer{
- testcontainers.WithLogConsumers(&printingLogConsumer{prefix: "RECOVERY"}),
- testcontainers.WithExposedPorts(httpPort + "/tcp"),
- testcontainers.WithEnv(map[string]string{
- "STALWART_RECOVERY_ADMIN": strings.Join([]string{adminUsername, adminPassword}, ":"),
- "STALWART_RECOVERY_MODE": "1",
- }),
- testcontainers.WithFiles(testcontainers.ContainerFile{
- Reader: strings.NewReader(config),
- ContainerFilePath: "/etc/stalwart/config.json",
- FileMode: 0o666,
- }),
- testcontainers.WithWaitStrategyAndDeadline(
- 30*time.Second,
- wait.ForMappedPort(httpPort),
- httpWait,
- ),
- testcontainers.WithName(recoveryAlias),
- testcontainers.WithMounts(
- testcontainers.ContainerMount{
- Source: testcontainers.GenericVolumeMountSource{
- Name: volumeName,
- },
- Target: "/var/lib/stalwart",
- ReadOnly: false,
+ var recovery *testcontainers.DockerContainer = nil
+ if useRecoveryContainer {
+ // first we need to start the Stalwart container in recovery mode, in order to be able to feed it
+ // the configuration through the CLI
+ opts := []testcontainers.ContainerCustomizer{
+ testcontainers.WithLogConsumers(&printingLogConsumer{prefix: "RECOVERY"}),
+ testcontainers.WithExposedPorts(httpPort + "/tcp"),
+ testcontainers.WithEnv(map[string]string{
+ "STALWART_RECOVERY_ADMIN": strings.Join([]string{adminUsername, adminPassword}, ":"),
+ "STALWART_RECOVERY_MODE": "1",
+ }),
+ testcontainers.WithFiles(testcontainers.ContainerFile{
+ Reader: strings.NewReader(stalwartConfig),
+ ContainerFilePath: stalwartConfigPath,
+ FileMode: 0o666,
+ }),
+ testcontainers.WithWaitStrategyAndDeadline(
+ 30*time.Second,
+ wait.ForMappedPort(httpPort),
+ httpWait,
+ ),
+ testcontainers.WithName(recoveryAlias),
+ testcontainers.WithMounts(
+ testcontainers.ContainerMount{
+ Source: testcontainers.GenericVolumeMountSource{
+ Name: volumeName,
},
- ),
- }
- if net != nil {
- opts = append(opts, network.WithNetwork([]string{recoveryAlias}, net))
- }
-
- if c, err := testcontainers.Run(ctx, stalwartImage, opts...); err != nil {
- return nil, err
- } else {
- testcontainers.CleanupContainer(t, c)
- recovery = c
- }
+ Target: stalwartStoragePath,
+ ReadOnly: false,
+ },
+ ),
+ }
+ if net != nil {
+ opts = append(opts, network.WithNetwork([]string{recoveryAlias}, net))
}
- // import config using the cli
- {
- uri := ""
- if net != nil {
- uri = (&url.URL{Scheme: "http", Host: recoveryAlias + ":" + httpPort, Path: "/"}).String()
- } else {
- if ir, err := recovery.Inspect(ctx); err != nil {
- return nil, err
- } else {
- id := ir.Config.Hostname
- for _, network := range ir.NetworkSettings.Networks {
- id = network.IPAddress.String()
- }
- uri = (&url.URL{Scheme: "http", Host: id + ":" + httpPort, Path: "/"}).String()
- }
- }
+ stalwartImage := fmt.Sprintf(stalwartImageTemplate, StalwartVersion)
+ if c, err := testcontainers.Run(ctx, stalwartImage, opts...); err != nil {
+ return nil, err
+ } else {
+ testcontainers.CleanupContainer(t, c)
+ recovery = c
+ }
- snapshot := []string{}
- {
- t := template.New("snapshot").Delims("<<", ">>")
- for _, line := range dumpTemplate {
- buf := bytes.NewBufferString("")
- p, err := t.Parse(line)
- if err != nil {
- return nil, err
- }
- err = p.Execute(buf, settings)
- if err != nil {
- return nil, err
- }
-
- var m map[string]any
- err = json.Unmarshal(buf.Bytes(), &m)
- if err != nil {
- return nil, err
- }
- b, err := json.Marshal(m)
- if err != nil {
- return nil, err
- }
- snapshot = append(snapshot, strings.TrimSpace(string(b)))
- }
- }
- text := strings.Join(snapshot, "\n")
-
- t.Logf("Snapshot:\n%s", text)
- output, err := applySnapshot(ctx, net, uri, adminUsername, adminPassword, strings.NewReader(text))
- if err != nil {
- return nil, err
- }
+ // now that the container is running in recovery mode, we can use the stalwart CLI to import
+ // the configuration
+ if output, err := importConfig(t, recovery, ctx, net, recoveryAlias, adminUsername, adminPassword, settings, false); err != nil {
+ return nil, err
+ } else {
t.Logf("Output of applying configuration:\n%s", strings.Join(output, ""))
}
+ // we can now stop the Stalwart container in recovery mode, but without removing any volumes,
+ // since we need to use that initialized volume to run the "proper" container we will be
+ // performing the tests against
if err := recovery.Terminate(ctx, testcontainers.RemoveVolumes()); err != nil {
return nil, err
}
}
+ // and now we start the container in "proper" mode (not recovery), re-using the same volume for data
var container *testcontainers.DockerContainer
{
opts := []testcontainers.ContainerCustomizer{
testcontainers.WithLogConsumers(&printingLogConsumer{prefix: "STALWART"}),
testcontainers.WithExposedPorts(httpPort+"/tcp", imapsPort+"/tcp"),
+ testcontainers.WithEnv(map[string]string{
+ "STALWART_RECOVERY_ADMIN": strings.Join([]string{adminUsername, adminPassword}, ":"),
+ }),
testcontainers.WithFiles(testcontainers.ContainerFile{
- Reader: strings.NewReader(config),
- ContainerFilePath: "/etc/stalwart/config.json",
+ Reader: strings.NewReader(stalwartConfig),
+ ContainerFilePath: stalwartConfigPath,
FileMode: 0o666,
}),
testcontainers.WithWaitStrategyAndDeadline(
@@ -847,7 +838,7 @@ func newStalwartTest(t *testing.T, options ...func(map[string]any)) (*StalwartTe
Source: testcontainers.GenericVolumeMountSource{
Name: volumeName,
},
- Target: "/var/lib/stalwart",
+ Target: stalwartStoragePath,
ReadOnly: false,
},
),
@@ -857,6 +848,7 @@ func newStalwartTest(t *testing.T, options ...func(map[string]any)) (*StalwartTe
opts = append(opts, network.WithNetwork([]string{containerAlias}, net))
}
+ stalwartImage := fmt.Sprintf(stalwartImageTemplate, StalwartVersion)
if c, err := testcontainers.Run(ctx, stalwartImage, opts...); err != nil {
return nil, err
} else {
@@ -865,6 +857,19 @@ func newStalwartTest(t *testing.T, options ...func(map[string]any)) (*StalwartTe
}
}
+ if !useRecoveryContainer {
+ // we didn't use a recovery container to initialize the configuration, we will use the
+ // regular container to do so
+ if output, err := importConfig(t, container, ctx, net, containerAlias, adminUsername, adminPassword, settings, true); err != nil {
+ t.Logf("Output of applying configuration:\n%s", strings.Join(output, ""))
+ return nil, fmt.Errorf("failed to import configuration: %w: output: %s", err, strings.Join(output, ""))
+ } else {
+ t.Logf("Output of applying configuration:\n%s", strings.Join(output, ""))
+ }
+ }
+
+ // and now we do have a container that we can use for tests
+
ip, err := container.Host(ctx)
if err != nil {
return nil, err
@@ -1724,7 +1729,7 @@ func allBoxesAreTicked[S any](t *testing.T, s S, exceptions ...string) {
func deepEqual[T any](t *testing.T, expected, actual T) {
diff := ""
- if EnableTypes {
+ if enableTypes {
diff = cmp.Diff(expected, actual)
} else {
diff = cmp.Diff(expected, actual, cmp.FilterPath(func(p cmp.Path) bool {
diff --git a/pkg/jmap/export_prefixed_writer_test.go b/pkg/jmap/export_prefixed_writer_test.go
new file mode 100644
index 0000000000..260ec5de54
--- /dev/null
+++ b/pkg/jmap/export_prefixed_writer_test.go
@@ -0,0 +1,62 @@
+package jmap
+
+import (
+ "bytes"
+)
+
+// LogLineWriter captures data chunk by chunk, extracts full lines,
+// and passes them directly to log.Printf.
+type LogLineWriter struct {
+ buf bytes.Buffer
+ printer func(string)
+ lines *[]string
+}
+
+// NewLogLineWriter initializes the writer with a specific prefix.
+func NewLogLineWriter(printer func(string), lines *[]string) *LogLineWriter {
+ return &LogLineWriter{printer: printer, lines: lines}
+}
+
+// Write intercepts the byte stream and looks for complete text lines.
+func (w *LogLineWriter) Write(p []byte) (n int, err error) {
+ w.buf.Write(p)
+
+ for {
+ bufferedBytes := w.buf.Bytes()
+ idx := bytes.IndexByte(bufferedBytes, '\n')
+ if idx == -1 {
+ break // Line is incomplete; wait for more data
+ }
+
+ // Slice UP TO the newline (idx), omitting the '\n' itself.
+ // Go's log package handles its own line-endings.
+ line := bufferedBytes[:idx]
+
+ // Emit to log.Printf. Using %s works perfectly with []byte
+ // without forcing an expensive string allocation.
+ s := string(line)
+ w.printer(s)
+ if w.lines != nil {
+ *w.lines = append(*w.lines, s)
+ }
+
+ // Advance the buffer past the processed text AND the '\n' (idx + 1)
+ w.buf.Next(idx + 1)
+ }
+
+ return len(p), nil
+}
+
+// Flush ensures that any lingering text without a trailing newline
+// gets safely pushed out to the log before exit.
+func (w *LogLineWriter) Flush() error {
+ if w.buf.Len() > 0 {
+ s := w.buf.String()
+ w.printer(s)
+ if w.lines != nil {
+ *w.lines = append(*w.lines, s)
+ }
+ w.buf.Reset()
+ }
+ return nil
+}