diff --git a/README.md b/README.md index ede7146..070008f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # vps_installer -Первоначальная настройка сервера Ubuntu 24.04 по [инструкции](https://wiki.hsrv.site/books/pervonacalnaia-nastroika-ubuntu/page/pervonacalnaia-nastroika-servera-ubuntu-2404). +Подготовка свежего сервера Ubuntu 24.04 по инструкциям из [вики](https://wiki.hsrv.site/books/pervonacalnaia-nastroika-ubuntu). + +Скрипт ставит базовую защиту и выбранное ПО, но **не настраивает конфиги сервисов** — +после него сервер готов либо к заливке бекапа, либо к ручной донастройке. ## Запуск @@ -10,30 +13,42 @@ curl -fsSL https://gitea.hsrv.site/mrleo1nid/vps_installer/raw/branch/main/setup.sh | sudo bash ``` -Скрипт спросит логин и пароль нового пользователя, имя машины, домен для Caddy и т.д. - ## Что делает -1. `apt upgrade`, включает unattended-upgrades -2. Создаёт пользователя с sudo без пароля (вход по SSH — по паролю) -3. Меняет hostname (+ `/etc/hosts`, cloud-init не перезапишет его при перезагрузке) -4. Переключает iptables на legacy -5. Ставит fail2ban (jail sshd, ваш текущий IP в ignoreip) -6. UFW: открыты SSH, 80, 443 -7. Caddy (опционально — с reverse_proxy на домен) -8. Блокировка IP по внешнему списку через ipset + cron (`/etc/cron.d/ban-blacklist`) -9. Запрещает вход root по SSH и блокирует пароль root -10. Предлагает перезагрузку +Всегда: -Лог: `/var/log/vps-setup.log`. +1. `apt upgrade`, unattended-upgrades +2. Пользователь с паролем и sudo без пароля +3. hostname (+ `/etc/hosts`, защита от перезаписи cloud-init) +4. iptables → legacy +5. fail2ban (jail sshd, ваш текущий IP в ignoreip) +6. UFW: SSH, 80, 443 +7. Блокировка IP по внешнему списку — ipset + cron *(можно отключить)* +13. Запрет входа root по SSH, блокировка пароля root *(можно отключить)* + +Опционально (только установка): + +| # | ПО | Версия | Состояние после скрипта | +|---|----|--------|-------------------------| +| 8 | Docker + Compose | из репо Docker | запущен, пользователь в группе `docker` | +| 9 | Telegraf | из репо InfluxData | установлен, **выключен** до настройки `telegraf.conf` | +| 10 | Nebula | **1.10.3** | бинарники + юнит, **не запущен** до появления сертификатов | +| 11 | 3x-ui | **v2.8.11** | официальный установщик (интерактивный), порт панели открыт в UFW | +| 12 | Caddy | из репо Caddy | установлен со стандартным Caddyfile | + +Версии 3x-ui и Nebula задаются в начале `setup.sh` (`XUI_VERSION`, `NEBULA_VERSION`). +В конце скрипт выводит список того, что осталось настроить. + +Лог: `/var/log/vps-setup.log` (права 600 — в нём учётные данные 3x-ui). ## Без вопросов Любой ответ можно передать переменной окружения: ```bash -curl -fsSL https://gitea.hsrv.site/mrleo1nid/vps_installer/raw/branch/main/setup.sh | sudo NEW_USER=admin NEW_HOSTNAME=vps1.example.com INSTALL_CADDY=y DOMAIN=example.com UPSTREAM=127.0.0.1:8080 INSTALL_BLOCKLIST=y HARDEN_SSH=y bash +curl -fsSL https://gitea.hsrv.site/mrleo1nid/vps_installer/raw/branch/main/setup.sh | sudo NEW_USER=admin NEW_HOSTNAME=vps1.example.com INSTALL_CADDY=y INSTALL_DOCKER=y bash ``` -Переменные: `NEW_USER`, `NEW_PASS`, `NEW_HOSTNAME`, `INSTALL_CADDY`, `DOMAIN`, `UPSTREAM`, -`INSTALL_BLOCKLIST`, `BLOCKLIST_URL`, `HARDEN_SSH`, `DO_REBOOT`. +Переменные: `NEW_USER`, `NEW_PASS`, `NEW_HOSTNAME`, `INSTALL_BLOCKLIST`, `BLOCKLIST_URL`, +`INSTALL_CADDY`, `INSTALL_DOCKER`, `INSTALL_TELEGRAF`, `INSTALL_NEBULA`, `NEBULA_LIGHTHOUSE`, +`INSTALL_3XUI`, `HARDEN_SSH`, `DO_REBOOT` (y/n). diff --git a/setup.sh b/setup.sh index 7715127..b7e2574 100755 --- a/setup.sh +++ b/setup.sh @@ -1,18 +1,27 @@ #!/usr/bin/env bash # -# Первоначальная настройка сервера Ubuntu 24.04 -# по инструкции: https://wiki.hsrv.site/books/pervonacalnaia-nastroika-ubuntu/page/pervonacalnaia-nastroika-servera-ubuntu-2404 +# Первоначальная подготовка сервера 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_CADDY (y/n), DOMAIN, UPSTREAM, -# INSTALL_BLOCKLIST (y/n), BLOCKLIST_URL, HARDEN_SSH (y/n) +# 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" + 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" @@ -36,6 +45,10 @@ 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; } @@ -82,7 +95,6 @@ ask_password() { # ask_password VAR printf -v "$__var" '%s' "$p1" } - apt_install() { apt-get install -y -q -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold "$@" } @@ -92,6 +104,7 @@ apt_install() { . /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="" @@ -128,23 +141,31 @@ while true; do NEW_HOSTNAME="" done -ask_yn INSTALL_CADDY "Установить Caddy (reverse proxy с автоматическим HTTPS)?" y -if [[ $INSTALL_CADDY == y ]]; then - ask DOMAIN "Домен для Caddy (пусто — настроить позже)" "-" - [[ $DOMAIN == "-" ]] && DOMAIN="" - if [[ -n $DOMAIN ]]; then - ask UPSTREAM "Куда проксировать" "127.0.0.1:8080" - fi -fi - 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" @@ -174,14 +195,14 @@ 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)" + ok "Пароль задан, sudo без пароля ($SUDOERS_FILE)" else rm -f "$SUDOERS_FILE.tmp" die "visudo отверг файл sudoers" fi -# ---------- Имя машины ---------- -step "Имя машины: $NEW_HOSTNAME" +# ---------- 3. Имя машины ---------- +step "3. Имя машины: $NEW_HOSTNAME" SHORT_HOSTNAME=${NEW_HOSTNAME%%.*} hostnamectl set-hostname "$NEW_HOSTNAME" HOSTS_LINE="127.0.1.1 $NEW_HOSTNAME" @@ -197,8 +218,8 @@ if [[ -d /etc/cloud/cloud.cfg.d ]]; then fi ok "hostname: $(hostname) (в приглашении терминала — после переподключения)" -# ---------- 3. iptables legacy ---------- -step "3. Переключение на iptables-legacy" +# ---------- 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 @@ -209,8 +230,8 @@ iptables --version | grep -q legacy || die "iptables не переключилс ok "$(iptables --version)" NEED_REBOOT=y # старые nft-правила уйдут только после перезагрузки -# ---------- 4. Fail2ban ---------- -step "4. Fail2ban" +# ---------- 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 @@ -235,8 +256,8 @@ sleep 2 fail2ban-client status sshd >/dev/null && ok "Fail2ban работает, jail sshd активен" \ || warn "Fail2ban не отвечает, проверьте: journalctl -u fail2ban" -# ---------- 5. UFW ---------- -step "5. Firewall (UFW)" +# ---------- 6. UFW ---------- +step "6. Firewall (UFW)" apt_install ufw ufw default deny incoming >/dev/null ufw default allow outgoing >/dev/null @@ -248,53 +269,12 @@ 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" -# ---------- 6. Caddy ---------- -if [[ $INSTALL_CADDY == y ]]; then - step "6. 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}') установлен" - - if [[ -n ${DOMAIN:-} ]]; then - DOMAIN_IPS=$(getent ahostsv4 "$DOMAIN" | awk '{print $1}' | sort -u | tr '\n' ' ' || true) - PUBLIC_IP=$(curl -4 -fsS --max-time 5 https://api.ipify.org 2>/dev/null || true) - if [[ -z $DOMAIN_IPS ]]; then - warn "$DOMAIN не резолвится — сертификат не выпустится, пока не будет A-записи" - elif [[ -n $PUBLIC_IP && " $DOMAIN_IPS" != *" $PUBLIC_IP "* ]]; then - warn "$DOMAIN указывает на $DOMAIN_IPS, а IP сервера $PUBLIC_IP" - else - ok "$DOMAIN → $DOMAIN_IPS" - fi - - [[ -f /etc/caddy/Caddyfile ]] && cp /etc/caddy/Caddyfile "/etc/caddy/Caddyfile.bak.$(date +%s)" - cat >/etc/caddy/Caddyfile </dev/null 2>&1; then - systemctl reload caddy || systemctl restart caddy - ok "Caddyfile: $DOMAIN → $UPSTREAM (логи: journalctl -u caddy -f)" - else - warn "Caddyfile не прошёл валидацию: caddy validate --config /etc/caddy/Caddyfile" - fi - else - info "Домен не задан — отредактируйте /etc/caddy/Caddyfile позже" - fi -else - step "6. Caddy — пропущено" -fi - # ---------- 7. Блокировка IP по списку ---------- if [[ $INSTALL_BLOCKLIST == y ]]; then step "7. Блокировка IP по внешнему списку" @@ -317,10 +297,136 @@ else step "7. Блокировка IP — пропущено" fi -# ---------- 8. Отключение доступа root ---------- +# ---------- 8. Docker ---------- +if [[ $INSTALL_DOCKER == y ]]; then + step "8. 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 "8. Docker — пропущено" +fi + +# ---------- 9. Telegraf ---------- +if [[ $INSTALL_TELEGRAF == y ]]; then + step "9. 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 "9. Telegraf — пропущено" +fi + +# ---------- 10. Nebula ---------- +if [[ $INSTALL_NEBULA == y ]]; then + step "10. 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 "10. Nebula — пропущено" +fi + +# ---------- 11. 3x-ui ---------- +# ставим до Caddy: установщику 3x-ui для сертификата Let's Encrypt нужен свободный порт 80 +if [[ $INSTALL_3XUI == y ]]; then + step "11. 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 "11. 3x-ui — пропущено" +fi + +# ---------- 12. Caddy ---------- +if [[ $INSTALL_CADDY == y ]]; then + step "12. 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 "12. Caddy — пропущено" +fi + +# ---------- 13. Отключение доступа root ---------- if [[ $HARDEN_SSH == y ]]; then - step "8. Отключение доступа root" - # вход новым пользователем — по паролю, поэтому PasswordAuthentication оставляем включённым + step "13. Отключение доступа root" + # вход новым пользователем — по паролю, поэтому PasswordAuthentication включён явно cat >"$SSHD_HARDENING" <<'EOF' PermitRootLogin no PasswordAuthentication yes @@ -333,27 +439,38 @@ EOF passwd -l root >/dev/null ok "Вход root по SSH запрещён, пароль root заблокирован" else - step "8. Отключение доступа root — пропущено" + step "13. Отключение доступа root — пропущено" fi # ---------- итог ---------- step "Итоговая проверка" +info "hostname: $(hostname)" info "iptables: $(iptables --version)" info "UFW: $(ufw status | head -n1)" -info "Fail2ban: $(systemctl is-active fail2ban)" -[[ $INSTALL_CADDY == y ]] && info "Caddy: $(systemctl is-active caddy)" -[[ $INSTALL_BLOCKLIST == y ]] && info "Blacklist: $(ipset list blacklist 2>/dev/null | awk '/Number of entries/{print $4}') записей" +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-правил)." - DO_REBOOT="" ask_yn DO_REBOOT "Перезагрузить сервер сейчас?" y if [[ $DO_REBOOT == y ]]; then - ok "Перезагрузка через 5 секунд..." + ok "Перезагрузка через 5 секунд... Подключайтесь как: ssh $NEW_USER@<адрес>" sleep 5 reboot else