Added comprehensive support for managing WireGuard interfaces, including CRUD operations and peer management. Updated permissions to include access control for WireGuard routes. Enhanced the UI components to display and interact with WireGuard configurations, improving user experience and functionality. Introduced new tests for WireGuard-related functionalities to ensure reliability.
MikrotikManager-3
Монорепозиторий веб-приложения для управления MikroTik: UI на Next.js, API на Fastify, общие Zod-контракты, сборка Docker-образов через Gitea Actions и автоматическое обновление контейнеров на сервере через sidecar updater.
Содержание
- Состав монорепозитория
- Архитектура
- Локальная разработка
- CI/CD (Gitea Actions)
- Прод-развёртывание Docker
- Сервис updater
- Первичная настройка сервера
- Эксплуатация и сопровождение
- Чеклист деплоя
Состав монорепозитория
| Компонент | Путь | Стек | Порт (runtime) | Docker-образ |
|---|---|---|---|---|
| Frontend | корень (app/, components/, lib/, …) |
Next.js 16.2.4 (App Router), React 19 | 3000 | …-frontend |
| Backend | backend/ |
Fastify 5, TypeScript ESM, SQLite, Drizzle | 8000 | …-backend |
| Контракты | packages/contracts/ |
Zod 4, @mmapp/contracts |
— | встраиваются в frontend/backend |
| Updater | deploy/updater/ |
bash, docker:27-cli, curl, jq |
— | …-updater |
| Деплой | deploy/docker-compose.yml |
Docker Compose | — | — |
| CI | .gitea/workflows/docker.yml |
Gitea Actions, Buildx | — | push в git.shx.one |
Имена образов в registry для репозитория denozord/MikrotikManager-3:
git.shx.one/denozord/mikrotikmanager-backendgit.shx.one/denozord/mikrotikmanager-frontendgit.shx.one/denozord/mikrotikmanager-updater
Для другого owner/repo подставьте имя по правилу CI (см. CI/CD).
Архитектура
Общая схема
flowchart TB
subgraph dev [Локальная разработка]
Browser["Браузер"]
NextDev["Next.js :3000"]
FastifyDev["Fastify :8000"]
SqliteDev["SQLite файл"]
Contracts["@mmapp/contracts"]
Browser --> NextDev
Browser -->|"fetch /api, CORS"| FastifyDev
FastifyDev --> SqliteDev
Contracts --> NextDev
Contracts --> FastifyDev
end
subgraph ci [Gitea Actions]
WF[".gitea/workflows/docker.yml"]
WF --> Reg["git.shx.one registry"]
end
subgraph prod [Прод-сервер]
FE["mmapp-frontend :3000"]
BE["mmapp-backend :8000"]
DBVol["volume backend-data /app/data"]
UPD["mmapp-updater"]
Sock["/var/run/docker.sock"]
Reg --> FE
Reg --> BE
Reg --> UPD
UPD --> Sock
UPD --> FE
UPD --> BE
BE --> DBVol
BrowserProd["Клиент"] --> FE
BrowserProd -->|"live + backendUrl"| BE
end
Frontend и backend
- Браузер загружает UI с порта 3000.
- Запросы к API выполняются напрямую с клиента (
fetch) на URL бэкенда. Next.js rewrites/proxy не используются (next.config.ts— толькоoutput: "standalone"). - URL бэкенда и режим данных (
mock/live) хранятся вlocalStorage(routerlists:data-source,routerlists:backend-url); по умолчаниюhttp://localhost:8000. Настройка — страница Настройки в UI (app/(main)/settings/page.tsx, провайдерlib/data-source.tsx). - Backend слушает 8000, отдаёт
GET /healthи маршруты под/api/…. CORS разрешён для одного origin — переменнаяCORS_ORIGIN(backend/src/config.ts,backend/src/index.ts). - Отдельно от UI-режима:
NEXT_PUBLIC_API_MODE(mockпо умолчанию,liveдля RouterOS REST) —lib/api-mock.ts; для работы с API приложения через бэкенд в проде достаточно режима live в UI и корректногоbackendUrl.
Общие контракты (packages/contracts)
- npm workspaces: корень +
packages/*+backend(package.json). - Пакет
@mmapp/contracts: сборкаtsc→dist/, exports./servers,./events,./alerts. - При
npm installвыполняетсяpostinstall→npm run build -w @mmapp/contracts. - Backend: валидация и типы в маршрутах; frontend: типы и разбор ответов в
shared/api/.
Поток образов: CI → registry → сервер
sequenceDiagram
participant Gitea
participant Actions
participant Registry
participant Updater
participant Backend
participant Frontend
Gitea->>Actions: push main / workflow_dispatch
Actions->>Registry: push backend/frontend/updater :latest и :sha
Actions-->>Gitea: optional DEPLOY_WEBHOOK_URL POST
loop каждые POLL_INTERVAL_SECONDS
Updater->>Registry: imagetools inspect digest
alt digest изменился
Updater->>Registry: docker pull
Updater->>Backend: stop/rm/run из snapshot
Updater->>Frontend: stop/rm/run из snapshot
Updater->>Backend: HTTP health
Updater->>Frontend: HTTP health
end
end
- Триггер workflow:
pushв веткуmain, ручнойworkflow_dispatch. - Параллельные jobs:
backend-image,frontend-image,updater-image; опциональноnotify-webhookпосле backend и frontend, если задан секретDEPLOY_WEBHOOK_URL. - Теги на каждый успешный push:
:latestи:<commit-sha>; платформа linux/amd64. - На сервере образы подтягиваются вручную (
docker compose pull) и/или через updater (сравнение digest у тега изtargets.json). Webhook CI не заменяет updater.
Зависимости в проде
| Зависимость | Реализация |
|---|---|
| SQLite | Не отдельный контейнер. Файл mikrotik.db в томе backend-data → /app/data (DATABASE_PATH=/app/data/mikrotik.db в образе backend). |
| Docker socket | Только у контейнера updater: /var/run/docker.sock — доступ к Docker API хоста (управление контейнерами, pull). |
Локальная разработка
Требования
- Node.js 22 (как в
Dockerfile.frontendиbackend/Dockerfile). - npm с workspaces; установка из корня:
npm ciилиnpm install. - Для нативной сборки
better-sqlite3на Linux может понадобиться toolchain (python3,make,g++); в Docker-образе backend они уже ставятся. - Backend Docker-образ ставит только workspaces
backend+contracts(без корневых Next/React deps); в production логи — JSON безpino-pretty.
Запуск
Два процесса (frontend и backend):
npm install
npm run dev
npm --prefix backend run dev
| Сервис | URL | Проверка |
|---|---|---|
| Frontend | http://localhost:3000 | открыть в браузере |
| Backend | http://localhost:8000 | curl -fsS http://localhost:8000/health |
Переменные окружения (разработка)
Backend — скопировать backend/.env.example в backend/.env:
| Переменная | По умолчанию | Назначение |
|---|---|---|
DATABASE_PATH |
./mikrotik.db |
путь к файлу SQLite |
PORT |
8000 |
порт Fastify |
CORS_ORIGIN |
http://localhost:3000 |
origin фронтенда для CORS |
Frontend — в репозитории нет корневого .env.example. Опционально .env.local:
| Переменная | По умолчанию | Назначение |
|---|---|---|
NEXT_PUBLIC_API_MODE |
mock |
live — вызовы RouterOS REST через lib/api-mock.ts |
Режим live для API приложения и URL бэкенда задаются в UI (не через build-time env).
Контракты и БД в dev
npm run build -w @mmapp/contracts
После изменения схем Drizzle:
npm --prefix backend run db:generate
npm --prefix backend run db:migrate
npm --prefix backend run db:studio
CI/CD (Gitea Actions)
Файл: .gitea/workflows/docker.yml (имя workflow: Docker images).
| Job | Build context | Dockerfile | Имя образа |
|---|---|---|---|
backend-image |
.ci/docker/backend (staging в CI) |
backend/Dockerfile |
git.shx.one/<owner>/<stem>-backend |
frontend-image |
.ci/docker/frontend |
Dockerfile.frontend |
git.shx.one/<owner>/<stem>-frontend |
updater-image |
deploy/updater |
deploy/updater/Dockerfile |
git.shx.one/<owner>/<stem>-updater |
<owner>— первая частьgitea.repository, lower case.<stem>— имя репозитория lower case без суффикса-<цифры>в конце (напримерMikrotikManager-3→mikrotikmanager).
Логин в registry в CI: gitea.actor + секрет ACTIONS_PAT.
Кэш Buildx: type=gha, отдельные scope backend, frontend, updater.
Опциональный job notify-webhook: POST JSON {"repository","sha","ref","version","tag","releaseUrl"} на URL из секрета DEPLOY_WEBHOOK_URL после успешной сборки backend и frontend и публикации релиза (updater в needs не входит).
Версионирование и релизы
- Базовая версия:
v1.0.0. Линия semver:1.x.y(major2.xвне scope). - Job
prepare-releaseзапускает.ci/scripts/compute-release.mjs: коммиты с последнего тегаv*.*.*, bump по Conventional Commits. feat/feat!/BREAKING CHANGE→ minor (1.x.0);fix,chore,docs,refactor,style,test,build,ci→ patch (1.0.x).- Если после последнего тега нет новых коммитов, релиз пропускается; Docker-образы при этом всё равно собираются и публикуются с
:latestи:<sha>. - Первый релиз без тега
v1.0.0возможен автоматически: CI берёт историюHEAD, считает bump от1.0.0и создаёт тег (напримерv1.1.0приfeat:). - Job
publish-release: annotated tagv1.2.3, Gitea Release наhttps://git.shx.one(markdown notes), образы с тегами:latest,:<sha>,:<semver>. - UI: версия в sidebar и страница
/releases; manifestpublic/release-manifest.json(в CI подставляется из артефакта). - Bootstrap: один раз выставить
1.0.0в workspacepackage.jsonи создать тегv1.0.0наmainперед первым автоматическим bump. - Сообщения коммитов: см.
.cursor/rules/release-versioning.mdc; subject и body — на русском, префикс Conventional Commits — на английском.
Прод-развёртывание Docker
Эталон без reverse-proxy: deploy/docker-compose.yml (порты 3000 / 8000 на хост).
Стек с Traefik + HTTPS (Let's Encrypt DNS-01 / Cloudflare), по аналогии с CDNManager: deploy/docker-compose.traefik.yml + deploy/env.traefik.example. На сервере публикуются только :80/:443; frontend получает HTTPS, /api и /health проксируются на backend внутри сети mmapp. Домен по умолчанию: mm.shnt.top.
CDN Manager + MikrotikManager (один Traefik)
Полный стек: Traefik + cdn.shnt.top + mm.shnt.top в одном Compose.
| Файл | Назначение |
|---|---|
deploy/docker-compose.cdn-mm.yml |
Traefik + CDN Manager + MM backend/frontend/updater |
deploy/env.cdn-mm.example |
общий .env |
mkdir -p /opt/cdn-mm/{data/cdn,data/mm,state,updater}
cp deploy/docker-compose.cdn-mm.yml /opt/cdn-mm/docker-compose.yml
cp deploy/env.cdn-mm.example /opt/cdn-mm/.env
cp deploy/updater/targets.json.example /opt/cdn-mm/updater/targets.json
# заполнить CF_DNS_API_TOKEN, CLOUDFLARE_API_TOKEN, AUTH_JWT_SECRET, CORS_ORIGIN, …
docker login git.shx.one
cd /opt/cdn-mm && docker compose pull && docker compose up -d
curl -fsS https://cdn.shnt.top/health
curl -fsS https://mm.shnt.top/health
Данные: ./data/cdn (CDN), ./data/mm (MM). Сеть Traefik: edge. Не запускайте параллельно standalone docker-compose.traefik.yml CDNManager или MM на тех же 80/443.
Если на сервере уже крутится CDNManager Traefik (cdnmanager-traefik, сеть cdnmanager) — не поднимайте второй Traefik. Варианты:
| Способ | Файл |
|---|---|
| Compose без своего Traefik | deploy/docker-compose.traefik-cdn.yml |
Plain docker CLI (скрипт) |
deploy/run-beside-cdn-traefik.sh |
Frontend вешается в сеть cdnmanager с Traefik-labels; backend/updater остаются в mmapp. Сертификат для MM_DOMAIN выпускает уже работающий Traefik CDNManager (тот же letsencrypt / Cloudflare DNS-01).
# Compose (рекомендуется)
mkdir -p /opt/mmapp/{data,state,updater}
cp deploy/docker-compose.traefik-cdn.yml /opt/mmapp/docker-compose.yml
cp deploy/env.traefik.example /opt/mmapp/.env # MM_DOMAIN + CORS_ORIGIN
cp deploy/updater/targets.json.example /opt/mmapp/updater/targets.json
cd /opt/mmapp && docker compose pull && docker compose up -d
# Или одной CLI-командой (скрипт сам сделает network/pull/run/connect):
curl -fsSL -o /tmp/run-beside-cdn-traefik.sh \
https://git.shx.one/denozord/MikrotikManager/raw/branch/main/deploy/run-beside-cdn-traefik.sh
chmod +x /tmp/run-beside-cdn-traefik.sh
sudo MM_DOMAIN=mm.shnt.top CORS_ORIGIN=https://mm.shnt.top /tmp/run-beside-cdn-traefik.sh
DNS: A/AAAA для mm.shnt.top → IP VPS, Cloudflare DNS only. Проверка: curl -fsS https://mm.shnt.top/health.
SSO auth-portal: docs/integrate-auth-portal.md (app id mm).
Рабочий каталог для команд compose — deploy/ (или -f deploy/docker-compose.yml / -f deploy/docker-compose.traefik.yml / -f deploy/docker-compose.traefik-cdn.yml / -f deploy/docker-compose.cdn-mm.yml из корня).
Прод-контейнеры
| Сервис | container_name |
Образ (пример) | Порты host:container | Тома | restart |
|---|---|---|---|---|---|
| backend | mmapp-backend |
git.shx.one/denozord/mikrotikmanager-backend:latest |
8000:8000 |
backend-data → /app/data |
unless-stopped |
| frontend | mmapp-frontend |
git.shx.one/denozord/mikrotikmanager-frontend:latest |
3000:3000 |
— | unless-stopped |
| updater | mmapp-updater |
git.shx.one/denozord/mikrotikmanager-updater:latest |
не публикуются | docker.sock, updater-state → /state, targets.json → /etc/updater/targets.json:ro |
unless-stopped |
Метки для updater на backend и frontend:
| Метка | Пример значения |
|---|---|
mmapp.updater.managed |
true |
mmapp.updater.target |
backend / frontend |
mmapp.updater.image |
полное имя образа с тегом |
Явной пользовательской сети в compose нет — используется сеть проекта Compose по умолчанию.
Переменные окружения (прод)
Backend (в образе заданы NODE_ENV=production, PORT=8000, DATABASE_PATH=/app/data/mikrotik.db; в compose обычно переопределяют только CORS):
| Переменная | Источник в compose | Назначение |
|---|---|---|
CORS_ORIGIN |
${CORS_ORIGIN:-http://localhost:3000} |
origin UI, с которого браузер вызывает API |
Frontend (в образе: PORT=3000, HOSTNAME=0.0.0.0). URL API в образ не зашит — задаётся в браузере (режим live + URL бэкенда) вместе с CORS_ORIGIN на backend.
Updater:
| Переменная | По умолчанию | Назначение |
|---|---|---|
TARGETS_FILE |
/etc/updater/targets.json |
список целей |
STATE_FILE |
/state/updater-state.json |
digest и ошибки |
POLL_INTERVAL_SECONDS |
300 |
пауза между циклами опроса |
HEALTH_TIMEOUT_SECONDS |
120 |
ожидание HTTP health |
STOP_TIMEOUT_SECONDS |
30 |
docker stop -t |
REGISTRY |
git.shx.one |
registry для docker login |
REGISTRY_USERNAME |
пусто | логин (если заданы оба с паролем) |
REGISTRY_PASSWORD |
пусто | пароль registry |
Docker Compose
docker login git.shx.one
export CORS_ORIGIN=http://<хост>:3000
export REGISTRY_USERNAME=<user>
export REGISTRY_PASSWORD=<token>
Скопировать и отредактировать конфиг updater (health URL должны быть достижимы из контейнера updater):
cp deploy/updater/targets.json.example deploy/updater/targets.json
В deploy/docker-compose.yml для updater заменить bind-mount на рабочий файл:
- ./updater/targets.json:/etc/updater/targets.json:ro
Запуск:
cd deploy
docker compose pull
docker compose up -d
Эквивалентные docker run
Имена томов можно согласовать с compose (backend-data, updater-state) или задать явно.
Backend:
docker volume create backend-data
docker run -d \
--name mmapp-backend \
--restart unless-stopped \
-p 8000:8000 \
-e CORS_ORIGIN=http://localhost:3000 \
-v backend-data:/app/data \
--label mmapp.updater.managed=true \
--label mmapp.updater.target=backend \
--label mmapp.updater.image=git.shx.one/denozord/mikrotikmanager-backend:latest \
git.shx.one/denozord/mikrotikmanager-backend:latest
Frontend:
docker run -d \
--name mmapp-frontend \
--restart unless-stopped \
-p 3000:3000 \
--label mmapp.updater.managed=true \
--label mmapp.updater.target=frontend \
--label mmapp.updater.image=git.shx.one/denozord/mikrotikmanager-frontend:latest \
git.shx.one/denozord/mikrotikmanager-frontend:latest
Updater:
docker volume create updater-state
docker run -d \
--name mmapp-updater \
--restart unless-stopped \
-e REGISTRY=git.shx.one \
-e REGISTRY_USERNAME=<user> \
-e REGISTRY_PASSWORD=<token> \
-e POLL_INTERVAL_SECONDS=300 \
-e HEALTH_TIMEOUT_SECONDS=120 \
-e STOP_TIMEOUT_SECONDS=30 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v updater-state:/state \
-v /path/to/targets.json:/etc/updater/targets.json:ro \
git.shx.one/denozord/mikrotikmanager-updater:latest
Проверка после деплоя
| Проверка | Команда / ожидание |
|---|---|
| Backend health | curl -fsS http://127.0.0.1:8000/health → JSON со status |
| Frontend | curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/ → 200 |
| Контейнеры | docker ps --filter name=mmapp- |
| UI | режим live, URL бэкенда совпадает с доступом клиента и с CORS_ORIGIN |
Сервис updater
Исходники: deploy/updater/entrypoint.sh, образ deploy/updater/Dockerfile.
Назначение
Sidecar на хосте с Docker: периодически сравнивает digest манифеста образа в registry с последним применённым digest, при изменении выполняет docker pull, пересоздаёт целевой контейнер и проверяет HTTP health. Состояние — файл STATE_FILE (том updater-state).
Обнаружение новых образов
docker buildx imagetools inspect <image>→ digest манифеста (тег вtargets.json, обычно:latest).- Сравнение с
last_applied_digestв state; при совпадении — noop. - При первом запуске — baseline из digest работающего контейнера или remote, чтобы не пересоздавать без смены digest.
- Если remote digest недоступен — running контейнер не останавливается, в state пишется ошибка.
Перезапуск backend/frontend
Только контейнеры с метками mmapp.updater.managed=true, совпадающими mmapp.updater.target и mmapp.updater.image с записью в targets.json.
Последовательность: docker inspect (snapshot) → docker pull → docker stop → docker rm → docker run -d с восстановлением env, mounts, -p, --network, labels, restart policy из snapshot.
При неуспешном health после обновления — откат на previous_digest (image@digest). Если предыдущего digest нет — откат пропускается (лог rollback_skipped).
Конфигурация целей
Пример: deploy/updater/targets.json.example. Поля цели: id, container_name, image, health.type, health.url, health.expect_status.
Важно: в примере health URL — http://127.0.0.1:8000/health и http://127.0.0.1:3000/. В default bridge-сети контейнера updater 127.0.0.1 указывает на сам updater, а не на backend/frontend. Перед продом задайте URL, достижимые из контейнера updater (например опубликованные порты хоста при network_mode: host у updater, host.docker.internal с extra_hosts, или иной согласованный с вашей сетью вариант). Скопируйте example в deploy/updater/targets.json и отредактируйте.
Docker socket и безопасность
- Монтирование
/var/run/docker.sockдаёт updater права на Docker API хоста: остановка, удаление и создание контейнеров, pull от имени хоста. - Ограничение по меткам снижает риск случайного пересоздания чужих контейнеров, но не изолирует updater от остального Docker на хосте.
- Учётные данные registry передавайте через env, не коммитьте в git.
- Блокировка цели:
/state/locks/<target_id>; при занятом lock цикл для цели пропускается. - При ошибке цикла пауза удваивается (
2 × POLL_INTERVAL_SECONDS) с джиттером до +10%.
Валидация и staging
bash deploy/updater/validate.sh
Чеклист сценариев на staging: deploy/updater/test-staging.sh.
Первичная настройка сервера
Пошагово на чистом Linux-хосте с доступом в интернет и к git.shx.one.
- Установить Docker Engine и плагин Compose (официальная документация Docker для вашего дистрибутива).
- Проверить:
docker version
docker compose version
- Открыть входящие порты 3000 (frontend) и 8000 (backend) в firewall или настроить внешний доступ согласно вашей схеме (reverse proxy в репозитории не описан).
- Войти в registry:
docker login git.shx.one
- Получить файлы деплоя: клонировать репозиторий или скопировать каталог
deploy/и подготовитьdeploy/updater/targets.json. - Задать переменные окружения для compose (
CORS_ORIGIN,REGISTRY_USERNAME,REGISTRY_PASSWORDпри приватных образах). - Подтянуть образы и запустить стек:
cd deploy
docker compose pull
docker compose up -d
- Проверить health (см. таблицу выше) и логи updater:
docker logs mmapp-updater --tail 50
- В UI включить режим live и указать URL бэкенда, доступный браузеру пользователя; убедиться, что значение совпадает с
CORS_ORIGINна backend.
Эксплуатация и сопровождение
Автообновление
Updater опрашивает registry с интервалом POLL_INTERVAL_SECONDS (по умолчанию 300 с). Обновление привязано к смене digest у образа из targets.json (часто тег :latest после push в main).
Ручное обновление
docker pull git.shx.one/denozord/mikrotikmanager-backend:latest
docker pull git.shx.one/denozord/mikrotikmanager-frontend:latest
Через compose (пересоздание при смене образа):
cd deploy
docker compose pull
docker compose up -d --force-recreate
Пинning версии: тег :<commit-sha> из CI вместо :latest в image, метке mmapp.updater.image и в targets.json.
Откат
- Автоматически: updater при failed health после обновления — контейнер на
previous_digest. - Вручную: остановить контейнер, запустить образ с нужным тегом или digest из registry, сохранив те же volume и labels. Откат схемы SQLite updater не выполняет — нужен отдельный backup тома
backend-data/ файлаmikrotik.db.
Резервное копирование SQLite
Том backend-data (или mmapp-backend-data при явном docker volume create). Пример остановки backend для консистентной копии:
docker stop mmapp-backend
docker run --rm -v backend-data:/data -v $(pwd):/backup alpine tar czf /backup/mikrotik-db-backup.tar.gz -C /data .
docker start mmapp-backend
Отладка
| Действие | Команда |
|---|---|
| Логи сервиса | docker logs mmapp-backend, docker logs mmapp-frontend, docker logs mmapp-updater |
| Следить в реальном времени | docker logs -f mmapp-updater |
| Конфигурация контейнера | docker inspect mmapp-backend |
| Состояние updater | том updater-state, файл /state/updater-state.json внутри контейнера |
Чеклист деплоя
- Установить Docker Engine и Compose plugin; проверить
docker versionиdocker compose version. - Открыть порты 3000 и 8000 (или обеспечить доступ клиентов к UI и API).
- Выполнить
docker login git.shx.one. - Склонировать репозиторий или скопировать
deploy/. - Задать
CORS_ORIGIN(origin фронтенда для браузера). - Задать
REGISTRY_USERNAMEиREGISTRY_PASSWORDдля updater при приватном registry. - Создать
deploy/updater/targets.jsonиз example; настроить health URL для сети хоста. - Обновить bind-mount targets в
deploy/docker-compose.ymlна./updater/targets.json. - Выполнить
docker compose pullиdocker compose up -dв каталогеdeploy. - Проверить
curlна:8000/healthи:3000/. - В UI: режим live и URL бэкенда; сверить с
CORS_ORIGIN. - Просмотреть
docker logs mmapp-updater; настроить регулярный backup томаbackend-data.