fix(inbounds): serve fresh client UUIDs for list and allLinks (#6458)

* fix(inbounds): serve fresh client UUIDs for list and allLinks (#6436)

Resolve clients from the clients table in inboundLinks and
backfillClientStats so /inbounds/list ClientStats and allLinks match
the running Xray identity when embedded settings JSON is stale.

* fix(sub): keep WG/AWG settings identity in link exports (#6436)

clientsForLinkExport uses the clients table for UUID-bearing protocols and
the inbound settings JSON for WireGuard/AmneziaWG so allLinks and per-client
QR links stay consistent without collapsing per-inbound tunnel keys.

* fix(sub): fall back to settings clients for link export (#6458)

Prefer ListClientsForInbound for UUID protocols, but when the clients
table is empty or unavailable fall back to GetClients so settings-only
inbounds (and share-link unit tests) still produce links. Keep WG/AWG
on settings identity.

---------

Co-authored-by: mrchatam <mrchatam@users.noreply.github.com>
Co-authored-by: mrchatam <287639636+mrchatam@users.noreply.github.com>
This commit is contained in:
mrchatam
2026-09-12 12:42:13 +03:30
committed by GitHub
parent 7a41c59494
commit 67addab343
6 changed files with 325 additions and 12 deletions
+9 -6
View File
@@ -24,10 +24,12 @@ type CopyClientsResult struct {
Errors []string `json:"errors"`
}
// enrichClientStats parses each inbound's clients once, fills in the
// UUID/SubId fields on the preloaded ClientStats, and tops up rows owned by
// a sibling inbound (shared-email mode — the row is keyed on email so it
// only preloads on its owning inbound).
// enrichClientStats resolves each inbound's clients from the clients table
// once, fills in the UUID/SubId fields on the preloaded ClientStats, and tops
// up rows owned by a sibling inbound (shared-email mode — the row is keyed on
// email so it only preloads on its owning inbound). Reading identity from the
// clients table keeps /inbounds/list in sync with the running Xray config
// when the embedded settings JSON is stale (#6436).
func (s *InboundService) enrichClientStats(db *gorm.DB, inbounds []*model.Inbound) {
if len(inbounds) == 0 {
return
@@ -55,13 +57,14 @@ func (s *InboundService) enrichClientStats(db *gorm.DB, inbounds []*model.Inboun
// backfillClientStats tops up each inbound's preloaded ClientStats with rows
// owned by a sibling inbound: client_traffics is keyed on email, so a client
// attached to several inbounds has one row that only preloads on the inbound
// it was created on. Returns the parsed clients per inbound for reuse.
// it was created on. Returns the clients-table clients per inbound for reuse
// (not the embedded settings JSON, which can lag the live UUID — #6436).
func (s *InboundService) backfillClientStats(db *gorm.DB, inbounds []*model.Inbound) [][]model.Client {
clientsByInbound := make([][]model.Client, len(inbounds))
seenByInbound := make([]map[string]struct{}, len(inbounds))
missing := make(map[string]struct{})
for i, inbound := range inbounds {
clients, _ := s.GetClients(inbound)
clients, _ := s.clientService.ListForInbound(db, inbound.Id)
clientsByInbound[i] = clients
seen := make(map[string]struct{}, len(inbound.ClientStats))
for _, st := range inbound.ClientStats {