feat: manual landing endpoints for forward nodes (item 8)
A forward node's exit target is now a choice: a managed panel server (existing behavior) or a manual landing endpoint — address + port + protocol (VLESS / SS2022 / SOCKS5) + operator-supplied credentials, including third-party nodes the panel does not run. The panel only builds the entry-side sing-box outbound and route rule; there is no exit resource, no exit job, and the node is exit-ready by definition. - model: Node.ExitMode/ExitAddress; manual exits force fixed port mode and skip the exit-server port table entirely - validation: address/port required; per-protocol credential presence; SS2022 passwords must be base64 key material of the method's key length (one PSK segment or serverKey:userKey) — the exact form sing-box accepts, rejected up front instead of failing at deploy - orchestration: resource building, deploy validation and rebuilds all branch on manualExit; switching managed->manual queues removal of the old exit resource, manual->managed mints a fresh exit resource id (deploy validation previously dereferenced the nil exit server — regression test locks the fix) - UI: exit target segmented control, link paste box that parses vless:// / ss:// / socks5:// links into the form, per-protocol credential fields (blank keeps the stored value on edit) - golden: new manualCreate/manualAssign/manualDeploy/manualJobs/ manualNodes sections — exactly one entry-side job whose outbound targets the manual address; all prior sections unchanged - the generated combined sing-box config passes the real sing-box 1.13.21 check
This commit is contained in:
+483
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"meta": {
|
||||
"capturedAt": "2026-10-20T21:20:39.194Z",
|
||||
"capturedAt": "2026-10-20T21:35:58.550Z",
|
||||
"note": "Go panel golden baseline; see README.md in this directory."
|
||||
},
|
||||
"sections": {
|
||||
@@ -2835,15 +2835,15 @@
|
||||
}
|
||||
],
|
||||
"keyStore": {
|
||||
"reality-<nod>#3": {
|
||||
"privateKey": "<rpriv>#1",
|
||||
"publicKey": "<rpub>#2",
|
||||
"updatedAt": "<ts>"
|
||||
},
|
||||
"reality-<nod>#2": {
|
||||
"privateKey": "<rpriv>#2",
|
||||
"publicKey": "<rpub>#1",
|
||||
"updatedAt": "<ts>"
|
||||
},
|
||||
"reality-<nod>#3": {
|
||||
"privateKey": "<rpriv>#1",
|
||||
"publicKey": "<rpub>#2",
|
||||
"updatedAt": "<ts>"
|
||||
}
|
||||
},
|
||||
"nodeClients": [
|
||||
@@ -3608,6 +3608,483 @@
|
||||
},
|
||||
"status": 401
|
||||
}
|
||||
},
|
||||
"manualCreate": {
|
||||
"body": {
|
||||
"node": {
|
||||
"artifacts": {},
|
||||
"createdAt": "<ts>",
|
||||
"entryReady": false,
|
||||
"error": null,
|
||||
"exitAddress": "203.0.113.9",
|
||||
"exitMode": "manual",
|
||||
"exitPort": "<port>#6",
|
||||
"exitPortMode": "fixed",
|
||||
"exitProtocol": "shadowsocks-2022-aes128",
|
||||
"exitReady": true,
|
||||
"exitResourceId": "",
|
||||
"exitServerId": null,
|
||||
"id": "<nod>#4",
|
||||
"lastUsageAt": null,
|
||||
"meteredTraffic": null,
|
||||
"name": "N4-Manual",
|
||||
"networkMode": "ipv4",
|
||||
"pendingDelete": false,
|
||||
"port": "<port>#7",
|
||||
"portMode": "fixed",
|
||||
"protocol": "vless-reality-vision",
|
||||
"reality": {
|
||||
"destPort": 443,
|
||||
"keyId": "reality-<nod>#4",
|
||||
"serverName": "www.amd.com"
|
||||
},
|
||||
"realityDestPort": 443,
|
||||
"realityServerName": "www.amd.com",
|
||||
"resourceId": "<res>#7",
|
||||
"retiredUsageEpochs": [],
|
||||
"serverId": "<srv>#1",
|
||||
"sortIndex": 3,
|
||||
"status": "draft",
|
||||
"topology": "forward",
|
||||
"updatedAt": "<ts>",
|
||||
"usageCounters": null,
|
||||
"wsPath": "<secret>#13"
|
||||
}
|
||||
},
|
||||
"status": 201
|
||||
},
|
||||
"manualAssign": {
|
||||
"body": {
|
||||
"nodeClient": {
|
||||
"createdAt": "<ts>",
|
||||
"customerId": "<cus>#3",
|
||||
"id": "<ncl>#5",
|
||||
"nodeId": "<nod>#4",
|
||||
"status": "active",
|
||||
"updatedAt": "<ts>"
|
||||
}
|
||||
},
|
||||
"status": 200
|
||||
},
|
||||
"manualDeploy": {
|
||||
"body": {
|
||||
"ok": true
|
||||
},
|
||||
"status": 202
|
||||
},
|
||||
"manualJobs": [
|
||||
{
|
||||
"complete": {
|
||||
"job": {
|
||||
"action": "apply_resource",
|
||||
"attempts": 1,
|
||||
"completedAt": "<ts>",
|
||||
"createdAt": "<ts>",
|
||||
"error": null,
|
||||
"id": "<job>#7",
|
||||
"leaseUntil": null,
|
||||
"nodeId": "<nod>#4",
|
||||
"payload": {
|
||||
"keyMaterial": {
|
||||
"reality-<nod>#4": {
|
||||
"privateKey": "<rpriv>#3",
|
||||
"publicKey": "<rpub>#3"
|
||||
}
|
||||
},
|
||||
"kind": "node",
|
||||
"resource": {
|
||||
"engine": "sing-box",
|
||||
"id": "<res>#7",
|
||||
"inbounds": [
|
||||
{
|
||||
"listen": "<hostip>",
|
||||
"listen_port": "<port>#7",
|
||||
"tag": "ng-<nod>#4-in",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"reality": {
|
||||
"enabled": true,
|
||||
"handshake": {
|
||||
"server": "www.amd.com",
|
||||
"server_port": "<port>#5"
|
||||
},
|
||||
"private_key": "${REALITY_PRIVATE:reality-<nod>#4}",
|
||||
"short_id": [
|
||||
"<secret>#8"
|
||||
]
|
||||
},
|
||||
"server_name": "www.amd.com"
|
||||
},
|
||||
"type": "vless",
|
||||
"users": [
|
||||
{
|
||||
"flow": "xtls-rprx-vision",
|
||||
"name": "ng:<cus>#3",
|
||||
"uuid": "<uuid>#3"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"kind": "node",
|
||||
"metricsTag": "ng-<nod>#4-in",
|
||||
"nodeId": "<nod>#4",
|
||||
"protocol": "vless-reality-vision"
|
||||
},
|
||||
"outbounds": [
|
||||
{
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "<secret>#14",
|
||||
"server": "203.0.113.9",
|
||||
"server_port": "<port>#6",
|
||||
"tag": "ng-<nod>#4-exit",
|
||||
"type": "shadowsocks"
|
||||
}
|
||||
],
|
||||
"routingRules": [
|
||||
{
|
||||
"action": "route",
|
||||
"inbound": [
|
||||
"ng-<nod>#4-in"
|
||||
],
|
||||
"outbound": "ng-<nod>#4-exit"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"serverId": "<srv>#1",
|
||||
"startedAt": "<ts>",
|
||||
"status": "completed",
|
||||
"updatedAt": "<ts>"
|
||||
}
|
||||
},
|
||||
"job": {
|
||||
"action": "apply_resource",
|
||||
"attempts": 1,
|
||||
"createdAt": "<ts>",
|
||||
"error": null,
|
||||
"id": "<job>#7",
|
||||
"leaseUntil": "<ts>",
|
||||
"nodeId": "<nod>#4",
|
||||
"payload": {
|
||||
"keyMaterial": {
|
||||
"reality-<nod>#4": {
|
||||
"privateKey": "<rpriv>#3",
|
||||
"publicKey": "<rpub>#3"
|
||||
}
|
||||
},
|
||||
"kind": "node",
|
||||
"resource": {
|
||||
"engine": "sing-box",
|
||||
"id": "<res>#7",
|
||||
"inbounds": [
|
||||
{
|
||||
"listen": "<hostip>",
|
||||
"listen_port": "<port>#7",
|
||||
"tag": "ng-<nod>#4-in",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"reality": {
|
||||
"enabled": true,
|
||||
"handshake": {
|
||||
"server": "www.amd.com",
|
||||
"server_port": "<port>#5"
|
||||
},
|
||||
"private_key": "${REALITY_PRIVATE:reality-<nod>#4}",
|
||||
"short_id": [
|
||||
"<secret>#8"
|
||||
]
|
||||
},
|
||||
"server_name": "www.amd.com"
|
||||
},
|
||||
"type": "vless",
|
||||
"users": [
|
||||
{
|
||||
"flow": "xtls-rprx-vision",
|
||||
"name": "ng:<cus>#3",
|
||||
"uuid": "<uuid>#3"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"kind": "node",
|
||||
"metricsTag": "ng-<nod>#4-in",
|
||||
"nodeId": "<nod>#4",
|
||||
"protocol": "vless-reality-vision"
|
||||
},
|
||||
"outbounds": [
|
||||
{
|
||||
"method": "2022-blake3-aes-128-gcm",
|
||||
"password": "<secret>#14",
|
||||
"server": "203.0.113.9",
|
||||
"server_port": "<port>#6",
|
||||
"tag": "ng-<nod>#4-exit",
|
||||
"type": "shadowsocks"
|
||||
}
|
||||
],
|
||||
"routingRules": [
|
||||
{
|
||||
"action": "route",
|
||||
"inbound": [
|
||||
"ng-<nod>#4-in"
|
||||
],
|
||||
"outbound": "ng-<nod>#4-exit"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"serverId": "<srv>#1",
|
||||
"startedAt": "<ts>",
|
||||
"status": "running",
|
||||
"updatedAt": "<ts>"
|
||||
}
|
||||
}
|
||||
],
|
||||
"manualNodes": {
|
||||
"body": {
|
||||
"nodeClients": [
|
||||
{
|
||||
"clientUri": "vless://<uuid>#1@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#2&type=tcp&headerType=none&flow=xtls-rprx-vision#N1-renamed",
|
||||
"createdAt": "<ts>",
|
||||
"customerId": "<cus>#1",
|
||||
"id": "<ncl>#1",
|
||||
"nodeId": "<nod>#2",
|
||||
"status": "active",
|
||||
"updatedAt": "<ts>"
|
||||
},
|
||||
{
|
||||
"clientUri": "vless://<uuid>#2@entry.example.com:<port>#2?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#1&sid=<secret>#4&type=tcp&headerType=none&flow=xtls-rprx-vision#N1-renamed",
|
||||
"createdAt": "<ts>",
|
||||
"customerId": "<cus>#2",
|
||||
"id": "<ncl>#2",
|
||||
"nodeId": "<nod>#2",
|
||||
"status": "active",
|
||||
"updatedAt": "<ts>"
|
||||
},
|
||||
{
|
||||
"clientUri": "ss://<ssblob>#1@entry.example.com:<port>#3#N2",
|
||||
"createdAt": "<ts>",
|
||||
"customerId": "<cus>#1",
|
||||
"id": "<ncl>#3",
|
||||
"nodeId": "<nod>#1",
|
||||
"status": "active",
|
||||
"updatedAt": "<ts>"
|
||||
},
|
||||
{
|
||||
"clientUri": "vless://<uuid>#3@s3.example.com:<port>#4?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#2&sid=<secret>#8&type=tcp&headerType=none#N3",
|
||||
"createdAt": "<ts>",
|
||||
"customerId": "<cus>#3",
|
||||
"id": "<ncl>#4",
|
||||
"nodeId": "<nod>#3",
|
||||
"status": "active",
|
||||
"updatedAt": "<ts>"
|
||||
},
|
||||
{
|
||||
"clientUri": "vless://<uuid>#3@entry.example.com:<port>#7?encryption=none&security=reality&sni=www.amd.com&fp=chrome&pbk=<rpub>#3&sid=<secret>#8&type=tcp&headerType=none&flow=xtls-rprx-vision#N4-Manual",
|
||||
"createdAt": "<ts>",
|
||||
"customerId": "<cus>#3",
|
||||
"id": "<ncl>#5",
|
||||
"nodeId": "<nod>#4",
|
||||
"status": "active",
|
||||
"updatedAt": "<ts>"
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"artifacts": {
|
||||
"realityPublicKey": "<rpub>#1"
|
||||
},
|
||||
"autoRemoving": false,
|
||||
"clientCount": 2,
|
||||
"createdAt": "<ts>",
|
||||
"entryReady": true,
|
||||
"error": null,
|
||||
"exitPort": "<port>#1",
|
||||
"exitPortMode": "random",
|
||||
"exitProtocol": "shadowsocks-2022-aes256",
|
||||
"exitReady": true,
|
||||
"exitResourceId": "<res>#1",
|
||||
"exitServerId": "<srv>#2",
|
||||
"id": "<nod>#2",
|
||||
"lastActiveAt": "<ts>",
|
||||
"lastSampleAt": "<ts>",
|
||||
"lastUsageAt": "<ts>",
|
||||
"meteredTraffic": {
|
||||
"downlink": 14000,
|
||||
"uplink": 12000
|
||||
},
|
||||
"name": "N1-renamed",
|
||||
"networkMode": "ipv4",
|
||||
"onlineCount": 1,
|
||||
"onlineCustomerIds": [
|
||||
"<cus>#1"
|
||||
],
|
||||
"pendingDelete": false,
|
||||
"port": "<port>#2",
|
||||
"portMode": "random",
|
||||
"protocol": "vless-reality-vision",
|
||||
"reality": {
|
||||
"destPort": 443,
|
||||
"keyId": "reality-<nod>#2",
|
||||
"serverName": "www.amd.com"
|
||||
},
|
||||
"realityDestPort": 443,
|
||||
"realityServerName": "www.amd.com",
|
||||
"resourceId": "<res>#2",
|
||||
"retiredUsageEpochs": [
|
||||
"diff-epoch-1"
|
||||
],
|
||||
"serverId": "<srv>#1",
|
||||
"sortIndex": 0,
|
||||
"speedAt": "<ts>",
|
||||
"speedDownBps": "<speed+>",
|
||||
"speedUpBps": "<speed+>",
|
||||
"status": "active",
|
||||
"topology": "forward",
|
||||
"updatedAt": "<ts>",
|
||||
"usageCounters": {
|
||||
"downlink": 9000,
|
||||
"epoch": "golden-epoch-1",
|
||||
"uplink": 8000
|
||||
},
|
||||
"wsPath": "<secret>#5"
|
||||
},
|
||||
{
|
||||
"artifacts": {},
|
||||
"autoRemoving": false,
|
||||
"clientCount": 1,
|
||||
"createdAt": "<ts>",
|
||||
"entryReady": true,
|
||||
"error": null,
|
||||
"exitPort": null,
|
||||
"exitPortMode": null,
|
||||
"exitProtocol": null,
|
||||
"exitReady": false,
|
||||
"exitResourceId": "<res>#3",
|
||||
"exitServerId": null,
|
||||
"id": "<nod>#1",
|
||||
"lastUsageAt": null,
|
||||
"meteredTraffic": null,
|
||||
"name": "N2",
|
||||
"networkMode": "ipv4",
|
||||
"onlineCount": 1,
|
||||
"onlineCustomerIds": [
|
||||
"<cus>#1"
|
||||
],
|
||||
"pendingDelete": false,
|
||||
"port": "<port>#3",
|
||||
"portMode": "random",
|
||||
"protocol": "shadowsocks-2022-aes128",
|
||||
"realityDestPort": 443,
|
||||
"realityServerName": "www.tesla.com",
|
||||
"resourceId": "<res>#4",
|
||||
"retiredUsageEpochs": [],
|
||||
"serverId": "<srv>#1",
|
||||
"sortIndex": 1,
|
||||
"speedDownBps": 0,
|
||||
"speedUpBps": 0,
|
||||
"status": "active",
|
||||
"topology": "direct",
|
||||
"updatedAt": "<ts>",
|
||||
"usageCounters": null,
|
||||
"wsPath": "<secret>#6"
|
||||
},
|
||||
{
|
||||
"artifacts": {
|
||||
"realityPublicKey": "<rpub>#2"
|
||||
},
|
||||
"autoRemoving": false,
|
||||
"clientCount": 1,
|
||||
"createdAt": "<ts>",
|
||||
"entryReady": true,
|
||||
"error": null,
|
||||
"exitPort": null,
|
||||
"exitPortMode": null,
|
||||
"exitProtocol": null,
|
||||
"exitReady": false,
|
||||
"exitResourceId": "<res>#5",
|
||||
"exitServerId": null,
|
||||
"id": "<nod>#3",
|
||||
"lastUsageAt": null,
|
||||
"meteredTraffic": null,
|
||||
"name": "N3",
|
||||
"networkMode": "ipv4",
|
||||
"onlineCount": 0,
|
||||
"onlineCustomerIds": [],
|
||||
"pendingDelete": false,
|
||||
"port": "<port>#4",
|
||||
"portMode": "random",
|
||||
"protocol": "vless-reality",
|
||||
"reality": {
|
||||
"destPort": 443,
|
||||
"keyId": "reality-<nod>#3",
|
||||
"serverName": "www.amd.com"
|
||||
},
|
||||
"realityDestPort": 443,
|
||||
"realityServerName": "www.amd.com",
|
||||
"resourceId": "<res>#6",
|
||||
"retiredUsageEpochs": [],
|
||||
"serverId": "<srv>#3",
|
||||
"sortIndex": 2,
|
||||
"speedDownBps": 0,
|
||||
"speedUpBps": 0,
|
||||
"status": "active",
|
||||
"topology": "direct",
|
||||
"updatedAt": "<ts>",
|
||||
"usageCounters": null,
|
||||
"wsPath": "<secret>#9"
|
||||
},
|
||||
{
|
||||
"artifacts": {
|
||||
"realityPublicKey": "<rpub>#3"
|
||||
},
|
||||
"autoRemoving": false,
|
||||
"clientCount": 1,
|
||||
"createdAt": "<ts>",
|
||||
"entryReady": true,
|
||||
"error": null,
|
||||
"exitAddress": "203.0.113.9",
|
||||
"exitMode": "manual",
|
||||
"exitPort": "<port>#6",
|
||||
"exitPortMode": "fixed",
|
||||
"exitProtocol": "shadowsocks-2022-aes128",
|
||||
"exitReady": true,
|
||||
"exitResourceId": "",
|
||||
"exitServerId": null,
|
||||
"id": "<nod>#4",
|
||||
"lastUsageAt": null,
|
||||
"meteredTraffic": null,
|
||||
"name": "N4-Manual",
|
||||
"networkMode": "ipv4",
|
||||
"onlineCount": 0,
|
||||
"onlineCustomerIds": [],
|
||||
"pendingDelete": false,
|
||||
"port": "<port>#7",
|
||||
"portMode": "fixed",
|
||||
"protocol": "vless-reality-vision",
|
||||
"reality": {
|
||||
"destPort": 443,
|
||||
"keyId": "reality-<nod>#4",
|
||||
"serverName": "www.amd.com"
|
||||
},
|
||||
"realityDestPort": 443,
|
||||
"realityServerName": "www.amd.com",
|
||||
"resourceId": "<res>#7",
|
||||
"retiredUsageEpochs": [],
|
||||
"serverId": "<srv>#1",
|
||||
"sortIndex": 3,
|
||||
"speedDownBps": 0,
|
||||
"speedUpBps": 0,
|
||||
"status": "active",
|
||||
"topology": "forward",
|
||||
"updatedAt": "<ts>",
|
||||
"usageCounters": null,
|
||||
"wsPath": "<secret>#13"
|
||||
}
|
||||
]
|
||||
},
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user