chore(deploy): 将 DKIM 组件切换为 Rspamd

- 用 Rspamd 替换 OpenDKIM 及其相关镜像、编排和启动脚本。
- 调整 Postfix、单容器镜像与 Docker 配置,改为对接 Rspamd milter。
- 增加 DKIM 私钥同步脚本与 Rspamd 本地配置,支持从 SQLite 周期导出签名密钥。
- 更新 API 启动与测试逻辑,兼容新的默认管理员邮箱与邮箱加载方式。
This commit is contained in:
LanQin
2026-06-15 23:41:21 +08:00
parent 2a5c4fcc0c
commit f8ff2a814b
25 changed files with 234 additions and 137 deletions
+47 -31
View File
@@ -148,6 +148,28 @@ func (c *testClient) do(method, path string, body any, out any) int {
return resp.StatusCode
}
func createTestDomain(t *testing.T, admin *testClient, name string) Domain {
t.Helper()
var domain Domain
if code := admin.do("POST", "/api/admin/domains", map[string]string{"name": name}, &domain); code != http.StatusCreated {
t.Fatalf("create domain %s code=%d domain=%+v", name, code, domain)
}
return domain
}
func createTestMailbox(t *testing.T, admin *testClient, domainID, localPart, displayName, password string, extra map[string]any) Mailbox {
t.Helper()
payload := map[string]any{"domainId": domainID, "localPart": localPart, "displayName": displayName, "password": password}
for key, value := range extra {
payload[key] = value
}
var mailbox Mailbox
if code := admin.do("POST", "/api/admin/mailboxes", payload, &mailbox); code != http.StatusCreated {
t.Fatalf("create mailbox %s code=%d mailbox=%+v", localPart, code, mailbox)
}
return mailbox
}
func TestAuthAdminAndLocalDeliveryFlow(t *testing.T) {
a := newTestApp(t)
ts := httptest.NewServer(a.Router())
@@ -159,22 +181,10 @@ func TestAuthAdminAndLocalDeliveryFlow(t *testing.T) {
t.Fatalf("login code=%d body=%v", code, login)
}
var domains struct {
Items []Domain `json:"items"`
}
if code := admin.do("GET", "/api/admin/domains", nil, &domains); code != http.StatusOK || len(domains.Items) == 0 {
t.Fatalf("domains code=%d items=%d", code, len(domains.Items))
}
domainID := domains.Items[0].ID
domainID := createTestDomain(t, admin, "lanqin.local").ID
var mb1 Mailbox
if code := admin.do("POST", "/api/admin/mailboxes", map[string]any{"domainId": domainID, "localPart": "alice", "displayName": "Alice", "password": "Password123!"}, &mb1); code != http.StatusCreated {
t.Fatalf("create alice code=%d mailbox=%+v", code, mb1)
}
var mb2 Mailbox
if code := admin.do("POST", "/api/admin/mailboxes", map[string]any{"domainId": domainID, "localPart": "bob", "displayName": "Bob", "password": "Password123!"}, &mb2); code != http.StatusCreated {
t.Fatalf("create bob code=%d mailbox=%+v", code, mb2)
}
mb1 := createTestMailbox(t, admin, domainID, "alice", "Alice", "Password123!", nil)
mb2 := createTestMailbox(t, admin, domainID, "bob", "Bob", "Password123!", nil)
var alias Alias
if code := admin.do("POST", "/api/admin/aliases", map[string]any{"domainId": domainID, "source": "sales", "destination": mb1.Address}, &alias); code != http.StatusCreated {
@@ -269,22 +279,11 @@ func TestUserCanSelectMultipleMailboxes(t *testing.T) {
t.Fatalf("login code=%d body=%v", code, login)
}
var domains struct {
Items []Domain `json:"items"`
}
if code := admin.do("GET", "/api/admin/domains", nil, &domains); code != http.StatusOK || len(domains.Items) == 0 {
t.Fatalf("domains code=%d items=%d", code, len(domains.Items))
}
domainID := domains.Items[0].ID
domainID := createTestDomain(t, admin, "lanqin.local").ID
createTestMailbox(t, admin, domainID, "admin", "Admin", "ChangeMe123!", map[string]any{"ownerEmail": "admin@lanqin.local", "role": "admin"})
var primary Mailbox
if code := admin.do("POST", "/api/admin/mailboxes", map[string]any{"domainId": domainID, "localPart": "multi", "displayName": "Multi", "password": "Password123!"}, &primary); code != http.StatusCreated {
t.Fatalf("create primary code=%d mailbox=%+v", code, primary)
}
var secondary Mailbox
if code := admin.do("POST", "/api/admin/mailboxes", map[string]any{"domainId": domainID, "localPart": "multi-work", "displayName": "Multi Work", "password": "Password456!", "ownerEmail": primary.Address}, &secondary); code != http.StatusCreated {
t.Fatalf("create secondary code=%d mailbox=%+v", code, secondary)
}
primary := createTestMailbox(t, admin, domainID, "multi", "Multi", "Password123!", nil)
secondary := createTestMailbox(t, admin, domainID, "multi-work", "Multi Work", "Password456!", map[string]any{"ownerEmail": primary.Address})
if primary.UserID != secondary.UserID {
t.Fatalf("mailboxes were not bound to one user: primary=%s secondary=%s", primary.UserID, secondary.UserID)
}
@@ -331,6 +330,8 @@ func TestCatchAllStoresUnregisteredMailForAdminOnly(t *testing.T) {
if code := admin.do("POST", "/api/auth/login", map[string]string{"email": "admin@lanqin.local", "password": "ChangeMe123!"}, &login); code != http.StatusOK {
t.Fatalf("login code=%d body=%v", code, login)
}
domainID := createTestDomain(t, admin, "lanqin.local").ID
createTestMailbox(t, admin, domainID, "admin", "Admin", "ChangeMe123!", map[string]any{"ownerEmail": "admin@lanqin.local", "role": "admin"})
payload := map[string]any{
"to": []string{"ghost@lanqin.local"},
@@ -530,7 +531,11 @@ func TestUserTwoFactorSetupAndLogin(t *testing.T) {
func TestDNSRecords(t *testing.T) {
a := newTestApp(t)
d, err := a.domainByID(context.Background(), mustDefaultDomainID(t, a))
domainID, err := a.createDomainTx(context.Background(), nil, "lanqin.local")
if err != nil {
t.Fatal(err)
}
d, err := a.domainByID(context.Background(), domainID)
if err != nil {
t.Fatal(err)
}
@@ -548,6 +553,17 @@ func TestMaildirSyncImportsRFC822(t *testing.T) {
ctx := context.Background()
root := t.TempDir()
a.cfg.MaildirRoot = root
domainID, err := a.createDomainTx(ctx, nil, "lanqin.local")
if err != nil {
t.Fatal(err)
}
adminUser, _, err := a.userByEmail(ctx, "admin@lanqin.local")
if err != nil {
t.Fatal(err)
}
if _, err := a.createMailbox(ctx, adminUser.ID, domainID, "admin", "Admin", "ChangeMe123!", 1024, "active"); err != nil {
t.Fatal(err)
}
mailboxes, err := a.maildirMailboxes(ctx)
if err != nil {