Denozordec 1fefed2aa0
Docker images / backend-image (push) Successful in 39s
Docker images / frontend-image (push) Successful in 1m38s
Docker images / updater-image (push) Successful in 35s
Docker images / notify-webhook (push) Has been skipped
chore: enhance data source configuration and mock data handling
- Added support for mock data mode in the frontend by introducing a new environment variable in the Dockerfile and workflow configuration.
- Updated the settings page to conditionally display options based on the availability of mock data, improving user experience.
- Refactored data source logic to include a check for mock data availability, ensuring proper mode handling in the application.
2026-05-12 15:38:27 +07:00
2026-05-03 11:16:07 +07:00
2026-05-02 01:17:08 +07:00
2026-05-02 01:17:08 +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.shts.su

Имена образов в registry для репозитория denozord/MikrotikManager-3:

  • git.shts.su/denozord/mikrotikmanager-backend
  • git.shts.su/denozord/mikrotikmanager-frontend
  • git.shts.su/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.shts.su 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 они уже ставятся.

Запуск

Два процесса (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.shts.su/<owner>/<stem>-backend
frontend-image .ci/docker/frontend Dockerfile.frontend git.shts.su/<owner>/<stem>-frontend
updater-image deploy/updater deploy/updater/Dockerfile git.shts.su/<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"} на URL из секрета DEPLOY_WEBHOOK_URL после успешной сборки backend и frontend (updater в needs не входит).

Прод-развёртывание Docker

Эталон: deploy/docker-compose.yml. Рабочий каталог для команд compose — deploy/ (или укажите -f deploy/docker-compose.yml из корня репозитория).

Прод-контейнеры

Сервис container_name Образ (пример) Порты host:container Тома restart
backend mmapp-backend git.shts.su/denozord/mikrotikmanager-backend:latest 8000:8000 backend-data/app/data unless-stopped
frontend mmapp-frontend git.shts.su/denozord/mikrotikmanager-frontend:latest 3000:3000 unless-stopped
updater mmapp-updater git.shts.su/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.shts.su registry для docker login
REGISTRY_USERNAME пусто логин (если заданы оба с паролем)
REGISTRY_PASSWORD пусто пароль registry

Docker Compose

docker login git.shts.su
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.shts.su/denozord/mikrotikmanager-backend:latest \
  git.shts.su/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.shts.su/denozord/mikrotikmanager-frontend:latest \
  git.shts.su/denozord/mikrotikmanager-frontend:latest

Updater:

docker volume create updater-state
docker run -d \
  --name mmapp-updater \
  --restart unless-stopped \
  -e REGISTRY=git.shts.su \
  -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.shts.su/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.shts.su.

  1. Установить Docker Engine и плагин Compose (официальная документация Docker для вашего дистрибутива).
  2. Проверить:
docker version
docker compose version
  1. Открыть входящие порты 3000 (frontend) и 8000 (backend) в firewall или настроить внешний доступ согласно вашей схеме (reverse proxy в репозитории не описан).
  2. Войти в registry:
docker login git.shts.su
  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.shts.su/denozord/mikrotikmanager-backend:latest
docker pull git.shts.su/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.shts.su.
  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.1 MiB
1.6.0
Latest
2026-09-04 20:48:37 +07:00
Languages
TypeScript 98%
Shell 0.8%
CSS 0.4%
JavaScript 0.4%
Python 0.3%