# Architecture

> One Go binary that holds the panel, the bot and the background workers, and supervises Xray-core as a child process.

Source: https://joinnasnet.com/en/guides/nasnet-linux/architecture/
Last updated: 2026-08-17

---

## Overview

Nasnet Linux is a **single Go binary** that runs everything: the HTTP API, the
embedded admin and subscriber panels, the Telegram bot, the background workers,
and supervision of a local Xray-core process. There is no agent to install and
no fleet to coordinate, because the panel and the proxy core sit on the same
host.

Knowing that shape explains most of the operational advice elsewhere in these
guides: why an upgrade is usually "replace the binary and restart", why there is
no message broker to keep alive, and why losing the host loses everything, which
is what makes [backups](/en/guides/nasnet-linux/backup/) the thing to get right.

## What the binary runs

Entry point `main.go` → `cmd/root.go`, started with `nasnet-panel serve`.

| Component | What it does |
| --------- | ------------ |
| HTTP server (Gin) | The REST API plus the embedded admin and subscriber panels, served from a `go:embed`ed Vite build. |
| Telegram bot | Optional. Admin operations and user-facing subscription info. |
| Xray supervisor | Starts Xray-core as a child process, keeps it alive with a watchdog, applies generated config. |
| Background services | Scheduler, monitor, provisioning worker, alert engine, notification dispatcher. |
| Database | PostgreSQL or SQLite through GORM, with automatic schema migration on boot. |

## How it drives Xray

The panel does **not** talk to Xray over a network control plane. Inbounds,
outbounds, routing and balancing that you build in the panel are compiled into
an Xray configuration by the config generator; the supervisor then applies it to
the local core and reloads it. Stats and proxy users are read and managed
through Xray's local gRPC API, by default on `127.0.0.1:10085`.

The request and response shapes exchanged with the core are defined in
`proto/node_agent.proto`, but they are used as **in-process types**. There is
no wire transport between the panel and the core, and therefore no mutual TLS
to configure between them.

## Layered code

Every feature under `internal/<feature>/` follows the same four layers, which is
why the codebase stays navigable as features are added:

| Layer | Responsibility |
| ----- | -------------- |
| `domain` | Entities and the repository/usecase interfaces. No framework imports. |
| `repository` | Data access, implementing the domain interfaces against GORM. |
| `usecase` | Business logic. Orchestrates repositories, enforces rules, emits events. |
| `delivery` | Transport adapters, meaning the Gin handlers and Telegram bot handlers that call usecases. |

Reusable libraries live in `pkg/`, covering JWT, ACME, metrics, scheduler,
events, cache, geoip, i18n, WireGuard keys and Xray helpers. HTTP and Telegram
wiring lives in `transport/`. Everything is constructed once at startup in
`cmd/bootstrap.go` and injected.

## Events and background work

- **Event bus.** Usecases publish domain events such as subscription created,
  subscription expired, and Xray down or recovered. The metrics collector, the
  alert engine and the notification dispatcher subscribe to them.
- **Scheduler.** Periodic jobs covering subscription expiry, usage aggregation,
  certificate renewal, retention cleanup, digests and notifications.
- **Monitor.** Polls Xray and host health, raising events when state changes.
- **Provisioning worker.** Applies account add and remove work against the
  local core from a queue.
- **Alert engine.** Evaluates rules against events and emits system alerts,
  which the dispatcher routes to Telegram, Discord or a webhook.

## What happens when a subscriber fetches their config

1. The client requests `/sub/{link_key}`.
2. The HTTP layer resolves the subscription and checks expiry and traffic, and
   detects the client app from the `User-Agent`.
3. The subscription usecase asks the Xray provider to build per-client configs
   and links from the subscription's inbounds and the server's host data.
4. The response goes back as a base64 feed with usage metadata in the headers.

## Persistence

GORM models from every feature are registered and auto-migrated on startup, and
the boot sequence runs idempotent data backfills alongside them. PostgreSQL
picks up a few GIN indexes that SQLite does not support; otherwise the two
behave the same.

## Related

- [System requirements](/en/guides/nasnet-linux/system-requirements/)
- [Server and Xray](/en/guides/nasnet-linux/server/)
- [Backup](/en/guides/nasnet-linux/backup/)
