{"record":{"id":"ecf76b880774bce7","repo":"MHSanaei/3x-ui","slug":"sqlite-backup-is-unavailable-for-postgresql","errorCode":null,"errorMessage":"sqlite backup is unavailable for PostgreSQL","messagePattern":"sqlite backup is unavailable for PostgreSQL","errorType":"http","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/database/db.go","lineNumber":2182,"sourceCode":"}\n\nfunc IsNotFound(err error) bool {\n\treturn errors.Is(err, gorm.ErrRecordNotFound)\n}\n\nfunc IsSQLiteDB(file io.ReaderAt) (bool, error) {\n\tsignature := []byte(\"SQLite format 3\\x00\")\n\tbuf := make([]byte, len(signature))\n\t_, err := file.ReadAt(buf, 0)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn bytes.Equal(buf, signature), nil\n}\n\nfunc BackupSQLite(dstPath string) (err error) {\n\tif IsPostgres() {\n\t\treturn errors.New(\"sqlite backup is unavailable for PostgreSQL\")\n\t}\n\tif db == nil {\n\t\treturn errors.New(\"database is not initialized\")\n\t}\n\tif _, err := os.Lstat(dstPath); err == nil {\n\t\treturn fmt.Errorf(\"sqlite backup destination already exists: %s\", dstPath)\n\t} else if !errors.Is(err, os.ErrNotExist) {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\t_ = os.Remove(dstPath)\n\t\t}\n\t}()\n\n\tctx, cancel := context.WithTimeout(context.Background(), backupSQLiteTimeout)\n\tdefer cancel()\n","sourceCodeStart":2164,"sourceCodeEnd":2200,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/database/db.go#L2164-L2200","documentation":"BackupSQLite guards at the top with IsPostgres() and refuses to run when the panel is booted against PostgreSQL, because there is no SQLite file to back up — the logical database lives in Postgres. It is a deliberate fail-fast guard, not a corruption signal. Callers that offer a 'backup database' button should hide or re-route it when Postgres is active.","triggerScenarios":"Invoking the backup endpoint (panel settings backup or CLI path that calls database.BackupSQLite) while XUI_DB_TYPE=postgres; a cron or script written against SQLite that survived a migration to Postgres.","commonSituations":"After migrating SQLite→Postgres the admin still clicks the old backup button; automation scripts calling the backup API unchanged post-migration.","solutions":["Use a Postgres-native backup instead: pg_dump against the DSN in XUI_DB_DSN","If code calls BackupSQLite, branch on database.IsPostgres() first and export via ExportPostgresToSQLite if a .db file is genuinely wanted","Remove or condition scheduled backup jobs that assume SQLite"],"exampleFix":"// before\nerr := database.BackackSQLite(dst) // typo aside, called unconditionally\n\n// after\nif database.IsPostgres() {\n    return fmt.Errorf(\"use pg_dump; sqlite backup unavailable under postgres\")\n}\nerr := database.BackupSQLite(dst)","handlingStrategy":"validation","validationCode":"if database.IsPostgres() {\n    return errors.New(\"skip sqlite backup: panel runs postgres; use pg_dump\")\n}\nreturn database.BackupSQLite(dst)","typeGuard":null,"tryCatchPattern":"if err := database.BackupSQLite(dst); err != nil {\n    if strings.Contains(err.Error(), \"unavailable for PostgreSQL\") {\n        // route to pg_dump instead of failing the backup job\n    }\n}","preventionTips":["Branch backup tooling on database.IsPostgres() after any DB migration","For Postgres, schedule pg_dump rather than the panel's SQLite path"],"tags":["database","backup","postgres","sqlite"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}