Nexon manages Xray nodes and issues subscriptions (base64 / plain links / Xray / Clash / Clash.Meta / sing-box). Spiritual successor to Hystron, modelled after PasarGuard / Remnawave / Marzban — driven by a CLI and an interactive TUI, with a subscription server for clients. SQLite (WAL) is the single source of truth; users are generated with a full proxy bundle and selectively pushed to nodes over the Xray gRPC API.
- Users with traffic limits, expiry dates and per-device (HWID) caps; auto-kick on limit
- Node groups — route a subset of users to a subset of nodes (e.g. your users vs a friend's)
- Subscriptions auto-detect the client by User-Agent and return the right format; a browser gets an HTML dashboard with a QR code
- Per-client output format — pin a UA to a format (mihomo → Clash, Happ → links/base64, …)
- Custom templates per format (Clash/Clash.Meta/sing-box/Xray): you own dns/rules/proxy-groups, proxies are injected automatically; edited in
$EDITOR, validated and previewable - Real Xray gRPC connector: add/remove users via
AlterInbound, traffic polling viaStatsService - TUI (Bubble Tea) for interactive management, CLI for scripting
- Embedded SQLite migrations run automatically on startup
curl -fsSL https://raw.githubusercontent.com/BX-Team/Nexon/master/scripts/install.sh -o /tmp/nexon.sh \
&& sudo bash /tmp/nexon.sh installThis downloads the latest static binary to /usr/local/bin/nexon, installs a
nexon.service systemd unit, and creates /etc/nexon/nexon.env. Then:
sudo nano /etc/nexon/nexon.env # set NEXON_SUB_BASE_URL to your public URL
sudo systemctl restart nexon
nexon --helpUpdate or remove:
sudo nexon update # built-in alias: re-runs the installer's update path
sudo bash /tmp/nexon.sh uninstall # keeps data in /var/lib/nexonRequires Go 1.26+.
git clone https://github.com/BX-Team/Nexon.git
cd Nexon
go build -o nexon ./cmd/nexon
./nexon --helpRun directly from the flake, or build a local binary:
nix run github:BX-Team/Nexon -- --help # run without installing
nix build github:BX-Team/Nexon # ./result/bin/nexon
nix develop # dev shell with go, gopls, sqliteThe repo ships a flake.lock, so every build is pinned and reproducible.
Add the flake as an input and import the module. It runs nexon serve as a
hardened systemd unit under a dedicated nexon user, puts the CLI in the
system PATH, and provisions the state directory via StateDirectory:
# flake.nix
{
inputs.nexon.url = "github:BX-Team/Nexon";
outputs = { self, nixpkgs, nexon, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
# ...
modules = [
nexon.nixosModules.nexon
({ ... }: {
services.nexon = {
enable = true;
subBaseURL = "https://vpn.example.com"; # required: public URL for sub links
openFirewall = true; # open the subscription port
};
})
];
};
};
}All module options (services.nexon.*):
| Option | Default | Meaning |
|---|---|---|
enable |
false |
Enable the Nexon service. |
subBaseURL |
— (required) | Public base URL used to build subscription links. |
subListen |
:8080 |
Subscription server listen address. |
dataDir |
/var/lib/nexon |
State directory (holds the SQLite database). Must live under /var/lib. |
envFile |
null |
File with extra NEXON_* vars, loaded via EnvironmentFile. |
openFirewall |
false |
Open the subscription port in the firewall. |
package |
flake default | The nexon package to run. |
Manage users/nodes on the host with the same binary, always as the service user so the SQLite files keep one owner:
sudo -u nexon nexon user add alice --data-limit 100G(Running the CLI as root would leave root-owned WAL files behind and lock the
service out of its own database. If you changed dataDir, also pass
NEXON_DATA_DIR.)
A node is a VPS running xray-core with its gRPC API enabled. The helper script
installs Xray and writes an API inbound:
curl -fsSL https://raw.githubusercontent.com/BX-Team/Nexon/master/scripts/node-setup.sh | sudo API_PORT=8443 bashThen add your real proxy inbounds (vless/trojan/hysteria2…) to
/usr/local/etc/xray/config.json next to the api inbound and register the node:
nexon node add tokyo --address 1.2.3.4 --api-port 8443
nexon node inbound add tokyo --tag vless-reality --protocol vless --port 443 \
--tls reality --settings '{"sni":"example.com","pbk":"<public-key>","sid":"<short-id>"}'
nexon node sync tokyoSecurity: the Xray gRPC API has no authentication. Allow the API port only from the Nexon host (
ufw allow from <NEXON_IP> to any port 8443) or keep it on a private network / tunnel (WireGuard / SSH). Nexon dials the API in plaintext.
nexon user add alice --data-limit 100G --expire 30d --hwid-limit 3
nexon user sub alice # subscription link + QR in the terminal
nexon serve # subscription server :8080 + traffic poller
nexon tui # interactive cockpitClients fetch their config from the link printed by nexon user sub; opening it
in a browser shows an HTML dashboard with a QR code.
| Command | Purpose |
|---|---|
user |
create/list/edit users, show sub link + QR, rotate token, devices |
node |
register nodes, manage inbounds, sync, show status (list/show/inbounds) |
group |
node groups; assign users/nodes to groups, set the default group |
migrate |
import users from other panels (PasarGuard) |
clients |
managed client apps: UA → custom headers + pinned output format |
template |
custom per-format subscription templates (list/show/edit/preview/rm) |
settings / rule |
runtime settings and UA→format detection rules |
serve |
run the subscription server + traffic poller |
tui |
launch the interactive TUI |
update |
update to the latest release (re-runs the installer) |
nexon tui opens an interactive cockpit with tabs: Dashboard (live stats),
Users (create/edit/delete, per-user detail with QR + devices, group cycle),
Nodes, Groups, Clients (UA headers + format), Templates (edit in
$EDITOR, preview) and Settings. Switch tabs with tab/←→, refresh with
r, quit with q.
Each format (clash / clash-meta / singbox / xray) can use a custom Go
text/template stored in the DB. The generated proxies are injected via
{{ .Proxies }} (format-native entries) and {{ .Names }} (their names) — you
own everything else (dns, rules, proxy-groups, tun…). Rich defaults are seeded on
first run.
nexon template edit clash # opens $EDITOR with the current/starter template
nexon template preview clash # render against a sample subscription
nexon template rm clash # revert to the built-in generatorOutput is validated (YAML/JSON) on save and at render time; on any error Nexon falls back to the built-in generator so subscriptions never break.
Nexon can take over a PasarGuard install
without breaking existing subscription links. PasarGuard tokens are not
stored anywhere — they are base64("v3,<user_id>,<timestamp>") signed with the
panel's secret key. The import copies that secret and a user_id mapping, so
Nexon verifies and serves every token PasarGuard ever handed out, regardless of
when it was generated.
# 1. Copy the PasarGuard SQLite database next to nexon (or any readable path).
# 2. Preview what would be imported:
nexon migrate pasarguard db_backup.sqlite --dry-run
# 3. Import. Users keep their name, data limit, used traffic, expiry,
# HWID limit and reset strategy; each gets a fresh native sub token:
nexon migrate pasarguard db_backup.sqlite --group 2Flags:
| Flag | Meaning |
|---|---|
--only a,b / --skip a,b |
import only / all but these usernames |
--reset-traffic |
zero used traffic instead of carrying it over |
--keep-traffic a,b |
exempt these users from --reset-traffic |
--group <id> |
node group for imported users (important — a user in the wrong group gets an empty subscription) |
--dry-run |
print the plan, write nothing |
Usernames that already exist in Nexon are not touched — they only get the
legacy-id mapping, so their old PasarGuard link starts resolving too. Point the
old subscription domain at Nexon's sub server and the switch is invisible:
clients keep fetching their old /sub/<token> URL forever.
Optionally, Happ clients can be moved onto their
native Nexon links: set your provider id from the Happ Proxy Account and legacy responses
will carry providerid + new-url headers, which Happ applies on its next
refresh.
nexon settings set sub.happ.providerid <your-provider-id>Rotating a user's token (nexon user rotate) also severs their legacy
PasarGuard token, so compromise recovery still works.
All via NEXON_* environment variables (see internal/config/config.go):
| Var | Default | Meaning |
|---|---|---|
NEXON_DATA_DIR |
/var/lib/nexon |
state directory |
NEXON_DB |
<data>/nexon.db |
SQLite path |
NEXON_SUB_LISTEN |
:8080 |
subscription server address |
NEXON_SUB_BASE_URL |
http://localhost:8080 |
public base for building sub links |
NEXON_POLL_INTERVAL |
30 |
traffic poll interval (seconds) |
NEXON_NODE_MODE |
— | stub = logging connector (dev without a real node) |
nexon CLI ──┐
nexon TUI ──┼─► core.Service ─► SQLite (source of truth)
sub server ─┘ │
└─► NodeConnector (gRPC) ─► Xray nodes (Handler/Stats)
cmd/nexon/ entrypoint
internal/cli/ cobra commands
internal/tui/ Bubble Tea terminal cockpit
internal/core/ Services (business logic)
internal/store/ SQLite + migrations + queries
internal/node/ NodeConnector (Xray gRPC) + stub
internal/secrets/ per-user proxy generation
internal/subgen/ format generators + templates
internal/subserver/ sub HTTP server + UA detection + HWID
nix/ NixOS module
scripts/ install.sh, node-setup.sh
Licensed under the MIT License.