feat(panel): build-before-bind + socks5 fixed node account (items 20+21)
Item 20: drop the zero-client deploy gate in ValidateNodeDeploy for all protocols, stop RebuildNode from auto-removing a node whose bindings reach zero, and let server/heartbeat reconciliation rebuild zero-client nodes like any other. Nodes are built and deployed first; bindings can be added and removed freely. Item 21: socks5-auth nodes carry one node-level fixed account (node.socksUser/socksPassword, minted at creation or by the store-load migration MigrateSocksNodeCredentials, idempotent). The sing-box inbound's only user is that pair regardless of bindings; subscription links for every bound customer carry the same pair. The generated username avoids the ng: prefix so the agent never attributes the account's traffic to a customer — it stays on the node's inbound counters. Edit form shows both fields (copyable) for socks nodes; NormalizeNode validates operator-supplied usernames.
This commit is contained in:
+26
-4
@@ -236,7 +236,27 @@ func clientCredentials(nc *NodeClient) Credentials {
|
||||
return *nc.Credentials
|
||||
}
|
||||
|
||||
// EnsureSocksCredentials mints the node-level fixed account of a
|
||||
// socks5-auth node (round-3 item 21) when either half is missing. The
|
||||
// username deliberately avoids the "ng:" prefix: the agent maps sing-box
|
||||
// user counters named "ng:<customerId>" to per-customer usage samples,
|
||||
// and the fixed account's traffic must stay node-level (the "inbound"
|
||||
// sample), never attributed to a customer. The password is a base64
|
||||
// 32-byte random, the same strength as a customer's relay password.
|
||||
func EnsureSocksCredentials(node *Node) {
|
||||
if node.Protocol != "socks5-auth" {
|
||||
return
|
||||
}
|
||||
if node.SocksUser == "" {
|
||||
node.SocksUser = "ngs_" + hex.EncodeToString(xbRandBytes(5))
|
||||
}
|
||||
if node.SocksPassword == "" {
|
||||
node.SocksPassword = b64(32)
|
||||
}
|
||||
}
|
||||
|
||||
func EnsureNodeSecrets(node *Node) {
|
||||
EnsureSocksCredentials(node)
|
||||
if node.WSPath == nil || *node.WSPath == "" {
|
||||
node.WSPath = StrPtr("/" + hex.EncodeToString(xbRandBytes(9)))
|
||||
}
|
||||
@@ -409,10 +429,12 @@ func buildSingBoxInbound(tag string, node *Node, nodeClients []*NodeClient, serv
|
||||
}
|
||||
|
||||
if protocol == "socks5-auth" {
|
||||
users := make([]any, 0, len(nodeClients))
|
||||
for _, nc := range nodeClients {
|
||||
users = append(users, map[string]any{"username": NodeEmail(nc.CustomerID), "password": clientCredentials(nc).RelayPassword})
|
||||
}
|
||||
// Round-3 item 21: the inbound carries exactly one user — the
|
||||
// node's fixed account — regardless of bindings. Bindings only
|
||||
// control subscription visibility; per-person accounting for
|
||||
// relay chains happens at the entry side, not here.
|
||||
EnsureSocksCredentials(node)
|
||||
users := []any{map[string]any{"username": node.SocksUser, "password": node.SocksPassword}}
|
||||
return map[string]any{"type": "socks", "tag": tag, "listen": listen, "listen_port": port, "users": users}, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user