NexorPanel

Документація публікується англійською та російською; нижче — англійська версія. Русский

How NexorPanel is built

NexorPanel is a self-contained hosting control panel: its own interface, its own API, its own accounts, its own storage and its own agent on the machine. Nothing is borrowed from another program.

Panel and agent

browser ──HTTPS──► nexor-api (Go) ──── panel.db (SQLite): accounts,
  React/TS SPA          │                sites, log
                        │
                        └──HMAC──► nexor-agent on the machine
                                      │
                                      ├─ site folders, system users
                                      ├─ web server configuration + reload
                                      └─ metrics, services, files, logs

The panel stores what the server should look like. It does not walk the machine, does not know its paths and does not run programs on it. Its data file answers one question: “how should things be”.

The agent brings the machine to that state. It knows its machine, its folders and its services — and nothing about the panel except what the panel sent: a domain name, a mailbox name, an “enabled” flag. A path never arrives in a request; the agent takes its folders from its own configuration, written by the machine's administrator, not by the panel.

The main property follows from this: whoever gets into the panel does not get the machine. They get the right to ask the agent for what the agent was configured to do — and nothing beyond that. The one named exception is the Terminal section, which runs a typed command on the machine; it is off by default and is switched on by a person on the machine, with NEXOR_AGENT_CONSOLE=1 in the agent's settings.

Components

ProgramWhat it does
nexor-apiThe panel: the HTTP API, sessions, accounts, its own storage, and the built interface. One static Go binary under systemd, behind nginx.
nexor-api doctorChecks a live installation using the same environment as the service: data file, agent settings, session key, request origin, trusted proxies. Changes nothing; says what exactly to fix.
nexor-agentThe helper on each managed machine: site folders, system users, web server, PHP pools, certificates, mail, DNS, databases, backups, metrics, services. Accepts only HMAC-signed requests from the panel.
nexor-hookAn optional webhook receiver: a git push becomes a server update.
Web interfaceA React + TypeScript single-page app. It is an ordinary client of the API and has no other path into the panel — so anything the interface can do, the API can do too.

Source layout

FolderContents
apps/apiGo backend: the panel's API, storage, sessions, agent, doctor
apps/webReact + TypeScript frontend: shell, dashboard, lists, sign-in; end-to-end tests with Playwright and axe
deploysystemd units, configuration examples, nginx, the webhook receiver, installation guide
packaging.deb packages of the panel and of the agent: control files, postinst / prerm / postrm
docsarchitecture, API, roadmap, mockups and screenshots

Backend layers:

cmd/nexor-api       entry point, configuration, mode selection, graceful shutdown
cmd/nexor-agent     the agent on the managed machine
cmd/nexor-hook      the webhook receiver
internal/config     configuration from the environment
internal/native     own storage: the data file, accounts, sites
internal/agent      the agent and the panel's client to it
internal/store      what the panel needs from a data source: interface and implementations
internal/session    sessions, HttpOnly / SameSite=Strict cookies
internal/httpapi    routes, middleware, JSON responses and error codes

internal/store is the boundary: everything the panel can do is described there as an interface. Own storage, an external engine and fixtures all plug into the same place, and no screen knows which one is behind it.

On the server

PathWhat
/usr/local/bin/nexor-apithe panel binary, run as the system user nexor
/usr/local/bin/nexor-agentthe agent binary, run as root
/usr/local/share/nexorpanel/webthe built interface (NEXOR_WEB_ROOT)
/etc/nexorpanel/nexor-api.envpanel settings, chmod 600; the package creates it with a generated NEXOR_SESSION_KEY
/etc/nexorpanel/nexor-agent.envagent settings: listen address, shared secret, services, folders
/var/lib/nexorpanel/panel.dball of the panel's own data (NEXOR_DATA)
nexor-api.service, nexor-agent.servicesystemd units

The panel listens on 127.0.0.1:8080 and the agent on 127.0.0.1:8462 by default; nginx or another TLS terminator sits in front of the panel. One panel can manage several machines: each one runs its own agent, and the panel is told about them in NEXOR_AGENTS.

Run modes

The mode is not a separate setting; it follows from the others.

ModeWhenWhat it does
Nativenothing set — the defaultown storage, own agent
External engineNEXOR_ENGINE_URL is setworks through an external execution engine over its API; kept for installations that already run one
FixturesNEXOR_MOCK=1demonstration data; manages nothing, useful to look at the screens

Order of operations

Record first, machine second. If writing the record fails, nothing is left on disk that the panel does not know about. The worst case is a record without a folder: it is visible in the list and can be applied again or deleted.

Deleting goes the other way: first removed from the machine, then forgotten. Deleting a site removes its configuration but does not touch its files — configuration is recreated in a minute, files are not.

Every section is being rewritten onto the panel's own code one by one. Until a section is ready, it answers “not connected yet” rather than an empty list, because an empty list reads as “there is nothing”.

Accounts and roles

  • Roles: administrator (everything, including servers, plans, firewall and logs), reseller (their own clients and their objects) and client (only their own sites, mail, DNS, databases).
  • A client sees only their own objects, and the query guarantees it, not a filter over the response. Someone else's object and a missing one both answer 404.
  • Passwords are bcrypt hashes. A disabled account says “disabled” only to someone who knows the password.
  • Sessions are sealed cookies (AES-256-GCM, HttpOnly, Secure, SameSite=Strict), rechecked on every request: a disabled account, a changed password or a removed role ends the session immediately.
  • Second factor — TOTP codes with recovery codes.
  • Programs use API keys, not passwords: one key per task, read-only if needed, with an expiry, revocable on its own. See API keys.
  • The first administrator is created on the first start. The password comes from NEXOR_ADMIN_PASSWORD or is generated and written to the log once.

Storage

One SQLite file next to the panel: accounts, sites, the panel's log, second-factor bindings, API keys and revoked sessions. There is no separate database server — a panel that needs another program to start is no longer self-contained. A backup is a copy of the file, and on an empty machine with just the binary the panel comes up whole.

The schema is applied on every start, and every step is idempotent, so there are no schema versions to drift apart. Copies of the panel can also be sent to S3-compatible storage (NEXOR_S3_*), encrypted beforehand with NEXOR_BACKUP_KEY.

Configuration

Everything is configured with environment variables in the .env files above. The most important ones:

VariableMeaning
NEXOR_ADDRwhere the panel listens, e.g. 127.0.0.1:8080
NEXOR_PUBLIC_ORIGINthe origin the panel is served from; state-changing requests from anywhere else are refused
NEXOR_SESSION_KEY32 bytes in base64 that seal session cookies; without it every restart signs everyone out
NEXOR_SESSION_TTLsession lifetime, e.g. 8h
NEXOR_DATAthe data file
NEXOR_WEB_ROOTthe built interface; empty if nginx serves it
NEXOR_ENTRANCEsecret entrance: a path segment under which the whole panel lives
NEXOR_TRUSTED_PROXIESproxies whose X-Forwarded-For is believed
NEXOR_AGENTSthe machines and their agents
NEXOR_S3_*, NEXOR_BACKUP_KEYoff-site storage for panel copies and its encryption key
NEXOR_NOTIFY_URL, NEXOR_NOTIFY_TOKENwhere notifications are sent outside the panel
NEXOR_IMAP, NEXOR_SMTPmail servers for webmail
NEXOR_AGENT_LISTEN, NEXOR_AGENT_SECRETagent side: listen address and the shared HMAC secret
NEXOR_AGENT_SERVICESagent side: which systemd services the panel may start, stop and restart
NEXOR_AGENT_CONSOLEagent side: 1 enables the Terminal section

Run nexor-api doctor after changing them.

Security rules

  • State-changing requests are only POST, PUT and DELETE, with an Origin check.
  • Administrative sections are closed by a separate adminOnly layer, independent of storage ownership checks.
  • The agent verifies an HMAC signature with a one-time nonce on every request; the secret is in a root-only file and is never sent anywhere.
  • The agent runs only programs from its own list, with arguments it builds itself. There is no shell anywhere: a semicolon in a name is a semicolon, not a second command.
  • Sign-in is rate-limited: 5 attempts a minute per address and login, plus a limit per address.
  • File paths never leave the site's folder, including through symbolic links. Deleted files go to the site's .nexor-trash for 30 days.
  • A secret entrance (NEXOR_ENTRANCE) hides the sign-in form from scanners: everything else gets an identical empty 404.
  • The service sets its own security headers — content security policy without inline scripts, frame ban, nosniff, Referrer-Policy.
  • The panel never stores database passwords; phpMyAdmin is opened with a half-hour account created for that one visit.

Stack

LayerChoice
BackendGo, one static binary under systemd; HTTP on the standard library, no web framework
StorageSQLite in one file (modernc.org/sqlite, no cgo)
Passwordsbcrypt
FrontendReact 19 + TypeScript + Vite; own components on design tokens, own SVG charts
LanguagesEnglish, Russian, Ukrainian, Belarusian, German, Polish — the API answers in the language of Accept-Language
Checksgofmt, go vet, go test -race; type checks and dictionary completeness; installer tests; end-to-end tests with WCAG 2.1 AA checks in both themes

API reference Завантажити