diff --git a/internal/web/runtime/remote.go b/internal/web/runtime/remote.go index 594af642d..b6c8cef6d 100644 --- a/internal/web/runtime/remote.go +++ b/internal/web/runtime/remote.go @@ -404,6 +404,13 @@ func (r *Remote) refreshRemoteIDs(ctx context.Context) error { next[ib.Tag] = ib.Id } r.mu.Lock() + // A rebuild sees only node-reported tags, so the adopted aliases must be + // re-applied or a later op on an adopted inbound re-creates it as a duplicate. + for centralTag, nodeTag := range r.adoptedAliases { + if id, ok := next[nodeTag]; ok { + next[centralTag] = id + } + } r.remoteIDByTag = next r.mu.Unlock() return nil diff --git a/internal/web/runtime/remote_test.go b/internal/web/runtime/remote_test.go index a97073006..bd0916eaa 100644 --- a/internal/web/runtime/remote_test.go +++ b/internal/web/runtime/remote_test.go @@ -440,3 +440,35 @@ func TestSanitizeStreamSettingsForRemote(t *testing.T) { }) } } + +// An adopted alias maps a central tag onto a differently-named node inbound. +// refreshRemoteIDs rebuilds the cache from node-reported tags only, so the +// alias must be re-applied or every later op on that inbound misses. +func TestRemoteAdoptedAliasSurvivesRefresh(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + if req.URL.Path == "/panel/api/inbounds/list" { + _, _ = w.Write([]byte(`{"success":true,"obj":[{"id":5,"tag":"legacy-in"},{"id":6,"tag":"in-2"}]}`)) + return + } + http.NotFound(w, req) + })) + defer srv.Close() + + r := NewRemote(nodeForPlainServer(t, srv, "verify", "tok"), nil) + central := &model.Inbound{Tag: "central-in", Settings: `{"clients":[]}`} + r.AdoptInboundAlias(central, RemoteInboundOption{Id: 5, Tag: "legacy-in"}) + + // Resolving a different tag misses the cache and forces a full refresh. + if _, err := r.resolveRemoteID(context.Background(), "in-2"); err != nil { + t.Fatalf("resolveRemoteID(in-2): %v", err) + } + + id, err := r.resolveRemoteID(context.Background(), central.Tag) + if err != nil { + t.Fatalf("resolveRemoteID(%s) after refresh: %v", central.Tag, err) + } + if id != 5 { + t.Fatalf("adopted alias resolved to %d, want 5", id) + } +}