feat: Dynamically generate VLESS URIs from server-provided templates, update QR card node labels, and add a troubleshooting runbook.
This commit is contained in:
parent
e412c33e0f
commit
95884dfc83
794
docs/runbooks/vless-uri-scheme-troubleshooting.md
Normal file
794
docs/runbooks/vless-uri-scheme-troubleshooting.md
Normal file
@ -0,0 +1,794 @@
|
||||
# VLESS URI Scheme Troubleshooting Runbook
|
||||
|
||||
## Overview
|
||||
|
||||
This runbook helps diagnose and fix issues with VLESS QR code generation, copy link, and download functionality in the user center. The system **requires valid node data from the server** - there are no hardcoded fallbacks.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ agent.svc.plus │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ Agent Registration & Heartbeat │ │
|
||||
│ │ - Registers with metadata (name, region, etc.) │ │
|
||||
│ │ - Sends periodic heartbeats │ │
|
||||
│ └────────────────────────┬─────────────────────────────────┘ │
|
||||
└───────────────────────────┼─────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ accounts.svc.plus │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ URI Scheme Templates (embedded in binary) │ │
|
||||
│ │ - VLESS-TCP-URI.Scheme │ │
|
||||
│ │ - VLESS-XHTTP-URI.Scheme │ │
|
||||
│ └────────────────────────┬─────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────────────▼─────────────────────────────────┐ │
|
||||
│ │ /api/agent/nodes Handler │ │
|
||||
│ │ 1. Reads registered agent metadata │ │
|
||||
│ │ 2. Loads URI scheme templates │ │
|
||||
│ │ 3. Renders templates with user UUID, domain, etc. │ │
|
||||
│ │ 4. Returns array of VlessNode objects │ │
|
||||
│ └────────────────────────┬─────────────────────────────────┘ │
|
||||
└───────────────────────────┼─────────────────────────────────────┘
|
||||
│
|
||||
▼ JSON Response
|
||||
{
|
||||
"name": "NODE-NAME",
|
||||
"address": "example.com",
|
||||
"transport": "tcp",
|
||||
"uri_scheme_tcp": "vless://...",
|
||||
"uri_scheme_xhttp": "vless://..."
|
||||
}
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ console.svc.plus │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ /api/agent/nodes (proxy to accounts.svc.plus) │ │
|
||||
│ └────────────────────────┬─────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────────────▼─────────────────────────────────┐ │
|
||||
│ │ VlessQrCard Component │ │
|
||||
│ │ - Fetches nodes via useSWR │ │
|
||||
│ │ - User selects node and transport (TCP/XHTTP) │ │
|
||||
│ └────────────────────────┬─────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────────────▼─────────────────────────────────┐ │
|
||||
│ │ buildVlessUri(uuid, node) │ │
|
||||
│ │ ✅ Validates: uuid, node, node.transport │ │
|
||||
│ │ ✅ Selects: uri_scheme_tcp or uri_scheme_xhttp │ │
|
||||
│ │ ❌ No fallbacks - returns null if data missing │ │
|
||||
│ └────────────────────────┬─────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────────────▼─────────────────────────────────┐ │
|
||||
│ │ renderVlessUriFromScheme(template, values) │ │
|
||||
│ │ - Substitutes ${UUID}, ${DOMAIN}, etc. │ │
|
||||
│ │ - Returns complete VLESS URI │ │
|
||||
│ └────────────────────────┬─────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ vless://uuid@host:port?... │
|
||||
│ │ │
|
||||
│ ┌─────────────┼─────────────┐ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ QR Code Copy Link Download QR │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Key Design Principles
|
||||
|
||||
### ✅ What the System Does
|
||||
|
||||
1. **Server-Driven Configuration**: All node information comes from `accounts.svc.plus`
|
||||
2. **Dynamic Node Discovery**: Nodes are discovered from registered agents in `agent.svc.plus`
|
||||
3. **Template-Based URI Generation**: URI schemes are defined in `accounts.svc.plus` and rendered server-side
|
||||
4. **Strict Validation**: Frontend validates all required fields and fails fast with clear errors
|
||||
5. **No Hardcoded Defaults**: No fake nodes, hosts, or labels in the UI code
|
||||
|
||||
### ❌ What the System Does NOT Do
|
||||
|
||||
1. **No Fallback Nodes**: If no agents are registered, no nodes are shown (not "TOKYO-NODE")
|
||||
2. **No Hardcoded Hosts**: No `'ha-proxy-jp.svc.plus'` or similar in frontend code
|
||||
3. **No Manual URI Construction**: Frontend never builds URIs from scratch using URLSearchParams
|
||||
4. **No Default Labels**: No hardcoded "TOKYO-NODE" or similar labels
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Issue 1: No Nodes Displayed / Empty Node List
|
||||
|
||||
**Symptoms:**
|
||||
- Node selector is empty or not shown
|
||||
- QR code shows error or "Generating QR code..." indefinitely
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
1. Check `/api/agent/nodes` response:
|
||||
```bash
|
||||
curl -H "Cookie: xc_session=$TOKEN" \
|
||||
https://console.svc.plus/api/agent/nodes
|
||||
```
|
||||
|
||||
Expected: Array with at least one node
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "NODE-NAME",
|
||||
"address": "example.com",
|
||||
"transport": "tcp",
|
||||
"uri_scheme_tcp": "vless://...",
|
||||
"uri_scheme_xhttp": "vless://..."
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
2. Check browser console for errors:
|
||||
```
|
||||
[VLESS] Cannot build URI: node is undefined
|
||||
```
|
||||
|
||||
**Root Causes & Fixes:**
|
||||
|
||||
| Cause | Diagnosis | Fix |
|
||||
|-------|-----------|-----|
|
||||
| No agents registered | Check agent.svc.plus logs for registration | Register at least one agent |
|
||||
| `XRAY_PROXY_NODES` env var empty | Check accounts.svc.plus env vars | Set `XRAY_PROXY_NODES=host1.com,host2.com` |
|
||||
| Agent heartbeat failing | Check agent.svc.plus health endpoint | Fix agent connectivity |
|
||||
| API proxy misconfigured | Check console.svc.plus API routes | Verify `/api/agent/*` proxies to accounts.svc.plus |
|
||||
|
||||
### Issue 2: QR Code Not Displaying
|
||||
|
||||
**Symptoms:**
|
||||
- Nodes are listed but QR code doesn't generate
|
||||
- Error in browser console
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
Check browser console for `[VLESS]` errors:
|
||||
|
||||
```javascript
|
||||
// Error 1: Missing transport
|
||||
[VLESS] Missing transport type from node: example.com
|
||||
|
||||
// Error 2: Missing URI scheme
|
||||
[VLESS] Missing URI scheme template from server for transport: tcp.
|
||||
Node: example.com.
|
||||
Please ensure accounts.svc.plus is returning uri_scheme_tcp and uri_scheme_xhttp fields.
|
||||
|
||||
// Error 3: Invalid UUID
|
||||
[VLESS] Cannot build URI: node is undefined
|
||||
```
|
||||
|
||||
**Root Causes & Fixes:**
|
||||
|
||||
| Error | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| Missing transport | Node object missing `transport` field | Check accounts.svc.plus `listAgentNodes` function |
|
||||
| Missing URI scheme | `uri_scheme_tcp` or `uri_scheme_xhttp` not in response | Verify `VLESSTCPScheme()` and `VLESSXHTTPScheme()` are called |
|
||||
| Invalid UUID | User doesn't have proxy UUID | Check user record in database |
|
||||
|
||||
### Issue 3: Copy Link Returns Empty
|
||||
|
||||
**Symptoms:**
|
||||
- Click "Copy Link" button
|
||||
- Nothing copied to clipboard
|
||||
- Or clipboard contains "null"
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
This is the same as Issue 2 - `buildVlessUri()` is returning `null`.
|
||||
|
||||
**Fix:**
|
||||
|
||||
See Issue 2 root causes and fixes.
|
||||
|
||||
### Issue 4: Switching TCP/XHTTP Doesn't Update QR
|
||||
|
||||
**Symptoms:**
|
||||
- Click TCP or XHTTP button
|
||||
- QR code doesn't change
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
1. Check if both `uri_scheme_tcp` and `uri_scheme_xhttp` exist in node data
|
||||
2. Check React DevTools to see if `preferredTransport` state is updating
|
||||
3. Check if `vlessUri` is recomputing
|
||||
|
||||
**Root Cause:**
|
||||
|
||||
Usually a React rendering issue, not related to URI scheme logic. The `useEffect` hook should automatically trigger when `vlessUri` changes.
|
||||
|
||||
**Fix:**
|
||||
|
||||
```typescript
|
||||
// In VlessQrCard.tsx, verify this useEffect exists:
|
||||
useEffect(() => {
|
||||
if (!vlessUri) {
|
||||
setQrDataUrl(null)
|
||||
return
|
||||
}
|
||||
|
||||
toDataURL(vlessUri, { /* ... */ })
|
||||
.then(setQrDataUrl)
|
||||
.catch(console.error)
|
||||
}, [vlessUri]) // ✅ Depends on vlessUri
|
||||
```
|
||||
|
||||
### Issue 5: Node Shows Generic "Node" Label
|
||||
|
||||
**Symptoms:**
|
||||
- Node badge shows "Node" instead of actual node name
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
Check node data structure:
|
||||
```javascript
|
||||
// In browser console
|
||||
console.log(effectiveNode)
|
||||
```
|
||||
|
||||
Expected:
|
||||
```javascript
|
||||
{
|
||||
name: "TOKYO-NODE", // ✅ Should have name
|
||||
address: "example.com",
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Root Causes & Fixes:**
|
||||
|
||||
| Cause | Fix |
|
||||
|-------|-----|
|
||||
| Agent not sending name metadata | Update agent to send `name` in registration |
|
||||
| `resolveNodeName()` logic broken | Check accounts.svc.plus `resolveNodeName` function |
|
||||
| Fallback to generic label | Expected behavior if `name` and `address` both missing |
|
||||
|
||||
## Debugging Steps
|
||||
|
||||
### Step 1: Verify Backend URI Schemes
|
||||
|
||||
```bash
|
||||
# SSH into accounts.svc.plus container or check source code
|
||||
cd /app/internal/xrayconfig
|
||||
|
||||
# Check scheme files exist
|
||||
ls -la VLESS-*.Scheme
|
||||
|
||||
# View TCP scheme
|
||||
cat VLESS-TCP-URI.Scheme
|
||||
# Expected: vless://${UUID}@${DOMAIN}:1443?encryption=none&type=tcp&security=tls&sni=${SNI}&fp=${FP}&flow=${FLOW}#${TAG}
|
||||
|
||||
# View XHTTP scheme
|
||||
cat VLESS-XHTTP-URI.Scheme
|
||||
# Expected: vless://${UUID}@${DOMAIN}:443?encryption=none&type=xhttp&security=tls&host=${DOMAIN}&path=${PATH}&mode=${MODE}&sni=${SNI}&fp=${FP}&alpn=h2%2Chttp%2F1.1%2Ch3#${TAG}
|
||||
```
|
||||
|
||||
### Step 2: Test API Endpoint
|
||||
|
||||
```bash
|
||||
# Get auth token from browser cookies
|
||||
TOKEN="your-xc_session-cookie-value"
|
||||
|
||||
# Test nodes endpoint
|
||||
curl -H "Cookie: xc_session=$TOKEN" \
|
||||
https://console.svc.plus/api/agent/nodes | jq '.'
|
||||
|
||||
# Expected output:
|
||||
[
|
||||
{
|
||||
"name": "NODE-NAME",
|
||||
"address": "example.com",
|
||||
"port": 443,
|
||||
"users": ["uuid-here"],
|
||||
"transport": "xhttp",
|
||||
"path": "/split",
|
||||
"mode": "auto",
|
||||
"security": "tls",
|
||||
"flow": "xtls-rprx-vision",
|
||||
"server_name": "example.com",
|
||||
"xhttp_port": 443,
|
||||
"tcp_port": 1443,
|
||||
"uri_scheme_xhttp": "vless://uuid@example.com:443?...",
|
||||
"uri_scheme_tcp": "vless://uuid@example.com:1443?..."
|
||||
}
|
||||
]
|
||||
|
||||
# Check specific fields
|
||||
curl -H "Cookie: xc_session=$TOKEN" \
|
||||
https://console.svc.plus/api/agent/nodes | \
|
||||
jq '.[0] | {name, address, uri_scheme_tcp, uri_scheme_xhttp}'
|
||||
```
|
||||
|
||||
### Step 3: Check Frontend Logs
|
||||
|
||||
Open browser DevTools Console and look for:
|
||||
|
||||
```
|
||||
✅ Good (no errors):
|
||||
(no [VLESS] messages)
|
||||
|
||||
❌ Bad:
|
||||
[VLESS] Cannot build URI: node is undefined
|
||||
[VLESS] Missing transport type from node: example.com
|
||||
[VLESS] Missing URI scheme template from server for transport: tcp. Node: example.com. Please ensure accounts.svc.plus is returning uri_scheme_tcp and uri_scheme_xhttp fields.
|
||||
```
|
||||
|
||||
### Step 4: Verify QR Code Generation
|
||||
|
||||
```javascript
|
||||
// In browser console, test QR generation manually
|
||||
import { toDataURL } from 'qrcode'
|
||||
|
||||
const testUri = 'vless://test-uuid@example.com:1443?encryption=none&type=tcp&security=tls#TEST'
|
||||
|
||||
toDataURL(testUri, {
|
||||
errorCorrectionLevel: 'M',
|
||||
margin: 1,
|
||||
scale: 8,
|
||||
}).then(url => {
|
||||
console.log('QR generated successfully:', url.substring(0, 50) + '...')
|
||||
// Create img element to view
|
||||
const img = document.createElement('img')
|
||||
img.src = url
|
||||
document.body.appendChild(img)
|
||||
})
|
||||
```
|
||||
|
||||
### Step 5: Check Agent Registration
|
||||
|
||||
```bash
|
||||
# Check if agents are registered
|
||||
# (This depends on your agent.svc.plus implementation)
|
||||
|
||||
# Example: Check agent heartbeat logs
|
||||
kubectl logs -n your-namespace deployment/agent-svc-plus | grep heartbeat
|
||||
|
||||
# Example: Check registered agents in database
|
||||
psql -c "SELECT * FROM agents WHERE last_heartbeat > NOW() - INTERVAL '5 minutes';"
|
||||
```
|
||||
|
||||
## Code References
|
||||
|
||||
### Frontend (console.svc.plus)
|
||||
|
||||
**Main Logic:**
|
||||
- [vless.ts](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/console.svc.plus/src/modules/extensions/builtin/user-center/lib/vless.ts)
|
||||
- `VLESS_DEFAULTS` - Technical constants (fingerprint, tcpFlow)
|
||||
- `buildVlessUri()` - Renders URI from server template (**no fallbacks**)
|
||||
- `renderVlessUriFromScheme()` - Template variable substitution
|
||||
- `buildVlessConfig()` - Builds Xray config for download
|
||||
|
||||
**UI Component:**
|
||||
- [VlessQrCard.tsx](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/console.svc.plus/src/modules/extensions/builtin/user-center/components/VlessQrCard.tsx)
|
||||
- Fetches nodes from `/api/agent/nodes` via `useSWR`
|
||||
- Manages TCP/XHTTP transport switching
|
||||
- Generates QR code via `toDataURL()`
|
||||
- Displays node selector if multiple nodes available
|
||||
|
||||
### Backend (accounts.svc.plus)
|
||||
|
||||
**URI Schemes:**
|
||||
- `internal/xrayconfig/VLESS-TCP-URI.Scheme` - TCP URI template
|
||||
- `internal/xrayconfig/VLESS-XHTTP-URI.Scheme` - XHTTP URI template
|
||||
|
||||
**Template Loading:**
|
||||
- `internal/xrayconfig/templates.go`
|
||||
- `VLESSTCPScheme()` - Returns embedded TCP URI template
|
||||
- `VLESSXHTTPScheme()` - Returns embedded XHTTP URI template
|
||||
|
||||
**API Handler:**
|
||||
- `api/user_agents.go`
|
||||
- `listAgentNodes()` - Returns node list with rendered URI schemes
|
||||
- `renderVLESSURIScheme()` - Substitutes template variables (${UUID}, ${DOMAIN}, etc.)
|
||||
- `registeredNodeMetadata()` - Reads agent registration data
|
||||
- `parseProxyNodeHosts()` - Parses `XRAY_PROXY_NODES` env var
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### accounts.svc.plus
|
||||
|
||||
| Variable | Purpose | Default | Example |
|
||||
|----------|---------|---------|---------|
|
||||
| `XRAY_PROXY_NODES` | Additional proxy nodes (comma-separated) | - | `node1.com,node2.com` |
|
||||
| `XRAY_XHTTP_PATH` | XHTTP path | `/split` | `/custom-path` |
|
||||
| `XRAY_XHTTP_MODE` | XHTTP mode | `auto` | `stream` |
|
||||
| `XRAY_XHTTP_PORT` | XHTTP port | `443` | `8443` |
|
||||
| `XRAY_TCP_PORT` | TCP port | `1443` | `1444` |
|
||||
|
||||
## Recovery Procedures
|
||||
|
||||
### Procedure 1: Fix Missing URI Schemes
|
||||
|
||||
If `uri_scheme_tcp` or `uri_scheme_xhttp` are missing from API response:
|
||||
|
||||
1. **Check scheme files exist:**
|
||||
```bash
|
||||
ls -la /app/internal/xrayconfig/VLESS-*.Scheme
|
||||
```
|
||||
|
||||
2. **Verify templates.go loads them:**
|
||||
```go
|
||||
// Should have these embed directives:
|
||||
//go:embed VLESS-TCP-URI.Scheme
|
||||
vlessTCPScheme []byte
|
||||
|
||||
//go:embed VLESS-XHTTP-URI.Scheme
|
||||
vlessXHTTPScheme []byte
|
||||
```
|
||||
|
||||
3. **Rebuild and redeploy accounts.svc.plus:**
|
||||
```bash
|
||||
cd /Users/shenlan/workspaces/cloud-neutral-toolkit/accounts.svc.plus
|
||||
go build -o accounts-svc-plus
|
||||
# Deploy to Cloud Run or your platform
|
||||
```
|
||||
|
||||
4. **Verify schemes are returned:**
|
||||
```bash
|
||||
curl https://accounts.svc.plus/api/agent/nodes | jq '.[0].uri_scheme_tcp'
|
||||
```
|
||||
|
||||
### Procedure 2: Fix No Nodes Returned
|
||||
|
||||
If `/api/agent/nodes` returns empty array `[]`:
|
||||
|
||||
1. **Check `XRAY_PROXY_NODES` env var:**
|
||||
```bash
|
||||
# In accounts.svc.plus container
|
||||
echo $XRAY_PROXY_NODES
|
||||
# Should output: host1.com,host2.com
|
||||
```
|
||||
|
||||
2. **Check agent registration:**
|
||||
```bash
|
||||
# Verify agents are sending heartbeats
|
||||
# (Implementation-specific)
|
||||
```
|
||||
|
||||
3. **Add manual proxy nodes:**
|
||||
```bash
|
||||
# Set env var in Cloud Run or your platform
|
||||
XRAY_PROXY_NODES=node1.example.com,node2.example.com
|
||||
```
|
||||
|
||||
4. **Restart accounts.svc.plus**
|
||||
|
||||
### Procedure 3: Reset Frontend State
|
||||
|
||||
If QR code is stuck or showing old data:
|
||||
|
||||
1. **Clear browser cache:**
|
||||
- Chrome: Cmd+Shift+Delete (Mac) or Ctrl+Shift+Delete (Windows)
|
||||
- Select "Cached images and files"
|
||||
|
||||
2. **Hard refresh:**
|
||||
- Mac: Cmd+Shift+R
|
||||
- Windows: Ctrl+Shift+R
|
||||
|
||||
3. **Clear SWR cache:**
|
||||
```javascript
|
||||
// In browser console
|
||||
localStorage.clear()
|
||||
sessionStorage.clear()
|
||||
location.reload()
|
||||
```
|
||||
|
||||
4. **Verify API returns valid data:**
|
||||
```bash
|
||||
curl -H "Cookie: xc_session=$TOKEN" \
|
||||
https://console.svc.plus/api/agent/nodes
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Key Metrics
|
||||
|
||||
1. **QR Code Generation Success Rate**
|
||||
- Track `toDataURL()` success/failure ratio
|
||||
- Alert if failure rate > 5%
|
||||
|
||||
2. **API Response Time**
|
||||
- Monitor `/api/agent/nodes` latency
|
||||
- Alert if p95 > 500ms
|
||||
|
||||
3. **Error Rate**
|
||||
- Count `[VLESS]` console errors
|
||||
- Alert if error rate > 1% of page loads
|
||||
|
||||
4. **Node Availability**
|
||||
- Track number of nodes returned by `/api/agent/nodes`
|
||||
- Alert if drops to 0
|
||||
|
||||
### Alerts
|
||||
|
||||
Set up alerts for:
|
||||
|
||||
- High rate of `[VLESS] Missing URI scheme` errors (> 10/min)
|
||||
- `/api/agent/nodes` returning empty array for > 5 minutes
|
||||
- QR code generation failures (> 5% of attempts)
|
||||
- Agent heartbeat failures (no heartbeat for > 5 minutes)
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
Before deploying changes:
|
||||
|
||||
- [ ] `/api/agent/nodes` returns valid node data with `uri_scheme_tcp` and `uri_scheme_xhttp`
|
||||
- [ ] QR code generates successfully for TCP transport
|
||||
- [ ] QR code generates successfully for XHTTP transport
|
||||
- [ ] Copy link button copies valid VLESS URI
|
||||
- [ ] Download QR button downloads valid QR code image
|
||||
- [ ] Switching TCP ↔ XHTTP regenerates QR code
|
||||
- [ ] Node selector shows all registered nodes (if multiple exist)
|
||||
- [ ] No `[VLESS]` errors in browser console (with valid data)
|
||||
- [ ] Clear error messages in console when data is missing
|
||||
- [ ] No hardcoded "TOKYO-NODE" or fake hosts appear in UI
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [VLESS Protocol Specification](https://github.com/XTLS/Xray-core)
|
||||
- [Implementation Plan](file:///Users/shenlan/.gemini/antigravity/brain/57f5a000-a95d-484c-999d-ac7b60bfa953/implementation_plan.md)
|
||||
- [Walkthrough](file:///Users/shenlan/.gemini/antigravity/brain/57f5a000-a95d-484c-999d-ac7b60bfa953/walkthrough.md)
|
||||
|
||||
## Change Log
|
||||
|
||||
| Date | Change | Impact |
|
||||
|------|--------|--------|
|
||||
| 2026-02-04 | Removed `DEFAULT_VLESS_TEMPLATE` hardcoded values | No more fake "TOKYO-NODE" or `ha-proxy-jp.svc.plus` |
|
||||
| 2026-02-04 | Removed fallback URI construction logic | System now fails fast with clear errors |
|
||||
| 2026-02-04 | Removed `DEFAULT_VLESS_LABEL` export | Node labels come from server only |
|
||||
| 2026-02-04 | Added strict validation in `buildVlessUri` | Better error messages for debugging |
|
||||
| 2026-02-04 | Created troubleshooting runbook | Easier diagnosis and recovery |
|
||||
|
||||
|
||||
### Issue 1: QR Code Not Displaying
|
||||
|
||||
**Symptoms:**
|
||||
- QR code shows "Generating QR code..." indefinitely
|
||||
- Or shows error message
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
1. Check browser console for `[VLESS]` errors:
|
||||
```
|
||||
[VLESS] Missing URI scheme template from server for transport: tcp
|
||||
```
|
||||
|
||||
2. Check `/api/agent/nodes` response in Network tab:
|
||||
```bash
|
||||
# Should include these fields:
|
||||
{
|
||||
"uri_scheme_tcp": "vless://...",
|
||||
"uri_scheme_xhttp": "vless://..."
|
||||
}
|
||||
```
|
||||
|
||||
**Root Causes:**
|
||||
|
||||
| Cause | Fix |
|
||||
|-------|-----|
|
||||
| accounts.svc.plus not returning URI schemes | Check `user_agents.go` is calling `xrayconfig.VLESSXHTTPScheme()` and `xrayconfig.VLESSTCPScheme()` |
|
||||
| URI scheme files missing from binary | Ensure `VLESS-TCP-URI.Scheme` and `VLESS-XHTTP-URI.Scheme` exist and are embedded via `//go:embed` |
|
||||
| API proxy not working | Check console.svc.plus API proxy configuration for `/api/agent/*` routes |
|
||||
|
||||
### Issue 2: Copy Link Returns Empty
|
||||
|
||||
**Symptoms:**
|
||||
- Click "Copy Link" button
|
||||
- Nothing is copied to clipboard
|
||||
- Or error in console
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
Check if `buildVlessUri()` is returning null:
|
||||
```javascript
|
||||
// In browser console
|
||||
console.log(vlessUri) // Should be a string starting with "vless://"
|
||||
```
|
||||
|
||||
**Root Causes:**
|
||||
|
||||
Same as Issue 1 - missing URI schemes from server.
|
||||
|
||||
### Issue 3: Switching TCP/XHTTP Doesn't Update QR
|
||||
|
||||
**Symptoms:**
|
||||
- Click TCP or XHTTP button
|
||||
- QR code doesn't change
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
This should work automatically via React state. Check:
|
||||
1. `preferredTransport` state is updating
|
||||
2. `effectiveNode` is recomputing
|
||||
3. `vlessUri` is changing
|
||||
4. QR code `useEffect` is triggering
|
||||
|
||||
**Root Cause:**
|
||||
|
||||
Likely a React rendering issue, not related to URI scheme logic.
|
||||
|
||||
### Issue 4: Node Selector Not Showing
|
||||
|
||||
**Symptoms:**
|
||||
- Only one node shown even though multiple agents are registered
|
||||
- Node selector dropdown doesn't appear
|
||||
|
||||
**Diagnosis:**
|
||||
|
||||
1. Check `/api/agent/nodes` returns multiple nodes:
|
||||
```bash
|
||||
curl -H "Authorization: Bearer <token>" \
|
||||
https://accounts.svc.plus/api/agent/nodes | jq 'length'
|
||||
```
|
||||
|
||||
2. Check agent.svc.plus has registered multiple nodes
|
||||
|
||||
**Root Causes:**
|
||||
|
||||
| Cause | Fix |
|
||||
|-------|-----|
|
||||
| Only one agent registered | Register more agents via agent.svc.plus |
|
||||
| `XRAY_PROXY_NODES` env var only has one host | Add more hosts to env var in accounts.svc.plus |
|
||||
| Agent registration not working | Check agent.svc.plus heartbeat and registration logic |
|
||||
|
||||
## Debugging Steps
|
||||
|
||||
### Step 1: Verify Backend URI Schemes
|
||||
|
||||
```bash
|
||||
# SSH into accounts.svc.plus container
|
||||
cd /app/internal/xrayconfig
|
||||
|
||||
# Check scheme files exist
|
||||
ls -la VLESS-*.Scheme
|
||||
|
||||
# View TCP scheme
|
||||
cat VLESS-TCP-URI.Scheme
|
||||
# Expected: vless://${UUID}@${DOMAIN}:1443?encryption=none&type=tcp&security=tls&sni=${SNI}&fp=${FP}&flow=${FLOW}#${TAG}
|
||||
|
||||
# View XHTTP scheme
|
||||
cat VLESS-XHTTP-URI.Scheme
|
||||
# Expected: vless://${UUID}@${DOMAIN}:443?encryption=none&type=xhttp&security=tls&host=${DOMAIN}&path=${PATH}&mode=${MODE}&sni=${SNI}&fp=${FP}&alpn=h2%2Chttp%2F1.1%2Ch3#${TAG}
|
||||
```
|
||||
|
||||
### Step 2: Test API Endpoint
|
||||
|
||||
```bash
|
||||
# Get auth token from browser (check cookies or localStorage)
|
||||
TOKEN="your-session-token"
|
||||
|
||||
# Test nodes endpoint
|
||||
curl -H "Cookie: xc_session=$TOKEN" \
|
||||
https://console.svc.plus/api/agent/nodes | jq '.'
|
||||
|
||||
# Should return array with uri_scheme_tcp and uri_scheme_xhttp fields
|
||||
```
|
||||
|
||||
### Step 3: Check Frontend Logs
|
||||
|
||||
Open browser DevTools Console and look for:
|
||||
|
||||
```
|
||||
✅ Good:
|
||||
(no [VLESS] errors)
|
||||
|
||||
❌ Bad:
|
||||
[VLESS] Cannot build URI: node is undefined
|
||||
[VLESS] Missing URI scheme template from server for transport: tcp. Node: TOKYO-NODE. Please ensure accounts.svc.plus is returning uri_scheme_tcp and uri_scheme_xhttp fields.
|
||||
```
|
||||
|
||||
### Step 4: Verify QR Code Generation
|
||||
|
||||
```javascript
|
||||
// In browser console, test QR generation manually
|
||||
import { toDataURL } from 'qrcode'
|
||||
|
||||
const testUri = 'vless://test-uuid@example.com:1443?encryption=none&type=tcp&security=tls#TEST'
|
||||
|
||||
toDataURL(testUri, {
|
||||
errorCorrectionLevel: 'M',
|
||||
margin: 1,
|
||||
scale: 8,
|
||||
}).then(url => console.log('QR generated:', url))
|
||||
```
|
||||
|
||||
## Code References
|
||||
|
||||
### Frontend (console.svc.plus)
|
||||
|
||||
- **Main logic:** `src/modules/extensions/builtin/user-center/lib/vless.ts`
|
||||
- `buildVlessUri()` - Renders URI from server template
|
||||
- `renderVlessUriFromScheme()` - Template variable substitution
|
||||
|
||||
- **UI component:** `src/modules/extensions/builtin/user-center/components/VlessQrCard.tsx`
|
||||
- Fetches nodes from `/api/agent/nodes`
|
||||
- Manages TCP/XHTTP transport switching
|
||||
- Generates QR code via `toDataURL()`
|
||||
|
||||
### Backend (accounts.svc.plus)
|
||||
|
||||
- **URI schemes:** `internal/xrayconfig/VLESS-TCP-URI.Scheme`, `VLESS-XHTTP-URI.Scheme`
|
||||
- **Template loading:** `internal/xrayconfig/templates.go`
|
||||
- `VLESSTCPScheme()` - Returns TCP URI template
|
||||
- `VLESSXHTTPScheme()` - Returns XHTTP URI template
|
||||
|
||||
- **API handler:** `api/user_agents.go`
|
||||
- `listAgentNodes()` - Returns node list with rendered URI schemes
|
||||
- `renderVLESSURIScheme()` - Substitutes template variables
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### accounts.svc.plus
|
||||
|
||||
| Variable | Purpose | Example |
|
||||
|----------|---------|---------|
|
||||
| `XRAY_PROXY_NODES` | Additional proxy nodes | `ha-proxy-jp.svc.plus,ha-proxy-us.svc.plus` |
|
||||
| `XRAY_XHTTP_PATH` | XHTTP path | `/split` |
|
||||
| `XRAY_XHTTP_MODE` | XHTTP mode | `auto` |
|
||||
| `XRAY_XHTTP_PORT` | XHTTP port | `443` |
|
||||
| `XRAY_TCP_PORT` | TCP port | `1443` |
|
||||
|
||||
## Recovery Procedures
|
||||
|
||||
### Procedure 1: Fix Missing URI Schemes
|
||||
|
||||
If URI schemes are missing from API response:
|
||||
|
||||
1. Check scheme files exist in accounts.svc.plus:
|
||||
```bash
|
||||
ls internal/xrayconfig/VLESS-*.Scheme
|
||||
```
|
||||
|
||||
2. Rebuild accounts.svc.plus to embed schemes:
|
||||
```bash
|
||||
cd /Users/shenlan/workspaces/cloud-neutral-toolkit/accounts.svc.plus
|
||||
go build -o accounts-svc-plus
|
||||
```
|
||||
|
||||
3. Redeploy accounts.svc.plus
|
||||
|
||||
4. Verify schemes are returned:
|
||||
```bash
|
||||
curl https://accounts.svc.plus/api/agent/nodes | jq '.[0].uri_scheme_tcp'
|
||||
```
|
||||
|
||||
### Procedure 2: Reset Frontend State
|
||||
|
||||
If QR code is stuck:
|
||||
|
||||
1. Clear browser cache
|
||||
2. Hard refresh (Cmd+Shift+R on Mac)
|
||||
3. Check for console errors
|
||||
4. Verify `/api/agent/nodes` returns valid data
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Key Metrics
|
||||
|
||||
- **QR Code Generation Success Rate:** Track `toDataURL()` success/failure
|
||||
- **API Response Time:** `/api/agent/nodes` latency
|
||||
- **Error Rate:** Count of `[VLESS]` console errors
|
||||
|
||||
### Alerts
|
||||
|
||||
Set up alerts for:
|
||||
- High rate of `[VLESS] Missing URI scheme` errors
|
||||
- `/api/agent/nodes` returning empty array
|
||||
- QR code generation failures
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [VLESS Protocol Specification](https://github.com/XTLS/Xray-core)
|
||||
- [Implementation Plan](file:///Users/shenlan/.gemini/antigravity/brain/57f5a000-a95d-484c-999d-ac7b60bfa953/implementation_plan.md)
|
||||
- [Walkthrough](file:///Users/shenlan/.gemini/antigravity/brain/57f5a000-a95d-484c-999d-ac7b60bfa953/walkthrough.md)
|
||||
|
||||
## Change Log
|
||||
|
||||
| Date | Change | Author |
|
||||
|------|--------|--------|
|
||||
| 2026-02-04 | Removed fallback URI construction logic | System |
|
||||
| 2026-02-04 | Added error logging for missing URI schemes | System |
|
||||
| 2026-02-04 | Created troubleshooting runbook | System |
|
||||
@ -10,7 +10,6 @@ import Card from './Card'
|
||||
import {
|
||||
buildVlessConfig,
|
||||
buildVlessUri,
|
||||
DEFAULT_VLESS_LABEL,
|
||||
serializeConfigForDownload,
|
||||
VlessNode,
|
||||
VlessTransport,
|
||||
@ -175,7 +174,7 @@ export default function VlessQrCard({ uuid, copy }: VlessQrCardProps) {
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-xs font-semibold uppercase tracking-wide text-[var(--color-primary)]">{copy.label}</p>
|
||||
<span className="rounded-full bg-[var(--color-primary-muted)] px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-[var(--color-primary)]">
|
||||
{effectiveNode?.name || DEFAULT_VLESS_LABEL}
|
||||
{effectiveNode?.name || effectiveNode?.address || 'Node'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-2 text-xs text-[var(--color-text-subtle)]">{copy.description}</p>
|
||||
|
||||
@ -1,34 +1,8 @@
|
||||
export type VlessEndpoint = {
|
||||
host: string
|
||||
port: number
|
||||
type: string
|
||||
security: string
|
||||
flow: string
|
||||
encryption: string
|
||||
serverName: string
|
||||
fingerprint: string
|
||||
allowInsecure: boolean
|
||||
label: string
|
||||
}
|
||||
|
||||
export type VlessTemplate = {
|
||||
endpoint: VlessEndpoint
|
||||
}
|
||||
|
||||
const DEFAULT_VLESS_TEMPLATE: VlessTemplate = {
|
||||
endpoint: {
|
||||
host: 'ha-proxy-jp.svc.plus',
|
||||
port: 1443,
|
||||
type: 'tcp',
|
||||
security: 'tls',
|
||||
flow: 'xtls-rprx-vision',
|
||||
encryption: 'none',
|
||||
serverName: 'ha-proxy-jp.svc.plus',
|
||||
fingerprint: 'chrome',
|
||||
allowInsecure: false,
|
||||
label: 'TOKYO-NODE',
|
||||
},
|
||||
}
|
||||
// Technical constants for VLESS protocol
|
||||
const VLESS_DEFAULTS = {
|
||||
fingerprint: 'chrome',
|
||||
tcpFlow: 'xtls-rprx-vision',
|
||||
} as const
|
||||
|
||||
const DEFAULT_XRAY_CONFIG = {
|
||||
log: {
|
||||
@ -169,58 +143,49 @@ export function buildVlessUri(rawUuid: string | null | undefined, node?: VlessNo
|
||||
return null
|
||||
}
|
||||
|
||||
const { endpoint: defaultEndpoint } = DEFAULT_VLESS_TEMPLATE
|
||||
|
||||
const host = node?.address ?? defaultEndpoint.host
|
||||
const serverName = node?.server_name ?? node?.address ?? defaultEndpoint.serverName
|
||||
const label = node?.name ?? defaultEndpoint.label
|
||||
const transport = node?.transport ?? (defaultEndpoint.type as VlessTransport)
|
||||
const flow = node?.flow ?? (transport === 'tcp' ? defaultEndpoint.flow : '')
|
||||
const port = resolveTransportPort(node, transport, transport === 'xhttp' ? 443 : defaultEndpoint.port)
|
||||
|
||||
const schemeTemplate = transport === 'xhttp' ? node?.uri_scheme_xhttp : node?.uri_scheme_tcp
|
||||
if (schemeTemplate) {
|
||||
const rendered = renderVlessUriFromScheme(schemeTemplate, {
|
||||
UUID: uuid,
|
||||
DOMAIN: host,
|
||||
NODE: host,
|
||||
PATH: encodeURIComponent(node?.path ?? '/split'),
|
||||
MODE: encodeURIComponent(node?.mode ?? 'auto'),
|
||||
SNI: serverName,
|
||||
FP: defaultEndpoint.fingerprint,
|
||||
FLOW: flow || defaultEndpoint.flow,
|
||||
TAG: encodeURIComponent(label),
|
||||
})
|
||||
if (rendered) {
|
||||
return rendered
|
||||
}
|
||||
if (!node) {
|
||||
console.error('[VLESS] Cannot build URI: node is undefined')
|
||||
return null
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
type: transport,
|
||||
security: defaultEndpoint.security,
|
||||
encryption: defaultEndpoint.encryption,
|
||||
sni: serverName,
|
||||
fp: defaultEndpoint.fingerprint,
|
||||
allowInsecure: defaultEndpoint.allowInsecure ? '1' : '0',
|
||||
if (!node.transport) {
|
||||
console.error('[VLESS] Missing transport type from node:', node.name || node.address)
|
||||
return null
|
||||
}
|
||||
|
||||
const transport = node.transport
|
||||
const schemeTemplate = transport === 'xhttp' ? node.uri_scheme_xhttp : node.uri_scheme_tcp
|
||||
|
||||
if (!schemeTemplate) {
|
||||
console.error(
|
||||
`[VLESS] Missing URI scheme template from server for transport: ${transport}. ` +
|
||||
`Node: ${node.name || node.address}. ` +
|
||||
`Please ensure accounts.svc.plus is returning uri_scheme_tcp and uri_scheme_xhttp fields.`
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
const host = node.address
|
||||
const serverName = node.server_name ?? node.address
|
||||
const label = node.name || node.address
|
||||
const flow = node.flow ?? (transport === 'tcp' ? VLESS_DEFAULTS.tcpFlow : '')
|
||||
|
||||
return renderVlessUriFromScheme(schemeTemplate, {
|
||||
UUID: uuid,
|
||||
DOMAIN: host,
|
||||
NODE: host,
|
||||
PATH: encodeURIComponent(node.path ?? '/split'),
|
||||
MODE: encodeURIComponent(node.mode ?? 'auto'),
|
||||
SNI: serverName,
|
||||
FP: VLESS_DEFAULTS.fingerprint,
|
||||
FLOW: flow || VLESS_DEFAULTS.tcpFlow,
|
||||
TAG: encodeURIComponent(label),
|
||||
})
|
||||
|
||||
if (flow) {
|
||||
params.set('flow', flow)
|
||||
}
|
||||
|
||||
if (transport === 'xhttp') {
|
||||
params.set('host', host)
|
||||
params.set('path', node?.path ?? '/split')
|
||||
params.set('mode', node?.mode ?? 'auto')
|
||||
}
|
||||
|
||||
return `vless://${uuid}@${host}:${port}?${params.toString()}#${encodeURIComponent(label)}`
|
||||
}
|
||||
|
||||
export function buildVlessConfig(rawUuid: string | null | undefined, node?: VlessNode): XrayConfig | null {
|
||||
const uuid = (rawUuid ?? '').trim()
|
||||
if (!uuid) {
|
||||
if (!uuid || !node) {
|
||||
return null
|
||||
}
|
||||
|
||||
@ -229,12 +194,11 @@ export function buildVlessConfig(rawUuid: string | null | undefined, node?: Vles
|
||||
const vnext = outbound?.settings?.vnext?.[0]
|
||||
const user = vnext?.users?.[0]
|
||||
|
||||
const { endpoint: defaultEndpoint } = DEFAULT_VLESS_TEMPLATE
|
||||
const address = node?.address ?? defaultEndpoint.host
|
||||
const serverName = node?.server_name ?? node?.address ?? defaultEndpoint.serverName
|
||||
const transport = node?.transport ?? (defaultEndpoint.type as VlessTransport)
|
||||
const flow = node?.flow ?? (transport === 'tcp' ? defaultEndpoint.flow : '')
|
||||
const port = resolveTransportPort(node, transport, transport === 'xhttp' ? 443 : defaultEndpoint.port)
|
||||
const address = node.address
|
||||
const serverName = node.server_name ?? node.address
|
||||
const transport = node.transport ?? 'tcp'
|
||||
const flow = node.flow ?? (transport === 'tcp' ? VLESS_DEFAULTS.tcpFlow : '')
|
||||
const port = resolveTransportPort(node, transport, transport === 'xhttp' ? 443 : 1443)
|
||||
|
||||
if (vnext) {
|
||||
vnext.address = address
|
||||
@ -271,4 +235,4 @@ export function serializeConfigForDownload(config: XrayConfig): string {
|
||||
return `${JSON.stringify(config, null, 2)}\n`
|
||||
}
|
||||
|
||||
export const DEFAULT_VLESS_LABEL = DEFAULT_VLESS_TEMPLATE.endpoint.label
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user