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
+40
View File
@@ -224,6 +224,41 @@ func liftActivatedClientRecordExpiries(tx *gorm.DB) error {
).Error
}
// SnapshotHasUnadoptedInbounds reports whether the snapshot carries a tag with
// no central row yet, i.e. the next merge would adopt a new inbound.
func (s *InboundService) SnapshotHasUnadoptedInbounds(nodeID int, snap *runtime.TrafficSnapshot) (bool, error) {
if snap == nil || len(snap.Inbounds) == 0 {
return false, nil
}
var tags []string
if err := database.GetDB().Model(model.Inbound{}).
Where("node_id = ?", nodeID).
Pluck("tag", &tags).Error; err != nil {
return false, err
}
prefix := nodeTagPrefix(&nodeID)
known := make(map[string]struct{}, len(tags)*2)
for _, tag := range tags {
known[tag] = struct{}{}
if prefix != "" {
if stripped, found := strings.CutPrefix(tag, prefix); found {
known[stripped] = struct{}{}
} else {
known[prefix+tag] = struct{}{}
}
}
}
for _, ib := range snap.Inbounds {
if ib == nil {
continue
}
if _, ok := known[ib.Tag]; !ok {
return true, nil
}
}
return false, nil
}
func (s *InboundService) SetRemoteTraffic(nodeID int, snap *runtime.TrafficSnapshot, dirty bool) (bool, error) {
var structuralChange bool
err := submitTrafficWrite(func() error {
@@ -537,6 +572,11 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
if newIb.Tag != snapIb.Tag {
tagToCentral[newIb.Tag] = &newIb
}
if rows := adoptedHostRows(snap.HostGroups, snapIb.Id, newIb.Id); len(rows) > 0 {
if err := tx.Create(&rows).Error; err != nil {
logger.Warningf("setRemoteTraffic: adopt host rows for tag %q failed: %v", newIb.Tag, err)
}
}
newInboundIDs[newIb.Id] = struct{}{}
structuralChange = true
continue