Files
3X-UI_autoVPS/scripts/3x-ui-local.sh
T

414 lines
27 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# 3X-UI KIT (форк hacker07) — установка сервера одной командой из нашего канала.
#
# Что делает (по шагам):
# 1) проверяет сервер и наличие curl, ssh-keygen, openssl;
# 2) берёт из нашего Gitea релиз своей же версии, проверяет ПОДПИСЬ SHA256SUMS и SHA256 установщика;
# 3) запускает установщик автора (scripts/3x-ui.sh) — панель 3X-UI, протоколы, сайт-заглушка,
# маскировка, сертификат, подписка, ufw. Флаги установщика можно передавать как есть,
# а можно ничего не передавать — тогда он задаст вопросы сам (в том числе про домен);
# 4) спрашивает, какие протоколы ставить: невыбранные не создаются вообще — ни подключения в панели,
# ни записей в nginx, ни ссылок в подписке (за это отвечает флаг автора --protocols);
# 5) ставит сайт-заглушку: случайный правдоподобный ИТ-сайт из нашего набора (10 штук;
# --site N — конкретный, --no-site — не трогать авторскую страницу);
# 6) спрашивает и делает то, чего у автора нет: закрыть ответ на ping, включить обновления
# безопасности, перевести SSH на другой порт, сменить пароль root (поведение из StressOzz/vps-setup,
# но безопаснее);
# 7) в конце: kit fix (время и безопасные починки), kit check, kit local hardening.
#
# Запуск:
# # одна команда, дальше — вопросы:
# bash <(curl -fsSL https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main/scripts/3x-ui-local.sh)
# # или сразу с ответами, без вопросов:
# ... 3x-ui-local.sh --domain vpn.example.com
# ... 3x-ui-local.sh --sni dl.google.com --no-harden --ssh-port 2222
# # без вопросов, всё по умолчанию (для автоматизации):
# ... 3x-ui-local.sh --yes
#
# Флаги этого скрипта (остальные уходят установщику автора):
# --domain ДОМЕН, --sni САЙТ, --panel-on ip|domain, --host IP, --user ИМЯ, --port N, --restore ФАЙЛ
# --harden закрыть ping и включить автообновления, без вопросов
# --no-harden не трогать ping и автообновления
# --ssh-port N добавить порт SSH N (прежний порт продолжает работать)
# --yes ни о чём не спрашивать (ping закрою, автообновления включу, порт и пароль — нет)
# --no-time не запускать kit fix
# --no-check не запускать в конце kit check
# --verify-only только проверить релиз (подпись и SHA256) и выйти
# --dry-run показать план и ничего не делать
# -h | --help эта справка
#
# Наш файл: авторские релизы его не перезаписывают, а правки форка живут в scripts/kit-local.sh.
set -Eeuo pipefail
KIT_VERSION="1.2.6" # версия сборки форка; должна совпадать с VERSION релиза
GITEA="${KIT_FORK_BASE:-https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS}"
NS="3x-ui-kit-release"
SIG_ID="releases@3x-ui-kit"
# Публичный ключ форка. Меняется только вместе с ключом подписи (тогда обновляется и здесь, и в kit).
SIGNER_PUB='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM0Sfn/UQeqjvUvY9wVgg7QtG1RR2pEUkqrUD5Yy6EF 3X-UI KIT fork releases (hacker07)'
G=$'\e[32m'; Y=$'\e[33m'; R=$'\e[31m'; B=$'\e[1m'; D=$'\e[2m'; N=$'\e[0m'
[[ -t 1 ]] || { G=; Y=; R=; B=; D=; N=; }
say() { printf '%s\n' "${G}==>${N} $*"; }
warn() { printf '%s\n' "${Y}!${N} $*" >&2; }
die() { printf '%s\n' "${R}✗${N} $*" >&2; exit 1; }
usage() {
cat <<'EOF'
3X-UI KIT (форк hacker07) — установка сервера одной командой из нашего канала.
bash <(curl -fsSL <наш Gitea>/raw/branch/main/scripts/3x-ui-local.sh) [флаги…]
Без флагов скрипт спрашивает: какие протоколы ставить (невыбранные не создаются вообще),
маскировку и домен — это вопросы установщика автора; затем про ответ на ping, автообновления,
второй порт SSH и пароль root — это наши вопросы.
Флаги, которые скрипт передаёт установщику автора как есть:
--domain vpn.example.com свой домен для маскировки (A-запись на этот сервер, свободный 80)
--sni сайт.com маскировка под чужой сайт
--panel-on domain | ip где открывать панель и подписку
--host IP, --user имя, --port N, --protocols …, --restore файл
Флаги этого скрипта:
--harden закрыть ping и включить автообновления, без вопросов
--no-harden не трогать ping и автообновления
--ssh-port N перевести SSH на порт N (прежний закроется после проверки входа)
--yes ни о чём не спрашивать (для автоматизации)
--site N|random|off сайт-заглушка: конкретный из нашего набора, случайный (по умолчанию) или
не трогать авторскую страницу (то же: --no-site)
--no-time не запускать kit fix (время и безопасные починки)
--no-check не запускать в конце kit check
--verify-only только проверить релиз (подпись и SHA256) и выйти
--dry-run показать план и ничего не делать
-h | --help эта справка
EOF
}
# ------------------------------------------------------------------ разбор флагов
INSTALLER_ARGS=(); harden=auto; ssh_port=""; fix_time=yes; do_check=yes; dry=no; verify_only=no; assume_yes=no
site=random # заглушка: случайный сайт из нашего набора (off — не трогать)
while (($#)); do
case $1 in
--harden) harden=yes ;;
--no-harden) harden=no ;;
--ssh-port) ssh_port=${2:-}; shift || die "--ssh-port: нужен номер порта" ;;
--ssh-port=*) ssh_port=${1#*=} ;;
--yes | -y) assume_yes=yes ;;
--site) site=${2:-random}; shift || die "--site: нужен номер сайта (1..10), random или off" ;;
--site=*) site=${1#*=} ;;
--no-site) site=off ;;
--no-time) fix_time=no ;;
--no-check) do_check=no ;;
--verify-only) verify_only=yes ;;
--dry-run) dry=yes ;;
-h | --help) usage; exit 0 ;;
*) INSTALLER_ARGS+=("$1") ;;
esac
shift
done
[[ -z $ssh_port ]] || { [[ $ssh_port =~ ^[0-9]{1,5}$ ]] && ((10#$ssh_port >= 1024 && 10#$ssh_port <= 65535)); } \
|| die "--ssh-port: нужно число 1024–65535, например 2222"
if [[ $site != off && $site != random ]]; then
# 10 — число сайтов в наборе (scripts/kit-local.sh, KL_SITE_TITLES); добавите сайт — поднимите границу
[[ $site =~ ^[0-9]{1,2}$ ]] && ((10#$site >= 1 && 10#$site <= 10)) \
|| die "--site: random, off или номер 1..10 (список: kit local site --list)"
fi
# Если --protocols задан флагом — не спрашиваем и не дублируем его.
proto_flag=""
for ((_i = 0; _i < ${#INSTALLER_ARGS[@]}; _i++)); do
[[ ${INSTALLER_ARGS[_i]} == --protocols ]] && proto_flag=${INSTALLER_ARGS[_i + 1]:-all}
done
pre() { printf ' %s\n' "$*"; }
ASK_SRC=${KIT_ASK_SRC:-/dev/tty} # откуда читать ответы (в тестах подменяется файлом)
interactive() { [[ $assume_yes == no && -r $ASK_SRC ]]; }
if [[ $dry == yes ]]; then
say "план установки (--dry-run: ничего не делаю)"
pre "версия сборки: $KIT_VERSION"
pre "откуда: $GITEA (тег v$KIT_VERSION), подпись проверяется ключом форка"
if ((${#INSTALLER_ARGS[@]})); then pre "установщик автора: bash scripts/3x-ui.sh ${INSTALLER_ARGS[*]}"; else pre "установщик автора: bash scripts/3x-ui.sh — спросит про маскировку и домен"; fi
if [[ $harden == auto ]]; then
if interactive; then pre "защита: спрошу про ответ на ping и автообновления"
else pre "защита: без терминала — закрыть ping и включить автообновления"; fi
elif [[ $harden == yes ]]; then pre "защита: закрыть ping и включить автообновления (--harden)"
else pre "защита: пропускаю (--no-harden)"; fi
if [[ -n $proto_flag ]]; then pre "протоколы: ${proto_flag} (задано флагом)"
elif interactive; then pre "протоколы: спрошу (Enter — REALITY + AmneziaWG-3.1, «все» — набор автора)"
else pre "протоколы: REALITY + AmneziaWG-3.1 (без терминала вопроса нет)"; fi
if [[ -n $ssh_port ]]; then pre "порт SSH: перевожу на $ssh_port (прежний — после проверки входа)"
elif interactive; then pre "порт SSH: спрошу (Enter — оставить текущий)"; fi
if interactive; then pre "пароль root: спрошу (по умолчанию — не менять)"; fi
case $site in
off) pre "заглушка: оставляю авторскую страницу (--no-site)" ;;
random) pre "заглушка: случайный ИТ-сайт из нашего набора (10 штук)" ;;
*) pre "заглушка: сайт №$site из нашего набора (список: kit local site --list)" ;;
esac
[[ $fix_time == yes ]] && pre "после: kit fix (время, безопасные починки)"
[[ $do_check == yes ]] && pre "проверка: kit check + kit local hardening"
echo
say "запуск без --dry-run выполнит всё это на сервере"
exit 0
fi
# ------------------------------------------------------------------ релиз: скачать и проверить подпись
# Подпись проверяем ДО запуска чужого кода, поэтому здесь нужны только curl/ssh-keygen/openssl.
for c in curl ssh-keygen openssl; do command -v "$c" >/dev/null || die "нет $c — поставьте: apt-get update && apt-get install -y $c"; done
say "беру релиз v$KIT_VERSION и проверяю подпись"
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
raw="$GITEA/raw/tag/v$KIT_VERSION"
for f in SHA256SUMS SHA256SUMS.sig scripts/3x-ui.sh; do
mkdir -p "$tmp/$(dirname "$f")"
curl -fsSL --retry 3 -m 60 -o "$tmp/$f" "$raw/$f" || die "не скачался $f из $raw (Gitea доступна с сервера?)"
done
printf '%s namespaces="%s" %s\n' "$SIG_ID" "$NS" "$SIGNER_PUB" >"$tmp/allowed_signers"
ssh-keygen -Y verify -f "$tmp/allowed_signers" -I "$SIG_ID" -n "$NS" -s "$tmp/SHA256SUMS.sig" <"$tmp/SHA256SUMS" >/dev/null 2>&1 \
|| die "подпись релиза v$KIT_VERSION не сошлась с ключом форка — это не наш релиз, ничего не запускаю"
sum=$(awk '{f = $2; sub(/^\*/, "", f)} f == "scripts/3x-ui.sh" {print $1}' "$tmp/SHA256SUMS")
[[ $(printf '%s' "$sum" | wc -c) -eq 64 ]] || die "в SHA256SUMS нет установщика — релиз собран неверно"
got=$(sha256sum "$tmp/scripts/3x-ui.sh" | awk '{print $1}')
[[ $got == "$sum" ]] || die "SHA256 установщика не совпал с подписанным — не запускаю"
bash -n "$tmp/scripts/3x-ui.sh" || die "установщик не проходит проверку синтаксиса"
printf ' ✓ подпись сошлась, SHA256 установщика совпал\n'
if [[ $verify_only == yes ]]; then
say "релиз v$KIT_VERSION проверен: подпись и SHA256 сошлись, установщик не запускался"
exit 0
fi
# ------------------------------------------------------------------ предполётные проверки
say "проверяю сервер"
[[ ${EUID:-0} -eq 0 ]] || die "нужен root: запустите от root (на чистом VPS обычно так и есть)"
[[ -r /etc/os-release ]] || die "не вижу /etc/os-release — это не Debian/Ubuntu?"
# shellcheck disable=SC1091
. /etc/os-release
case ${ID:-} in debian | ubuntu | raspbian) ;; *) warn "система ${PRETTY_NAME:-?}: поддерживаются Debian и Ubuntu" ;; esac
printf ' %s\n' "${PRETTY_NAME:-Linux}, ядро $(uname -r), памяти $(awk '/MemTotal/ {printf "%.1f ГБ", $2 / 1048576}' /proc/meminfo 2>/dev/null || echo '?')"
df -P / | awk 'NR==2 {gsub("%","",$5); if ($5 >= 95) exit 1}' || warn "диск занят почти полностью — установка может не пройти"
for p in 443 80; do ss -H -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]$p\$" && warn "порт $p уже кем-то занят"; done
# ------------------------------------------------------------------ какие протоколы ставить
# Список, порядок и набор по умолчанию — как у установщика автора (scripts/3x-ui.sh:
# ALL_PROTOS / DEFAULT_PROTOS). Невыбранные протоколы не создаются вообще: под них не будет
# ни подключения в панели, ни записей в nginx, ни ссылок в подписке.
ALL_PROTOS=(reality reality2 hy2 xhttp ws trojan vmess ss tuic wg awg awg3 mtproto)
PROTO_HINT=(
"REALITY основной: VLESS + Reality, порт 443"
"REALITY-2 второй REALITY на случайном высоком порту"
"Hysteria2 быстрее на плохой сети (UDP 443)"
"XHTTP VLESS + Reality под другим именем маскировки"
"VLESS-WS VLESS через WebSocket"
"Trojan-gRPC Trojan через gRPC"
"VMess-WS VMess через WebSocket"
"Shadowsocks Shadowsocks 2022"
"TUIC быстрый UDP-протокол"
"WireGuard обычный WireGuard: легко распознаётся, обычно не ставят"
"AmneziaWG AmneziaWG: Mihomo, роутеры Keenetic"
"AmneziaWG-3.1 AmneziaWG с защитой заголовков: AmneziaVPN"
"MTProto прокси для Telegram"
)
# Наш набор по умолчанию: VLESS REALITY и AmneziaWG-3.1 — самый совместимый минимум.
# Набор автора (флаг --protocols all) доступен ответом «все».
AUTHOR_PROTOS=(reality hy2 xhttp ws trojan vmess ss tuic awg awg3 mtproto)
DEFAULT_PROTOS=(reality awg3)
proto_menu() {
local i n mark
say "какие протоколы поставить?" >&2
for i in "${!ALL_PROTOS[@]}"; do
n=$((i + 1)) mark=" "
[[ " ${DEFAULT_PROTOS[*]} " == *" ${ALL_PROTOS[i]} "* ]] && mark="*"
printf ' %s %2d. %s\n' "$mark" "$n" "${PROTO_HINT[i]}" >&2
done
printf ' %s\n' "${D}[* — по умолчанию: REALITY + AmneziaWG-3.1. «все» — набор автора (всё, кроме WireGuard).${N}" >&2
printf ' %s\n' "${D}[Невыбранные протоколы не создаются вообще: ни подключения, ни записей в nginx, ни ссылок в подписке.]${N}" >&2
}
proto_parse() { # ответ → выбранные имена через запятую; код 1, если не разобрал
local ans=${1,,} tok i all=" ${ALL_PROTOS[*]} " picked=""
ans=${ans//[[:space:]]/,} # «1 3 11» и «1,3,11» — одно и то же
[[ -n $ans ]] || { printf '%s' "$(IFS=,; echo "${DEFAULT_PROTOS[*]}")"; return 0; }
case $ans in
all | все | всё) printf '%s' "$(IFS=,; echo "${AUTHOR_PROTOS[*]}")"; return 0 ;;
minimal | минимум) printf '%s' "reality"; return 0 ;;
esac
local IFS=','
for tok in $ans; do
[[ -n $tok ]] || continue # двойные запятые и пробелы — не ошибка
if [[ $tok =~ ^[0-9]{1,2}$ ]]; then
i=$((10#$tok - 1))
((i >= 0 && i < ${#ALL_PROTOS[@]})) || return 1
tok=${ALL_PROTOS[i]}
fi
[[ $all == *" $tok "* ]] || return 1 # только имена из списка автора
[[ ,$picked, == *",$tok,"* ]] || picked+="${picked:+,}$tok"
done
[[ -n $picked ]] || return 1
printf '%s' "$picked"
}
ask_protocols() { # печатает итоговый список через запятую
local i ans picked
interactive || { printf '%s' "$(IFS=,; echo "${DEFAULT_PROTOS[*]}")"; return 0; }
proto_menu
for i in 1 2 3; do
printf '\n%sНомера через запятую (например 1,12) или «все»; Enter — REALITY + AmneziaWG-3.1: %s' "$B" "$N" >&2
ans=""
read -r -t 300 ans <"$ASK_SRC" 2>/dev/null || true
if picked=$(proto_parse "$ans"); then printf '%s' "$picked"; return 0; fi
warn "не понял «$ans» — нужны номера из списка через запятую или «все»"
done
warn "оставляю набор по умолчанию"
printf '%s' "$(IFS=,; echo "${DEFAULT_PROTOS[*]}")"
}
# ------------------------------------------------------------------ установка
if [[ -z $proto_flag ]]; then
protos=$(ask_protocols)
INSTALLER_ARGS+=(--protocols "$protos")
else
protos=$proto_flag
fi
say "протоколы: $protos — под остальные не создаётся ничего"
say "запускаю установщик автора (панель 3X-UI, протоколы, сайт-заглушка, подписка)"
if ((${#INSTALLER_ARGS[@]})); then printf ' флаги: %s\n' "${INSTALLER_ARGS[*]}"; else printf ' флагов нет: установщик задаст вопросы (маскировка, домен)\n'; fi
# stdin: если есть терминал — отдаём его установщику (там есть вопросы), иначе /dev/null,
# иначе установщик начнёт читать наш собственный скрипт вместо ответов.
if [[ -r /dev/tty ]]; then
bash "$tmp/scripts/3x-ui.sh" ${INSTALLER_ARGS[@]+"${INSTALLER_ARGS[@]}"} </dev/tty || die "установщик остановился с ошибкой — смотрите его вывод выше"
else
bash "$tmp/scripts/3x-ui.sh" ${INSTALLER_ARGS[@]+"${INSTALLER_ARGS[@]}"} </dev/null || die "установщик остановился с ошибкой (нет терминала — вопросы задать некому)"
fi
command -v kit >/dev/null || die "после установки нет команды kit — что-то пошло не так, смотрите вывод выше"
# ------------------------------------------------------------------ наши дополнения
ask_yes() { # "вопрос" y|n — по умолчанию
local q=$1 def=$2 ans=""
interactive || { [[ $def == y ]]; return; }
printf '\n%s [%s]: ' "$q" "$([[ $def == y ]] && echo 'Y/n' || echo 'y/N'])"
read -r -t 300 ans <"$ASK_SRC" 2>/dev/null || true
ans=${ans,,}; ans=${ans//[[:space:]]/}
[[ -z $ans && $def == y ]] && return 0
[[ $ans =~ ^(y|yes|д|да)$ ]]
}
# Сайт-заглушка: у автора это случайные «кофейни и пекарни», у нас — правдоподобный ИТ-сайт.
# Меняем не файл вручную, а командой форка: она же обновляет копию страницы в ответе Hysteria2,
# иначе 443/tcp и UDP-порт начнут отвечать разными страницами (это видно снаружи).
case $site in
off) say "заглушку не трогаю (--no-site): остаётся страница автора" ;;
random)
say "ставлю сайт-заглушку: случайный ИТ-сайт из нашего набора"
kit local site || warn "не удалось поставить заглушку — позже: kit local site"
;;
*)
say "ставлю сайт-заглушку №$site"
kit local site "$site" || warn "не удалось поставить заглушку — позже: kit local site $site"
;;
esac
if [[ $fix_time == yes ]]; then
say "kit fix: синхронизация времени и безопасные починки (авторская команда, ничего необратимого)"
kit fix || warn "kit fix завершился с замечаниями — посмотрите вывод"
fi
# ping и автообновления: --harden/--no-harden решают без вопросов, иначе спрашиваем
want_ping=$harden; want_update=$harden
[[ $want_ping == auto ]] && want_ping=yes
[[ $want_update == auto ]] && want_update=yes
if [[ $harden == auto ]] && interactive; then
if ask_yes "Закрыть ответ сервера на ping? Снаружи пинг перестанет отвечать (подписка и VPN не затрагиваются)." y; then want_ping=yes; else want_ping=no; fi
if ask_yes "Включить автообновления безопасности? Только security-обновления, без перезагрузки." y; then want_update=yes; else want_update=no; fi
fi
if [[ $want_ping == yes ]]; then
say "закрываю ответ на ping (правилом ufw)"
kit local ping off || warn "не удалось — повторите: kit local ping off"
fi
if [[ $want_update == yes ]]; then
say "включаю автообновления безопасности"
kit local upgrades on || warn "не удалось — повторите: kit local upgrades on"
fi
# порт SSH: по флагу или по вопросу. Смена делается в два шага: сначала ДОБАВИТЬ новый порт
# (прежний продолжает работать), потом закрыть прежние — иначе одна опечатка оставляет сервер без доступа.
ssh_ports_now() {
local p
p=$(kit local ssh --status 2>/dev/null | sed -n 's/^порты SSH сейчас: //p')
[[ -n $p ]] || p=$(sshd -T 2>/dev/null | awk '$1 == "port" {print $2}' | paste -sd, -)
[[ -n $p ]] || p=$(grep -rhiE '^[[:space:]]*Port[[:space:]]+[0-9]+' /etc/ssh/sshd_config /etc/ssh/sshd_config.d 2>/dev/null | awk '{print $2}' | paste -sd, -)
printf '%s' "${p:-22}"
}
ask_ssh_port() { # пусто — оставить как есть; иначе номер нового порта
local cur ans try
cur=$(ssh_ports_now)
interactive || return 0
for try in 1 2 3; do
printf '\n%sСменить порт SSH? Сейчас %s. Введите новый номер, Enter — оставить как есть: %s' "$B" "$cur" "$N" >&2
ans=""
read -r -t 300 ans <"$ASK_SRC" 2>/dev/null || true
ans=${ans//[[:space:]]/}
[[ -n $ans ]] || { say "порт SSH оставляю как есть ($cur)" >&2; return 0; }
if ! [[ $ans =~ ^[0-9]{1,5}$ ]] || ((10#$ans < 1024 || 10#$ans > 65535)); then
warn "«$ans» не похоже на порт (нужно число 1024–65535)"
continue
fi
if [[ ,$cur, == *",$ans,"* ]]; then
say "порт $ans уже слушает SSH — менять нечего" >&2; return 0
fi
if ss -H -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]$ans\$"; then
warn "порт $ans/tcp занят другой программой"
continue
fi
printf '%s' "$ans"; return 0
done
warn "оставляю прежний порт ($cur)"
}
[[ -n $ssh_port ]] || ssh_port=$(ask_ssh_port)
if [[ -n $ssh_port ]]; then
say "перевожу SSH на порт $ssh_port: новый добавляю, прежний пока остаётся рабочим"
kit local ssh "$ssh_port" || warn "порт не добавился — сделайте отдельно: kit local ssh $ssh_port"
cat <<EOF
${Y}Дальше обязательно (иначе можно потерять доступ к серверу):${N}
1) откройте ВТОРОЕ окно и проверьте вход: ssh -p $ssh_port root@<IP сервера>
2) вход работает — закройте прежние порты: kit local ssh --close-old $ssh_port
EOF
fi
# пароль root: в флагах его нет намеренно — значение в командной строке видно в списке процессов
if interactive; then
if ask_yes "Сменить пароль root прямо сейчас?" n; then
kit local passwd || warn "не получилось — повторите: kit local passwd"
fi
fi
# ------------------------------------------------------------------ проверка
if [[ $do_check == yes ]]; then
say "проверяю, что получилось"
kit check || warn "kit check нашёл замечания — читайте выше; безопасные починки: kit fix"
kit local hardening
fi
cat <<EOF
${G}${B}Готово.${N} Сервер поставлен из нашей сборки (3X-UI KIT $KIT_VERSION, канал: наш Gitea).
подписка и ссылки: kit user link admin (или страница подписки из вывода установщика)
если что-то не так: kit check --deep, затем kit fix
вернуться к авторскому каналу: положить авторский kit поверх и выполнить kit update --force
EOF