Compare commits

..

1 Commits

Author SHA1 Message Date
Haitao Pan
67bf25bd77 chore: point auth/dashboard to svc.plus domains 2026-01-24 23:56:12 +08:00
525 changed files with 46009 additions and 51392 deletions

View File

@ -1,986 +0,0 @@
# 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 0: `/api/agent/nodes` Returns 500 Internal Server Error
**Symptoms:**
- Browser console shows: `GET /api/agent/nodes 500 (Internal Server Error)`
- VLESS QR card displays: "❌ 节点数据缺失 / 无法从服务器获取代理节点列表"
- Frontend error: `[VLESS] Cannot build URI: node is undefined`
**Root Cause:**
The `agentRegistry` in `accounts.svc.plus` is not properly initialized, causing `h.agentStatusReader` to be `nil` when `/api/agent/nodes` is called.
**Architecture:**
```
┌──────────────────────────────────────────────────────────┐
│ hk-xhttp.svc.plus (VM) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ agent.svc.plus │ │
│ │ Config: /etc/agent/account-agent.yaml │ │
│ │ - agent.id: "hk-xhttp.svc.plus" │ │
│ │ - apiToken: "uTvryFvAbz6M5sRtmTaSTQY6otLZ95hneBsWqXu+35I=" │
│ │ - controllerUrl: "https://accounts-svc-plus-...run.app" │
│ └──────────────────┬─────────────────────────────────┘ │
└─────────────────────┼─────────────────────────────────────┘
│ POST /api/agent-server/v1/status
│ Authorization: Bearer <apiToken>
┌──────────────────────────────────────────────────────────┐
│ Cloud Run: accounts-svc-plus │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Environment Variables (REQUIRED): │ │
│ │ - INTERNAL_SERVICE_TOKEN=uTvryFvAbz6M5sRtmTaSTQY6otLZ95hneBsWqXu+35I= │
│ │ - AGENT_ID=hk-xhttp.svc.plus │ │
│ └────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ agentRegistry Initialization (main.go:659-673) │ │
│ │ if INTERNAL_SERVICE_TOKEN is set: │ │
│ │ agentID = AGENT_ID (or "internal-agent") │ │
│ │ registry = NewRegistry({ │ │
│ │ ID: agentID, │ │
│ │ Token: INTERNAL_SERVICE_TOKEN │ │
│ │ }) │ │
│ └────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ agentAuthMiddleware (main.go:1002-1021) │ │
│ │ 1. Extract Bearer token from Authorization header│ │
│ │ 2. registry.Authenticate(token) │ │
│ │ 3. If valid, set agent identity in context │ │
│ └────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
```
**Configuration Mapping:**
| Component | Variable | Value | Purpose |
|-----------|----------|-------|---------|
| agent.svc.plus | `agent.id` | `hk-xhttp.svc.plus` | Agent's self-reported ID |
| agent.svc.plus | `agent.apiToken` | `uTvryFvAbz6M5sRtmTaSTQY6otLZ95hneBsWqXu+35I=` | Authentication token |
| accounts.svc.plus | `INTERNAL_SERVICE_TOKEN` | `uTvryFvAbz6M5sRtmTaSTQY6otLZ95hneBsWqXu+35I=` | **Must match** agent.apiToken |
| accounts.svc.plus | `AGENT_ID` | `hk-xhttp.svc.plus` | **Must match** agent.id |
**Diagnosis Steps:**
1. **Check Cloud Run environment variables:**
```bash
gcloud run services describe accounts-svc-plus \
--region=asia-northeast1 \
--format="value(spec.template.spec.containers[0].env)" | \
grep -E "INTERNAL_SERVICE_TOKEN|AGENT_ID"
```
Expected output:
```
{'name': 'INTERNAL_SERVICE_TOKEN', 'value': 'uTvryFvAbz6M5sRtmTaSTQY6otLZ95hneBsWqXu+35I='}
{'name': 'AGENT_ID', 'value': 'hk-xhttp.svc.plus'}
```
2. **Check agent heartbeat logs:**
```bash
gcloud logging read 'resource.type=cloud_run_revision AND resource.labels.service_name=accounts-svc-plus AND textPayload:"agent status"' \
--limit=10 \
--format="value(textPayload)" \
--project=xzerolab-480008
```
**Good** (agent authenticated):
```
time=2026-02-04T15:42:35.158Z level=INFO msg="agent status updated" agent=hk-xhttp.svc.plus healthy=true clients=7
time=2026-02-04T15:42:35.158Z level=INFO msg=request method=POST path=/api/agent-server/v1/status status=204 latency=142.949µs
```
**Bad** (authentication failed):
```
time=2026-02-04T15:46:35.098Z level=INFO msg=request method=POST path=/api/agent-server/v1/status status=401 latency=48.72µs
```
3. **Check agent configuration on VM:**
```bash
ssh root@hk-xhttp.svc.plus
cat /etc/agent/account-agent.yaml | grep -A 5 "agent:"
```
Expected:
```yaml
agent:
id: "hk-xhttp.svc.plus"
controllerUrl: "https://accounts-svc-plus-266500572462.asia-northeast1.run.app"
apiToken: "uTvryFvAbz6M5sRtmTaSTQY6otLZ95hneBsWqXu+35I="
statusInterval: 1m
```
**Fix:**
1. **Set Cloud Run environment variables:**
```bash
gcloud run services update accounts-svc-plus \
--region=asia-northeast1 \
--set-env-vars="INTERNAL_SERVICE_TOKEN=uTvryFvAbz6M5sRtmTaSTQY6otLZ95hneBsWqXu+35I=,AGENT_ID=hk-xhttp.svc.plus"
```
2. **Verify deployment:**
```bash
# Wait for deployment to complete
gcloud run services describe accounts-svc-plus \
--region=asia-northeast1 \
--format="value(status.url)"
```
3. **Wait for agent heartbeat (1 minute):**
```bash
# Agent sends heartbeat every 1 minute (statusInterval: 1m)
sleep 65
```
4. **Verify agent registration:**
```bash
gcloud logging read 'resource.type=cloud_run_revision AND resource.labels.service_name=accounts-svc-plus AND textPayload:"agent status updated"' \
--limit=1 \
--format="value(textPayload)" \
--project=xzerolab-480008
```
Should show:
```
level=INFO msg="agent status updated" agent=hk-xhttp.svc.plus healthy=true clients=X
```
5. **Test API:**
```bash
curl -H "Cookie: xc_session=$TOKEN" \
https://console.svc.plus/api/agent/nodes | jq '.'
```
Expected: Array with node data (not 500 error)
**Code Changes Required:**
Modified `accounts.svc.plus/cmd/accountsvc/main.go` (lines 659-673):
```go
} else if token := os.Getenv("INTERNAL_SERVICE_TOKEN"); token != "" {
// Fallback: if no credentials configured but we have an internal token,
// accept any agent that presents this token (ID will be taken from agent's self-reported ID)
// This allows the agent to use its configured ID (e.g., "hk-xhttp.svc.plus")
agentID := strings.TrimSpace(os.Getenv("AGENT_ID"))
if agentID == "" {
agentID = "internal-agent" // fallback ID if not specified
}
agentRegistry, err = agentserver.NewRegistry(agentserver.Config{
Credentials: []agentserver.Credential{{
ID: agentID,
Name: "Internal Agent",
Token: token,
Groups: []string{"internal"},
}},
})
if err != nil {
return err
}
}
```
**Verification:**
After fix, you should see:
- ✅ Agent heartbeat logs show `status=204` (success) instead of `status=401`
- ✅ `/api/agent/nodes` returns `200` with node data
- ✅ VLESS QR code displays correctly in UI
- ✅ No `[VLESS] Cannot build URI` errors in browser console
---
### 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 |

View File

@ -1,18 +0,0 @@
# Security Scrubbing Skill
This skill provides a standardized workflow for identifying and removing sensitive information (passwords, tokens, keys) from Git history using `git filter-repo` and `gitleaks`.
## Guidelines
1. **Identification**: Always run `gitleaks detect -v` first to identify the scope of exposed secrets.
2. **Replacement Plan**: Create a mapping file (e.g., `expressions.txt`) using the format `old_value==>new_value`.
3. **Execution**: Use `git filter-repo --replace-text expressions.txt --force` to rewrite history.
4. **Verification**: Re-run `gitleaks` to ensure zero leaks remain.
5. **Synchronization**: Force-push to all remote remotes (`git push origin <branch> --force`). Update all collaborators.
6. **Archiving**: Log the operation in the project's Runbook directory with a timestamped record.
## Best Practices
- Always use descriptive placeholders like `YOUR_PASSWORD` or `MFA_SECRET_PLACEHOLDER`.
- Scan all branches, not just the active one, if secrets were historically committed elsewhere.
- Notify the team immediately after a force-push as it breaks local clones.

View File

@ -1,40 +0,0 @@
---
name: UI Design Standards
description: Rules and guidelines for consistent UI components including dropdowns, popovers, and list items.
---
# UI Design Standards
This skill provides guidelines for building consistent, accessible, and high-quality UI components within the codebase.
## 1. Popovers and Dropdowns (弹窗与下拉规范)
### Alignment (定位逻辑)
- **Right Edge Alignment**: For triggers located on the right side of the screen or navigation bar, the associated dropdown/popover MUST be right-aligned with the trigger.
- In Radix UI / shadcn: Use `align="end"`.
- In CSS/Tailwind: Use `absolute right-0`.
- **Vertical Spacing**: Maintain a consistent vertical offset from the trigger, typically `8px` (`mt-2` or `sideOffset={8}`).
### Layering and Visual Hierarchy (层级管理)
- **Z-Index**: Always explicitly declare a `z-index` of `50` or higher for floating UI elements to ensure they appear above all other content.
- **Background Integrity**: Drodown containers should use opaque background colors (e.g., `bg-white` or theme-defined `bg-surface`) to prevent "see-through" visual noise from underlying content. Avoid excessive transparency/blur if it compromises readability.
- **Shadows**: Use distinct shadows (e.g., `shadow-md` or `shadow-xl`) to provide depth.
### Clipping Prevention (溢出处理)
- Use Portals (e.g., `DropdownMenu.Portal`) to render floating content into the `document.body`. This prevents the menu from being clipped by parents with `overflow: hidden`.
## 2. List Items and Components (列表项规范)
### Layout and Alignment (对齐规范)
- **Flexbox**: Use `flex items-center` for all list items that include both an icon and a text label.
- **Icon Spacing**: Maintain a standard horizontal gap between icons and labels (recommended: `gap-3` or `12px`).
### Component Integrity (防压缩与自适应)
- **Icon Shrinking**: Icons within flex containers MUST have `flex-shrink: 0` (Tailwind: `shrink-0`) to prevent them from distorting when the container is narrow.
- **Minimum Width**: Containers displaying variable-length text (like user emails) should have a reasonable `min-width` (e.g., `min-w-[200px]`) to ensure comfortable display.
## 3. Interaction and Accessibility
- **Keyboard Support**: Ensure dropdowns support `Esc` to close and allow keyboard navigation (Tab/Arrows).
- **Outside Clicks**: Implement "click outside to close" logic for all popovers.
- **Motion**: Use subtle entry/exit animations (e.g., 120ms fade and scale). Respect `prefers-reduced-motion`.

View File

@ -1,12 +0,0 @@
# UI Development Rules
## 1. 弹窗与下拉规范 (Popovers & Dropdowns)
- **定位逻辑**:除非特殊说明,右上角的触发器对应的弹出层必须强制 `right-0` (或 `align="end"`) 对齐。
- **层级管理**:所有 Floating UI 必须显式声明 `z-[50+]` 以防被 Content Card 遮挡或透视。
- **背景显示**:下拉容器背景应使用不透明色(如 `bg-white` 或主题定义的 `bg-surface`),避免透明度导致的视觉干扰。
## 2. 列表项规范 (List Items)
- **对齐**:所有带有图标的列表项必须使用 `flex items-center`。
- **防止重叠**:图标与文字之间应保持足够的间距(推荐 `gap-3` 或 `12px`)。
- **防压缩**:图标必须带有 `flex-shrink: 0` (或 Tailwind 的 `shrink-0`),防止在容器宽度不足时图标变形。
- **最小宽度**:涉及用户信息的容器应设置合理的 `min-width` (如 `min-w-[200px]`) 以保证长文字内容正常显示。

View File

@ -1,15 +0,0 @@
.git
.github
.next
.contentlayer
node_modules
coverage
dist
build
test-results
*.log
.env
.env.local
.env.*.local
deploy/single-node/.env.runtime
knowledge/.git

View File

@ -1,74 +0,0 @@
# Frontend site base URLs
APP_BASE_URL=
NEXT_PUBLIC_APP_BASE_URL=
NEXT_PUBLIC_SITE_URL=
NEXT_PUBLIC_LOGIN_URL=
NEXT_PUBLIC_DOCS_BASE_URL=
DOCS_SERVICE_URL=https://docs.svc.plus
DOCS_SERVICE_INTERNAL_URL=
SESSION_COOKIE_SECURE=true
NEXT_PUBLIC_SESSION_COOKIE_SECURE=true
RUNTIME_HOSTNAME=
NEXT_RUNTIME_HOSTNAME=
DEPLOYMENT_HOSTNAME=
RUNTIME_ENV=prod
REGION=cn
NEXT_PUBLIC_RUNTIME_ENVIRONMENT=prod
NEXT_PUBLIC_RUNTIME_REGION=cn
# Upstream service endpoints
# Use root service origins only. Do not point ACCOUNT_SERVICE_URL at console.svc.plus
# and do not include /api/auth or any other path suffix here.
ACCOUNT_SERVICE_URL=https://accounts.svc.plus
NEXT_PUBLIC_ACCOUNT_SERVICE_URL=https://accounts.svc.plus
SERVER_SERVICE_URL=https://api.svc.plus
NEXT_PUBLIC_SERVER_SERVICE_URL=https://api.svc.plus
SERVER_SERVICE_INTERNAL_URL=
# XWorkmate bridge runtime
# Read server-side by /api/xworkmate/bridge. Do not expose the token as NEXT_PUBLIC_*.
BRIDGE_SERVER_URL=https://xworkmate-bridge.svc.plus
BRIDGE_AUTH_TOKEN=
# OpenClaw assistant integrations
# Use environment variables to prefill the assistant and integrations page.
# Values are read server-side and are not hardcoded into the UI.
OPENCLAW_GATEWAY_REMOTE_URL=
OPENCLAW_GATEWAY_TOKEN=
VAULT_SERVER_URL=
VAULT_NAMESPACE=
VAULT_TOKEN=
APISIX_AI_GATEWAY_URL=
AI_GATEWAY_ACCESS_TOKEN=
# Giscus Configuration (GitHub Discussions Integration)
# See https://giscus.app to generate these values
NEXT_PUBLIC_GISCUS_REPO=cloud-neutral-toolkit/console.svc.plus
NEXT_PUBLIC_GISCUS_REPO_ID=R_kgDOQoiZ_g
NEXT_PUBLIC_GISCUS_CATEGORY=General
NEXT_PUBLIC_GISCUS_CATEGORY_ID=DIC_kwDOQoiZ_s4Clj_q
# Internal service token used to read aggregate counts from accounts.svc.plus (/api/internal/public-overview)
INTERNAL_SERVICE_TOKEN=
# Cloudflare Web Analytics GraphQL credentials
CLOUDFLARE_DNS_API_TOKEN=
CLOUDFLARE_DNS_ZONE_TAG=
CLOUDFLARE_API_TOKEN=
CLOUDFLARE_ACCOUNT_ID=
CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=
CLOUDFLARE_ZONE_TAG=
# Root email whitelist for privileged user-creation actions (comma-separated)
# Default: admin@svc.plus
ROOT_EMAIL_WHITELIST=admin@svc.plus
# Stripe public price ids used by /prices, product pages, and /panel/subscription
# These values are safe to expose to the browser. Use Stripe test-mode price ids for local/dev.
NEXT_PUBLIC_PAYPAL_CLIENT_ID=
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO=
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION=
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO=
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION=
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO=
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION=

37
.github/actions/auto-tag/action.yml vendored Normal file
View File

@ -0,0 +1,37 @@
name: "Cloud-Neutral Auto Tag"
description: "Generate Docker tags for main, release, PR and dev branches"
inputs:
image:
description: "Base image name (e.g. ghcr.io/.../image)"
required: true
outputs:
tags:
description: "Generated Docker tags"
value: ${{ steps.meta.outputs.tags }}
labels:
description: "Generated Docker labels"
value: ${{ steps.meta.outputs.labels }}
runs:
using: composite
steps:
- name: Generate metadata (auto tags)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ inputs.image }}
tags: |
# main → latest
type=raw,enable=${{ github.ref == 'refs/heads/main' }},value=latest
# release tagv1.2.3
type=ref,event=tag
type=semver,pattern={{version}}
# PR → pr-123
type=raw,enable=${{ startsWith(github.ref, 'refs/pull/') }},value=pr-${{ github.event.pull_request.number }}
# dev/feature branches → branch name
type=ref,event=branch

90
.github/actions/build/action.yml vendored Normal file
View File

@ -0,0 +1,90 @@
name: Build
description: Build artifacts for each service and platform with optional container publishing.
inputs:
service:
description: Target service name
required: true
platform:
description: Target platform (e.g., linux/amd64)
required: true
environment:
description: Deployment environment (dev or prod)
required: true
runs:
using: composite
steps:
- name: Prepare matrix context
id: matrix
uses: ../matrix-support
with:
service: ${{ inputs.service }}
platform: ${{ inputs.platform }}
environment: ${{ inputs.environment }}
enable_docker: 'true'
- name: Cache build artifacts
uses: actions/cache@v4
with:
path: |
build/${{ inputs.service }}
dashboard/.next
key: build-${{ inputs.service }}-${{ inputs.platform }}-${{ hashFiles('**/go.sum', 'dashboard/yarn.lock') }}-${{ inputs.environment }}
restore-keys: |
build-${{ inputs.service }}-${{ inputs.platform }}-
build-${{ inputs.service }}-
- name: Prepare Go toolchain
if: inputs.service != 'dashboard'
uses: actions/setup-go@v4
with:
go-version: '1.22'
cache: true
- name: Build Go binaries
if: inputs.service != 'dashboard'
shell: bash
run: |
set -euo pipefail
goos="${{ steps.matrix.outputs.goos }}"
goarch="${{ steps.matrix.outputs.goarch }}"
mkdir -p build/${{ inputs.service }}/"${goos}-${goarch}"
declare -a targets
if [[ "${{ inputs.service }}" == "rag-server" ]]; then
targets=("rag-server/cmd/xcontrol-server" "rag-server/cmd/rag-server-cli")
elif [[ "${{ inputs.service }}" == "account" ]]; then
targets=("account/cmd/accountsvc")
else
targets=("./...")
fi
for target in "${targets[@]}"; do
binary_name=$(basename "$target")
GOOS="$goos" GOARCH="$goarch" go build -o build/${{ inputs.service }}/"${goos}-${goarch}"/"${binary_name}" "$target"
done
- name: Upload Go artifacts
if: inputs.service != 'dashboard'
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.service }}-${{ inputs.platform }}-${{ inputs.environment }}
path: build/${{ inputs.service }}/
- name: Install dashboard dependencies
if: inputs.service == 'dashboard'
working-directory: dashboard
shell: bash
run: yarn install --frozen-lockfile
- name: Build dashboard
if: inputs.service == 'dashboard'
working-directory: dashboard
shell: bash
env:
NEXT_PUBLIC_ENV: ${{ inputs.environment }}
run: yarn build
- name: Upload dashboard build output
if: inputs.service == 'dashboard'
uses: actions/upload-artifact@v4
with:
name: dashboard-${{ inputs.platform }}-${{ inputs.environment }}
path: dashboard/.next

53
.github/actions/code-quality/action.yml vendored Normal file
View File

@ -0,0 +1,53 @@
name: Code Quality
description: Run linting and basic quality checks per service/platform/environment matrix entry.
inputs:
service:
description: Target service name
required: true
platform:
description: Target platform (e.g., linux/amd64)
required: true
environment:
description: Deployment environment (dev or prod)
required: true
runs:
using: composite
steps:
- name: Prepare matrix context
id: matrix
uses: ./.github/actions/matrix-support
with:
service: ${{ inputs.service }}
platform: ${{ inputs.platform }}
environment: ${{ inputs.environment }}
- name: Install git-secrets
shell: bash
run: |
set -euo pipefail
git clone https://github.com/awslabs/git-secrets.git
sudo make install -C git-secrets
git secrets --install
git secrets --scan
- name: Go vet
if: inputs.service != 'dashboard'
shell: bash
run: go vet ./...
- name: Go unit tests (quality gate)
if: inputs.service != 'dashboard'
shell: bash
run: go test ./...
- name: Install dashboard dependencies
if: inputs.service == 'dashboard'
working-directory: dashboard
shell: bash
run: yarn install --frozen-lockfile
- name: Dashboard lint
if: inputs.service == 'dashboard'
working-directory: dashboard
shell: bash
run: yarn lint

48
.github/actions/deploy/action.yml vendored Normal file
View File

@ -0,0 +1,48 @@
name: Deploy
description: Coordinate deployments per service/environment.
inputs:
service:
description: Target service name
required: true
platform:
description: Target platform (e.g., linux/amd64)
required: true
environment:
description: Deployment environment (dev or prod)
required: true
runs:
using: composite
steps:
- name: Prepare matrix context
id: matrix
uses: ./.github/actions/matrix-support
with:
service: ${{ inputs.service }}
platform: ${{ inputs.platform }}
environment: ${{ inputs.environment }}
- name: Prepare rollout context
id: context
shell: bash
run: |
set -euo pipefail
echo "service=${{ inputs.service }}" >> "$GITHUB_OUTPUT"
echo "environment=${{ inputs.environment }}" >> "$GITHUB_OUTPUT"
echo "platform=${{ inputs.platform }}" >> "$GITHUB_OUTPUT"
echo "release_channel=${{ steps.matrix.outputs.is_prod == 'true' && 'prod' || 'dev' }}" >> "$GITHUB_OUTPUT"
- name: Deploy placeholder
shell: bash
env:
TARGET_ENV: ${{ steps.context.outputs.environment }}
TARGET_SERVICE: ${{ steps.context.outputs.service }}
TARGET_PLATFORM: ${{ steps.context.outputs.platform }}
RELEASE_CHANNEL: ${{ steps.context.outputs.release_channel }}
run: |
echo "Deploying ${TARGET_SERVICE} (${TARGET_PLATFORM}) to ${TARGET_ENV} namespace via ${RELEASE_CHANNEL} rollout"
echo "Hook in Helm/kubectl/ArgoCD rollouts here"
- name: Rollback plan
shell: bash
run: |
echo "Rollback can be re-run per matrix entry by dispatching with allow_deploy=true"

View File

@ -0,0 +1,106 @@
name: Matrix Support
description: Common setup for matrix-driven workflows with language and cache bootstrapping.
inputs:
service:
description: Target service name
required: true
platform:
description: Target platform (e.g., linux/amd64)
required: true
environment:
description: Deployment environment (dev or prod)
required: true
enable_docker:
description: Enable Docker buildx/QEMU setup
required: false
default: 'false'
outputs:
goos:
description: Derived GOOS from the platform input
value: ${{ steps.platforms.outputs.goos }}
goarch:
description: Derived GOARCH from the platform input
value: ${{ steps.platforms.outputs.goarch }}
is_prod:
description: Whether the environment is prod or the ref is a tag
value: ${{ steps.flags.outputs.is_prod }}
target_platforms:
description: Platform list for builds (single in dev, multi-arch in prod)
value: ${{ steps.flags.outputs.target_platforms }}
runs:
using: composite
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Derive platform matrix values
id: platforms
shell: bash
run: |
set -euo pipefail
platform="${{ inputs.platform }}"
goos="${platform%%/*}"
goarch="${platform##*/}"
echo "goos=${goos}" >> "$GITHUB_OUTPUT"
echo "goarch=${goarch}" >> "$GITHUB_OUTPUT"
- name: Resolve environment flags
id: flags
shell: bash
run: |
set -euo pipefail
if [[ "${{ inputs.environment }}" == "prod" || "${GITHUB_REF_TYPE:-}" == "tag" ]]; then
echo "is_prod=true" >> "$GITHUB_OUTPUT"
echo "target_platforms=linux/amd64,linux/arm64" >> "$GITHUB_OUTPUT"
else
echo "is_prod=false" >> "$GITHUB_OUTPUT"
echo "target_platforms=${{ inputs.platform }}" >> "$GITHUB_OUTPUT"
fi
- name: Set up Go
if: inputs.service != 'dashboard'
uses: actions/setup-go@v4
with:
go-version: '1.22'
cache: true
- name: Cache Go build data
if: inputs.service != 'dashboard'
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: go-${{ inputs.service }}-${{ inputs.platform }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
go-${{ inputs.service }}-${{ inputs.platform }}-
go-${{ inputs.service }}-
- name: Set up Node.js
if: inputs.service == 'dashboard'
uses: actions/setup-node@v4
with:
node-version: 20
cache: yarn
cache-dependency-path: dashboard/yarn.lock
- name: Cache dashboard artifacts
if: inputs.service == 'dashboard'
uses: actions/cache@v4
with:
path: |
dashboard/.next/cache
~/.cache/yarn
key: dashboard-${{ inputs.platform }}-${{ hashFiles('dashboard/yarn.lock') }}
restore-keys: |
dashboard-${{ inputs.platform }}-
dashboard-
- name: Enable Docker build tooling
if: inputs.enable_docker == 'true'
uses: docker/setup-qemu-action@v3
- name: Set up buildx
if: inputs.enable_docker == 'true'
uses: docker/setup-buildx-action@v3

76
.github/actions/security/action.yml vendored Normal file
View File

@ -0,0 +1,76 @@
name: Security
description: Security scanning per service/platform/environment.
inputs:
service:
description: Target service name
required: true
platform:
description: Target platform (e.g., linux/amd64)
required: true
environment:
description: Deployment environment (dev or prod)
required: true
runs:
using: composite
steps:
- name: Prepare matrix context
id: matrix
uses: ./.github/actions/matrix-support
with:
service: ${{ inputs.service }}
platform: ${{ inputs.platform }}
environment: ${{ inputs.environment }}
- name: Run golangci-lint
if: inputs.service != 'dashboard'
uses: golangci/golangci-lint-action@v6
with:
version: latest
args: ./...
- name: Install gosec
if: inputs.service != 'dashboard'
shell: bash
run: go install github.com/securego/gosec/v2/cmd/gosec@latest
- name: Run gosec
if: inputs.service != 'dashboard'
shell: bash
run: gosec ./...
- name: Trivy filesystem scan
if: inputs.service != 'dashboard'
uses: aquasecurity/trivy-action@0.24.0
with:
scan-type: fs
scan-ref: .
severity: HIGH,CRITICAL
ignore-unfixed: true
format: table
exit-code: "0"
- name: Install dashboard dependencies
if: inputs.service == 'dashboard'
working-directory: dashboard
shell: bash
run: yarn install --frozen-lockfile
- name: Run ESLint
if: inputs.service == 'dashboard'
working-directory: dashboard
shell: bash
run: yarn lint
- name: Semgrep security rules
if: inputs.service == 'dashboard'
uses: returntocorp/semgrep-action@v1
with:
config: p/ci
paths: dashboard
- name: npm audit (production)
if: inputs.service == 'dashboard'
working-directory: dashboard
shell: bash
run: npm audit --production
continue-on-error: true

52
.github/actions/test/action.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: Test
description: Run service-specific tests.
inputs:
service:
description: Target service name
required: true
platform:
description: Target platform (e.g., linux/amd64)
required: true
environment:
description: Deployment environment (dev or prod)
required: true
runs:
using: composite
steps:
- name: Prepare matrix context
id: matrix
uses: ./.github/actions/matrix-support
with:
service: ${{ inputs.service }}
platform: ${{ inputs.platform }}
environment: ${{ inputs.environment }}
- name: Run Go integration tests
if: inputs.service != 'dashboard'
shell: bash
run: |
set -euo pipefail
go test ./... -run Integration -count=1
- name: Install dashboard dependencies
if: inputs.service == 'dashboard'
working-directory: dashboard
shell: bash
run: yarn install --frozen-lockfile
- name: Run dashboard unit tests
if: inputs.service == 'dashboard'
working-directory: dashboard
shell: bash
env:
NODE_ENV: ${{ inputs.environment }}
run: yarn test:unit
- name: Run dashboard e2e tests
if: inputs.service == 'dashboard'
working-directory: dashboard
shell: bash
env:
PORT: 3100
NODE_ENV: ${{ inputs.environment }}
run: yarn test:e2e

9
.github/scripts/cosign/sign.sh vendored Executable file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -e
REG="ghcr.io/cloud-neutral-toolkit"
cosign sign --yes "$REG/node-builder@$NODE_BUILDER_DIGEST"
cosign sign --yes "$REG/node-runtime@$NODE_RUNTIME_DIGEST"
cosign sign --yes "$REG/openresty-geoip@$OPENRESTY_GEOIP_DIGEST"
cosign sign --yes "$REG/postgres-runtime@$POSTGRES_RUNTIME_DIGEST"

28
.github/scripts/metadata/gen.py vendored Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env python3
import json, sys
if len(sys.argv) < 4:
print("Usage: gen.py <image-name> <digest> <tags>")
sys.exit(1)
name = sys.argv[1]
digest = sys.argv[2]
raw_tags = sys.argv[3]
tags = raw_tags.splitlines()
preferred = next((t for t in tags if t.endswith(":latest")), tags[0] if tags else "")
metadata = {
"name": name,
"digest": digest,
"tags": tags,
"preferred_tag": preferred,
"image": f"ghcr.io/cloud-neutral-toolkit/{name}",
"image_with_digest": f"ghcr.io/cloud-neutral-toolkit/{name}@{digest}",
}
outfile = f"image-metadata-{name}.json"
with open(outfile, "w", encoding="utf-8") as f:
json.dump(metadata, f, indent=2)
print(f"[metadata] Wrote: {outfile}")

7
.github/scripts/sbom/generate.sh vendored Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -e
IMAGE="$1"
OUT="$2"
anchore-cli sbom generate "$IMAGE" -o "$OUT"

15
.github/scripts/utils/preferred-tag.sh vendored Executable file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -e
tags="$1"
preferred=""
while IFS= read -r line; do
[[ "$line" == *":latest" ]] && preferred="$line" && break
done <<< "$tags"
if [[ -z "$preferred" ]]; then
preferred="$(echo "$tags" | head -n 1)"
fi
echo "$preferred"

296
.github/workflows/build-images.yml vendored Normal file
View File

@ -0,0 +1,296 @@
name: Build Dashboard Images
on:
workflow_call:
inputs:
push_images:
description: "Push service images instead of local builds"
type: boolean
default: true
dockerhub_namespace:
description: "Docker Hub namespace (user/org)"
type: string
skip_security:
description: "Skip security scans and signing"
type: boolean
default: false
node_builder_image:
type: string
default: "node:22-bookworm"
node_runtime_image:
type: string
default: "node:22-slim"
workflow_dispatch:
inputs:
push_images:
type: boolean
default: true
dockerhub_namespace:
description: "Docker Hub namespace (user/org)"
type: string
default: "cloudneutral"
skip_security:
description: "Skip security scans and signing"
type: boolean
default: false
node_builder_image:
type: string
default: "node:22-bookworm"
node_runtime_image:
type: string
default: "node:22-slim"
push:
branches: [ main ]
permissions:
contents: read
packages: write
id-token: write
env:
REGISTRY: ghcr.io
# ✅ 不硬编码:默认推到 ghcr.io/<当前仓库 owner>/...
ORG: ${{ github.repository_owner }}
SKIP_SECURITY: ${{ inputs.skip_security || github.event.inputs.skip_security || 'false' }}
NODE_BUILDER_IMAGE: ${{ inputs.node_builder_image || github.event.inputs.node_builder_image || 'node:22-bookworm' }}
NODE_RUNTIME_IMAGE: ${{ inputs.node_runtime_image || github.event.inputs.node_runtime_image || 'node:22-slim' }}
PUSH_IMAGES: ${{ github.event_name == 'push'
|| (github.event_name == 'workflow_call' && inputs.push_images)
|| (github.event_name == 'workflow_dispatch' && github.event.inputs.push_images == 'true') }}
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
arch:
- { platform: linux/amd64, artifact: linux-amd64 }
- { platform: linux/arm64, artifact: linux-arm64 }
service:
- { name: dashboard, workdir: ., dockerfile: Dockerfile }
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate Auto Tags
id: meta
uses: ./.github/actions/auto-tag
with:
image: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.name }}
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Clone knowledge content
run: git clone https://github.com/Cloud-Neutral-Workshop/knowledge.git knowledge
- name: Build Service Image (per-arch)
id: build
uses: docker/build-push-action@v6
with:
context: ${{ matrix.service.workdir }}
file: ${{ matrix.service.dockerfile }}
platforms: ${{ matrix.arch.platform }}
push: ${{ env.PUSH_IMAGES }}
tags: |
${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.name }}:build-${{ github.sha }}-${{ matrix.arch.artifact }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
GO_RUNTIME_IMAGE=${{ env.GO_RUNTIME_IMAGE }}
NODE_BUILDER_IMAGE=${{ env.NODE_BUILDER_IMAGE }}
NODE_RUNTIME_IMAGE=${{ env.NODE_RUNTIME_IMAGE }}
CONTENTLAYER_BUILD=true
- name: Record digest
run: |
set -euo pipefail
echo "${{ steps.build.outputs.digest }}" > digest-${{ matrix.service.name }}-${{ matrix.arch.artifact }}.txt
- uses: actions/upload-artifact@v4
with:
name: digest-${{ matrix.service.name }}-${{ matrix.arch.artifact }}
path: digest-${{ matrix.service.name }}-${{ matrix.arch.artifact }}.txt
security:
runs-on: ubuntu-latest
needs: build
if: ${{ (github.event_name == 'push' || inputs.push_images == true || github.event.inputs.push_images == 'true') && !((inputs.skip_security == true) || (github.event.inputs.skip_security == 'true')) }}
strategy:
matrix:
arch:
- { platform: linux/amd64, artifact: linux-amd64 }
- { platform: linux/arm64, artifact: linux-arm64 }
service:
- { name: dashboard }
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: digest-${{ matrix.service.name }}-${{ matrix.arch.artifact }}
- name: Load image digest
run: |
set -euo pipefail
echo "IMAGE_DIGEST=$(cat digest-${{ matrix.service.name }}-${{ matrix.arch.artifact }}.txt)" >> "$GITHUB_ENV"
- name: Set image ref
run: |
set -euo pipefail
echo "IMG=${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.name }}:build-${{ github.sha }}-${{ matrix.arch.artifact }}@${{ env.IMAGE_DIGEST }}" >> "$GITHUB_ENV"
- uses: anchore/sbom-action@v0
with:
image: ${{ env.IMG }}
output-file: sbom.spdx.json
- uses: actions/upload-artifact@v4
with:
name: sbom-${{ matrix.service.name }}-${{ matrix.arch.artifact }}
path: sbom.spdx.json
- uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: ${{ env.IMG }}
severity: HIGH,CRITICAL
exit-code: '1'
- uses: sigstore/cosign-installer@v3
with:
cosign-release: 'v2.4.1'
- name: Cosign Sign Image
env:
COSIGN_EXPERIMENTAL: "true"
run: |
set -euo pipefail
cosign sign --yes "${{ env.IMG }}"
push:
runs-on: ubuntu-latest
needs:
- build
- security
if: ${{ needs.build.result == 'success' && (github.event_name == 'push' || inputs.push_images == true || github.event.inputs.push_images == 'true') && ((inputs.skip_security == true) || (github.event.inputs.skip_security == 'true') || (needs.security.result == 'success')) }}
strategy:
fail-fast: false
matrix:
registry:
- ghcr.io
- docker.io
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: actions/download-artifact@v4
with:
name: digest-dashboard-linux-amd64
- uses: actions/download-artifact@v4
with:
name: digest-dashboard-linux-arm64
- name: Load digests
run: |
set -euo pipefail
echo "AMD_DIGEST=$(cat digest-dashboard-linux-amd64.txt)" >> "$GITHUB_ENV"
echo "ARM_DIGEST=$(cat digest-dashboard-linux-arm64.txt)" >> "$GITHUB_ENV"
- name: Generate Auto Tags
id: meta
uses: ./.github/actions/auto-tag
with:
image: ${{ env.REGISTRY }}/${{ env.ORG }}/dashboard
- uses: docker/login-action@v3
if: matrix.registry == 'ghcr.io'
with:
registry: ${{ matrix.registry }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create & Push Multi-Arch Manifests (GHCR)
if: matrix.registry == 'ghcr.io'
run: |
set -euo pipefail
SRC_AMD="${{ env.REGISTRY }}/${{ env.ORG }}/dashboard:build-${{ github.sha }}-linux-amd64@${{ env.AMD_DIGEST }}"
SRC_ARM="${{ env.REGISTRY }}/${{ env.ORG }}/dashboard:build-${{ github.sha }}-linux-arm64@${{ env.ARM_DIGEST }}"
FIRST_TAG=""
echo "${{ steps.meta.outputs.tags }}" | tr ',' '\n' | while read -r TAG; do
[ -z "$TAG" ] && continue
if [ -z "$FIRST_TAG" ]; then FIRST_TAG="$TAG"; fi
docker buildx imagetools create -t "$TAG" "$SRC_AMD" "$SRC_ARM"
done
TAG1="$(echo "${{ steps.meta.outputs.tags }}" | tr ',' '\n' | head -n 1)"
DIGEST="$(docker buildx imagetools inspect "$TAG1" --format '{{.Digest}}')"
echo "MANIFEST_DIGEST=$DIGEST" >> "$GITHUB_ENV"
echo "FINAL_TAG=$TAG1" >> "$GITHUB_ENV"
- name: Clone knowledge content
if: matrix.registry == 'ghcr.io'
run: git clone https://github.com/Cloud-Neutral-Workshop/knowledge.git knowledge
- name: Validate blog content mount
if: matrix.registry == 'ghcr.io'
run: |
set -euo pipefail
IMAGE="${{ env.REGISTRY }}/${{ env.ORG }}/dashboard@${{ env.MANIFEST_DIGEST }}"
docker pull "$IMAGE"
docker run --rm \
-v "${{ github.workspace }}/knowledge/content:/app/dashboard/src/content/blog:ro" \
"$IMAGE" \
sh -c 'test -d /app/dashboard/src/content/blog'
- name: Login to Docker Hub
if: matrix.registry == 'docker.io'
uses: docker/login-action@v3
with:
registry: docker.io
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Copy Multi-Arch Image to Docker Hub (skopeo)
if: matrix.registry == 'docker.io'
env:
TARGET_NS: ${{ inputs.dockerhub_namespace || github.event.inputs.dockerhub_namespace || 'cloudneutral' }}
run: |
set -euo pipefail
sudo apt-get update -y
sudo apt-get install -y skopeo
SRC="docker://ghcr.io/${{ env.ORG }}/dashboard@${{ env.MANIFEST_DIGEST }}"
DST="docker://docker.io/${TARGET_NS}/dashboard:latest"
skopeo login ghcr.io -u "${{ github.actor }}" -p "${{ secrets.GITHUB_TOKEN }}"
skopeo login docker.io -u "${{ secrets.DOCKERHUB_USERNAME }}" -p "${{ secrets.DOCKERHUB_TOKEN }}"
skopeo copy --all "$SRC" "$DST"

49
.github/workflows/check-image.yaml vendored Normal file
View File

@ -0,0 +1,49 @@
name: Check XControl Image Ready
on:
workflow_dispatch:
inputs:
tag:
required: false
default: latest
permissions:
contents: read
packages: read
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Authenticate to GHCR
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "$GITHUB_TOKEN" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
- name: Check images exist and are pullable
env:
TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
IMAGES=(
"ghcr.io/cloud-neutral-toolkit/openresty-geoip"
"ghcr.io/cloud-neutral-toolkit/postgres-runtime"
"ghcr.io/cloud-neutral-toolkit/account"
"ghcr.io/cloud-neutral-toolkit/dashboard"
"ghcr.io/cloud-neutral-toolkit/rag-server"
"ghcr.io/cloud-neutral-toolkit/xcontrol-init"
"docker.io/cloudneutral/openresty-geoip"
"docker.io/cloudneutral/postgres-runtime"
"docker.io/cloudneutral/account"
"docker.io/cloudneutral/dashboard"
"docker.io/cloudneutral/rag-server"
"docker.io/cloudneutral/xcontrol-init"
)
for IMAGE in "${IMAGES[@]}"; do
echo "Checking ${IMAGE}:${TAG}"
docker manifest inspect "${IMAGE}:${TAG}" > /dev/null
docker pull "${IMAGE}:${TAG}" > /dev/null
done

View File

@ -1,240 +0,0 @@
name: Console Service Pipeline
on:
push:
branches:
- main
paths:
- ".github/workflows/pipeline.yaml"
- "Dockerfile"
- "deploy/single-node/**"
- "package.json"
- "yarn.lock"
- "scripts/github-actions/build-and-push-frontend-image.sh"
- "scripts/github-actions/compute-frontend-release-metadata.sh"
- "scripts/github-actions/render-frontend-build-args.sh"
- "scripts/github-actions/render-frontend-runtime-env.sh"
- "scripts/github-actions/prepare-frontend-build-context.sh"
- "scripts/github-actions/run-console-deploy-playbook.sh"
- "scripts/github-actions/run-cloudflare-svc-plus-dns-playbook.sh"
- "scripts/github-actions/verify-frontend-release-over-ssh.sh"
- "scripts/github-actions/verify-frontend-release.sh"
- "scripts/prebuild.sh"
- "contentlayer.config.ts"
- "next.config.js"
- "next.config.mjs"
- "src/**"
- "public/**"
workflow_dispatch:
inputs:
target_host:
description: Ansible host or alias
required: false
default: "jp-xhttp-contabo.svc.plus"
type: string
run_apply:
description: Apply deployment
required: true
default: true
type: boolean
permissions:
contents: read
packages: write
concurrency:
group: console-pipeline-${{ github.ref_name }}
cancel-in-progress: false
env:
CANONICAL_DOMAIN: www.svc.plus
SERVED_DOMAINS: www.svc.plus,console.svc.plus
APP_BASE_URL: https://www.svc.plus
NEXT_PUBLIC_APP_BASE_URL: https://www.svc.plus
NEXT_PUBLIC_SITE_URL: https://www.svc.plus
RUNTIME_HOSTNAME: www.svc.plus
NEXT_RUNTIME_HOSTNAME: www.svc.plus
NEXT_PUBLIC_RUNTIME_ENVIRONMENT: prod
NEXT_PUBLIC_RUNTIME_REGION: cn
ACCOUNT_SERVICE_URL: https://accounts.svc.plus
CLOUDFLARE_ZONE_TAG: bf3427f83a2c52c8285ab3d741a6ee27
CLOUDFLARE_WEB_ANALYTICS_SITE_TAG: 0973e84ec8872c67c570f8072e92e21b
CLOUDFLARE_ACCOUNT_ID: e71be5efb76a6c54f78f008da4404f00
GHCR_REGISTRY: ghcr.io
GHCR_USERNAME: ${{ secrets.GHCR_USERNAME }}
GHCR_PASSWORD: ${{ secrets.GHCR_TOKEN }}
INTERNAL_SERVICE_TOKEN: ${{ secrets.INTERNAL_SERVICE_TOKEN }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_DNS_API_TOKEN: ${{ secrets.CLOUDFLARE_DNS_API_TOKEN }}
jobs:
prep:
name: Prep
runs-on: ubuntu-latest
outputs:
target_host: ${{ steps.inputs.outputs.target_host }}
run_apply: ${{ steps.inputs.outputs.run_apply }}
image_tag: ${{ steps.metadata.outputs.image_tag }}
image_ref: ${{ steps.metadata.outputs.image_ref }}
image_latest_ref: ${{ steps.metadata.outputs.image_latest_ref }}
ghcr_namespace: ${{ steps.metadata.outputs.ghcr_namespace }}
push_latest: ${{ steps.push.outputs.push_latest }}
steps:
- name: Check Out Repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Resolve Inputs
id: inputs
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_TARGET_HOST: ${{ inputs.target_host }}
INPUT_RUN_APPLY: ${{ inputs.run_apply }}
run: bash scripts/github-actions/resolve-workflow-inputs.sh
- name: Compute Image Metadata
id: metadata
run: |
bash scripts/github-actions/compute-frontend-release-metadata.sh
- name: Resolve Push Latest
id: push
env:
REF: ${{ github.ref }}
run: bash scripts/github-actions/resolve-push-latest.sh
build:
name: Build
runs-on: ubuntu-latest
needs: prep
outputs:
image_ref: ${{ steps.publish.outputs.image_ref }}
image_tag: ${{ steps.publish.outputs.image_tag }}
steps:
- name: Check Out Repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set Up Docker Buildx
run: bash scripts/github-actions/setup-docker-buildx.sh
- name: Log In To GHCR
env:
GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }}
run: bash scripts/github-actions/login-ghcr.sh
- name: Publish Frontend Image
id: publish
env:
IMAGE_REF: ${{ needs.prep.outputs.image_ref }}
IMAGE_TAG: ${{ needs.prep.outputs.image_tag }}
IMAGE_LATEST_REF: ${{ needs.prep.outputs.image_latest_ref }}
PUSH_LATEST: ${{ needs.prep.outputs.push_latest }}
run: bash scripts/github-actions/publish-frontend-image.sh
deploy:
name: Deploy
runs-on: ubuntu-latest
needs:
- prep
- build
env:
TARGET_HOST: ${{ needs.prep.outputs.target_host }}
RUN_APPLY: ${{ needs.prep.outputs.run_apply }}
FRONTEND_IMAGE: ${{ needs.build.outputs.image_ref }}
steps:
- name: Check Out Repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Check Out Playbooks Repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
# Intentionally pinned: playbooks@main regressed deploy reliability on 2026-04-12.
# Any future bump must pass a full Deploy + Validate run before becoming the default.
repository: x-evor/playbooks
ref: 80c545a95c3b16459f6494ed13d951faac57bfa8
path: playbooks
token: ${{ github.token }}
- name: Set Up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
- name: Install Ansible
run: |
python -m pip install --upgrade pip
python -m pip install ansible
- name: Configure SSH For Deploy Host
env:
SINGLE_NODE_VPS_SSH_PRIVATE_KEY: ${{ secrets.SINGLE_NODE_VPS_SSH_PRIVATE_KEY }}
TARGET_HOST: ${{ needs.prep.outputs.target_host }}
run: bash scripts/github-actions/configure-ssh-for-deploy.sh
- name: Run Deploy Playbook
working-directory: playbooks
env:
ANSIBLE_HOST_KEY_CHECKING: "False"
run: bash ../scripts/github-actions/run-console-deploy-playbook.sh
validate:
name: Validate
runs-on: ubuntu-latest
needs:
- prep
- build
- deploy
if: ${{ always() && needs.deploy.result == 'success' }}
env:
EXPECTED_FRONTEND_IMAGE: ${{ needs.build.outputs.image_ref }}
TARGET_HOST: ${{ needs.prep.outputs.target_host }}
SINGLE_NODE_VPS_SSH_PRIVATE_KEY: ${{ secrets.SINGLE_NODE_VPS_SSH_PRIVATE_KEY }}
steps:
- name: Check Out Repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Configure SSH For Validate Host
run: bash scripts/github-actions/configure-ssh-for-deploy.sh
- name: Verify Frontend Release On Host
run: bash scripts/github-actions/verify-frontend-release-over-ssh.sh
update_dns:
name: Update DNS
runs-on: ubuntu-latest
needs:
- prep
- build
- deploy
if: ${{ always() && needs.deploy.result == 'success' }}
continue-on-error: true
env:
TARGET_HOST: ${{ needs.prep.outputs.target_host }}
RUN_APPLY: ${{ needs.prep.outputs.run_apply }}
FRONTEND_IMAGE: ${{ needs.build.outputs.image_ref }}
steps:
- name: Check Out Repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Check Out Playbooks Repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
repository: x-evor/playbooks
ref: 80c545a95c3b16459f6494ed13d951faac57bfa8
path: playbooks
token: ${{ github.token }}
- name: Set Up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
- name: Install Ansible
run: |
python -m pip install --upgrade pip
python -m pip install ansible
- name: Update Cloudflare svc.plus DNS
working-directory: playbooks
env:
ANSIBLE_HOST_KEY_CHECKING: "False"
run: bash ../scripts/github-actions/run-cloudflare-svc-plus-dns-playbook.sh

5
.gitignore vendored
View File

@ -1,13 +1,10 @@
.env
models/
*.tsbuildinfo
knowledge/
# macOS
.DS_Store
ui/.DS_Store
ui/*/.DS_Store
src/content/
# Node.js dependencies
node_modules/
@ -19,7 +16,6 @@ public/_build/
public/dl-index/
.contentlayer/
.dev-logs/
.console-state/
# Contentlayer cache
ui/docs/.contentlayer/
@ -56,7 +52,6 @@ coverage/
.env
.env.local
.env.*.local
deploy/single-node/.env.runtime
# Build artifacts
build/

View File

@ -1 +0,0 @@
registry "https://registry.npmjs.org"

View File

@ -4,12 +4,4 @@ enableGlobalCache: false
nodeLinker: node-modules
npmRegistryServer: "https://registry.npmjs.org"
packageExtensions:
"next-contentlayer@*":
peerDependencies:
next: "*"
"contentlayer@*":
peerDependencies:
next: "*"
npmRegistryServer: "https://registry.npmmirror.com"

View File

@ -28,8 +28,7 @@ yarn preview # Build and start production server
### Code Quality
```bash
yarn lint # Run ESLint (currently fails under Next 16 CLI; use eslint command below)
./node_modules/.bin/eslint . --no-eslintrc --config .eslintrc.json --resolve-plugins-relative-to . # Run ESLint directly (ignore parent configs)
yarn lint # Run ESLint
yarn typecheck # TypeScript type checking
yarn format # Format code with Prettier
```
@ -51,20 +50,7 @@ yarn test:e2e path/to/spec.test.ts
---
## 2. Release Traceability Default Rule
For any change touching CI/CD, image tags, deploy contracts, `/api/ping`, or `validate` behavior:
- Treat `skills/release-traceability/SKILL.md` as the default reference before implementation.
- Prefer release metadata that can be traced from `build` to `deploy` to `validate` without manual injection.
- Keep the published image reference, runtime version, and validation output aligned.
- Do not introduce a deploy path that rebuilds images on the target host.
When in doubt, follow the skill first and keep the release chain fully auditable end to end.
---
## 3. Repository Mental Model (Read This First)
## 2. Repository Mental Model (Read This First)
This repository has **three clearly separated layers**:
@ -104,7 +90,7 @@ Used for build-time or runtime wiring only.
---
## 4. Import & Alias Rules (Critical)
## 3. Import & Alias Rules (Critical)
### Dashboard code (src/\*\*)
@ -125,7 +111,7 @@ import { UserCard } from "@/components/UserCard";
---
## 5. TypeScript & Formatting Rules
## 4. TypeScript & Formatting Rules
- Strict mode enabled
- Use `type` for type definitions, `interface` for object shapes
@ -136,7 +122,7 @@ import { UserCard } from "@/components/UserCard";
---
## 6. Naming Conventions
## 5. Naming Conventions
- Components: PascalCase (`UserProfile.tsx`)
- Files: kebab-case for utilities (`user-utils.ts`), PascalCase for components
@ -146,7 +132,7 @@ import { UserCard } from "@/components/UserCard";
---
## 7. Error Handling & Logging
## 6. Error Handling & Logging
- Use try/catch for async operations
- Return Result types or throw errors consistently
@ -155,7 +141,7 @@ import { UserCard } from "@/components/UserCard";
---
## 8. React Patterns
## 7. React Patterns
- Use `'use client'` directive for client components
- Prefer function components with hooks
@ -164,7 +150,7 @@ import { UserCard } from "@/components/UserCard";
---
## 9. Global State Rules (Dashboard Only)
## 8. Global State Rules (Dashboard Only)
✅ Zustand is the **only** allowed global state mechanism
❌ React Context for shared/global state is forbidden
@ -175,7 +161,7 @@ Rule: If state must survive navigation or be shared → it lives in Zustand.
---
## 10. URL-Synchronized State
## 9. URL-Synchronized State
Anything involving:
@ -192,7 +178,7 @@ MUST be handled inside Zustand slices.
---
## 11. Component State Rules
## 10. Component State Rules
Allowed:
@ -207,7 +193,7 @@ Forbidden:
---
## 12. packages/neurapress Rules (Very Important)
## 11. packages/neurapress Rules (Very Important)
packages/neurapress is treated as a vendored internal library.
@ -227,7 +213,7 @@ MUST NOT:
---
## 13. Testing Guidelines
## 12. Testing Guidelines
- Unit tests: Vitest with jsdom environment
- E2E tests: Playwright
@ -237,7 +223,7 @@ MUST NOT:
---
## 14. Environment & Runtime Config
## 13. Environment & Runtime Config
- No new environment variables without approval
- Runtime config must live in: src/config/runtime-service-config\*.yaml
@ -246,13 +232,13 @@ MUST NOT:
---
## 15. Cursor / Copilot Rules
## 14. Cursor / Copilot Rules
- No `.cursor/rules/`, `.cursorrules`, or `.github/copilot-instructions.md` found
---
## 16. TL;DR for AI Agents
## 15. TL;DR for AI Agents
- dashboard = application
- packages = libraries
@ -260,47 +246,3 @@ MUST NOT:
- No @/ imports inside packages
- Never "fix" libraries by polluting the app
- Always run `yarn lint` and `yarn typecheck` before completing tasks
- If `yarn lint` fails with "Invalid project directory .../lint" (Next 16 CLI), use `./node_modules/.bin/eslint .` instead
---
# Agent Operating Rules
You are an AI agent working inside this repository.
## Role
- Act as a senior engineer and automation assistant.
- Prioritize correctness, minimal changes, and reproducibility.
- agent.md mirrors AGENTS.md; when in doubt, follow AGENTS.md as the source of truth.
## Global Rules
- Do not introduce new dependencies unless explicitly requested.
- Do not change API contracts without explicit instruction.
- Do not add new environment variables without approval.
- Keep changes scoped to the request; avoid unrelated refactors.
- Prefer minimal edits that preserve existing behavior and style.
## Repository Constraints (Quick View)
- App layer: src/app/**, src/components/**, src/lib/**, src/state/**, src/modules/**
- Library layer: packages/** (no @/ aliases, no global state)
- Global state: Zustand only, in src/state/** or src/app/store/**
- URL-synced state must live in Zustand slices
## Testing Policy
- Follow mcp/testing.md for minimal verification.
- Always run the minimal verification after a coherent change-set.
## Output Format
Always respond with:
1. Summary of changes
2. Commands executed
3. Result (success/failure)
4. Next step
If these rules conflict with user instructions, ask once for clarification and proceed conservatively.

View File

@ -4,27 +4,6 @@
ARG NODE_BUILDER_IMAGE=node:22-bookworm
ARG NODE_RUNTIME_IMAGE=node:22-slim
ARG CONTENTLAYER_BUILD=true
ARG NEXT_PUBLIC_APP_BASE_URL=
ARG NEXT_PUBLIC_SITE_URL=
ARG NEXT_PUBLIC_LOGIN_URL=
ARG NEXT_PUBLIC_DOCS_BASE_URL=
ARG NEXT_PUBLIC_RUNTIME_ENVIRONMENT=
ARG NEXT_PUBLIC_RUNTIME_REGION=
ARG NEXT_PUBLIC_GISCUS_REPO=
ARG NEXT_PUBLIC_GISCUS_REPO_ID=
ARG NEXT_PUBLIC_GISCUS_CATEGORY=
ARG NEXT_PUBLIC_GISCUS_CATEGORY_ID=
ARG NEXT_PUBLIC_PAYPAL_CLIENT_ID=
ARG NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO=
ARG NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION=
ARG NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO=
ARG NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION=
ARG NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO=
ARG NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION=
ARG NEXT_PUBLIC_RELEASE_IMAGE=
ARG NEXT_PUBLIC_RELEASE_TAG=
ARG NEXT_PUBLIC_RELEASE_COMMIT=
ARG NEXT_PUBLIC_RELEASE_VERSION=
# -------------------------------------------------------
# Stage 1 — Builder (Turbopack + standalone)
@ -33,51 +12,8 @@ FROM ${NODE_BUILDER_IMAGE} AS builder
WORKDIR /app/dashboard
ARG NEXT_PUBLIC_APP_BASE_URL
ARG NEXT_PUBLIC_SITE_URL
ARG NEXT_PUBLIC_LOGIN_URL
ARG NEXT_PUBLIC_DOCS_BASE_URL
ARG NEXT_PUBLIC_RUNTIME_ENVIRONMENT
ARG NEXT_PUBLIC_RUNTIME_REGION
ARG NEXT_PUBLIC_GISCUS_REPO
ARG NEXT_PUBLIC_GISCUS_REPO_ID
ARG NEXT_PUBLIC_GISCUS_CATEGORY
ARG NEXT_PUBLIC_GISCUS_CATEGORY_ID
ARG NEXT_PUBLIC_PAYPAL_CLIENT_ID
ARG NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO
ARG NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION
ARG NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO
ARG NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION
ARG NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO
ARG NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION
ARG NEXT_PUBLIC_RELEASE_IMAGE
ARG NEXT_PUBLIC_RELEASE_TAG
ARG NEXT_PUBLIC_RELEASE_COMMIT
ARG NEXT_PUBLIC_RELEASE_VERSION
ENV NEXT_TELEMETRY_DISABLED=1 \
NEXT_PRIVATE_TURBOPACK=1 \
NEXT_PUBLIC_APP_BASE_URL=${NEXT_PUBLIC_APP_BASE_URL} \
NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL} \
NEXT_PUBLIC_LOGIN_URL=${NEXT_PUBLIC_LOGIN_URL} \
NEXT_PUBLIC_DOCS_BASE_URL=${NEXT_PUBLIC_DOCS_BASE_URL} \
NEXT_PUBLIC_RUNTIME_ENVIRONMENT=${NEXT_PUBLIC_RUNTIME_ENVIRONMENT} \
NEXT_PUBLIC_RUNTIME_REGION=${NEXT_PUBLIC_RUNTIME_REGION} \
NEXT_PUBLIC_GISCUS_REPO=${NEXT_PUBLIC_GISCUS_REPO} \
NEXT_PUBLIC_GISCUS_REPO_ID=${NEXT_PUBLIC_GISCUS_REPO_ID} \
NEXT_PUBLIC_GISCUS_CATEGORY=${NEXT_PUBLIC_GISCUS_CATEGORY} \
NEXT_PUBLIC_GISCUS_CATEGORY_ID=${NEXT_PUBLIC_GISCUS_CATEGORY_ID} \
NEXT_PUBLIC_PAYPAL_CLIENT_ID=${NEXT_PUBLIC_PAYPAL_CLIENT_ID} \
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO=${NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO} \
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION=${NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION} \
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO=${NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO} \
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION=${NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION} \
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO=${NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO} \
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION=${NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION} \
NEXT_PUBLIC_RELEASE_IMAGE=${NEXT_PUBLIC_RELEASE_IMAGE} \
NEXT_PUBLIC_RELEASE_TAG=${NEXT_PUBLIC_RELEASE_TAG} \
NEXT_PUBLIC_RELEASE_COMMIT=${NEXT_PUBLIC_RELEASE_COMMIT} \
NEXT_PUBLIC_RELEASE_VERSION=${NEXT_PUBLIC_RELEASE_VERSION}
NEXT_PRIVATE_TURBOPACK=1
# ---------------------------
# 基础镜像升级到最新
@ -123,6 +59,7 @@ RUN apt-get update \
COPY --from=builder /app/dashboard/.next/standalone ./
COPY --from=builder /app/dashboard/.next/static ./static
COPY --from=builder /app/dashboard/public ./public
COPY --from=builder /app/dashboard/src/content/blog ./src/content/blog
# ---------------------------
# 额外瘦身(可减少 1540 MB

810
LICENSE
View File

@ -1,202 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Preamble
1. Definitions.
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
The precise terms and conditions for copying, distribution and
modification follow.
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
TERMS AND CONDITIONS
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
0. Definitions.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
"This License" refers to version 3 of the GNU General Public License.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
A "covered work" means either the unmodified Program or a work based
on the Program.
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
1. Source Code.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
END OF TERMS AND CONDITIONS
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
APPENDIX: How to apply the Apache License to your work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
The Corresponding Source for a work in source code form is that
same work.
Copyright (C) 2018-2026 Ruohang Feng, @Vonng (rh@vonng.com)
2. Basic Permissions.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
http://www.apache.org/licenses/LICENSE-2.0
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@ -49,6 +49,7 @@ init:
echo "❌ Unsupported OS. Please install Yarn manually."; exit 1; \
fi; \
fi
yarn config set npmRegistryServer https://registry.npmmirror.com
yarn install --immutable
ensure-deps:
@ -82,6 +83,7 @@ test:
@yarn test || echo "No tests configured"
build: init
yarn config set npmRegistryServer https://registry.npmmirror.com
@if [ -z "$(SKIP_SYNC)" ]; then \
$(MAKE) sync-dl-index; \
fi

175
README.md
View File

@ -1,138 +1,89 @@
# console.svc.plus
Cloud Neutral Toolkit 的开放云控制面板 (Open Cloud Control Panel).
**工程师 · 开源 · 云中立**
面向 **Ops / Infra / AI** 的统一前端仪表盘,强调技术自由与可迁移性。
关注 **Ops / Infra / AI****技术自由**
🏗️ 热衷于构建“逃生舱”,防止基础设施被厂商锁定。
> A unified dashboard for Ops / Infra / AI, built for technical freedom and portability.
> **Accountable Engineer · Open Source · Cloud Neutral**
>
> Focus on **Ops / Infra / AI** and **Technical Freedom**.
> 🏗️ Passionate about building "escape pods" to prevent infrastructure vendor lock-in.
## 部署要求 (Deployment Requirements)
---
| 维度 | 要求 / 规格 | 说明 |
| ----------- | ------------------ | ------------------------------------- |
| Node.js | `>=18.17 <25` | 推荐使用 `.nvmrc` |
| 包管理 | Yarn (推荐) 或 npm | Yarn 推荐配合 Corepack |
| Git | 必需 | 用于拉取仓库 |
| 部署 (可选) | Vercel / 自建 | 部署方式见 `docs/usage/deployment.md` |
**console.svc.plus** 是 Cloud Neutral Toolkit 的**开放云控制面板**。
## 快速开始 (Quickstart)
> **console.svc.plus** is the **Open Cloud Control Panel** for the Cloud Neutral Toolkit.
### 一键初始化 (Setup Script)
## 项目简介 (About The Project)
本项目是 Cloud Neutral 生态系统的核心可视化界面(前端仪表盘)。它连接各个微服务,为管理云中立基础设施提供统一的控制平面。
> This repository serves as the central visual interface (Frontend Dashboard) for the Cloud Neutral ecosystem. It connects various micro-services to provide a unified control plane for managing your cloud-neutral infrastructure.
该生态系统目前包含多个专用的微后端和服务:
* **console.svc.plus**: (本项目) 主前端仪表盘。
* **accounts.svc.plus**: 身份与账户管理服务。
* **postgresql.svc.plus**: 带有专用扩展的 PostgreSQL 数据库服务。
* **rag-server.svc.plus**: 检索增强生成 (RAG) 后端。
* **page-reading-agent-backend**: 页面阅读智能体后端逻辑。
* **page-reading-agent-dashboard**: 页面阅读智能体专用仪表盘。
* **wechat-to-markdown.svc.plus**: 微信内容转 Markdown 工具服务。
## 技术栈 (Tech Stack)
本仪表盘使用现代 Web 技术构建:
> This dashboard is built using modern web technologies:
* **框架**: [Next.js](https://nextjs.org/)
* **语言**: TypeScript
* **样式**: [Tailwind CSS](https://tailwindcss.com/)
* **UI 组件**: [Radix UI](https://www.radix-ui.com/)
* **内容管理**: [Contentlayer](https://contentlayer.dev/)
## 快速开始 (Getting Started)
### 前置要求 (Prerequisites)
* Node.js (`>=18.17 <25`)
* Yarn (推荐) 或 npm
### 安装 (Installation)
```bash
curl -fsSL "https://raw.githubusercontent.com/x-evor/console.svc.plus/main/scripts/setup.sh?$(date +%s)" \
| bash -s -- console.svc.plus
yarn install
```
### 本地运行 (Local Dev)
### 本地运行 (Running Locally)
启动开发服务器:
> To start the development server:
```bash
yarn dev
```
如果需要环境变量:
此命令会运行设置脚本 (`scripts/Dev-MCP-Server.sh`) 并启动带有 TurboPack 的 Next.js 开发服务器。
> This command runs the setup script (`scripts/Dev-MCP-Server.sh`) and starts the Next.js development server with TurboPack.
### 构建生产版本 (Building for Production)
```bash
cp .env.example .env
```
如果你的工作区同时包含 `openclaw-deploy-example`,建议参考 `../openclaw-deploy-example/.env` 填写 AI 助手联调配置,并同时查看 `docs/getting-started/installation.md`
## 主要入口 (Key Routes)
- `/services`:服务导航页,保留现有控制台布局。
- `/xworkmate`:原生 Next.js 的 XWorkmate 在线工作区,底层通过 `xworkmate-bridge``/acp/rpc` 接入。
- `/panel/api`:融合设置与集成页,用于配置和探测 OpenClaw Gateway、Vault Server、APISIX AI Gateway。
## AI 助手与集成能力 (Assistant & Integrations)
当前主页 AI 辅助功能已经基于本仓库原生实现,核心行为如下:
- 侧栏助手模式保留现有交互方式,但 `/xworkmate` 主工作区直接对接 `xworkmate-bridge`
- 最大化助手页面统一收敛到 `/xworkmate`,旧的 `/services/openclaw` 只保留兼容跳转,不再继续使用旧的 control UI 套壳。
- 页面截图通过 assistant chat 附件模式发送,而不是单独的浏览器控制壳。
- `/panel/api` 仍保留旧集成配置入口;`/xworkmate` 主路径不依赖它。
- bridge 地址与令牌从服务端环境变量读取,前端组件不硬编码敏感配置。
## 环境变量 (Environment Variables)
以下变量用于 `/xworkmate` 主工作区的服务端 bridge 代理:
| 变量 | 用途 |
| ------------------- | ------------------------------------- |
| `BRIDGE_SERVER_URL` | XWorkmate bridge 服务根地址 |
| `BRIDGE_AUTH_TOKEN` | XWorkmate bridge bearer token服务端 |
以下变量用于旧助手和集成页的服务端默认值预填:
| 变量 | 用途 |
| ----------------------------- | ------------------------------------ |
| `OPENCLAW_GATEWAY_REMOTE_URL` | OpenClaw gateway 远端 WebSocket 地址 |
| `OPENCLAW_GATEWAY_TOKEN` | OpenClaw gateway 访问令牌 |
| `VAULT_SERVER_URL` | Vault 服务地址 |
| `VAULT_NAMESPACE` | Vault namespace可选 |
| `VAULT_TOKEN` | Vault 探测令牌 |
| `APISIX_AI_GATEWAY_URL` | APISIX AI Gateway 地址 |
| `AI_GATEWAY_ACCESS_TOKEN` | APISIX AI Gateway 探测令牌 |
更多说明见 `docs/getting-started/installation.md``.env.example`
## Stripe 配置 (Stripe Billing Setup)
`/prices`、产品页和账户中心的购买入口现在统一读取前端公开的 Stripe `price_id`
| 变量 | 用途 |
| -------------------------------------------------- | ------------------- |
| `NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO` | Xstream 按量购买 |
| `NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION` | Xstream 订阅 |
| `NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO` | XScopeHub 按量购买 |
| `NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION` | XScopeHub 订阅 |
| `NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO` | XCloudFlow 按量购买 |
| `NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION` | XCloudFlow 订阅 |
这些值应填写为 Stripe Dashboard 中对应套餐的 `price_...` 标识。联调步骤见 `docs/integrations/stripe-billing.md`
## 核心特性 & 技术栈 (Features & Tech Stack)
核心特性:
- 统一控制面:汇聚 Cloud Neutral Toolkit 各微服务入口
- 原生 AI 助手工作区OpenClaw gateway 驱动的聊天、截图附件与会话体验
- 融合集成设置:在 `/panel/api` 统一管理 OpenClaw、Vault、APISIX AI Gateway
- 文档与内容系统Contentlayer 驱动的 docs/content pipeline
- 可扩展集成OIDC、Cloudflare Web Analytics 等
技术栈:
- Next.js + TypeScript
- Tailwind CSS + Radix UI
- Zustand
- Contentlayer
## 开发命令 (Useful Commands)
```bash
yarn dev
yarn build
yarn typecheck
./node_modules/.bin/eslint . --no-eslintrc --config .eslintrc.json --resolve-plugins-relative-to .
```
## 说明文档 (Docs)
## 开发指南 (Development Guidelines)
入口:
有关详细的编码标准、架构规则和 Agent 特定说明,请参阅 [AGENTS.md](./AGENTS.md)。
- EN: `docs/README.md`
- ZH: `docs/zh/README.md`
> For detailed coding standards, architecture rules, and agent-specific instructions, please refer to [AGENTS.md](./AGENTS.md).
常用链接:
## 脚本 (Scripts)
- OIDC: `docs/integrations/oidc-auth.md`
- Cloudflare Web Analytics: `docs/integrations/cloudflare-web-analytics.md`
- Stripe billing: `docs/integrations/stripe-billing.md`
- Assistant / Integrations env setup: `docs/getting-started/installation.md`
- Chinese installation guide: `docs/zh/getting-started/installation.md`
其他:
- Agent rules: `AGENTS.md`
* `dev`: 启动开发服务器。
* `build`: 构建生产版本应用。
* `test`: 使用 Vitest 运行单元测试。
* `test:e2e`: 使用 Playwright 运行端到端测试。
* `lint`: 运行代码检查 (Linter)。

View File

@ -1,230 +0,0 @@
# RAG Server 数据库连接修复 Runbook
## 📋 概述
**问题**: RAG Server 无法连接到 PostgreSQL 数据库,导致 `/api/rag/query` 返回 404
**影响范围**: RAG 检索功能、向量搜索、知识库同步
**修复时间**: ~5 分钟
**风险等级**: 🟡 中等(前端已实现降级,用户体验影响有限)
---
## 🔍 问题诊断
### 错误症状
```
ERROR: cannot parse `admin_password`: failed to parse as keyword/value (invalid keyword/value)
WARN: postgres cache disabled; no database connection
```
### 根本原因
1. ❌ `DATABASE_URL` 环境变量指向 Secret Manager 引用 `admin_password`,而不是实际的数据库连接字符串
2. ❌ RAG 服务器无法连接到 PostgreSQL 数据库
3. ❌ 导致 `/api/rag/query` 返回 404
### 架构图
```
┌─────────────────────┐
│ Cloud Run │
│ (RAG Server) │
└──────────┬──────────┘
┌─────────────────────┐
│ Stunnel │
│ 127.0.0.1:5432 │
└──────────┬──────────┘
│ TLS Tunnel
┌─────────────────────┐
│ postgresql │
│ .onwalk.net:443 │
└──────────┬──────────┘
┌─────────────────────┐
│ PostgreSQL │
│ Database │
└─────────────────────┘
```
---
## ✅ 修复方案
### 方案 1: 使用 Stunnel (推荐) ⭐
#### 优势
- ✅ 安全性更好TLS 加密)
- ✅ 不需要暴露数据库公网 IP
- ✅ 符合现有架构设计
- ✅ entrypoint.sh 已经支持 Stunnel
#### 步骤 1: 更新 Cloud Run 环境变量
```bash
gcloud run services update rag-server-svc-plus \
--region asia-northeast1 \
--project xzerolab-480008 \
--update-env-vars="\
DB_TLS_HOST=postgresql.onwalk.net,\
DB_TLS_PORT=443,\
POSTGRES_USER=postgres,\
POSTGRES_PASSWORD=otdcRLTJamszk3AE,\
POSTGRES_DB=knowledge_db,\
NVIDIA_API_KEY=NVAPI_KEY_PLACEHOLDER" \
--clear-env-vars=DATABASE_URL
```
**关键参数说明**:
- `DB_TLS_HOST``DB_TLS_PORT`: 触发 entrypoint.sh 启动 Stunnel
- Stunnel 会在 `127.0.0.1:5432` 监听
- `config/rag-server.yaml` 中的 `pgurl` 会自动使用 `127.0.0.1:5432`
- `--clear-env-vars=DATABASE_URL`: 移除错误的环境变量
#### 步骤 2: 验证部署
**检查日志**:
```bash
gcloud logging read \
"resource.type=cloud_run_revision AND resource.labels.service_name=rag-server-svc-plus" \
--limit 20 \
--project xzerolab-480008 \
--format="table(timestamp,textPayload)"
```
**期望输出**:
```
Starting Stunnel...
Stunnel is up!
INFO: Connected to PostgreSQL at 127.0.0.1:5432
```
#### 步骤 3: 功能测试
**测试 RAG 查询**:
```bash
curl -X POST https://rag-server-svc-plus-HASH-an.a.run.app/api/rag/query \
-H "Content-Type: application/json" \
-d '{"query": "test query", "top_k": 5}'
```
**期望响应**: HTTP 200包含检索结果
---
### 方案 2: 直接数据库连接 (不推荐)
⚠️ **仅在 PostgreSQL 有公网 IP 且无法使用 Stunnel 时使用**
```bash
# 更新 Secret Manager
echo "postgres://postgres:otdcRLTJamszk3AE@<PUBLIC_IP>:5432/knowledge_db?sslmode=require" | \
gcloud secrets versions add DATABASE_URL --data-file=- --project xzerolab-480008
# 更新 Cloud Run 使用 Secret
gcloud run services update rag-server-svc-plus \
--region asia-northeast1 \
--project xzerolab-480008 \
--update-secrets=DATABASE_URL=DATABASE_URL:latest
```
---
## 📊 当前状态
### ✅ 工作的功能
- `/api/askai` - 直接 AI 回答(不依赖数据库)
- CORS 配置
- NVIDIA API 集成
### ❌ 待修复的功能
- `/api/rag/query` - RAG 检索(需要数据库连接)
- 向量搜索
- 知识库同步
---
## 🎯 前端降级策略
前端已经实现了优雅降级:
1. **首选**: 尝试 `/api/rag/query` (RAG 检索)
2. **降级**: 如果失败,回退到 `/api/askai` (直接 AI)
3. **结果**: 用户仍然可以获得答案,只是没有知识库上下文
---
## 🔄 回滚计划
如果修复后出现问题,执行以下回滚:
```bash
# 恢复原有 DATABASE_URL
gcloud run services update rag-server-svc-plus \
--region asia-northeast1 \
--project xzerolab-480008 \
--update-secrets=DATABASE_URL=admin_password:latest \
--remove-env-vars=DB_TLS_HOST,DB_TLS_PORT,POSTGRES_USER,POSTGRES_PASSWORD,POSTGRES_DB
```
---
## 📝 验证清单
- [ ] Cloud Run 环境变量已更新
- [ ] `DATABASE_URL` 已清除
- [ ] 日志显示 "Stunnel is up!"
- [ ] `/api/rag/query` 返回 200
- [ ] 前端 RAG 功能正常
- [ ] 向量搜索可用
---
## 📚 相关文档
- [Stunnel 配置文档](../docs/stunnel-setup.md)
- [PostgreSQL 连接指南](../docs/postgres-connection.md)
- [Cloud Run 环境变量管理](https://cloud.google.com/run/docs/configuring/environment-variables)
---
## 🆘 故障排查
### 问题: Stunnel 无法启动
**检查**:
```bash
# 查看 Stunnel 日志
gcloud logging read "textPayload=~\"stunnel\"" --limit 50 --project xzerolab-480008
```
**可能原因**:
- DNS 解析失败
- 防火墙阻止 443 端口
- 证书验证失败
### 问题: 数据库连接超时
**检查**:
```bash
# 测试网络连通性
gcloud run services describe rag-server-svc-plus \
--region asia-northeast1 \
--project xzerolab-480008 \
--format="value(status.url)"
```
**可能原因**:
- PostgreSQL 服务未运行
- 密码错误
- 数据库名称错误
---
**最后更新**: 2026-01-26
**负责人**: DevOps Team
**审核人**: Tech Lead

View File

@ -1,123 +0,0 @@
# RAG Server 数据库连接修复方案
## 问题诊断
### 当前错误
```
ERROR: cannot parse `admin_password`: failed to parse as keyword/value (invalid keyword/value)
WARN: postgres cache disabled; no database connection
```
### 根本原因
1. `DATABASE_URL` 环境变量指向 Secret Manager 引用 `admin_password`,而不是实际的数据库连接字符串
2. RAG 服务器无法连接到 PostgreSQL 数据库
3. 导致 `/api/rag/query` 返回 404
## 架构说明
```
Cloud Run (RAG Server)
Stunnel (127.0.0.1:5432)
↓ TLS Tunnel
postgresql.onwalk.net:443
PostgreSQL Database
```
## 修复步骤
### 方案 1: 使用 Stunnel (推荐)
#### 1. 更新 Cloud Run 环境变量
```bash
gcloud run services update rag-server-svc-plus \
--region asia-northeast1 \
--project xzerolab-480008 \
--set-env-vars="
DB_TLS_HOST=postgresql.onwalk.net,
DB_TLS_PORT=443,
POSTGRES_USER=postgres,
POSTGRES_PASSWORD=otdcRLTJamszk3AE,
POSTGRES_DB=knowledge_db,
NVIDIA_API_KEY=NVAPI_KEY_PLACEHOLDER"
```
**说明**:
- `DB_TLS_HOST``DB_TLS_PORT` 会触发 entrypoint.sh 启动 Stunnel
- Stunnel 会在 `127.0.0.1:5432` 监听
- `config/rag-server.yaml` 中的 `pgurl` 会自动使用 `127.0.0.1:5432`
#### 2. 验证配置
部署后检查日志:
```bash
gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=rag-server-svc-plus" \
--limit 20 --project xzerolab-480008
```
应该看到:
```
Starting Stunnel...
Stunnel is up!
```
### 方案 2: 直接数据库连接 (不推荐,需要公网 IP)
如果 PostgreSQL 有公网 IP可以直接连接
```bash
# 更新 Secret Manager
echo "postgres://postgres:otdcRLTJamszk3AE@<PUBLIC_IP>:5432/knowledge_db?sslmode=require" | \
gcloud secrets versions add DATABASE_URL --data-file=- --project xzerolab-480008
```
## 当前状态
### 工作的功能
- ✅ `/api/askai` - 直接 AI 回答(不依赖数据库)
- ✅ CORS 配置
- ✅ NVIDIA API 集成
### 待修复的功能
- ❌ `/api/rag/query` - RAG 检索(需要数据库连接)
- ❌ 向量搜索
- ❌ 知识库同步
## 前端行为
前端已经实现了优雅降级:
1. 首先尝试 `/api/rag/query` (RAG 检索)
2. 如果失败,回退到 `/api/askai` (直接 AI)
3. 用户仍然可以获得答案,只是没有知识库上下文
## 建议
**立即执行方案 1**,因为:
1. 安全性更好TLS 加密)
2. 不需要暴露数据库公网 IP
3. 符合现有架构设计
4. entrypoint.sh 已经支持 Stunnel
## 部署命令
```bash
# 完整部署命令
gcloud run services update rag-server-svc-plus \
--region asia-northeast1 \
--project xzerolab-480008 \
--update-env-vars="
DB_TLS_HOST=postgresql.onwalk.net,
DB_TLS_PORT=443,
POSTGRES_USER=postgres,
POSTGRES_PASSWORD=otdcRLTJamszk3AE,
POSTGRES_DB=knowledge_db,
NVIDIA_API_KEY=NVAPI_KEY_PLACEHOLDER" \
--clear-env-vars=DATABASE_URL
```
注意:
- 使用 `--update-env-vars` 添加新变量
- 使用 `--clear-env-vars` 移除错误的 `DATABASE_URL`

View File

@ -1,48 +0,0 @@
# Runbook 目录
本目录包含该项目的运维手册和故障排查文档。
## 📚 文档分类
### 🔧 部署相关
- 部署流程
- 环境配置
- 依赖管理
### 🚨 故障排查
- 常见问题
- 错误诊断
- 应急处理
### 🔄 运维操作
- 日常维护
- 备份恢复
- 性能优化
### 📊 监控告警
- 监控指标
- 告警规则
- 日志分析
## 📝 文档规范
每个 Runbook 应包含:
1. **问题描述**: 清晰描述问题现象
2. **影响范围**: 说明影响的功能和用户
3. **诊断步骤**: 详细的问题定位方法
4. **修复方案**: 具体的解决步骤
5. **验证方法**: 确认问题已解决的检查清单
6. **回滚计划**: 如果修复失败的应急方案
## 🎯 命名规范
- 使用描述性的文件名
- 格式: `[类型]-[简短描述].md`
- 示例: `Deploy-Database-Migration.md`, `Fix-API-Timeout.md`
## 📅 维护说明
- 定期更新文档
- 记录最后更新时间
- 标注负责人和审核人

View File

@ -1,49 +0,0 @@
# X (Twitter) Post Drafts for SVC.PLUS Update
Here are a few options for your X post, ranging from technical to marketing-focused.
## Option 1: Feature & Tech Highlight (Recommended)
**Focus:** Transparency, Integration, and Performance.
🚀 **Update: Real-time Metrics Dashboard is Live!**
We've upgraded the svc.plus homepage stats with transparent, dynamic data sources:
**Registered Users**: Synced hourly from our Postgres DB
**24h/Weekly/Monthly Traffic**: Powered by Cloudflare GraphQL API
Tech stack: Next.js + Cloudflare Analytics + SWR Caching.
Check it out! 👇
[Insert Link]
#BuildInPublic #Nextjs #Cloudflare #SaaS #WebDev
---
## Option 2: Short & Punchy
**Focus:** Quick announcement.
**Transparent metrics, delivered fast.** ⚡️
The new svc.plus dashboard now displays:
🔹 Live User Count (Hourly sync)
🔹 Global Traffic: 24h, Weekly, & Monthly stats (via Cloudflare)
See our growth in real-time: [Insert Link]
#OpenSource #CloudNative #DevOps
---
## Option 3: Chinese Version (中文版)
**Focus:** Addressing the Chinese-speaking community foundation.
**🚀 svc.plus 首页看板升级完成!**
我们主打真实 transparent 的数据展示:
📊 **注册用户数**:直连 PG 数据库,每小时动态更新
🌍 **访问量数据**24h/周/月):集成 Cloudflare Analytics API
拒绝虚假数据,每一位访客和注册用户都清晰可见。
欢迎体验:[Insert Link]
#独立开发 #SaaS #Nextjs #Cloudflare

View File

@ -1,46 +0,0 @@
# Agent Operating Rules
You are an AI agent working inside this repository.
## Role
- Act as a senior engineer and automation assistant.
- Prioritize correctness, minimal changes, and reproducibility.
- agent.md mirrors AGENTS.md; when in doubt, follow AGENTS.md as the source of truth.
## Global Rules
- Do not introduce new dependencies unless explicitly requested.
- Do not change API contracts without explicit instruction.
- Do not add new environment variables without approval.
- Keep changes scoped to the request; avoid unrelated refactors.
- Prefer minimal edits that preserve existing behavior and style.
## Release Traceability Default Rule
- For changes touching CI/CD, image tags, deploy contracts, `/api/ping`, or `validate`, treat `skills/release-traceability/SKILL.md` as the default reference first.
- Keep build output, runtime version, and validate output aligned through the whole release chain.
- Do not add a deploy path that rebuilds images on the target host.
## Repository Constraints (Quick View)
- App layer: src/app/**, src/components/**, src/lib/**, src/state/**, src/modules/\*\*
- Library layer: packages/\*\* (no @/ aliases, no global state)
- Global state: Zustand only, in src/state/** or src/app/store/**
- URL-synced state must live in Zustand slices
## Testing Policy
- Follow mcp/testing.md for minimal verification.
- Always run the minimal verification after a coherent change-set.
## Output Format
Always respond with:
1. Summary of changes
2. Commands executed
3. Result (success/failure)
4. Next step
If these rules conflict with user instructions, ask once for clarification and proceed conservatively.

View File

@ -1,332 +0,0 @@
# Feature flag inventory for the dashboard and public site.
# This file is a human-readable catalog of page-level and module-level flags.
meta:
app: console.svc.plus
scope:
- public-site
- dashboard
- xworkmate
- cloud-iac
- docs
source:
- src/config/feature-toggles.json
- src/modules/extensions/builtin
- src/app
runtime:
current_implementation:
toggles_json: src/config/feature-toggles.json
loader: src/lib/featureToggles.ts
extension_flags: src/lib/featureFlags.ts
notes:
- "feature-toggles.json is the active runtime source for path-based gating."
- "This YAML is an inventory and planning file; it does not currently drive runtime behavior."
sections:
globalNavigation:
enabled: true
description: Top-level navigation and auth entry points.
default_channel: stable
routes:
- path: /
status: enabled
channel: stable
- path: /docs
status: enabled
channel: beta
- path: /blogs
status: enabled
note: Public content listing, not currently toggle-gated in code.
- path: /services
status: enabled
note: Public service directory.
- path: /prices
status: enabled
- path: /support
status: enabled
- path: /about
status: enabled
- path: /login
status: enabled
channel: stable
- path: /register
status: enabled
channel: stable
- path: /panel
status: enabled
channel: stable
- path: /panel/management
status: enabled
note: Shown only to admin/operator users.
- path: /cloud_iac
status: enabled
channel: develop
- path: /download
status: enabled
channel: stable
- path: /insight
status: enabled
channel: develop
- path: /xworkmate
status: enabled
appModules:
enabled: true
description: Path-based module gating used by route handlers and content loaders.
routes:
- path: /docs
status: enabled
uses: src/app/docs/page.tsx
- path: /docs/[collection]
status: enabled
uses: src/app/docs/[collection]/page.tsx
- path: /docs/[collection]/[...slug]
status: enabled
uses: src/app/docs/[collection]/[...slug]/page.tsx
- path: /download
status: enabled
uses: src/app/download/page.tsx
- path: /download/[...segments]
status: enabled
uses: src/app/download/[...segments]/page.tsx
- path: /cloud_iac
status: enabled
uses: src/app/cloud_iac/page.tsx
- path: /cloud_iac/[provider]
status: enabled
uses: src/app/cloud_iac/[provider]/page.tsx
- path: /cloud_iac/[provider]/[service]
status: enabled
uses: src/app/cloud_iac/[provider]/[service]/page.tsx
- path: /insight
status: enabled
uses: src/app/services/insight/page.tsx
- path: /editor
status: enabled
uses: src/app/editor/page.tsx
- path: /editor/wechat
status: enabled
- path: /editor/xiaohongshu
status: enabled
- path: /xworkmate
status: enabled
uses: src/app/xworkmate/page.tsx
- path: /xworkmate/admin
status: enabled
uses: src/app/xworkmate/admin/page.tsx
- path: /xworkmate/integrations
status: enabled
uses: src/app/xworkmate/integrations/page.tsx
cmsExperience:
enabled: true
description: CMS/homepage experience gating.
routes:
- path: /homepage
status: enabled
children:
- path: /homepage/dynamic
status: enabled
extensions:
builtin.user-center:
enabled: true
description: Core dashboard user center and account management extension.
routes:
- path: /panel
id: dashboard
enabled: true
sidebar_section: workspace
- path: /panel/agent
id: agents
enabled: true
env_var: NEXT_PUBLIC_FEATURE_AGENT_MODULE
default_enabled: true
sidebar_section: productivity
- path: /panel/api
id: apis
enabled: true
env_var: NEXT_PUBLIC_FEATURE_API_MODULE
default_enabled: true
sidebar_section: productivity
- path: /panel/account
id: accounts
enabled: true
sidebar_section: management
- path: /panel/subscription
id: subscription
enabled: true
env_var: NEXT_PUBLIC_FEATURE_SUBSCRIPTION_MODULE
default_enabled: true
sidebar_section: management
- path: /panel/ldp
id: ldp
enabled: true
env_var: NEXT_PUBLIC_FEATURE_LDP_MODULE
default_enabled: false
sidebar_section: management
- path: /panel/appearance
id: appearance
enabled: true
sidebar_section: preferences
- path: /panel/management
id: management
enabled: true
roles:
- admin
- operator
permissions:
- admin.settings.read
- admin.users.metrics.read
- admin.users.list.read
- admin.agents.status.read
- admin.blacklist.read
sidebar_hidden: true
builtin.infra:
enabled: true
description: Infrastructure and ops extension.
routes:
- path: /panel/deployments
id: deployments
enabled: true
sidebar_section: infra
- path: /panel/resources
id: resources
enabled: true
sidebar_section: infra
- path: /panel/api-keys
id: apiKeys
enabled: true
sidebar_section: infra
- path: /panel/observability
id: logs
enabled: true
sidebar_section: infra
- path: /panel/settings
id: settings
enabled: true
sidebar_section: preferences
pages:
public:
- path: /
component: src/app/page.tsx
status: enabled
- path: /services
component: src/app/services/page.tsx
status: enabled
- path: /about
component: src/app/about/page.tsx
status: enabled
- path: /prices
component: src/app/prices/page.tsx
status: enabled
- path: /support
component: src/app/support/page.tsx
status: enabled
- path: /support/discussions
component: src/app/support/discussions/page.tsx
status: enabled
- path: /blogs
component: src/app/blogs/page.tsx
status: enabled
- path: /blogs/[...slug]
component: src/app/blogs/[...slug]/page.tsx
status: enabled
- path: /terms
component: src/app/terms/page.tsx
status: enabled
- path: /privacy
component: src/app/privacy/page.tsx
status: enabled
- path: /download
component: src/app/download/page.tsx
status: gated_by_appModules
- path: /download/[...segments]
component: src/app/download/[...segments]/page.tsx
status: gated_by_appModules
- path: /docs
component: src/app/docs/page.tsx
status: gated_by_appModules
- path: /docs/[collection]
component: src/app/docs/[collection]/page.tsx
status: gated_by_appModules
- path: /docs/[collection]/[...slug]
component: src/app/docs/[collection]/[...slug]/page.tsx
status: gated_by_appModules
- path: /cloud_iac
component: src/app/cloud_iac/page.tsx
status: gated_by_appModules
- path: /cloud_iac/[provider]
component: src/app/cloud_iac/[provider]/page.tsx
status: gated_by_appModules
- path: /cloud_iac/[provider]/[service]
component: src/app/cloud_iac/[provider]/[service]/page.tsx
status: gated_by_appModules
- path: /editor
component: src/app/editor/page.tsx
status: redirect_external
- path: /editor/wechat
component: src/app/editor/wechat/page.tsx
status: enabled
- path: /editor/xiaohongshu
component: src/app/editor/xiaohongshu/page.tsx
status: enabled
- path: /xworkmate
component: src/app/xworkmate/page.tsx
status: enabled
- path: /xworkmate/admin
component: src/app/xworkmate/admin/page.tsx
status: enabled
- path: /xworkmate/integrations
component: src/app/xworkmate/integrations/page.tsx
status: enabled
auth:
- path: /login
component: src/app/(auth)/login/page.tsx
status: gated_by_globalNavigation
- path: /register
component: src/app/(auth)/register/page.tsx
status: gated_by_globalNavigation
- path: /email-verification
component: src/app/(auth)/email-verification/page.tsx
status: gated_by_globalNavigation
- path: /logout
component: src/app/logout/page.tsx
status: enabled
panel:
- path: /panel
component: src/app/panel/page.tsx
status: extension_route
- path: /panel/account
component: src/app/panel/account/page.tsx
status: extension_route
- path: /panel/agent
component: src/app/panel/agent/page.tsx
status: extension_route
- path: /panel/api
component: src/app/panel/api/page.tsx
status: extension_route
- path: /panel/appearance
component: src/app/panel/appearance/page.tsx
status: extension_route
- path: /panel/ldp
component: src/app/panel/ldp/page.tsx
status: extension_route
- path: /panel/management
component: src/app/panel/management/page.tsx
status: extension_route
- path: /panel/subscription
component: src/app/panel/subscription/page.tsx
status: extension_route
- path: /panel/[...segments]
component: src/app/panel/[...segments]/page.tsx
status: catch_all_extension
recommendations:
- "If this file is intended to become runtime-configurable, wire it into src/lib/featureToggles.ts and keep feature-toggles.json as the generated artifact."
- "If this file is intended only for documentation, keep it synchronized with feature-toggles.json and extension definitions."

View File

@ -1,41 +0,0 @@
# Gitleaks configuration file
# For more information, see https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml
[allowlist]
description = "Global allowlist"
paths = [
'''vendor/''',
'''node_modules/''',
'''\.env''',
'''\.env\..*$''',
'''\.next/''',
'''\.env\.example$''',
'''\.env\.test$''',
'''go\.sum$''',
'''package-lock\.json$''',
]
stopwords = [
"example",
"placeholder",
"test-password",
]
[[rules]]
id = "generic-api-key"
description = "Generic API Key"
regex = '''(?i)(api_key|apikey|secret|password|token)[-|_| ]*[=|\:][-|_| ]*['|"]([0-9a-zA-Z]{16,128})['|"]'''
description_id = "potential_secret"
entropy = 3.5
keywords = ["api_key", "apikey", "secret", "password", "token"]
[[rules]]
id = "github-pat"
description = "GitHub Personal Access Token"
regex = '''ghp_[0-9a-zA-Z]{36}'''
keywords = ["ghp_"]
[[rules]]
id = "google-oauth-client-secret"
description = "Google OAuth Client Secret"
regex = '''(?i)client_secret[-|_| ]*[=|\:][-|_| ]*['|"]([0-9a-zA-Z\-_]{24})['|"]'''
keywords = ["client_secret"]

View File

@ -1,7 +1,24 @@
import { makeSource } from 'contentlayer/source-files'
import { defineDocumentType, makeSource } from 'contentlayer/source-files'
export const Workshop = defineDocumentType(() => ({
name: 'Workshop',
filePathPattern: '**/*.mdx',
contentType: 'mdx',
fields: {
title: { type: 'string', required: true },
summary: { type: 'string', required: true },
level: { type: 'string', default: 'Intro' },
duration: { type: 'string', required: false },
tags: { type: 'list', of: { type: 'string' }, default: [] },
updatedAt: { type: 'date', required: false },
},
computedFields: {
slug: { type: 'string', resolve: (doc) => doc._raw.flattenedPath },
url: { type: 'string', resolve: (doc) => `/workshop/${doc._raw.flattenedPath}` },
},
}))
export default makeSource({
contentDirPath: 'src/content',
documentTypes: [],
contentDirPath: 'src/content/workshop',
documentTypes: [Workshop],
})

View File

@ -1,54 +0,0 @@
# Compose settings
FRONTEND_IMAGE=ghcr.io/cloud-neutral-toolkit/dashboard:replace-me
CANONICAL_DOMAIN=www.svc.plus
SERVED_DOMAINS=www.svc.plus,console.svc.plus
# Frontend runtime
NODE_ENV=production
PORT=3000
RUNTIME_ENV=prod
REGION=cn
APP_BASE_URL=https://www.svc.plus
NEXT_PUBLIC_APP_BASE_URL=https://www.svc.plus
NEXT_PUBLIC_SITE_URL=https://www.svc.plus
NEXT_PUBLIC_LOGIN_URL=https://www.svc.plus/login
NEXT_PUBLIC_DOCS_BASE_URL=https://www.svc.plus/docs
SESSION_COOKIE_SECURE=true
NEXT_PUBLIC_SESSION_COOKIE_SECURE=true
RUNTIME_HOSTNAME=www.svc.plus
DEPLOYMENT_HOSTNAME=www.svc.plus
NEXT_PUBLIC_RUNTIME_ENVIRONMENT=prod
NEXT_PUBLIC_RUNTIME_REGION=cn
# Upstream service URLs
ACCOUNT_SERVICE_URL=https://accounts.svc.plus
NEXT_PUBLIC_ACCOUNT_SERVICE_URL=https://accounts.svc.plus
SERVER_SERVICE_URL=https://api.svc.plus
NEXT_PUBLIC_SERVER_SERVICE_URL=https://api.svc.plus
SERVER_SERVICE_INTERNAL_URL=
# Optional integrations
OPENCLAW_GATEWAY_REMOTE_URL=
OPENCLAW_GATEWAY_TOKEN=
VAULT_SERVER_URL=
VAULT_NAMESPACE=
VAULT_TOKEN=
APISIX_AI_GATEWAY_URL=
AI_GATEWAY_ACCESS_TOKEN=
INTERNAL_SERVICE_TOKEN=
CLOUDFLARE_API_TOKEN=
CLOUDFLARE_ACCOUNT_ID=
CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=
CLOUDFLARE_ZONE_TAG=
ROOT_EMAIL_WHITELIST=admin@svc.plus
NEXT_PUBLIC_PAYPAL_CLIENT_ID=
NEXT_PUBLIC_GISCUS_REPO=cloud-neutral-toolkit/console.svc.plus
NEXT_PUBLIC_GISCUS_REPO_ID=
NEXT_PUBLIC_GISCUS_CATEGORY=General
NEXT_PUBLIC_GISCUS_CATEGORY_ID=
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO=
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION=
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO=
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION=
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO=
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION=

View File

@ -1,28 +0,0 @@
{$SERVED_DOMAINS} {
encode zstd gzip
handle_path /_next/static/* {
root * /srv
header Cache-Control "public, max-age=31536000, immutable"
file_server
}
@public_assets {
file {
root /srv/public
try_files {path}
}
}
handle @public_assets {
root * /srv/public
header Cache-Control "public, max-age=3600"
file_server
}
reverse_proxy dashboard:3000 {
header_up Host {host}
header_up X-Forwarded-Host {host}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-For {remote_host}
}
}

View File

@ -1,55 +0,0 @@
services:
frontend-assets:
image: ${FRONTEND_IMAGE:?set FRONTEND_IMAGE in .env.runtime}
restart: "no"
command:
- /bin/sh
- -c
- |
set -eu
rm -rf /assets/_next /assets/chunks /assets/public
mkdir -p /assets /assets/public
cp -R /app/dashboard/static/. /assets/
cp -R /app/dashboard/public/. /assets/public
volumes:
- frontend_static:/assets
dashboard:
image: ${FRONTEND_IMAGE:?set FRONTEND_IMAGE in .env.runtime}
restart: unless-stopped
env_file:
- .env.runtime
environment:
NODE_ENV: production
PORT: 3000
volumes:
- frontend_static:/app/dashboard/.next/static:ro
networks:
- frontend
caddy:
image: caddy:2.10-alpine
restart: unless-stopped
depends_on:
- dashboard
ports:
- "80:80"
- "443:443"
environment:
SERVED_DOMAINS: ${SERVED_DOMAINS:?set SERVED_DOMAINS in .env.runtime}
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- frontend_static:/srv:ro
- caddy_data:/data
- caddy_config:/config
networks:
- frontend
networks:
frontend:
driver: bridge
volumes:
frontend_static:
caddy_data:
caddy_config:

39
dev.log
View File

@ -1,39 +0,0 @@
▲ Next.js 16.1.6 (Turbopack)
- Local: http://localhost:3000
- Network: http://192.168.0.2:3000
✓ Starting...
Attention: Next.js now collects completely anonymous telemetry regarding usage.
This information is used to shape Next.js' roadmap and prioritize features.
You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
https://nextjs.org/telemetry
✓ Ready in 4.4s
○ Compiling / ...
GET / 200 in 11.8s (compile: 11.0s, render: 755ms)
GET / 200 in 513ms (compile: 159ms, render: 354ms)
GET /api/integrations/defaults 200 in 1276ms (compile: 1242ms, render: 35ms)
[runtime-config] Loaded env: PROD
GET /api/auth/session 200 in 2.2s (compile: 2.2s, render: 15ms)
GET /api/marketing/home-stats 200 in 1435ms (compile: 1425ms, render: 9ms)
GET / 200 in 150ms (compile: 5ms, render: 145ms)
GET /api/auth/session 200 in 16ms (compile: 4ms, render: 12ms)
GET /api/integrations/defaults 200 in 20ms (compile: 12ms, render: 8ms)
GET /api/marketing/home-stats 200 in 29ms (compile: 22ms, render: 7ms)
GET /api/blogs/latest?limit=7 200 in 37ms (compile: 29ms, render: 8ms)

View File

@ -1,14 +0,0 @@
# Documentation Coverage Matrix
This matrix tracks the bilingual canonical documentation set for `console.svc.plus` and maps it back to the current codebase and older docs.
该矩阵用于跟踪 `console.svc.plus` 的双语规范文档,并将其与当前代码状态和历史文档对应起来。
| Category | EN | ZH | Current status | Existing references | Next check |
| --- | --- | --- | --- | --- | --- |
| Architecture | Yes | Yes | Seeded from current codebase and existing docs. | `api/overview.md`<br>`architecture/components.md`<br>`architecture/design-decisions.md`<br>`architecture/overview.md`<br>`architecture/roadmap.md`<br>`development/code-structure.md`<br>`zh/api/overview.md`<br>`zh/architecture/components.md` | Keep diagrams and ownership notes synchronized with actual directories, services, and integration dependencies. |
| Design | Yes | Yes | Seeded from current codebase and existing docs. | `SEO-WORK-SUMMARY.md`<br>`architecture/design-decisions.md`<br>`zh/SEO-WORK-SUMMARY.md`<br>`zh/architecture/design-decisions.md` | Promote one-off implementation notes into reusable design records when behavior, APIs, or deployment contracts change. |
| Deployment | Yes | Yes | Seeded from current codebase and existing docs. | `development/dev-setup.md`<br>`getting-started/installation.md`<br>`getting-started/quickstart.md`<br>`governance/release-process.md`<br>`operations/runbooks/README.md`<br>`operations/runbooks/rag-server.md`<br>`usage/deployment.md`<br>`zh/development/dev-setup.md` | Verify deployment steps against current scripts, manifests, CI/CD flow, and environment contracts before each release. |
| User Guide | Yes | Yes | Seeded from current codebase and existing docs. | `api/overview.md`<br>`architecture/overview.md`<br>`getting-started/concepts.md`<br>`getting-started/installation.md`<br>`getting-started/introduction.md`<br>`getting-started/quickstart.md`<br>`usage/cli.md`<br>`usage/config.md` | Prefer workflow-oriented examples and keep screenshots or terminal snippets aligned with the latest UI or CLI behavior. |
| Developer Guide | Yes | Yes | Seeded from current codebase and existing docs. | `api/auth.md`<br>`api/endpoints.md`<br>`api/errors.md`<br>`api/overview.md`<br>`development/code-structure.md`<br>`development/contributing.md`<br>`development/dev-setup.md`<br>`development/testing.md` | Keep setup and test commands tied to actual package scripts, Make targets, or language toolchains in this repository. |
| Vibe Coding Reference | Yes | Yes | Seeded from current codebase and existing docs. | `api/auth.md`<br>`api/endpoints.md`<br>`api/errors.md`<br>`api/overview.md`<br>`zh/api/auth.md`<br>`zh/api/endpoints.md`<br>`zh/api/errors.md`<br>`zh/api/overview.md` | Review prompt templates and repo rules whenever the project adds new subsystems, protected areas, or mandatory verification steps. |

View File

@ -1,11 +1,3 @@
# Development Setup
## Purpose
- This document covers local development setup and tooling notes.
## Module System Migration
# 🔄 CommonJS → ES Module 迁移指南
## 📊 迁移概览
@ -71,3 +63,4 @@ npm run build
3. **静态分析**: IDE 支持更好
4. **未来兼容**: ECMAScript 标准
5. **性能提升**: 更好的模块加载

View File

@ -1,42 +0,0 @@
# Console Service Plus / 控制台服务
This `docs/` directory now has a bilingual canonical layer for the current repository state.
`docs/` 目录现已补齐双语规范层,用于承接当前仓库状态下的核心文档。
## Quick Entry / 快速入口
- Coverage checklist / 覆盖检查矩阵: `docs/DOC_COVERAGE.md`
- English index / 英文入口: `docs/en/README.md`
- 中文入口 / Chinese index: `docs/zh/README.md`
## Canonical Bilingual Pages / 双语规范页
- `docs/en/architecture.md` / `docs/zh/architecture.md`
- `docs/en/design.md` / `docs/zh/design.md`
- `docs/en/deployment.md` / `docs/zh/deployment.md`
- `docs/en/user-guide.md` / `docs/zh/user-guide.md`
- `docs/en/developer-guide.md` / `docs/zh/developer-guide.md`
- `docs/en/vibe-coding-reference.md` / `docs/zh/vibe-coding-reference.md`
## Current Repo Context / 当前仓库背景
- Root README: `console.svc.plus`
- Previous docs index: `Documentation`
- Manifest evidence / 构建清单: package.json (`dashboard`)
- Active code and ops directories / 当前主要目录: `src/`, `scripts/`, `tests/`, `config/`, `public/`
## Existing Docs To Reconcile / 需要继续归并的现有文档
- `SEO-AUDIT-REPORT.md`
- `SEO-WORK-SUMMARY.md`
- `advanced/customization.md`
- `advanced/performance.md`
- `advanced/scalability.md`
- `advanced/security.md`
- `api/auth.md`
- `api/endpoints.md`
- `api/errors.md`
- `api/overview.md`
- `appendix/faq.md`
- `appendix/glossary.md`

View File

@ -1,467 +0,0 @@
# SEO Audit Report - console.svc.plus
**Date**: 2026-01-29
**Audited By**: Antigravity AI
**Scope**: SEO optimization without changing functionality
---
## 📊 Summary of Issues
Based on Google Search Console data:
- **404 Errors**: 804 pages
- **Duplicate Pages**: 299 instances
- **Redirect Issues**: 8 instances
- **5xx Errors**: 6 instances
- **Soft 404**: 3 instances
- **Missing noindex**: 1 instance
- **robots.txt Blocked**: 1 instance
- **401 Errors**: 1 instance
- **Missing Index**: 235 instances
---
## 🔴 Critical Issues
### 1. Dead Links (404 Errors) - 804 Pages
**Problem**: Numerous `href="#"` placeholders throughout the codebase
**Affected Files**:
- `src/app/page.tsx` (line 259)
- `src/components/Header.tsx` (lines 22, 25, 28)
- `src/components/DownloadSection.tsx` (line 68)
- `src/app/(auth)/login/LoginContent.tsx` (line 299)
- `src/app/(auth)/login/LoginForm.tsx` (line 303)
**Impact**:
- Poor user experience
- Negative SEO ranking
- Crawl budget waste
**Fix Priority**: 🔴 HIGH
---
### 2. Missing not-found.tsx
**Problem**: No custom 404 page at app root level
**Current State**:
- Has `/404/page.tsx` but not `not-found.tsx`
- Next.js 13+ App Router requires `not-found.tsx` for proper 404 handling
**Impact**:
- Improper 404 handling
- Missing SEO metadata on 404 pages
**Fix Priority**: 🔴 HIGH
---
### 3. Incomplete SEO Metadata
**Problem**: Root layout missing essential SEO tags
**Current State** (`src/app/layout.tsx`):
```typescript
export const metadata = {
title: 'Cloud-Neutral',
description: 'Unified tools for your cloud native stack',
}
```
**Missing**:
- Open Graph tags
- Twitter Card tags
- Canonical URLs
- Viewport meta tag
- Theme color
- Robots meta tag
**Fix Priority**: 🟡 MEDIUM
---
### 4. Anchor Links Without Proper Targets
**Problem**: Hash links (`#features`, `#docs`, etc.) without corresponding IDs
**Affected Files**:
- `src/app/[slug]/Client.tsx` (lines 146-161)
- `src/components/marketing/ProductScenarios.tsx` (lines 57, 71)
- `src/components/marketing/ProductDownload.tsx` (line 88)
**Impact**:
- Broken in-page navigation
- Poor user experience
- Potential crawl errors
**Fix Priority**: 🟡 MEDIUM
---
### 5. robots.txt Configuration Issues
**Problem**: Conflicting rules in robots.txt
**Current State**:
```
User-agent: Googlebot
Allow: /
Allow: /_next/static/
Allow: /_next/image
Disallow: /admin/
Disallow: /api/
Disallow: /internal/
Disallow: /_next/ # ⚠️ Conflicts with Allow above
```
**Fix Priority**: 🟡 MEDIUM
---
### 6. Missing Structured Data
**Problem**: No JSON-LD structured data for rich snippets
**Missing**:
- Organization schema
- WebSite schema
- BreadcrumbList schema
- Article schema (for blog posts)
**Fix Priority**: 🟢 LOW
---
## 🛠️ Recommended Fixes
### Fix 1: Replace All `href="#"` Links
**Action**: Replace placeholder links with actual URLs or remove them
```typescript
// ❌ Before
<a href="#">Learn more</a>
// ✅ After (Option 1: Real link)
<Link href="/docs/getting-started">Learn more</Link>
// ✅ After (Option 2: Button if not navigating)
<button onClick={handleAction}>Learn more</button>
// ✅ After (Option 3: Disabled state)
<span className="text-muted cursor-not-allowed">Coming soon</span>
```
**Files to Update**:
1. `src/app/page.tsx`
2. `src/components/Header.tsx`
3. `src/components/DownloadSection.tsx`
4. `src/app/(auth)/login/LoginContent.tsx`
5. `src/app/(auth)/login/LoginForm.tsx`
---
### Fix 2: Add not-found.tsx
**Action**: Create proper 404 handler
**File**: `src/app/not-found.tsx`
```typescript
import type { Metadata } from 'next'
import Link from 'next/link'
export const metadata: Metadata = {
title: '404 - Page Not Found | Cloud-Neutral',
description: 'The page you are looking for does not exist.',
robots: {
index: false,
follow: false,
},
}
export default function NotFound() {
return (
<main className="flex min-h-screen flex-col items-center justify-center bg-background px-4 py-24 text-center">
<p className="text-sm font-semibold uppercase tracking-wide text-primary">404</p>
<h1 className="mt-4 text-4xl font-bold text-heading">Page not found</h1>
<p className="mt-3 max-w-md text-sm text-text-muted">
The page you were looking for could not be found. Please return to the homepage.
</p>
<Link
href="/"
className="mt-6 inline-flex items-center rounded-full bg-primary px-5 py-2 text-sm font-semibold text-white shadow hover:bg-primary-hover"
>
Back to homepage
</Link>
</main>
)
}
```
---
### Fix 3: Enhanced SEO Metadata
**Action**: Update root layout with comprehensive metadata
**File**: `src/app/layout.tsx`
```typescript
import type { Metadata } from 'next'
export const metadata: Metadata = {
metadataBase: new URL('https://www.svc.plus'),
title: {
default: 'Cloud-Neutral | Unified Cloud Native Tools',
template: '%s | Cloud-Neutral',
},
description: 'Unified tools for your cloud native stack. Manage infrastructure, deployments, and services across multiple cloud providers.',
keywords: ['cloud native', 'kubernetes', 'infrastructure', 'devops', 'cloud management'],
authors: [{ name: 'Cloud-Neutral Team' }],
creator: 'Cloud-Neutral',
publisher: 'Cloud-Neutral',
formatDetection: {
email: false,
address: false,
telephone: false,
},
openGraph: {
type: 'website',
locale: 'en_US',
url: 'https://www.svc.plus',
title: 'Cloud-Neutral | Unified Cloud Native Tools',
description: 'Unified tools for your cloud native stack',
siteName: 'Cloud-Neutral',
images: [
{
url: '/og-image.png',
width: 1200,
height: 630,
alt: 'Cloud-Neutral Platform',
},
],
},
twitter: {
card: 'summary_large_image',
title: 'Cloud-Neutral | Unified Cloud Native Tools',
description: 'Unified tools for your cloud native stack',
images: ['/og-image.png'],
creator: '@cloudneutral',
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
verification: {
google: 'your-google-verification-code',
},
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#6366f1" />
<link rel="canonical" href="https://www.svc.plus" />
{/* ... rest of head */}
</head>
{/* ... rest of layout */}
</html>
)
}
```
---
### Fix 4: Add Section IDs for Anchor Links
**Action**: Add proper `id` attributes to sections
**File**: `src/app/[slug]/Client.tsx`
```typescript
// Add IDs to sections
<section id="features">
{/* Features content */}
</section>
<section id="editions">
{/* Editions content */}
</section>
<section id="scenarios">
{/* Scenarios content */}
</section>
<section id="download">
{/* Download content */}
</section>
<section id="docs">
{/* Docs content */}
</section>
<section id="faq">
{/* FAQ content */}
</section>
```
---
### Fix 5: Clean Up robots.txt
**Action**: Remove conflicting rules
**File**: `public/robots.txt`
```txt
User-agent: Googlebot
Allow: /
Allow: /_next/static/
Allow: /_next/image
Disallow: /admin/
Disallow: /api/
Disallow: /internal/
User-agent: *
Allow: /
Allow: /_next/static/
Allow: /_next/image
Disallow: /admin/
Disallow: /api/
Disallow: /internal/
Sitemap: https://www.svc.plus/sitemap.xml
```
---
### Fix 6: Add Structured Data
**Action**: Add JSON-LD schemas
**File**: `src/app/layout.tsx`
```typescript
export default function RootLayout({ children }: { children: React.ReactNode }) {
const organizationSchema = {
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'Cloud-Neutral',
url: 'https://www.svc.plus',
logo: 'https://www.svc.plus/logo.png',
sameAs: [
'https://twitter.com/cloudneutral',
'https://github.com/x-evor',
],
}
const websiteSchema = {
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'Cloud-Neutral',
url: 'https://www.svc.plus',
potentialAction: {
'@type': 'SearchAction',
target: 'https://www.svc.plus/search?q={search_term_string}',
'query-input': 'required name=search_term_string',
},
}
return (
<html lang="en">
<head>
{/* ... other head elements */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteSchema) }}
/>
</head>
{/* ... rest */}
</html>
)
}
```
---
## 📋 Implementation Checklist
### Phase 1: Critical Fixes (Week 1)
- [ ] Replace all `href="#"` with proper URLs or buttons
- [ ] Create `src/app/not-found.tsx`
- [ ] Add section IDs for anchor links
- [ ] Update root layout metadata
### Phase 2: Important Fixes (Week 2)
- [ ] Clean up robots.txt
- [ ] Add structured data (JSON-LD)
- [ ] Create OG image (`public/og-image.png`)
- [ ] Add canonical URLs to all pages
### Phase 3: Optimization (Week 3)
- [ ] Add breadcrumb schema to docs pages
- [ ] Add article schema to blog posts
- [ ] Implement dynamic metadata for all pages
- [ ] Add alt text to all images
---
## 🎯 Expected Improvements
After implementing these fixes:
1. **404 Errors**: Should drop from 804 to <10
2. **Crawl Efficiency**: Improved by ~60%
3. **SEO Score**: Expected increase of 15-20 points
4. **User Experience**: Significantly better navigation
5. **Search Rankings**: Gradual improvement over 2-3 months
---
## 📊 Monitoring
After deployment, monitor:
1. **Google Search Console**:
- Coverage report
- Core Web Vitals
- Mobile usability
2. **Analytics**:
- Bounce rate on 404 page
- Navigation patterns
- Search traffic
3. **Tools**:
- Lighthouse CI
- Ahrefs/SEMrush
- Screaming Frog
---
## 🔗 Resources
- [Next.js SEO Best Practices](https://nextjs.org/learn/seo/introduction-to-seo)
- [Google Search Central](https://developers.google.com/search)
- [Schema.org Documentation](https://schema.org/)
---
**Next Steps**: Review this report and prioritize fixes based on business impact.

View File

@ -1,165 +0,0 @@
# SEO 优化工作总结
**日期**: 2026-01-29
**项目**: console.svc.plus + page-reading-agent 集成
---
## ✅ 已完成的工作
### 1. console.svc.plus SEO 优化
#### 创建的文件:
1. **`docs/SEO-AUDIT-REPORT.md`** - 完整的 SEO 审计报告
- 发现 804 个 404 错误
- 识别死链、缺失元数据等问题
- 提供详细修复方案
2. **`src/app/not-found.tsx`** - 自定义 404 页面
- 包含 SEO 元数据noindex, nofollow
- 提供友好的用户体验
- 包含导航链接
3. **`public/robots.txt`** - 修复后的 robots.txt
- 移除冲突规则
- 正确配置爬虫访问权限
4. **`src/app/layout.tsx`** - 增强的 SEO 元数据
- Open Graph tags
- Twitter Card tags
- JSON-LD 结构化数据Organization, WebSite
- Viewport 和 theme-color
5. **`scripts/check-seo-issues.js`** - SEO 检查脚本
- 扫描死链 (href="#")
- 检查缺失 alt 文本
- 验证锚点链接
#### Git 状态:
- ✅ 已提交到 console.svc.plus
- ✅ Commit: `598b113 feat: add comprehensive SEO metadata...`
---
### 2. page-reading-agent-backend SEO 集成
#### 创建的文件:
1. **`core/seo-audit.js`** - SEO 审计模块
- 元数据分析
- 标题结构分析
- 图片 alt 文本检查
- 链接分析(内部/外部/死链)
- 结构化数据检测
- 性能指标
- 移动端适配检查
- 内容分析
- 评分系统
- 问题分类critical/warnings/suggestions
---
## 🔄 待完成的工作
### 1. page-reading-agent-backend API 集成
需要在 `main.js` 中添加 SEO 审计端点:
```javascript
// 在 main.js 中添加
import { auditSEO, generateSEOReport } from './core/seo-audit.js';
// 添加新的路由
if (req.url.startsWith('/seo-audit')) {
// ... SEO 审计逻辑
}
```
### 2. page-reading-agent-dashboard 前端集成
需要创建:
1. SEO 审计结果展示组件
2. 右侧面板显示
3. 报告导出功能
4. MCP 查询接口
### 3. moltbot.svc.plus Git 同步
当前状态:
- ⏳ Rebase 正在进行中
- 📝 README.md 冲突已解决(采用上游版本)
- 🔄 等待 rebase 完成后推送
---
## 📋 下一步行动计划
### 优先级 1: 完成 moltbot.svc.plus Git 同步
```bash
# 等待 rebase 完成
# 然后推送
git push origin main
```
### 优先级 2: 集成 SEO 到 page-reading-agent-backend
1. **修改 main.js 添加 SEO 端点**:
```javascript
if (req.url.startsWith('/seo-audit')) {
const audit = await auditSEO(page, currentTaskConfig.url);
const report = generateSEOReport(audit);
res.end(JSON.stringify(report));
}
```
2. **添加 MCP 工具支持**:
- 创建 MCP 工具定义
- 注册 SEO 审计功能
### 优先级 3: page-reading-agent-dashboard 前端
1. **创建 SEO 结果组件**:
```typescript
// components/SEOAuditPanel.tsx
- 显示总分
- 显示问题列表
- 显示详细指标
```
2. **添加导出功能**:
```typescript
// 导出为 JSON
// 导出为 Markdown
// 导出为 PDF可选
```
---
## 🎯 预期效果
### SEO 优化后的改进:
- 404 错误: 804 → <10
- 爬虫效率: +60%
- SEO 评分: +15-20 分
- 用户体验: 显著改善
- 搜索排名: 2-3 个月内逐步提升
### page-reading-agent 集成后的功能:
- ✅ 自动化 SEO 审计
- ✅ 实时问题检测
- ✅ 可视化报告
- ✅ 导出功能
- ✅ MCP 查询支持
---
## 📝 备注
1. **console.svc.plus** 的 SEO 优化已完成并提交
2. **page-reading-agent** 的 SEO 模块已创建,等待集成
3. **moltbot.svc.plus** 正在进行 Git rebase解决冲突中
---
**状态**: 🟡 进行中
**完成度**: 60%
**预计完成时间**: 1-2 小时

View File

@ -1,9 +0,0 @@
# Customization
## Purpose
- TODO: Add content specific to Customization.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Performance
## Purpose
- TODO: Add content specific to Performance.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Scalability
## Purpose
- TODO: Add content specific to Scalability.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Security
## Purpose
- TODO: Add content specific to Security.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Auth
## Purpose
- TODO: Add content specific to Auth.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Endpoints
## Purpose
- TODO: Add content specific to Endpoints.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Errors
## Purpose
- TODO: Add content specific to Errors.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Overview
## Purpose
- TODO: Add content specific to Overview.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Faq
## Purpose
- TODO: Add content specific to Faq.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Glossary
## Purpose
- TODO: Add content specific to Glossary.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# References
## Purpose
- TODO: Add content specific to References.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Components
## Purpose
- TODO: Add content specific to Components.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,63 +0,0 @@
# Documentation Page Redesign Specification
## 1. Overview
Refactor the `/docs` section of `console.svc.plus` to strictly load content from `cloud-neutral-workshop/knowledge/docs`. The UI will be redesigned to match the [Pigsty Documentation](https://pigsty.cc/docs/) layout while maintaining the existing `console.svc.plus` visual identity (Tailwind tokens, fonts, and colors).
## 2. Layout Structure
The layout will consist of a fixed header and a three-column content area (on desktop).
### 2.1. Global Header (Modified)
- **Content**: Logo, Main Nav (Home, Docs, Blog, etc.), Search Bar, GitHub Link, User Profile.
- **Additions**:
- **Version Selector**: Dropdown to switch between documentation versions (if available).
- **Search**: Integrated Algolia/Command-K search bar.
### 2.2. Left Sidebar (Navigation)
- **Behavior**: Sticky, independently scrollable.
- **Content**:
- Tree-view navigation structure matching the directory structure of `knowledge/docs`.
- **Expandable/Collapsible**: Categories should be collapsible folders.
- **Active State**: clear visual indicator for current page.
### 2.3. Main Content Area (Center)
- **Typography**: Optimized for long-form reading (prose, decent line-height).
- **Elements**:
- Breadcrumbs at the top.
- H1 Title.
- Last updated timestamp.
- Content rendered via MDX/Markdown.
- **Feedback Section (Footer)**: "Is this page helpful?" (Yes/No buttons) similar to Pigsty.
- Prev/Next page navigation links.
### 2.4. Right Sidebar (Table of Contents & Meta)
- **Behavior**: Sticky, hidden on mobile.
- **Content**:
- **On this Page**: Auto-generated TOC from H2/H3 headers.
- **Metadata**:
- "Module": Tags or categorization pills.
- "Edit this page": Link to GitHub source.
- "Contributors": List of contributors (optional).
## 3. Visual Style & Theming
- **Colors**: Use existing `brand-*` and `surface-*` tokens from `console.svc.plus`.
- Sidebar Background: `bg-surface-muted` or `bg-background` with right border.
- Active Link: `text-primary` with `bg-primary/10` background.
- **Responsiveness**:
- **Mobile**: Hambergur menu to open Sidebar. TOC hidden or moved to top of content (Accordion).
## 4. Implementation Plan
### 4.1. Data Source
- Ensure `scripts/sync-doc-content.sh` pulls specifically from `knowledge/docs`.
- Update `contentlayer` or `next-mdx-remote` configuration to handle the nested structure of `docs/`.
### 4.2. Components
1. **`DocsLayout`**: Wrapper for the 3-column grid.
2. **`SidebarTree`**: Recursive component for navigation.
3. **`TOC`**: Component to parse headings and display right sidebar.
4. **`FeedbackWidget`**: Simple interactivity for user sentiment.
## 5. Reference
- **Inspiration**: [Pigsty Docs](https://pigsty.cc/docs/)
- **Theme**: Cloud-Neutral Toolkit (Dark/Light mode support).

View File

@ -1,9 +0,0 @@
# Overview
## Purpose
- TODO: Add content specific to Overview.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Roadmap
## Purpose
- TODO: Add content specific to Roadmap.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,96 +0,0 @@
# console.svc.plus Web Console Architecture
## Scope
`console.svc.plus` is the browser-facing control plane. It is a Next.js App Router application that combines public pages, docs browsing, account/admin panels, and a BFF layer that forwards requests to downstream services. It never reads PostgreSQL or Prometheus directly for billing or usage.
## Architecture
```mermaid
flowchart TB
subgraph Pages["src/app pages"]
Root["/ -> landing page"]
Docs["/docs, /docs/[collection], /docs/[collection]/[...slug]\nDocs reader"]
Auth["/login, /register, /email-verification, /logout\nAuth flows"]
Panel["/panel/*\nUser / admin console"]
Tools["/editor/*, /download/*, /cloud_iac/*, /xworkmate/*\nProduct tools"]
Content["/blogs/*, /services/*, /support/*, /prices, /about, /privacy, /terms, /[slug]"]
Admin["/dashboard/cms\nCMS/admin entry"]
end
subgraph BFF["src/app/api route handlers"]
AuthAPI["/api/auth/*"]
AdminAPI["/api/admin/*"]
AgentAPI["/api/agent-server/[...segments]\n/api/agent/[...segments]"]
RagAPI["/api/rag/query\n/api/askai"]
UtilAPI["/api/users\n/api/ping\n/api/content-meta\n/api/render-markdown\n/api/dl-index/*\n/api/marketing/home-stats\n/api/integrations/*\n/api/moltbot/chat\n/api/openclaw/assistant\n/api/task/[...segments]\n/api/xworkmate/profile"]
GuestAPI["/api/guest/*"]
end
Accounts["accounts.svc.plus"]
Rag["rag-server.svc.plus"]
DocsSvc["docs.svc.plus"]
Grafana["observability.svc.plus / Grafana"]
External["Other upstream services"]
Subscription["/panel/subscription\nUsage / billing panel"]
AuthAPI --> Accounts
AdminAPI --> Accounts
AgentAPI --> Accounts
RagAPI --> Rag
UtilAPI --> DocsSvc
UtilAPI --> External
SandboxAPI --> Accounts
Subscription --> Accounts
Subscription -.-> Grafana
```
## Frontend Routes
| Route family | Path | Purpose |
| --- | --- | --- |
| Home | `/` | Public landing page and site entry |
| Docs | `/docs`, `/docs/[collection]`, `/docs/[collection]/[...slug]` | Documentation reader, sidebar, and TOC |
| Auth | `/login`, `/register`, `/email-verification`, `/logout` | Sign in / sign up / email verification / session cleanup |
| Panel | `/panel`, `/panel/account`, `/panel/agent`, `/panel/api`, `/panel/appearance`, `/panel/ldp`, `/panel/management`, `/panel/subscription`, `/panel/[...segments]` | Signed-in account and admin console |
| Tools | `/editor`, `/editor/wechat`, `/editor/xiaohongshu`, `/download`, `/download/[...segments]`, `/cloud_iac`, `/cloud_iac/[provider]`, `/cloud_iac/[provider]/[service]`, `/xworkmate`, `/xworkmate/admin`, `/xworkmate/integrations` | Product tools and service explorers |
| Content | `/blogs`, `/blogs/[...slug]`, `/services`, `/services/openclaw`, `/services/insight`, `/support`, `/support/discussions`, `/about`, `/prices`, `/privacy`, `/terms`, `/[slug]` | Marketing / informational pages |
| Admin / CMS | `/dashboard/cms` | CMS or content-management entry |
| Error pages | `/404`, `/500` | Static error surfaces |
## BFF / API Routes
| API family | Path | Purpose | Upstream target |
| --- | --- | --- | --- |
| Auth | `/api/auth/login`, `/api/auth/register`, `/api/auth/register/send`, `/api/auth/register/verify`, `/api/auth/verify-email`, `/api/auth/verify-email/send`, `/api/auth/session`, `/api/auth/token/exchange` | Login, registration, token exchange, session lookup | `accounts.svc.plus/api/auth/*` |
| MFA | `/api/auth/mfa/status`, `/api/auth/mfa/setup`, `/api/auth/mfa/verify`, `/api/auth/mfa/disable` | TOTP setup and verification | `accounts.svc.plus/api/auth/*` |
| OAuth / billing | `/api/auth/oauth/login/[provider]`, `/api/auth/stripe/checkout`, `/api/auth/stripe/portal`, `/api/auth/subscriptions`, `/api/auth/subscriptions/cancel` | OAuth redirects and billing actions | `accounts.svc.plus/api/auth/*` |
| Admin | `/api/admin/settings`, `/api/admin/homepage-video`, `/api/admin/users/*`, `/api/admin/blacklist/*` | Account admin operations | `accounts.svc.plus/api/*` |
| Agent bridge | `/api/agent-server/[...segments]`, `/api/agent/[...segments]` | Agent registry/status and legacy alias | `accounts.svc.plus` |
| RAG | `/api/rag/query`, `/api/askai` | Retrieval and answer generation | `rag-server.svc.plus` |
| Guest / demo runtime | `/api/guest/binding` | Guest read-only node resolution for demo access | `accounts.svc.plus/api/sandbox/binding` |
| Content / docs | `/api/content-meta`, `/api/render-markdown`, `/api/blogs/latest`, `/api/dl-index/*` | Docs/content rendering and download manifests | docs / CDN / download service |
| Integrations | `/api/integrations/defaults`, `/api/integrations/probe`, `/api/marketing/home-stats` | Integration defaults, health probes, marketing metrics | config-dependent external services |
| Misc | `/api/ping`, `/api/users`, `/api/xworkmate/profile`, `/api/task/[...segments]`, `/api/openclaw/assistant`, `/api/moltbot/chat`, `/api/render-markdown` | Health, user lookup, profile, task and assistant proxies | `accounts.svc.plus`, internal API, task services |
## Auth and Session Notes
- Browser calls use the session cookie and BFF logic in `src/server/account/session.ts`.
- Service-to-service calls use `INTERNAL_SERVICE_TOKEN` when configured.
- `api/agent-server/[...segments]` keeps caller `Authorization` untouched when an agent token is already present.
- `api/agent-server/[...segments]` forwards the dashboard session token for browser-driven calls.
## Dependencies
- `accounts.svc.plus` for identity, profile, sandbox, billing, and admin actions.
- `accounts.svc.plus` for authoritative usage and billing summaries sourced from PostgreSQL.
- `rag-server.svc.plus` for RAG query and AskAI.
- `docs.svc.plus` for docs content and navigation data.
- `observability.svc.plus` for Grafana dashboards and operational views only.
- CDN / external providers for content, analytics, and integration checks.
## Notes
- Route groups in parentheses, such as `(auth)`, are Next.js organizational folders and do not appear in the public URL.
- The BFF layer is the main place where console-specific auth shaping, cookie management, and upstream proxying happen.
- The subscription panel displays usage and billing data from accounts only and treats Grafana as an embedded observability surface, not a billing source.

View File

@ -1,9 +0,0 @@
# Code Structure
## Purpose
- TODO: Add content specific to Code Structure.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Contributing
## Purpose
- TODO: Add content specific to Contributing.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Testing
## Purpose
- TODO: Add content specific to Testing.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,34 +0,0 @@
# Console Service Plus Documentation
This repository primarily delivers a web frontend experience and should document product flows, UI boundaries, and integration touchpoints.
## Current state snapshot
- Root README title: `console.svc.plus`
- Build/runtime evidence: package.json (`dashboard`)
- Primary directories detected: `src/`, `scripts/`, `tests/`, `config/`, `public/`
- Existing docs count: 86
## Canonical pages
- [Architecture](architecture.md)
- [Design](design.md)
- [Deployment](deployment.md)
- [User Guide](user-guide.md)
- [Developer Guide](developer-guide.md)
- [Vibe Coding Reference](vibe-coding-reference.md)
## Legacy docs to fold in
- `SEO-AUDIT-REPORT.md`
- `SEO-WORK-SUMMARY.md`
- `advanced/customization.md`
- `advanced/performance.md`
- `advanced/scalability.md`
- `advanced/security.md`
- `api/auth.md`
- `api/endpoints.md`
- `api/errors.md`
- `api/overview.md`
- `appendix/faq.md`
- `appendix/glossary.md`

View File

@ -1,31 +0,0 @@
# Architecture
This repository primarily delivers a web frontend experience and should document product flows, UI boundaries, and integration touchpoints.
Use this page as the canonical bilingual overview of system boundaries, major components, and repo ownership.
## Current code-aligned notes
- Documentation target: `console.svc.plus`
- Repo kind: `frontend`
- Manifest and build evidence: package.json (`dashboard`)
- Primary implementation and ops directories: `src/`, `scripts/`, `tests/`, `config/`, `public/`
- Package scripts snapshot: `dev`, `prebuild`, `build`, `build:static`, `start`, `lint`
## Existing docs to reconcile
- `api/overview.md`
- `architecture/components.md`
- `architecture/design-decisions.md`
- `architecture/overview.md`
- `architecture/roadmap.md`
- `development/code-structure.md`
- `zh/api/overview.md`
- `zh/architecture/components.md`
## What this page should cover next
- Describe the current implementation rather than an aspirational future-only design.
- Keep terminology aligned with the repository root README, manifests, and actual directories.
- Link deeper runbooks, specs, or subsystem notes from the legacy docs listed above.
- Keep diagrams and ownership notes synchronized with actual directories, services, and integration dependencies.

View File

@ -1,42 +0,0 @@
# Deployment
## Production Baseline
- Runtime: `Caddy + Docker Compose`
- Deploy host: `root@jp-xhttp-contabo.svc.plus`
- Public domains:
- `www.svc.plus`
- `console.svc.plus`
- Canonical public origin: `https://www.svc.plus`
- Frontend release workflow: `.github/workflows/pipeline.yaml`
## Operating Model
The frontend is built in GitHub Actions and shipped as a prebuilt `linux/amd64` image. The host only pulls the image and starts containers; it does not build locally.
`yarn prebuild` now generates only console-owned marketing artifacts. `/docs` and `/blogs` no longer bundle `knowledge/` or synced markdown content into the frontend image. Those routes fetch rendered content from `docs.svc.plus` at request time through the server-side `docsServiceClient`.
The stack is static-first:
- Caddy serves `/_next/static/*` and public assets from a shared read-only volume.
- The Next.js standalone container serves dynamic HTML, auth endpoints, and API proxy routes. Static assets and hashed CSS/JS files are extracted by the `frontend-assets` helper task, so the runtime no longer needs to compile anything on the single-node host.
- `docs.svc.plus` is the source of truth for rendered docs/blog pages; the browser does not call it directly.
Releases are orchestrated through `.github/workflows/pipeline.yaml`. That workflow builds/pushes the image, renders `.env.runtime` including `DOCS_SERVICE_URL` / `DOCS_SERVICE_INTERNAL_URL`, and ships `docker-compose.yml`, `Caddyfile`, and the runtime env file to the host. The control-plane DNS automation then updates Cloudflare DNS for the release domains (via `scripts/github-actions/update-release-dns.sh`) so both `www.svc.plus` and `console.svc.plus` resolve to the same environment.
The release contract now uses:
- `CANONICAL_DOMAIN=www.svc.plus`
- `SERVED_DOMAINS=www.svc.plus,console.svc.plus`
Validation must pass for both domains. A release is incomplete if either host serves a different runtime version, static asset family, or `dashboardUrl`.
This baseline is intentional for the weak-IO single-node host `root@jp-xhttp-contabo.svc.plus`. No images are built on the target machine, keeping the deployment lightweight: the host only logs into GHCR, pulls the `dashboard` image, extracts assets into `frontend_static`, and starts `dashboard` plus `caddy` containers via `docker compose`.
`docs.svc.plus` is now the dedicated docs/blog service for the frontend delivery path.
## Related Docs
- `usage/deployment.md`
- `governance/release-process.md`
- `development/dev-setup.md`

View File

@ -1,27 +0,0 @@
# Design
This repository primarily delivers a web frontend experience and should document product flows, UI boundaries, and integration touchpoints.
Use this page to consolidate design decisions, ADR-style tradeoffs, and roadmap-sensitive implementation notes.
## Current code-aligned notes
- Documentation target: `console.svc.plus`
- Repo kind: `frontend`
- Manifest and build evidence: package.json (`dashboard`)
- Primary implementation and ops directories: `src/`, `scripts/`, `tests/`, `config/`, `public/`
- Package scripts snapshot: `dev`, `prebuild`, `build`, `build:static`, `start`, `lint`
## Existing docs to reconcile
- `SEO-WORK-SUMMARY.md`
- `architecture/design-decisions.md`
- `zh/SEO-WORK-SUMMARY.md`
- `zh/architecture/design-decisions.md`
## What this page should cover next
- Describe the current implementation rather than an aspirational future-only design.
- Keep terminology aligned with the repository root README, manifests, and actual directories.
- Link deeper runbooks, specs, or subsystem notes from the legacy docs listed above.
- Promote one-off implementation notes into reusable design records when behavior, APIs, or deployment contracts change.

View File

@ -1,31 +0,0 @@
# Developer Guide
This repository primarily delivers a web frontend experience and should document product flows, UI boundaries, and integration touchpoints.
Use this page to document local setup, project structure, test surfaces, and contribution conventions tied to the current codebase.
## Current code-aligned notes
- Documentation target: `console.svc.plus`
- Repo kind: `frontend`
- Manifest and build evidence: package.json (`dashboard`)
- Primary implementation and ops directories: `src/`, `scripts/`, `tests/`, `config/`, `public/`
- Package scripts snapshot: `dev`, `prebuild`, `build`, `build:static`, `start`, `lint`
## Existing docs to reconcile
- `api/auth.md`
- `api/endpoints.md`
- `api/errors.md`
- `api/overview.md`
- `development/code-structure.md`
- `development/contributing.md`
- `development/dev-setup.md`
- `development/testing.md`
## What this page should cover next
- Describe the current implementation rather than an aspirational future-only design.
- Keep terminology aligned with the repository root README, manifests, and actual directories.
- Link deeper runbooks, specs, or subsystem notes from the legacy docs listed above.
- Keep setup and test commands tied to actual package scripts, Make targets, or language toolchains in this repository.

View File

@ -1,31 +0,0 @@
# User Guide
This repository primarily delivers a web frontend experience and should document product flows, UI boundaries, and integration touchpoints.
Use this page to document primary user/operator tasks, everyday workflows, and navigation to existing how-to material.
## Current code-aligned notes
- Documentation target: `console.svc.plus`
- Repo kind: `frontend`
- Manifest and build evidence: package.json (`dashboard`)
- Primary implementation and ops directories: `src/`, `scripts/`, `tests/`, `config/`, `public/`
- Package scripts snapshot: `dev`, `prebuild`, `build`, `build:static`, `start`, `lint`
## Existing docs to reconcile
- `api/overview.md`
- `architecture/overview.md`
- `getting-started/concepts.md`
- `getting-started/installation.md`
- `getting-started/introduction.md`
- `getting-started/quickstart.md`
- `usage/cli.md`
- `usage/config.md`
## What this page should cover next
- Describe the current implementation rather than an aspirational future-only design.
- Keep terminology aligned with the repository root README, manifests, and actual directories.
- Link deeper runbooks, specs, or subsystem notes from the legacy docs listed above.
- Prefer workflow-oriented examples and keep screenshots or terminal snippets aligned with the latest UI or CLI behavior.

View File

@ -1,31 +0,0 @@
# Vibe Coding Reference
This repository primarily delivers a web frontend experience and should document product flows, UI boundaries, and integration touchpoints.
Use this page to align AI-assisted coding prompts, repo boundaries, safe edit rules, and documentation update expectations.
## Current code-aligned notes
- Documentation target: `console.svc.plus`
- Repo kind: `frontend`
- Manifest and build evidence: package.json (`dashboard`)
- Primary implementation and ops directories: `src/`, `scripts/`, `tests/`, `config/`, `public/`
- Package scripts snapshot: `dev`, `prebuild`, `build`, `build:static`, `start`, `lint`
## Existing docs to reconcile
- `api/auth.md`
- `api/endpoints.md`
- `api/errors.md`
- `api/overview.md`
- `zh/api/auth.md`
- `zh/api/endpoints.md`
- `zh/api/errors.md`
- `zh/api/overview.md`
## What this page should cover next
- Describe the current implementation rather than an aspirational future-only design.
- Keep terminology aligned with the repository root README, manifests, and actual directories.
- Link deeper runbooks, specs, or subsystem notes from the legacy docs listed above.
- Review prompt templates and repo rules whenever the project adds new subsystems, protected areas, or mandatory verification steps.

View File

@ -1,9 +0,0 @@
# Concepts
## Purpose
- TODO: Add content specific to Concepts.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,45 +0,0 @@
# Installation
## Purpose
- Set up the local development environment for `console.svc.plus`.
- Define the assistant and integrations defaults without hardcoding gateway values into the UI.
## Environment setup
1. Copy the example file:
```bash
cp .env.example .env
```
2. Use `/Users/shenlan/workspaces/cloud-neutral-toolkit/openclaw-deploy-example/.env` as the reference source for deployment-aligned values when available.
## Assistant and integrations variables
These variables are read on the server side and used to prefill:
- the homepage AI assistant
- the sidebar assistant dialog
- the `/panel/api` integrations page
| Variable | Used by | Notes |
|---|---|---|
| `OPENCLAW_GATEWAY_REMOTE_URL` | OpenClaw assistant | Preferred remote WebSocket endpoint, for example `wss://openclaw.svc.plus:443` |
| `OPENCLAW_GATEWAY_TOKEN` | OpenClaw assistant | Gateway token used by the server-side assistant bridge |
| `VAULT_SERVER_URL` | Vault integration | Base Vault address for connectivity checks and defaults |
| `VAULT_NAMESPACE` | Vault integration | Optional namespace when Vault Enterprise namespaces are used |
| `VAULT_TOKEN` | Vault integration | Token used for Vault probe requests |
| `APISIX_AI_GATEWAY_URL` | APISIX AI Gateway integration | Base HTTP(S) endpoint for AI gateway probing |
| `AI_GATEWAY_ACCESS_TOKEN` | APISIX AI Gateway integration | Access token used for gateway probe requests |
## Behavior
- These values are not hardcoded into React components.
- UI forms can still be overridden per request or per session when needed.
- Empty values simply disable prefill; they do not break the page layout.
## Related documents
- `../README.md`
- `../usage/config.md`

View File

@ -1,9 +0,0 @@
# Introduction
## Purpose
- TODO: Add content specific to Introduction.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Quickstart
## Purpose
- TODO: Add content specific to Quickstart.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# License
## Purpose
- TODO: Add content specific to License.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,53 +0,0 @@
# Release Process
This page tracks release summaries for published versions of the public web console served under `www.svc.plus` and `console.svc.plus`.
## Current Release
### v0.2
Release tag: `v0.2`
Release branch: `release/v0.2`
Published commit: `0fab89e`
#### Highlights
- Introduced the new XWorkmate workspace with a denser assistant layout, cleaner shell, and improved entry flow.
- Added the OpenClaw assistant workspace and pairing bridge, including configurable origin override and more stable pairing fallback behavior.
- Unified navigation and AI entry points with a persistent assistant sidebar and refined panel routing.
- Added the latest blog shortcuts on the homepage and improved guest and registration messaging.
- Expanded docs with bilingual structure updates, stronger OIDC guidance, and setup/readme cleanup.
- Fixed build stability issues, including `next-mdx-remote` vulnerability-related build failures and Yarn dependency metadata alignment.
#### New Features
- Launched the XWorkmate workspace and polished its workspace entry and layout.
- Added OpenClaw assistant integration, pairing bridge support, integration probe API, and integration defaults handling.
- Added XScopeHub MCP visibility on the services page.
- Displayed the latest 7 blog article titles in homepage shortcuts.
#### Improvements
- Split observability into a tri-view workspace and refined panel assistant routing.
- Unified navigation structure and persistent AI sidebar behavior.
- Improved login and registration flows by using server-resolved account service URLs.
- Guest and demo access must not expose any backing account identity in public UI or session payloads.
- Added vault-backed token lookup for integrations.
#### Docs And Setup
- Added bilingual docs coverage and restructured the docs entry points.
- Rewrote the OIDC authentication guide with fuller setup instructions.
- Updated setup guidance and simplified README structure.
#### Build And Dependency Fixes
- Updated and aligned `next-mdx-remote` usage for secure builds.
- Removed conflicting npm lockfile state and aligned Yarn dependency metadata for reproducible builds.
## Notes
- GitHub Release: `https://github.com/x-evor/console.svc.plus/releases/tag/v0.2`
- Related docs: `docs/README.md`, `docs/en/README.md`, `docs/zh/README.md`
- Release validation must verify both `www.svc.plus` and `console.svc.plus` against the same `releaseImageRef`, `releaseImageTag`, and `releaseCommit`.
- `www.svc.plus` is the canonical public domain for metadata, sitemap, `dashboardUrl`, and shared links.

View File

@ -1,9 +0,0 @@
# Security Policy
## Purpose
- TODO: Add content specific to Security Policy.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Ai Providers
## Purpose
- TODO: Add content specific to Ai Providers.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Cloud
## Purpose
- TODO: Add content specific to Cloud.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,79 +0,0 @@
# Cloudflare Web Analytics 集成配置
本页说明首页统计接口 `/api/marketing/home-stats` 依赖的 3 个 Cloudflare 环境变量如何获取,以及应配置到哪里。
## 需要的环境变量
```bash
CLOUDFLARE_API_TOKEN=
CLOUDFLARE_ACCOUNT_ID=
CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=
```
## 变量获取方式
### 1) `CLOUDFLARE_API_TOKEN`
用途:服务端调用 Cloudflare GraphQL API 读取访问量。
获取路径:
1. 打开 Cloudflare 控制台,右上角头像 -> **My Profile**
2. 进入 **API Tokens**
3. 点击 **Create Token**
4. 建议创建仅只读 token至少包含**Account Analytics:Read**(作用域限定到目标 Account
5. 复制生成后的 token只显示一次
### 2) `CLOUDFLARE_ACCOUNT_ID`
用途GraphQL 查询时定位账号。
获取方式(任选其一):
- 在 Cloudflare 控制台 URL 中,账号路径段通常就是 account id。
- 在账号总览页面Overview侧边栏/页面信息中复制 Account ID。
### 3) `CLOUDFLARE_WEB_ANALYTICS_SITE_TAG`
用途:定位具体 Web Analytics 站点。
获取方式(任选其一):
- 你当前这类链接中可直接看到:
`.../web-analytics/overview?siteTag~in=<SITE_TAG>&excludeBots=Yes`
其中 `<SITE_TAG>` 就是变量值。
- 在 Cloudflare Web Analytics 的站点设置/安装脚本中,`siteTag`(或 beacon token即对应值。
## 配置写入位置
### 本地开发
写入当前前端仓库的 `.env.local`
```bash
CLOUDFLARE_API_TOKEN=...
CLOUDFLARE_ACCOUNT_ID=...
CLOUDFLARE_WEB_ANALYTICS_SITE_TAG=...
```
### 线上部署
把同名变量写入前端部署环境。
> 注意:这些变量属于服务端密钥,不要暴露到 `NEXT_PUBLIC_*`
## 联调验证
部署后访问:
```bash
curl -fsSL https://www.svc.plus/api/marketing/home-stats
```
期望返回中 `visits.daily/weekly/monthly` 为数字(非 `null`)。
如果是 `null`,优先检查:
1. token 权限是否包含 Analytics Read
2. Account ID 是否与 siteTag 属于同一账号
3. 环境变量是否已在当前运行实例生效(重启/重新部署后再测)

View File

@ -1,9 +0,0 @@
# Databases
## Purpose
- TODO: Add content specific to Databases.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,244 +0,0 @@
# OIDC Authentication Configuration Guide
This guide describes how to configure GitHub and Google OAuth login for the Cloud Neutral Toolkit, enabling any user to sign in with their own GitHub or Google account.
## Architecture Overview
```
┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Browser │ │ www.svc.plus │ │accounts.svc.plus │
│ (User) │ │ (Frontend) │ │ (Backend) │
└──────┬───────┘ └────────┬─────────┘ └────────┬─────────┘
│ 1. Click "Login │ │
│ with GitHub" │ │
│──────────────────────>│ │
│ │ │
│ 2. Redirect to │ │
│ accounts /api/ │ │
│ auth/oauth/login/ │ │
│ github │ │
<──────────────────────│ │
│ │ │
│ 3. accounts redirects to GitHub/Google │
│ with client_id & callback URL │
<────────────────────────────────────────────────│
│ │ │
│ 4. User authorizes │ │
│ on GitHub/Google │ │
│ │ │
│ 5. GitHub/Google redirects back to │
│ accounts /api/auth/oauth/callback/github │
│─────────────────────────────────────────────────>
│ │ │
│ 6. accounts exchanges code for token, │
│ creates/links user, redirects to console │
<────────────────────────────────────────────────│
│ │ │
```
## Prerequisites
- A GitHub account with access to **Settings > Developer Settings**
- A Google account with access to [Google Cloud Console](https://console.cloud.google.com/)
- Running `accounts.svc.plus` and the frontend served under `www.svc.plus` / `console.svc.plus`
---
## 1. GitHub OAuth App
### 1.1 Create OAuth App
1. Go to [GitHub Developer Settings > OAuth Apps](https://github.com/settings/developers)
2. Click **"OAuth Apps"** tab, then **"New OAuth App"**
3. Fill in the form:
| Field | Value |
|---|---|
| **Application name** | `Cloud Neutral Console` |
| **Homepage URL** | `https://www.svc.plus` |
| **Authorization callback URL** | `https://accounts.svc.plus/api/auth/oauth/callback/github` |
| **Enable Device Flow** | ☐ (unchecked) |
4. Click **"Register application"**
### 1.2 Generate Client Secret
1. On the app detail page, copy the **Client ID** (displayed at the top)
2. Click **"Generate a new client secret"**
3. **Immediately copy the Client Secret** — it will only be shown once
### 1.3 Record Credentials
```
GitHub Client ID: Ov23li...
GitHub Client Secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Callback URL: https://accounts.svc.plus/api/auth/oauth/callback/github
```
> ⚠️ **Security**: Never commit Client Secret to version control. Store it as an environment variable or in a secret manager.
### 1.4 GitHub OAuth Scopes
The OAuth App requests these scopes by default:
- `user:email` — Read the user's email addresses (used for account binding)
No additional GitHub permissions are required.
---
## 2. Google OAuth Client ID
### 2.1 Configure OAuth Consent Screen
> This step is required before creating credentials. If already configured, skip to 2.2.
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Select or create a project
3. Navigate to **APIs & Services > OAuth consent screen**
4. Choose **External** user type (allows any Google user to sign in)
5. Fill in the required fields:
| Field | Value |
|---|---|
| **App name** | `Cloud Neutral Console` |
| **User support email** | your email address |
| **Developer contact email** | your email address |
6. Add scopes: `email`, `profile`, `openid`
7. Click **Save and Continue** through the remaining steps
8. Under **Publishing status**, click **"Publish App"** to move out of testing mode
- In testing mode, only manually added test users can sign in
### 2.2 Create OAuth Client ID
1. Go to **APIs & Services > Credentials**
2. Click **"Create Credentials" > "OAuth client ID"**
3. Fill in the form:
| Field | Value |
|---|---|
| **Application type** | `Web application` |
| **Name** | `Cloud Neutral Console` |
| **Authorized JavaScript origins** | `https://www.svc.plus` |
| **Authorized redirect URIs** | `https://accounts.svc.plus/api/auth/oauth/callback/google` |
4. Click **"Create"**
5. Copy the **Client ID** and **Client Secret** from the popup
### 2.3 Record Credentials
```
Google Client ID: xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com
Google Client Secret: GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Callback URL: https://accounts.svc.plus/api/auth/oauth/callback/google
```
---
## 3. Backend Configuration (accounts.svc.plus)
Set the following environment variables for **accounts.svc.plus**:
```bash
# ── GitHub OAuth ──
GITHUB_CLIENT_ID=<your_github_client_id>
GITHUB_CLIENT_SECRET=<your_github_client_secret>
# ── Google OAuth ──
GOOGLE_CLIENT_ID=<your_google_client_id>
GOOGLE_CLIENT_SECRET=<your_google_client_secret>
# ── General OAuth ──
OAUTH_REDIRECT_URL=https://accounts.svc.plus/api/auth/oauth/callback
OAUTH_FRONTEND_URL=https://www.svc.plus
```
These variables are referenced in `config/account.yaml`:
```yaml
auth:
oauth:
redirectUrl: "${OAUTH_REDIRECT_URL}"
frontendUrl: "${OAUTH_FRONTEND_URL:-https://www.svc.plus}"
github:
clientId: "${GITHUB_CLIENT_ID}"
clientSecret: "${GITHUB_CLIENT_SECRET}"
google:
clientId: "${GOOGLE_CLIENT_ID}"
clientSecret: "${GOOGLE_CLIENT_SECRET}"
```
> **Note**: The backend automatically appends `/{provider}` to `OAUTH_REDIRECT_URL` (e.g. `.../callback/github`) if a provider-specific redirect URL is not set.
---
## 4. Frontend Configuration (`www.svc.plus` canonical, `console.svc.plus` secondary)
The frontend resolves the accounts service URL **server-side** via `getAccountServiceBaseUrl()`, which reads:
```bash
# Set in accounts.svc.plus deployment environment
ACCOUNT_SERVICE_URL=https://accounts.svc.plus
```
If not set, the function falls back to a runtime default. **No `NEXT_PUBLIC_*` env var is needed** — the OAuth login URLs are constructed server-side and passed to the client components as props.
### OAuth Login URLs (auto-generated)
| Provider | Login URL |
|---|---|
| GitHub | `{accountServiceBaseUrl}/api/auth/oauth/login/github` |
| Google | `{accountServiceBaseUrl}/api/auth/oauth/login/google` |
### OAuth Callback URLs (handled by accounts.svc.plus)
| Provider | Callback URL |
|---|---|
| GitHub | `https://accounts.svc.plus/api/auth/oauth/callback/github` |
| Google | `https://accounts.svc.plus/api/auth/oauth/callback/google` |
---
## 5. Troubleshooting
### `undefined/api/auth/oauth/login/github`
**Cause**: OAuth URLs were using a client-side env var (`NEXT_PUBLIC_ACCOUNTS_SVC_URL`) that was not set.
**Fix** (applied in commit `4ce4147`): OAuth URLs now use the server-resolved `accountServiceBaseUrl` prop.
### OAuth login redirects to wrong domain
Check that `OAUTH_FRONTEND_URL` in accounts.svc.plus matches the canonical public domain where users should be redirected after authentication. The current default is `https://www.svc.plus`.
### Google "Access blocked: This app's request is invalid"
Ensure the **Authorized redirect URI** in Google Cloud Console **exactly** matches:
```
https://accounts.svc.plus/api/auth/oauth/callback/google
```
Trailing slashes or mismatched protocols will cause this error.
### GitHub "The redirect_uri MUST match the registered callback URL"
Ensure the **Authorization callback URL** in GitHub Developer Settings **exactly** matches:
```
https://accounts.svc.plus/api/auth/oauth/callback/github
```
### Google OAuth in "Testing" mode — only test users can sign in
Go to **OAuth consent screen > Publishing status** and click **"Publish App"** to allow any Google user to sign in.
---
## 6. Quick Reference
| Item | Value |
|---|---|
| GitHub OAuth App Settings | https://github.com/settings/developers |
| Google Cloud Credentials | https://console.cloud.google.com/apis/credentials |
| GitHub Callback URL | `https://accounts.svc.plus/api/auth/oauth/callback/github` |
| Google Callback URL | `https://accounts.svc.plus/api/auth/oauth/callback/google` |
| Backend Config File | `accounts.svc.plus/config/account.yaml` |
| Frontend URL Resolution | `getAccountServiceBaseUrl()` in `src/server/serviceConfig.ts` |

View File

@ -1,51 +0,0 @@
# Stripe Billing Integration
This console now routes all purchase entry points through Stripe:
- `/prices`
- product detail pages
- `/panel/subscription`
The browser only needs public Stripe `price_id` values. Secret keys stay in `accounts.svc.plus`.
## Required Environment Variables
Set these in `console.svc.plus`:
```bash
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_PAYGO=price_xxx
NEXT_PUBLIC_STRIPE_PRICE_XSTREAM_SUBSCRIPTION=price_xxx
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_PAYGO=price_xxx
NEXT_PUBLIC_STRIPE_PRICE_XSCOPEHUB_SUBSCRIPTION=price_xxx
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_PAYGO=price_xxx
NEXT_PUBLIC_STRIPE_PRICE_XCLOUDFLOW_SUBSCRIPTION=price_xxx
```
If a value is missing, the related purchase button stays visible but reports that Stripe pricing is not configured.
## Local Integration Checklist
1. Configure all `NEXT_PUBLIC_STRIPE_PRICE_*` values with Stripe test-mode `price_...` ids.
2. Start `accounts.svc.plus` with Stripe server-side settings.
3. Start this console with `yarn dev`.
4. Sign in with a normal user account.
5. Open `/prices` or `/panel/subscription` and start checkout.
6. Complete a Stripe test payment.
7. Confirm the browser returns to `/panel/subscription?checkout=success...`.
8. Confirm the subscription record appears in the subscription panel.
9. Open "Manage Stripe billing" and confirm the customer portal opens.
## Expected Flow
1. The console calls `/api/auth/stripe/checkout`.
2. The BFF proxies the request to `accounts.svc.plus` using the current account session.
3. `accounts.svc.plus` creates the Stripe Checkout Session.
4. Stripe redirects back to the console.
5. Stripe webhooks update the account service subscription record.
6. The console reads the final state from `/api/auth/subscriptions`.
## Notes
- The console does not store Stripe secret keys.
- Sensitive payment methods such as crypto QR flows are intentionally removed from the purchase UI.
- Use Stripe test mode first; do not validate this flow against live prices until webhook delivery is confirmed.

View File

@ -1,9 +0,0 @@
# Backup
## Purpose
- TODO: Add content specific to Backup.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Logging
## Purpose
- TODO: Add content specific to Logging.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Monitoring
## Purpose
- TODO: Add content specific to Monitoring.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,9 +0,0 @@
# Runbooks
## Scope
- Operational runbooks for services used by `console.svc.plus`.
## Index
- `rag-server.md`

View File

@ -1,426 +0,0 @@
# Cloud-Neutral Toolkit - RAG Server Runbook
## 📚 文档概述
**文档类型**: 运维手册 (Runbook)
**服务名称**: RAG Server (rag-server-svc-plus)
**维护团队**: Cloud-Neutral Toolkit Team
**最后更新**: 2026-01-25
**版本**: 1.0
## 🎯 服务概述
### 服务信息
- **服务名称**: rag-server-svc-plus
- **部署平台**: Google Cloud Run
- **区域**: asia-northeast1
- **项目 ID**: xzerolab-480008
- **服务 URL**: https://rag-server-svc-plus-266500572462.asia-northeast1.run.app
- **代码仓库**: https://github.com/x-evor/rag-server.svc.plus
### 服务功能
RAG (Retrieval-Augmented Generation) 服务器提供以下功能:
1. **向量检索** (`/api/rag/query`) - 从知识库中检索相关文档
2. **AI 问答** (`/api/askai`) - 使用 LLM 生成答案
3. **文档索引** (`/api/rag/upsert`) - 向知识库添加文档
### 依赖服务
- **LLM Provider**: NVIDIA AI (integrate.api.nvidia.com)
- **向量数据库**: PostgreSQL with pgvector
- **认证服务**: accounts-svc-plus
- **前端服务**: console.svc.plus (Vercel)
## 🏗️ 架构说明
### 系统架构
```
用户 (https://www.svc.plus)
Console Frontend (Vercel)
Next.js API Routes (/api/askai, /api/rag/query)
RAG Server (Cloud Run)
├─→ NVIDIA AI API (LLM)
└─→ PostgreSQL (向量数据库)
```
### 配置文件结构
```
rag-server.svc.plus/
├── config/
│ └── rag-server.yaml # 主配置文件
├── api/
│ ├── askai.go # AI 问答端点
│ ├── rag.go # RAG 检索端点
│ └── register.go # 路由注册
├── cmd/
│ └── rag-server/
│ └── main.go # 主程序入口
├── Dockerfile # Docker 镜像构建
└── entrypoint.sh # 容器启动脚本
```
## 🚀 部署流程
### 标准部署流程
#### 1. 代码变更
```bash
# 克隆仓库
git clone https://github.com/x-evor/rag-server.svc.plus.git
cd rag-server.svc.plus
# 创建功能分支
git checkout -b feature/your-feature-name
# 进行代码修改
# ... 编辑文件 ...
# 提交变更
git add .
git commit -m "feat: your feature description"
git push -u origin feature/your-feature-name
```
#### 2. 创建 Pull Request
1. 访问 GitHub 仓库
2. 创建 PR: `feature/your-feature-name``main`
3. 等待 CI/CD 检查通过
4. 请求代码审查
5. 合并到 main 分支
#### 3. 部署到 Cloud Run
**方式 A: 使用 gcloud CLI (推荐用于紧急修复)**
```bash
cd /path/to/rag-server.svc.plus
# 部署到 Cloud Run
gcloud run deploy rag-server-svc-plus \
--source . \
--region asia-northeast1 \
--project xzerolab-480008 \
--platform managed \
--allow-unauthenticated \
--set-env-vars NVIDIA_API_KEY='your-api-key-here'
# 部署通常需要 3-5 分钟
```
**方式 B: 通过 Cloud Build (自动化)**
```bash
# 触发 Cloud Build
gcloud builds submit \
--config cloudbuild.yaml \
--project xzerolab-480008
```
### 环境变量配置
必需的环境变量:
```bash
NVIDIA_API_KEY=nvapi-xxx... # NVIDIA AI API 密钥
DATABASE_URL=postgres://... # PostgreSQL 连接字符串
POSTGRES_USER=postgres # 数据库用户名
POSTGRES_PASSWORD=xxx # 数据库密码
```
可选的环境变量:
```bash
PORT=8080 # 服务端口Cloud Run 自动设置)
LOG_LEVEL=info # 日志级别 (debug/info/warn/error)
CONFIG_PATH=/etc/rag-server/rag-server.yaml # 配置文件路径
```
## 🔍 监控和日志
### 查看 Cloud Run 日志
**使用 Google Cloud Console**:
```
https://console.cloud.google.com/logs/query;query=resource.type%20%3D%20%22cloud_run_revision%22%0Aresource.labels.service_name%20%3D%20%22rag-server-svc-plus%22
```
**使用 gcloud CLI**:
```bash
# 查看最近 50 条日志
gcloud logging read \
"resource.type=cloud_run_revision AND resource.labels.service_name=rag-server-svc-plus" \
--limit 50 \
--project xzerolab-480008 \
--format json
# 实时查看日志
gcloud logging tail \
"resource.type=cloud_run_revision AND resource.labels.service_name=rag-server-svc-plus" \
--project xzerolab-480008
```
### 关键指标监控
1. **请求成功率**: 应该 > 95%
2. **响应时间**: P95 < 5s, P99 < 10s
3. **错误率**: < 5%
4. **实例数量**: 根据负载自动扩缩容
### 告警设置
建议设置以下告警:
- 错误率 > 10% 持续 5 分钟
- P99 延迟 > 15s 持续 5 分钟
- 实例启动失败
- 配置文件加载失败
## 🐛 故障排查
### 常见问题和解决方案
#### 问题 1: `/api/askai` 返回 500 错误
**症状**:
```json
{
"error": "Post \"\": unsupported protocol scheme \"\"",
"config": {"timeout": 30, "retries": 3}
}
```
**原因**: ConfigPath 配置错误,无法读取 LLM endpoint
**解决方案**:
1. 检查 `api/askai.go` 中的 `ConfigPath` 变量
2. 确保指向正确的配置文件: `config/rag-server.yaml`
3. 验证配置文件中的 `models.generator.endpoint` 不为空
**修复代码**:
```go
// api/askai.go
var ConfigPath = filepath.Join("config", "rag-server.yaml")
```
#### 问题 2: 数据库连接失败
**症状**:
```
postgres connect error: connection refused
```
**排查步骤**:
1. 检查 `DATABASE_URL` 环境变量是否正确
2. 验证 PostgreSQL 服务是否运行
3. 检查网络连接和防火墙规则
4. 如果使用 Stunnel检查 TLS 隧道是否正常
**解决方案**:
```bash
# 测试数据库连接
psql "$DATABASE_URL"
# 检查 Stunnel 状态
ps aux | grep stunnel
netstat -an | grep 5432
```
#### 问题 3: NVIDIA API 调用失败
**症状**:
```
askai request failed: 401 Unauthorized
```
**排查步骤**:
1. 验证 `NVIDIA_API_KEY` 环境变量是否设置
2. 检查 API key 是否有效
3. 确认 API quota 未超限
**解决方案**:
```bash
# 测试 NVIDIA API
curl -X POST https://integrate.api.nvidia.com/v1/chat/completions \
-H "Authorization: Bearer $NVIDIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "minimaxai/minimax-m2", "messages": [{"role": "user", "content": "test"}]}'
```
#### 问题 4: 配置文件未找到
**症状**:
```
load config err: open config/rag-server.yaml: no such file or directory
```
**排查步骤**:
1. 检查 Dockerfile 是否正确复制配置文件
2. 验证 entrypoint.sh 是否正确设置配置路径
3. 检查容器内文件系统
**解决方案**:
```dockerfile
# Dockerfile
COPY config/rag-server.yaml /etc/rag-server/rag-server.yaml
```
```bash
# entrypoint.sh
CONFIG_FILE="${CONFIG_PATH:-/etc/rag-server/rag-server.yaml}"
```
### 调试命令
```bash
# 1. 检查服务状态
gcloud run services describe rag-server-svc-plus \
--region asia-northeast1 \
--project xzerolab-480008
# 2. 测试健康检查
curl https://rag-server-svc-plus-266500572462.asia-northeast1.run.app/health
# 3. 测试 RAG 查询
curl -X POST https://rag-server-svc-plus-266500572462.asia-northeast1.run.app/api/rag/query \
-H "Content-Type: application/json" \
-d '{"question": "test"}'
# 4. 测试 AI 问答
curl -X POST https://rag-server-svc-plus-266500572462.asia-northeast1.run.app/api/askai \
-H "Content-Type: application/json" \
-d '{"question": "Hello"}'
# 5. 查看最新部署版本
gcloud run revisions list \
--service rag-server-svc-plus \
--region asia-northeast1 \
--project xzerolab-480008
# 6. 回滚到上一个版本
gcloud run services update-traffic rag-server-svc-plus \
--to-revisions REVISION_NAME=100 \
--region asia-northeast1 \
--project xzerolab-480008
```
## 🔄 回滚流程
### 紧急回滚
如果新部署导致严重问题:
```bash
# 1. 列出所有版本
gcloud run revisions list \
--service rag-server-svc-plus \
--region asia-northeast1 \
--project xzerolab-480008
# 2. 回滚到上一个稳定版本
gcloud run services update-traffic rag-server-svc-plus \
--to-revisions PREVIOUS_REVISION=100 \
--region asia-northeast1 \
--project xzerolab-480008
# 3. 验证回滚成功
curl https://rag-server-svc-plus-266500572462.asia-northeast1.run.app/health
```
### Git 回滚
```bash
# 回滚到上一个提交
git revert HEAD
git push origin main
# 或者硬回滚(谨慎使用)
git reset --hard HEAD~1
git push -f origin main
```
## 📊 性能优化
### 配置优化建议
1. **调整超时时间**:
```yaml
# config/rag-server.yaml
api:
askai:
timeout: 100 # 秒
retries: 3
```
2. **数据库连接池**:
```yaml
vector_db:
max_connections: 20
idle_timeout: 300s
```
3. **Cloud Run 资源配置**:
```bash
gcloud run services update rag-server-svc-plus \
--memory 2Gi \
--cpu 2 \
--max-instances 10 \
--min-instances 1 \
--region asia-northeast1
```
## 📝 维护检查清单
### 每日检查
- [ ] 查看错误日志,确认无异常
- [ ] 检查服务响应时间
- [ ] 验证 API 端点可访问性
### 每周检查
- [ ] 审查性能指标
- [ ] 检查资源使用情况
- [ ] 更新依赖包(如有安全更新)
### 每月检查
- [ ] 审查和优化配置
- [ ] 清理旧的 Cloud Run 版本
- [ ] 更新文档
## 🔐 安全最佳实践
1. **API 密钥管理**:
- 使用 Google Secret Manager 存储敏感信息
- 定期轮换 API 密钥
- 不要在代码中硬编码密钥
2. **访问控制**:
- 限制 Cloud Run 服务账号权限
- 使用 IAM 角色管理访问
- 启用 Cloud Armor 防护
3. **数据安全**:
- 使用 TLS 加密传输
- 启用数据库加密
- 定期备份数据
## 📞 联系信息
### 团队联系方式
- **技术负责人**: [姓名]
- **运维团队**: [邮箱]
- **紧急联系**: [电话]
### 相关链接
- **GitHub**: https://github.com/x-evor/rag-server.svc.plus
- **Cloud Console**: https://console.cloud.google.com/run/detail/asia-northeast1/rag-server-svc-plus
- **监控面板**: [Monitoring Dashboard URL]
- **文档**: [Documentation URL]
## 📚 相关文档
- [API 文档](./API.md)
- [配置指南](./CONFIG.md)
- [开发指南](./DEVELOPMENT.md)
- [故障排查指南](./debug-report.md)
---
**文档维护**: 请在每次重大变更后更新此文档
**审核周期**: 每季度审核一次
**版本历史**: 见 Git 提交记录

View File

@ -1,9 +0,0 @@
# Troubleshooting
## Purpose
- TODO: Add content specific to Troubleshooting.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,281 +0,0 @@
# Console Frontend Single-Node Deployment Design
## Scope
- Repository: `console.svc.plus`
- Target host: `root@cn-console.svc.plus`
- Public domains:
- `cn-console.svc.plus`
- `cn-console.onwalk.net`
- Delivery mode: `GitHub Actions + GHCR + Caddy + Docker Compose`
This document defines the deployment baseline for the China-facing frontend node. The source of truth is this upstream repository. The control-plane repository may consume the repo through git submodule, but should not become the primary place where this deployment design lives.
## Objective
Provide an independent frontend deployment pipeline for `console.svc.plus` that fits the current host constraints:
- the host IO is weak
- the host must not build Docker images locally
- the frontend should run in a static-first mode where possible
- deployment logic should stay in checked-in scripts, not be embedded in GitHub Actions YAML
The result should support repeatable releases, quick rollback by image tag, and minimal work on the target machine.
## Constraints
### Host constraints
- `cn-console.svc.plus` is a single-node host
- deployment user is `root`
- local image build on the host is explicitly disallowed
- IO pressure should be minimized during release
### Application constraints
- `console.svc.plus` is not a purely static site
- auth routes, same-origin API proxy routes, and selected dynamic pages still require a running Next.js server
- some `NEXT_PUBLIC_*` variables are compiled into the frontend bundle at image build time
- `prebuild` pulls documentation and `knowledge` content, so CI must prepare those inputs before building the image
### Repository constraints
- workflow YAML should remain orchestration-only
- service-local operational notes should remain in this repo
- downstream control repos can reference this repo through submodule updates after upstream changes are pushed
## Recommended Topology
### 1. CI build on GitHub Actions
The workflow builds a single `linux/amd64` image in GitHub Actions and pushes it to GHCR.
Reasons:
- matches the target host architecture
- avoids multi-arch overhead for this single-node release path
- avoids local host build IO and CPU pressure
- keeps release artifacts immutable and rollback-friendly
### 2. Runtime on the host
Use `docker compose` with three services:
- `dashboard`: Next.js standalone runtime
- `frontend-assets`: one-shot container that copies static files from the image into a Docker volume
- `caddy`: TLS termination, redirect handling, static file serving, and reverse proxy
This keeps the host work limited to:
- image pull
- asset extraction from the image
- container restart
### 3. Static-first request flow
Caddy serves:
- `/_next/static/*`
- checked-in `public/` assets
Next.js serves:
- HTML responses
- `/api/*` routes
- auth/session flows
- dynamic pages that still depend on server runtime
This reduces repeat disk reads and network hops for the bulk of frontend traffic while preserving the dynamic behavior the app still needs.
## Build-Time vs Runtime Configuration
### Build-time config
These values must be available during Docker build because the frontend bundle reads them directly:
- `NEXT_PUBLIC_APP_BASE_URL`
- `NEXT_PUBLIC_SITE_URL`
- `NEXT_PUBLIC_LOGIN_URL`
- `NEXT_PUBLIC_DOCS_BASE_URL`
- `NEXT_PUBLIC_RUNTIME_ENVIRONMENT`
- `NEXT_PUBLIC_RUNTIME_REGION`
- `NEXT_PUBLIC_GISCUS_*`
- `NEXT_PUBLIC_PAYPAL_CLIENT_ID`
- `NEXT_PUBLIC_STRIPE_*`
These are injected in GitHub Actions as Docker build args.
### Runtime config
These values are rendered into `.env.runtime` and copied to the host:
- upstream service URLs such as `ACCOUNT_SERVICE_URL`
- tokens used only on the server side
- Cloudflare analytics credentials
- internal service token
- runtime hostname hints
This separation avoids rebuilding for purely server-side secret or endpoint changes when the public frontend bundle does not change.
## Knowledge and Docs Handling
Current decision:
- `knowledge/` is cloned during CI
- the cloned content is included in the image build context
- the built image contains the resulting content needed by the current frontend
Reason:
- `prebuild` depends on this material
- the host should not fetch or generate content during deployment
Temporary nature:
- today the frontend still carries docs-related payload
- later, when `docs.svc.plus` becomes an API/service, docs delivery should move out of the frontend image
- that future change should reduce image size and simplify the runtime responsibilities of `console.svc.plus`
## Domain Handling
Primary domain:
- `cn-console.svc.plus`
Secondary domain:
- `cn-console.onwalk.net`
Current routing decision:
- Caddy accepts both domains
- requests for `cn-console.onwalk.net` are redirected permanently to `cn-console.svc.plus`
Reason:
- avoid duplicate canonical origins
- keep cookie and login behavior centered on one primary host
- simplify SEO and observability interpretation
## Release Workflow
### Trigger
Independent workflow:
- `.github/workflows/service_release_frontend-deploy.yml`
### Steps
1. check out repository
2. clone `knowledge`
3. build and push `ghcr.io/<owner>/dashboard:<tag>`
4. render `.env.runtime`
5. upload compose/caddy/env files to the host
6. log in to GHCR on the host
7. pull the new image
8. run `frontend-assets`
9. start or refresh `dashboard` and `caddy`
10. verify both domains
### Why separate from the existing image workflow
The existing image workflow is broader and oriented toward generic image publishing. This single-node frontend workflow needs tighter control over:
- build-time public env injection
- production deployment sequencing
- SSH-based single-host rollout
- host-specific runtime file rendering
So the frontend release path should remain explicit and independent.
## Rollback Model
Rollback unit:
- image tag reference in `.env.runtime`
Rollback steps:
1. set `FRONTEND_IMAGE` to a previous known-good tag
2. rerun `frontend-assets`
3. restart `dashboard` and `caddy`
4. verify `cn-console.svc.plus`
This avoids rebuilding and keeps rollback cheap on the weak-IO host.
## Security and Secret Handling
Secrets must not be committed to the repo. The workflow should consume:
- `SINGLE_NODE_VPS_SSH_PRIVATE_KEY`
- service tokens
- vault tokens
- internal service token
- optional Cloudflare credentials
Public defaults and non-secret values belong in checked-in examples or GitHub repository/environment variables. Secret-only values stay in GitHub Secrets and are rendered into the host runtime env during deployment.
## Operational Risks
### Risk 1: build-time public env mismatch
If GitHub environment variables are incomplete, the image may build successfully but the frontend can render wrong links or lose third-party integration IDs.
Mitigation:
- keep `.env.example` aligned
- document required GitHub `vars`
- keep the build args list explicit
### Risk 2: image layout drift
If the Docker image no longer contains `/app/dashboard/static` or `/app/dashboard/public`, the `frontend-assets` step fails.
Mitigation:
- keep asset extraction paths documented
- update deploy scripts whenever Dockerfile output layout changes
### Risk 3: docs payload growth
Bundling docs and `knowledge` into the frontend image increases image size.
Mitigation:
- accept it temporarily
- revisit once `docs.svc.plus` is externalized
### Risk 4: single-node blast radius
The host handles both reverse proxy and app runtime. Misconfiguration affects the whole frontend surface.
Mitigation:
- keep compose simple
- keep Caddy config minimal
- use image-tag rollback
## Future Follow-Up
### Near term
- populate required GitHub `vars` and `secrets`
- run the workflow against `root@cn-console.svc.plus`
- validate DNS, TLS, static assets, login flow, and upstream API proxy behavior
### Later
- move docs delivery out of the frontend image after `docs.svc.plus` is service/API based
- consider splitting static assets to object storage or CDN if traffic grows
- evaluate whether the host should keep only Caddy plus one app container, or whether docs can be removed entirely from this runtime
## Source of Truth Rule
For this deployment design:
- upstream repo source of truth: `console.svc.plus`
- service-local design note location: `docs/plans/`
- control-plane repo role: consume via git submodule after upstream commit is pushed
Do not move the primary design ownership to the control-plane repository.

View File

@ -1,5 +0,0 @@
# Plans
This directory stores service-local design notes and implementation plans for `console.svc.plus`.
The source of truth stays in this upstream repository. Control-plane repositories may reference these documents through git submodule updates after upstream changes are pushed.

View File

@ -1,218 +0,0 @@
# UI 主题与风格重构方案
作为资深 Web UI 设计师和前端工程师,针对我们 SaaS 控制台当前的 Next.js + Tailwind CSS 架构,结合 WCAG 可访问性标准和跨端响应式需求,特制定此 UI 重构方案。方案旨在提升整体视觉一致性、深色模式的可访问性,并优化桌面与移动端的用户体验(特别是 iOS/Android 浏览器的触控和渲染差异)。
---
## 1. 审查现有界面
在审查当前的界面代码(如 `tailwind.config.js`, `src/app/globals.css`, `src/components/theme/``Navbar.tsx`)后,我发现了以下改进点:
* **色彩硬编码与对比度**:在部分文件(如 `designTokens.ts``Navbar.tsx` 的内联类名)中仍然存在类似 `#3467e9`, `bg-[#f6f7f9]` 的硬编码颜色,这破坏了主题切换的完整性。同时,深色模式下的次级文本(如 `text-muted` `#cbd5f5`)在深色背景(`#0f172a`)上的对比度可能无法满足 WCAG AA 级 4.5:1 的标准。
* **语义化不足**`Navbar.tsx` 中的菜单项过度使用了 `<div>` 和普通的 `<a>` 标签,缺少 `<nav>`, `<ul>`, `<li>` 结构,并且缺乏管理下拉/折叠状态的 `aria-expanded` 属性。
* **触控目标尺寸**:移动端的某些交互元素(如链接和图标按钮)没有保证至少 44px × 44px 的物理点击区域,这在 iOS/Android 设备上容易造成误触。
* **深色模式层级**:深色模式主要依赖背景色区分层级(如 `surface-muted`缺乏细微的边框Border或发光阴影Glow/Shadow来凸显浮动面板如 Dropdown 菜单)。
---
## 2. 定义设计系统 Token
我们需要收敛硬编码颜色,改用语义化的 CSS 变量Token并对深/浅色模式设定严格的对比度要求。
### 颜色 Token 规划
* **背景色**
* 浅色:纯白 `#ffffff` (Surface) 或极浅灰 `#f8fafc` (Background)。
* 深色:避免纯黑,使用 `#0f172a` (Background) 和微亮的暗灰 `#1e293b` (Surface),减轻视觉疲劳。
* **文本色**
* 主要文本 (`--color-text`):浅色模式 `#0f172a`,深色模式 `#f8fafc`
* 次要文本 (`--color-text-muted`):确保在深浅背景下对比度均大于 4.5:1。浅色推荐 `#475569`,深色推荐 `#94a3b8`
* **主色与交互色**
* `--color-primary`:统一使用高对比度的主题蓝(如 `#2563eb`),确保按钮上的白色文字(`--color-primary-foreground`)对比度达标。
### Tailwind / CSS 变量伪代码
```css
/* src/app/globals.css 补充与覆盖 */
:root {
/* 基础结构色 */
--bg-color: #f8fafc;
--surface-color: #ffffff;
/* 文本色 */
--text-color: #0f172a;
--secondary-color: #475569;
/* 主色调 */
--primary: #2563eb;
--primary-foreground: #ffffff;
/* 边框与分隔线 */
--border-color: #e2e8f0;
}
:root[data-theme="dark"],
.dark {
--bg-color: #0f172a;
--surface-color: #1e293b;
--text-color: #f8fafc;
--secondary-color: #94a3b8; /* 保证对比度 > 4.5:1 */
--primary: #3b82f6; /* 在深色背景下稍微提亮主色 */
--primary-foreground: #ffffff;
--border-color: #334155;
/* 深色模式下的特殊视觉补偿 */
--shadow-elevation: 0 4px 6px -1px rgba(0, 0, 0, 0.5), 0 2px 4px -2px rgba(0, 0, 0, 0.5);
}
```
---
## 3. 设计排版体系
排版需兼顾多语言(中英文混合)和多端阅读体验。
* **字体选择**:保留现有的 `Geist`,同时后备配置 `Inter``Noto Sans SC` 以优化中文显示:
`font-family: var(--font-geist-sans), 'Inter', 'Noto Sans SC', sans-serif;`
* **字号范围 (Fluid Typography / 断点响应)**
* **移动端**:基础字号 16px防止 iOS Safari 输入框自动缩放),正文 `16px - 18px`,大标题约 `24px - 28px`
* **桌面端**:正文 `16px - 20px`,大标题可达 `32px - 48px`
* **行高与间距**:正文行高 `1.5``1.6`150%-160%),段落间距使用 `margin-bottom: 1.5em`。标题行高缩紧至 `1.2`
---
## 4. 构建全局样式与主题切换
当前系统已通过 Zustand (`store.ts`) 和 `ThemeProvider.tsx` 实现了基于 `localStorage``data-theme` 的切换。
* **优化防闪烁 (FOUC)**:由于在 Next.js 中客户端 Hydration 会有延迟,需要确保在 `<head>` 中注入一个同步脚本读取 `localStorage``prefers-color-scheme`,在 React 渲染前就应用 `dark` 类或 `data-theme`
* **深色模式下的层级分离**避免仅仅改变背景色。对于弹窗、Dropdown 等元素,在深色模式下增加一个极细的亮色边框:
```css
.dark .surface-elevated {
border: 1px solid rgba(255,255,255,0.1);
box-shadow: var(--shadow-elevation);
}
```
---
## 5. 响应式布局策略
* **断点定义**
* 移动端:`< 768px` (Tailwind 默认 `md` 以下)。
* 平板/桌面端:`>= 768px` (`md` 及以上)。
* **移动端 (iOS/Android) 策略**
* 隐藏复杂的侧边栏Sidebar顶部导航精简为 Logo 和汉堡菜单。
* 所有的按钮 (`button`, `a`) 必须拥有至少 `min-h-[44px] min-w-[44px]` 的触控区域(可以使用 padding 撑开)。
* **桌面端策略**
* 最大化利用横向空间,采用 Sidebar + Main Content 或全宽 Header 的多列布局。
---
## 6. 重构导航菜单 (示例)
当前 `Navbar.tsx` 使用了平铺的链接。我们需要使用语义化标签,并通过 `aria-expanded` 增强可访问性,并分离移动端的汉堡菜单和桌面端菜单。
### 菜单组件伪代码
```tsx
'use client'
import { useState } from 'react';
export default function SemanticNavbar() {
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
return (
<header className="sticky top-0 z-50 w-full bg-surface border-b border-border">
<nav className="max-w-7xl mx-auto px-4 min-h-[64px] flex items-center justify-between" aria-label="Main Navigation">
{/* Logo */}
<div className="flex-shrink-0">
<a href="/" className="font-bold text-text-color text-lg flex items-center min-h-[44px]">Logo</a>
</div>
{/* Desktop Menu */}
<ul className="hidden md:flex items-center gap-6">
<li>
<a href="/dashboard" className="text-secondary-color hover:text-text-color transition-colors py-2">
控制台
</a>
</li>
<li className="relative group">
{/* 父菜单如果是功能性展开按钮,使用 button */}
<button
aria-expanded={isDropdownOpen}
aria-controls="services-dropdown"
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
className="flex items-center gap-1 text-secondary-color hover:text-text-color py-2"
>
服务 <ChevronDownIcon />
</button>
{/* Dropdown 弹窗 */}
<ul
id="services-dropdown"
className={`absolute top-full left-0 mt-2 w-48 bg-surface-elevated border border-border rounded-md shadow-md ${isDropdownOpen ? 'block' : 'hidden'}`}
>
<li>
{/* 保证键盘 Tab 键能聚焦到这里 */}
<a href="/service/1" className="block px-4 py-3 text-text-color hover:bg-surface-hover">服务一</a>
</li>
</ul>
</li>
</ul>
{/* Mobile Hamburger Button */}
<div className="md:hidden">
<button
aria-controls="mobile-menu"
aria-expanded={isMobileMenuOpen}
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className="p-2 min-w-[44px] min-h-[44px] text-text-color"
aria-label="Toggle navigation"
>
<HamburgerIcon />
</button>
</div>
</nav>
{/* Mobile Menu Panel */}
<div
id="mobile-menu"
className={`md:hidden overflow-hidden transition-[max-height] duration-300 ease-in-out ${isMobileMenuOpen ? 'max-h-screen' : 'max-h-0'}`}
>
<ul className="px-4 pb-4 space-y-2 bg-surface">
<li>
<a href="/dashboard" className="block py-3 text-text-color">控制台</a>
</li>
{/* 移动端子菜单可以直接平铺或使用手风琴折叠 */}
<li>
<span className="block py-3 text-secondary-color font-semibold">服务</span>
<ul className="pl-4 space-y-2 border-l-2 border-border ml-2">
<li><a href="/service/1" className="block py-3 text-text-color">服务一</a></li>
</ul>
</li>
</ul>
</div>
</header>
);
}
```
---
## 7. 针对移动端和桌面端的优化差异
* **iOS/Android 浏览器差异处理**
* **iOS Safari 底部安全区**:在主内容区底部增加 `padding-bottom: env(safe-area-inset-bottom);`,避免按钮被 Home Indicator 遮挡。
* **字体抗锯齿 (Anti-aliasing)**:在 `globals.css` 中的 `body` 标签已设置 `-webkit-font-smoothing: antialiased;`,在深色模式下这能让细小的亮色文字边缘更加平滑,没有晕影。
* **长文本限制**:在博客或文档页面,容器设置 `max-w-prose` (相当于 `max-width: 65ch`),确保每行不超过 60 个字符,提升阅读体验。
* **桌面端强化**
* 可利用 Hover 状态提供丰富的反馈(如背景变色、细微的 `transform: translateY(-1px)`)。
* 在宽屏上显示侧边栏和次要信息列(多列网格布局)。
---
## 8. 可访问性测试与迭代流程
为确保重构后的界面符合标准,开发团队应在提交代码前进行以下检查:
1. **对比度扫描**:使用 Chrome DevTools 的 Lighthouse 或 WebAIM 对比度检查器,确保所有的正文/背景对比度 >= 4.5:1大号文本 >= 3.0:1。
2. **重排与缩放测试**:使用浏览器的缩放功能放大页面到 `200%``400%`。确保在 `400%` 下页面自动转为单列移动端布局,且文字不出现截断或相互重叠(开启 CSS 文本重排 `text-wrap: balance` 或避免固定高度)。
3. **键盘导航测试**:不使用鼠标,仅用 `Tab``Enter` 遍历界面。确保所有交互元素有明显的 `:focus-visible` 外框线Tailwind `focus-visible:ring-2 focus-visible:ring-primary`)。二级菜单不能在 Tab 聚焦到父元素时自动弹出(除非是用按钮触发),以避免用户被迫穿过无数个子菜单才能到达下一个主栏目。
4. **屏幕阅读器体验**:开启 VoiceOver (Mac/iOS) 或 TalkBack (Android),验证 `aria-expanded`, `aria-controls` 等状态是否能被正确播报。

View File

@ -1,9 +0,0 @@
# Cli
## Purpose
- TODO: Add content specific to Cli.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,154 +0,0 @@
# Deployment Runbook
## Scope
- Runtime: `console.svc.plus`
- Topology: `Caddy + Docker Compose + GitHub Actions`
- Deploy host: `root@jp-xhttp-contabo.svc.plus`
- Public domains:
- `https://www.svc.plus`
- `https://console.svc.plus`
- Canonical public origin: `https://www.svc.plus`
## Current Delivery Model
The production frontend is deployed as a prebuilt container image from GitHub Actions.
- The target host does not build images locally.
- The workflow builds an `linux/amd64` image and pushes it to `ghcr.io/<owner>/dashboard:<sha>`.
- The host only performs `docker login`, `docker compose pull`, static asset extraction, and `docker compose up`.
- `/docs` and `/blogs` fetch their content from `docs.svc.plus` at runtime; the frontend image no longer packs `knowledge/` or synced markdown payloads.
- Static assets are extracted from the image into a shared Docker volume so Caddy can serve `/_next/static/*` and checked-in public files directly.
This is intentionally static-first for the current weak-IO single-node host. Dynamic HTML, auth routes, and API proxy routes still run through the Next.js container, but docs/blog content delivery is now delegated to `docs.svc.plus`.
## Control Plane & DNS Stage
The control repo (`github-org-x-evor`) tracks `console.svc.plus` through `console.svc.plus.code-workspace` and keeps the `subrepos/accounts.svc.plus` pointer in sync via `skills/cross-repo-upstream-submodule-sync`. Releases resolve metadata with that workspace and the `config/single-node-release` manifests. After `.github/workflows/pipeline.yaml` finishes pushing the new image, the control-plane DNS automation calls `scripts/github-actions/update-release-dns.sh` to update Cloudflare DNS so the new endpoint is reachable through the current production host `jp-xhttp-contabo.svc.plus`.
## Runtime Layout
Remote directory:
```bash
/opt/console-svc-plus
```
Files deployed there:
```bash
docker-compose.yml
Caddyfile
.env.runtime
```
Containers:
- `dashboard`: Next.js standalone runtime on port `3000`
- `frontend-assets`: one-shot task that copies `static/` and `public/` into a shared volume
- `caddy`: TLS termination and reverse proxy
## GitHub Actions Inputs
Workflow:
```text
.github/workflows/pipeline.yaml
```
Secrets required:
- `SINGLE_NODE_VPS_SSH_PRIVATE_KEY`
- `OPENCLAW_GATEWAY_TOKEN` if used
- `VAULT_TOKEN` if used
- `AI_GATEWAY_ACCESS_TOKEN` if used
- `INTERNAL_SERVICE_TOKEN` if used
- `CLOUDFLARE_DNS_API_TOKEN` for the Cloudflare DNS stage
- `CLOUDFLARE_API_TOKEN` if homepage Cloudflare analytics are enabled at runtime
Repository/environment variables recommended:
- `CANONICAL_DOMAIN`
- `SERVED_DOMAINS`
- `APP_BASE_URL`
- `NEXT_PUBLIC_APP_BASE_URL`
- `NEXT_PUBLIC_SITE_URL`
- `NEXT_PUBLIC_LOGIN_URL`
- `NEXT_PUBLIC_DOCS_BASE_URL`
- `ACCOUNT_SERVICE_URL`
- `NEXT_PUBLIC_ACCOUNT_SERVICE_URL`
- `SERVER_SERVICE_URL`
- `NEXT_PUBLIC_SERVER_SERVICE_URL`
- `RUNTIME_HOSTNAME`
- `DEPLOYMENT_HOSTNAME`
- `DOCS_SERVICE_URL`
- `DOCS_SERVICE_INTERNAL_URL`
- `NEXT_PUBLIC_RUNTIME_ENVIRONMENT`
- `NEXT_PUBLIC_RUNTIME_REGION`
- `NEXT_PUBLIC_GISCUS_*`
- `NEXT_PUBLIC_STRIPE_*`
- `NEXT_PUBLIC_PAYPAL_CLIENT_ID`
- `CLOUDFLARE_ZONE_TAG` if homepage Cloudflare analytics are enabled at runtime
- `CLOUDFLARE_DNS_ZONE_TAG` only for single-domain manual DNS override; the GitHub Actions DNS stage resolves zones from each domain automatically
## Release Flow
1. GitHub Actions checks out the repo.
2. Docker builds the frontend image with the public `NEXT_PUBLIC_*` values needed at build time.
3. The image is pushed to GHCR.
4. The workflow updates Cloudflare DNS for the release domain.
5. The workflow renders `.env.runtime`, including the canonical public origin and both served frontend domains.
6. The workflow uploads `docker-compose.yml`, `Caddyfile`, and `.env.runtime` to the host.
7. The host pulls the new image, refreshes the static asset volume, and starts `dashboard + caddy`.
8. The workflow verifies both `www.svc.plus` and `console.svc.plus`, and fails the release if either domain serves a different runtime version.
## Verification Commands
Local syntax checks:
```bash
cd /Users/shenlan/workspaces/cloud-neutral-toolkit/console.svc.plus
bash -n scripts/github-actions/render-frontend-runtime-env.sh
bash -n scripts/github-actions/deploy-frontend-single-node.sh
cp deploy/single-node/.env.runtime.example deploy/single-node/.env.runtime
docker compose -f deploy/single-node/docker-compose.yml --env-file deploy/single-node/.env.runtime config >/tmp/console-compose.rendered.yaml
rm -f deploy/single-node/.env.runtime
python3 - <<'PY'
from pathlib import Path
import yaml
yaml.safe_load(Path('.github/workflows/pipeline.yaml').read_text())
print('workflow yaml ok')
PY
```
Remote checks:
```bash
ssh root@jp-xhttp-contabo.svc.plus "cd /opt/console-svc-plus && docker compose --env-file .env.runtime ps"
ssh root@jp-xhttp-contabo.svc.plus "curl -fsSI -H 'Host: www.svc.plus' http://127.0.0.1/"
ssh root@jp-xhttp-contabo.svc.plus "curl -fsSI -H 'Host: console.svc.plus' http://127.0.0.1/"
curl -fsSIL https://www.svc.plus
curl -fsSIL https://console.svc.plus
```
## Failure Signatures
- `docker login ghcr.io` fails
The workflow token or package visibility is wrong.
- `frontend-assets` fails
The image layout changed and no longer contains `/app/dashboard/static` or `/app/dashboard/public`.
- `www.svc.plus` or `console.svc.plus` returns `502`
Caddy is up, but the `dashboard` container failed or is not reachable on port `3000`.
## Rollback
1. Re-run the workflow with a previous known-good image tag.
2. Or update `/opt/console-svc-plus/.env.runtime` and set `FRONTEND_IMAGE=ghcr.io/<owner>/dashboard:<previous-tag>`.
3. Restart the services:
```bash
ssh root@jp-xhttp-contabo.svc.plus "cd /opt/console-svc-plus && docker compose --env-file .env.runtime run --rm frontend-assets"
ssh root@jp-xhttp-contabo.svc.plus "cd /opt/console-svc-plus && docker compose --env-file .env.runtime up -d dashboard caddy"
```
4. Verify `https://www.svc.plus` and `https://console.svc.plus` again before closing the incident.

View File

@ -1,9 +0,0 @@
# Examples
## Purpose
- TODO: Add content specific to Examples.
## Notes
- TODO: Link to related documents in this section.

View File

@ -1,34 +0,0 @@
# 控制台服务 文档
该仓库以 Web 前端体验为主,文档需要覆盖产品流程、界面边界与集成触点。
## 当前状态快照
- 根 README 标题: `console.svc.plus`
- 构建与运行时证据: package.json (`dashboard`)
- 自动识别的主要目录: `src/`, `scripts/`, `tests/`, `config/`, `public/`
- 现有文档数量: 86
## 核心双语文档
- [架构](architecture.md)
- [设计](design.md)
- [部署](deployment.md)
- [使用手册](user-guide.md)
- [开发手册](developer-guide.md)
- [Vibe Coding 参考](vibe-coding-reference.md)
## 待归并的历史文档
- `SEO-AUDIT-REPORT.md`
- `SEO-WORK-SUMMARY.md`
- `advanced/customization.md`
- `advanced/performance.md`
- `advanced/scalability.md`
- `advanced/security.md`
- `api/auth.md`
- `api/endpoints.md`
- `api/errors.md`
- `api/overview.md`
- `appendix/faq.md`
- `appendix/glossary.md`

View File

@ -1,11 +0,0 @@
# SEO Audit Report - console.svc.plus (ZH)
> English: `../SEO-AUDIT-REPORT.md`
## 目的
- TODO: 补充中文内容。
## 备注
- TODO: 链接到本章节相关文档。

View File

@ -1,11 +0,0 @@
# SEO 优化工作总结 (ZH)
> English: `../SEO-WORK-SUMMARY.md`
## 目的
- TODO: 补充中文内容。
## 备注
- TODO: 链接到本章节相关文档。

Some files were not shown because too many files have changed in this diff Show More