Справочник команд mitdev

Все команды: mitdev <команда> [аргументы]. Команды, меняющие систему, требуют root (sudo mitdev …).

Справка доступна и на сервере: mitdev help — обзор, mitdev help <тема> — подробности с примерами (net, pg, ha, k8s, app, backup, remove, web, diag). Список тем — mitdev help topics.

Пошаговые руководства по задачам (этот файл — справочник, а не учебник):

Задача Документ
Объединить серверы в приватную сеть MESH.md
Пережить отказ сервера без Kubernetes HA.md
Подготовить проект к Kubernetes и развернуть k3s KUBERNETES.md
Развернуть приложение под свой стек DEPLOY.md
Разобрать конкретную аварию TROUBLESHOOTING.md

Диагностика

mitdev info

Обзор сервера: ОС, ядро, CPU, RAM, диск, IP, uptime; версии node/npm/pnpm/yarn/pm2; Docker (версия, контейнеры, образы, тома, сети, запущенные контейнеры); статусы служб; список процессов PM2.

mitdev doctor [--deep]

Диагностика с цветным отчётом: Docker, Node, PM2, Nginx (включая nginx -t), диск (порог 80/90 %), RAM, swap, UFW, Fail2Ban (текущие баны), ожидающие обновления, открытые порты (LISTEN). Код выхода = число критических проблем — удобно для мониторинга: mitdev doctor || alert.

Отдельный блок «Кластеры / HA» сводит состояние всех отказоустойчивых сервисов в одном месте: PostgreSQL (роль/стриминг реплики или число реплик), Redis (master по Sentinel), MongoDB (replica set: primary/узлов), RabbitMQ (число узлов кластера), MinIO (кворум distributed), WireGuard (число узлов mesh). Если ничего не настроено — «HA-кластеры не настроены».

С флагом --deep дополнительно выполняются реальные функциональные пробы (не только «служба запущена»):

Каждая проваленная проба увеличивает код выхода — mitdev doctor --deep подходит как «умный» health-check в cron/мониторинге.

mitdev services

Статус ключевых служб: ssh, nginx, docker, postgresql, redis-server, mongod, rabbitmq-server, minio, fail2ban, certbot.timer.

mitdev logs [цель]

Журналы. Без аргумента — меню. Цели: install, errors, nginx (tail -f), pm2, system (journalctl).

mitdev ssh-key [cmd] [deploy|root|all]

Сгенерированные SSH-ключи deploy-пользователя и root для клиентов вроде Termius. Требует root.

Рекомендуемый SSH-профиль — deploy-пользователь с sudo. Ключ root создаётся, но прямой root-вход может быть запрещён настройкой PermitRootLogin no.

mitdev report --json

Машиночитаемый снимок сервера: система (ОС, ядро, CPU, RAM, диск, uptime, IP), установленные службы с их состоянием, кластеры PostgreSQL и Redis, приватная сеть с пирами. Байты — для памяти и диска, секунды — для времени.

Это единственный источник данных для веб-панели: агент mitdevd выполняет эту команду и отдаёт JSON как есть. Требует jq (ставится модулем utils).

sudo mitdev report --json | jq .system.ram
sudo mitdev report --json | jq -r '.services[] | select(.active|not) | .name'
sudo mitdev report --json | jq '.net.peers[] | {name, ip, online, latency_ms}'

Удобно для мониторинга: подставьте в Zabbix/Prometheus textfile collector или опрашивайте по SSH из системы алертинга.

Обслуживание

mitdev update [--backup]

apt update && dist-upgrade (не ломается на пакетных переходах вроде fwupd), npm@latest, pnpm (corepack), pm2@latest + pm2 update.

Перед обновлением делается снимок конфигураций (nginx, sshd drop-in, redis.conf, minio, fail2ban, /etc/postgresql, install.env) в /var/lib/mitdev/pre-update/config-<дата>.tar.gz (хранятся последние 5) — так обновление остаётся обратимым, даже если пакет тронет конфиг. Флаг --backup дополнительно делает полный mitdev backup до обновления.

mitdev self-update [url]

Обновление самого mitdev с вашего сервера установки:

sudo mitdev self-update              # URL запомнен при установке через /get
sudo mitdev self-update https://mitdev.top   # или явно

Скачивает свежий mitdev.tar.gz, проверяет целостность архива и сравнивает версии (актуальная — ничего не делает). При обновлении: прежняя версия сохраняется в /opt/mitdev.prev (откат — переместить обратно), рантайм-копии для systemd (watchdog, эндпоинт роли, VIP-check) обновляются с перезапуском служб, состояние/маркеры/учётные данные не затрагиваются. Новые модули после обновления ставятся повторным sudo /opt/mitdev/install.sh (идемпотентно).

Цикл выпуска обновления: правите проект → node site/build.mjs --url … → перезаливаете site/public/ → на серверах sudo mitdev self-update.

mitdev clean

apt autoremove --purge, apt autoclean, обрезка journald до 100 МБ, опционально (с подтверждением) docker system prune -f.

mitdev backup [--output <каталог>] [--volumes]

Единый архив mitdev-backup-<дата>.tar.gz (по умолчанию в ~deploy/backups, права 0600). Содержит:

--volumes дополнительно сохраняет все именованные docker-тома (каждый — .tar.gz; может быть объёмным).

Архив содержит ключи и сертификаты — храните защищённо. Пароли служб (/root/.mitdev-credentials) в backup не входят — для их переноса используйте mitdev export --secrets.

mitdev restore <архив>

Восстановление из архива. Без подтверждения — каталоги пользователя, .env-файлы и конфигурации nginx (с nginx -t и reload). Каждое из потенциально разрушительных — по отдельному подтверждению: дампы PostgreSQL/Redis/MongoDB, SSL, SSH-ключи (и опц. настройки sshd — с проверкой sshd -t), cron, определения RabbitMQ, окружение MinIO, docker-тома.

Миграция и версионирование конфигурации

Перенос сервера между VPS в два шага: конфигурацияexport/import (что и как установлено), данныеbackup/restore (базы, тома, код).

mitdev export [--output <файл>] [--secrets]

Снимает переносимый манифест — какие компоненты установлены и с какими ключевыми параметрами (список компонентов, deploy-пользователь, порт SSH, политика входа, версия Node, роль PostgreSQL и т.д.). По умолчанию файл ~deploy/mitdev-manifest-<хост>-<дата>.env (права 0600).

Манифест — обычный KEY="value"-файл: его можно держать в git (без --secrets) и вести историю изменений сервера.

mitdev import <манифест> [--yes] [--components "a b c"]

Воспроизводит конфигурацию из манифеста, запуская установщик:

Ограничения: пароли (кроме варианта с --secrets) не переносятся — часть будет сгенерирована заново; роль replica для неинтерактивного импорта требует пароль репликации (перенесите манифест с --secrets). Импортируйте только доверенные манифесты (файл выполняется как конфигурация).

mitdev diff <манифест>

Сравнивает манифест с текущим сервером (только чтение): версия mitdev, deploy-пользователь, порт SSH и, главное, компоненты — что есть в манифесте, но не установлено здесь, и наоборот. Полезно перед import и для аудита дрейфа.

Пример переноса на новый VPS:

# на старом сервере:
mitdev export --secrets -o /root/srv.env      # конфигурация (+секреты)
mitdev backup                                 # данные (код, .env, дампы БД)

# скопировать srv.env и mitdev-backup-*.tar.gz на новый VPS, затем на нём:
curl -fsSL https://mitdev.top/get | sudo bash # поставить сам mitdev
mitdev diff /root/srv.env                     # посмотреть, чего не хватает
mitdev import /root/srv.env --yes             # воспроизвести конфигурацию
mitdev restore mitdev-backup-*.tar.gz         # вернуть данные

Приложения

mitdev app [пресет] [имя] [repo|путь] [домен]

Развёртывание приложения. Первый аргумент — необязательный пресет фреймворка, который подставляет порт и стратегию запуска, чтобы меньше думать о конфигурации:

Пресет Порт Запуск Требует модуль
next nest node 3000 сборка (npm run build) + PM2 (pm2 start npm -- start) node, pm2
fastapi 8000 venv + pip install -r requirements.txt + PM2 (uvicorn main:app) python, pm2
django 8000 venv + deps + PM2 (gunicorn <proj>.wsgi) python, pm2
go 8080 go build + PM2 (бинарник) go, pm2
laravel composer install, nginx + PHP-FPM, docroot public/ php
wordpress nginx + PHP-FPM, docroot репозитория php
static 80 nginx отдаёт каталог репозитория (root)
docker 8080 docker compose up -d из репозитория docker

Без пресета — прежнее полностью интерактивное поведение. Пресеты подставляют порт и стратегию запуска и реально запускают приложение (node/python/go — под PM2; PHP — через PHP-FPM; static — nginx-root), а не только настраивают nginx.

Команду запуска можно переопределить одностроч­ным файлом .mitdev/start в репозитории (например, .venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000).

Рантаймы ставятся отдельными модулями через install.sh (или добавьте компонент и переустановите): php (PHP-FPM + Composer), python (Python 3 + venv), go (официальный дистрибутив Go). Если рантайм не установлен, пресет об этом сообщит. Для django имя проекта для gunicorn определяется автопоиском wsgi.py; если не найдено — задайте .mitdev/start.

Шаги (то же, что и раньше):

  1. имя, источник (git-URL или локальный путь), домен (опц.), порт, путь к своему nginx-конфигу (опц.);
  2. клонирование в ~deploy/apps/<имя> (или git pull, если уже есть); для локального пути — копирование через rsync с запоминанием источника;
  3. .env из .env.example;
  4. зависимости — менеджер определяется по lock-файлу (pnpm-lock.yaml → pnpm, yarn.lock → yarn, package-lock.json → npm ci);
  5. docker compose up -d, если есть compose-файл;
  6. nginx: свой конфиг или стандартный reverse-proxy шаблон + reload;
  7. по подтверждению — SSL через certbot (автопродление — certbot.timer).

Свой nginx-конфиг. Три способа задать (в порядке приоритета):

В своём конфиге можно использовать плейсхолдеры __APP_NAME__, __DOMAIN__, __PORT__ — они подставятся из ответов. Перед включением конфиг проверяется nginx -t: при ошибке сайт не включается, прежнее состояние восстанавливается, nginx продолжает работать. SSL с автопродлением выпускается одинаково для своего конфига и шаблона.

mitdev deploy [имя]

Обновление приложения (без имени — меню выбора): git pull --ff-only → зависимости → npm run build (если есть script) → docker compose up -d --build + docker image prune -fpm2 restart <имя> (если процесс существует).

Для приложений, развёрнутых из локального пути, вместо git pull выполняется ре-синхронизация из сохранённого источника (rsync); серверные .env/node_modules сохраняются.

mitdev ssl <домен> [email]

Выпуск сертификата Let's Encrypt: certbot --nginx -d <домен> --redirect. Автопродление — системный таймер certbot.timer.

Компоненты

mitdev docker [cmd]

status (по умолчанию) | ps | images | prune | restart | logs <контейнер>.

mitdev node [cmd]

status (версии node/npm/pnpm/yarn/pm2) | update.

mitdev nginx [cmd]

status (версия, служба, валидность конфига, сайты) | test | reload | restart. reload/restart выполняются только после успешного nginx -t.

mitdev firewall [cmd] (алиас: ufw)

Безопасное управление файрволом:

Точечные правила БД/кластера добавляются своими командами (mitdev pg allow, add-replica) — не открывайте 5432 «всем».

mitdev pg [cmd]

Управление PostgreSQL и кластером потоковой репликации (алиасы: postgres, postgresql).

Самовосстановление (watchdog v2). Watchdog ставится на все узлы кластера (mitdev-pg-watchdog.service, работает от postgres) и полностью заменяет ручные действия при авариях:

На реплике:

  1. каждые PG_WATCHDOG_INTERVAL (5 с) primary проверяется двумя независимыми способами — pg_isready и прямое TCP-подключение; сбоем считается только отказ обоих;
  2. после PG_WATCHDOG_THRESHOLD (6) подряд неудач и приоритетной задержки PROMOTE_DELAY (выбирается вопросом инсталлятора: основная — 0 с, резервная — 45 с) watchdog опрашивает эндпоинты ролей остальных узлов (PEERS): если кто-то уже стал primary — сам перецепляется на него (смена primary_conninfo, без копирования данных); если нового primary нет и AUTO_PROMOTE=yes — повышается сам;
  3. кратковременные сбои failover не вызывают: любой успешный ответ primary сбрасывает счётчик, повторная проверка после задержки отменяет повышение.

На primary (fencing): эндпоинт роли отдаёт primary tl=<timeline>; если узел видит соседа-primary со старшим timeline — его сместили, пока он лежал — он сам понижается: pg_rewind от нового primary (fallback — pg_basebackup) и возврат в кластер репликой. Два primary одновременно невозможны, ручной pg follow при авариях не нужен.

События: journalctl -t mitdev-pg-watchdog. Для строгих гарантий кворума на 5+ узлах по-прежнему уместен Patroni, но для схемы 1+2 самовосстановление mitdev закрывает все штатные сценарии.

Дополнительные команды кластера:

Единая строка подключения (виртуальный IP)

Модуль pgvip ставит keepalived на все узлы кластера: свободный IP вашей подсети («VIP») автоматически держит тот узел, который сейчас primary (проверка pg_is_in_recovery() каждые 2 с). VIP не принадлежит ни одному серверу: умер держатель — адрес за 2–3 секунды поднимается на новом primary (unicast VRRP — работает и там, где multicast закрыт; правило proto 112 добавляется в UFW автоматически).

Приложения используют одну строку подключения навсегда:

postgresql://app:пароль@10.0.0.100:5432/app

После failover меняется только держатель VIP — строка не меняется. Драйвер переподключится сам при обрыве соединения.

Узлы в разных сетях? VIP работает только в одной L2-сети — используйте mitdev pg proxy (см. раздел «Узлы в разных сетях» ниже).

Доступ приложений с других серверов: mitdev pg allow

По умолчанию PostgreSQL принимает по сети только реплики. Чтобы проект с другого сервера подключался через VIP, откройте доступ его IP:

mitdev pg allow 10.0.0.20              # база=all, пользователь=all
mitdev pg allow 10.0.0.20 app deploy   # только база app, пользователь deploy

Команда добавляет запись в pg_hba.conf (scram-sha-256, /32), правило UFW (5432/tcp только с этого IP) и перечитывает конфигурацию без рестарта.

Выполняйте на каждом узле кластера — после failover новый primary тоже должен принимать это приложение (реплике записи не мешают, а после её повышения уже работают).

Требования к сети: серверы приложений должны иметь маршрут до подсети VIP — в облаках это общая приватная сеть (private network/VPC), на bare metal — общий VLAN. VIP для клиента — обычный IP; ничего особенного на стороне приложения не требуется.

Альтернатива без VIP — multi-host строка libpq (Python/Go/Java/.NET; не поддерживается node-postgres и Prisma):

postgresql://app:пароль@10.0.0.1:5432,10.0.0.2:5432,10.0.0.3:5432/app?target_session_attrs=read-write

Узлы в разных сетях / дата-центрах (без VIP)

VIP (keepalived) работает только внутри одной L2-сети. Если три сервера у разных провайдеров с разными публичными IP — используйте локальный прокси:

  Сервер приложения                DB-узлы (разные ДЦ)
┌─────────────────────┐          ┌──────────────────────┐
│ app → 127.0.0.1:5432│──TLS────▶│ 203.0.113.10 primary │◀─ 200 на :8008
│        HAProxy      │─чек :8008│ 198.51.100.20 replica│◀─ 503 на :8008
│ (mitdev pg proxy)│          │ 192.0.2.30   replica │◀─ 503 на :8008
└─────────────────────┘          └──────────────────────┘

Каждый DB-узел (модуль pgcluster) поднимает эндпоинт роли — HTTP-ответ 200 primary / 503 replica на порту PG_ROLE_PORT (8008). На каждом сервере приложений:

sudo mitdev pg proxy 203.0.113.10 198.51.100.20 192.0.2.30

HAProxy начинает слушать 127.0.0.1:5432 и держит трафик на том узле, который отвечает «primary»; при failover соединения к старому primary принудительно рвутся, драйвер переподключается — уже к новому. Строка подключения приложений одна навсегда:

postgresql://app:пароль@127.0.0.1:5432/app?sslmode=require

На каждом DB-узле откройте доступ серверу приложений (5432 + 8008):

sudo mitdev pg allow <ip-сервера-приложений>

Шифрование обязательно: между дата-центрами трафик идёт через интернет, поэтому mitdev настраивает репликацию и клиентские записи pg_hba как hostssl (соединения без TLS отклоняются), а primary_conninfo реплик — с sslmode=require. В строках подключения приложений тоже указывайте sslmode=require.

Watchdog авто-failover работает так же (пробы идут по публичным IP); приоритет реплики (задержка повышения) выбирается вопросом при установке.

Импорт бекапа проекта: mitdev pg import

Заливка существующей базы в кластер — одной командой на primary:

sudo mitdev pg import /path/backup.dump mydb

Развёртывание кластера из трёх узлов (разные ДЦ) — только mitdev

Ни одной ручной правки файлов; вы только отвечаете на вопросы.

  1. Узел A: curl -fsSL https://домен/get | sudo bash → компоненты PostgreSQL + «PostgreSQL-кластер» → роль primary, IP реплик: B C. Пароль репликации показан в /root/.mitdev-credentials.
  2. Бекап проекта (на A): sudo mitdev pg import /path/backup.dump mydb.
  3. Узел B: та же установка → роль replica, primary = A, пароль репликации; авто-failover «Да», приоритет 1 — основная.
  4. Узел C: так же, но приоритет 2 — резервная (задержка 45 с — настроится сама).
  5. Доступ приложений — на каждом из A, B, C: sudo mitdev pg allow <IP-приложения> mydb deploy.
  6. Сервер приложений: sudo mitdev pg proxy <A> <B> <C> → в .env: postgresql://deploy:ПАРОЛЬ@127.0.0.1:5432/mydb?sslmode=require.
  7. Проверка: mitdev pg replication (A), mitdev pg watchdog status (B, C), mitdev doctor (везде).

Тонкая настройка watchdog при нестабильной сети между ДЦ — тоже командой:

sudo mitdev pg watchdog set FAIL_THRESHOLD 12   # ~минута до failover

После failover (умер A, повысился B) — всё происходит само: прокси переключается на B; реплика C сама перецепляется на B (увидев его через эндпоинты ролей); ожившый A сам понижается (fencing по timeline + pg_rewind) и возвращается репликой B. Проверка автоматики: journalctl -t mitdev-pg-watchdog -n 30 на любом узле.

(Вариант с VIP для одной сети: те же шаги + модуль «Виртуальный IP» на всех узлах; строка подключения — postgresql://…@<VIP>:5432/….)

mitdev k8s [cmd]

Управление кластером k3s (алиас: mitdev kubernetes).

HA-кластер из трёх серверов. Режим выбирается вопросом инсталлятора: single (одиночный, по умолчанию), init (первый сервер кластера, embedded etcd) или join (присоединение: URL https://<IP-первого>:6443 и токен из mitdev k8s token). Кластеру нужно нечётное число серверов (3 или 5); кластерные порты (etcd 2379-2380, kubelet 10250, flannel 8472/udp) открываются в файрволе точечно по IP пиров. Реплики deployment'ов разносятся по серверам анти-аффинити: отказ любого сервера не прерывает сервис, его поды пересоздаются на живых автоматически. Вход трафика направляйте на все серверы (DNS-записи на все IP или LB провайдера).

mitdev k8s deploy <имя> <образ> [порт] [домен] [реплики]

Развёртывание приложения в кластер (аргументы можно опустить — будут запрошены):

  1. рендер манифеста (Deployment с probes/limits + Service NodePort, по умолчанию 2 реплики, rolling update без даунтайма);
  2. kubectl apply + ожидание rollout status (таймаут 180 с);
  3. публикация домена:
    • traefik включён (nginx на хосте нет) → Ingress;
    • есть nginx → reverse proxy на выданный NodePort + предложение SSL (certbot).

Манифесты сохраняются в /var/lib/mitdev/k8s/ — их можно править и переприменять через mitdev k8s apply.

mitdev k8s delete <имя>

Удаляет Deployment/Service/Ingress приложения (по метке app=<имя>) — то есть и его поды, — его манифесты и nginx-сайт k8s-<имя>, если он создавался.

mitdev k8s delete-all

Удаляет все приложения mitdev (метка managed-by=mitdev) и их поды, сохранённые манифесты и созданные для них nginx-сайты. Сам кластер k3s остаётся.

mitdev k8s delete-cluster

Полное удаление Kubernetes: снимает k3s целиком (все поды, рабочие нагрузки и состояние кластера etcd/sqlite), Helm, kubeconfig deploy-пользователя и правила файрвола кластера. Эквивалент mitdev remove kubernetes.

Отказоустойчивый Redis (Sentinel): mitdev redis

HA-кластер Redis: master + N реплик, на каждом узле — redis-sentinel, который следит за master и при отказе автоматически повышает одну из реплик (взаимозаменяемость, как у PostgreSQL-кластера). Узлы связываются по приватной сети (mitdev net), если она есть. Пароль общий (requirepass = masterauth), поэтому бывший master после failover возвращается репликой без ручной правки.

mitdev redis proxy <ip-узла-1> [ip-2] [ip-3]

Поднимает на этом сервере локальный HAProxy: приложение подключается к 127.0.0.1:6379 обычным Redis-клиентом, а трафик уходит на тот узел кластера, который прямо сейчас отвечает role:master. Клиенту не нужна поддержка Sentinel.

Зачем это нужно. Failover переключает сервер, а не клиента. Приложение, настроенное на прямой адрес мастера, после переключения продолжит стучаться в узел, ставший репликой, и получит -READONLY на каждую запись. Кластер при этом исправен — ляжет только приложение.

# на сервере приложения (укажите ВСЕ узлы кластера)
sudo mitdev redis proxy 10.0.0.1 10.0.0.2 10.0.0.3
sudo mitdev redis proxy-status      # через прокси должен отвечать master

# порт занят локальным redis-server? возьмите другой:
sudo REDIS_PROXY_PORT=6380 mitdev redis proxy 10.0.0.1 10.0.0.2 10.0.0.3

Как устроена проверка: HAProxy сам ходит в каждый узел по протоколу Redis (AUTHINFO replication) и ищет role:master. Реплики отвечают role:slave и в пул не попадают. balance first вместе с backup на всех узлах, кроме первого, гарантирует, что запись идёт ровно в один узел — даже в момент, когда при split-brain два узла ненадолго объявляют себя мастером. on-marked-down shutdown-sessions рвёт старые соединения при смене мастера, чтобы драйвер переподключился к новому.

Пароль Redis попадает в /etc/haproxy/haproxy.cfg (иначе health-check не пройдёт аутентификацию), поэтому файл переводится в режим 640 root:haproxy.

Альтернатива без прокси — Sentinel-aware клиент: он спрашивает адрес мастера у Sentinel на порту 26379 и переподключается сам. Так умеют ioredis (Node.js), redis-py (Python), Lettuce (Java). Прокси нужен, когда менять код приложения нельзя или клиентов несколько и они разные.

Приложения подключаются через Sentinel (клиент спрашивает у 26379 актуальный адрес master) — при failover переключение прозрачно. Кворум N/2+1 определяет, сколько Sentinel'ов должны согласиться на failover: для реального авто-переезда нужно ≥3 узлов (нечётное число), иначе split-brain.

Почему нужен полный доступ между всеми узлами

Sentinel находит реплики, разбирая INFO на master, и сам ходит к каждой из них на 6379. Если узел A не может достучаться до узла B на 6379, он объявляет B недоступным (+sdown) — даже когда B прекрасно работает и другие узлы его видят. Тогда Sentinel'ы расходятся во мнении, кто master, и начинают перевыбирать его друг против друга: в журнале это выглядит как непрерывная череда +switch-master и +failover-end-for-timeout, а кластер получает двух master одновременно.

Поэтому 6379 и 26379 должны быть открыты между всеми узлами, в обе стороны — и в UFW, и в файрволе провайдера. mitdev redis cluster-init/join делают это сами для узлов, которые им известны; для добавленных позже выполните mitdev redis allow <ip> на каждом узле. Проверка — mitdev redis check.

Пример: кластер из трёх узлов на публичных IP

# на узле 1 (будущий master) — перечислите ВСЕ остальные узлы
sudo mitdev redis cluster-init mitdev 2 23.134.42.2 23.134.42.3
sudo mitdev redis token          # скопируйте токен

# на узлах 2 и 3
sudo mitdev redis join <токен>

# на каждом из трёх узлов — убедитесь, что видно всех
sudo mitdev redis check

Пока mitdev redis check не проходит на всех узлах, авто-failover работать не будет.

Распределённый MinIO: mitdev minio

Distributed MinIO с erasure coding — данные и чётность распределяются по узлам, кластер переживает отказ части узлов/дисков.

Важно: набор узлов MinIO фиксируется при старте (MINIO_VOLUMES со списком всех узлов) и не расширяется динамически. Поэтому cluster-init принимает сразу все адреса, а расширение = пересоздание набора.

Кластер поднимается, когда стартует кворум узлов набора (до этого MinIO ждёт остальных — это нормально). Все узлы делят одинаковые MINIO_ROOT_USER/PASSWORD/root/.mitdev-credentials).

Кластер RabbitMQ: mitdev rabbitmq

Кластеризация узлов: все делят один erlang-cookie (переносится в токене), новый узел делает reset + join_cluster к первому. Имена узлов — rabbit@<hostname>, резолвятся через /etc/hosts приватной сети. На vhost «/» по умолчанию включается тип очередей quorum (реплицируются между узлами, переживают отказ). Требуется приватная сеть (для резолва имён узлов).

Приложения подключаются к любому узлу (или через LB/HAProxy на 5672); quorum-очереди обеспечивают отказоустойчивость сообщений при потере узла (нужно ≥3 узлов для кворума). Учётные данные admin — из модуля rabbitmq (/root/.mitdev-credentials), общие в пределах кластера.

Отказоустойчивый MongoDB (replica set): mitdev mongo

Нативный replica set: авто-failover и выбор нового primary встроены в MongoDB (любой secondary становится primary при отказе). Между узлами — internal auth по общему keyfile (переносится в токене), клиентская авторизация включается автоматически (keyFile ⇒ authorization). Узлы связываются по приватной сети, если она есть.

Для честного авто-failover нужно ≥3 узлов (нечётное число) — иначе при отказе половины набор теряет кворум и уходит в read-only. Приложения используют строку подключения с replicaSet=<имя> и списком узлов (<узел>.internal).

Приватная сеть (WireGuard mesh): mitdev net

Full-mesh приватная сеть между серверами: каждый узел напрямую соединён с каждым (без центрального VPN-хаба), публичные IP сохраняются, для внутреннего взаимодействия появляется приватный IP (напр. 10.100.0.1). Все службы могут слушать только приватную сеть. Каждый узел хранит реестр всех пиров и рендерит из него /etc/wireguard/wg0.conf; операции идемпотентны, перед изменениями wg0.conf резервируется.

Несколько сетей на одном хосте (--project)

Один сервер может держать несколько независимых приватных сетей — по одной на проект. Любая подкоманда net принимает флаг --project <slug>:

mitdev net create --project shop      # поднять сеть проекта «shop» (wg-shop)
mitdev net token  --project shop      # токен именно этой сети
mitdev net status --project shop      # статус только сети «shop»
mitdev net join   --project shop <токен>

Без --project команды работают с исходной сетью wg0 — поведение полностью сохранено для уже развёрнутых узлов. Подсети разных проектов на одном хосте не должны пересекаться (проверяется при create). Имя интерфейса — wg-<slug> (для длинных имён — с детерминированным хеш-суффиксом, ≤15 символов).

mitdev net create

Создаёт сеть (этот сервер — первый узел). Спрашивает имя, подсеть (с проверкой пересечений с Docker/Kubernetes/локальными маршрутами), UDP-порт и опционально IPv6 (dual-stack на ULA fdXX:XXXX:XXXX::/64 — генерируется автоматически). Затем: ставит WireGuard, генерирует ключи (+ preshared key сети), создаёт wg0, включает IPv4/IPv6-forward, открывает порт в UFW, включает автозапуск wg-quick@wg0, назначает себе первый адрес (.1, при IPv6 — ::1) и записывает .internal-имена в /etc/hosts.

При включённом IPv6 каждому узлу назначается парный адрес (тот же номер хоста: 10.100.0.7fdXX:…::7); wg0 и правила AllowedIPs становятся dual-stack. Сети без IPv6 работают как прежде (обратная совместимость).

mitdev net token

Печатает токен сети — полный дескриптор (имя, подсеть, порт, preshared key, все текущие пиры) в base64. Его вставляют на новом сервере.

mitdev net join <токен>

Подключает текущий сервер к сети: устанавливает WireGuard, генерирует ключи, импортирует всех пиров из токена, выделяет свободный IP из подсети, поднимает wg0. В конце печатает peer-токен этого узла.

Чтобы связь стала двусторонней (mesh), peer-токен нужно добавить на остальных узлах: mitdev net add-peer <peer-токен>. Для сети из 2 серверов это один шаг; для N серверов — по одному add-peer на каждом существующем узле (либо используйте --ssh ниже — MITDEV разнесёт пир сам).

mitdev net join --ssh <host> [user]

Zero-touch подключение (сценарий «SSH» из ТЗ). MITDEV:

  1. подключается к существующему узлу по SSH (спрашивает пользователя и пароль/ключ; для пароля ставит sshpass);
  2. забирает токен сети (mitdev net token --raw удалённо);
  3. локально присоединяется (импорт пиров, автовыделение IP, поднятие wg0);
  4. добавляет этот узел как пир на целевом сервере и на всех остальных известных узлах (SSH → mitdev net add-peer);
  5. проверяет связь (ping по приватному IP).

Требование к удалённой стороне: SSH-пользователь либо root, либо имеет passwordless sudo (иначе удалённый sudo недоступен). Если на части узлов добавить пир не удалось, MITDEV честно перечислит их и покажет команду для ручного add-peer. Токен-механизм net token/join/add-peer остаётся как надёжный запасной путь без удалённого выполнения команд.

mitdev net add-peer <peer-токен> / remove-peer <имя>

Добавляет/убирает узел в реестре и wg0.conf (идемпотентно, без дубликатов), перечитывает конфиг «на лету» (wg syncconf, без обрыва туннеля).

mitdev net set-ip <ip>

Меняет приватный IP этого узла. Обновляет meta, собственную строку в реестре пиров и парный IPv6 (если сеть dual-stack); публичный ключ и endpoint сохраняются.

Отклоняется, если адрес вне подсети или уже занят другим узлом — mitdev подскажет свободный.

sudo mitdev net set-ip 10.100.0.7

После смены на каждом другом узле нужно применить новый peer-токен (mitdev net add-peer <токен>), иначе они продолжат слать пакеты на старый адрес. Токен печатается прямо в выводе команды.

Основной сценарий — разрешение конфликта IP после параллельного join двух новых узлов с одним токеном (см. MESH.md).

mitdev net status

Состояние сети: подсеть, число узлов, для каждого пира — задержка (ping), время последнего handshake и трафик (wg show … transfer). Также попадает в mitdev doctor.

mitdev net export [файл] / import <архив>

Экспорт/импорт всей сети (wg0.conf, ключи, реестр) одним архивом — для быстрого восстановления или миграции узла. Архив содержит приватные ключи — храните защищённо.

mitdev net remove / mitdev net repair

remove — снять сеть с этого узла (остановить и удалить wg0, watchdog, закрыть порт, почистить /etc/hosts). repair — самопроверка и починка: наличие WireGuard, ключей, конфига, службы, правила файрвола, IP-forward, watchdog — недостающее восстанавливается из реестра. mitdev repair делает то же.

Отказоустойчивость: mitdev net watchdog [status|enable|disable]

Сеть спроектирована без единой точки отказа:

Поверх этого на каждом узле работает watchdog (mitdev-wg-watchdog, ставится автоматически при create/join), который:

Управление: mitdev net watchdog status|enable|disable. Параметры (CHECK_INTERVAL, STALE_THRESHOLD) — в /etc/default/mitdev-wg-watchdog.

mitdev net bind <служба> [private|public|both]

Переключает, на каком интерфейсе слушает служба (идемпотентно, обратимо, перед изменением конфиг резервируется, служба перезапускается):

Поддерживаемые службы и что правится:

Служба Файл/механизм private
postgresql drop-in conf.d/zz-mitdev-listen.conf (listen_addresses) '<priv>,127.0.0.1'
redis bind в /etc/redis/redis.conf <priv> 127.0.0.1 -::1
mongodb net.bindIp в /etc/mongod.conf 127.0.0.1,<priv>
rabbitmq listeners.tcp.default + management.tcp.ip в rabbitmq.conf <priv>:5672
minio MINIO_OPTS в /etc/default/minio --address <priv>:9000

Для PostgreSQL drop-in назван zz-…, чтобы гарантированно перекрывать 99-mitdev-replication.conf кластера. Возврат: mitdev net bind <служба> public. После команды показывается фактический список прослушиваемых сокетов (ss).

mitdev net bind postgresql private     # PG слушает только приватную сеть
mitdev net bind redis private
mitdev net bind minio private
mitdev net bind postgresql public      # вернуть на все интерфейсы

Приватные адреса других узлов доступны и по имени <узел>.internal (записываются в /etc/hosts), так что строки подключения между серверами можно писать по именам.

Во время установки. Если приватная сеть уже создана, установщик при выборе модулей БД/кэшей сам спросит «Слушать только приватную сеть?» (по умолчанию — да) и применит net bind к установленным службам автоматически — вручную запускать net bind не обязательно.

Отказоустойчивый app-ingress через Cloudflare: mitdev cf

Вводит app-узел в отказоустойчивый ingress через Cloudflare — всё только через API Cloudflare (без дашборда, без локальных tunnel-конфигов): туннель (без входящих портов на узле), DNS и Load Balancing, плюс два локальных демона (health-эндпоинт и tunnel-watchdog) для честного failover на уровне приложения. Подробно, с архитектурой и режимами — в modules/24-cloudflare.md.

Токен — только локально. Экспортируйте CF_API_TOKENCF_ACCOUNT_ID/ CF_ZONE_ID) на самом узле перед командами; в чат и на чужие серверы токен не передавайте. Нужны scope: Account→Cloudflare Tunnel: Edit; Zone→DNS: Edit; Zone→Load Balancers: Edit; Account→Load Balancing: Monitors and Pools: Edit; Account→Account Settings: Read.

Четыре варианта ingress: tunnel и LB в разных сочетаниях

Режим задаётся переменной CF_INGRESS_MODE (по умолчанию lb-over-tunnel). На практике tunnel и LB складываются в четыре варианта применения:

Вариант Туннель LB Узлов Health-failover Входящие порты Поддержка в mitdev
1. lb-over-tunnel (рекоменд.) ✅ на каждом узле ✅ поверх туннелей 1..N ✅ два эшелона (watchdog + LB-монитор) закрыты полная
2. tunnel-only, один узел ✅ один 1 — (резерва нет) закрыты полная
3. tunnel-only + round-robin DNS ✅ на каждом узле ❌ (DNS-раскидка) 2..N ❌ мёртвый узел держит свою долю трафика закрыты частичная (LB нет)
4. LB по публичным IP без туннеля (lb-only) ✅ по публичным IP 1..N ❌ health из edge недостижим открыты 80/443 cf lb … падает fail-loud

1. lb-over-tunnel — туннель на каждом узле + Cloudflare LB поверх туннелей; единственный вариант с настоящим health-failover между узлами (полный сценарий — ниже в этом разделе).

2. tunnel-only, один узел — один туннель + cf dns set на него, LB не нужен (cf lb … в этом режиме откажет):

export CF_INGRESS_MODE=tunnel-only
sudo -E mitdev cf login
sudo mitdev cf tunnel create app
sudo mitdev cf tunnel config app app.example.com
sudo mitdev cf tunnel run
sudo mitdev cf dns set app.example.com CNAME <tunnel-id>.cfargotunnel.com

3. tunnel-only + round-robin DNS — по туннелю на узел и несколько CNAME на один хост. ⚠️ Health-проверки нет: при падении узла ~его доля запросов летит в мёртвый узел, пока запись не убрать (cf dns rm). cf watchdog install грубо смягчает (stop cloudflared → ошибка коннектора), но «серый отказ» не ловит. Нужен настоящий межузловой failover — берите вариант 1.

4. LB по публичным IP без туннеля (lb-only) — в mitdev не поддержан: cf lb monitor/pool/create намеренно падают fail-loud. Причина: origin — публичный IP узла, cloudflared нет, а health-эндпоинт слушает loopback/WG и из сети Cloudflare недостижим — увести пробу монитора в скрытый health-хост без туннеля некому. Итог был бы либо «все origin unhealthy → отказ ingress», либо «монитор мерит лишь живость веб-сервера → серый отказ невидим». Нужен LB без туннеля — настраивайте его вручную в аккаунте Cloudflare с публичным health-эндпоинтом (health-эндпоинт mitdev для этого не подходит).

Полное описание с матрицей и архитектурой — в modules/24-cloudflare.md.

mitdev cf login

Проверяет CF_API_TOKEN (/user/tokens/verify) и сохраняет token/account/zone в креды (chmod 600). Различает «токен невалиден», «сеть недоступна» и «Cloudflare деградировал (5xx/429)» — в последних двух случаях менять токен бесполезно.

mitdev cf health install / remove

Поднимает/снимает health-эндпоинт узла: HTTP 200, только если живы app И БД И Redis, иначе 503. Слушает CF_HEALTH_BIND_IP (loopback/WG, никогда 0.0.0.0) на порту CF_HEALTH_PORT (8009). Ставит socat и клиентов pg_isready/redis-cli (иначе проверки fail-closed давали бы вечное 503).

mitdev cf watchdog install / remove

Поднимает/снимает tunnel-watchdog: при устойчивом 503 (мёртв локальный app — серый отказ) останавливает cloudflared → узел выходит из ротации; при возврате 200 запускает обратно. Дебаунс CF_TUNNEL_FAIL_THRESHOLD/RESTORE_THRESHOLD. По общей зависимости (лежит общая БД/Redis) узел НЕ снимает — иначе из ротации вышли бы все узлы разом.

mitdev cf tunnel create <имя>

Создаёт remotely-managed туннель (config_src=cloudflare) или переиспользует существующий с тем же именем; сохраняет id и connector-token в креды.

mitdev cf tunnel config <имя> <домен> [локальный_url]

Записывает ingress-правила туннеля в Cloudflare: скрытый health-хост (health.<домен> → health-эндпоинт) + домен → локальный url (по умолчанию http://localhost:CF_APP_PORT) + обязательный catch-all 404. Локального config.yml на узле нет.

mitdev cf tunnel run

Ставит cloudflared, поднимает юнит mitdev-cf-tunnel (connector-token — через EnvironmentFile, НЕ в argv) и, убедившись что коннектор активен, закрывает вход 80/443 (исходящие 443/7844 разрешаются явно до этого). SSH не трогается. После закрытия 80/tcp ACME HTTP-01 не пройдёт — при ingress через туннель TLS терминирует Cloudflare.

mitdev cf tunnel remove

Снимает коннектор с узла и возвращает вход 80/443.

mitdev cf dns set <имя> <тип> <значение> [proxied] / rm <имя> <тип>

Идемпотентно создаёт/обновляет (или удаляет) запись зоны CF_ZONE_ID. Типы: A, AAAA, CNAME, TXT. proxied по умолчанию true (CNAME на <id>.cfargotunnel.com работает только проксированным; для TXT — только false). Имя должно быть FQDN.

# домен на туннель:
sudo mitdev cf dns set app.example.com CNAME <tunnel-id>.cfargotunnel.com

mitdev cf lb monitor <домен> / pool <имя> <origin…> / create <домен>

Собирает Cloudflare Load Balancing поверх узлов. monitor — health-проба (идёт в скрытый health-хост). pool — origin'ы (в lb-over-tunnel это UUID туннелей, в lb-only — публичные IP). create — балансировщик на зоне; он сам выступает DNS-записью на своём имени и замещает CNAME на туннель (об этом предупреждает). Все три идемпотентны. В режиме tunnel-only LB не нужен, в lb-only health- failover не обеспечить — команды честно откажут.

mitdev cf status

Сводка: валидность токена, туннели и их коннекторы, пулы и здоровье origin'ов, локально — активны ли cloudflared/health/watchdog. Никогда не выдаёт «здорово» по секции, которую не смогла опросить; rc 0 только если всё опрошено и здорово — годится как гейт в мониторинге.

Удаление компонентов

У каждого модуля — своя команда удаления; единой «снести всё» нет. Удаление обращает вспять то, что создавал соответствующий модуль установки: останавливает службы, удаляет пакеты, конфигурацию, systemd-юниты, репозитории и служебных пользователей, созданные mitdev.

mitdev remove --list

Показывает все модули, доступные для удаления, и их статус (установлен/нет).

mitdev remove <модуль> [--purge] [-y]

Удаляет один компонент. По умолчанию данные сохраняются (базы, тома, каталоги, сертификаты) — чтобы можно было переустановить компонент без потери данных. Флаги:

Доступные модули: system user security node docker nginx pm2 certbot firewall fail2ban utils postgresql pgcluster pgvip redis mongodb rabbitmq minio kubernetes.

То же изнутри службы

Чтобы не искать команду на верхнем уровне, удаление доступно и как подкоманда самой службы — это полные синонимы mitdev remove <модуль>, флаги те же:

sudo mitdev redis delete --purge      # ≡ mitdev remove redis --purge
sudo mitdev minio delete -y
sudo mitdev rabbitmq delete
sudo mitdev mongo delete              # у mongo и pg алиаса `remove` нет:
sudo mitdev pg delete --purge         # он путался бы с remove-member/remove-replica

Для redis, minio и rabbitmq работают также remove и uninstall. Удаление не требует, чтобы служба была жива: если установка поломана или снесена наполовину, команда всё равно уберёт остатки.

Не путайте с cluster-remove — та снимает только HA-оверлей (Sentinel, правила файрвола, репликацию), оставляя саму СУБД и её данные на месте.

Что именно сохраняется/удаляется по модулям:

Модуль Удаляет всегда Удаляет только с --purge
system swap-файл и запись в /etc/fstab — (пакеты и обновления не трогает)
user пользователя деплоя домашний каталог /home/<user>
security drop-in усиления SSH, возврат к дефолтам
node Node.js, npm, corepack, репозиторий NodeSource
docker пакеты Docker, репозиторий образы/контейнеры/тома (/var/lib/docker)
nginx пакет nginx, дефолт-сайт mitdev всю конфигурацию /etc/nginx
pm2 автозапуск и глобальный PM2 ~/.pm2 deploy-пользователя
certbot пакет и таймер продления сертификаты /etc/letsencrypt
firewall отключает UFW сброс правил + удаление пакета
fail2ban пакет и jail.local
utils ничего (по умолчанию) весь набор утилит
postgresql пакеты PostgreSQL (+ оверлей кластера, если был) данные кластера + роль/базу приложения
pgcluster watchdog, эндпоинт роли, настройки репликации, правила replica: удаляет БД реплики + пересоздаёт пустой кластер; primary: роль репликации и слоты mitdev_*
pgvip keepalived-конфиг, VRRP-правило, снимает VIP пакет keepalived
redis пакет redis данные /var/lib/redis
mongodb пакет и репозиторий данные /var/lib/mongodb
rabbitmq пакет, admin-пользователь mitdev данные /var/lib/rabbitmq
minio юнит, бинарник, env, системного пользователя данные (MINIO_DATA_DIR)
kubernetes k3s целиком (все поды), Helm, kubeconfig, правила — (uninstaller и так стирает данные)

Примеры:

mitdev remove --list                 # что установлено
mitdev remove pgcluster              # снять оверлей кластера PostgreSQL
mitdev remove pgcluster --purge      # replica → удалить её БД; primary → роль+слоты mitdev_*
mitdev remove kubernetes             # удалить k3s и все поды
mitdev remove redis --purge -y       # Redis вместе с данными, без вопросов
mitdev remove postgresql --purge     # PostgreSQL со всеми базами

mitdev pg cluster-remove [--purge]

Синоним mitdev remove pgcluster. Снимает оверлей потоковой репликации (watchdog, эндпоинт роли, conf.d-настройки, записи pg_hba, правила файрвола). Поведение --purge зависит от роли узла:

Без --purge удаляется только оверлей; PostgreSQL и данные остаются.

Сценарии:

# Убрать ненужную реплику 10.0.0.5 из кластера:
#   на primary:
mitdev pg remove-replica 10.0.0.5      # слот + pg_hba + файрвол
#   на самой реплике 10.0.0.5:
mitdev remove pgcluster --purge        # удалить её БД (или remove postgresql --purge)

# Переустановить/пересобрать реплику (например, после сбоя pg_basebackup):
#   на реплике:
mitdev remove pgcluster --purge        # чистое состояние
#   на primary (если остался зависший слот):
mitdev pg remove-replica <ip_реплики>  # снять слот
#   затем заново установить роль replica через install.sh

Если pg_basebackup на реплике упал и на primary остался слот mitdev_…, выполните на primary mitdev pg remove-replica <ip> (адресно) или mitdev remove pgcluster --purge (все слоты mitdev_*).

Веб-панель

mitdev web install

Устанавливает и настраивает панель управления (mitdevd) на этом сервере: скачивает бинарник под архитектуру хоста, создаёт systemd-юнит, генерирует внутренний CA и сертификаты, заводит администратора с паролем и TOTP (2FA).

mitdev web agent-install

Ставит только агента — на узел, который должен отображаться в панели, но сам панель не размещает. Агент отдаёт mitdev report --json по защищённому каналу.

mitdev web status

Состояние службы, порт, активные сессии.

sudo mitdev web install
sudo mitdev web status
sudo mitdev report --json | jq .   # ровно те данные, что показывает панель

Панель — read-only дашборд: система, службы, кластеры PostgreSQL и Redis, пиры приватной сети. Обновляется опросом каждые несколько секунд.

Прочее

mitdev version

Версия и дата установки.

mitdev uninstall

Удаляет сам mitdev: /opt/mitdev, симлинк, состояние, (опционально) журналы. Установленные пакеты и данные приложений не трогает. Чтобы удалить установленные компоненты, используйте mitdev remove <модуль> (см. выше).

mitdev help [тема]

Без аргумента — обзор всех команд на один экран.

С темой — подробная справка с примерами и разбором частых ошибок:

Тема Содержание
net приватная mesh-сеть: create, join, add-peer, bind, watchdog, --project
pg PostgreSQL: репликация, watchdog, VIP, локальный прокси
ha отказоустойчивость: Redis, MongoDB, RabbitMQ, MinIO
k8s Kubernetes: кластер, деплой, чек-лист готовности приложения
app развёртывание приложений, пресеты, SSL
backup бэкапы, восстановление, перенос конфигурации на другой VPS
remove удаление компонентов, что сохраняется без --purge
web веб-панель управления
diag диагностика: doctor, logs, services, куда смотреть при проблемах
mitdev help              # обзор
mitdev help topics       # список тем
mitdev help net          # подробно про приватную сеть

Темы принимают синонимы: mitdev help mesh = mitdev help net, mitdev help kubernetes = mitdev help k8s, mitdev help redis = mitdev help ha.