inbound_migration.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. package service
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "strconv"
  7. "strings"
  8. "time"
  9. "github.com/mhsanaei/3x-ui/v3/internal/database"
  10. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  11. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  12. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  13. "gorm.io/gorm"
  14. )
  15. func (s *InboundService) MigrationRemoveOrphanedTraffics() {
  16. db := database.GetDB()
  17. query := fmt.Sprintf(
  18. "DELETE FROM client_traffics WHERE email NOT IN (SELECT email FROM clients) AND email NOT IN (SELECT %s %s)",
  19. database.JSONFieldText("client.value", "email"),
  20. database.JSONClientsFromInbound(),
  21. )
  22. result := db.Exec(query)
  23. if result.Error != nil {
  24. logger.Warning("MigrationRemoveOrphanedTraffics failed:", result.Error)
  25. return
  26. }
  27. if result.RowsAffected > 0 {
  28. logger.Infof("MigrationRemoveOrphanedTraffics: removed %d orphaned client_traffics row(s)", result.RowsAffected)
  29. }
  30. }
  31. func (s *InboundService) MigrationRequirements() (err error) {
  32. db := database.GetDB()
  33. tx := db.Begin()
  34. defer func() {
  35. if err == nil {
  36. if commitErr := tx.Commit().Error; commitErr != nil {
  37. err = commitErr
  38. return
  39. }
  40. if !database.IsPostgres() {
  41. if dbErr := db.Exec(`VACUUM "main"`).Error; dbErr != nil {
  42. logger.Warningf("VACUUM failed: %v", dbErr)
  43. }
  44. }
  45. } else {
  46. tx.Rollback()
  47. }
  48. }()
  49. if tx.Migrator().HasColumn(&model.Inbound{}, "all_time") {
  50. if err = tx.Migrator().DropColumn(&model.Inbound{}, "all_time"); err != nil {
  51. return
  52. }
  53. }
  54. if tx.Migrator().HasColumn(&xray.ClientTraffic{}, "all_time") {
  55. if err = tx.Migrator().DropColumn(&xray.ClientTraffic{}, "all_time"); err != nil {
  56. return
  57. }
  58. }
  59. if err = normalizeInboundShareAddressColumns(tx); err != nil {
  60. return
  61. }
  62. // Normalize "enable" columns to boolean on Postgres. Legacy SQLite data
  63. // (0/1 integers), partial migrations, or mixed write paths (public API
  64. // inbound updates that flow through UpdateClientStat + client syncs, plus
  65. // node traffic merge deltas) can leave the column as integer or with mixed
  66. // interpretation. This (combined with the dialect-aware
  67. // ClientTrafficEnableMergeExpr) prevents type problems in the node traffic
  68. // sync merge (SetRemoteTraffic) and makes the sync robust even when
  69. // inbounds are updated via the public API (incl. ones carrying
  70. // externalProxy in streamSettings). The same expression is also safe on
  71. // SQLite (no PG :: casts).
  72. if database.IsPostgres() {
  73. // Use DO block so it is idempotent and doesn't fail if already boolean.
  74. normalizeBool := func(table, col string) error {
  75. return tx.Exec(fmt.Sprintf(`
  76. DO $$
  77. BEGIN
  78. IF EXISTS (
  79. SELECT 1 FROM information_schema.columns
  80. WHERE table_name = '%s' AND column_name = '%s'
  81. AND data_type <> 'boolean'
  82. ) THEN
  83. ALTER TABLE %s ALTER COLUMN %s
  84. TYPE boolean USING (CASE WHEN %s::text IN ('1','true','t','yes') THEN true ELSE false END);
  85. END IF;
  86. END $$;`, table, col, table, col, col)).Error
  87. }
  88. for _, column := range [][2]string{{"inbounds", "enable"}, {"client_traffics", "enable"}, {"nodes", "enable"}, {"clients", "enable"}, {"api_tokens", "enabled"}, {"outbound_subscriptions", "enabled"}} {
  89. if err = normalizeBool(column[0], column[1]); err != nil {
  90. return
  91. }
  92. }
  93. }
  94. // Fix inbounds based problems
  95. var inbounds []*model.Inbound
  96. err = tx.Model(model.Inbound{}).Where("protocol IN (?)", []string{"vmess", "vless", "trojan", "shadowsocks", "hysteria"}).Find(&inbounds).Error
  97. if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
  98. return
  99. }
  100. for inbound_index := range inbounds {
  101. settings := map[string]any{}
  102. _ = json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
  103. if raw, exists := settings["clients"]; exists && raw == nil {
  104. settings["clients"] = []any{}
  105. }
  106. clients, ok := settings["clients"].([]any)
  107. if ok {
  108. // Fix Client configuration problems
  109. newClients := make([]any, 0, len(clients))
  110. hasVisionFlow := false
  111. for client_index := range clients {
  112. c := clients[client_index].(map[string]any)
  113. // Add email='' if it is not exists
  114. if _, ok := c["email"]; !ok {
  115. c["email"] = ""
  116. }
  117. // Convert string tgId to int64
  118. if _, ok := c["tgId"]; ok {
  119. tgId := c["tgId"]
  120. if tgIdStr, ok2 := tgId.(string); ok2 {
  121. tgIdInt64, err := strconv.ParseInt(strings.ReplaceAll(tgIdStr, " ", ""), 10, 64)
  122. if err == nil {
  123. c["tgId"] = tgIdInt64
  124. }
  125. }
  126. }
  127. // Remove "flow": "xtls-rprx-direct"
  128. if _, ok := c["flow"]; ok {
  129. if c["flow"] == "xtls-rprx-direct" {
  130. c["flow"] = ""
  131. }
  132. }
  133. if flow, _ := c["flow"].(string); flow == "xtls-rprx-vision" {
  134. hasVisionFlow = true
  135. }
  136. // Backfill created_at and updated_at
  137. if _, ok := c["created_at"]; !ok {
  138. c["created_at"] = time.Now().Unix() * 1000
  139. }
  140. c["updated_at"] = time.Now().Unix() * 1000
  141. newClients = append(newClients, any(c))
  142. }
  143. settings["clients"] = newClients
  144. // Drop orphaned testseed: VLESS-only field, only meaningful when at least
  145. // one client uses the exact xtls-rprx-vision flow. Older versions saved it
  146. // for any non-empty flow (including the UDP variant) or kept it after the
  147. // flow was cleared from the client modal — clean those up here.
  148. if inbounds[inbound_index].Protocol == model.VLESS && !hasVisionFlow {
  149. delete(settings, "testseed")
  150. }
  151. var modifiedSettings []byte
  152. modifiedSettings, err = json.MarshalIndent(settings, "", " ")
  153. if err != nil {
  154. return
  155. }
  156. inbounds[inbound_index].Settings = string(modifiedSettings)
  157. }
  158. // Add client traffic row for all clients which has email
  159. var modelClients []model.Client
  160. modelClients, err = s.GetClients(inbounds[inbound_index])
  161. if err != nil {
  162. return
  163. }
  164. for _, modelClient := range modelClients {
  165. if len(modelClient.Email) > 0 {
  166. var count int64
  167. if err = tx.Model(xray.ClientTraffic{}).Where("email = ?", modelClient.Email).Count(&count).Error; err != nil {
  168. return
  169. }
  170. if count == 0 {
  171. if err = s.AddClientStat(tx, inbounds[inbound_index].Id, &modelClient); err != nil {
  172. return
  173. }
  174. }
  175. }
  176. }
  177. // Heal clients table for installs where the one-shot seeder
  178. // skipped clients due to a tgId-string unmarshal error.
  179. if err = s.clientService.SyncInbound(tx, inbounds[inbound_index].Id, modelClients); err != nil {
  180. return
  181. }
  182. }
  183. if err = tx.Save(inbounds).Error; err != nil {
  184. return
  185. }
  186. // Remove orphaned traffics
  187. if err = tx.Where("inbound_id = 0").Delete(xray.ClientTraffic{}).Error; err != nil {
  188. return
  189. }
  190. // Migrate old MultiDomain to External Proxy
  191. var externalProxy []struct {
  192. Id int
  193. Port int
  194. StreamSettings string // text column on both DBs; safer than []byte for cross-DB scan
  195. }
  196. externalProxyQuery := `select id, port, stream_settings
  197. from inbounds
  198. WHERE protocol in ('vmess','vless','trojan')
  199. AND json_extract(stream_settings, '$.security') = 'tls'
  200. AND json_extract(stream_settings, '$.tlsSettings.settings.domains') IS NOT NULL`
  201. if database.IsPostgres() {
  202. externalProxyQuery = `select id, port, stream_settings
  203. from inbounds
  204. WHERE protocol in ('vmess','vless','trojan')
  205. AND NULLIF(stream_settings, '')::jsonb #>> '{security}' = 'tls'
  206. AND NULLIF(stream_settings, '')::jsonb #> '{tlsSettings,settings,domains}' IS NOT NULL`
  207. }
  208. err = tx.Raw(externalProxyQuery).Scan(&externalProxy).Error
  209. if err != nil || len(externalProxy) == 0 {
  210. return
  211. }
  212. for _, ep := range externalProxy {
  213. var reverses any
  214. var stream map[string]any
  215. _ = json.Unmarshal([]byte(ep.StreamSettings), &stream)
  216. if tlsSettings, ok := stream["tlsSettings"].(map[string]any); ok {
  217. if settings, ok := tlsSettings["settings"].(map[string]any); ok {
  218. if domains, ok := settings["domains"].([]any); ok {
  219. for _, domain := range domains {
  220. if domainMap, ok := domain.(map[string]any); ok {
  221. domainMap["forceTls"] = "same"
  222. domainMap["port"] = ep.Port
  223. domainMap["dest"] = domainMap["domain"].(string)
  224. delete(domainMap, "domain")
  225. }
  226. }
  227. }
  228. reverses = settings["domains"]
  229. delete(settings, "domains")
  230. }
  231. }
  232. stream["externalProxy"] = reverses
  233. newStream, marshalErr := json.MarshalIndent(stream, " ", " ")
  234. if marshalErr != nil {
  235. err = marshalErr
  236. return
  237. }
  238. if err = tx.Model(model.Inbound{}).Where("id = ?", ep.Id).Update("stream_settings", newStream).Error; err != nil {
  239. return
  240. }
  241. }
  242. // Legacy tag cleanup for old auto-generated tags (e.g. "0.0.0.0:443-...").
  243. // Must be cross-DB: INSTR/REPLACE work on SQLite; Postgres needs position().
  244. tagCleanup := `UPDATE inbounds
  245. SET tag = REPLACE(tag, '0.0.0.0:', '')
  246. WHERE INSTR(tag, '0.0.0.0:') > 0;`
  247. if database.IsPostgres() {
  248. tagCleanup = `UPDATE inbounds
  249. SET tag = REPLACE(tag, '0.0.0.0:', '')
  250. WHERE position('0.0.0.0:' in tag) > 0;`
  251. }
  252. err = tx.Exec(tagCleanup).Error
  253. if err != nil {
  254. return
  255. }
  256. return err
  257. }
  258. func (s *InboundService) MigrateDB() {
  259. if err := s.MigrationRequirements(); err != nil {
  260. logger.Errorf("MigrationRequirements failed: %v", err)
  261. }
  262. s.MigrationRemoveOrphanedTraffics()
  263. s.MigrationRestoreVisionFlow()
  264. }
  265. // MigrationRestoreVisionFlow repairs VLESS inbounds whose clients lost their
  266. // XTLS Vision flow because the inbound was not flow-eligible when the client was
  267. // written (e.g. an XHTTP inbound whose vlessenc encryption was enabled only
  268. // later). For each now-eligible inbound it restores flow=xtls-rprx-vision on
  269. // clients whose intended flow (their flow_override on a sibling inbound) is
  270. // Vision. Idempotent: once a client carries the flow it is skipped, so this is a
  271. // no-op on healthy installs and on subsequent boots.
  272. func (s *InboundService) MigrationRestoreVisionFlow() {
  273. db := database.GetDB()
  274. var inbounds []*model.Inbound
  275. if err := db.Model(&model.Inbound{}).
  276. Where("protocol = ?", model.VLESS).
  277. Find(&inbounds).Error; err != nil {
  278. logger.Warning("MigrationRestoreVisionFlow: load inbounds failed:", err)
  279. return
  280. }
  281. for _, ib := range inbounds {
  282. restored, changed := s.restoreVisionFlowForEligibleInbound(nil, ib.Settings, ib.StreamSettings, ib.Protocol)
  283. if !changed {
  284. continue
  285. }
  286. clients, err := s.GetClients(&model.Inbound{Settings: restored})
  287. if err != nil {
  288. logger.Warning("MigrationRestoreVisionFlow: parse clients for inbound", ib.Id, "failed:", err)
  289. continue
  290. }
  291. err = db.Transaction(func(tx *gorm.DB) error {
  292. if e := tx.Model(&model.Inbound{}).Where("id = ?", ib.Id).Update("settings", restored).Error; e != nil {
  293. return e
  294. }
  295. return s.clientService.SyncInbound(tx, ib.Id, clients)
  296. })
  297. if err != nil {
  298. logger.Warning("MigrationRestoreVisionFlow: update inbound", ib.Id, "failed:", err)
  299. continue
  300. }
  301. logger.Info("MigrationRestoreVisionFlow: restored XTLS Vision flow on inbound", ib.Id)
  302. }
  303. }