Skip to content

feat(nodes): add Veil core dispatch alongside Xray#168

Draft
Rxflex wants to merge 1 commit into
remnawave:devfrom
Rxflex:feat/veil-protocol
Draft

feat(nodes): add Veil core dispatch alongside Xray#168
Rxflex wants to merge 1 commit into
remnawave:devfrom
Rxflex:feat/veil-protocol

Conversation

@Rxflex
Copy link
Copy Markdown

@Rxflex Rxflex commented May 12, 2026

Summary

Backend counterpart to remnawave/node#38 (also Draft). Lets operators register a node whose proxy daemon is Veil instead of Xray. The queue processors that drive node lifecycle dispatch on a new Nodes.core enum (XRAY | VEIL); existing fleets keep their behaviour unchanged because every existing row defaults to core='XRAY'.

Why Draft. Two reasons:

  1. The node-side companion PR is also Draft; @remnawave/node-contract@>=2.8.0 (which would expose StartVeilCommand natively) hasn't been published yet — this PR ships an inline shim with a clear migration TODO.
  2. Veil itself is pre-alpha (v0.1.0-alpha.1) pending its first external audit; we don't want operators landing on this without that context.

What ships

Schema

enum NodeCore { XRAY VEIL }

model Nodes {
  ...
  core NodeCore @default(XRAY) @map("core")
  ...
}

Migration 20260512000000_add_node_core/migration.sql — additive, NOT NULL DEFAULT 'XRAY' + index on the column for fast per-node lookups.

Axios facade (src/common/axios/axios.service.ts) — three counterparts to the existing startXray / stopXray / getNodeHealth:

  • startVeil(data, url, port) — POST /node/veil/start with the same zstd-compressed body shape Xray uses
  • stopVeil(url, port) — GET /node/veil/stop
  • getVeilNodeHealth(url, port) — GET /node/veil/healthcheck

Same TResult<> return surface, same ERRORS.NODE_ERROR_WITH_MSG mapping, same JWT-bound axiosInstance defaults — no new HTTP plumbing.

Processors

  • StartNodeProcessor — branches at node.core. The Xray happy-path is byte-identical to today; the VEIL branch goes through a new private startVeilNode() that probes /node/veil/healthcheck, generates server.yaml, pushes via /node/veil/start, updates the node row + emits the same NODE.CONNECTED / NODE.DISCONNECTED events as the Xray path.
  • StopNodeProcessor — branches the same way (stopVeil vs stopXray).

Inline contract shim (src/common/veil-contract/veil-commands.ts) — Zod schemas for the three Veil commands. Mirrors what @remnawave/node-contract will expose once the node-side PR lands a new release. The shim is documented as throwaway: a single comment block in the file lists the imports to swap once the package is on npm.

Deliberately scoped out of this PR

Concern Why not now
Full server.yaml generator that maps the operator's existing inbounds + Reality decoy origins / mimicry profiles / decoy traffic into Veil's schema The Veil schema isn't stable enough to commit to pre-audit. buildVeilServerConfig ships a known-good three-transport skeleton on canonical ports so end-to-end connectivity is verifiable; richer mapping lands in a follow-up.
Per-user CRUD on VEIL-cored nodes Veil exposes /api/users on its admin port; the existing handler/* processors use Xray's gRPC user-add path. A VeilUserSyncProcessor lands in the same follow-up that fleshes out the YAML generator.
Frontend changes Lives in remnawave/frontend, separate PR.
Per-node stats parsing /node/stats/* is already core-agnostic (system metrics, not Xray-specific) so no change needed in this PR.

Backwards compat

Pure addition. Every existing Nodes row migrates to core='XRAY'. Each branch in the touched processors falls through to the unchanged Xray path for those rows. An operator who never registers a VEIL node sees zero behavioural change. Rolling this out to a fleet that hasn't onboarded VEIL is a no-op deploy.

Test plan

The repo's existing test infra is integration-shaped (Postgres + Redis + node mocks); rather than spinning up a half-stub I exercised this against a live setup:

  • npx prisma migrate deploy against a fresh Postgres — migration applies cleanly, SELECT core FROM nodes returns XRAY for pre-migration rows.
  • Registered a node with core='VEIL' (manual UPDATE for now until the frontend PR lands the dropdown). StartNodeProcessor picks the VEIL branch, probes /node/veil/healthcheck, posts /node/veil/start with the stub server.yaml, node flips to isConnected=true.
  • Stop → StopNodeProcessor calls stopVeil, supervisord on the node side stops the veil program, node row clears.
  • Verified an XRAY-cored node onboarded against the same panel build still uses the unchanged startXray path (regression check).

File map

prisma/
  schema.prisma                                                  +9 -0   NodeCore enum + Nodes.core field
  migrations/20260512000000_add_node_core/migration.sql          +18 -0  CREATE TYPE + ALTER TABLE + INDEX
src/
  common/
    axios/axios.service.ts                                       +97 -0  startVeil / stopVeil / getVeilNodeHealth
    veil-contract/veil-commands.ts                               +90 -0  inline shim until node-contract publishes
  modules/nodes/entities/nodes.entity.ts                         +1 -0   public core: 'XRAY' | 'VEIL'
  queue/_nodes/processors/
    start-node.processor.ts                                      +138 -0 VEIL fork + startVeilNode + buildVeilServerConfig + sha256
    stop-node.processor.ts                                       +6 -1   stopVeil branch

Total: 7 files changed, +369 / -3.

Notes for reviewers

  • Style: matches existing repo conventions — 4-space indent, alphabetical @modules / @common / @libs import groups, pRetry + AxiosError failure surface, same logger / event-emitter shapes the Xray processor uses.
  • No new top-level dependencies. The veil shim uses zod and the SHA-256 helper uses node:crypto, both already in the dep tree.
  • The shim's enum names + URL paths are pinned to what the node PR exposes; keep them in lock-step if the node side renames.
  • Once @remnawave/node-contract ships the real namespaces, the veil-contract shim folder gets deleted in a one-line PR (just swap the import).

Backend counterpart to the matching feat(veil-core) PR on
remnawave/node. Lets operators register a node whose proxy daemon
is Veil (https://github.com/redstone-md/veil) instead of Xray; the
queue processors that drive node lifecycle dispatch on a new
Nodes.core enum.

What ships
==========

* Prisma migration 20260512000000_add_node_core
    - new enum node_core { XRAY, VEIL }
    - new column Nodes.core (NOT NULL DEFAULT 'XRAY', indexed)
  Existing rows default to XRAY → migration is a no-op for every
  existing fleet. Direct INSERTs (admin scripts, seed) keep
  working without specifying the column.

* NodesEntity.core: 'XRAY' | 'VEIL' alongside the existing fields.

* AxiosService gains three counterparts to startXray/stopXray/
  getNodeHealth:
    - startVeil(data, url, port)
    - stopVeil(url, port)
    - getVeilNodeHealth(url, port)
  Same zstd-compressed POST shape as startXray, same TResult<>
  return surface, same ERRORS map.

* StartNodeProcessor branches at node.core: XRAY path is unchanged;
  VEIL path probes /node/veil/healthcheck, generates server.yaml,
  pushes via /node/veil/start, updates the node row + emits the
  same NODE.CONNECTED / NODE.DISCONNECTED events the Xray path does.

* StopNodeProcessor branches the same way (stopVeil vs stopXray).

* Inline shim src/common/veil-contract/veil-commands.ts holds the
  Zod schemas for the three commands until @remnawave/node-contract
  is published with the matching namespaces (the node-side PR is
  also Draft today). Once node-contract@>=2.8.0 ships, the shim is
  deleted and the imports collapse to a single line.

Deliberately scoped out of this PR
==================================

* server.yaml generator is a deliberate stub. buildVeilServerConfig
  emits a known-good three-transport skeleton on canonical ports so
  end-to-end connectivity is verifiable; mapping the operator's
  existing inbounds + Reality decoy origins / mimicry profiles /
  decoy traffic into the Veil schema lands in a follow-up after
  the upstream Veil schema stabilises post-audit.

* Per-user CRUD on VEIL-cored nodes. Veil exposes /api/users on
  the admin port; the existing handler/* processors hit Xray's
  gRPC user-add path which doesn't apply. A VeilUserSyncProcessor
  comes in the same follow-up that fleshes out the YAML generator.

* Frontend (remnawave/frontend) — separate repo, separate PR, no
  changes here.

Backwards compatibility
=======================

Pure addition. All existing Nodes default core=XRAY → every
processor branch falls through to the unchanged Xray path. Nodes
the operator hasn't migrated to VEIL see zero behavioural change.
Removing this column or dropping the new axios methods has no
runtime effect on a fleet that hasn't onboarded a VEIL node.

Marked Draft because:
  1. The node-side counterpart (remnawave/node feat(veil-core))
     is also Draft, and node-contract isn't published yet.
  2. Veil itself is pre-alpha (v0.1.0-alpha.1) and pending its
     first external audit; we don't want operators landing on
     this without that context.
@snyk-io
Copy link
Copy Markdown

snyk-io Bot commented May 12, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@what-the-diff
Copy link
Copy Markdown

what-the-diff Bot commented May 12, 2026

PR Summary

  • Addition of a New Column in the Nodes Table:
    A new column named 'core' has been added to the 'nodes' table in the SQL Database. This column will contain two types of data: 'XRAY' and 'VEIL'. By default, the value selected will be 'XRAY'.

  • Alterations in the Prisma Schema:
    Modifications have been done to the Prisma schema to accommodate the new 'core' field in the 'Nodes' model. A fresh enum named 'NodeCore' has been defined to signify the types of nodes.

  • Improvements in the Axios Service:
    The Axios service now includes methods to handle 'VEIL' nodes. New methods like 'startVeil', 'stopVeil', and 'getVeilNodeHealth' have been introduced. These methods connect to the new endpoints for operations related to 'VEIL'.

  • New File Creation:
    A new file named 'veil-commands.ts' has been created. It formats requests and responses associated with 'VEIL' related commands in a consistent pattern. This pattern is akin to the pattern followed by existing Xray commands.

  • Nodes Entity Class Modification:
    The Nodes entity class now houses a 'core' property. It will signify the type of the node, whether 'XRAY' or 'VEIL'.

  • Improvements to the StartNodeProcessor:
    The StartNodeProcessor can now handle preparation for 'VEIL'-cored nodes. The method 'startVeilNode' will be used for initialising such nodes.

  • Changes in the StopNodeProcessor:
    The StopNodeProcessor can now differentiate when to stop 'VEIL' and 'XRAY' nodes. The stop methods will be used based on the core type of the node.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant