Files
docs/core-private-routes.md
T
2026-06-11 02:41:13 +03:00

6.4 KiB

Core Private Node Routes

These routes are consumed only by the node agent and require the x-node-auth-key header.

Transport

The node uses two transports to communicate with Core:

  • HTTP — registration only (POST /node/register).
  • gRPC — all other node-to-core calls: heartbeat, usage reporting, config sync, and stats. Core listens on GRPC_PORT (default 3002). The node connects via CORE_GRPC_URL. Set CORE_INSECURE=true on the node to use plaintext instead of TLS.

The HTTP routes for heartbeat, usage, config, and error remain available as a fallback but the node agent uses gRPC by default.

Authentication

  • POST /node/register is the only public route in this group.
  • All other HTTP routes require the x-node-auth-key header.
  • gRPC calls pass the auth key as the x-node-auth-key metadata header (or in the authKey request field as fallback).
  • 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
  • ip: string
  • port: number — agent HTTP port (required; auto-detected from PORT env on the node)
  • region: string
  • domain?: string — optional domain name (omitted on auto-registration; can be set later via admin panel)

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:

{
  revision: number
  clientMap: Record<string, { userId: number; subscriptionId: number | null }>
  mtprotoClientMap: 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 xray traffic attribution.
  • mtprotoClientMap maps MTProto link UUIDs to the user and subscription — used for telemt 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 maps 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.

gRPC interface (NodeService)

Core exposes a gRPC service that the node agent uses for all real-time communication. The service name is nodeservice.NodeService and is defined in proto/node-service.proto.

Authentication: pass x-node-auth-key as gRPC metadata (or in the authKey field of the request as fallback).

Method Request fields Response fields Notes
Heartbeat authKey, revision?, ip? ok, needSync Equivalent to POST /node/heartbeat
ReportUsage authKey, entries[] ok Equivalent to POST /node/usage
ReportOutboundUsage authKey, items[] ok Equivalent to POST /node/outbound-usage
GetLatestConfig authKey revision, clientMapJson, mtprotoClientMapJson, servicesJson Config fields are JSON-encoded strings
ConfirmConfigApplied authKey, revision ok Equivalent to POST /node/config/applied
ReportNodeStats authKey, system{}, xray{}, mtprotoJson ok Reports CPU/RAM/disk/network/xray/mtproto snapshot; stored in Redis; drives GET /admin/nodes/:id/runtime-status and stats history
ReportOnlineUsers authKey, users[] ok Reports currently connected users with their IPs every 5 s; Core stores Redis TTL keys and updates IP history

ReportNodeStats nested fields:

  • system: uptimeSeconds, cpuPercent, cpuCores, ramUsed, ramTotal, diskUsed, diskTotal, netRxPerSec, netTxPerSec, netRxTotal, netTxTotal
  • xray: running, uptimeSeconds?
  • mtprotoJson: JSON string Record<tag, MtProtoStatus>

ReportOnlineUsers.users[] entry: userId, nodeProtocolId, ips[]{ip, lastSeen}.

The node sends ReportNodeStats every 2 s. GET /admin/nodes/:id/runtime-status returns the latest stored snapshot without polling the node; ?fromNode=true forces a live HTTP fetch instead.

The node sends ReportOnlineUsers every 5 s. Core sets online:user:{userId} and online:inbound:{nodeProtocolId}:user:{userId} Redis keys with TTL 20 s. Existing (userId, ip) rows only receive a new lastSeenAt; missing pairs are inserted. When IPinfo is configured, a daily job stores formatted country and ASN data in user_ips.info. Core also emits a user.online domain event when the user had a gap > 10 s.