lightweight-server/ochenstarik-server-uninstall.sh
2026-07-16 22:19:30 +07:00

390 lines
17 KiB
Bash
Executable file
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
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-selection.conf"
readonly LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-selection.absent"
readonly LOCALE_PACKAGES="${CONFIG_DIR}/locale-installed-packages.list"
readonly LEGACY_LOCALE_BACKUP="${CONFIG_DIR}/locale-before-russian.conf"
readonly LEGACY_LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-russian.absent"
readonly LEGACY_LOCALE_PACKAGES="${CONFIG_DIR}/russian-locale-installed-packages.list"
readonly PROGRAM_PACKAGES="${CONFIG_DIR}/step1-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" line key parsed_value
if [[ -f "$BACKUP_CONFIG" && ! -L "$BACKUP_CONFIG" ]]; then
[[ "$(stat -c '%u' "$BACKUP_CONFIG")" == 0 ]] \
|| die "Файл $BACKUP_CONFIG должен принадлежать root"
[[ "$(stat -c '%a' "$BACKUP_CONFIG")" =~ ^[0-7]*[0-5][0-5]$ ]] \
|| die "Файл $BACKUP_CONFIG не должен быть доступен для записи группе или всем"
parsed_value=""
while IFS= read -r line || [[ -n "$line" ]]; do
line="${line//$'\r'/}"
[[ -n "$line" && "$line" != \#* ]] || continue
[[ "$line" == *=* ]] || die "Некорректная строка в $BACKUP_CONFIG: $line"
key="${line%%=*}"
value="${line#*=}"
case "$key" in
BACKUP_ROOT)
[[ "$value" =~ ^/[A-Za-z0-9._/-]+$ ]] \
|| die "Некорректный BACKUP_ROOT в $BACKUP_CONFIG"
parsed_value="$value"
;;
*) die "Неизвестный ключ в $BACKUP_CONFIG: $key" ;;
esac
done < "$BACKUP_CONFIG"
value="${parsed_value:-$value}"
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 package_file
local -a packages=()
if [[ -f "$LOCALE_BACKUP" && ! -L "$LOCALE_BACKUP" ]]; then
cp -a -- "$LOCALE_BACKUP" /etc/default/locale
log "Предыдущая системная локаль восстановлена"
elif [[ -f "$LEGACY_LOCALE_BACKUP" && ! -L "$LEGACY_LOCALE_BACKUP" ]]; then
cp -a -- "$LEGACY_LOCALE_BACKUP" /etc/default/locale
log "Предыдущая системная локаль восстановлена"
elif [[ -f "$LOCALE_ABSENT_MARKER" && ! -L "$LOCALE_ABSENT_MARKER" ]]; then
rm -f -- /etc/default/locale
log "Созданный скриптом файл локали удалён"
elif [[ -f "$LEGACY_LOCALE_ABSENT_MARKER" \
&& ! -L "$LEGACY_LOCALE_ABSENT_MARKER" ]]; then
rm -f -- /etc/default/locale
log "Созданный скриптом файл локали удалён"
fi
for package_file in "$LOCALE_PACKAGES" "$LEGACY_LOCALE_PACKAGES"; do
[[ -f "$package_file" && ! -L "$package_file" ]] || continue
while IFS= read -r package_name || [[ -n "$package_name" ]]; do
[[ "$package_name" =~ ^[a-z0-9][a-z0-9+.-]*$ ]] \
&& [[ ! " ${packages[*]} " =~ " ${package_name} " ]] \
&& packages+=("$package_name")
done < "$package_file"
done
if ((${#packages[@]} > 0)) && ask_yes_no \
"Удалить языковые пакеты, которые ранее отсутствовали?" yes; then
DEBIAN_FRONTEND=noninteractive apt-get purge -y "${packages[@]}"
fi
rm -f -- "$LOCALE_BACKUP" "$LOCALE_ABSENT_MARKER" "$LOCALE_PACKAGES" \
"$LEGACY_LOCALE_BACKUP" "$LEGACY_LOCALE_ABSENT_MARKER" \
"$LEGACY_LOCALE_PACKAGES"
}
remove_step1_programs() {
local package_name
local -a packages=()
[[ -f "$PROGRAM_PACKAGES" && ! -L "$PROGRAM_PACKAGES" ]] || return 0
while IFS= read -r package_name || [[ -n "$package_name" ]]; do
[[ "$package_name" =~ ^[a-z0-9][a-z0-9+.-]*$ ]] && packages+=("$package_name")
done < "$PROGRAM_PACKAGES"
if ((${#packages[@]} > 0)) && ask_yes_no \
"Удалить дополнительные программы, установленные первым этапом?" yes; then
DEBIAN_FRONTEND=noninteractive apt-get purge -y "${packages[@]}"
fi
rm -f -- "$PROGRAM_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 и другие), потому что они нужны для доступа к серверу. Пакеты,
которые первый этап зафиксировал как новые, можно удалить отдельным подтверждением.
Сначала будет открыт 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_step1_programs
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