Add optional Docker, Telegraf, Nebula, 3x-ui installs; drop service configs
The script now only prepares the server: services are installed without configuration so they can be restored from backup or set up manually. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
52c01f225b
commit
ad4400f3d3
2 files changed
+224
-92
No files matched your search
@@ -1,6 +1,9 @@
|
|||||||
# vps_installer
|
# 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
|
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
|
```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`,
|
Переменные: `NEW_USER`, `NEW_PASS`, `NEW_HOSTNAME`, `INSTALL_BLOCKLIST`, `BLOCKLIST_URL`,
|
||||||
`INSTALL_BLOCKLIST`, `BLOCKLIST_URL`, `HARDEN_SSH`, `DO_REBOOT`.
|
`INSTALL_CADDY`, `INSTALL_DOCKER`, `INSTALL_TELEGRAF`, `INSTALL_NEBULA`, `NEBULA_LIGHTHOUSE`,
|
||||||
|
`INSTALL_3XUI`, `HARDEN_SSH`, `DO_REBOOT` (y/n).
|
||||||
@@ -1,18 +1,27 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
#
|
#
|
||||||
# Первоначальная настройка сервера Ubuntu 24.04
|
# Первоначальная подготовка сервера Ubuntu 24.04 по инструкциям
|
||||||
# по инструкции: https://wiki.hsrv.site/books/pervonacalnaia-nastroika-ubuntu/page/pervonacalnaia-nastroika-servera-ubuntu-2404
|
# https://wiki.hsrv.site/books/pervonacalnaia-nastroika-ubuntu
|
||||||
|
#
|
||||||
|
# Скрипт только готовит сервер: базовая защита + установка выбранного ПО.
|
||||||
|
# Конфиги сервисов (Caddy, 3x-ui, Nebula, Telegraf) не трогаются — их либо
|
||||||
|
# восстанавливают из бекапа, либо настраивают вручную.
|
||||||
#
|
#
|
||||||
# Запуск (от root):
|
# Запуск (от root):
|
||||||
# curl -fsSL https://gitea.hsrv.site/mrleo1nid/vps_installer/raw/branch/main/setup.sh | sudo bash
|
# curl -fsSL https://gitea.hsrv.site/mrleo1nid/vps_installer/raw/branch/main/setup.sh | sudo bash
|
||||||
#
|
#
|
||||||
# Все вопросы задаются через /dev/tty, поэтому запуск через пайп работает.
|
# Все вопросы задаются через /dev/tty, поэтому запуск через пайп работает.
|
||||||
# Ответы можно передать заранее через переменные окружения (тогда вопрос не задаётся):
|
# Ответы можно передать заранее через переменные окружения (тогда вопрос не задаётся):
|
||||||
# NEW_USER, NEW_PASS, NEW_HOSTNAME, INSTALL_CADDY (y/n), DOMAIN, UPSTREAM,
|
# NEW_USER, NEW_PASS, NEW_HOSTNAME, INSTALL_BLOCKLIST (y/n), BLOCKLIST_URL,
|
||||||
# INSTALL_BLOCKLIST (y/n), BLOCKLIST_URL, HARDEN_SSH (y/n)
|
# INSTALL_CADDY, INSTALL_DOCKER, INSTALL_TELEGRAF, INSTALL_NEBULA, NEBULA_LIGHTHOUSE,
|
||||||
|
# INSTALL_3XUI, HARDEN_SSH, DO_REBOOT (все y/n)
|
||||||
|
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# ---------- версии (как в вики) ----------
|
||||||
|
XUI_VERSION="v2.8.11"
|
||||||
|
NEBULA_VERSION="1.10.3"
|
||||||
|
|
||||||
LOG_FILE="/var/log/vps-setup.log"
|
LOG_FILE="/var/log/vps-setup.log"
|
||||||
DEFAULT_BLOCKLIST_URL="https://raw.githubusercontent.com/jahlib/somebody-knocked-my-ssh/refs/heads/main/ban.txt"
|
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"
|
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
|
trap 'echo "${C_RED}Скрипт упал на строке $LINENO: $BASH_COMMAND${C_RST}" >&2' ERR
|
||||||
|
|
||||||
|
# Что осталось сделать руками — выводится в конце
|
||||||
|
TODO=()
|
||||||
|
todo() { TODO+=("$*"); }
|
||||||
|
|
||||||
# ---------- ввод (через /dev/tty, т.к. stdin занят пайпом curl) ----------
|
# ---------- ввод (через /dev/tty, т.к. stdin занят пайпом curl) ----------
|
||||||
TTY=/dev/tty
|
TTY=/dev/tty
|
||||||
have_tty() { [[ -r $TTY && -w $TTY ]] && { : <"$TTY"; } 2>/dev/null; }
|
have_tty() { [[ -r $TTY && -w $TTY ]] && { : <"$TTY"; } 2>/dev/null; }
|
||||||
@@ -82,7 +95,6 @@ ask_password() { # ask_password VAR
|
|||||||
printf -v "$__var" '%s' "$p1"
|
printf -v "$__var" '%s' "$p1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
apt_install() {
|
apt_install() {
|
||||||
apt-get install -y -q -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold "$@"
|
apt-get install -y -q -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold "$@"
|
||||||
}
|
}
|
||||||
@@ -92,6 +104,7 @@ apt_install() {
|
|||||||
. /etc/os-release
|
. /etc/os-release
|
||||||
[[ ${ID:-} == ubuntu ]] || die "Скрипт рассчитан на Ubuntu (обнаружено: ${PRETTY_NAME:-?})"
|
[[ ${ID:-} == ubuntu ]] || die "Скрипт рассчитан на Ubuntu (обнаружено: ${PRETTY_NAME:-?})"
|
||||||
[[ ${VERSION_ID:-} == 24.04 ]] || warn "Скрипт писался под Ubuntu 24.04, у вас ${PRETTY_NAME}. Продолжаю."
|
[[ ${VERSION_ID:-} == 24.04 ]] || warn "Скрипт писался под Ubuntu 24.04, у вас ${PRETTY_NAME}. Продолжаю."
|
||||||
|
ARCH=$(dpkg --print-architecture)
|
||||||
|
|
||||||
# IP, с которого мы подключены (чтобы не забанить себя в fail2ban)
|
# IP, с которого мы подключены (чтобы не забанить себя в fail2ban)
|
||||||
CLIENT_IP=""
|
CLIENT_IP=""
|
||||||
@@ -128,23 +141,31 @@ while true; do
|
|||||||
NEW_HOSTNAME=""
|
NEW_HOSTNAME=""
|
||||||
done
|
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
|
ask_yn INSTALL_BLOCKLIST "Блокировать IP по внешнему списку (ipset + cron)?" y
|
||||||
if [[ $INSTALL_BLOCKLIST == y ]]; then
|
if [[ $INSTALL_BLOCKLIST == y ]]; then
|
||||||
ask BLOCKLIST_URL "URL списка IP" "$DEFAULT_BLOCKLIST_URL"
|
ask BLOCKLIST_URL "URL списка IP" "$DEFAULT_BLOCKLIST_URL"
|
||||||
fi
|
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
|
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
|
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||||
echo "===== $(date '+%F %T') vps-setup start =====" >>"$LOG_FILE"
|
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"
|
chmod 440 "$SUDOERS_FILE.tmp"
|
||||||
if visudo -c -f "$SUDOERS_FILE.tmp" >/dev/null; then
|
if visudo -c -f "$SUDOERS_FILE.tmp" >/dev/null; then
|
||||||
mv "$SUDOERS_FILE.tmp" "$SUDOERS_FILE"
|
mv "$SUDOERS_FILE.tmp" "$SUDOERS_FILE"
|
||||||
ok "sudo без пароля настроен ($SUDOERS_FILE)"
|
ok "Пароль задан, sudo без пароля ($SUDOERS_FILE)"
|
||||||
else
|
else
|
||||||
rm -f "$SUDOERS_FILE.tmp"
|
rm -f "$SUDOERS_FILE.tmp"
|
||||||
die "visudo отверг файл sudoers"
|
die "visudo отверг файл sudoers"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------- Имя машины ----------
|
# ---------- 3. Имя машины ----------
|
||||||
step "Имя машины: $NEW_HOSTNAME"
|
step "3. Имя машины: $NEW_HOSTNAME"
|
||||||
SHORT_HOSTNAME=${NEW_HOSTNAME%%.*}
|
SHORT_HOSTNAME=${NEW_HOSTNAME%%.*}
|
||||||
hostnamectl set-hostname "$NEW_HOSTNAME"
|
hostnamectl set-hostname "$NEW_HOSTNAME"
|
||||||
HOSTS_LINE="127.0.1.1 $NEW_HOSTNAME"
|
HOSTS_LINE="127.0.1.1 $NEW_HOSTNAME"
|
||||||
@@ -197,8 +218,8 @@ if [[ -d /etc/cloud/cloud.cfg.d ]]; then
|
|||||||
fi
|
fi
|
||||||
ok "hostname: $(hostname) (в приглашении терминала — после переподключения)"
|
ok "hostname: $(hostname) (в приглашении терминала — после переподключения)"
|
||||||
|
|
||||||
# ---------- 3. iptables legacy ----------
|
# ---------- 4. iptables legacy ----------
|
||||||
step "3. Переключение на iptables-legacy"
|
step "4. Переключение на iptables-legacy"
|
||||||
apt_install iptables ipset
|
apt_install iptables ipset
|
||||||
for t in iptables ip6tables arptables ebtables; do
|
for t in iptables ip6tables arptables ebtables; do
|
||||||
if [[ -x /usr/sbin/$t-legacy ]] && update-alternatives --list "$t" &>/dev/null; then
|
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)"
|
ok "$(iptables --version)"
|
||||||
NEED_REBOOT=y # старые nft-правила уйдут только после перезагрузки
|
NEED_REBOOT=y # старые nft-правила уйдут только после перезагрузки
|
||||||
|
|
||||||
# ---------- 4. Fail2ban ----------
|
# ---------- 5. Fail2ban ----------
|
||||||
step "4. Fail2ban"
|
step "5. Fail2ban"
|
||||||
apt_install fail2ban python3-systemd
|
apt_install fail2ban python3-systemd
|
||||||
IGNORE_IP="127.0.0.1/8 ::1 192.168.0.0/16 10.0.0.0/8"
|
IGNORE_IP="127.0.0.1/8 ::1 192.168.0.0/16 10.0.0.0/8"
|
||||||
if [[ -n $CLIENT_IP ]]; then
|
if [[ -n $CLIENT_IP ]]; then
|
||||||
@@ -235,8 +256,8 @@ sleep 2
|
|||||||
fail2ban-client status sshd >/dev/null && ok "Fail2ban работает, jail sshd активен" \
|
fail2ban-client status sshd >/dev/null && ok "Fail2ban работает, jail sshd активен" \
|
||||||
|| warn "Fail2ban не отвечает, проверьте: journalctl -u fail2ban"
|
|| warn "Fail2ban не отвечает, проверьте: journalctl -u fail2ban"
|
||||||
|
|
||||||
# ---------- 5. UFW ----------
|
# ---------- 6. UFW ----------
|
||||||
step "5. Firewall (UFW)"
|
step "6. Firewall (UFW)"
|
||||||
apt_install ufw
|
apt_install ufw
|
||||||
ufw default deny incoming >/dev/null
|
ufw default deny incoming >/dev/null
|
||||||
ufw default allow outgoing >/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
|
ufw allow "$SSH_PORT/tcp" >/dev/null
|
||||||
info "Открыт нестандартный SSH-порт $SSH_PORT"
|
info "Открыт нестандартный SSH-порт $SSH_PORT"
|
||||||
fi
|
fi
|
||||||
|
# 80 — для выпуска сертификатов Let's Encrypt (Caddy, 3x-ui), 443 — HTTPS
|
||||||
ufw allow 80/tcp >/dev/null
|
ufw allow 80/tcp >/dev/null
|
||||||
ufw allow 443/tcp >/dev/null
|
ufw allow 443/tcp >/dev/null
|
||||||
ufw --force enable >/dev/null
|
ufw --force enable >/dev/null
|
||||||
ok "UFW включён: SSH, 80, 443"
|
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 <<EOF
|
|
||||||
$DOMAIN {
|
|
||||||
reverse_proxy $UPSTREAM
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
if caddy validate --config /etc/caddy/Caddyfile --adapter 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 по списку ----------
|
# ---------- 7. Блокировка IP по списку ----------
|
||||||
if [[ $INSTALL_BLOCKLIST == y ]]; then
|
if [[ $INSTALL_BLOCKLIST == y ]]; then
|
||||||
step "7. Блокировка IP по внешнему списку"
|
step "7. Блокировка IP по внешнему списку"
|
||||||
@@ -317,10 +297,136 @@ else
|
|||||||
step "7. Блокировка IP — пропущено"
|
step "7. Блокировка IP — пропущено"
|
||||||
fi
|
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, <node>.crt, <node>.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 <port>/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
|
if [[ $HARDEN_SSH == y ]]; then
|
||||||
step "8. Отключение доступа root"
|
step "13. Отключение доступа root"
|
||||||
# вход новым пользователем — по паролю, поэтому PasswordAuthentication оставляем включённым
|
# вход новым пользователем — по паролю, поэтому PasswordAuthentication включён явно
|
||||||
cat >"$SSHD_HARDENING" <<'EOF'
|
cat >"$SSHD_HARDENING" <<'EOF'
|
||||||
PermitRootLogin no
|
PermitRootLogin no
|
||||||
PasswordAuthentication yes
|
PasswordAuthentication yes
|
||||||
@@ -333,27 +439,38 @@ EOF
|
|||||||
passwd -l root >/dev/null
|
passwd -l root >/dev/null
|
||||||
ok "Вход root по SSH запрещён, пароль root заблокирован"
|
ok "Вход root по SSH запрещён, пароль root заблокирован"
|
||||||
else
|
else
|
||||||
step "8. Отключение доступа root — пропущено"
|
step "13. Отключение доступа root — пропущено"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---------- итог ----------
|
# ---------- итог ----------
|
||||||
step "Итоговая проверка"
|
step "Итоговая проверка"
|
||||||
|
info "hostname: $(hostname)"
|
||||||
info "iptables: $(iptables --version)"
|
info "iptables: $(iptables --version)"
|
||||||
info "UFW: $(ufw status | head -n1)"
|
info "UFW: $(ufw status | head -n1)"
|
||||||
info "Fail2ban: $(systemctl is-active fail2ban)"
|
info "Fail2ban: $(systemctl is-active fail2ban || true)"
|
||||||
[[ $INSTALL_CADDY == y ]] && info "Caddy: $(systemctl is-active caddy)"
|
if [[ $INSTALL_BLOCKLIST == y ]]; then
|
||||||
[[ $INSTALL_BLOCKLIST == y ]] && info "Blacklist: $(ipset list blacklist 2>/dev/null | awk '/Number of entries/{print $4}') записей"
|
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 "root: $(passwd -S root | awk '{print $2}') (L = заблокирован)"
|
||||||
info "sshd: $(sshd -T 2>/dev/null | grep -E '^(permitrootlogin|passwordauthentication) ' | tr '\n' ' ')"
|
info "sshd: $(sshd -T 2>/dev/null | grep -E '^(permitrootlogin|passwordauthentication) ' | tr '\n' ' ')"
|
||||||
info "Лог: $LOG_FILE"
|
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
|
if [[ ${NEED_REBOOT:-n} == y || -f /var/run/reboot-required ]]; then
|
||||||
echo
|
echo
|
||||||
warn "Нужна перезагрузка (обновление ядра / сброс старых nft-правил)."
|
warn "Нужна перезагрузка (обновление ядра / сброс старых nft-правил)."
|
||||||
DO_REBOOT=""
|
|
||||||
ask_yn DO_REBOOT "Перезагрузить сервер сейчас?" y
|
ask_yn DO_REBOOT "Перезагрузить сервер сейчас?" y
|
||||||
if [[ $DO_REBOOT == y ]]; then
|
if [[ $DO_REBOOT == y ]]; then
|
||||||
ok "Перезагрузка через 5 секунд..."
|
ok "Перезагрузка через 5 секунд... Подключайтесь как: ssh $NEW_USER@<адрес>"
|
||||||
sleep 5
|
sleep 5
|
||||||
reboot
|
reboot
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in new issue
Block a user