Skip to content

Manager (Backend API) ​

The vgate-manager/ component is the control plane: a Go service (Gin + GORM) that owns users, nodes, plans, orders, traffic, and system config — including per-user and per-node speed caps — and exposes both the human-facing REST API and the node data plane.

Source: github.com/vgate-project/vgate-manager

Build & run ​

bash
cd vgate-manager
go build -o vgate-manager .
./vgate-manager --config config.yml     # defaults to ./config.yml; HTTP on server.port (8081)

Other commands (cobra subcommands):

bash
vgate-manager admin create --username X --password Y --role admin|super_admin

GORM AutoMigrate runs automatically on startup (idempotent), followed by idempotent data migrations in cmd/migrate.go (orphaned virtual-node cleanup, virtual-token normalization, a sibling-address unique index, and on PostgreSQL a nodes.parent_id foreign key). On first start the DB is auto-migrated and an initial admin is bootstrapped from admin.bootstrap in config.yml (default username admin; the bundled docker-compose.yml sets the password to change-me).

Test & lint ​

bash
go test ./...
go vet ./... && gofmt -l .

Layered structure (internal/) ​

  • api/ — HTTP layer: router.go (Gin engine + middleware wiring), handler/ (one file per resource: admin_node.go, admin_user.go, server.go, user.go, order.go, plan.go, …), dto/ (request/response structs). Tests: admin_test.go, server_test.go, sub_test.go, auth-level tests.
  • middleware/ — cors.go, jwt.go (user/admin auth), node_auth.go (node token auth), logger.go, ratelimit.go.
  • service/ — business logic: auth, user, node, plan, order/billing, traffic, stats, subscription, system_config, email/invite, announcement, telegram, ticket. Many have _test.go.
  • model/ — GORM models. util/ — helpers. wire/ — dependency wiring. pkg/crypto/ — shared crypto (VLESS credential helpers).

Startup sequence (run()) ​

  1. Load config (viper).
  2. Open DB (SQLite default via glebarez/sqlite, or Postgres via db.dialect / db.dsn).
  3. AutoMigrate all GORM models.
  4. Run the idempotent data-migration hook (cmd/migrate.go — node integrity constraints and cleanup; see the Virtual nodes & traffic attribution section below).
  5. Merge DB-backed system-config overrides on top of config.yml.
  6. Bootstrap the admin.
  7. Build router via api.NewRouter.
  8. Start background tickers: expired-order closer (5 min), hourly-stats pruning (startup + every 24 h), quota reset (startup + every 24 h), and an hourly traffic-reminder scanner (reminderSvc.CheckAndSend). Hourly traffic stats are recorded as nodes report it, not by a scheduler.

API surface (all under /api/v1) ​

GroupEndpoints
Public / user/user/login, /user/register, /user/verify-email, /user/resend-verification, /user/dashboard, /sub/:sub_token, profile / subscribe / plans / nodes / traffic, orders (+ /:id/pay, /:id/close, /:id/apple-verify), /user/payment-methods, /user/balance, /user/change-plan (+ /change-plan/preview), invites, redemption codes, reminder-channel, support tickets, self-service Telegram link, /billing/:platform/notify (alipay, wechat, stripe, paypal, apple)
Node data planeGET /server/config, GET /server/users, POST /server/traffic
Adminnodes (incl. /:id/users, /:id/regenerate-token), users (incl. /:id/balance read/adjust, /:id/nodes), traffic, stats, system-config, orders, plans, traffic-packages, reference, payment-methods, full admins[/:id] CRUD, invites, redemption codes, announcements, email, tickets, Telegram broadcast/admin-link, zombie-user preview/cleanup
HealthGET /health

Auth ​

  • JWT. Admin login returns an access token plus a refresh token, and a 401 on a protected endpoint triggers one automatic silent refresh; user login returns only an access token (users get no refresh token — a 401 means re-login).
  • Roles: admin vs super_admin gate plan/admins endpoints.
  • Node endpoints use a separate node token (node_auth middleware).

Telegram & notifications ​

The manager can run a Telegram bot that delivers alerts and announcements and lets users and admins bind their personal accounts for ticket notifications. It is enabled and configured via DB-backed system config (TelegramConfig):

KeyDefaultMeaning
telegram.enabledfalseMaster switch for the bot.
telegram.bot_token""BotFather token (secret).
telegram.bot_username""Bot @username, used to build /start deep links.
telegram.user_bot_enabledfalseAllow users to self-bind via deep link.
telegram.alert_ticketfalseNotify linked admins on new tickets / user replies.
telegram.alert_announcementfalseForward announcements to linked users.
telegram.alert_order_paidfalseNotify on paid orders (and other alert toggles).
telegram.alert_new_registrationfalseNotify linked admins on new user registrations.
telegram.alert_node_upfalseNotify linked admins when a real node comes online.
telegram.alert_node_downfalseNotify linked admins when a real node goes offline.
telegram.alert_traffic_exceededfalseNotify linked admins when a user exceeds their traffic quota.

Binding uses a /start <code> deep link. The code carries a u_ (user) or a_ (admin) prefix so the bot routes the bind to the right account: admins link from Settings → Telegram in the admin console, users from Settings in the portal.

When an admin replies to a ticket, the owner is notified on the channel they chose when opening it (none / email / telegram). Every admin with a linked Telegram account also receives an alert on each new ticket and user reply.

Support tickets ​

Tickets are a lightweight support channel between users and admins.

  • Users open tickets (POST /user/tickets), reply, and can close their own ticket (POST /user/tickets/:id/close). When opening one they pick a notification method (notify_method: none | email | telegram); if omitted it defaults to telegram when their account is Telegram-linked, else none.
  • Admins list/view all tickets, reply (POST /admin/tickets/:id/messages), and move them through a status machine open → in_progress → resolved → closed (PUT /admin/tickets/:id/status). A later user reply reopens a closed ticket.

Admins can also broadcast a message to every linked Telegram user via POST /admin/telegram/broadcast (optionally also published as an announcement).

Virtual nodes & traffic attribution ​

Nodes live in a single table: parent_id IS NULL marks a real node (polls the manager, owns transport config and liveness); a non-null parent_id marks a virtual child — an alternate entry point that inherits the parent's transport config and never polls or authenticates.

When the parent uses Reality, each child can carry a dedicated reality_sid:

  • The manager auto-generates it on create (or accepts a manual 1–16 hex value), keeps it in the parent's short_ids whitelist, and removes it when the child is deleted, cleared, or reparented.
  • Subscription links for the child advertise its SID, and GET /server/config delivers a traffic_sids (SID → child) mapping to the node.
  • POST /server/traffic accepts a per-delta node_id, honored only for the reporting node or its direct children — anything else falls back to the real node with a warning. Attributed traffic lands in user_node_traffic under the child's ID and inherits the parent's traffic multiplier, so the admin Traffic page breaks usage down per entry point.

Dashboard node counts (GET /admin/stats/overview) and Telegram node alerts cover real nodes only.

Config split ​

File / env onlyDB-backed (hot-reloadable)
server.portjwt TTLs, log.level / log.format
db.*cors.allowed_origins, server.read/write_timeout_secs
jwt.secretquota.reset_day, password.*, registration (user.register_enabled, user.register_require_invite, user.register_require_email_verify, user.register_email_suffix_whitelist) & invite settings, site.* / sub.base_urls, email.*, captcha.*, telegram.*, payment gateway creds (alipay.*, wechat.*, stripe.*, paypal.*, apple.*), reminder.*
admin.bootstrap.*(used only on first run)

The DB-backed set is broad — see Configuration Reference for the full list of hot-reloadable keys. viper maps SERVER_PORT-style env vars.

Database ​

  • SQLite by default (glebarez/sqlite) — zero external dependencies, great for small deployments.
  • Postgres via db.dialect: postgres and db.dsn.

Production notes ​

  • Set jwt.secret and a strong admin.bootstrap.password (Docker Compose defaults to change-me).
  • Put the manager behind a reverse proxy (nginx/Caddy) for TLS and to serve the frontends.
  • Enable ratelimit middleware for public endpoints.

Licensed under AGPL-3.0.