Denozordec 15ad53af1f
Docker images / prepare-release (push) Successful in 8s
Docker images / backend-image (push) Successful in 1m39s
Docker images / frontend-image (push) Successful in 2m56s
Docker images / notify-webhook (push) Skipped
Docker images / updater-image (push) Successful in 53s
Docker images / publish-release (push) Successful in 10s
feat(wireguard): implement WireGuard interface management and permissions
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.
2026-09-05 02:10:55 +07:00
2026-05-03 11:16:07 +07:00
2026-05-02 01:17:08 +07:00

MikrotikManager-3

Монорепозиторий веб-приложения для управления MikroTik: UI на Next.js, API на Fastify, общие Zod-контракты, сборка Docker-образов через Gitea Actions и автоматическое обновление контейнеров на сервере через sidecar updater.

Содержание

  1. Состав монорепозитория
  2. Архитектура
  3. Локальная разработка
  4. CI/CD (Gitea Actions)
  5. Прод-развёртывание Docker
  6. Сервис updater
  7. Первичная настройка сервера
  8. Эксплуатация и сопровождение
  9. Чеклист деплоя

Состав монорепозитория

Компонент Путь Стек Порт (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-backend
  • git.shx.one/denozord/mikrotikmanager-frontend
  • git.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: сборка tscdist/, exports ./servers, ./events, ./alerts.
  • При npm install выполняется postinstallnpm 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-3mikrotikmanager).

Логин в 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 (major 2.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 tag v1.2.3, Gitea Release на https://git.shx.one (markdown notes), образы с тегами :latest, :<sha>, :<semver>.
  • UI: версия в sidebar и страница /releases; manifest public/release-manifest.json (в CI подставляется из артефакта).
  • Bootstrap: один раз выставить 1.0.0 в workspace package.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 pulldocker stopdocker rmdocker 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.

  1. Установить Docker Engine и плагин Compose (официальная документация Docker для вашего дистрибутива).
  2. Проверить:
docker version
docker compose version
  1. Открыть входящие порты 3000 (frontend) и 8000 (backend) в firewall или настроить внешний доступ согласно вашей схеме (reverse proxy в репозитории не описан).
  2. Войти в registry:
docker login git.shx.one
  1. Получить файлы деплоя: клонировать репозиторий или скопировать каталог deploy/ и подготовить deploy/updater/targets.json.
  2. Задать переменные окружения для compose (CORS_ORIGIN, REGISTRY_USERNAME, REGISTRY_PASSWORD при приватных образах).
  3. Подтянуть образы и запустить стек:
cd deploy
docker compose pull
docker compose up -d
  1. Проверить health (см. таблицу выше) и логи updater:
docker logs mmapp-updater --tail 50
  1. В 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 внутри контейнера

Чеклист деплоя

  1. Установить Docker Engine и Compose plugin; проверить docker version и docker compose version.
  2. Открыть порты 3000 и 8000 (или обеспечить доступ клиентов к UI и API).
  3. Выполнить docker login git.shx.one.
  4. Склонировать репозиторий или скопировать deploy/.
  5. Задать CORS_ORIGIN (origin фронтенда для браузера).
  6. Задать REGISTRY_USERNAME и REGISTRY_PASSWORD для updater при приватном registry.
  7. Создать deploy/updater/targets.json из example; настроить health URL для сети хоста.
  8. Обновить bind-mount targets в deploy/docker-compose.yml на ./updater/targets.json.
  9. Выполнить docker compose pull и docker compose up -d в каталоге deploy.
  10. Проверить curl на :8000/health и :3000/.
  11. В UI: режим live и URL бэкенда; сверить с CORS_ORIGIN.
  12. Просмотреть docker logs mmapp-updater; настроить регулярный backup тома backend-data.
S
Description
No description provided
Readme
2.5 MiB
1.7.0
Latest
2026-09-05 02:16:55 +07:00
Languages
TypeScript 98.1%
Shell 0.8%
CSS 0.4%
JavaScript 0.3%
Python 0.3%