mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-24 11:57:15 +00:00
940842495968fc52a38fd0a8f6f8498dd6e70de5
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6f7a305239 |
fix(node): stop stale expiry sync from undoing client extensions (#6228) (#6231)
* fix(node): stop stale expiry sync from undoing client extensions (#6228) After an expired client is extended on the master, a lagging node could overwrite client_traffics with an older absolute expiry and latch enable=false. Reject older absolute expiries on merge, ignore expiry-stale disables when the master is not over quota, lift stale lifecycle fields out of adopted settings, stamp reconcile fingerprints from the pre-lift node blob, and mark the node dirty so the next tick re-pushes. * fix(node): lockstep client_traffics expiry/enable SQL with review fixes (#6228) Make expiry merge keep any master absolute (node only activates when master is unset/duration). Include this tick's up/down deltas in the enable stale-disable quota check so a crossing-tick disable is not dropped. * fix(node): add settings absolute helper for renew/lift guards (#6228) Expose settingsClientAbsoluteExpiry so traffic merge can tell a real node auto-renew (settings+stats later) from lagging ClientStats after a master shorten. Trim lift godoc to the invariant. * fix(node): authority-aware lifecycle merge for multi-node sync (#6228) While config_dirty, accumulate traffic only — do not adopt node expiry/enable/total/reset (and preserve dipped baselines so a false renew cannot fire after clear). On clean ticks, master absolute expiry wins; node auto-renew still goes through nodeClientRenewed when settings also show the later deadline. Defer settings lifecycle lift until after traffic deltas land, align SyncInbound via applyMasterClientLifecycle, and avoid re-MarkNodeDirty when already dirty. * fix(node): clear config_dirty only after the post-reconcile traffic merge (#6228) After a successful ReconcileNode, keep the node dirty through the same tick's SetRemoteTraffic so lagging ClientStats cannot clobber the just-pushed master lifecycle, then ClearNodeDirty. * test(node): cover dirty-gate, master-absolute, and renew false-positives (#6228) Add regressions for extend/shorten while dirty, clean-sibling shorten, settings vs lagging disable, renew recovery after dirty, renew with matching settings, and shorten+Reset lagging stats not treated as renew. * fix(node): address the review findings on the lifecycle merge (#6228) The automated review on #6231 flagged a blocking regression and six smaller issues. All of them are fixed here. Blocking: making the master's absolute expiry always win left nodeClientRenewed as the only channel for a node-side auto-renew, and that required a counter dip. A client that used no traffic in the period never dips, so its renewal was dropped, the master kept the expired deadline and disableInvalidClients removed it with no way back (master-side autoRenewClients skips node inbounds). The node bumps reset_count on every renewal, so that counter is now an independent renewal signal and is persisted with the renewal so it keeps converging. The deferred ClearNodeDirty made every reconcile-success tick merge in dirty mode, which suppressed inbound adoption, new client_traffics rows, the orphan sweeps and the whole SyncInbound record loop -- and left the node dirty forever whenever SetRemoteTraffic errored. The clear goes back to where it was; a separate justPushed flag now freezes only the client lifecycle merge for the tick whose push just landed. staleNodeDisable only recognised a lagging disable by an older expiry, so a quota top-up (raise totalGB, leave the expiry alone) was re-latched to disabled by the next lagging snapshot -- the #6228 symptom on a second axis. The reviewer's suggestion of dropping the expiry precondition outright fails TestNodeQuotaDisable_SameExpiryStillLatches, because the master's own counters legitimately sit below a node's after a seeded-at-zero adoption. nodeDisableIsStale instead compares the limits the node judged the client against with the master's own: matching limits mean a genuine verdict that still latches (#4917), differing limits mean the node has not seen the master's change yet. It also now measures the master deadline against wall-clock now, so an expired master row no longer looks "extended" merely because the node's copy is older still. Also: the settings lift now writes enable in both directions, so a blob fetched before a master disable cannot carry enable=true back into central settings and on to the node; the renewal guard parses the inbound settings once per inbound instead of once per renewing client; the adoption loop only writes settings when they actually changed; and two comments that described mechanisms the code does not use were corrected. The test deadlines are now relative to the run: the merge compares against now, so fixed timestamps would have rotted into the wrong side of it. --------- Co-authored-by: mrchatam <287639636+mrchatam@users.noreply.github.com> Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com> |
||
|
|
8dd3b31ee8 |
fix(node): show the activated first-use deadline on the Clients page
With "start after first use" on a node inbound, the node activates the absolute deadline and the master adopts it into client_traffics via the sync CASE merge — but the client record (what the Clients page reads) was only refreshed by SyncInbound from the snapshot's settings JSON. A node whose JSON still carried the negative duration (stale conversion, older node build, or a mixed local+node attachment) kept rewriting the record back to "not started" even though the DB held the real deadline (#5714). Lift the activated deadline from client_traffics onto still-negative client records at the end of every node sync, after SyncInbound has run. Intentional resets back to delayed start are unaffected: editing a client also resets client_traffics to the negative duration, so the lift's expiry_time > 0 guard never matches. Closes #5714 |
||
|
|
628406117e |
fix(nodes): sync "start after first connect" expiry so un-activated nodes do not reset it (#5319)
* fix(nodes): stop un-activated nodes from resetting "start after first connect" expiry In a multi-node setup a client is attached to inbounds on several nodes, but its `client_traffics` row is shared per-email (the column is `gorm:"unique"`). With "start expiry after first connect", the expiry is stored as a negative duration and each node converts it to an absolute deadline (now+duration) the first time the client connects *there*. The master's per-node traffic merge wrote `expiry_time = ?` unconditionally for every node sync. So a node where the client never connected keeps reporting the un-activated negative duration and clobbers the absolute deadline that the node where the client *did* connect had already activated — last writer wins. The shared row flip-flops and usually lands back on the negative value, so the main panel shows the timer "not started" while the active node counts down, and the subscription (which reads this row and recomputes negative as now+duration on every fetch) reports a perpetually-resetting, wrong expiry and usage. Guard the merge so an un-activated (<= 0) value reported by a node can never reset an already-activated absolute deadline. A positive node value is still adopted, so a node that legitimately moves the deadline forward (traffic reset / auto-renew) still propagates. The rule lives in both the SQL CASE used by the merge and a small `mergeActivationExpiry` helper (kept in lockstep) that the structural-change check reuses so the guard does not trigger spurious config re-pushes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(nodes): cast expiry merge params to BIGINT for Postgres The "start after first connect" merge guard introduced the comparison `? <= 0` in the client_traffics expiry_time CASE. There Postgres infers the parameter type as int4 from the literal 0, so binding a real expiry value — a negative start-after-connect duration or a positive absolute deadline (~1.7e12 ms) — overflows int4 and the whole setRemoteTrafficLocked transaction fails, breaking node traffic and expiry sync on Postgres. SQLite (dynamic typing) was unaffected. Wrap both params in CAST(? AS BIGINT) (portable across SQLite and Postgres) so the parameter is typed bigint, matching the explicit casts the sibling GreatestExpr/ClientTrafficEnableMergeExpr helpers already use. Verified against Postgres 16: TestNodeFirstConnectExpiry_NotClobbered failed before this change and passes after; SQLite suite unchanged. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com> |