#!/usr/bin/env bash # # Первоначальная подготовка сервера Ubuntu 24.04 по инструкциям # https://wiki.hsrv.site/books/pervonacalnaia-nastroika-ubuntu # # Скрипт только готовит сервер: базовая защита + установка выбранного ПО. # Конфиги сервисов (Caddy, 3x-ui, Nebula, Telegraf) не трогаются — их либо # восстанавливают из бекапа, либо настраивают вручную. # # Запуск (от root): # curl -fsSL https://gitea.hsrv.site/mrleo1nid/vps_installer/raw/branch/main/setup.sh | sudo bash # # Все вопросы задаются через /dev/tty, поэтому запуск через пайп работает. # Ответы можно передать заранее через переменные окружения (тогда вопрос не задаётся): # NEW_USER, NEW_PASS, NEW_HOSTNAME, INSTALL_BLOCKLIST (y/n), BLOCKLIST_URL, # INSTALL_CADDY, INSTALL_DOCKER, INSTALL_TELEGRAF, INSTALL_NEBULA, NEBULA_LIGHTHOUSE, # INSTALL_3XUI, HARDEN_SSH, DO_REBOOT (все y/n) set -Eeuo pipefail # ---------- версии (как в вики) ---------- XUI_VERSION="v2.8.11" NEBULA_VERSION="1.10.3" REPO_RAW="https://gitea.hsrv.site/mrleo1nid/vps_installer/raw/branch/main" STATUSPAGE_DIR="/var/www/statuspage" LOG_FILE="/var/log/vps-setup.log" DEFAULT_BLOCKLIST_URL="https://raw.githubusercontent.com/jahlib/somebody-knocked-my-ssh/refs/heads/main/ban.txt" BAN_SCRIPT_URL="https://raw.githubusercontent.com/jahlib/somebody-knocked-my-ssh/refs/heads/main/ban.sh" # 00- — чтобы файл читался раньше 50-cloud-init.conf: sshd берёт ПЕРВОЕ найденное значение SSHD_HARDENING="/etc/ssh/sshd_config.d/00-hardening.conf" export DEBIAN_FRONTEND=noninteractive export NEEDRESTART_MODE=a # ---------- вывод ---------- if [[ -t 1 ]]; then C_RED=$'\e[31m' C_GRN=$'\e[32m' C_YLW=$'\e[33m' C_BLU=$'\e[1;34m' C_RST=$'\e[0m' else C_RED='' C_GRN='' C_YLW='' C_BLU='' C_RST='' fi step() { echo; echo "${C_BLU}==> $*${C_RST}"; } info() { echo " $*"; } ok() { echo "${C_GRN} ✓ $*${C_RST}"; } warn() { echo "${C_YLW} ! $*${C_RST}"; } die() { echo "${C_RED}ОШИБКА: $*${C_RST}" >&2; exit 1; } trap 'echo "${C_RED}Скрипт упал на строке $LINENO: $BASH_COMMAND${C_RST}" >&2' ERR # Что осталось сделать руками — выводится в конце TODO=() todo() { TODO+=("$*"); } # ---------- ввод (через /dev/tty, т.к. stdin занят пайпом curl) ---------- TTY=/dev/tty have_tty() { [[ -r $TTY && -w $TTY ]] && { : <"$TTY"; } 2>/dev/null; } ask() { # ask VAR "вопрос" [default] local __var=$1 __q=$2 __def=${3:-} __ans [[ -n ${!__var:-} ]] && return 0 have_tty || die "Нет терминала для вопроса «$__q». Задайте переменную $__var." if [[ -n $__def ]]; then read -r -p "$__q [$__def]: " __ans <"$TTY" __ans=${__ans:-$__def} else read -r -p "$__q: " __ans <"$TTY" fi printf -v "$__var" '%s' "$__ans" } ask_yn() { # ask_yn VAR "вопрос" y|n local __var=$1 __q=$2 __def=$3 __ans if [[ -z ${!__var:-} ]]; then have_tty || die "Нет терминала для вопроса «$__q». Задайте переменную $__var." local hint="[y/N]"; [[ $__def == y ]] && hint="[Y/n]" read -r -p "$__q $hint: " __ans <"$TTY" __ans=${__ans:-$__def} else __ans=${!__var} fi case ${__ans,,} in y|yes|д|да) printf -v "$__var" y ;; *) printf -v "$__var" n ;; esac } ask_password() { # ask_password VAR local __var=$1 p1 p2 [[ -n ${!__var:-} ]] && return 0 have_tty || die "Нет терминала для ввода пароля. Задайте переменную $__var." while true; do read -r -s -p "Пароль для $NEW_USER: " p1 <"$TTY"; echo >"$TTY" read -r -s -p "Повторите пароль: " p2 <"$TTY"; echo >"$TTY" if [[ -z $p1 ]]; then echo "Пароль не может быть пустым." >"$TTY" elif [[ $p1 != "$p2" ]]; then echo "Пароли не совпадают, ещё раз." >"$TTY" elif (( ${#p1} < 8 )); then echo "Слишком короткий, минимум 8 символов." >"$TTY" else break fi done printf -v "$__var" '%s' "$p1" } apt_install() { apt-get install -y -q -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold "$@" } # ---------- проверки ---------- [[ $EUID -eq 0 ]] || die "Запустите от root: curl -fsSL | sudo bash" . /etc/os-release [[ ${ID:-} == ubuntu ]] || die "Скрипт рассчитан на Ubuntu (обнаружено: ${PRETTY_NAME:-?})" [[ ${VERSION_ID:-} == 24.04 ]] || warn "Скрипт писался под Ubuntu 24.04, у вас ${PRETTY_NAME}. Продолжаю." ARCH=$(dpkg --print-architecture) # IP, с которого мы подключены (чтобы не забанить себя в fail2ban) CLIENT_IP="" if [[ -n ${SSH_CLIENT:-} ]]; then CLIENT_IP=${SSH_CLIENT%% *} else CLIENT_IP=$(who am i 2>/dev/null | sed -nE 's/.*\(([0-9a-fA-F.:]+)\).*/\1/p' || true) fi # ---------- сбор ответов ---------- step "Параметры установки" while true; do ask NEW_USER "Логин нового пользователя" if [[ $NEW_USER =~ ^[a-z_][a-z0-9_-]{0,31}$ && $NEW_USER != root ]]; then break; fi echo "Недопустимый логин (строчные латинские буквы, цифры, _ и -; не root)." NEW_USER="" done USER_EXISTS=n if id "$NEW_USER" &>/dev/null; then USER_EXISTS=y CONTINUE_EXISTING="" ask_yn CONTINUE_EXISTING "Пользователь $NEW_USER уже существует. Сменить ему пароль и продолжить?" n [[ $CONTINUE_EXISTING == y ]] || die "Отменено." fi ask_password NEW_PASS while true; do ask NEW_HOSTNAME "Имя машины (hostname, можно FQDN, напр. vps1.example.com)" "$(hostname)" if [[ ${#NEW_HOSTNAME} -le 253 && $NEW_HOSTNAME =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]; then NEW_HOSTNAME=${NEW_HOSTNAME,,} break fi echo "Недопустимое имя (латиница, цифры, дефис, части через точку)." NEW_HOSTNAME="" done ask_yn INSTALL_BLOCKLIST "Блокировать IP по внешнему списку (ipset + cron)?" y if [[ $INSTALL_BLOCKLIST == y ]]; then ask BLOCKLIST_URL "URL списка IP" "$DEFAULT_BLOCKLIST_URL" fi echo echo "Дополнительное ПО (ставится без настройки):" ask_yn INSTALL_CADDY " Caddy?" y ask_yn INSTALL_DOCKER " Docker + Docker Compose?" n ask_yn INSTALL_TELEGRAF " Telegraf?" n ask_yn INSTALL_NEBULA " Nebula $NEBULA_VERSION?" n if [[ $INSTALL_NEBULA == y ]]; then ask_yn NEBULA_LIGHTHOUSE " Этот сервер — lighthouse (открыть 4242/udp)?" n fi ask_yn INSTALL_3XUI " 3x-ui $XUI_VERSION? (установщик задаст свои вопросы)" n if [[ $INSTALL_NEBULA == y && $ARCH != amd64 && $ARCH != arm64 ]]; then die "Nebula: архитектура $ARCH не поддерживается скриптом (только amd64/arm64)" fi echo ask_yn HARDEN_SSH "В конце запретить вход root по SSH и заблокировать пароль root?" y # Всё дальнейшее пишем ещё и в лог (там будут и учётные данные 3x-ui — только для root) touch "$LOG_FILE" && chmod 600 "$LOG_FILE" exec > >(tee -a "$LOG_FILE") 2>&1 echo "===== $(date '+%F %T') vps-setup start =====" >>"$LOG_FILE" # ---------- 1. Обновление системы ---------- step "1. Обновление системы" apt-get update -q apt-get -y -q -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold upgrade apt_install unattended-upgrades curl ca-certificates gnupg cat >/etc/apt/apt.conf.d/20auto-upgrades <<'EOF' APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Unattended-Upgrade "1"; EOF ok "Система обновлена, автообновления безопасности включены" # ---------- 2. Пользователь с sudo ---------- step "2. Пользователь $NEW_USER" if [[ $USER_EXISTS == n ]]; then useradd -m -s /bin/bash "$NEW_USER" ok "Пользователь создан" fi echo "$NEW_USER:$NEW_PASS" | chpasswd unset NEW_PASS usermod -aG sudo "$NEW_USER" SUDOERS_FILE="/etc/sudoers.d/$NEW_USER" echo "$NEW_USER ALL=(ALL) NOPASSWD:ALL" >"$SUDOERS_FILE.tmp" chmod 440 "$SUDOERS_FILE.tmp" if visudo -c -f "$SUDOERS_FILE.tmp" >/dev/null; then mv "$SUDOERS_FILE.tmp" "$SUDOERS_FILE" ok "Пароль задан, sudo без пароля ($SUDOERS_FILE)" else rm -f "$SUDOERS_FILE.tmp" die "visudo отверг файл sudoers" fi # ---------- 3. Имя машины ---------- step "3. Имя машины: $NEW_HOSTNAME" SHORT_HOSTNAME=${NEW_HOSTNAME%%.*} hostnamectl set-hostname "$NEW_HOSTNAME" HOSTS_LINE="127.0.1.1 $NEW_HOSTNAME" [[ $SHORT_HOSTNAME != "$NEW_HOSTNAME" ]] && HOSTS_LINE+=" $SHORT_HOSTNAME" if grep -q '^127\.0\.1\.1[[:space:]]' /etc/hosts; then sed -i "s/^127\.0\.1\.1[[:space:]].*/$HOSTS_LINE/" /etc/hosts else echo "$HOSTS_LINE" >>/etc/hosts fi # иначе cloud-init вернёт старое имя при перезагрузке if [[ -d /etc/cloud/cloud.cfg.d ]]; then echo "preserve_hostname: true" >/etc/cloud/cloud.cfg.d/99-preserve-hostname.cfg fi ok "hostname: $(hostname) (в приглашении терминала — после переподключения)" # ---------- 4. iptables legacy ---------- step "4. Переключение на iptables-legacy" apt_install iptables ipset for t in iptables ip6tables arptables ebtables; do if [[ -x /usr/sbin/$t-legacy ]] && update-alternatives --list "$t" &>/dev/null; then update-alternatives --set "$t" "/usr/sbin/$t-legacy" >/dev/null fi done iptables --version | grep -q legacy || die "iptables не переключился на legacy" ok "$(iptables --version)" NEED_REBOOT=y # старые nft-правила уйдут только после перезагрузки # ---------- 5. Fail2ban ---------- step "5. Fail2ban" apt_install fail2ban python3-systemd IGNORE_IP="127.0.0.1/8 ::1 192.168.0.0/16 10.0.0.0/8" if [[ -n $CLIENT_IP ]]; then IGNORE_IP+=" $CLIENT_IP" info "Ваш текущий IP $CLIENT_IP добавлен в ignoreip" fi cat >/etc/fail2ban/jail.local </dev/null 2>&1 systemctl restart fail2ban sleep 2 fail2ban-client status sshd >/dev/null && ok "Fail2ban работает, jail sshd активен" \ || warn "Fail2ban не отвечает, проверьте: journalctl -u fail2ban" # ---------- 6. UFW ---------- step "6. Firewall (UFW)" apt_install ufw ufw default deny incoming >/dev/null ufw default allow outgoing >/dev/null ufw allow OpenSSH >/dev/null # если SSH висит не на 22 — открываем и его mkdir -p /run/sshd SSH_PORT=$(sshd -T 2>/dev/null | awk '/^port /{print $2; exit}' || true) if [[ -n $SSH_PORT && $SSH_PORT != 22 ]]; then ufw allow "$SSH_PORT/tcp" >/dev/null info "Открыт нестандартный SSH-порт $SSH_PORT" fi # 80 — для выпуска сертификатов Let's Encrypt (Caddy, 3x-ui), 443 — HTTPS ufw allow 80/tcp >/dev/null ufw allow 443/tcp >/dev/null ufw --force enable >/dev/null ok "UFW включён: SSH, 80, 443" # ---------- 7. Блокировка IP по списку ---------- if [[ $INSTALL_BLOCKLIST == y ]]; then step "7. Блокировка IP по внешнему списку" curl -fsSL "$BAN_SCRIPT_URL" -o /usr/local/sbin/ban.sh chmod +x /usr/local/sbin/ban.sh if [[ $BLOCKLIST_URL != "$DEFAULT_BLOCKLIST_URL" ]]; then ESCAPED_URL=$(printf '%s' "$BLOCKLIST_URL" | sed 's/[&|\\]/\\&/g') sed -i "s|^URL=.*|URL=\"$ESCAPED_URL\"|" /usr/local/sbin/ban.sh fi bash /usr/local/sbin/ban.sh || warn "ban.sh завершился с ошибкой" cat >/etc/cron.d/ban-blacklist <<'EOF' SHELL=/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin @reboot root sleep 60 && bash /usr/local/sbin/ban.sh >/dev/null 2>&1 0 */6 * * * root bash /usr/local/sbin/ban.sh >/dev/null 2>&1 EOF chmod 644 /etc/cron.d/ban-blacklist ok "ipset blacklist: $(ipset list blacklist 2>/dev/null | awk '/Number of entries/{print $4}') записей, обновление каждые 6 ч" else step "7. Блокировка IP — пропущено" fi # ---------- 8. Статус-страница ---------- step "8. Статус-страница $STATUSPAGE_DIR" if [[ -f $STATUSPAGE_DIR/index.html ]]; then info "$STATUSPAGE_DIR/index.html уже есть — не трогаю" else install -d -m 755 "$STATUSPAGE_DIR" curl -fsSL "$REPO_RAW/statuspage/index.html" -o "$STATUSPAGE_DIR/index.html" chmod 644 "$STATUSPAGE_DIR/index.html" ok "Заглушка положена в $STATUSPAGE_DIR/index.html" fi # ---------- 9. Docker ---------- if [[ $INSTALL_DOCKER == y ]]; then step "9. Docker + Docker Compose" for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do dpkg -s "$pkg" &>/dev/null && apt-get remove -y -q "$pkg" done install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc chmod a+r /etc/apt/keyrings/docker.asc echo "deb [arch=$ARCH signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $VERSION_CODENAME stable" \ >/etc/apt/sources.list.d/docker.list apt-get update -q apt_install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin systemctl enable docker.service containerd.service >/dev/null 2>&1 usermod -aG docker "$NEW_USER" ok "$(docker --version), $(docker compose version | head -n1)" info "$NEW_USER добавлен в группу docker" warn "Docker публикует порты в обход UFW — биндите на 127.0.0.1:PORT или фильтруйте в DOCKER-USER" else step "9. Docker — пропущено" fi # ---------- 10. Telegraf ---------- if [[ $INSTALL_TELEGRAF == y ]]; then step "10. Telegraf" mkdir -p /etc/apt/keyrings curl -fsSL https://repos.influxdata.com/influxdata-archive.key \ | gpg --batch --yes --dearmor -o /etc/apt/keyrings/influxdata.gpg echo "deb [signed-by=/etc/apt/keyrings/influxdata.gpg] https://repos.influxdata.com/ubuntu stable main" \ >/etc/apt/sources.list.d/influxdata.list apt-get update -q apt_install telegraf influxdata-archive-keyring # доступ telegraf к статусу fail2ban cat >/etc/sudoers.d/telegraf.tmp <<'EOF' telegraf ALL=(root) NOPASSWD: /usr/bin/fail2ban-client status telegraf ALL=(root) NOPASSWD: /usr/bin/fail2ban-client status * Defaults!/usr/bin/fail2ban-client !logfile, !syslog, !pam_session EOF chmod 440 /etc/sudoers.d/telegraf.tmp if visudo -c -f /etc/sudoers.d/telegraf.tmp >/dev/null; then mv /etc/sudoers.d/telegraf.tmp /etc/sudoers.d/telegraf else rm -f /etc/sudoers.d/telegraf.tmp warn "sudoers для telegraf не прошёл проверку — пропущен" fi # с конфигом из пакета он только сыпет ошибками в лог — запустим после настройки systemctl disable --now telegraf >/dev/null 2>&1 || true ok "$(telegraf --version) установлен, сервис выключен до настройки" todo "Telegraf: заполнить /etc/telegraf/telegraf.conf → telegraf --config /etc/telegraf/telegraf.conf --test → systemctl enable --now telegraf" else step "10. Telegraf — пропущено" fi # ---------- 11. Nebula ---------- if [[ $INSTALL_NEBULA == y ]]; then step "11. Nebula $NEBULA_VERSION" NEBULA_TMP=$(mktemp -d) curl -fsSL "https://github.com/slackhq/nebula/releases/download/v$NEBULA_VERSION/nebula-linux-$ARCH.tar.gz" \ -o "$NEBULA_TMP/nebula.tar.gz" tar -xzf "$NEBULA_TMP/nebula.tar.gz" -C /usr/local/bin nebula nebula-cert chmod +x /usr/local/bin/nebula /usr/local/bin/nebula-cert nebula -version | grep -q "$NEBULA_VERSION" || die "nebula -version не показывает $NEBULA_VERSION" mkdir -p /etc/nebula curl -fsSL "https://raw.githubusercontent.com/slackhq/nebula/v$NEBULA_VERSION/examples/config.yml" \ -o /etc/nebula/config.yml.example curl -fsSL "https://raw.githubusercontent.com/slackhq/nebula/v$NEBULA_VERSION/examples/service_scripts/nebula.service" \ -o /etc/systemd/system/nebula.service grep -q '^ExecStart=/usr/local/bin/nebula -config /etc/nebula/config.yml' /etc/systemd/system/nebula.service \ || warn "В nebula.service неожиданный ExecStart — проверьте пути" systemctl daemon-reload rm -rf "$NEBULA_TMP" if [[ ${NEBULA_LIGHTHOUSE:-n} == y ]]; then ufw allow 4242/udp >/dev/null info "Открыт 4242/udp для lighthouse" fi ok "$(nebula -version | head -n1), юнит установлен, сервис не запущен (нет сертификатов)" todo "Nebula: положить ca.crt, .crt, .key и config.yml в /etc/nebula (пример: config.yml.example), chmod 600 /etc/nebula/*.key → systemctl enable --now nebula" else step "11. Nebula — пропущено" fi # ---------- 12. 3x-ui ---------- # ставим до Caddy: установщику 3x-ui для сертификата Let's Encrypt нужен свободный порт 80 if [[ $INSTALL_3XUI == y ]]; then step "12. 3x-ui $XUI_VERSION" XUI_INSTALLER=$(mktemp) curl -fsSL "https://raw.githubusercontent.com/mhsanaei/3x-ui/$XUI_VERSION/install.sh" -o "$XUI_INSTALLER" info "Дальше — вопросы установщика 3x-ui (порт панели, SSL)." XUI_STDIN=/dev/null; have_tty && XUI_STDIN=$TTY if (cd /tmp && bash "$XUI_INSTALLER" "$XUI_VERSION" <"$XUI_STDIN"); then XUI_PORT=$(/usr/local/x-ui/x-ui setting -show true 2>/dev/null | grep -Eo 'port: [0-9]+' | awk '{print $2}' | head -n1 || true) if [[ -n $XUI_PORT ]]; then ufw allow "$XUI_PORT/tcp" >/dev/null ok "3x-ui установлен, порт панели $XUI_PORT/tcp открыт в UFW" else warn "Не удалось определить порт панели — откройте его: ufw allow /tcp" fi info "Логин/пароль/путь панели — выше в выводе установщика (и в $LOG_FILE)" else warn "Установщик 3x-ui завершился с ошибкой" fi rm -f "$XUI_INSTALLER" else step "12. 3x-ui — пропущено" fi # ---------- 13. Caddy ---------- if [[ $INSTALL_CADDY == y ]]; then step "13. Caddy" apt_install debian-keyring debian-archive-keyring apt-transport-https curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \ | gpg --batch --yes --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \ >/etc/apt/sources.list.d/caddy-stable.list apt-get update -q apt_install caddy ok "Caddy $(caddy version | awk '{print $1}') установлен (стандартный Caddyfile)" todo "Caddy: заполнить /etc/caddy/Caddyfile → caddy validate --config /etc/caddy/Caddyfile && systemctl reload caddy" else step "13. Caddy — пропущено" fi # ---------- 14. Отключение доступа root ---------- if [[ $HARDEN_SSH == y ]]; then step "14. Отключение доступа root" # вход новым пользователем — по паролю, поэтому PasswordAuthentication включён явно cat >"$SSHD_HARDENING" <<'EOF' PermitRootLogin no PasswordAuthentication yes KbdInteractiveAuthentication no PubkeyAuthentication yes EOF mkdir -p /run/sshd sshd -t || { rm -f "$SSHD_HARDENING"; die "sshd -t нашёл ошибку в конфигурации"; } systemctl restart ssh passwd -l root >/dev/null ok "Вход root по SSH запрещён, пароль root заблокирован" else step "14. Отключение доступа root — пропущено" fi # ---------- итог ---------- step "Итоговая проверка" info "hostname: $(hostname)" info "iptables: $(iptables --version)" info "UFW: $(ufw status | head -n1)" info "Fail2ban: $(systemctl is-active fail2ban || true)" if [[ $INSTALL_BLOCKLIST == y ]]; then info "Blacklist: $(ipset list blacklist 2>/dev/null | awk '/Number of entries/{print $4}') записей" fi if [[ $INSTALL_DOCKER == y ]]; then info "Docker: $(systemctl is-active docker || true)"; fi if [[ $INSTALL_TELEGRAF == y ]]; then info "Telegraf: установлен, выключен"; fi if [[ $INSTALL_NEBULA == y ]]; then info "Nebula: установлен, ждёт сертификатов"; fi if [[ $INSTALL_3XUI == y ]]; then info "3x-ui: $(systemctl is-active x-ui || true)"; fi if [[ $INSTALL_CADDY == y ]]; then info "Caddy: $(systemctl is-active caddy || true)"; fi info "root: $(passwd -S root | awk '{print $2}') (L = заблокирован)" info "sshd: $(sshd -T 2>/dev/null | grep -E '^(permitrootlogin|passwordauthentication) ' | tr '\n' ' ')" info "Лог: $LOG_FILE" if (( ${#TODO[@]} )); then step "Осталось настроить (или восстановить из бекапа)" for t in "${TODO[@]}"; do info "• $t"; done fi if [[ ${NEED_REBOOT:-n} == y || -f /var/run/reboot-required ]]; then echo warn "Нужна перезагрузка (обновление ядра / сброс старых nft-правил)." ask_yn DO_REBOOT "Перезагрузить сервер сейчас?" y if [[ $DO_REBOOT == y ]]; then ok "Перезагрузка через 5 секунд... Подключайтесь как: ssh $NEW_USER@<адрес>" sleep 5 reboot else info "Не забудьте: sudo reboot" fi fi ok "Готово."