{"record":{"id":"77085485c8143dde","repo":"MHSanaei/3x-ui","slug":"destination-dsn-is-required","errorCode":null,"errorMessage":"destination DSN is required","messagePattern":"destination DSN is required","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/database/migrate_data.go","lineNumber":72,"sourceCode":"\t\t&model.Host{},\n\t\t&model.NodeClientTraffic{},\n\t\t&model.NodeClientIp{},\n\t\t&model.ClientGlobalTraffic{},\n\t\t&model.OutboundSubscription{},\n\t}\n}\n\n// MigrateData copies every row from the configured SQLite file at srcPath into\n// a fresh PostgreSQL database described by dstDSN. The destination tables are\n// (re)created with AutoMigrate; truncate and copy then run in one transaction,\n// so a failed migration leaves the destination data unchanged. Source data is\n// left untouched.\nfunc MigrateData(srcPath, dstDSN string) error {\n\tif _, err := os.Stat(srcPath); err != nil {\n\t\treturn fmt.Errorf(\"source sqlite not found at %s: %w\", srcPath, err)\n\t}\n\tif dstDSN == \"\" {\n\t\treturn errors.New(\"destination DSN is required\")\n\t}\n\n\tif err := os.MkdirAll(path.Dir(srcPath), 0o755); err != nil {\n\t\treturn err\n\t}\n\n\tsrcDSN := srcPath + \"?_journal_mode=WAL&_busy_timeout=10000\"\n\tsrc, err := gorm.Open(sqlite.Open(srcDSN), &gorm.Config{Logger: logger.Discard})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"open sqlite source: %w\", err)\n\t}\n\tsrcSQL, err := src.DB()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer srcSQL.Close()\n\n\tdst, err := gorm.Open(postgres.Open(dstDSN), &gorm.Config{Logger: logger.Discard})","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/database/migrate_data.go#L54-L90","documentation":"MigrateData (SQLite→Postgres migration) requires a non-empty destination DSN and rejects the call before touching either database when dstDSN is ''. It is the symmetric guard to the XUI_DB_TYPE check: copying rows into 'no database' is meaningless. The source file at srcPath must also exist (checked immediately before).","triggerScenarios":"Running the migration CLI/endpoint with the --dst flag omitted or an empty string; a wrapper script that reads the DSN from an unset env var and passes '' through.","commonSituations":"First-time migration to Postgres where the operator forgets to export the DSN variable the script expects; shell quoting mistakes that expand to empty.","solutions":["Pass a valid Postgres DSN: ./x-ui migrate-db --dst 'postgres://user:pass@host:5432/xui?sslmode=disable' (or set the env var your wrapper reads)","Confirm the source path exists at the reported location before retrying","Echo the variable before running if a script builds the flag dynamically"],"exampleFix":"# before\n./x-ui migrate --src /etc/x-ui/x-ui.db --dst \"\"\n\n# after\n./x-ui migrate --src /etc/x-ui/x-ui.db --dst \"postgres://xui:pass@10.0.0.5:5432/xui\"","handlingStrategy":"validation","validationCode":"if dstDSN == \"\" {\n    return errors.New(\"pass the postgres DSN, e.g. postgres://user:pass@host:5432/xui\")\n}","typeGuard":null,"tryCatchPattern":"if err := database.MigrateData(src, dst); err != nil {\n    if strings.Contains(err.Error(), \"destination DSN is required\") {\n        // fix invocation, not data: prompt for DSN and re-run\n    }\n    return err\n}","preventionTips":["Require the DSN flag in migration scripts (fail on empty) before calling MigrateData","Dry-run psql connectivity with the same DSN first"],"tags":["database","migration","postgres","configuration"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}