Thanks for the response, but I think this is a different issue. The linker named in my error is SystemSettings#singleton, not a DKIM key:
✗ destroy Domain: Domain: destroy failed for id b: error: objectIsLinked | Object id: Domain#b | Linked by: SystemSettings#singleton
The problem is that SystemSettings.defaultDomainId was set to #dom-a by the update in the first run’s pass 2. On the second run, that reference is still live when pass 1 (destroy) starts, so the Domain destroy fails --and pass 2, which would repoint defaultDomainId to the newly-created Domain, never gets a chance to run because the plan halts on the first error.
As a sanity check I ran:
❯ sudo rm -rf /var/lib/stalwart/db
❯ sudo systemctl restart stalwart
Initial snapshot:
❯ STALWART_URL="http://localhost:8080" STALWART_USER='recovery-admin' STALWART_PASSWORD="12345678" stalwart-cli snapshot Directory DnsServer AcmeProvider Role Tenant Domain Certificate SystemSettings Authentication
snapshot: 20 creates, 2 singletons
fetching Certificate...
0 fetched
Certificate: 0
fetching Tenant...
0 fetched
Tenant: 0
fetching Role...
0 fetched
Role: 0
fetching AcmeProvider...
0 fetched
AcmeProvider: 0
fetching DnsServer...
0 fetched
DnsServer (GoogleCloudDns): 0
DnsServer (Route53): 0
DnsServer (Spaceship): 0
DnsServer (Dnsimple): 0
DnsServer (Porkbun): 0
DnsServer (Bunny): 0
DnsServer (Ovh): 0
DnsServer (DeSEC): 0
DnsServer (DigitalOcean): 0
DnsServer (Cloudflare): 0
DnsServer (Sig0): 0
DnsServer (Tsig): 0
fetching Directory...
0 fetched
Directory (Oidc): 0
Directory (Sql): 0
Directory (Ldap): 0
fetching Domain...
0 fetched
Domain: 0
fetching singleton SystemSettings...
fetching singleton Authentication...
{"@type":"destroy","object":"Certificate"}
{"@type":"destroy","object":"Tenant"}
{"@type":"destroy","object":"Role"}
{"@type":"destroy","object":"AcmeProvider"}
{"@type":"destroy","object":"DnsServer"}
{"@type":"destroy","object":"Directory"}
{"@type":"destroy","object":"Domain"}
{"@type":"update","object":"SystemSettings","value":{"defaultCertificateId":null,"defaultHostname":"","threadPoolSize":null,"proxyTrustedNetworks":{},"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":{"hostname":null,"priority":10}},"maxConnections":8192,"defaultDomainId":"#domain-p333333333333","providerInfo":{}}}
{"@type":"update","object":"Authentication","value":{"maxApiKeys":5,"defaultTenantRoleIds":{},"directoryId":null,"passwordMinLength":8,"passwordMaxLength":128,"passwordDefaultExpiry":null,"defaultUserRoleIds":{},"defaultGroupRoleIds":{},"passwordHashAlgorithm":"argon2id","maxAppPasswords":5,"passwordMinStrength":"three","defaultAdminRoleIds":{}}}
And then tried creating a domain with manual dkim management:
❯ STALWART_URL="http://localhost:8080" STALWART_USER='recovery-admin' STALWART_PASSWORD="12345678" stalwart-cli apply \
--stdin <<'EOF'
{"@type":"destroy","object":"Domain","value":{"name":"example.com"}}
{"@type":"create","object":"Domain","value":{"dom-a":{"name":"example.com","dkimManagement":{"@type":"Manual"}}}}
{"@type":"update","object":"SystemSettings","value":{"defaultDomainId":"#dom-a","defaultHostname":"mail.example.com"}}
EOF
Plan: 1 destroy, 1 update, 1 create (1 objects)
✓ destroyed Domain (0)
✓ created Domain (1)
✓ updated SystemSettings (1)
Done: 0 destroyed, 1 updated, 1 created (0 failed)
Snapshot after first attempt:
❯ STALWART_URL="http://localhost:8080" STALWART_USER='recovery-admin' STALWART_PASSWORD="12345678" stalwart-cli snapshot Domain SystemSettings Tenant AcmeProvider DnsServer Directory Certificate Role
snapshot: 20 creates, 1 singletons
fetching Certificate...
0 fetched
Certificate: 0
fetching Tenant...
0 fetched
Tenant: 0
fetching Role...
0 fetched
Role: 0
fetching Directory...
0 fetched
Directory (Oidc): 0
Directory (Sql): 0
Directory (Ldap): 0
fetching DnsServer...
0 fetched
DnsServer (GoogleCloudDns): 0
DnsServer (Route53): 0
DnsServer (Spaceship): 0
DnsServer (Dnsimple): 0
DnsServer (Porkbun): 0
DnsServer (Bunny): 0
DnsServer (Ovh): 0
DnsServer (DeSEC): 0
DnsServer (DigitalOcean): 0
DnsServer (Cloudflare): 0
DnsServer (Sig0): 0
DnsServer (Tsig): 0
fetching AcmeProvider...
0 fetched
AcmeProvider: 0
fetching Domain...
1 fetched
Domain: 1
fetching singleton SystemSettings...
{"@type":"destroy","object":"Certificate"}
{"@type":"destroy","object":"Tenant"}
{"@type":"destroy","object":"Role"}
{"@type":"destroy","object":"Directory"}
{"@type":"destroy","object":"DnsServer"}
{"@type":"destroy","object":"AcmeProvider"}
{"@type":"destroy","object":"Domain"}
{"@type":"create","object":"Domain","value":{"domain-b":{"isEnabled":true,"dkimManagement":{"@type":"Manual"},"certificateManagement":{"@type":"Manual"},"directoryId":null,"allowRelaying":false,"description":null,"aliases":{},"dnsManagement":{"@type":"Manual"},"memberTenantId":null,"name":"example.com","reportAddressUri":"mailto:postmaster","subAddressing":{"@type":"Enabled"},"catchAllAddress":null,"logo":null}}}
{"@type":"update","object":"SystemSettings","value":{"threadPoolSize":null,"proxyTrustedNetworks":{},"providerInfo":{},"mailExchangers":{"0":{"priority":10,"hostname":null}},"defaultDomainId":"#domain-b","defaultHostname":"mail.example.com","maxConnections":8192,"defaultCertificateId":null,"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}}}}
snapshot complete
idempotency failure demonstrated when running the same command again:
❯ STALWART_URL="http://localhost:8080" STALWART_USER='recovery-admin' STALWART_PASSWORD="12345678" stalwart-cli apply \
--stdin <<'EOF'
{"@type":"destroy","object":"Domain","value":{"name":"example.com"}}
{"@type":"create","object":"Domain","value":{"dom-a":{"name":"example.com","dkimManagement":{"@type":"Manual"}}}}
{"@type":"update","object":"SystemSettings","value":{"defaultDomainId":"#dom-a","defaultHostname":"mail.example.com"}}
EOF
Plan: 1 destroy, 1 update, 1 create (1 objects)
✗ destroy Domain: Domain: destroy failed for id b: error: objectIsLinked | Object id: Domain#b | Linked by: SystemSettings#singleton
Done: 0 destroyed, 0 updated, 0 created (1 failed)
error: Domain: destroy failed for id b: error: objectIsLinked | Object id: Domain#b | Linked by: SystemSettings#singleton
Even trying to replay the output of the snapshot commands fails:
❯ STALWART_URL="http://localhost:8080" STALWART_USER='recovery-admin' STALWART_PASSWORD="12345678" stalwart-cli apply \
--stdin <<'EOF'
{"@type":"destroy","object":"Certificate"}
{"@type":"destroy","object":"Tenant"}
{"@type":"destroy","object":"Role"}
{"@type":"destroy","object":"Directory"}
{"@type":"destroy","object":"DnsServer"}
{"@type":"destroy","object":"AcmeProvider"}
{"@type":"destroy","object":"Domain"}
{"@type":"create","object":"Domain","value":{"domain-b":{"isEnabled":true,"dkimManagement":{"@type":"Manual"},"certificateManagement":{"@type":"Manual"},"directoryId":null,"allowRelaying":false,"description":null,"aliases":{},"dnsManagement":{"@type":"Manual"},"memberTenantId":null,"name":"example.com","reportAddressUri":"mailto:postmaster","subAddressing":{"@type":"Enabled"},"catchAllAddress":null,"logo":null}}}
{"@type":"update","object":"SystemSettings","value":{"threadPoolSize":null,"proxyTrustedNetworks":{},"providerInfo":{},"mailExchangers":{"0":{"priority":10,"hostname":null}},"defaultDomainId":"#domain-b","defaultHostname":"mail.example.com","maxConnections":8192,"defaultCertificateId":null,"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}}}}
EOF
Plan: 7 destroy, 1 update, 1 create (1 objects)
✗ destroy Domain: Domain: destroy failed for id b: error: objectIsLinked | Object id: Domain#b | Linked by: SystemSettings#singleton
Done: 0 destroyed, 0 updated, 0 created (1 failed)
error: Domain: destroy failed for id b: error: objectIsLinked | Object id: Domain#b | Linked by: SystemSettings#singleton