{"record":{"id":"9b8d2e0608bc25b3","repo":"MHSanaei/3x-ui","slug":"source-dsn-is-required","errorCode":null,"errorMessage":"source DSN is required","messagePattern":"source DSN is required","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/database/migrate_data.go","lineNumber":158,"sourceCode":"\n\t// setval is never rolled back by PostgreSQL, so sequences are resynced only\n\t// after the transaction has committed.\n\tif err := resetPostgresSequences(dst); err != nil {\n\t\tlog.Printf(\"warning: failed to reset some postgres sequences: %v\", err)\n\t}\n\n\tlog.Printf(\"Migration complete: %d rows across %d tables.\", totalRows, len(migrationModels()))\n\tlog.Println(\"Set XUI_DB_TYPE=postgres and XUI_DB_DSN=... in /etc/default/x-ui, then restart x-ui.\")\n\treturn nil\n}\n\n// ExportPostgresToSQLite copies every row from the PostgreSQL database described\n// by srcDSN into a fresh SQLite file at dstPath. It is the reverse of\n// MigrateData and is used to hand a PostgreSQL-backed panel a portable .db file.\n// dstPath is created/overwritten; the PostgreSQL source is left untouched.\nfunc ExportPostgresToSQLite(srcDSN, dstPath string) error {\n\tif srcDSN == \"\" {\n\t\treturn errors.New(\"source DSN is required\")\n\t}\n\tif err := os.MkdirAll(path.Dir(dstPath), 0o755); err != nil {\n\t\treturn err\n\t}\n\t// Start from an empty file so AutoMigrate creates the canonical schema.\n\tif err := os.Remove(dstPath); err != nil && !os.IsNotExist(err) {\n\t\treturn err\n\t}\n\n\tsrc, err := gorm.Open(postgres.Open(srcDSN), &gorm.Config{Logger: logger.Discard})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"open postgres source: %w\", err)\n\t}\n\tsrcSQL, err := src.DB()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer srcSQL.Close()","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/database/migrate_data.go#L140-L176","documentation":"ExportPostgresToSQLite (Postgres→SQLite export, the reverse of MigrateData) requires a non-empty srcDSN and fails fast otherwise. Unlike the SQLite→Postgres direction, the source DSN is a parameter rather than the process env, so passing '' means there is no Postgres to read from. The destination file is removed/recreated, so the guard runs before any destructive step.","triggerScenarios":"Calling ExportPostgresToSQLite programmatically with an empty first argument; a CLI wrapper that resolves the DSN from env but the env var is unset in that shell.","commonSituations":"Handing a Postgres-backed panel a portable .db file; downgrading back to SQLite; scripts run under systemd where the DSN env var is missing from the unit.","solutions":["Pass the same DSN used for XUI_DB_DSN as srcDSN","If scripting, default it explicitly: srcDSN := os.Getenv(\"XUI_DB_DSN\") and fail with a clear message if empty","Verify connectivity with psql '<dsn>' -c 'select 1' before exporting"],"exampleFix":"// before\nerr := database.ExportPostgresToSQLite(\"\", dstPath)\n\n// after\ndsn := os.Getenv(\"XUI_DB_DSN\")\nif dsn == \"\" { log.Fatal(\"XUI_DB_DSN required for export\") }\nerr := database.ExportPostgresToSQLite(dsn, dstPath)","handlingStrategy":"validation","validationCode":"dsn := os.Getenv(\"XUI_DB_DSN\")\nif dsn == \"\" {\n    log.Fatal(\"XUI_DB_DSN must be set for postgres export\")\n}","typeGuard":null,"tryCatchPattern":"if err := database.ExportPostgresToSQLite(dsn, dst); err != nil {\n    if strings.Contains(err.Error(), \"source DSN is required\") {\n    log.Fatal(\"provide the postgres DSN as the first argument\")\n    }\n    return err\n}","preventionTips":["Default srcDSN from XUI_DB_DSN and validate non-empty before calling","Ensure the env var is present in systemd units / cron shells that run exports"],"tags":["database","migration","export","configuration"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}