diff --git a/.github/workflows/bash-syntax.yml b/.github/workflows/bash-syntax.yml new file mode 100644 index 0000000..70064e2 --- /dev/null +++ b/.github/workflows/bash-syntax.yml @@ -0,0 +1,24 @@ +name: Bash syntax + +on: + push: + branches: ['**'] + pull_request: + +permissions: + contents: read + +jobs: + syntax: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Check every installer + run: | + set -e + for script in ./*.sh; do + echo "Checking ${script}" + bash -n "${script}" + done diff --git a/README.md b/README.md index 3bf9567..b4ef7fe 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,14 @@ | Файл | Назначение | Можно запускать отдельно | | --- | --- | --- | -| `ochenstarik-server-1.sh` | Предлагает выбрать часовой пояс, создаёт swap-файл 2 ГБ и задаёт `vm.swappiness=20` | Да | +| `ochenstarik-server-1.sh` | Предлагает выбрать часовой пояс и русификацию терминала, создаёт swap-файл 2 ГБ и задаёт `vm.swappiness=20` | Да | | `ochenstarik-server-2.sh` | Предлагает выбрать будущий SSH-порт и режим IPv4/IPv6, обновляет систему, ставит пакеты и настраивает UFW | Да | | `ochenstarik-server-user-3.sh` | Создаёт администратора, переносит SSH на порт из этапа 2, проверяет права и включает fail2ban | После установки необходимых пакетов; проще всего сначала запустить скрипт 2 | | `ochenstarik-server-tg-4.sh` | Отправляет уведомления в Telegram при успешном входе по SSH | После установки OpenSSH и `curl`; обычно после скрипта 2 или 3 | | `ochenstarik-server-vpn-5.sh` | Устанавливает Xray и направляет системный трафик через VLESS + REALITY | Да; зависимости устанавливаются автоматически | | `ochenstarik-server-panel-warp-6.sh` | Устанавливает 3x-ui и Cloudflare WARP, настраивает и проверяет их порты | Ubuntu 22.04/24.04/26.04 или Debian 12/13 | | `ochenstarik-server-backup-7.sh` | Создаёт неизменяемый первичный снимок и настраивает выбранные расписания резервного копирования | Да; зависимости устанавливаются автоматически | +| `ochenstarik-server-uninstall.sh` | Удаляет настройки, службы и данные проекта для повторной установки с начала | Да; запускать только из сохранённой SSH-сессии | Файлы `*.env.example` являются только примерами. Не записывайте настоящие пароли, токены и приватные SSH-ключи в Git. @@ -69,7 +70,11 @@ bash -n ./ИМЯ-СКРИПТА.sh Файл: `ochenstarik-server-1.sh`. -Сначала показывает нумерованное меню популярных часовых поясов, включая `Asia/Novosibirsk`, `Europe/Moscow` и `UTC`. Нужную зону достаточно выбрать по номеру. Для остальных зон предусмотрен полный список с последовательным выбором региона и часового пояса — вводить имя вручную не требуется. Затем скрипт создаёт `/swapfile` размером 2 ГБ, добавляет его в `/etc/fstab` и устанавливает `vm.swappiness=20`. Повторный запуск предусмотрен. +Сначала показывает нумерованное меню популярных часовых поясов, включая `Asia/Novosibirsk`, `Europe/Moscow` и `UTC`. Нужную зону достаточно выбрать по номеру. Для остальных зон предусмотрен полный список с последовательным выбором региона и часового пояса — вводить имя вручную не требуется. + +После выбора времени скрипт спрашивает, нужна ли русификация терминала. При согласии он устанавливает доступные пакеты русской локали, создаёт `ru_RU.UTF-8` и задаёт русский язык для новых терминальных сессий. Прежний `/etc/default/locale` и список только что установленных языковых пакетов сохраняются в `/etc/ochenstarik-server`, поэтому отдельный скрипт очистки может вернуть исходную локаль. Без явного согласия языковые настройки не меняются. + +Затем скрипт создаёт `/swapfile` размером 2 ГБ, добавляет его в `/etc/fstab` и устанавливает `vm.swappiness=20`. Повторный запуск предусмотрен. ```bash curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-1.sh @@ -321,6 +326,23 @@ sudo ./ochenstarik-server-backup-7.sh VPN, 3x-ui, WARP, Telegram-уведомления и автоматические расписания резервного копирования являются необязательными этапами. +## Удаление и повторная установка с начала + +Файл: `ochenstarik-server-uninstall.sh`. + +Скрипт удаляет управляемые правила UFW, настройки IPv4/IPv6, SSH drop-in, fail2ban-конфигурацию, Telegram-hook, Xray, 3x-ui, WARP, расписания резервного копирования, swap и служебные файлы проекта. Перед изменением SSH открывается порт `22/tcp`. Правило порта, через который работает текущая SSH-сессия, сохраняется, чтобы не потерять доступ. + +Удаление администратора и самих архивов резервных копий требует отдельных подтверждений. Общие системные пакеты, обновления Ubuntu и программы вроде OpenSSH, UFW и `curl` не удаляются: они могли существовать до запуска проекта и необходимы серверу. Пакеты русификации удаляются только если первый скрипт зафиксировал, что до русификации их не было. + +```bash +curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-uninstall.sh +chmod 700 ochenstarik-server-uninstall.sh +bash -n ochenstarik-server-uninstall.sh +sudo ./ochenstarik-server-uninstall.sh +``` + +Не закрывайте текущую SSH-сессию. После очистки сначала проверьте отдельное подключение через порт 22, затем снова запускайте нужные установочные скрипты. Старое правило текущего SSH-порта можно удалить только после успешной проверки нового входа. + ## Безопасность - Всегда проверяйте скачанные скрипты перед запуском от `root`. diff --git a/ochenstarik-server-1.sh b/ochenstarik-server-1.sh index 606b72b..b7db1b0 100644 --- a/ochenstarik-server-1.sh +++ b/ochenstarik-server-1.sh @@ -5,11 +5,90 @@ readonly SWAPFILE="/swapfile" readonly SWAPSIZE="2G" readonly SWAPPINESS="20" readonly SYSCTL_FILE="/etc/sysctl.d/60-hermes-swap.conf" +readonly CONFIG_DIR="/etc/ochenstarik-server" +readonly LOCALE_BACKUP="${CONFIG_DIR}/locale-before-russian.conf" +readonly LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-russian.absent" +readonly LOCALE_PACKAGES="${CONFIG_DIR}/russian-locale-installed-packages.list" log() { printf '[+] %s\n' "$*"; } warn() { printf '[!] %s\n' "$*" >&2; } die() { printf '[x] %s\n' "$*" >&2; exit 1; } +ask_yes_no() { + local prompt="$1" default_answer="${2:-no}" answer suffix + [[ "$default_answer" == yes || "$default_answer" == no ]] \ + || die "Invalid default answer: $default_answer" + [[ "$default_answer" == yes ]] && suffix='[Y/n]' || suffix='[y/N]' + + while :; do + read -rp "$prompt $suffix " answer || die "Input was interrupted" + answer="${answer:-$default_answer}" + case "${answer,,}" in + y|yes|д|да) return 0 ;; + n|no|н|нет) return 1 ;; + *) warn "Ответьте yes/no или да/нет" ;; + esac + done +} + +package_is_installed() { + dpkg-query -W -f='${Status}' "$1" 2>/dev/null | grep -Fqx 'install ok installed' +} + +install_russian_terminal_locale() { + local package_name + local -a packages=(locales) newly_installed=() + + ask_yes_no "Установить русскую локаль терминала ru_RU.UTF-8?" no || { + log "Русификация терминала пропущена" + return 0 + } + + for package_name in apt-get apt-cache dpkg-query; do + command -v "$package_name" >/dev/null 2>&1 \ + || die "Required command not found: $package_name" + done + + log "Обновление списка пакетов для русификации" + apt-get update + + install -d -m 700 -o root -g root "$CONFIG_DIR" + if [[ ! -e "$LOCALE_BACKUP" && ! -e "$LOCALE_ABSENT_MARKER" ]]; then + if [[ -f /etc/default/locale && ! -L /etc/default/locale ]]; then + cp -a -- /etc/default/locale "$LOCALE_BACKUP" + chmod 600 "$LOCALE_BACKUP" + else + : > "$LOCALE_ABSENT_MARKER" + chmod 600 "$LOCALE_ABSENT_MARKER" + fi + fi + + for package_name in language-pack-ru manpages-ru; do + if apt-cache show "$package_name" >/dev/null 2>&1; then + packages+=("$package_name") + fi + done + for package_name in "${packages[@]}"; do + package_is_installed "$package_name" || newly_installed+=("$package_name") + done + + log "Установка пакетов русской локали" + export DEBIAN_FRONTEND=noninteractive + apt-get install -y "${packages[@]}" + locale-gen ru_RU.UTF-8 + update-locale LANG=ru_RU.UTF-8 LANGUAGE=ru_RU:ru LC_MESSAGES=ru_RU.UTF-8 + + touch "$LOCALE_PACKAGES" + for package_name in "${newly_installed[@]}"; do + grep -Fqx -- "$package_name" "$LOCALE_PACKAGES" \ + || printf '%s\n' "$package_name" >> "$LOCALE_PACKAGES" + done + chown root:root "$LOCALE_PACKAGES" + chmod 600 "$LOCALE_PACKAGES" + + log "Русская локаль включена. Она применится при следующем входе в терминал" +} + choose_numbered_option() { local prompt="$1" result_variable="$2" answer index shift 2 @@ -86,6 +165,7 @@ for command_name in awk blkid fallocate grep mkswap swapon sysctl timedatectl; d done choose_timezone +install_russian_terminal_locale log "Configuring ${SWAPSIZE} swap file" if [[ ! -e "$SWAPFILE" ]]; then diff --git a/ochenstarik-server-uninstall.sh b/ochenstarik-server-uninstall.sh new file mode 100644 index 0000000..5057d6c --- /dev/null +++ b/ochenstarik-server-uninstall.sh @@ -0,0 +1,342 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +readonly CONFIG_DIR="/etc/ochenstarik-server" +readonly MANAGED_PORTS_CONFIG="${CONFIG_DIR}/ufw-managed-ports.conf" +readonly IP_FAMILY_CONFIG="${CONFIG_DIR}/ip-family.conf" +readonly SSH_PORT_CONFIG="${CONFIG_DIR}/ssh-port.conf" +readonly IPV6_SYSCTL_FILE="/etc/sysctl.d/99-zz-ochenstarik-disable-ipv6.conf" +readonly SWAPFILE="/swapfile" +readonly SWAP_SYSCTL_FILE="/etc/sysctl.d/60-hermes-swap.conf" +readonly SSHD_DROPIN="/etc/ssh/sshd_config.d/00-hermes-hardening.conf" +readonly FAIL2BAN_JAIL="/etc/fail2ban/jail.d/hermes.local" +readonly SETUP_ENV="/root/setup-data/env.txt" +readonly SETUP_HASH="/root/setup-data/password.hash" +readonly TELEGRAM_CONFIG="${CONFIG_DIR}/telegram.conf" +readonly TELEGRAM_SCRIPT="/usr/local/libexec/ochenstarik-ssh-login-telegram.sh" +readonly TELEGRAM_LEGACY_SCRIPT="/usr/local/libexec/ssh-login-telegram.sh" +readonly TELEGRAM_LOG="/var/log/ochenstarik-ssh-login-telegram.log" +readonly TELEGRAM_LOGROTATE="/etc/logrotate.d/ochenstarik-ssh-login-telegram" +readonly PAM_SSHD="/etc/pam.d/sshd" +readonly XRAY_STATE_DIR="/etc/ochenstarik-xray" +readonly XRAY_CONFIG_DIR="/usr/local/etc/xray" +readonly XRAY_SHARE_DIR="/usr/local/share/xray" +readonly XRAY_LOG_DIR="/var/log/xray" +readonly XRAY_HELPER="/usr/local/sbin/ochenstarik-xray-routing" +readonly XRAY_SERVICE="/etc/systemd/system/ochenstarik-xray-routing.service" +readonly BACKUP_CONFIG="${CONFIG_DIR}/backup.conf" +readonly BACKUP_RUNNER="/usr/local/sbin/ochenstarik-server-backup" +readonly LOCALE_BACKUP="${CONFIG_DIR}/locale-before-russian.conf" +readonly LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-russian.absent" +readonly LOCALE_PACKAGES="${CONFIG_DIR}/russian-locale-installed-packages.list" + +log() { printf '[+] %s\n' "$*"; } +warn() { printf '[!] %s\n' "$*" >&2; } +die() { printf '[x] %s\n' "$*" >&2; exit 1; } + +ask_yes_no() { + local prompt="$1" default_answer="${2:-no}" answer suffix + [[ "$default_answer" == yes || "$default_answer" == no ]] \ + || die "Некорректный ответ по умолчанию" + [[ "$default_answer" == yes ]] && suffix='[Y/n]' || suffix='[y/N]' + while :; do + read -rp "$prompt $suffix " answer || die "Ввод прерван" + answer="${answer:-$default_answer}" + case "${answer,,}" in + y|yes|д|да) return 0 ;; + n|no|н|нет) return 1 ;; + *) warn "Ответьте yes/no или да/нет" ;; + esac + done +} + +service_exists() { + command -v systemctl >/dev/null 2>&1 && systemctl cat "$1" >/dev/null 2>&1 +} + +stop_disable_service() { + local unit="$1" + service_exists "$unit" || return 0 + systemctl disable --now "$unit" >/dev/null 2>&1 || true + systemctl reset-failed "$unit" >/dev/null 2>&1 || true +} + +is_valid_port() { + [[ "$1" =~ ^[0-9]{1,5}$ ]] && ((10#$1 >= 1 && 10#$1 <= 65535)) +} + +current_ssh_port() { + local port="" + if [[ -n "${SSH_CONNECTION:-}" ]]; then + port="${SSH_CONNECTION##* }" + is_valid_port "$port" && printf '%s' "$((10#$port))" + fi +} + +remove_ufw_rules() { + local rule port protocol active_port + command -v ufw >/dev/null 2>&1 || return 0 + active_port="$(current_ssh_port)" + + if LANG=C ufw status 2>/dev/null | grep -q '^Status: active'; then + ufw allow 22/tcp >/dev/null + log "Порт 22/tcp открыт перед сбросом SSH" + fi + + [[ -f "$MANAGED_PORTS_CONFIG" && ! -L "$MANAGED_PORTS_CONFIG" ]] || return 0 + while IFS= read -r rule || [[ -n "$rule" ]]; do + [[ "$rule" =~ ^([0-9]{1,5})/(tcp|udp)$ ]] || continue + port="$((10#${BASH_REMATCH[1]}))" + protocol="${BASH_REMATCH[2]}" + if [[ "$protocol" == tcp && "$port" == 22 ]]; then + continue + fi + if [[ -n "$active_port" && "$protocol" == tcp && "$port" == "$active_port" ]]; then + warn "Правило текущего SSH-порта ${port}/tcp оставлено для защиты соединения" + continue + fi + while ufw --force delete allow from 0.0.0.0/0 to any port "$port" proto "$protocol" \ + >/dev/null 2>&1; do :; done + while ufw --force delete allow from ::/0 to any port "$port" proto "$protocol" \ + >/dev/null 2>&1; do :; done + while ufw --force delete allow "${port}/${protocol}" >/dev/null 2>&1; do :; done + done < "$MANAGED_PORTS_CONFIG" + ufw reload >/dev/null 2>&1 || true + log "Управляемые правила UFW удалены" +} + +restore_ipv6_defaults() { + rm -f -- "$IPV6_SYSCTL_FILE" + if command -v sysctl >/dev/null 2>&1; then + sysctl -w net.ipv6.conf.all.disable_ipv6=0 >/dev/null 2>&1 || true + sysctl -w net.ipv6.conf.default.disable_ipv6=0 >/dev/null 2>&1 || true + fi + if [[ -f /etc/default/ufw && ! -L /etc/default/ufw ]]; then + if grep -q '^IPV6=' /etc/default/ufw; then + sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw + else + printf '\nIPV6=yes\n' >> /etc/default/ufw + fi + fi + command -v ufw >/dev/null 2>&1 && ufw reload >/dev/null 2>&1 || true +} + +reset_ssh_and_user() { + local ssh_service="" configured_user="" login_user="${SUDO_USER:-}" + + if [[ -f "$SETUP_ENV" && ! -L "$SETUP_ENV" ]]; then + configured_user="$(sed -n 's/^NEW_USERNAME=//p' "$SETUP_ENV" | head -n1 | tr -d '\r')" + fi + + rm -f -- "$SSHD_DROPIN" "$FAIL2BAN_JAIL" + if command -v sshd >/dev/null 2>&1; then + sshd -t || die "После удаления drop-in конфигурация SSH не прошла проверку" + fi + for ssh_service in ssh sshd; do + if service_exists "$ssh_service"; then + systemctl restart "$ssh_service" + log "SSH перезапущен; проверьте новый вход через порт 22" + break + fi + done + service_exists fail2ban && systemctl restart fail2ban >/dev/null 2>&1 || true + + if [[ -n "$configured_user" && "$configured_user" != root ]] && id "$configured_user" >/dev/null 2>&1; then + rm -f -- "/etc/sudoers.d/${configured_user}" + if ask_yes_no "Удалить созданного администратора ${configured_user} вместе с домашней папкой?" no; then + if [[ -n "$login_user" && "$configured_user" == "$login_user" ]]; then + warn "Нельзя удалить пользователя текущей sudo/SSH-сессии: ${configured_user}" + else + read -rp "Для удаления введите имя пользователя ${configured_user}: " confirmation + if [[ "$confirmation" == "$configured_user" ]]; then + userdel --remove "$configured_user" + log "Пользователь ${configured_user} удалён" + else + warn "Имя не совпало; пользователь оставлен" + fi + fi + else + warn "Пользователь ${configured_user} и его членство в группах оставлены" + fi + fi + + rm -f -- "$SETUP_ENV" "$SETUP_HASH" + rmdir /root/setup-data >/dev/null 2>&1 || true +} + +remove_telegram() { + if [[ -f "$PAM_SSHD" && ! -L "$PAM_SSHD" ]]; then + cp -a -- "$PAM_SSHD" "${PAM_SSHD}.bak.before-ochenstarik-uninstall.$(date +%F-%H%M%S)" + sed -i "\|${TELEGRAM_SCRIPT}|d;\|${TELEGRAM_LEGACY_SCRIPT}|d" "$PAM_SSHD" + fi + rm -f -- "$TELEGRAM_CONFIG" "$TELEGRAM_SCRIPT" "$TELEGRAM_LEGACY_SCRIPT" \ + "$TELEGRAM_LOG" "$TELEGRAM_LOGROTATE" + find "$CONFIG_DIR" -maxdepth 1 -type f -name 'telegram.conf.bak.*' -delete 2>/dev/null || true + command -v sshd >/dev/null 2>&1 && sshd -t \ + || warn "Не удалось проверить SSH после удаления Telegram PAM-hook" +} + +remove_xray() { + stop_disable_service ochenstarik-xray-rollback.timer + stop_disable_service ochenstarik-xray-rollback.service + stop_disable_service ochenstarik-xray-routing.service + [[ ! -x "$XRAY_HELPER" ]] || "$XRAY_HELPER" stop >/dev/null 2>&1 || true + if command -v nft >/dev/null 2>&1; then + nft list table ip ochenstarik_xray >/dev/null 2>&1 \ + && nft delete table ip ochenstarik_xray || true + nft list table ip6 ochenstarik_xray6 >/dev/null 2>&1 \ + && nft delete table ip6 ochenstarik_xray6 || true + fi + stop_disable_service xray.service + stop_disable_service xray@.service + rm -f -- "$XRAY_HELPER" "$XRAY_SERVICE" /etc/systemd/system/xray.service \ + /etc/systemd/system/xray@.service /usr/local/bin/xray + rm -rf -- "$XRAY_STATE_DIR" "$XRAY_CONFIG_DIR" "$XRAY_SHARE_DIR" "$XRAY_LOG_DIR" +} + +remove_panel_and_warp() { + stop_disable_service x-ui.service + if command -v warp-cli >/dev/null 2>&1; then + warp-cli --accept-tos disconnect >/dev/null 2>&1 || true + warp-cli --accept-tos registration delete >/dev/null 2>&1 || true + fi + stop_disable_service warp-svc.service + + rm -f -- /etc/systemd/system/x-ui.service /usr/bin/x-ui /usr/local/bin/x-ui + rm -rf -- /usr/local/x-ui /etc/x-ui + + if command -v apt-get >/dev/null 2>&1 && dpkg-query -W -f='${Status}' cloudflare-warp \ + 2>/dev/null | grep -Fqx 'install ok installed'; then + DEBIAN_FRONTEND=noninteractive apt-get purge -y cloudflare-warp + fi + rm -f -- /etc/apt/sources.list.d/cloudflare-client.list \ + /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg +} + +read_backup_root() { + local value="/var/backups/ochenstarik-server" + if [[ -f "$BACKUP_CONFIG" && ! -L "$BACKUP_CONFIG" ]]; then + [[ "$(stat -c '%u' "$BACKUP_CONFIG")" == 0 ]] \ + || die "Файл $BACKUP_CONFIG должен принадлежать root" + value="$(bash -c 'set -efu; source "$1"; printf "%s" "$BACKUP_ROOT"' \ + _ "$BACKUP_CONFIG")" + fi + [[ "$value" == /* && "$value" != / ]] || die "Опасный путь резервных копий: $value" + case "$value" in + /var|/var/backups|/home|/root|/mnt|/media|/etc|/usr|/opt) + warn "Общий каталог ${value} не будет очищен автоматически" + printf '' + return 0 + ;; + esac + printf '%s' "$value" +} + +remove_backup_automation() { + local schedule backup_root + for schedule in daily weekly monthly; do + stop_disable_service "ochenstarik-backup-${schedule}.timer" + done + rm -f -- /etc/systemd/system/ochenstarik-backup@.service \ + /etc/systemd/system/ochenstarik-backup-daily.timer \ + /etc/systemd/system/ochenstarik-backup-weekly.timer \ + /etc/systemd/system/ochenstarik-backup-monthly.timer "$BACKUP_RUNNER" + + backup_root="$(read_backup_root)" + if [[ -n "$backup_root" && -d "$backup_root" ]] \ + && ask_yes_no "Удалить все архивы из ${backup_root}?" no; then + read -rp 'Для необратимого удаления введите УДАЛИТЬ БЭКАПЫ: ' confirmation + if [[ "$confirmation" == 'УДАЛИТЬ БЭКАПЫ' ]]; then + if command -v chattr >/dev/null 2>&1; then + find "$backup_root" -xdev -type f -exec chattr -i -- {} + 2>/dev/null || true + fi + rm -rf -- "$backup_root/initial" "$backup_root/daily" \ + "$backup_root/weekly" "$backup_root/monthly" + log "Архивы проекта удалены" + else + warn "Фраза не совпала; архивы оставлены" + fi + else + warn "Архивы оставлены; они не помешают повторной установке" + fi + rm -f -- "$BACKUP_CONFIG" +} + +restore_locale() { + local package_name + local -a packages=() + if [[ -f "$LOCALE_BACKUP" && ! -L "$LOCALE_BACKUP" ]]; then + cp -a -- "$LOCALE_BACKUP" /etc/default/locale + log "Предыдущая системная локаль восстановлена" + elif [[ -f "$LOCALE_ABSENT_MARKER" && ! -L "$LOCALE_ABSENT_MARKER" ]]; then + rm -f -- /etc/default/locale + log "Созданный скриптом файл локали удалён" + fi + + if [[ -f "$LOCALE_PACKAGES" && ! -L "$LOCALE_PACKAGES" ]]; then + while IFS= read -r package_name || [[ -n "$package_name" ]]; do + [[ "$package_name" =~ ^[a-z0-9][a-z0-9+.-]*$ ]] && packages+=("$package_name") + done < "$LOCALE_PACKAGES" + if ((${#packages[@]} > 0)) && ask_yes_no \ + "Удалить пакеты русификации, которые ранее отсутствовали?" yes; then + DEBIAN_FRONTEND=noninteractive apt-get purge -y "${packages[@]}" + fi + fi + rm -f -- "$LOCALE_BACKUP" "$LOCALE_ABSENT_MARKER" "$LOCALE_PACKAGES" +} + +remove_swap() { + [[ -e "$SWAP_SYSCTL_FILE" || -e "$SWAPFILE" ]] || return 0 + ask_yes_no "Отключить и удалить ${SWAPFILE}?" yes || return 0 + swapoff "$SWAPFILE" >/dev/null 2>&1 || true + if [[ -f /etc/fstab && ! -L /etc/fstab ]]; then + cp -a -- /etc/fstab "/etc/fstab.bak.before-ochenstarik-uninstall.$(date +%F-%H%M%S)" + sed -i '\|^[[:space:]]*/swapfile[[:space:]].*[[:space:]]swap[[:space:]]|d' /etc/fstab + fi + rm -f -- "$SWAP_SYSCTL_FILE" + if [[ -f "$SWAPFILE" && ! -L "$SWAPFILE" ]]; then + rm -f -- "$SWAPFILE" + else + warn "${SWAPFILE} не является обычным файлом и не удалён" + fi + sysctl --system >/dev/null 2>&1 || true +} + +[[ "$EUID" -eq 0 ]] || die "Запустите скрипт от имени root" + +cat <<'WARNING' + +Этот скрипт удалит настройки, службы и данные, созданные проектом lightweight-server. +Он не откатывает обновления Ubuntu и не удаляет общие системные пакеты (OpenSSH, +UFW, curl, мультимедийные библиотеки и другие), потому что они могли существовать +до установки и нужны для доступа к серверу. Их наличие не мешает установке заново. + +Сначала будет открыт SSH-порт 22. Не закрывайте текущую сессию, пока не проверите +новый вход после очистки. +WARNING + +read -rp 'Для продолжения введите СБРОСИТЬ СЕРВЕР: ' final_confirmation +[[ "$final_confirmation" == 'СБРОСИТЬ СЕРВЕР' ]] || die "Очистка отменена" + +remove_ufw_rules +restore_ipv6_defaults +reset_ssh_and_user +remove_telegram +remove_xray +remove_panel_and_warp +remove_backup_automation +restore_locale +remove_swap + +rm -f -- "$SSH_PORT_CONFIG" "$IP_FAMILY_CONFIG" "$MANAGED_PORTS_CONFIG" +find "$CONFIG_DIR" -maxdepth 1 -type f -name '*.bak.*' -delete 2>/dev/null || true +rmdir "$CONFIG_DIR" >/dev/null 2>&1 || true +systemctl daemon-reload >/dev/null 2>&1 || true + +log "Очистка компонентов lightweight-server завершена" +printf '\nПроверьте вход в новой сессии: ssh -p 22 root@IP_СЕРВЕРА\n' +printf 'После проверки можно снова запускать скрипты установки по порядку.\n' +if [[ -n "$(current_ssh_port)" && "$(current_ssh_port)" != 22 ]]; then + printf 'Правило текущего старого SSH-порта %s/tcp оставлено. Удалите его после проверки нового входа.\n' \ + "$(current_ssh_port)" +fi