Billionmail/BillionMail · error

failed to create backup for %s: %v

Error message

failed to create backup for %s: %v

What it means

createBackups snapshots Postfix main.cf and master.cf before ApplyConfigsWithRollback modifies them. When ioutil.WriteFile cannot write the timestamped backup file (<path>.backup.<UnixNano>), the whole apply operation aborts before any change is made. This means the backup directory is not writable or the filesystem is broken.

Source

Thrown at core/internal/service/multi_ip_domain/config_manager.go:94

	return nil
}

// createBackups Create backups for Postfix's main.cf and master.cf
func (m *ConfigManager) createBackups(ctx context.Context) (map[string]string, error) {
	backups := make(map[string]string)

	files := []string{PostfixMainCfPath, PostfixMasterCfPath}

	for _, file := range files {
		if gfile.Exists(file) {
			content, err := ioutil.ReadFile(file)
			if err != nil {
				return nil, fmt.Errorf("failed to read file %s: %v", file, err)
			}

			backupPath := fmt.Sprintf("%s.backup.%d", file, time.Now().UnixNano())
			if err := ioutil.WriteFile(backupPath, content, 0644); err != nil {
				return nil, fmt.Errorf("failed to create backup for %s: %v", file, err)
			}
			g.Log().Debugf(ctx, "Created backup for %s at %s", file, backupPath)
			backups[file] = backupPath
		}
	}

	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)

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Check the wrapped %v OS error and fix filesystem permissions: chown/chmod the core/conf/postfix directory so the process user can write.
  2. Verify the volume is not mounted read-only (docker inspect the mount's RW flag); remount as writable.
  3. Free disk space or raise the quota on the filesystem holding the conf directory.
  4. If SELinux is enforcing, relabel with restorecon or adjust the container's SELinux policy.

Example fix

// before
if err := ioutil.WriteFile(backupPath, content, 0644); err != nil {
	return nil, fmt.Errorf("failed to create backup for %s: %v", file, err)
}
// after
if err := os.MkdirAll(filepath.Dir(backupPath), 0o755); err != nil {
	return nil, fmt.Errorf("backup dir unavailable: %v", err)
}
if err := ioutil.WriteFile(backupPath, content, 0o600); err != nil {
	return nil, fmt.Errorf("failed to create backup for %s (check dir permissions/disk space): %v", file, err)
}
Defensive patterns

Strategy: validation

Validate before calling

// before calling ApplyConfigsWithRollback
for _, f := range []string{multi_ip_domain.PostfixMainCfPath, multi_ip_domain.PostfixMasterCfPath} {
	if fi, err := os.Stat(f); err == nil {
		if err := syscall.Access(f, os.O_WRONLY); err != nil {
			return fmt.Errorf("no write access to %s (or its dir): %v", f, err)
		}
		_ = fi
	}
}
out, _ := exec.Command("df", "-h", filepath.Dir(multi_ip_domain.PostfixMainCfPath)).Output() // ensure free space

Try / catch

if err := mgr.ApplyConfigsWithRollback(ctx, configs); err != nil {
	if strings.Contains(err.Error(), "failed to create backup") {
		// fix permissions/disk before retry; nothing was modified yet
	}
}

Prevention

When it happens

Trigger: ioutil.WriteFile fails writing '<main.cf|master.cf>.backup.<unixnano>': read-only filesystem, missing write permission on conf/postfix dir, disk full, or the path is a directory.

Common situations: Container running with a read-only mount over ../conf/postfix; running as non-root user without ownership of the postfix conf dir; disk quota exhausted; SELinux/AppArmor denial.

Related errors


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