Billionmail/BillionMail · error
read dovecot.conf failed: %w
Error message
read dovecot.conf failed: %w
What it means
Raised in ensureDovecotConf when ioutil.ReadFile cannot load <confRoot>/dovecot.conf during quota plugin initialization. The Dovecot main config is missing at the expected path or unreadable, so the function cannot patch in the quota plugin directives; the error is wrapped with %w preserving the os error chain.
Source
Thrown at core/internal/service/mail_boxes/init_quota_plugin.go:87
// 6. mark & reload dovecot (best-effort)
if err := gfile.PutContents(markPath, fmt.Sprintf("First sync completed at %s", time.Now().Format("2006-01-02 15:04:05"))); err != nil {
g.Log().Warningf(ctx, "Failed to create the quota marker file: %v", err)
}
if err := reloadDovecot(ctx); err != nil {
g.Log().Warning(ctx, "reload dovecot failed", err)
}
g.Log().Info(ctx, "Quota plugin initialization completed")
return nil
}
func ensureDovecotConf(confRoot string) error {
path := filepath.Join(confRoot, "dovecot.conf")
data, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("read dovecot.conf failed: %w", err)
}
content := string(data)
// Search for the "mail_plugins" line
mailPluginsRe := regexp.MustCompile(`(?m)^\s*mail_plugins\s*=.*$`)
if mailPluginsRe.MatchString(content) {
// If it exists but there is no quota, add a quota.
newContent := mailPluginsRe.ReplaceAllStringFunc(content, func(line string) string {
if strings.Contains(line, "quota") {
return line
}
return line + " quota"
})
content = newContent
} else {
// Insert before the first !include
includeIdx := strings.Index(content, "!include")
if includeIdx > -1 {View on GitHub (pinned to fc36c76c05)
Solutions
- Confirm dovecot.conf exists at confRoot (ls the directory from error 315's path)
- Fix file permissions so the process user can read it
- Restore the stock dovecot.conf from the repo conf/dovecot directory
- Check the wrapped error (%w) for the exact OS reason
Example fix
// before
data, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("read dovecot.conf failed: %w", err)
}
// after
data, err := ioutil.ReadFile(path)
if err != nil {
return fmt.Errorf("read dovecot.conf at %s failed: %w", path, err)
} Defensive patterns
Strategy: validation
Validate before calling
path := filepath.Join(confRoot, "dovecot.conf")
if f, err := os.Open(path); err != nil {
return fmt.Errorf("dovecot.conf unreadable at %s: %v", path, err)
} else {
f.Close()
} Try / catch
if err := mail_boxes.InitQuotaPluginAndUpdateUsedSpace(ctx); err != nil && strings.Contains(err.Error(), "read dovecot.conf failed") {
log.Printf("restore or chmod dovecot.conf: %v", errors.Unwrap(err))
} Prevention
- Never rename or delete stock dovecot.conf; use includes for customization
- Ensure conf files are world-readable for the service user
- Verify file presence after upgrades/migrations
- Use errors.Unwrap to log the underlying OS cause
When it happens
Trigger: ioutil.ReadFile(dovecot.conf) fails: file deleted or renamed, path permission denied, confRoot points to the wrong directory (often the cause of 315 being bypassed because the dir exists but the file doesn't).
Common situations: dovecot.conf renamed to dovecot.conf.custom by an admin; partial extraction of config files; running as a user without read permission on the conf dir; symlinked conf broken after migration.
Related errors
- dovecot conf dir not found: %s
- 333333write new dovecot-sql.conf.ext failed: %w
- failed to read dovecot config: %v
- failed to write dovecot config: %v
- unable to determine domain for noreply email
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/1a020be110449031.
Report an issue: GitHub.