fix(node): adopt a matching deployed inbound instead of recreating it (#6197)

* fix(node): adopt compatible origin inbounds without mutation

* fix(nodes): preserve ambiguous and adopted aliases

---------

Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
This commit is contained in:
n0ctal
2026-08-14 23:01:35 +05:00
committed by GitHub
parent 3b19091547
commit bb29b6afec
7 changed files with 241 additions and 10 deletions
+32 -7
View File
@@ -78,8 +78,9 @@ func (e *remoteAPIError) Error() string { return "remote: " + e.msg }
type Remote struct {
node *model.Node
mu sync.RWMutex
remoteIDByTag map[string]int
mu sync.RWMutex
remoteIDByTag map[string]int
adoptedAliases map[string]string
// pushedFP holds the fingerprint of the last inbound wire payload successfully
// pushed, keyed by panel-side tag, so reconcile can skip re-sending an
// unchanged inbound. Guarded by mu; dropped with the Remote on node config change.
@@ -99,8 +100,10 @@ type Remote struct {
}
type RemoteInboundOption struct {
Id int `json:"id"`
Tag string `json:"tag"`
Remark string `json:"remark"`
Listen string `json:"listen"`
Protocol model.Protocol `json:"protocol"`
Port int `json:"port"`
}
@@ -109,6 +112,7 @@ func NewRemote(n *model.Node, r NodeEgressResolver) *Remote {
return &Remote{
node: n,
remoteIDByTag: make(map[string]int),
adoptedAliases: make(map[string]string),
pushedFP: make(map[string]string),
egressResolver: r,
}
@@ -479,13 +483,33 @@ func (r *Remote) recordPushedInbound(ib *model.Inbound) {
r.mu.Unlock()
}
// RecordAdoptedInbound stamps the fingerprint when the master adopts the
// node's own settings serialization into its DB — direct knowledge of the
// exact payload the node holds.
// RecordAdoptedInbound stamps the exact payload fingerprint after the master
// adopts a node's settings serialization.
func (r *Remote) RecordAdoptedInbound(ib *model.Inbound) {
r.recordPushedInbound(ib)
}
// AdoptInboundAlias records a deployed alias without mutating either panel.
// The runtime association is rediscovered after a master restart.
func (r *Remote) AdoptInboundAlias(ib *model.Inbound, remote RemoteInboundOption) {
r.mu.Lock()
r.remoteIDByTag[remote.Tag] = remote.Id
r.remoteIDByTag[ib.Tag] = remote.Id
r.adoptedAliases[ib.Tag] = remote.Tag
r.pushedFP[ib.Tag] = wireFingerprint(wireInbound(ib, r.node.Id))
r.mu.Unlock()
}
func (r *Remote) AdoptedInboundAliases() []string {
r.mu.RLock()
defer r.mu.RUnlock()
aliases := make([]string, 0, len(r.adoptedAliases))
for _, alias := range r.adoptedAliases {
aliases = append(aliases, alias)
}
return aliases
}
// AdvancePushedInbound moves the reconcile-skip fingerprint from an inbound's
// pre-edit payload to its post-edit payload once every per-client push for the
// edit succeeded. It advances only when the recorded fingerprint proves the
@@ -661,8 +685,9 @@ func (r *Remote) ResetInboundTraffic(ctx context.Context, ib *model.Inbound) err
}
type TrafficSnapshot struct {
Inbounds []*model.Inbound
OnlineEmails []string
Inbounds []*model.Inbound
OnlineEmails []string
ManagedAliases []string
// OnlineTree is the node's GUID-keyed online subtree (its own clients under
// its panelGuid plus every descendant under theirs). Preferred over the flat
// OnlineEmails so the master can attribute deeply nested clients to the real