Как устроена NexorPanel
NexorPanel — самостоятельная панель управления хостингом: свой интерфейс, свой API, свои учётные записи, своё хранилище и свой агент на машине. Ничего из этого не одалживается у другой программы.
Панель и агент
браузер ──HTTPS──► nexor-api (Go) ──── panel.db (SQLite): учётные записи,
React/TS SPA │ сайты, журнал
│
└──HMAC──► nexor-agent на машине
│
├─ каталоги сайтов, системные пользователи
├─ конфигурация веб-сервера + перезагрузка
└─ метрики, службы, файлы, логи
Панель хранит, каким должен быть сервер. Она не ходит по машине, не знает её путей и не запускает на ней программ. Её файл — это ответ на вопрос «как должно быть».
Агент приводит машину к этому состоянию. Он знает свою машину, свои каталоги и свои службы — и ничего не знает о панели, кроме того, что та прислала: имя домена, имя ящика, признак «включено». Путь в запросе не приходит никогда; каталоги агент берёт из своей конфигурации, которую пишет администратор машины, а не панель.
Отсюда главное свойство: тот, кто получил доступ к панели, не получает машину. Он получает право просить агента о том, на что агента настроили, — и ни о чём сверх того. Единственное названное исключение — раздел «Терминал», который выполняет на машине набранную команду; он выключен по умолчанию и включается человеком на машине строкой NEXOR_AGENT_CONSOLE=1 в настройках агента.
Компоненты
| Программа | Что делает |
|---|---|
nexor-api | Панель: HTTP API, сессии, учётные записи, своё хранилище и собранный интерфейс. Один статический бинарник на Go под systemd, за nginx. |
nexor-api doctor | Проверяет живую установку с тем же окружением, что и служба: файл данных, настройку агента, ключ сессий, источник запросов, доверенные прокси. Ничего не меняет и пишет, что именно поправить. |
nexor-agent | Помощник на каждой управляемой машине: каталоги сайтов, системные пользователи, веб-сервер, пулы PHP, сертификаты, почта, DNS, базы, копии, метрики, службы. Принимает только запросы панели, подписанные HMAC. |
nexor-hook | Необязательный приёмник вебхука: push в git превращается в обновление сервера. |
| Веб-интерфейс | Одностраничное приложение на React + TypeScript. Это обычный клиент API, и другого пути в панель у него нет — поэтому всё, что умеет интерфейс, умеет и API. |
Исходный код
| Каталог | Что внутри |
|---|---|
apps/api | backend на Go: API панели, хранилище, сессии, агент, doctor |
apps/web | frontend на React + TypeScript: оболочка, дашборд, списки, вход; сквозные тесты на Playwright и axe |
deploy | systemd-юниты, примеры конфигурации, nginx, приёмник вебхука, инструкция по установке |
packaging | .deb-пакеты панели и агента: control, postinst / prerm / postrm |
docs | архитектура, API, дорожная карта, макеты и скриншоты |
Слои backend:
cmd/nexor-api точка входа, конфигурация, выбор режима, graceful shutdown cmd/nexor-agent агент на управляемой машине cmd/nexor-hook приёмник вебхука internal/config конфигурация из окружения internal/native собственное хранилище: файл, учётные записи, сайты internal/agent агент и клиент панели к нему internal/store что панели нужно от источника данных: интерфейс и реализации internal/session сессии, cookie HttpOnly / SameSite=Strict internal/httpapi маршруты, middleware, JSON-ответы и коды ошибок
internal/store — это граница: всё, что умеет панель, описано там интерфейсом. Собственное хранилище, внешний движок и фикстуры встают на одно и то же место, и ни один экран не знает, что за ним.
На сервере
| Путь | Что |
|---|---|
/usr/local/bin/nexor-api | бинарник панели, работает от системного пользователя nexor |
/usr/local/bin/nexor-agent | бинарник агента, работает от root |
/usr/local/share/nexorpanel/web | собранный интерфейс (NEXOR_WEB_ROOT) |
/etc/nexorpanel/nexor-api.env | настройки панели, chmod 600; пакет создаёт его со сгенерированным NEXOR_SESSION_KEY |
/etc/nexorpanel/nexor-agent.env | настройки агента: адрес, общий секрет, службы, каталоги |
/var/lib/nexorpanel/panel.db | все собственные данные панели (NEXOR_DATA) |
nexor-api.service, nexor-agent.service | systemd-юниты |
По умолчанию панель слушает 127.0.0.1:8080, агент — 127.0.0.1:8462; перед панелью стоит nginx или другой терминатор TLS. Одна панель может управлять несколькими машинами: на каждой свой агент, а панели они перечисляются в NEXOR_AGENTS.
Режимы запуска
Режим не задаётся отдельной настройкой, а выводится из остальных.
| Режим | Когда | Что делает |
|---|---|---|
| Собственный | ничего не задано — умолчание | своё хранилище, свой агент |
| Внешний движок | задан NEXOR_ENGINE_URL | работает через внешний исполнительный движок по его API; оставлен для установок, где он уже стоит |
| Фикстуры | NEXOR_MOCK=1 | демонстрационные данные; ничем не управляет, годится посмотреть экраны |
Порядок действий
Сначала запись, потом машина. Если запись не удалась, на диске не остаётся ничего, о чём панель не знает. Худший случай — запись без каталога: она видна в списке, её можно применить заново или удалить.
При удалении порядок обратный: сначала снять с машины, потом забыть. Удаление сайта снимает конфигурацию, но не трогает его файлы — конфигурация делается заново за минуту, файлы — неоткуда.
Разделы переписываются на собственный код по одному. Пока раздел не готов, он отвечает «ещё не подключён», а не пустым списком: пустой список читается как «ничего нет».
Учётные записи и роли
- Роли: администратор (всё, включая серверы, тарифы, межсетевой экран и журналы), реселлер (свои клиенты и их объекты) и клиент (только свои сайты, почта, DNS, базы).
- Клиент видит только свои объекты, и это обеспечивает запрос, а не фильтр поверх ответа. Чужой объект и несуществующий одинаково отвечают
404. - Пароли хранятся bcrypt-хешем. Заблокированная запись сообщает «заблокирована» только тому, кто знает пароль.
- Сессии — запечатанные cookie (AES-256-GCM,
HttpOnly,Secure,SameSite=Strict), перепроверяются на каждом запросе: блокировка, смена пароля или снятая роль заканчивают сессию сразу. - Второй фактор — коды TOTP и резервные коды.
- Программы входят ключами API, а не паролем: ключ на одну задачу, при необходимости только на чтение, со сроком, отзывается отдельно. См. ключи API.
- Первый администратор заводится при первом запуске. Пароль берётся из
NEXOR_ADMIN_PASSWORDили придумывается и один раз пишется в журнал.
Хранилище
Один файл SQLite рядом с панелью: учётные записи, сайты, журнал панели, привязки второго фактора, ключи API и отозванные сессии. Отдельного сервера баз данных нет — панель, которой для запуска нужна ещё одна программа, уже не самостоятельная. Резервная копия — это копия файла; на пустой машине, где есть только бинарник, панель поднимается целиком.
Схема применяется на каждом запуске, каждый шаг идемпотентен, поэтому версиям схемы нечему расходиться. Копии панели можно отправлять и в S3-совместимое хранилище (NEXOR_S3_*), предварительно шифруя ключом NEXOR_BACKUP_KEY.
Настройки
Всё настраивается переменными окружения в файлах .env, перечисленных выше. Главные из них:
| Переменная | Значение |
|---|---|
NEXOR_ADDR | где слушает панель, например 127.0.0.1:8080 |
NEXOR_PUBLIC_ORIGIN | источник, с которого открывается панель; изменяющие запросы с любого другого отклоняются |
NEXOR_SESSION_KEY | 32 байта в base64 для cookie сессий; без него каждый перезапуск разлогинивает всех |
NEXOR_SESSION_TTL | срок сессии, например 8h |
NEXOR_DATA | файл данных |
NEXOR_WEB_ROOT | собранный интерфейс; пусто, если его отдаёт nginx |
NEXOR_ENTRANCE | тайный вход: строка в адресе, под которой живёт вся панель |
NEXOR_TRUSTED_PROXIES | прокси, чьему X-Forwarded-For можно верить |
NEXOR_AGENTS | машины и их агенты |
NEXOR_S3_*, NEXOR_BACKUP_KEY | внешнее хранилище для копий панели и ключ их шифрования |
NEXOR_NOTIFY_URL, NEXOR_NOTIFY_TOKEN | куда отправлять уведомления за пределы панели |
NEXOR_IMAP, NEXOR_SMTP | почтовые серверы для почты в браузере |
NEXOR_AGENT_LISTEN, NEXOR_AGENT_SECRET | сторона агента: адрес и общий секрет HMAC |
NEXOR_AGENT_SERVICES | сторона агента: какие службы systemd панель может запускать, останавливать и перезапускать |
NEXOR_AGENT_CONSOLE | сторона агента: 1 включает раздел «Терминал» |
После изменений выполните nexor-api doctor.
Правила безопасности
- Изменяющие запросы — только
POST,PUTиDELETE, с проверкойOrigin. - Административные разделы закрыты отдельным слоем
adminOnly, независимо от проверок принадлежности в хранилище. - Агент проверяет на каждом запросе подпись HMAC с одноразовым номером; секрет лежит в файле, который читает только root, и никуда не отправляется.
- Агент запускает только программы из своего списка и с аргументами, которые составил сам. Оболочки нет нигде: точка с запятой в имени — это точка с запятой, а не вторая команда.
- Попытки входа ограничены: 5 в минуту на связку адрес + логин и отдельный лимит на адрес.
- Путь к файлу не выходит за пределы папки сайта, в том числе через символические ссылки. Удалённые файлы 30 дней лежат в корзине сайта
.nexor-trash. - Тайный вход (
NEXOR_ENTRANCE) прячет форму входа от сканеров: всё остальное получает одинаковую пустую 404. - Заголовки защиты ставит сама служба — политика содержимого без вставных скриптов, запрет рамки,
nosniff,Referrer-Policy. - Пароли баз данных панель не хранит; phpMyAdmin открывается получасовой учётной записью, заведённой под один вход.
Стек
| Слой | Выбор |
|---|---|
| Backend | Go, один статический бинарник под systemd; HTTP на стандартной библиотеке, без веб-фреймворка |
| Хранилище | SQLite одним файлом (modernc.org/sqlite, без cgo) |
| Пароли | bcrypt |
| Frontend | React 19 + TypeScript + Vite; свои компоненты на дизайн-токенах, свои SVG-графики |
| Языки | русский, английский, украинский, белорусский, немецкий, польский — API отвечает на языке из Accept-Language |
| Проверки | gofmt, go vet, go test -race; проверка типов и полноты словарей; тесты установщика; сквозные тесты с проверкой WCAG 2.1 AA в обеих схемах |