{"record":{"id":"5be28371a570b0ae","repo":"Billionmail/BillionMail","slug":"failed-to-rollback-file-s-v","errorCode":null,"errorMessage":"failed to rollback file %s: %v","messagePattern":"failed to rollback file (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"core/internal/service/multi_ip_domain/config_manager.go","lineNumber":118,"sourceCode":"\n\treturn backups, nil\n}\n\n// rollback Roll back configurations\nfunc (m *ConfigManager) rollback(ctx context.Context, backups map[string]string) error {\n\tvar allErrors []string\n\tfor file, backup := range backups {\n\t\tif gfile.Exists(backup) {\n\t\t\tg.Log().Infof(ctx, \"Rolling back %s from %s\", file, backup)\n\t\t\tcontent, err := ioutil.ReadFile(backup)\n\t\t\tif err != nil {\n\t\t\t\terr = fmt.Errorf(\"failed to read backup file %s: %v\", backup, err)\n\t\t\t\tallErrors = append(allErrors, err.Error())\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif err := ioutil.WriteFile(file, content, 0644); err != nil {\n\t\t\t\terr = fmt.Errorf(\"failed to rollback file %s: %v\", file, err)\n\t\t\t\tallErrors = append(allErrors, err.Error())\n\t\t\t}\n\t\t}\n\t}\n\tif len(allErrors) > 0 {\n\t\treturn fmt.Errorf(\"%s\", strings.Join(allErrors, \"; \"))\n\t}\n\treturn nil\n}\n\n// cleanupBackups Clean up backup files\nfunc (m *ConfigManager) cleanupBackups(ctx context.Context, backups map[string]string) {\n\tfor _, backup := range backups {\n\t\tif gfile.Exists(backup) {\n\t\t\tg.Log().Debugf(ctx, \"Cleaning up backup file: %s\", backup)\n\t\t\t_ = os.Remove(backup)\n\t\t}\n\t}","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/multi_ip_domain/config_manager.go#L100-L136","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nif err := ioutil.WriteFile(file, content, 0644); err != nil {\n\terr = fmt.Errorf(\"failed to rollback file %s: %v\", file, err)\n\tallErrors = append(allErrors, err.Error())\n}\n// after\nif err := os.Chmod(file, 0o644); err == nil {\n\terr = ioutil.WriteFile(file, content, 0o644)\n}\nif err != nil {\n\treturn fmt.Errorf(\"failed to rollback file %s (manual restore from %s required): %v\", file, backup, err)\n}","handlingStrategy":"try-catch","validationCode":"// ensure configs are writable before apply\nfor _, f := range []string{multi_ip_domain.PostfixMainCfPath, multi_ip_domain.PostfixMasterCfPath} {\n\tif err := syscall.Access(f, os.O_WRONLY); err != nil {\n\t\treturn fmt.Errorf(\"%s not writable, rollback would fail: %v\", f, err)\n\t}\n}","typeGuard":null,"tryCatchPattern":"if err := mgr.ApplyConfigsWithRollback(ctx, configs); err != nil {\n\tif strings.Contains(err.Error(), \"rollback also failed\") {\n\t\t// configs may be inconsistent: restore from *.backup.* immediately\n\t\tbks, _ := filepath.Glob(multi_ip_domain.PostfixMainCfPath + \".backup.*\")\n\t\t_ = bks\n\t}\n}","preventionTips":["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."],"tags":["filesystem","permissions","rollback","go"],"backgroundTag":"rollback-restore-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}