fix(node): adopt a node inbound's host overrides into the master

Per-inbound Host overrides (Security/SNI/Fingerprint/ALPN and friends)
are looked up by the local inbound id when subscriptions render, but
nothing in the node sync ever fetched the node's hosts table: an
inbound adopted from a managed node got zero Host rows on the master,
so its subscription configs fell back to a bare TLS block without the
fingerprint/SNI the node was configured with.

When a traffic snapshot carries a tag with no central row yet - the
only moment adoption can happen - the sync job now also pulls the
node's existing hosts/list endpoint (best-effort, so old nodes just
skip it) and the adoption branch materializes that inbound's groups
against the new central id inside the same transaction, reusing the
group-to-rows projection the hosts API already uses. Master stays
authoritative afterwards: this is a one-time import, not a continuous
sync, matching how the inbound's own settings are adopted.

Closes #5890
This commit is contained in:
MHSanaei
2026-07-11 23:17:57 +02:00
parent e6bef229ae
commit cbd2940a63
5 changed files with 166 additions and 0 deletions
+20
View File
@@ -21,6 +21,7 @@ import (
"github.com/mhsanaei/3x-ui/v3/internal/logger"
"github.com/mhsanaei/3x-ui/v3/internal/util/netsafe"
"github.com/mhsanaei/3x-ui/v3/internal/util/wirecodec"
"github.com/mhsanaei/3x-ui/v3/internal/web/entity"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
)
@@ -669,6 +670,25 @@ type TrafficSnapshot struct {
// the per-GUID endpoint — OnlineEmails is the fallback then.
OnlineTree map[string][]string
LastOnlineMap map[string]int64
// HostGroups carries the node's per-inbound host overrides (TLS/SNI/
// fingerprint), fetched only when the snapshot holds a not-yet-adopted tag.
HostGroups []*entity.HostGroup
}
// FetchHostGroups pulls the node's host overrides so a freshly adopted inbound
// keeps its subscription TLS/SNI/fingerprint settings on the master.
func (r *Remote) FetchHostGroups(ctx context.Context) ([]*entity.HostGroup, error) {
env, err := r.do(ctx, http.MethodGet, "panel/api/hosts/list", nil)
if err != nil {
return nil, err
}
var groups []*entity.HostGroup
if len(env.Obj) > 0 {
if err := json.Unmarshal(env.Obj, &groups); err != nil {
return nil, fmt.Errorf("decode host groups: %w", err)
}
}
return groups, nil
}
func (r *Remote) FetchTrafficSnapshot(ctx context.Context) (*TrafficSnapshot, error) {