mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-12 07:36:07 +00:00
fix(node): never sweep a node's inbounds before their first adoption
Adding a node imports nothing; its pre-existing inbounds only become central rows on the first clean traffic-sync tick. But any save of the node (switching sync mode, picking tags after "Load inbounds from node") marks it config-dirty, and the next tick then ran ReconcileNode before that first adoption: with zero central rows the delete sweep saw every remote tag as undesired and destroyed the node's real inbounds - in "all" mode all of them - disconnecting live clients with no confirmation, and the master then reported "record not found". Track the first completed clean sync in nodes.inbounds_adopted_at and skip the sweep (pushes still run) until it is set, so "absent locally" can no longer be conflated with "deleted on the master". A node that has synced before still sweeps normally, including the offline last-inbound-deleted case. Existing nodes are seeded as adopted on upgrade to keep their behavior unchanged. Closes #5898
This commit is contained in:
+18
-1
@@ -928,7 +928,7 @@ func runSeeders(isUsersEmpty bool) error {
|
||||
}
|
||||
|
||||
if empty && isUsersEmpty {
|
||||
seeders := []string{"UserPasswordHash", "ClientsTable", "InboundClientsArrayFix", "InboundClientTgIdFix", "InboundClientSubIdFix", "FreedomFinalRulesReverseFix", "ApiTokensHash", "LegacyProxySettingsCleanup", "WireguardPeersToClients", "MtprotoSecretsToClients"}
|
||||
seeders := []string{"UserPasswordHash", "ClientsTable", "InboundClientsArrayFix", "InboundClientTgIdFix", "InboundClientSubIdFix", "FreedomFinalRulesReverseFix", "ApiTokensHash", "LegacyProxySettingsCleanup", "WireguardPeersToClients", "MtprotoSecretsToClients", "NodeInboundsAdopted"}
|
||||
for _, name := range seeders {
|
||||
if err := db.Create(&model.HistoryOfSeeders{SeederName: name}).Error; err != nil {
|
||||
return err
|
||||
@@ -1021,6 +1021,12 @@ func runSeeders(isUsersEmpty bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
if !slices.Contains(seedersHistory, "NodeInboundsAdopted") {
|
||||
if err := seedNodeInboundsAdopted(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := seedHostsFromExternalProxy(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1055,6 +1061,17 @@ func runSeeders(isUsersEmpty bool) error {
|
||||
return normalizeSettingPaths()
|
||||
}
|
||||
|
||||
// seedNodeInboundsAdopted keeps the pre-existing reconcile behavior for nodes
|
||||
// that were already syncing before the inbounds_adopted_at gate was introduced.
|
||||
func seedNodeInboundsAdopted() error {
|
||||
if err := db.Model(&model.Node{}).
|
||||
Where("inbounds_adopted_at = 0").
|
||||
Update("inbounds_adopted_at", time.Now().Unix()).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Create(&model.HistoryOfSeeders{SeederName: "NodeInboundsAdopted"}).Error
|
||||
}
|
||||
|
||||
func seedHostGroupIds() error {
|
||||
var history []string
|
||||
if err := db.Model(&model.HistoryOfSeeders{}).Pluck("seeder_name", &history).Error; err != nil {
|
||||
|
||||
@@ -719,6 +719,10 @@ type Node struct {
|
||||
ConfigDirty bool `json:"configDirty" gorm:"default:false"`
|
||||
ConfigDirtyAt int64 `json:"configDirtyAt"`
|
||||
|
||||
// InboundsAdoptedAt records the first clean traffic sync that imported the
|
||||
// node's pre-existing inbounds; reconcile must not sweep remote tags before it.
|
||||
InboundsAdoptedAt int64 `json:"-" gorm:"column:inbounds_adopted_at;default:0"`
|
||||
|
||||
InboundCount int `json:"inboundCount" gorm:"-" example:"5"`
|
||||
ClientCount int `json:"clientCount" gorm:"-" example:"27"`
|
||||
OnlineCount int `json:"onlineCount" gorm:"-" example:"3"`
|
||||
|
||||
Reference in New Issue
Block a user