Billionmail/BillionMail · critical
failed to rollback file %s: %v
Error message
failed to rollback file %s: %v
What it means
rollback writes each backup's content back over the live main.cf/master.cf. If ioutil.WriteFile fails, the error is accumulated into allErrors and the aggregate is returned, so ApplyConfigsWithRollback reports that the apply failed AND rollback failed — leaving configs in a possibly modified state. This is the most dangerous failure in the apply/rollback flow because it means the system may be left inconsistent.
Source
Thrown at core/internal/service/multi_ip_domain/config_manager.go:118
return backups, nil
}
// rollback Roll back configurations
func (m *ConfigManager) rollback(ctx context.Context, backups map[string]string) error {
var allErrors []string
for file, backup := range backups {
if gfile.Exists(backup) {
g.Log().Infof(ctx, "Rolling back %s from %s", file, backup)
content, err := ioutil.ReadFile(backup)
if err != nil {
err = fmt.Errorf("failed to read backup file %s: %v", backup, err)
allErrors = append(allErrors, err.Error())
continue
}
if err := ioutil.WriteFile(file, content, 0644); err != nil {
err = fmt.Errorf("failed to rollback file %s: %v", file, err)
allErrors = append(allErrors, err.Error())
}
}
}
if len(allErrors) > 0 {
return fmt.Errorf("%s", strings.Join(allErrors, "; "))
}
return nil
}
// cleanupBackups Clean up backup files
func (m *ConfigManager) cleanupBackups(ctx context.Context, backups map[string]string) {
for _, backup := range backups {
if gfile.Exists(backup) {
g.Log().Debugf(ctx, "Cleaning up backup file: %s", backup)
_ = os.Remove(backup)
}
}View on GitHub (pinned to fc36c76c05)
Solutions
- Read allErrors in the returned error to see which file failed and why; fix the reported permission/disk problem.
- Manually restore: copy <file>.backup.<nano> over main.cf/master.cf with sudo, then verify with postfix check.
- chown/chmod the conf/postfix dir and the config files to the running process user before retrying.
- Free disk space / fix the read-only mount, then re-run rollback or ApplyConfigsWithRollback.
Example fix
// before
if err := ioutil.WriteFile(file, content, 0644); err != nil {
err = fmt.Errorf("failed to rollback file %s: %v", file, err)
allErrors = append(allErrors, err.Error())
}
// after
if err := os.Chmod(file, 0o644); err == nil {
err = ioutil.WriteFile(file, content, 0o644)
}
if err != nil {
return fmt.Errorf("failed to rollback file %s (manual restore from %s required): %v", file, backup, err)
} Defensive patterns
Strategy: try-catch
Validate before calling
// ensure configs are writable before apply
for _, f := range []string{multi_ip_domain.PostfixMainCfPath, multi_ip_domain.PostfixMasterCfPath} {
if err := syscall.Access(f, os.O_WRONLY); err != nil {
return fmt.Errorf("%s not writable, rollback would fail: %v", f, err)
}
} Try / catch
if err := mgr.ApplyConfigsWithRollback(ctx, configs); err != nil {
if strings.Contains(err.Error(), "rollback also failed") {
// configs may be inconsistent: restore from *.backup.* immediately
bks, _ := filepath.Glob(multi_ip_domain.PostfixMainCfPath + ".backup.*")
_ = bks
}
} Prevention
- Keep configs writable by the service account at all times.
- Alert on read-only-filesystem events on the host.
- Take an out-of-band copy of main.cf/master.cf before programmatic applies.
- Run postfix check after any rollback to validate consistency.
When it happens
Trigger: ioutil.WriteFile fails restoring the original file: target file/dir permissions changed during apply, read-only remount, disk full, or the original file was replaced by a directory/symlink.
Common situations: The apply step or an external process changed ownership/permissions of main.cf/master.cf before rollback; container filesystem went read-only due to disk errors; inode exhausted.
Related errors
- failed to read backup file %s: %v
- error reading project configuration file: %v
- error writing project configuration file: %v
- error writing knowledge base file: %v
- error removing knowledge base file: %v
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/5be28371a570b0ae.
Report an issue: GitHub.