UPDATE
This commit is contained in:
@@ -1,67 +1,164 @@
|
|||||||
# Nexuma
|
# Nexuma Documentation
|
||||||
|
|
||||||
Nexuma is a multi-node VPN and proxy management platform. It lets you deploy VPN servers, manage users and subscriptions, and control everything from a single web interface.
|
Nexuma is a multi-node VPN and proxy management platform. It provides a central web panel, a control API, and node agents that run on VPN/proxy servers.
|
||||||
|
|
||||||
## Features
|
This documentation repository is self-contained. The examples below use published container images and do not require access to the application source repositories.
|
||||||
|
|
||||||
- **Multi-node** — connect unlimited VPN servers, manage them from one place
|
## What Nexuma Provides
|
||||||
- **Subscriptions** — per-user links with traffic limits, expiry, and auto-renewal
|
|
||||||
- **Multiple protocols** — VLESS, VMess, Trojan, Shadowsocks, Hysteria2, MTProto
|
|
||||||
- **Balance system** — user balance with deposits, withdrawals, and auto-charge on renewal
|
|
||||||
- **Traffic accounting** — per-subscription usage analytics
|
|
||||||
- **Telegram integration** — OAuth login, user approval flow, admin notifications
|
|
||||||
- **Routing rules** — xray-native rule sets and load balancers per node
|
|
||||||
- **Config builder** — guided wizard for protocol and stream configuration
|
|
||||||
- **Double VPN** — proxy chain support via node outbounds
|
|
||||||
- **External subscriptions** — aggregate external VPN share links into user subscriptions
|
|
||||||
|
|
||||||
## Architecture
|
- Central management for users, tariffs, subscriptions, nodes, routing, and audit logs.
|
||||||
|
- Public subscription links for VPN clients.
|
||||||
|
- Per-user traffic limits, expiry, balance, and auto-renewal.
|
||||||
|
- Multi-node deployment with per-node protocol configuration.
|
||||||
|
- Supported subscription exports: `v2ray`, `clash`, and `singbox`.
|
||||||
|
- Supported protocols: VLESS, VMess, Trojan, Shadowsocks, HTTP, SOCKS, WireGuard, Hysteria, and MTProto.
|
||||||
|
- Routing rule sets, traffic blocking options, and node outbounds for proxy chains.
|
||||||
|
- External subscription sources that can be appended to user subscriptions.
|
||||||
|
- Telegram login, approval flow, and notifications when configured.
|
||||||
|
|
||||||
```
|
## Main Components
|
||||||
┌───────────────────────────────────┐
|
|
||||||
│ Panel │
|
```text
|
||||||
│ (Admin Web Interface) │
|
Users and admins
|
||||||
└────────────────┬──────────────────┘
|
|
|
||||||
│
|
v
|
||||||
┌────────────────▼──────────────────┐
|
Panel - web interface and public subscription page
|
||||||
│ Core │
|
|
|
||||||
│ (Control Plane API) │
|
v
|
||||||
│ PostgreSQL ◄────────► Redis │
|
Core - API, state, subscription generation, node coordination
|
||||||
└──────┬────────────────────────────┘
|
|
|
||||||
│
|
v
|
||||||
┌────┴──────────────┐
|
Nodes - VPN/proxy servers that apply configs and report usage
|
||||||
▼ ▼
|
|
||||||
Node 1 . . . Node N
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### 1. Deploy Core and Panel
|
### 1. Deploy Core and Panel
|
||||||
|
|
||||||
Download [`docker-compose.base.yml`](./docker-compose.base.yml), fill in your values, and run:
|
Create a `docker-compose.yml` from the base example:
|
||||||
|
|
||||||
```bash
|
```yaml
|
||||||
docker compose -f docker-compose.base.yml up -d
|
version: '3.9'
|
||||||
|
|
||||||
|
services:
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: nexuma-redis
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
networks:
|
||||||
|
- nexuma
|
||||||
|
|
||||||
|
core:
|
||||||
|
image: nexuma/core:latest
|
||||||
|
container_name: nexuma-core
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
PORT: 3000
|
||||||
|
DB_HOST: your-postgres-host
|
||||||
|
DB_PORT: 5432
|
||||||
|
DB_USER: postgres
|
||||||
|
DB_PASSWORD: change-me
|
||||||
|
DB_NAME: nexuma
|
||||||
|
REDIS_HOST: redis
|
||||||
|
REDIS_PORT: 6379
|
||||||
|
JWT_SECRET: replace-with-long-random-secret
|
||||||
|
JWT_REFRESH_SECRET: replace-with-another-long-random-secret
|
||||||
|
JWT_EXPIRES_IN: 15m
|
||||||
|
JWT_REFRESH_EXPIRES_IN: 7d
|
||||||
|
CORS_ORIGINS: https://panel.example.com
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
networks:
|
||||||
|
- nexuma
|
||||||
|
|
||||||
|
panel:
|
||||||
|
image: nexuma/panel:latest
|
||||||
|
container_name: nexuma-panel
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
NUXT_CORE_URL: http://core:3000
|
||||||
|
NUXT_PUBLIC_API_BASE: /api
|
||||||
|
ports:
|
||||||
|
- "3010:3000"
|
||||||
|
depends_on:
|
||||||
|
- core
|
||||||
|
networks:
|
||||||
|
- nexuma
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
redis_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
nexuma:
|
||||||
|
driver: bridge
|
||||||
```
|
```
|
||||||
|
|
||||||
Open `http://your-server:3010` — the first registered account becomes admin.
|
Run:
|
||||||
|
|
||||||
### 2. Deploy a Node
|
|
||||||
|
|
||||||
On each VPN server, download [`docker-compose.node.yml`](./docker-compose.node.yml).
|
|
||||||
|
|
||||||
1. In the admin panel, open **Nodes** and generate a registration code.
|
|
||||||
2. Set `CORE_URL` and `REGISTRATION_CODE` in the file.
|
|
||||||
3. Run:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose -f docker-compose.node.yml up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
The node registers with Core automatically on first start. After that, remove the `REGISTRATION_CODE` line.
|
Open `http://your-server:3010`.
|
||||||
|
|
||||||
## Documentation
|
### 2. Create the First Admin
|
||||||
|
|
||||||
- [Core](./core.md) — configuration and API reference
|
Register the first user in the panel. Promote or seed an admin according to your deployment policy before exposing the system publicly.
|
||||||
- [Node](./node.md) — setup and registration
|
|
||||||
- [Panel](./panel.md) — features overview
|
### 3. Deploy a Node
|
||||||
|
|
||||||
|
In the panel, open `Nodes` and generate a registration code. Then deploy a node on the VPN/proxy server:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3.9'
|
||||||
|
|
||||||
|
services:
|
||||||
|
node:
|
||||||
|
image: nexuma/node:latest
|
||||||
|
container_name: nexuma-node
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
PORT: 3001
|
||||||
|
CORE_URL: https://core.example.com
|
||||||
|
REGISTRATION_CODE: paste-one-time-code-here
|
||||||
|
HEARTBEAT_INTERVAL_SEC: 30
|
||||||
|
volumes:
|
||||||
|
- node_data:/var/lib/vpnnode
|
||||||
|
ports:
|
||||||
|
- "3001:3001"
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
node_data:
|
||||||
|
```
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
After the node registers successfully, remove `REGISTRATION_CODE` and redeploy. Keep the `node_data` volume; it contains the persistent node auth key and local service state.
|
||||||
|
|
||||||
|
## Documentation Index
|
||||||
|
|
||||||
|
- [Core](./core.md) - control API, environment, Docker, public/user/admin API, and node-private API overview.
|
||||||
|
- [Panel](./panel.md) - operator UI, self-service UI, routes, environment, Docker, and API behavior.
|
||||||
|
- [Node](./node.md) - node agent setup, registration, environment, Docker, local endpoints, and operations.
|
||||||
|
- [Core API routes](./core-routes.md) - detailed Core route table.
|
||||||
|
- [Core private node routes](./core-private-routes.md) - node-to-Core private API contract.
|
||||||
|
|
||||||
|
## Security Checklist
|
||||||
|
|
||||||
|
- Replace all example secrets before production.
|
||||||
|
- Use HTTPS for public panel and Core endpoints.
|
||||||
|
- Restrict Core API exposure with firewall or reverse-proxy rules.
|
||||||
|
- Restrict node agent ports to trusted networks.
|
||||||
|
- Remove one-time node registration codes after successful registration.
|
||||||
|
- Rotate node auth keys when a node is rebuilt or compromised.
|
||||||
|
- Keep database and Redis inaccessible from the public internet.
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
# Core Private Node Routes
|
||||||
|
|
||||||
|
These routes are consumed only by the node agent and require the `x-node-auth-key` header.
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
- `POST /node/register` is the only public route in this group.
|
||||||
|
- All other routes require a valid node auth key.
|
||||||
|
- The auth key is issued by Core during registration and persisted on the node side.
|
||||||
|
|
||||||
|
## Route table
|
||||||
|
|
||||||
|
| Method | Route | Input | Output / Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `POST` | `/node/register` | `RegisterNodeDto` | Returns `{ authKey, nodeId }` |
|
||||||
|
| `POST` | `/node/heartbeat` | `NodeHeartbeatDto` | Returns `{ ok: true, needSync }` |
|
||||||
|
| `POST` | `/node/usage` | `NodeUsageDto` | Returns `{ ok: true }` |
|
||||||
|
| `POST` | `/node/outbound-usage` | `NodeOutboundUsageDto` | Returns `{ ok: true }` |
|
||||||
|
| `POST` | `/node/session-events` | `SessionEventsDto` | Returns `{ ok: true }` |
|
||||||
|
| `GET` | `/node/config/latest` | none | Returns `NodeConfig` |
|
||||||
|
| `POST` | `/node/config/applied` | `NodeConfigAppliedDto` | Returns `{ ok: true }` |
|
||||||
|
| `POST` | `/node/error` | `NodeErrorDto` | Returns `{ ok: true }` |
|
||||||
|
|
||||||
|
## Request payloads
|
||||||
|
|
||||||
|
### `RegisterNodeDto`
|
||||||
|
|
||||||
|
- `code: string`
|
||||||
|
- `name: string`
|
||||||
|
- `host: string`
|
||||||
|
- `ip: string`
|
||||||
|
- `region: string`
|
||||||
|
|
||||||
|
### `NodeHeartbeatDto`
|
||||||
|
|
||||||
|
- `ip?: string`
|
||||||
|
- `revision?: number`
|
||||||
|
|
||||||
|
The node sends its current revision in heartbeats. Core uses it to decide whether a sync is needed.
|
||||||
|
|
||||||
|
### `NodeUsageDto`
|
||||||
|
|
||||||
|
- `entries: UsageEntryDto[]`
|
||||||
|
|
||||||
|
`UsageEntryDto` fields:
|
||||||
|
|
||||||
|
- `userId: number`
|
||||||
|
- `subscriptionId?: number | null`
|
||||||
|
- `nodeProtocolId: number`
|
||||||
|
- `bytesUp: number`
|
||||||
|
- `bytesDown: number`
|
||||||
|
- `recordedAt: string`
|
||||||
|
|
||||||
|
### `NodeOutboundUsageDto`
|
||||||
|
|
||||||
|
- `items: OutboundUsageItem[]`
|
||||||
|
|
||||||
|
`OutboundUsageItem` fields:
|
||||||
|
|
||||||
|
- `tag: string`
|
||||||
|
- `bytesUp: number`
|
||||||
|
- `bytesDown: number`
|
||||||
|
|
||||||
|
### `SessionEventsDto`
|
||||||
|
|
||||||
|
- `events: SessionEventEntryDto[]`
|
||||||
|
|
||||||
|
`SessionEventEntryDto` fields:
|
||||||
|
|
||||||
|
- `userId: number`
|
||||||
|
- `protocol: VpnProtocol`
|
||||||
|
- `eventType: SessionEventType`
|
||||||
|
- `occurredAt: string`
|
||||||
|
- `ipAddress?: string`
|
||||||
|
|
||||||
|
### `NodeConfigAppliedDto`
|
||||||
|
|
||||||
|
- `revision: number`
|
||||||
|
|
||||||
|
### `NodeErrorDto`
|
||||||
|
|
||||||
|
- `message: string`
|
||||||
|
- `code?: string`
|
||||||
|
- `details?: Record<string, unknown>`
|
||||||
|
|
||||||
|
## `NodeConfig`
|
||||||
|
|
||||||
|
The config returned by `GET /node/config/latest` has this structure:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
revision: number
|
||||||
|
clientMap: Record<string, { userId: number; subscriptionId: number | null }>
|
||||||
|
services: {
|
||||||
|
xray?: XrayServiceConfig
|
||||||
|
mtproto?: MtProtoServiceConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Important notes:
|
||||||
|
|
||||||
|
- `clientMap` maps subscription link UUIDs to the user and subscription used for traffic attribution.
|
||||||
|
- `services.xray` contains the Xray inbounds, routing, and outbounds for the node.
|
||||||
|
- `services.mtproto` contains MTProto inbounds for `telemt`.
|
||||||
|
- the node stores the current revision and client map locally after a successful apply
|
||||||
|
|
||||||
|
## Route semantics
|
||||||
|
|
||||||
|
- `POST /node/heartbeat` updates `lastSeenAt` and can request a sync when the node revision is behind.
|
||||||
|
- `POST /node/usage` stores per-minute usage rows and increments subscription traffic counters.
|
||||||
|
- `POST /node/outbound-usage` increments `node_outbounds.traffic_bytes_up/down`.
|
||||||
|
- `POST /node/session-events` stores online/offline events in `session_events`.
|
||||||
|
- `POST /node/config/applied` marks the node sync status as `synced`.
|
||||||
|
- `POST /node/error` marks the node as `error` and the sync status as `failed`.
|
||||||
|
|
||||||
+314
@@ -0,0 +1,314 @@
|
|||||||
|
# Core Routes
|
||||||
|
|
||||||
|
This file lists the current HTTP routes exposed by Core, grouped by access level.
|
||||||
|
|
||||||
|
## Common conventions
|
||||||
|
|
||||||
|
- Authenticated routes use JWT unless noted otherwise.
|
||||||
|
- Admin routes require `role = admin`.
|
||||||
|
- Node-private routes require the `x-node-auth-key` header and are documented separately in [`core-private-routes.md`](./core-private-routes.md).
|
||||||
|
- The API returns raw JSON objects and arrays, not a wrapper object.
|
||||||
|
|
||||||
|
## Public routes
|
||||||
|
|
||||||
|
| Method | Route | Input | Output / Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `GET` | `/health` | none | `{ status: 'ok', timestamp }` |
|
||||||
|
| `GET` | `/metrics` | none | Prometheus text format |
|
||||||
|
| `POST` | `/auth/register` | `RegisterDto` | Creates a user account |
|
||||||
|
| `POST` | `/auth/login` | `LoginDto` | Returns JWT tokens |
|
||||||
|
| `POST` | `/auth/refresh` | `RefreshTokenDto` | Returns new JWT tokens |
|
||||||
|
| `POST` | `/auth/telegram` | `TelegramAuthDto` | Telegram OAuth login |
|
||||||
|
| `GET` | `/auth/telegram/bot-id` | none | `{ botId }` |
|
||||||
|
| `GET` | `/sub/:uuid/info` | query string and request IP | Returns `SubscriptionInfo` JSON |
|
||||||
|
| `GET` | `/sub/:uuid` | `format=v2ray|clash|singbox` | Returns encoded subscription content |
|
||||||
|
|
||||||
|
### `/sub/:uuid` output
|
||||||
|
|
||||||
|
- `format=v2ray` - plain text, newline-separated URIs
|
||||||
|
- `format=clash` - YAML with `proxies`
|
||||||
|
- `format=singbox` - JSON with `outbounds`
|
||||||
|
- unknown or missing `format` falls back to `v2ray`
|
||||||
|
|
||||||
|
## Authenticated user routes
|
||||||
|
|
||||||
|
| Method | Route | Input | Output / Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `GET` | `/auth/me` | none | Current user without `passwordHash` |
|
||||||
|
| `POST` | `/auth/telegram/link` | `TelegramAuthDto` | Links Telegram to the current account |
|
||||||
|
| `DELETE` | `/auth/telegram/unlink` | none | Unlinks Telegram from the current account |
|
||||||
|
| `GET` | `/me` | none | Current user without `passwordHash` |
|
||||||
|
| `PATCH` | `/me/profile` | `UpdateProfileDto` | Updates name, email, or password |
|
||||||
|
| `GET` | `/me/stats` | none | `UserStats` |
|
||||||
|
| `GET` | `/me/traffic/daily` | none | Daily traffic series |
|
||||||
|
| `GET` | `/me/traffic/hourly-dist` | none | Hour-of-day distribution |
|
||||||
|
| `GET` | `/me/traffic` | `period=30d|7d|1d|1h` | Period traffic series |
|
||||||
|
| `GET` | `/me/balance` | none | `{ balance }` |
|
||||||
|
| `GET` | `/me/balance/transactions` | none | User balance transaction history |
|
||||||
|
| `GET` | `/me/subscriptions` | none | User subscriptions |
|
||||||
|
| `GET` | `/me/links` | none | All user subscription links |
|
||||||
|
| `GET` | `/me/configs` | none | Available node/protocol configs for the current user |
|
||||||
|
| `POST` | `/me/links` | `CreateUserLinkDto` | Creates a combined link or a per-subscription link |
|
||||||
|
| `POST` | `/me/subscriptions` | `CreateSubscriptionLinkDto` | Legacy per-subscription link creation |
|
||||||
|
| `POST` | `/me/subscriptions/:id/reset` | none | Resets the selected link UUID |
|
||||||
|
| `PATCH` | `/me/subscriptions/links/:id` | `UpdateSubscriptionLinkDto` | Renames or reconfigures a link |
|
||||||
|
| `DELETE` | `/me/subscriptions/links/:id` | none | Deletes a link |
|
||||||
|
| `PATCH` | `/me/subscriptions/:id/auto-renew` | `UpdateAutoRenewDto` | Toggles auto-renew for own subscription |
|
||||||
|
|
||||||
|
## Admin routes - users
|
||||||
|
|
||||||
|
| Method | Route | Input | Output / Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `GET` | `/admin/users` | `page`, `limit`, `search`, `status`, `isBlocked` | Paginated users |
|
||||||
|
| `GET` | `/admin/users/:id` | none | User detail |
|
||||||
|
| `GET` | `/admin/users/:id/stats` | none | User stats |
|
||||||
|
| `PATCH` | `/admin/users/:id` | `UpdateUserDto` | Updates email, password, role, block flag, name, comment |
|
||||||
|
| `PATCH` | `/admin/users/:id/status` | `UpdateUserStatusDto` | Approves, rejects, or restores a user |
|
||||||
|
| `DELETE` | `/admin/users/:id` | none | Deletes a user |
|
||||||
|
|
||||||
|
## Admin routes - balance
|
||||||
|
|
||||||
|
| Method | Route | Input | Output / Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `POST` | `/admin/users/:id/balance/transactions` | `CreateBalanceTransactionDto` | Creates a deposit, withdrawal, or refund |
|
||||||
|
| `GET` | `/admin/users/:id/balance` | none | `{ balance }` |
|
||||||
|
| `GET` | `/admin/users/:id/balance/transactions` | none | Full admin transaction history |
|
||||||
|
| `PATCH` | `/admin/balance/transactions/:id` | `UpdateBalanceTransactionDto` | Edits a balance transaction |
|
||||||
|
|
||||||
|
## Admin routes - tariffs
|
||||||
|
|
||||||
|
| Method | Route | Input | Output / Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `GET` | `/admin/tariffs` | none | Tariff list |
|
||||||
|
| `GET` | `/admin/tariffs/:id` | none | Tariff detail |
|
||||||
|
| `POST` | `/admin/tariffs` | `CreateTariffDto` | Creates a tariff |
|
||||||
|
| `PATCH` | `/admin/tariffs/:id` | `UpdateTariffDto` | Updates a tariff |
|
||||||
|
| `DELETE` | `/admin/tariffs/:id` | none | Deletes a tariff |
|
||||||
|
|
||||||
|
## Admin routes - subscriptions
|
||||||
|
|
||||||
|
| Method | Route | Input | Output / Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `GET` | `/admin/subscriptions` | `page`, `limit`, `status`, `userId`, `tariffId` | Paginated subscriptions |
|
||||||
|
| `GET` | `/admin/users/:id/subscriptions` | none | Subscriptions for a user |
|
||||||
|
| `GET` | `/admin/users/:id/links` | none | All links for a user |
|
||||||
|
| `POST` | `/admin/subscriptions` | `CreateSubscriptionDto` | Creates a subscription |
|
||||||
|
| `DELETE` | `/admin/subscriptions/:id` | none | Cancels a subscription |
|
||||||
|
| `PATCH` | `/admin/subscriptions/:id/auto-renew` | `UpdateAutoRenewDto` | Toggles auto-renew |
|
||||||
|
| `POST` | `/admin/users/:id/links` | `CreateUserLinkDto` | Creates a link for any user |
|
||||||
|
| `POST` | `/admin/subscriptions/:id/links` | `CreateSubscriptionLinkDto` | Creates a link for a specific subscription |
|
||||||
|
| `POST` | `/admin/subscriptions/links/:id/reset` | none | Resets a link UUID |
|
||||||
|
| `PATCH` | `/admin/subscriptions/links/:id` | `UpdateSubscriptionLinkDto` | Updates a link |
|
||||||
|
| `DELETE` | `/admin/subscriptions/links/:id` | none | Deletes a link |
|
||||||
|
|
||||||
|
## Admin routes - nodes
|
||||||
|
|
||||||
|
| Method | Route | Input | Output / Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `GET` | `/admin/nodes` | `page`, `limit` | Paginated nodes |
|
||||||
|
| `GET` | `/admin/nodes/status` | none | Computed node status list |
|
||||||
|
| `GET` | `/admin/nodes/outbound-options` | none | Node/protocol outbound options |
|
||||||
|
| `GET` | `/admin/nodes/:id` | none | Node detail |
|
||||||
|
| `POST` | `/admin/nodes` | `CreateNodeDto` | Creates a node |
|
||||||
|
| `PATCH` | `/admin/nodes/:id` | `UpdateNodeDto` | Updates a node |
|
||||||
|
| `DELETE` | `/admin/nodes/:id` | none | Deletes a node |
|
||||||
|
| `POST` | `/admin/nodes/:id/sync` | none | Forces a config sync |
|
||||||
|
| `POST` | `/admin/nodes/auth-code` | none | Generates a one-time node registration code |
|
||||||
|
| `POST` | `/admin/nodes/:id/rotate-key` | none | Rotates the node auth key |
|
||||||
|
| `GET` | `/admin/nodes/:id/protocols` | none | Node protocols |
|
||||||
|
| `POST` | `/admin/nodes/:id/protocols` | `CreateProtocolDto` | Creates a node protocol |
|
||||||
|
| `PUT` | `/admin/nodes/:id/protocols/:protocolId` | `UpdateProtocolDto` | Updates a node protocol |
|
||||||
|
| `DELETE` | `/admin/nodes/:id/protocols/:protocolId` | none | Deletes a node protocol |
|
||||||
|
| `GET` | `/admin/nodes/:id/accesses` | none | User access map for the node |
|
||||||
|
| `POST` | `/admin/nodes/:nodeId/access/:userId/:protocol` | none | Grants protocol access |
|
||||||
|
| `DELETE` | `/admin/nodes/:nodeId/access/:userId/:protocol` | none | Revokes protocol access |
|
||||||
|
| `POST` | `/admin/nodes/:id/install-xray` | `InstallXrayDto` | Installs an xray version and stores the result |
|
||||||
|
| `POST` | `/admin/nodes/:id/restart-xray` | none | Restarts xray |
|
||||||
|
| `POST` | `/admin/nodes/:id/update-geoip` | `UpdateGeoipDto` | Updates stored geoip/geosite URLs and node files |
|
||||||
|
| `POST` | `/admin/nodes/:id/crypto/x25519` | none | Generates x25519 keys |
|
||||||
|
| `POST` | `/admin/nodes/:id/crypto/mldsa65` | none | Generates ML-DSA-65 keys |
|
||||||
|
| `GET` | `/admin/nodes/xray/versions` | none | Cached xray release tags |
|
||||||
|
| `GET` | `/admin/nodes/mtproto/versions` | none | Cached telemt release tags |
|
||||||
|
| `PATCH` | `/admin/nodes/:id/routing-config` | `UpdateNodeRoutingConfigDto` | Updates per-node routing config |
|
||||||
|
| `POST` | `/admin/nodes/:id/routing-config/apply-template` | `ApplyRoutingTemplateDto` | Copies a routing template into the node config |
|
||||||
|
| `DELETE` | `/admin/nodes/:id/routing-config` | none | Clears per-node routing config |
|
||||||
|
| `PUT` | `/admin/nodes/:id/nginx/site` | multipart form-data | Uploads cert/key/html for a masking site |
|
||||||
|
| `GET` | `/admin/nodes/:id/nginx/site` | none | Returns nginx masking site status |
|
||||||
|
| `DELETE` | `/admin/nodes/:id/nginx/sites/:domain` | none | Deletes a masking site |
|
||||||
|
| `POST` | `/admin/nodes/:id/install-mtproto` | body `{ version?: string }` | Installs telemt and stores the version |
|
||||||
|
| `POST` | `/admin/nodes/:id/restart-mtproto` | none | Restarts telemt |
|
||||||
|
| `GET` | `/admin/nodes/:id/runtime-status` | none | Live runtime status from the node |
|
||||||
|
| `GET` | `/admin/nodes/:id/outbounds` | none | Node outbounds |
|
||||||
|
| `POST` | `/admin/nodes/:id/outbounds` | `CreateNodeOutboundDto` | Creates a node outbound |
|
||||||
|
| `PATCH` | `/admin/nodes/:id/outbounds/:outboundId` | partial `CreateNodeOutboundDto` | Updates a node outbound |
|
||||||
|
| `DELETE` | `/admin/nodes/:id/outbounds/:outboundId` | none | Deletes a node outbound |
|
||||||
|
|
||||||
|
## Admin routes - routing rule sets
|
||||||
|
|
||||||
|
| Method | Route | Input | Output / Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `GET` | `/admin/routing-rule-sets` | none | All routing rule sets |
|
||||||
|
| `POST` | `/admin/routing-rule-sets` | `CreateRoutingRuleSetDto` | Creates a routing rule set |
|
||||||
|
| `PATCH` | `/admin/routing-rule-sets/:id` | `CreateRoutingRuleSetDto` | Updates a routing rule set |
|
||||||
|
| `DELETE` | `/admin/routing-rule-sets/:id` | none | Deletes a routing rule set |
|
||||||
|
|
||||||
|
## Admin routes - external subscriptions
|
||||||
|
|
||||||
|
| Method | Route | Input | Output / Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `GET` | `/admin/external-subscriptions` | none | External subscription sources |
|
||||||
|
| `GET` | `/admin/external-subscriptions/:id` | none | External subscription source detail |
|
||||||
|
| `POST` | `/admin/external-subscriptions` | `CreateExternalSubscriptionDto` | Creates a source |
|
||||||
|
| `POST` | `/admin/external-subscriptions/test` | `TestExternalSubscriptionDto` | Tests a URL and returns source data |
|
||||||
|
| `PATCH` | `/admin/external-subscriptions/:id` | `UpdateExternalSubscriptionDto` | Updates a source |
|
||||||
|
| `DELETE` | `/admin/external-subscriptions/:id` | none | Deletes a source |
|
||||||
|
|
||||||
|
## Admin routes - logs and usage
|
||||||
|
|
||||||
|
| Method | Route | Input | Output / Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `GET` | `/admin/logs` | `page`, `limit`, `action`, `userId` | Paginated audit logs |
|
||||||
|
| `GET` | `/admin/usage` | `nodeId`, `userId`, `from`, `to` | Up to 1000 traffic usage rows |
|
||||||
|
|
||||||
|
## Admin routes - xray helpers
|
||||||
|
|
||||||
|
| Method | Route | Input | Output / Notes |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `POST` | `/admin/xray/parse-link` | `{ link }` | Parses a proxy link into outbound config |
|
||||||
|
|
||||||
|
## DTO summary
|
||||||
|
|
||||||
|
### Auth
|
||||||
|
|
||||||
|
- `LoginDto` - `email`, `password`
|
||||||
|
- `RegisterDto` - `email`, `password`
|
||||||
|
- `RefreshTokenDto` - `refreshToken`
|
||||||
|
- `TelegramAuthDto` - `tgAuthResult`
|
||||||
|
|
||||||
|
### Users
|
||||||
|
|
||||||
|
- `UpdateProfileDto` - `name?`, `email?`, `password?`
|
||||||
|
- `UpdateUserDto` - `email?`, `password?`, `role?`, `isBlocked?`, `name?`, `comment?`
|
||||||
|
- `UpdateUserStatusDto` - `status: 'active' | 'pending' | 'rejected'`
|
||||||
|
|
||||||
|
### Balance
|
||||||
|
|
||||||
|
- `CreateBalanceTransactionDto` - `amount`, `type`, `comment?`, `createdAt?`
|
||||||
|
- `UpdateBalanceTransactionDto` - `amount?`, `type?`, `comment?`
|
||||||
|
|
||||||
|
### Tariffs
|
||||||
|
|
||||||
|
- `CreateTariffDto`
|
||||||
|
- `name`
|
||||||
|
- `durationMonths: number | null`
|
||||||
|
- `price`
|
||||||
|
- `trafficLimitBytes?`
|
||||||
|
- `isActive?`
|
||||||
|
- `allowedProtocols: VpnProtocol[]`
|
||||||
|
- `allowedNodeIds?`
|
||||||
|
- `externalSubscriptionIds?`
|
||||||
|
- `allowBittorrent?`
|
||||||
|
- `allowAdult?`
|
||||||
|
- `UpdateTariffDto` - partial `CreateTariffDto`
|
||||||
|
|
||||||
|
### Subscriptions
|
||||||
|
|
||||||
|
- `CreateSubscriptionDto` - `userId`, `tariffId`, `startAt?`
|
||||||
|
- `CreateSubscriptionLinkDto` - `subscriptionId`, `name?`
|
||||||
|
- `CreateUserLinkDto` - `subscriptionId?`, `name?`, `selectedNodeIds?`
|
||||||
|
- `UpdateSubscriptionLinkDto` - `name?`, `isEnabled?`, `selectedNodeIds?`
|
||||||
|
- `UpdateAutoRenewDto` - `autoRenew`
|
||||||
|
|
||||||
|
### Nodes
|
||||||
|
|
||||||
|
- `CreateNodeDto`
|
||||||
|
- `name`
|
||||||
|
- `host`
|
||||||
|
- `port?`
|
||||||
|
- `useSsl?`
|
||||||
|
- `ip?`
|
||||||
|
- `region?`
|
||||||
|
- `metadata?`
|
||||||
|
- `comment?`
|
||||||
|
- `emoji?`
|
||||||
|
- `status?`
|
||||||
|
- `useIpInSubscription?`
|
||||||
|
- `UpdateNodeDto` - partial `CreateNodeDto` plus `geoipUrl?` and `geositeUrl?`
|
||||||
|
- `CreateProtocolDto` - `protocol`, `config?`, `label?`, `isEnabled?`
|
||||||
|
- `UpdateProtocolDto` - `config?`, `label?`, `isEnabled?`
|
||||||
|
- `CreateNodeOutboundDto`
|
||||||
|
- `name`
|
||||||
|
- `tag`
|
||||||
|
- `outboundType?` = `manual | node`
|
||||||
|
- `config?`
|
||||||
|
- `targetNodeId?`
|
||||||
|
- `targetProtocolId?`
|
||||||
|
- `CreateRoutingRuleSetDto`
|
||||||
|
- `name`
|
||||||
|
- `description?`
|
||||||
|
- `domainStrategy?`
|
||||||
|
- `rules?`
|
||||||
|
- `balancers?`
|
||||||
|
- `blockTorrents?`
|
||||||
|
- `blockAds?`
|
||||||
|
- `blockMalware?`
|
||||||
|
- `blockPhishing?`
|
||||||
|
- `blockCryptominers?`
|
||||||
|
- `blockAdult?`
|
||||||
|
- `UpdateNodeRoutingConfigDto` - same routing fields as above, without `name` and `description`
|
||||||
|
- `ApplyRoutingTemplateDto` - `templateId`
|
||||||
|
- `InstallXrayDto` - `version`
|
||||||
|
- `UpdateGeoipDto` - `geoipUrl?`, `geositeUrl?`
|
||||||
|
|
||||||
|
### External subscriptions
|
||||||
|
|
||||||
|
- `CreateExternalSubscriptionDto` - `name`, `url`, `description?`
|
||||||
|
- `UpdateExternalSubscriptionDto` - partial `CreateExternalSubscriptionDto`
|
||||||
|
- `TestExternalSubscriptionDto` - `url`
|
||||||
|
|
||||||
|
### Routing rules
|
||||||
|
|
||||||
|
`XrayRule` fields:
|
||||||
|
|
||||||
|
- `type` (`field`)
|
||||||
|
- `domainMatcher?`
|
||||||
|
- `domain?`
|
||||||
|
- `ip?`
|
||||||
|
- `port?`
|
||||||
|
- `sourcePort?`
|
||||||
|
- `localPort?`
|
||||||
|
- `network?`
|
||||||
|
- `source?`
|
||||||
|
- `sourceIP?`
|
||||||
|
- `localIP?`
|
||||||
|
- `user?`
|
||||||
|
- `inboundTag?`
|
||||||
|
- `protocol?`
|
||||||
|
- `attrs?`
|
||||||
|
- `process?`
|
||||||
|
- `vlessRoute?`
|
||||||
|
- `webhook?`
|
||||||
|
- `outboundTag?`
|
||||||
|
- `balancerTag?`
|
||||||
|
- `ruleTag?`
|
||||||
|
|
||||||
|
`XrayBalancer` fields:
|
||||||
|
|
||||||
|
- `tag`
|
||||||
|
- `selector`
|
||||||
|
- `strategy?`
|
||||||
|
- `fallbackTag?`
|
||||||
|
|
||||||
|
`XrayDomainStrategy` values:
|
||||||
|
|
||||||
|
- `AsIs`
|
||||||
|
- `IPIfNonMatch`
|
||||||
|
- `IPOnDemand`
|
||||||
|
|
||||||
|
`XrayBalancerStrategy` values:
|
||||||
|
|
||||||
|
- `random`
|
||||||
|
- `roundRobin`
|
||||||
|
- `leastPing`
|
||||||
|
- `leastLoad`
|
||||||
|
|
||||||
@@ -1,59 +1,212 @@
|
|||||||
# Core
|
# Core
|
||||||
|
|
||||||
REST API server — the control plane of Nexuma. Manages users, tariffs, subscriptions, nodes, traffic accounting, and generates subscription configs for VPN clients.
|
Core is the Nexuma control API. It stores platform state, authenticates users, coordinates nodes, builds node configuration revisions, records traffic, and serves public subscription exports.
|
||||||
|
|
||||||
## Configuration
|
This page is public operator documentation. It describes behavior, API, environment variables, and Docker deployment without requiring source-code access.
|
||||||
|
|
||||||
Edit the environment variables in [`docker-compose.base.yml`](./docker-compose.base.yml):
|
## Capabilities
|
||||||
|
|
||||||
| Variable | Required | Description |
|
- User registration, login, Telegram OAuth, account approval, profile updates, and role-based access.
|
||||||
|----------|----------|-------------|
|
- Tariff management with price, duration, traffic limit, protocol permissions, node permissions, and external subscription sources.
|
||||||
| `DB_HOST` | Yes | PostgreSQL host |
|
- Subscription lifecycle with activation, cancellation, expiry, auto-renewal, traffic accounting, and public links.
|
||||||
| `DB_PORT` | Yes | PostgreSQL port |
|
- Balance management with deposit, withdrawal, refund, history, and auto-renewal charging rules.
|
||||||
| `DB_USER` | Yes | Database user |
|
- Node registration through one-time codes.
|
||||||
| `DB_PASSWORD` | Yes | Database password |
|
- Node heartbeat, status, sync state, revision tracking, config delivery, and error reporting.
|
||||||
| `DB_NAME` | Yes | Database name |
|
- Per-node protocol configuration, routing configuration, masking sites, runtime operations, and outbounds.
|
||||||
| `REDIS_HOST` | Yes | Redis host |
|
- Routing rule sets with rules, balancers, and blocking options.
|
||||||
| `REDIS_PORT` | Yes | Redis port |
|
- External subscription source ingestion and testing.
|
||||||
| `JWT_SECRET` | Yes | Access token secret |
|
- Public subscription exports for VPN clients.
|
||||||
| `JWT_REFRESH_SECRET` | Yes | Refresh token secret |
|
- Audit logs, usage queries, metrics, and health checks.
|
||||||
| `JWT_EXPIRES_IN` | No | Access token TTL (default: `15m`) |
|
|
||||||
| `JWT_REFRESH_EXPIRES_IN` | No | Refresh token TTL (default: `7d`) |
|
|
||||||
| `TELEGRAM_BOT_TOKEN` | No | Enables Telegram bot and OAuth login |
|
|
||||||
| `CORS_ORIGINS` | No | Allowed CORS origins (default: `*`) |
|
|
||||||
|
|
||||||
## API Overview
|
## Environment Variables
|
||||||
|
|
||||||
### Public
|
| Variable | Required | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `PORT` | No | `3000` | HTTP port. |
|
||||||
|
| `NODE_ENV` | No | `development` | Runtime mode. |
|
||||||
|
| `DB_HOST` | Yes | `localhost` | Database host. |
|
||||||
|
| `DB_PORT` | No | `5432` | Database port. |
|
||||||
|
| `DB_USER` | Yes | `postgres` | Database user. |
|
||||||
|
| `DB_PASSWORD` | Yes | empty | Database password. |
|
||||||
|
| `DB_NAME` | Yes | `nexuma` | Database name. |
|
||||||
|
| `REDIS_HOST` | Yes | `localhost` | Redis host. |
|
||||||
|
| `REDIS_PORT` | No | `6379` | Redis port. |
|
||||||
|
| `REDIS_PASSWORD` | No | empty | Redis password. |
|
||||||
|
| `REDIS_DB` | No | `0` | Redis database index. |
|
||||||
|
| `JWT_SECRET` | Yes | insecure dev value | Access token secret. Use a long random value. |
|
||||||
|
| `JWT_EXPIRES_IN` | No | `15m` | Access token lifetime. |
|
||||||
|
| `JWT_REFRESH_SECRET` | Yes | insecure dev value | Refresh token secret. Use a different long random value. |
|
||||||
|
| `JWT_REFRESH_EXPIRES_IN` | No | `7d` | Refresh token lifetime. |
|
||||||
|
| `TELEGRAM_BOT_TOKEN` | No | empty | Enables Telegram login and notifications. |
|
||||||
|
| `CORS_ORIGINS` | No | `*` | Comma-separated allowed origins. |
|
||||||
|
|
||||||
- `POST /auth/register` — create account
|
## Docker Compose
|
||||||
- `POST /auth/login` — login, get JWT tokens
|
|
||||||
- `POST /auth/refresh` — refresh access token
|
|
||||||
- `GET /sub/:uuid` — subscription config for VPN clients (base64 URI list)
|
|
||||||
- `GET /sub/:uuid/info` — subscription info (JSON)
|
|
||||||
- `GET /health`, `GET /metrics`
|
|
||||||
|
|
||||||
### User
|
Minimal Core deployment with Redis and an external database:
|
||||||
|
|
||||||
- `GET /me` — profile and balance
|
```yaml
|
||||||
- `GET /me/subscriptions` — subscriptions and links
|
version: '3.9'
|
||||||
- `GET /me/balance/transactions` — transaction history
|
|
||||||
|
|
||||||
### Admin
|
services:
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: nexuma-redis
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
networks:
|
||||||
|
- nexuma
|
||||||
|
|
||||||
- **Users** — CRUD, status approval (pending / active / rejected), balance management
|
core:
|
||||||
- **Tariffs** — CRUD
|
image: nexuma/core:latest
|
||||||
- **Nodes** — CRUD, protocol management, xray/MTProto version install, GeoIP update, outbounds
|
container_name: nexuma-core
|
||||||
- **Subscriptions** — create, manage, auto-renewal toggle
|
restart: unless-stopped
|
||||||
- **Routing rule sets** — xray-native rules and balancers
|
environment:
|
||||||
- **External subscriptions** — aggregate external VPN share URLs
|
PORT: 3000
|
||||||
- **Audit logs**
|
DB_HOST: your-postgres-host
|
||||||
|
DB_PORT: 5432
|
||||||
|
DB_USER: postgres
|
||||||
|
DB_PASSWORD: change-me
|
||||||
|
DB_NAME: nexuma
|
||||||
|
REDIS_HOST: redis
|
||||||
|
REDIS_PORT: 6379
|
||||||
|
JWT_SECRET: replace-with-long-random-secret
|
||||||
|
JWT_REFRESH_SECRET: replace-with-another-long-random-secret
|
||||||
|
JWT_EXPIRES_IN: 15m
|
||||||
|
JWT_REFRESH_EXPIRES_IN: 7d
|
||||||
|
CORS_ORIGINS: https://panel.example.com
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
networks:
|
||||||
|
- nexuma
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -qO- http://localhost:3000/health || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
## Tariff types
|
volumes:
|
||||||
|
redis_data:
|
||||||
|
|
||||||
| Type | Expiry | Auto-renews |
|
networks:
|
||||||
|------|--------|-------------|
|
nexuma:
|
||||||
| Timed paid | By date | If balance ≥ 0 |
|
driver: bridge
|
||||||
| Timed free | By date | Always |
|
```
|
||||||
| Indefinite paid | When traffic exhausted | If balance ≥ 0 |
|
|
||||||
| Indefinite free | When traffic exhausted | Always |
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Health check:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/health
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Conventions
|
||||||
|
|
||||||
|
- JSON request and response bodies are used unless a route explicitly returns text, YAML, or metrics.
|
||||||
|
- User and admin routes use `Authorization: Bearer <access_token>`.
|
||||||
|
- Admin routes require an admin account.
|
||||||
|
- Node-private routes use `x-node-auth-key`.
|
||||||
|
- Public subscription content is served by UUID and does not require login.
|
||||||
|
- Validation errors return HTTP error responses with an error message.
|
||||||
|
|
||||||
|
## Public API
|
||||||
|
|
||||||
|
| Method | Route | Purpose |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `GET` | `/health` | Health status. |
|
||||||
|
| `GET` | `/metrics` | Metrics text output. |
|
||||||
|
| `POST` | `/auth/register` | Register with email and password. |
|
||||||
|
| `POST` | `/auth/login` | Login with email and password. |
|
||||||
|
| `POST` | `/auth/refresh` | Refresh tokens. |
|
||||||
|
| `POST` | `/auth/telegram` | Login or register through Telegram OAuth. |
|
||||||
|
| `GET` | `/auth/telegram/bot-id` | Return configured Telegram bot ID. |
|
||||||
|
| `GET` | `/sub/:uuid/info` | Return public subscription metadata. |
|
||||||
|
| `GET` | `/sub/:uuid` | Return generated VPN subscription content. |
|
||||||
|
|
||||||
|
`GET /sub/:uuid` supports:
|
||||||
|
|
||||||
|
- `?format=v2ray`
|
||||||
|
- `?format=clash`
|
||||||
|
- `?format=singbox`
|
||||||
|
|
||||||
|
Missing or unknown `format` falls back to `v2ray`.
|
||||||
|
|
||||||
|
## User API
|
||||||
|
|
||||||
|
| Method | Route | Purpose |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `GET` | `/me` | Current user profile. |
|
||||||
|
| `PATCH` | `/me/profile` | Update name, email, or password. |
|
||||||
|
| `GET` | `/me/stats` | User summary statistics. |
|
||||||
|
| `GET` | `/me/traffic` | Traffic series by period. |
|
||||||
|
| `GET` | `/me/traffic/daily` | Daily traffic series. |
|
||||||
|
| `GET` | `/me/traffic/hourly-dist` | Hour-of-day traffic distribution. |
|
||||||
|
| `GET` | `/me/balance` | Current balance. |
|
||||||
|
| `GET` | `/me/balance/transactions` | User balance history. |
|
||||||
|
| `GET` | `/me/subscriptions` | User subscriptions. |
|
||||||
|
| `GET` | `/me/links` | User subscription links. |
|
||||||
|
| `GET` | `/me/configs` | Available configs for the user. |
|
||||||
|
| `POST` | `/me/links` | Create a public link. |
|
||||||
|
| `PATCH` | `/me/subscriptions/links/:id` | Rename, enable, disable, or reconfigure a link. |
|
||||||
|
| `DELETE` | `/me/subscriptions/links/:id` | Delete a link. |
|
||||||
|
| `POST` | `/me/subscriptions/:id/reset` | Reset a link UUID. |
|
||||||
|
| `PATCH` | `/me/subscriptions/:id/auto-renew` | Toggle auto-renew. |
|
||||||
|
|
||||||
|
## Admin API Groups
|
||||||
|
|
||||||
|
- `/admin/users` - users, approval status, block flag, profile fields, stats.
|
||||||
|
- `/admin/tariffs` - tariff CRUD.
|
||||||
|
- `/admin/subscriptions` - subscriptions and subscription links.
|
||||||
|
- `/admin/nodes` - nodes, protocols, sync, runtime operations, masking sites, outbounds.
|
||||||
|
- `/admin/routing-rule-sets` - reusable routing templates.
|
||||||
|
- `/admin/external-subscriptions` - external source CRUD and URL testing.
|
||||||
|
- `/admin/logs` - audit log viewer data.
|
||||||
|
- `/admin/usage` - traffic usage rows.
|
||||||
|
- `/admin/xray/parse-link` - parse a proxy share link into outbound settings.
|
||||||
|
|
||||||
|
See [Core API routes](./core-routes.md) for the detailed route table.
|
||||||
|
|
||||||
|
## Node-Private API
|
||||||
|
|
||||||
|
Node agents consume the private `/node/*` API:
|
||||||
|
|
||||||
|
- `POST /node/register`
|
||||||
|
- `POST /node/heartbeat`
|
||||||
|
- `GET /node/config/latest`
|
||||||
|
- `POST /node/config/applied`
|
||||||
|
- `POST /node/usage`
|
||||||
|
- `POST /node/outbound-usage`
|
||||||
|
- `POST /node/session-events`
|
||||||
|
- `POST /node/error`
|
||||||
|
|
||||||
|
See [Core private node routes](./core-private-routes.md) for the payload contract.
|
||||||
|
|
||||||
|
## Data Behavior
|
||||||
|
|
||||||
|
- Users can be `active`, `pending`, or `rejected`.
|
||||||
|
- Nodes can be `active`, `disabled`, `maintenance`, or `error`.
|
||||||
|
- Effective node status is computed from stored status and last heartbeat.
|
||||||
|
- Subscriptions can be `active`, `expired`, or `cancelled`.
|
||||||
|
- A tariff with `durationMonths = null` is indefinite and expires only by traffic limit or cancellation.
|
||||||
|
- A subscription link UUID is the credential used in generated client configs.
|
||||||
|
- Disabling or resetting a link affects generated configs on the next node sync.
|
||||||
|
- Node config revisions increment when node-facing configuration changes.
|
||||||
|
- Active nodes receive only active subscription clients.
|
||||||
|
|
||||||
|
## Operational Notes
|
||||||
|
|
||||||
|
- Run Core behind HTTPS in production.
|
||||||
|
- Keep database and Redis private.
|
||||||
|
- Use unique long secrets for access and refresh tokens.
|
||||||
|
- Restrict CORS to the panel domain.
|
||||||
|
- Back up the database before upgrades.
|
||||||
|
- Rotate node auth keys if a node host is compromised.
|
||||||
|
- Do not expose node-private endpoints directly to the internet.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
version: '3.9'
|
version: '3.9'
|
||||||
|
|
||||||
# Core + Panel deployment (main server)
|
# Core + Panel deployment for the main server.
|
||||||
# Usage: docker compose -f docker-compose.base.yml up -d
|
# Usage: docker compose -f docker-compose.base.yml up -d
|
||||||
|
|
||||||
services:
|
services:
|
||||||
@@ -19,23 +19,19 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
PORT: 3000
|
PORT: 3000
|
||||||
# PostgreSQL — use an external instance or add a postgres service below
|
|
||||||
DB_HOST: your-postgres-host
|
DB_HOST: your-postgres-host
|
||||||
DB_PORT: 5432
|
DB_PORT: 5432
|
||||||
DB_USER: postgres
|
DB_USER: postgres
|
||||||
DB_PASSWORD: change-me
|
DB_PASSWORD: change-me
|
||||||
DB_NAME: nexuma
|
DB_NAME: nexuma
|
||||||
# Redis
|
|
||||||
REDIS_HOST: redis
|
REDIS_HOST: redis
|
||||||
REDIS_PORT: 6379
|
REDIS_PORT: 6379
|
||||||
# JWT — use long random strings
|
JWT_SECRET: replace-with-long-random-secret
|
||||||
JWT_SECRET: change-me
|
JWT_REFRESH_SECRET: replace-with-another-long-random-secret
|
||||||
JWT_REFRESH_SECRET: change-me
|
|
||||||
JWT_EXPIRES_IN: 15m
|
JWT_EXPIRES_IN: 15m
|
||||||
JWT_REFRESH_EXPIRES_IN: 7d
|
JWT_REFRESH_EXPIRES_IN: 7d
|
||||||
# Optional: Telegram bot (enables OAuth login and notifications)
|
CORS_ORIGINS: https://panel.example.com
|
||||||
# TELEGRAM_BOT_TOKEN: your-bot-token
|
# TELEGRAM_BOT_TOKEN: your-bot-token
|
||||||
# CORS_ORIGINS: https://your-domain.com
|
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -55,6 +51,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
NUXT_CORE_URL: http://core:3000
|
NUXT_CORE_URL: http://core:3000
|
||||||
|
NUXT_PUBLIC_API_BASE: /api
|
||||||
ports:
|
ports:
|
||||||
- "3010:3000"
|
- "3010:3000"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
version: '3.9'
|
version: '3.9'
|
||||||
|
|
||||||
# Node agent deployment (each VPN server)
|
# Node agent deployment for each VPN/proxy server.
|
||||||
# Usage: docker compose -f docker-compose.node.yml up -d
|
# Usage: docker compose -f docker-compose.node.yml up -d
|
||||||
|
|
||||||
services:
|
services:
|
||||||
@@ -11,15 +11,18 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
PORT: 3001
|
PORT: 3001
|
||||||
# URL of Core — must be reachable from this server
|
# URL of Core — must be reachable from this server
|
||||||
CORE_URL: http://your-core-server:3000
|
CORE_URL: https://core.example.com
|
||||||
# One-time registration code — generate in admin panel → Nodes
|
# One-time registration code — generate in admin panel → Nodes
|
||||||
# Remove this line after the node registers successfully
|
# Remove this line after the node registers successfully
|
||||||
REGISTRATION_CODE: your-one-time-code
|
REGISTRATION_CODE: paste-one-time-code-here
|
||||||
HEARTBEAT_INTERVAL_SEC: 30
|
HEARTBEAT_INTERVAL_SEC: 30
|
||||||
volumes:
|
volumes:
|
||||||
- node_data:/var/lib/vpnnode
|
- node_data:/var/lib/vpnnode
|
||||||
ports:
|
ports:
|
||||||
- "3001:3001"
|
- "3001:3001"
|
||||||
|
# Add protocol ports here, for example:
|
||||||
|
# - "443:443"
|
||||||
|
# - "443:443/udp"
|
||||||
cap_add:
|
cap_add:
|
||||||
- NET_ADMIN
|
- NET_ADMIN
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|||||||
@@ -1,30 +1,172 @@
|
|||||||
# Node
|
# Node
|
||||||
|
|
||||||
Agent that runs on each VPN server. Connects to Core, receives protocol configuration, manages the xray process, and reports traffic statistics.
|
Node is the agent installed on each VPN/proxy server. It registers with Core, receives configuration revisions, applies local services, reports usage, and exposes authenticated runtime actions.
|
||||||
|
|
||||||
## Configuration
|
This page is public operator documentation. It describes setup, behavior, environment variables, Docker deployment, and the node HTTP API without requiring source-code access.
|
||||||
|
|
||||||
Edit the environment variables in [`docker-compose.node.yml`](./docker-compose.node.yml):
|
## Capabilities
|
||||||
|
|
||||||
| Variable | Required | Description |
|
- One-time registration with Core.
|
||||||
|----------|----------|-------------|
|
- Persistent auth key storage.
|
||||||
| `CORE_URL` | Yes | URL of the Core service (e.g. `http://your-core-server:3000`) |
|
- Config sync by revision.
|
||||||
| `REGISTRATION_CODE` | Once | One-time code generated in admin panel → Nodes |
|
- Xray install, restart, validation, GeoIP update, and key generation.
|
||||||
| `HEARTBEAT_INTERVAL_SEC` | No | Heartbeat interval in seconds (default: `30`) |
|
- MTProto install and restart.
|
||||||
|
- Nginx masking site management.
|
||||||
|
- System status reporting with CPU, RAM, disk, network, and process state.
|
||||||
|
- Heartbeat reporting.
|
||||||
|
- Per-user and per-subscription traffic reporting.
|
||||||
|
- Outbound traffic reporting for proxy chains.
|
||||||
|
- Session event reporting.
|
||||||
|
- Runtime error reporting.
|
||||||
|
- Crash watchdog for local services.
|
||||||
|
|
||||||
Data directory is `/var/lib/vpnnode` and mounted as a Docker volume — all state (auth key, VPN configs, binaries) persists across container restarts automatically.
|
## Environment Variables
|
||||||
|
|
||||||
|
| Variable | Required | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `PORT` | No | `3001` | Local node agent HTTP port. |
|
||||||
|
| `NODE_ENV` | No | `development` | Runtime mode. |
|
||||||
|
| `CORE_URL` | Yes | `http://localhost:3000` | Core API URL reachable from the node server. |
|
||||||
|
| `REGISTRATION_CODE` | First start only | empty | One-time node registration code generated in the panel. |
|
||||||
|
| `NODE_AUTH_KEY` | No | empty | Optional fallback auth key when no key file exists. |
|
||||||
|
| `HEARTBEAT_INTERVAL_SEC` | No | `30` | Heartbeat interval in seconds. |
|
||||||
|
|
||||||
|
Persistent state is stored under `/var/lib/vpnnode`.
|
||||||
|
|
||||||
|
Keep this directory on a persistent Docker volume. It contains the auth key, generated configs, binaries, stats state, and masking-site files.
|
||||||
|
|
||||||
|
## Docker Compose
|
||||||
|
|
||||||
|
Create a `docker-compose.yml` on the VPN/proxy server:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3.9'
|
||||||
|
|
||||||
|
services:
|
||||||
|
node:
|
||||||
|
image: nexuma/node:latest
|
||||||
|
container_name: nexuma-node
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
PORT: 3001
|
||||||
|
CORE_URL: https://core.example.com
|
||||||
|
REGISTRATION_CODE: paste-one-time-code-here
|
||||||
|
HEARTBEAT_INTERVAL_SEC: 30
|
||||||
|
volumes:
|
||||||
|
- node_data:/var/lib/vpnnode
|
||||||
|
ports:
|
||||||
|
- "3001:3001"
|
||||||
|
# Add protocol ports here, for example:
|
||||||
|
# - "443:443"
|
||||||
|
# - "443:443/udp"
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -qO- http://localhost:3001/health || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 15s
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
node_data:
|
||||||
|
```
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
After successful registration, remove `REGISTRATION_CODE` and redeploy:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
## Registration
|
## Registration
|
||||||
|
|
||||||
1. In the admin panel, open **Nodes** and generate a registration code.
|
1. In the panel, open `Nodes`.
|
||||||
2. Set `CORE_URL` and `REGISTRATION_CODE` in `docker-compose.node.yml`.
|
2. Generate a registration code.
|
||||||
3. Run `docker compose -f docker-compose.node.yml up -d`.
|
3. Set `CORE_URL` and `REGISTRATION_CODE` on the node server.
|
||||||
4. Once the node appears as online in the panel, remove the `REGISTRATION_CODE` line.
|
4. Start the node container.
|
||||||
|
5. Wait until the node appears as registered.
|
||||||
|
6. Remove `REGISTRATION_CODE`.
|
||||||
|
7. Keep the `node_data` volume for future restarts.
|
||||||
|
|
||||||
## Behaviour
|
If the persistent volume is deleted, the node loses its auth key and must be registered again or started with `NODE_AUTH_KEY`.
|
||||||
|
|
||||||
- Sends a heartbeat to Core every 30 seconds; if Core reports a newer config revision, the node pulls it immediately.
|
## Local HTTP API
|
||||||
- Core can also trigger an instant config pull by calling the node directly (e.g. on admin-initiated sync).
|
|
||||||
- Reports per-subscription traffic to Core every minute.
|
Public:
|
||||||
- On first start, automatically installs the latest xray release if not present.
|
|
||||||
- Updates GeoIP/Geosite files automatically every 12 hours.
|
| Method | Route | Purpose |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `GET` | `/health` | Health check. |
|
||||||
|
|
||||||
|
All other routes require `x-node-auth-key`.
|
||||||
|
|
||||||
|
| Method | Route | Purpose |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `GET` | `/node/status` | Runtime status and resource usage. |
|
||||||
|
| `POST` | `/node/apply-config` | Trigger config pull and apply. |
|
||||||
|
| `POST` | `/node/xray/restart` | Restart Xray. |
|
||||||
|
| `POST` | `/node/xray/install` | Install an Xray version. |
|
||||||
|
| `POST` | `/node/geoip/update` | Update GeoIP/geosite files. |
|
||||||
|
| `POST` | `/node/crypto/x25519` | Generate X25519 keys. |
|
||||||
|
| `POST` | `/node/crypto/mldsa65` | Generate an ML-DSA-65 seed. |
|
||||||
|
| `POST` | `/node/mtproto/restart` | Restart MTProto processes. |
|
||||||
|
| `POST` | `/node/mtproto/install` | Install an MTProto version. |
|
||||||
|
| `GET` | `/node/nginx/sites` | List masking sites. |
|
||||||
|
| `GET` | `/node/nginx/sites/:domain` | Get one masking site. |
|
||||||
|
| `PUT` | `/node/nginx/sites` | Replace masking sites. |
|
||||||
|
| `PUT` | `/node/nginx/site` | Upsert one masking site. |
|
||||||
|
| `GET` | `/node/nginx/site` | Get current masking site status. |
|
||||||
|
| `DELETE` | `/node/nginx/sites/:domain` | Delete one masking site. |
|
||||||
|
| `DELETE` | `/node/nginx/site` | Delete current masking site. |
|
||||||
|
| `POST` | `/node/nginx/restart` | Restart nginx. |
|
||||||
|
|
||||||
|
These endpoints are intended to be called by Core. Protect the agent port with firewall rules or private networking.
|
||||||
|
|
||||||
|
## Config Sync
|
||||||
|
|
||||||
|
- The node keeps the latest applied revision locally.
|
||||||
|
- Core can ask the node to apply config immediately.
|
||||||
|
- Heartbeat responses can also request sync.
|
||||||
|
- The node pulls the latest config from Core.
|
||||||
|
- If config apply succeeds, the node confirms the applied revision.
|
||||||
|
- If config apply fails, the node reports an error and keeps the previous valid config where possible.
|
||||||
|
|
||||||
|
## Traffic and Events
|
||||||
|
|
||||||
|
The node reports:
|
||||||
|
|
||||||
|
- user traffic
|
||||||
|
- subscription traffic attribution
|
||||||
|
- per-protocol usage
|
||||||
|
- outbound usage
|
||||||
|
- online/offline session events
|
||||||
|
- service crash and config errors
|
||||||
|
|
||||||
|
Traffic is flushed periodically. If reporting fails temporarily, the node keeps pending stats for the next flush where possible.
|
||||||
|
|
||||||
|
## Status Output
|
||||||
|
|
||||||
|
`GET /node/status` returns operational status including:
|
||||||
|
|
||||||
|
- system uptime
|
||||||
|
- CPU usage and core count
|
||||||
|
- RAM usage
|
||||||
|
- disk usage
|
||||||
|
- network transfer rates and totals
|
||||||
|
- Xray running state and uptime
|
||||||
|
- MTProto process state and connection counts
|
||||||
|
|
||||||
|
## Operations
|
||||||
|
|
||||||
|
- Use node sync from the panel after changing protocols, routing, or outbounds.
|
||||||
|
- Open every configured inbound port in Docker and the server firewall.
|
||||||
|
- Keep `/var/lib/vpnnode` persistent.
|
||||||
|
- Keep the node agent API private.
|
||||||
|
- Remove one-time registration codes after registration.
|
||||||
|
- Rotate auth keys when moving or rebuilding a server.
|
||||||
|
|||||||
@@ -1,30 +1,226 @@
|
|||||||
# Panel
|
# Panel
|
||||||
|
|
||||||
Admin web interface for Nexuma. Built with Nuxt 3 and Nuxt UI.
|
Panel is the Nexuma web interface for operators and users. Operators manage the platform from one place. Users manage their own subscriptions, public links, configs, traffic, finance history, and account settings.
|
||||||
|
|
||||||
## Configuration
|
This page is public operator documentation. It describes panel capabilities, routes, API behavior, environment variables, and Docker deployment without requiring source-code access.
|
||||||
|
|
||||||
| Variable | Required | Description |
|
## Capabilities
|
||||||
|----------|----------|-------------|
|
|
||||||
| `NUXT_CORE_URL` | Yes | Internal URL of Core (e.g. `http://core:3000`) |
|
|
||||||
| `NUXT_PUBLIC_API_BASE` | No | Public API base path (default: `/api`) |
|
|
||||||
|
|
||||||
## Running
|
### Admin Dashboard
|
||||||
|
|
||||||
|
- Node status grid.
|
||||||
|
- Recent audit log feed.
|
||||||
|
- Auto-refresh for operational visibility.
|
||||||
|
|
||||||
|
### Users
|
||||||
|
|
||||||
|
- User list with search and status filters.
|
||||||
|
- Approval, rejection, restore, block, edit, and delete actions.
|
||||||
|
- User profile details.
|
||||||
|
- User subscriptions.
|
||||||
|
- User subscription links.
|
||||||
|
- User node/protocol access matrix.
|
||||||
|
- User finance and balance transaction history.
|
||||||
|
|
||||||
|
### Tariffs
|
||||||
|
|
||||||
|
- Create, edit, and delete tariffs.
|
||||||
|
- Configure price, duration, traffic limit, active state, and allowed protocols.
|
||||||
|
- Restrict tariffs to selected nodes.
|
||||||
|
- Attach external subscription sources.
|
||||||
|
- Allow or block selected routing categories through tariff permissions.
|
||||||
|
|
||||||
|
### Subscriptions
|
||||||
|
|
||||||
|
- Create and cancel subscriptions.
|
||||||
|
- Filter subscriptions by status, user, and tariff.
|
||||||
|
- Toggle auto-renew.
|
||||||
|
- Create, rename, disable, reset, and delete public links.
|
||||||
|
- Restrict public links to selected nodes.
|
||||||
|
|
||||||
|
### Nodes
|
||||||
|
|
||||||
|
- List, create, edit, and delete nodes.
|
||||||
|
- Generate registration codes.
|
||||||
|
- Rotate node auth keys.
|
||||||
|
- Force config sync.
|
||||||
|
- Manage protocols.
|
||||||
|
- Manage outbounds for proxy chains.
|
||||||
|
- Manage routing config and apply routing templates.
|
||||||
|
- Manage masking sites.
|
||||||
|
- Install and restart Xray and MTProto.
|
||||||
|
- Update GeoIP/geosite files.
|
||||||
|
- Generate X25519 and ML-DSA-65 keys.
|
||||||
|
- View live runtime status and resource usage.
|
||||||
|
|
||||||
|
### Routing Rules
|
||||||
|
|
||||||
|
- Create, edit, and delete reusable rule sets.
|
||||||
|
- Manage custom rules and balancers.
|
||||||
|
- Assign routing configuration to nodes.
|
||||||
|
|
||||||
|
### External Subscriptions
|
||||||
|
|
||||||
|
- Create, edit, and delete external subscription sources.
|
||||||
|
- Test a source URL before attaching it to tariffs.
|
||||||
|
- Attach sources to tariffs so their share links are appended to subscriptions.
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
|
||||||
|
- View audit events.
|
||||||
|
- Filter logs by action and user.
|
||||||
|
|
||||||
|
### Self-Service
|
||||||
|
|
||||||
|
- Personal dashboard.
|
||||||
|
- Traffic charts and hourly distribution.
|
||||||
|
- Personal subscriptions.
|
||||||
|
- Personal public links.
|
||||||
|
- Personal configs.
|
||||||
|
- Finance history and balance.
|
||||||
|
- Account profile settings.
|
||||||
|
- Telegram account linking and unlinking.
|
||||||
|
|
||||||
|
### Public Subscription Page
|
||||||
|
|
||||||
|
- Public `/sub/:uuid` page.
|
||||||
|
- Subscription metadata.
|
||||||
|
- Raw config download/copy.
|
||||||
|
- QR code.
|
||||||
|
- Per-config copy buttons.
|
||||||
|
- MTProto link display.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
| Variable | Required | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `NUXT_PUBLIC_API_BASE` | No | `/api` | Browser-facing API base. Use `/api` for same-origin panel proxying. |
|
||||||
|
| `NUXT_CORE_URL` | Yes for Docker | `http://localhost:3000` fallback | Core URL used by the panel server proxy. |
|
||||||
|
|
||||||
|
Recommended production values:
|
||||||
|
|
||||||
|
```env
|
||||||
|
NUXT_PUBLIC_API_BASE=/api
|
||||||
|
NUXT_CORE_URL=http://core:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker Compose
|
||||||
|
|
||||||
|
Panel-only deployment:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '3.9'
|
||||||
|
|
||||||
|
services:
|
||||||
|
panel:
|
||||||
|
image: nexuma/panel:latest
|
||||||
|
container_name: nexuma-panel
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
NUXT_PUBLIC_API_BASE: /api
|
||||||
|
NUXT_CORE_URL: https://core.example.com
|
||||||
|
ports:
|
||||||
|
- "3010:3000"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -qO- http://localhost:3000/ || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
```
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
Open `http://your-server:3010`.
|
Open:
|
||||||
|
|
||||||
## What you can do
|
```text
|
||||||
|
http://your-server:3010
|
||||||
|
```
|
||||||
|
|
||||||
- **Users** — manage accounts, approve or reject registrations, top up balances
|
## Routes
|
||||||
- **Tariffs** — create and configure tariff plans (protocols, nodes, traffic limits, price)
|
|
||||||
- **Nodes** — add VPN servers, configure protocols, install xray, manage routing
|
Public routes:
|
||||||
- **Subscriptions** — view and manage all subscriptions, create links, toggle auto-renewal
|
|
||||||
- **Config builder** — guided wizard to set up a new protocol on a node
|
| Route | Purpose |
|
||||||
- **Routing rules** — create xray rule sets and assign them to nodes
|
| --- | --- |
|
||||||
- **External subscriptions** — import external VPN share URLs into user subscriptions
|
| `/auth/login` | Login screen. |
|
||||||
- **Logs** — audit log viewer
|
| `/auth/pending` | Waiting-for-approval screen. |
|
||||||
- **User self-service** — users can view their own subscriptions, traffic, and balance
|
| `/auth/telegram/redirect` | Telegram login callback. |
|
||||||
|
| `/auth/telegram/link-redirect` | Telegram account-link callback. |
|
||||||
|
| `/sub/:uuid` | Public subscription page. |
|
||||||
|
| `/:uuid` | Root UUID entry point for clients and redirects. |
|
||||||
|
|
||||||
|
Admin routes:
|
||||||
|
|
||||||
|
| Route | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `/` | Dashboard. |
|
||||||
|
| `/users` | User list. |
|
||||||
|
| `/users/:id` | User profile. |
|
||||||
|
| `/users/:id/subscriptions` | User subscriptions. |
|
||||||
|
| `/users/:id/links` | User links. |
|
||||||
|
| `/users/:id/access` | User node/protocol access. |
|
||||||
|
| `/users/:id/finance` | User finance. |
|
||||||
|
| `/tariffs` | Tariff management. |
|
||||||
|
| `/nodes` | Node list. |
|
||||||
|
| `/nodes/:id` | Node overview. |
|
||||||
|
| `/nodes/:id/protocols` | Node protocols. |
|
||||||
|
| `/nodes/:id/outbounds` | Node outbounds. |
|
||||||
|
| `/nodes/:id/routing` | Node routing. |
|
||||||
|
| `/nodes/:id/masking` | Node masking site. |
|
||||||
|
| `/subscriptions` | Subscription management. |
|
||||||
|
| `/routing-rules` | Routing rule set management. |
|
||||||
|
| `/external-subscriptions` | External source management. |
|
||||||
|
| `/logs` | Audit logs. |
|
||||||
|
| `/config-builder` | Protocol config builder. |
|
||||||
|
|
||||||
|
User routes:
|
||||||
|
|
||||||
|
| Route | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `/me` | Personal dashboard. |
|
||||||
|
| `/me/subscriptions` | Personal subscriptions. |
|
||||||
|
| `/me/configs` | Personal configs. |
|
||||||
|
| `/me/links` | Personal links. |
|
||||||
|
| `/me/finance` | Personal finance history. |
|
||||||
|
|
||||||
|
## API Behavior
|
||||||
|
|
||||||
|
- Browser API calls use `NUXT_PUBLIC_API_BASE`.
|
||||||
|
- The recommended browser base is `/api`.
|
||||||
|
- Requests to `/api/*` are proxied by the panel server to `NUXT_CORE_URL`.
|
||||||
|
- The proxy strips the `/api` prefix before forwarding.
|
||||||
|
- UUID root requests from VPN clients are proxied to Core subscription output when the request does not ask for HTML.
|
||||||
|
- Access and refresh tokens are stored in the browser.
|
||||||
|
- API calls include the bearer access token when available.
|
||||||
|
- A `401` response triggers a refresh attempt and one retry.
|
||||||
|
- Failed operations are shown as user-facing notifications unless marked silent.
|
||||||
|
|
||||||
|
## Auth Rules
|
||||||
|
|
||||||
|
- `/auth/*`, `/sub/*`, and UUID-only root paths are public.
|
||||||
|
- Pending users can only see the pending page and logout.
|
||||||
|
- Admin pages require an admin account.
|
||||||
|
- Self-service pages require a logged-in user.
|
||||||
|
|
||||||
|
## Typical Operator Flow
|
||||||
|
|
||||||
|
1. Log in as admin.
|
||||||
|
2. Create or approve users.
|
||||||
|
3. Create tariffs.
|
||||||
|
4. Add or register nodes.
|
||||||
|
5. Configure node protocols.
|
||||||
|
6. Create subscriptions.
|
||||||
|
7. Share public subscription links.
|
||||||
|
8. Monitor node status, usage, and logs.
|
||||||
|
|
||||||
|
## Public Subscription Flow
|
||||||
|
|
||||||
|
1. User or admin opens a public link.
|
||||||
|
2. The panel loads subscription metadata from Core.
|
||||||
|
3. The panel displays configs, QR, traffic, expiry, and MTProto links.
|
||||||
|
4. VPN clients can request the UUID directly and receive raw subscription output.
|
||||||
|
|||||||
Reference in New Issue
Block a user