mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-11 23:55:01 +03:00
fix(clients): stop client ops from reverting a renewal committed mid-op
Invariant: an operation on an inbound's clients writes back only what it changed, onto the settings as committed when it writes. Every client op (add, edit, delete, bulk adjust/detach/delete/set-enable) read the inbound outside the serial traffic writer and then tx.Save'd the whole row inside it. A traffic tick that committed in between - auto-renew re-enabling a client, the delayed-start conversion, a node adoption - was overwritten with the stale copy. A renewed neighbour ended up enable=false with its old expiry in settings while client_traffics said enabled, so the next runtime rebuild dropped a healthy client nobody had touched. The bulk ops then ran a full SyncInbound from those stale settings, copying enable=false into the client record too. Each site now commits through commitInboundClientSettings: inside the serialized tx it three-way merges the op's edit (read -> output, per client and per field) onto the committed settings and updates only the settings column, which also stops the stale up/down/enable inbound columns being written back. advancePushedInbound now records what the per-client push delivered rather than the merged settings, so the node's reconcile-skip fingerprint cannot claim a renewal it never received.
This commit is contained in:
@@ -39,16 +39,18 @@ func sameClientConfigExceptUpdatedAt(a, b map[string]any) bool {
|
||||
return aerr == nil && berr == nil && string(an) == string(bn)
|
||||
}
|
||||
|
||||
// advancePushedInbound advances the node's reconcile-skip fingerprint from the
|
||||
// pre-edit settings to the saved ones after every per-client push succeeded.
|
||||
func advancePushedInbound(rt runtime.Runtime, prevSettings string, ib *model.Inbound) {
|
||||
// advancePushedInbound advances the node's reconcile-skip fingerprint to what the
|
||||
// per-client pushes delivered, not the saved settings a traffic tick may have extended.
|
||||
func advancePushedInbound(rt runtime.Runtime, prevSettings, pushedSettings string, ib *model.Inbound) {
|
||||
rem, ok := rt.(*runtime.Remote)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
prev := *ib
|
||||
prev.Settings = prevSettings
|
||||
rem.AdvancePushedInbound(&prev, ib)
|
||||
pushed := *ib
|
||||
pushed.Settings = pushedSettings
|
||||
rem.AdvancePushedInbound(&prev, &pushed)
|
||||
}
|
||||
|
||||
// delInboundClients removes several clients from a single inbound in one pass:
|
||||
@@ -184,7 +186,7 @@ func (s *ClientService) delInboundClients(inboundSvc *InboundService, inboundId
|
||||
}
|
||||
}
|
||||
}
|
||||
if e := tx.Save(oldInbound).Error; e != nil {
|
||||
if e := commitInboundClientSettings(tx, oldInbound, prevSettings); e != nil {
|
||||
return e
|
||||
}
|
||||
detached := make([]string, 0, len(targets))
|
||||
@@ -249,7 +251,7 @@ func (s *ClientService) delInboundClients(inboundSvc *InboundService, inboundId
|
||||
}
|
||||
}
|
||||
if nodePush && !nodePushFailed {
|
||||
advancePushedInbound(nodeRt, prevSettings, oldInbound)
|
||||
advancePushedInbound(nodeRt, prevSettings, string(newSettings), oldInbound)
|
||||
}
|
||||
|
||||
return needRestart, nil
|
||||
@@ -563,7 +565,7 @@ func (s *ClientService) AddInboundClient(inboundSvc *InboundService, data *model
|
||||
return e
|
||||
}
|
||||
}
|
||||
if e := tx.Save(oldInbound).Error; e != nil {
|
||||
if e := commitInboundClientSettings(tx, oldInbound, prevSettings); e != nil {
|
||||
return e
|
||||
}
|
||||
if err := s.ApplyInboundClientDelta(tx, oldInbound.Id, addedClients, nil); err != nil {
|
||||
@@ -648,7 +650,7 @@ func (s *ClientService) AddInboundClient(inboundSvc *InboundService, data *model
|
||||
}
|
||||
}
|
||||
if push {
|
||||
advancePushedInbound(rt, prevSettings, oldInbound)
|
||||
advancePushedInbound(rt, prevSettings, string(newSettings), oldInbound)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -980,7 +982,7 @@ func (s *ClientService) UpdateInboundClient(inboundSvc *InboundService, data *mo
|
||||
}
|
||||
}
|
||||
|
||||
if e := tx.Save(oldInbound).Error; e != nil {
|
||||
if e := commitInboundClientSettings(tx, oldInbound, prevSettings); e != nil {
|
||||
return e
|
||||
}
|
||||
// Rename the client record in the same transaction as the settings JSON
|
||||
@@ -1074,7 +1076,7 @@ func (s *ClientService) UpdateInboundClient(inboundSvc *InboundService, data *mo
|
||||
if err1 != nil {
|
||||
logger.Warning("Error in updating client on", rt.Name(), ":", err1)
|
||||
} else {
|
||||
advancePushedInbound(rt, prevSettings, oldInbound)
|
||||
advancePushedInbound(rt, prevSettings, string(newSettings), oldInbound)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1184,7 +1186,7 @@ func (s *ClientService) DelInboundClientByEmail(inboundSvc *InboundService, inbo
|
||||
return e
|
||||
}
|
||||
}
|
||||
if e := tx.Save(oldInbound).Error; e != nil {
|
||||
if e := commitInboundClientSettings(tx, oldInbound, prevSettings); e != nil {
|
||||
return e
|
||||
}
|
||||
if err := s.ApplyInboundClientDelta(tx, inboundId, nil, []string{email}); err != nil {
|
||||
@@ -1248,7 +1250,7 @@ func (s *ClientService) DelInboundClientByEmail(inboundSvc *InboundService, inbo
|
||||
if err1 != nil {
|
||||
logger.Warning("Error in deleting client on", rt.Name(), ":", err1)
|
||||
} else {
|
||||
advancePushedInbound(rt, prevSettings, oldInbound)
|
||||
advancePushedInbound(rt, prevSettings, string(newSettings), oldInbound)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user