Deleting a client on the master propagated to nodes via the detach
endpoint, which removes the client from that one inbound's settings but
deliberately keeps the client record. The node ended up with an
orphaned record that kept showing in its Clients view; the master and
node could never converge on a delete.
Full-delete and detach intent now travel separately: the Runtime
interface gains DeleteClient, which on Remote hits the node's
panel/api/clients/del endpoint (record, attachments, traffic; repeat
calls for a client on several inbounds of the same node are swallowed
as idempotent "not found"). Delete/DeleteByEmail/BulkDelete use it for
node inbounds, while Detach/BulkDetach keep the inbound-scoped detach
RPC so removing a client from one inbound never wipes it node-wide
(the #5543 guarantee is preserved and covered by tests). Bulk deletes
above the fold threshold still converge membership via reconcile; their
leftover node records can be cleaned with the node's delete-orphans
action.
Closes#5797
Add .golangci.yml (v2): the standard linters plus bodyclose, errorlint, noctx, misspell, rowserrcheck, sqlclosecheck, unconvert, usestdlibvars, with gofumpt + goimports formatters. Enable the std-error-handling exclusion preset for idiomatic Close/Remove/Setenv ignores; scope-exclude SA1019 (parser.ParseDir in tools/openapigen) and ST1005 (intentional capitalized user-facing error copy that tests assert verbatim). No inline nolint directives were introduced.
Resolve all 217 findings behavior-preserving: gofumpt/goimports formatting, explicit blank assignment on intentionally ignored errors, errors.Is/errors.As and %w wrapping, context-aware stdlib calls (CommandContext/QueryContext/NewRequestWithContext/Dialer), staticcheck simplifications, removed redundant conversions, http.StatusOK and http.MethodGet, inlined the go:fix intPtr helper, and deferred sql rows Close. Add a golangci CI job mirroring the existing Go jobs.
DelInboundClientByEmail gated the runtime RemoveUser/DeleteUser (and its
push-plan resolution) on !emailShared. But Xray users are keyed by inbound
tag + email, so a client attached to two inbounds left its user live in the
running Xray of every inbound where the email was still shared by a sibling
inbound, until an Xray restart.
Decouple the per-inbound runtime removal from emailShared; keep emailShared
only for preserving the shared email-keyed client_traffics/IP rows.