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

  1. Read allErrors in the returned error to see which file failed and why; fix the reported permission/disk problem.
  2. Manually restore: copy <file>.backup.<nano> over main.cf/master.cf with sudo, then verify with postfix check.
  3. chown/chmod the conf/postfix dir and the config files to the running process user before retrying.
  4. 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

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


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/5be28371a570b0ae. Report an issue: GitHub.