Billionmail/BillionMail · critical

failed to apply configurations: %v, rollback also failed: %v

Error message

failed to apply configurations: %v, rollback also failed: %v

What it means

ApplyConfigsWithRollback failed to apply the new multi-IP domain configurations AND the rollback of the earlier backups also failed. This is the worst-case outcome: the system is left in a partially modified, unknown config state with no automatic restoration, so the caller gets both errors combined.

Source

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

}

// ApplyConfigsWithRollback Apply configurations and rollback on failure
func (m *ConfigManager) ApplyConfigsWithRollback(ctx context.Context, configs []map[string]interface{}) error {

	m.fileLocker.Lock()
	defer m.fileLocker.Unlock()

	backups, err := m.createBackups(ctx)
	if err != nil {
		return fmt.Errorf("failed to create backup: %v", err)
	}

	// Apply configurations
	if err := m.applyConfigs(ctx, configs); err != nil {

		g.Log().Debugf(ctx, "Failed to apply configurations, rolling back... Error: %v", err)
		if rollbackErr := m.rollback(ctx, backups); rollbackErr != nil {
			return fmt.Errorf("failed to apply configurations: %v, rollback also failed: %v", err, rollbackErr)
		}
		return fmt.Errorf("failed to apply configurations, but rollback succeeded: %v", err)
	}

	m.cleanupBackups(ctx, backups)
	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 {

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Manually inspect postfix main.cf/master.cf and restore from the remaining .backup.* files under the config directory
  2. Investigate why both apply and rollback failed (usually disk/permissions) via mail server and app logs
  3. Fix the root cause, then re-run ApplyConfigsWithRollback to converge to a known state
  4. Consider verifying backup files exist (or copying them to a temp dir) immediately before rollback

Example fix

// before
if rollbackErr := m.rollback(ctx, backups); rollbackErr != nil {
    return fmt.Errorf("failed to apply configurations: %v, rollback also failed: %v", err, rollbackErr)
}
// after
if rollbackErr := m.rollback(ctx, backups); rollbackErr != nil {
    g.Log().Errorf(ctx, "MANUAL INTERVENTION REQUIRED: apply err=%v rollback err=%v backups=%v", err, rollbackErr, backups)
    return fmt.Errorf("failed to apply configurations: %v, rollback also failed: %v", err, rollbackErr)
}
Defensive patterns

Strategy: fallback

Validate before calling

// No caller-side check can fully prevent dual failure; pre-flight the environment:
if !writable(PostfixMainCfPath) || !writable(PostfixMasterCfPath) {
    return errors.New("postfix configs not writable; apply would fail without rollback safety")
}

Try / catch

err := manager.ApplyConfigsWithRollback(ctx, configs)
if err != nil && strings.Contains(err.Error(), "rollback also failed") {
    // CRITICAL: unknown state — page ops, restore manually from .backup.* files
    pageOnCall(err)
    restoreFromLatestBackups(PostfixMainCfPath, PostfixMasterCfPath)
}

Prevention

When it happens

Trigger: m.applyConfigs(ctx, configs) returned an error (write failure, invalid config) and the subsequent m.rollback(ctx, backups) also errored — e.g. backup files were deleted between backup and rollback, or write permissions changed mid-operation.

Common situations: Disk filled up during apply so rollback writes also fail; another process deleted/moved the .backup.* files; config directory became read-only; concurrent ApplyConfigsWithRollback runs interfering despite locking scope.

Related errors


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