Устранение неполадок

Как пользоваться страницей: найдите свой случай по заголовку и выполните команды по порядку. Ошибка — это последние строки вывода команды; mitdev doctor в строке каждой проблемы сам подсказывает команду для починки. (Совсем новичок? Термины и «как читать ошибки» — в Азбуке.)

Первое действие при любой проблеме:

mitdev doctor                                # общая диагностика
sudo tail -50 /var/log/mitdev/errors.log     # что именно упало
sudo less +G /var/log/mitdev/install.log     # полный контекст (вывод команд)

Установка идемпотентна: исправив причину, просто перезапустите sudo ./install.sh — успешные модули предложит пропустить. Ничего не сломается, если запустить установку повторно.

Повторный запуск сохраняет настройки. Начиная с 2.27.1 установщик читает предыдущие ответы из /var/lib/mitdev/install.env и подставляет их как значения по умолчанию: порт SSH, deploy-пользователь, а политика входа (root/пароли) берётся из текущего sshd_config.d/99-mitdev.conf. Поэтому если просто принимать значения по умолчанию, порт SSH и правила файрвола не «переедут» на 22 и службы не отвалятся. Уже установленные компоненты в списке предотмечены; на «переустановить?» по умолчанию — «нет» (пропуск). Если вы намеренно меняете порт SSH — не забудьте, что файрвол обновится под новый порт в том же запуске.

Если при переустановке модуль падает и вы соглашаетесь на откат, откат работает в защищённом режиме: он не удаляет уже работавшие службы, пакеты, пользователей и роли (только отменяет новые изменения — конфиги, репозитории, правила файрвола). На первой установке откат по-прежнему полностью очищает частичную установку.


Установка

sudo ./install.sh → «command not found»

Две причины, проверяются одной командой:

ls -la install.sh && head -3 install.sh | cat -A
  1. Файла нет (ls ругается) — перенос на сервер был неполным.

  2. В конце строк видно ^M — файлы испорчены Windows-переводами строк (CRLF): ядро ищет интерпретатор «bash\r» и не находит. Лечение:

    find . -type f \( -name '*.sh' -o -name mitdev -o -name '*.conf' \
      -o -name '*.template' -o -name '*.mjs' \) -exec sed -i 's/\r$//' {} +
    chmod +x install.sh mitdev
    sudo ./install.sh
    

Чтобы не повторялось, переносите проект так:

/get устанавливает старую версию

Скрипт ставит ровно то, что отдаёт сайт. Проверьте, что сайт отдаёт свежее:

curl -s https://ваш-домен/version.txt      # версия на сайте
mitdev version                              # версия на сервере

Если на сайте старая версия:

  1. Не перезалит site/public/ — пересоберите (node site/build.mjs --url https://ваш-домен) и загрузите заново; проверьте version.txt.
  2. Кэш CDN/прокси (Cloudflare и т.п.) — очистите кэш для /get, /mitdev.tar.gz, /version.txt или добавьте правило «не кэшировать» (готовый nginx-блок — в SITE.md). Начиная с 2.8.1 /get сам обходит кэш (query-параметр) и печатает версию скачанного дистрибутива — расхождение видно сразу.

mitdev self-update падает: tmp: unbound variable (версия не поменялась)

Симптом: sudo mitdev self-update показывает экран успеха, но завершается строкой вида /usr/local/bin/mitdev: line NNN: tmp: unbound variable, а mitdev version остаётся прежней.

Причина: баг в старой версии самого self-update (cleanup-trap на EXIT с локальной переменной tmp → под set -u падает уже после подмены каталога). Это «проблема начальной загрузки»: сломанная версия не может корректно обновить сама себя. Исправлено в свежем дистрибутиве (trap на RETURN), но чтобы его получить, сервер нужно один раз обновить вручную — дальше self-update работает штатно.

Ручное обновление (в обход self-update):

# 1. Скачать свежий дистрибутив и распаковать во временный каталог
cd /tmp
curl -fsSL https://mitdev.top/mitdev.tar.gz -o mitdev.tar.gz
sudo rm -rf /opt/mitdev.new && sudo mkdir -p /opt/mitdev.new
sudo tar -xzf mitdev.tar.gz -C /opt/mitdev.new --strip-components=1
grep MITDEV_VERSION /opt/mitdev.new/lib/core.sh          # проверьте новую версию

# 2. Подменить установку (прежняя сохраняется в /opt/mitdev.prev для отката)
sudo rm -rf /opt/mitdev.prev
sudo mv /opt/mitdev /opt/mitdev.prev
sudo mv /opt/mitdev.new /opt/mitdev
sudo chmod +x /opt/mitdev/install.sh /opt/mitdev/mitdev
sudo ln -sf /opt/mitdev/mitdev /usr/local/bin/mitdev
hash -r                                                   # сбросить кэш путей shell
mitdev version                                            # должна быть новая

Если экран успеха всё-таки прошёл, а версия старая — обычно достаточно поправить симлинк и сбросить кэш shell:

readlink -f /usr/local/bin/mitdev            # куда указывает
grep MITDEV_VERSION /opt/mitdev/lib/core.sh  # что реально лежит в /opt/mitdev
sudo ln -sf /opt/mitdev/mitdev /usr/local/bin/mitdev; hash -r

«APT занят другим процессом»

На свежих серверах несколько минут работает unattended-upgrades.

sudo fuser -v /var/lib/dpkg/lock-frontend   # кто держит
# дождитесь завершения и повторите установку

«Нет доступа в интернет»

Preflight проверяет HTTPS до api.github.com и archive.ubuntu.com. Проверьте DNS (resolvectl status), исходящий 443 и прокси, если он есть.

gum не установился / интерфейс «некрасивый»

Не критично: TUI автоматически деградирует до whiptail/dialog/plain. Установить gum вручную: повторите установку — модуль попробует снова (репозиторий Charm, затем GitHub-релиз).

Модуль упал, я откатил, что дальше?

Откат вернул систему к состоянию до модуля. Причина сбоя — в errors.log; чаще всего это сеть/зеркала apt. Исправьте и перезапустите инсталлятор — он продолжит с нужного места.

Система: обновления, swap, NTP упал с кодом 100

Смотрите полный вывод команды в errors.log, а не только последние строки TUI:

sudo less +G /var/log/mitdev/errors.log

Типичный случай на Ubuntu 22.04 — конфликт переходящих пакетов, например fwupd зависит от старой версии библиотек, а APT уже видит новую. mitdev использует apt-get dist-upgrade, чтобы такие переходы разрешались штатно. Если локальный APT всё равно сломан, сначала восстановите его и повторите установку:

sudo apt-get update
sudo apt-get -f install
sudo dpkg --configure -a
sudo apt-get dist-upgrade
sudo ./install.sh

Все ошибки команд целиком пишутся в /var/log/mitdev/errors.log, полный поток установки — в /var/log/mitdev/install.log.

Где взять SSH-ключи для Termius?

После установки:

sudo mitdev ssh-key status
sudo mitdev ssh-key show deploy
sudo mitdev ssh-key show root

В Termius импортируйте private key через Keychain → New Key → Import/Paste private key. Рекомендуемый профиль подключения — deploy-пользователь с sudo. Root-ключ создаётся отдельно, но прямой root-вход может быть запрещён настройкой PermitRootLogin no.

Хочу переустановить один модуль

Запустите sudo /opt/mitdev/install.sh, отметьте только нужный компонент, на вопрос «уже выполнен, переустановить?» ответьте «Да». Либо снимите маркер вручную: sudo rm /var/lib/mitdev/state/<модуль>.done.


SSH

«Connection refused» на порту 22

Сервер жив (ответил отказом), но на 22 никто не слушает или соединение отклоняется. Причины по частоте:

  1. Fail2Ban забанил ваш IP — предыдущие неудачные попытки входа (включая publickey!) считаются атакой; бан выглядит именно как refused. Через веб-консоль провайдера:
    fail2ban-client status sshd                # ваш IP в списке банов?
    fail2ban-client set sshd unbanip <ваш_IP>
    # чтобы не повторялось — свой IP в исключения:
    sed -i 's/^ignoreip = .*/& <ваш_IP>/' /etc/fail2ban/jail.local && systemctl restart fail2ban
    
  2. SSH на нестандартном порту (вы задали его при установке): в клиенте укажите свой порт; на сервере он виден в grep SSH_PORT /var/lib/mitdev/install.env и ss -tlnp | grep sshd.
  3. Сервер ещё загружается — подождите 1–2 минуты после перезагрузки.
  4. Служба ssh не поднялась: systemctl status sshsystemctl enable --now ssh (через консоль провайдера).

«Authentication failed (publickey)» — не пускает по ключу

Сервер принимает только ключи (пароли вы отключили при установке), а предложенный ключ не подходит. Причины по частоте:

  1. Ключ не прикреплён к хосту в Termius — Keychain-ключ есть, но в настройках Host поле Key пустое. Откройте Host → выберите ключ явно.
  2. Логин и ключ не совпадают: deploy-ключ работает только с Username deploy, root-ключ — только с root (и только если root-вход по SSH не был запрещён при установке).
  3. Импортирован публичный ключ вместо приватного, или вставлен не весь блок (нужны строки -----BEGIN…----- и -----END…----- целиком).

Первое, что стоит попробовать: сменить Username на deploy — root получите через sudo -i.

Полный локаут (не пускает никак): откройте веб-консоль в панели провайдера (VNC/KVM — это не SSH, запрет паролей на неё не действует), войдите root с паролем и:

mitdev ssh-key show deploy          # приватный ключ для импорта в Termius
sudo cat /home/deploy/.ssh/authorized_keys   # что реально примет сервер
sudo journalctl -u ssh -n 20        # причина отказа (отпечаток ключа)

# либо временно вернуть пароли по SSH:
sed -i 's/^PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config.d/99-mitdev.conf
sshd -t && systemctl restart ssh

Сменил порт и не могу подключиться

ssh -p <новый_порт> <пользователь>@<ip>

Если не помогло (со старой сессии или из консоли провайдера):

sudo sshd -t                                   # конфиг валиден?
sudo ss -tlnp | grep sshd                      # какой порт реально слушается
sudo ufw status                                # порт открыт в файрволе?
sudo systemctl status ssh ssh.socket

Экстренный возврат к дефолту:

sudo rm /etc/ssh/sshd_config.d/99-mitdev.conf
sudo systemctl enable --now ssh.socket && sudo systemctl restart ssh

Fail2Ban забанил мой IP

sudo fail2ban-client status sshd               # список банов
sudo fail2ban-client set sshd unbanip <ваш_ip>

Docker

permission denied у deploy-пользователя

Членство в группе docker применяется при новом входе:

exit && ssh deploy@server      # или: newgrp docker

hello-world не прошёл при установке

sudo systemctl status docker
sudo journalctl -u docker -n 50

В LXC/OpenVZ-контейнерах Docker может требовать вложенной виртуализации — preflight предупреждает об этом типе виртуализации заранее.


Nginx

nginx -t падает после моих правок

sudo nginx -t                                  # покажет файл и строку
ls /etc/nginx/sites-available/*.mitdev-bak.*  # резервные копии mitdev

Порт 80/443 занят

sudo lsof -i :80 -i :443

Типовой конфликт — traefik из k3s (см. раздел Kubernetes).


Kubernetes (k3s)

Узел не Ready

sudo journalctl -u k3s -n 100
mitdev k8s nodes

Частая причина на малых VPS — нехватка RAM (k3s требует ~512 МБ сверх нагрузки).

Nginx не стартует после установки k3s (или наоборот)

Оба претендуют на 80/443. При установке в одном прогоне mitdev отключает traefik автоматически. Если k3s ставился отдельно и traefik уже работает:

# отключить traefik в пользу host-nginx:
sudo tee -a /etc/rancher/k3s/config.yaml <<< 'disable: ["traefik"]'
sudo systemctl restart k3s
sudo kubectl -n kube-system delete helmchart traefik traefik-crd --ignore-not-found

Под в CrashLoopBackOff после k8s deploy

mitdev k8s logs <имя>
kubectl describe pod -l app=<имя>

Проверьте, что порт в k8s deploy совпадает с портом, который слушает приложение внутри контейнера, и что образ доступен (imagePullPolicy: Always).


Плейбук: отказал primary PostgreSQL

При watchdog v2 система полностью чинит себя сама — включая возврат узлов. Хронология без вашего участия:

Время Что происходит автоматически
0 с primary перестал отвечать
~30 с watchdog основной реплики дважды перепроверил и выполнил promote
+2–5 с прокси/VIP переключились; драйверы переподключились
+~30 с вторая реплика увидела нового primary через эндпоинты ролей и сама перецепилась (смена primary_conninfo, без копирования данных)
позже ожившый старый primary видит primary со старшим timeline → сам понижается (pg_rewind) и возвращается репликой

Итого: простой записи ~30–40 секунд, никаких ручных действий, split-brain исключён fencing'ом по timeline.

Ваша единственная задача — убедиться, что всё сошлось (когда удобно):

mitdev pg status        # кто primary
mitdev pg replication   # обе реплики streaming?
journalctl -t mitdev-pg-watchdog -n 30   # что делала автоматика

Автоматика не справилась (нет сети до пиров, pg_rewind не смог)? Журнал watchdog скажет, что именно; ручные запасные ходы прежние: mitdev pg promote и mitdev pg follow <ip>.

Плейбук: недоступен сервер Kubernetes

HA-кластер (3 сервера, режимы init/join): ничего не делать. Поды с упавшего сервера автоматически пересоздаются на живых (~1–5 минут, реплики deployment'ов разнесены по серверам анти-аффинити — сервис не прерывался). Вернувшийся сервер сам входит в кластер. Проверка: mitdev k8s nodes (упавший — NotReady, потом снова Ready).

Развернуть такой кластер: на первом сервере install.sh → Kubernetes → режим init; mitdev k8s token выдаст URL и токен; на остальных двух — режим join. Домен приложений направьте на все серверы (DNS round-robin или LB провайдера). Кворум etcd требует нечётного числа серверов (3 или 5); при трёх серверах кластер переживает отказ любого одного.

Одиночный k3s: приложения недоступны до возвращения сервера; после старта всё поднимается само:

mitdev k8s status && mitdev k8s pods
sudo journalctl -u k3s -n 50          # если узел не Ready
sudo systemctl restart k3s

Кластер PostgreSQL

Реплика не подключается (pg_basebackup: connection refused / no pg_hba entry)

На primary проверьте по порядку:

sudo -u postgres psql -c 'SHOW listen_addresses'          # должно быть *
sudo grep mitdev-replication /etc/postgresql/*/main/pg_hba.conf   # есть IP реплики?
sudo ufw status | grep 5432                               # правило для IP реплики?
mitdev pg add-replica <ip_реплики>                     # добавляет всё разом

Пароль — replication_password из /root/.mitdev-credentials на primary.

Реплика: Проверка доступности primary … no response (pg_isready exit 2)

Модуль реплики останавливается на первой же проверке и локальный PostgreSQL не трогает — до порта 5432 на primary не достучаться с этого узла. Отличать:

Частая причина: на primary ранее сняли оверлей кластера (mitdev remove pgcluster / mitdev pg cluster-remove) — это удаляет listen_addresses='*', и primary вернулся к прослушиванию только localhost (а --purge ещё и удалил роль репликации).

Починка на primary:

# 1. Восстановить приём реплик (add-replica теперь сам чинит listen_addresses):
sudo mitdev pg add-replica <ip_реплики>
#    add-replica вернёт listen_addresses='*', при необходимости перезапустит
#    PostgreSQL и в конце покажет, слушает ли 5432 на всех интерфейсах.

# 2. Если роль репликации была удалена (--purge) — add-replica об этом скажет.
#    Восстановите роль primary через установщик (идемпотентно):
sudo /opt/mitdev/install.sh      # выбрать PostgreSQL-кластер → роль primary

# 3. Проверить, что порт открыт наружу и файрвол пропускает:
sudo ss -tlnp | grep 5432        # ожидается 0.0.0.0:5432 / *:5432, не только 127.0.0.1
sudo ufw status | grep 5432

С самой реплики проверьте связность (файрвол провайдера тоже должен пропускать 5432 с IP реплики — это отдельный от UFW уровень):

nc -zv <ip_primary> 5432          # timeout → сеть/файрвол; refused → PostgreSQL не слушает
pg_isready -h <ip_primary> -p 5432

mitdev doctor: «неактивных слотов: N (копится WAL!)»

Реплика отвалилась, а её слот держит WAL — диск primary будет заполняться. Верните реплику в строй или уберите её из кластера штатной командой (снимет слот, доступ pg_hba и правило файрвола):

mitdev pg remove-replica <ip_реплики>     # на primary — всё разом

Вручную (если нужно точечно):

sudo -u postgres psql -c "SELECT slot_name, active FROM pg_replication_slots"
sudo -u postgres psql -c "SELECT pg_drop_replication_slot('<slot_name>')"

Полностью убрать реплику: на primary — mitdev pg remove-replica <ip>, затем на самой реплике — mitdev remove pgcluster --purge (удалит её БД) или mitdev remove postgresql --purge (удалит PostgreSQL целиком).

Отставание реплики растёт

mitdev pg replication на primary покажет отставание в байтах WAL. Причины по частоте: медленный диск реплики, сеть, массовые записи на primary. Разово реплика догонит сама; хронически — усильте реплику.

Авто-failover не сработал / сработал неожиданно

Журнал watchdog:

mitdev pg watchdog status                       # служба, конфиг, последние события
journalctl -t mitdev-pg-watchdog -n 50          # полная история

Не сработал — проверьте, что служба активна (mitdev pg watchdog enable) и что в /etc/default/mitdev-pg-watchdog AUTO_PROMOTE=yes. Сработал от кратковременного сбоя — увеличьте FAIL_THRESHOLD или CHECK_INTERVAL в том же файле и перезапустите: sudo systemctl restart mitdev-pg-watchdog.

После promote старый primary вернулся в сеть

Не подключайте его обратно как есть — это split-brain. Пересоздайте его репликой нового primary одной командой на нём:

mitdev pg follow <ip_нового_primary>

(предварительно на новом primary: mitdev pg add-replica <ip_старого>).

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

Проверяйте по порядку:

# 1. С сервера приложения: маршрут и порт
nc -zv <VIP> 5432          # timeout → сеть/UFW; refused → PostgreSQL/pg_hba

# 2. На DB-узлах: доступ открыт?
sudo grep mitdev-app-access /etc/postgresql/*/main/pg_hba.conf
sudo ufw status | grep 5432
# если пусто — откройте (на КАЖДОМ узле):
sudo mitdev pg allow <ip-приложения>

# 3. Ошибка "no pg_hba.conf entry" в логах приложения
#    → IP приложения не совпадает с разрешённым (NAT? другой интерфейс?)
#    → смотрите фактический адрес: sudo tail /var/log/postgresql/*.log

Серверы приложений должны быть в одной приватной сети с DB-узлами (или иметь маршрут до подсети VIP).

Прокси (mitdev pg proxy) не находит primary

# на сервере приложений:
sudo systemctl status haproxy
curl -s http://<ip-узла>:8008/            # primary|replica с каждого DB-узла
echo "show stat" | sudo socat stdio /run/haproxy/admin.sock 2>/dev/null | cut -d, -f1,2,18 | column -s, -t

# на DB-узлах:
sudo systemctl status mitdev-pg-role.service
sudo journalctl -u mitdev-pg-role.service -n 50   # история/ошибки socat
curl -s http://127.0.0.1:8008/            # локально отвечает?
sudo ufw status | grep 8008               # порт открыт для сервера приложений?

Эндпоинт роли — персистентный листенер на socat (юнит mitdev-pg-role.service, зависимость socat ставится модулем автоматически). Раньше это был socket-activated юнит (mitdev-pg-role.socket + [email protected]), который systemd поднимал заново на каждое подключение; при частых опросах HAProxy это упиралось в systemd start-limit, часть коннектов рвалась по RST, HAProxy метил primary как DOWN и сыпал каскадные 500/503 — поэтому от socket-activation отказались.

Если чек-порт закрыт (mitdev pg allow <ip-приложения> не выполнялся на узле) — HAProxy пометит узел как DOWN, даже когда PostgreSQL жив.

VIP не переезжает / не поднимается

mitdev pg vip                       # держатель, служба, события
journalctl -u keepalived -n 50         # полная история VRRP

Частые причины:

mitdev doctor отдельно сигналит опасную ситуацию: VIP поднят на узле, который является репликой.


Разное

mitdev: command not found

sudo ln -sf /opt/mitdev/mitdev /usr/local/bin/mitdev

Где пароли служб?

sudo cat /root/.mitdev-credentials

Внимание: пароль deploy-пользователя здесь НЕ хранится (в файле — только пароли служб: БД, Redis, RabbitMQ, MinIO). См. пункт ниже.

sudo: «пароль не подходит» после установки

Симптом: вход по SSH-ключу работает, но sudo отклоняет пароль («Sorry, try again» / «пароль не подходит»).

Причина: при установке в шаге «Пароль для deploy (пусто — не менять)» пароль оставили пустым. Пользователь создаётся с заблокированным паролем — вход по ключу возможен, а sudo (пользователь в группе sudo) требует пароль, которого нет. Пароль deploy-пользователя нигде не сохраняется — его нужно задать заново.

Проверка статуса пароля (замените deploy на своё имя — mitdev info или grep DEPLOY_USER /var/lib/mitdev/install.env):

sudo passwd -S deploy      # 2-е поле: P = пароль задан, L = заблокирован, NP = нет пароля

Если L или NP — задайте пароль и разблокируйте аккаунт от root (зайдите по root-SSH-ключу — mitdev ssh-key show root — или через консоль провайдера):

passwd deploy              # задать пароль
passwd -u deploy           # снять блокировку, если была
id deploy                  # убедиться, что в группе sudo (RHEL/Alma: wheel)
#   если нет:  usermod -aG sudo deploy     # Debian/Ubuntu
#              usermod -aG wheel deploy    # RHEL/Alma/Rocky
su - deploy -c 'sudo -k; sudo whoami'      # проверка: спросит пароль → выведет root

Профилактика: при новой установке задавайте непустой пароль для deploy-пользователя. Нужен вход только по ключу с рабочим sudo без пароля — это отдельная настройка (NOPASSWD), сейчас установщик её не делает.

Диск заполнен

mitdev doctor          # покажет процент
mitdev clean           # apt + journald + (опц.) docker prune
ncdu /                    # найти крупное вручную

Ничего не помогло

Откройте issue с выводом mitdev version, mitdev doctor, фрагментами errors.log/install.log вокруг сбоя и типом виртуализации (systemd-detect-virt). Шаблон — в CONTRIBUTING.md.