{"record":{"id":"bd5160e76d8c6964","repo":"Billionmail/BillionMail","slug":"failed-to-write-dkim-sign-config-v","errorCode":null,"errorMessage":"Failed to write DKIM sign config: %v","messagePattern":"Failed to write DKIM sign config: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/domains/domains.go","lineNumber":628,"sourceCode":"\t\t\tif public.FileExists(signConfPath) {\n\t\t\t\tsignContent, err = public.ReadFile(signConfPath)\n\t\t\t\tif err != nil {\n\t\t\t\t\terr = fmt.Errorf(\"Failed to read DKIM sign config: %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Remove old config block if it exists\n\t\t\tpattern := fmt.Sprintf(`(?s)#%s_DKIM_BEGIN.*?#%s_DKIM_END\\s*`, domain, domain)\n\t\t\tsignContent, err = gregex.ReplaceString(pattern, \"\", signContent)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tsignContent = strings.Replace(signContent, \"#BT_DOMAIN_DKIM_END\", signConf+\"\\n#BT_DOMAIN_DKIM_END\", 1)\n\t\t\t_, err = public.WriteFile(signConfPath, signContent)\n\t\t\tif err != nil {\n\t\t\t\terr = fmt.Errorf(\"Failed to write DKIM sign config: %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Restart rspamd service\n\t\t\terr = dk.RestartContainerByName(context.Background(), consts.SERVICES.Rspamd)\n\t\t\tif err != nil {\n\t\t\t\terr = fmt.Errorf(\"Failed to restart rspamd container: %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\t// DKIM public key is typically stored in a specific location in the container or host\n\t// Assuming we use docker exec to read the DKIM public key from the rspamd container\n\tdkimPub, err := public.ReadFile(dkimPubPath)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"Cannot read DKIM public key: %v\", err)\n\t\treturn","sourceCodeStart":610,"sourceCodeEnd":646,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/domains/domains.go#L610-L646","documentation":"Thrown when public.WriteFile fails to persist the modified rspamd DKIM signing config after inserting the domain's DKIM block before #BT_DOMAIN_DKIM_END. The library aborts the signing-config update (and skips the rspamd restart) because leaving the file unwritten would desync signing configuration.","triggerScenarios":"public.WriteFile(signConfPath, signContent) returns an error — read-only filesystem, disk full (ENOSPC), permission denied on write/open for truncate, parent directory missing, or immutable file attribute.","commonSituations":"Disk full on the mail server volume, config volume mounted read-only after a container update, rspamd config owned by _rspamd while BillionMail writes as another user, chattr +i set by hardening scripts.","solutions":["Free disk space (df -h) if ENOSPC, then re-run RepairDKIMSigningConfig","Fix ownership/permissions on the config file and its directory so the writer can create/truncate it","Check for immutable flag (lsattr/chattr -i) and read-only mounts","Verify the path's parent directory exists and is writable"],"exampleFix":"// before\n_, err = public.WriteFile(signConfPath, signContent)\n// after: diagnose write failure explicitly\nif errors.Is(err, syscall.ENOSPC) {\n    log.Printf(\"disk full; free space before retrying DKIM config write\")\n}\n_, err = public.WriteFile(signConfPath, signContent)","handlingStrategy":"try-catch","validationCode":"fi, err := os.Stat(filepath.Dir(signConfPath)); if err != nil || !fi.IsDir() { return fmt.Errorf(\"config dir missing\") }\nif err := syscall.Access(filepath.Dir(signConfPath), os.O_RDWR); err != nil { return fmt.Errorf(\"config dir not writable\") }","typeGuard":"func isWritable(path string) bool {\n    f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0o644)\n    if err != nil { return false }\n    f.Close()\n    return true\n}","tryCatchPattern":"if _, err := public.WriteFile(signConfPath, signContent); err != nil {\n    log.Printf(\"DKIM sign config write failed at %s: %v — check disk space, perms, read-only mounts\", signConfPath, err)\n    return err\n}","preventionTips":["Monitor disk usage on the config volume and alert before it fills","Never mount the rspamd config volume read-only when DKIM repair is enabled","Avoid chattr +i or hardening scripts that lock mail config files"],"tags":["filesystem","dkim","write-failure","rspamd"],"backgroundTag":"file-write-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}