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 +}