Inbounds were pinned to the server's registered public address
(ipv4: PublicAddress, ipv6: PublicAddressV6), so addresses not
registered as 'the' public address - intranet NICs used for relay
landings, additional v6 addresses - had nothing listening and
intranet relays were unreachable (field-verified: ss showed the
landing port bound to the public IP only). 3x-ui/xray bind the
wildcard and serve these paths fine.
- listenAddress: explicit bindAddress still wins; ipv4 -> 0.0.0.0,
ipv6 -> ::, dual -> :: (ipv6 wildcard per user decision
2026-10-21: multi-v6 machines must serve all their v6 addresses).
- ResolveBindAddress: no longer resolves the registered address
into a bind pin (that resolution was the production path of the
pinning via resourceForNode/resourceForNodeExit); returns
("", nil) for all stacks, as it already did for dual/absent.
- serverAddress (client connection address / subscription host)
is unchanged.
- Golden fixtures updated for the three listen fields that
intentionally change; new listen_hotfix_test.go covers the
listenAddress cases and the resourceForNode/exit deploy chains.
One credential set per customer (UUID + 32-byte root relay key + shortId)
lives on the customer record; node bindings no longer store credentials.
SS2022 user keys derive deterministically from the root key by method
length (16/24/32-byte prefix), so a single password serves every protocol,
matching 3x-ui's per-client credential model. Reality public keys stay a
node property and are shown per binding in the credentials pane.
- store: load-time convergence — first binding's set wins; a converged
password that is not a 32-byte root key is regenerated (uuid/shortId
preserved); bindings are stripped; idempotent, covered by tests
- deploy: the orchestrator stamps the customer's set onto a build copy;
subscriptions resolve the same set; the derivation applies in both the
inbound builder and the URI builder (verified live: Alice's N1 link is
byte-identical to the pre-change seed expectation, her N2 user key is
exactly the root key's 16-byte prefix)
- API: GET /api/customers/:id/credentials returns {credentials, bindings};
regenerate is now POST /api/customers/:id/credentials/regenerate and
rotates the whole set, rebuilding every deployed node of the customer
- UI: credentials pane shows the single shared set plus per-binding
node/protocol/Reality-public-key rows
- golden subscriptions fixture regenerated under one shared set (RF and
SS entries byte-identical to before; five entries audited by hand)
- golden baseline re-captured for the new shapes; difftest normalizer now
treats a base64 root key's prefix-truncation encodings and the Surge
'='-mangled variants as the same secret
Pure-stdlib Go implementation of the whole control plane: same
nexusgate.json schema (unknown fields preserved verbatim, explicit
null/false tri-states modeled), same HTTP API and error strings,
embedded frontend (NG_PUBLIC_DIR override), self-implemented scrypt
auth, X25519 Reality keys, panel-hosted keyStore and heartbeat
reconciliation included. Version stays 0.6.8.
Verified: Go tests 33/33 (scrypt + Reality vectors generated from
the Node implementation, subscription goldens byte-identical),
Node suite 52/52 untouched, differential E2E against the Node
panel 50/50 including two-way DB swap. See panel-go/PARITY.md
and panel-go/INSTALL-GO.md.