{"record":{"id":"2e41d42f7f854631","repo":"Billionmail/BillionMail","slug":"failed-to-write-dovecot-config-v","errorCode":null,"errorMessage":"failed to write dovecot config: %v","messagePattern":"failed to write dovecot config: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/mail_service/certificate.go","lineNumber":374,"sourceCode":"\t// Write certificate and key to files\n\tcertPath := public.AbsPath(filepath.Join(consts.SSL_PATH, \"dovecot.crt\"))\n\tkeyPath := public.AbsPath(filepath.Join(consts.SSL_PATH, \"dovecot.key\"))\n\n\tif err := os.WriteFile(certPath, []byte(csrPem), 0755); err != nil {\n\t\treturn fmt.Errorf(\"failed to write certificate file: %v\", err)\n\t}\n\n\tif err := os.WriteFile(keyPath, []byte(keyPem), 0755); err != nil {\n\t\treturn fmt.Errorf(\"failed to write key file: %v\", err)\n\t}\n\n\t// Update SSL certificate configuration\n\tconfig := string(content)\n\tconfig = c.updateConfigLine(config, \"ssl_cert\", \"<\"+certPath)\n\tconfig = c.updateConfigLine(config, \"ssl_key\", \"<\"+keyPath)\n\n\tif err := os.WriteFile(dovecotConf, []byte(config), 0755); err != nil {\n\t\treturn fmt.Errorf(\"failed to write dovecot config: %v\", err)\n\t}\n\n\treturn nil\n}\n\n// updateDovecotSNIConfig updates Dovecot SNI configuration\nfunc (c *Certificate) updateDovecotSNIConfig(domain, certPem, keyPem string) error {\n\t// Ensure domain directory exists\n\tdomainDir := filepath.Join(consts.SSL_PATH, domain)\n\tif err := os.MkdirAll(domainDir, 0755); err != nil {\n\t\treturn fmt.Errorf(\"failed to create domain directory: %v\", err)\n\t}\n\n\tsniCert := filepath.Join(domainDir, \"fullchain.pem\")\n\tsniKey := filepath.Join(domainDir, \"privkey.pem\")\n\n\t// Write certificate and key to files\n\tif err := os.WriteFile(sniCert, []byte(certPem), 0755); err != nil {","sourceCodeStart":356,"sourceCodeEnd":392,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/mail_service/certificate.go#L356-L392","documentation":"After updating ssl_cert/ssl_key lines in memory, updateDovecotConfig writes the modified config back to c.DovecotSslConf with os.WriteFile (0755). A failure is wrapped as 'failed to write dovecot config: %v'. At this point the new cert/key files are already on disk, so the config file may not reference them until this succeeds. Thrown by SetSSL and SetDovecotSSL.","triggerScenarios":"SetSSL/SetDovecotSSL invoked when the dovecot ssl conf file is read-only, owned by another user, on a full disk, or the path became unwritable after the read step.","commonSituations":"Config volume mounted read-only (common with bind-mounted /etc/dovecot); file immutable bit set (chattr +i); SELinux/AppArmor denying write; disk full after writing certs.","solutions":["Make the dovecot conf file writable (chmod/chown, remove chattr +i with chattr -i)","Remount the dovecot config volume read-write","Free disk space","After fixing, re-run SetSSL and restart the dovecot container so it picks up the new ssl_cert/ssl_key paths"],"exampleFix":"// before\nif err := os.WriteFile(dovecotConf, []byte(config), 0755); err != nil {\n    return fmt.Errorf(\"failed to write dovecot config: %v\", err)\n}\n// after\nif err := os.WriteFile(dovecotConf, []byte(config), 0644); err != nil {\n    return fmt.Errorf(\"failed to write dovecot config %s: %w\", dovecotConf, err)\n}\nreturn c.restartDovecot()","handlingStrategy":"try-catch","validationCode":"if info, err := os.Stat(dovecotConf); err != nil || info.IsDir() {\n    return fmt.Errorf(\"dovecot conf missing\")\n}\nif f, err := os.OpenFile(dovecotConf, os.O_WRONLY, 0644); err != nil {\n    return fmt.Errorf(\"dovecot conf not writable: %w\", err)\n} else {\n    f.Close()\n}","typeGuard":null,"tryCatchPattern":"if err := certSvc.SetSSL(ctx, domain); err != nil {\n    if strings.Contains(err.Error(), \"failed to write dovecot config\") {\n        // ensure conf volume is rw and not immutable, then retry\n    }\n    return err\n}","preventionTips":["Mount dovecot config volumes read-write, not read-only","Avoid chattr +i on generated config files","After success, restart the dovecot container so new ssl paths load"],"tags":["filesystem","dovecot","config","permissions"],"backgroundTag":"file-write-permission-denied","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"}