#!/usr/bin/env bash set -euo pipefail # Очистка сервера от установки HY2XS предыдущего поколения (0.x) и от # незавершённой установки v1. # # Почему это отдельный скрипт, а не шаг установщика: # # HY2XS v1 принципиально не поддерживает миграцию. Встроенная в installer # очистка вернула бы destructive migration logic обратно в путь свежей # установки — ровно то, от чего мы ушли. Установщик обязан обнаружить старую # установку, НИЧЕГО не изменить и отправить оператора сюда. # # Скрипт разрушительный. По умолчанию он ничего не делает: показывает план. # # sudo ./purge-v0.sh # план (dry-run) # sudo ./purge-v0.sh --apply --yes-i-know # выполнить # # ВАЖНО: скрипт удаляет базу админки вместе с учётными записями пиров. # Ссылки, выданные пользователям, перестанут работать. APPLY="false" CONFIRMED="false" log() { printf '[hy2xs-purge] %s\n' "$*"; } warn() { printf '[hy2xs-purge] WARNING: %s\n' "$*" >&2; } fail() { printf '[hy2xs-purge] ERROR: %s\n' "$*" >&2; exit 1; } # Флага «оставить бинарник Hysteria» здесь нет намеренно. # # /usr/local/bin/hysteria входит в список legacy-маркеров clean-host # контракта установщика. Скрипт, который оставлял бы его и при этом печатал # «хост чист для установки HY2XS v1», прямо противоречил бы следующему # запуску install.sh: тот отказался бы ставиться. # # Полезного сценария у флага тоже не было: свежая установка всё равно кладёт # собственную проверенную по SHA-256 версию Hysteria. usage() { cat >&2 <<'EOF' Usage: purge-v0.sh [--apply --yes-i-know] (без флагов) показать план очистки и выйти --apply выполнить очистку --yes-i-know подтверждение: обязателен вместе с --apply Скрипт удаляет службы, приложение, конфигурацию и БАЗУ ДАННЫХ админки. EOF exit 2 } parse_args() { while [ "$#" -gt 0 ]; do case "$1" in --apply) APPLY="true"; shift ;; --yes-i-know) CONFIRMED="true"; shift ;; -h|--help) usage ;; *) fail "unknown argument: $1" ;; esac done if [ "$APPLY" = "true" ] && [ "$CONFIRMED" != "true" ]; then fail "--apply requires an explicit --yes-i-know confirmation" fi } UNITS=( hysteria-server.service hy2xs-admin.service h-ui.service ) UNIT_FILES=( /etc/systemd/system/hysteria-server.service /etc/systemd/system/hy2xs-admin.service /etc/systemd/system/h-ui.service ) PATHS=( /etc/hy2xs /etc/hysteria /var/lib/hy2xs /var/lib/hy2xs-admin /var/lib/hysteria /var/log/hy2xs /opt/hy2xs-admin /usr/local/lib/hy2xs /usr/local/h-ui /usr/local/bin/hy2xs-orchestrator # Артефакты незавершённой операции в /run. Каталог отката хранит прежние # nftables.conf и hy2xs.nft вместе с маркером срабатывания guard, а замок # операций способен пережить убитый `kill -9` процесс оркестратора и не даст # запуститься следующей установке. /run — tmpfs, и перезагрузка убрала бы оба, # но очистка не имеет права требовать перезагрузки. /run/hy2xs /run/lock/hy2xs-orchestrator.lock ) # Candidate-файлы firewall: остаются на диске, если операция упала между # проверкой `nft -c` и подстановкой в production-путь. CANDIDATES=( /etc/nftables.conf.candidate /etc/nftables.d/hy2xs.nft.candidate ) NFT_FRAGMENT=/etc/nftables.d/hy2xs.nft require_root() { [ "$(id -u)" = "0" ] || fail "purge-v0.sh must run as root" } # Таймеры отката firewall переживают неудачную установку и продолжат менять # ruleset уже после очистки, если их не снять. rollback_guard_units() { systemctl list-units --all --no-legend 'hy2xs-fw-rollback-*.timer' 'hy2xs-fw-rollback-*.service' 2>/dev/null \ | awk '{print $1}' \ | grep -E '^hy2xs-fw-rollback-' || true } show_plan() { log "будут остановлены и выключены службы:" local unit for unit in "${UNITS[@]}"; do printf ' - %s\n' "$unit" done local guard guard="$(rollback_guard_units)" if [ -n "$guard" ]; then log "будут сняты таймеры отката firewall:" printf ' - %s\n' $guard fi log "будут удалены пути:" local path for path in "${UNIT_FILES[@]}" "${PATHS[@]}" "${CANDIDATES[@]}" "$NFT_FRAGMENT"; do if [ -e "$path" ]; then printf ' - %s (существует)\n' "$path" else printf ' - %s (отсутствует)\n' "$path" fi done log "будет удалён /usr/local/bin/hysteria" log "из /etc/nftables.conf будет убрана строка include для $NFT_FRAGMENT" warn "БАЗА ДАННЫХ админки (/var/lib/hy2xs-admin) удаляется: пиры и их ссылки будут потеряны" warn "sshd, сеть и правила firewall вне фрагмента HY2XS не изменяются" } stop_units() { local unit for unit in "${UNITS[@]}"; do systemctl stop "$unit" >/dev/null 2>&1 || true systemctl disable "$unit" >/dev/null 2>&1 || true systemctl reset-failed "$unit" >/dev/null 2>&1 || true done local guard guard="$(rollback_guard_units)" if [ -n "$guard" ]; then local guard_unit for guard_unit in $guard; do systemctl stop "$guard_unit" >/dev/null 2>&1 || true systemctl disable "$guard_unit" >/dev/null 2>&1 || true systemctl reset-failed "$guard_unit" >/dev/null 2>&1 || true rm -f "/etc/systemd/system/$guard_unit" done fi } remove_paths() { local path for path in "${UNIT_FILES[@]}"; do rm -f "$path" done systemctl daemon-reload || true for path in "${PATHS[@]}"; do rm -rf "$path" done for path in "${CANDIDATES[@]}"; do rm -f "$path" done rm -f /usr/local/bin/hysteria } # Из /etc/nftables.conf убирается только include HY2XS: остальной ruleset # принадлежит оператору и удалению не подлежит. detach_nftables_fragment() { rm -f "$NFT_FRAGMENT" if [ -f /etc/nftables.conf ] && grep -q 'nftables.d/hy2xs.nft' /etc/nftables.conf; then local tmp tmp="$(mktemp)" grep -v 'nftables.d/hy2xs.nft' /etc/nftables.conf >"$tmp" cat "$tmp" >/etc/nftables.conf rm -f "$tmp" log "убрана строка include HY2XS из /etc/nftables.conf" fi if command -v nft >/dev/null 2>&1 && [ -f /etc/nftables.conf ]; then if nft -c -f /etc/nftables.conf >/dev/null 2>&1; then nft -f /etc/nftables.conf >/dev/null 2>&1 || warn "не удалось перезагрузить /etc/nftables.conf" else warn "/etc/nftables.conf не проходит проверку nft -c; ruleset не перезагружен" fi fi } # Финальная проверка повторяет clean-host контракт установщика: если она не # прошла, следующая установка всё равно откажется, и лучше узнать об этом здесь. verify_clean_host() { local leftovers=() local path for path in "${UNIT_FILES[@]}" "${PATHS[@]}" "$NFT_FRAGMENT"; do [ -e "$path" ] && leftovers+=("$path") done if [ -e /usr/local/bin/hysteria ]; then leftovers+=(/usr/local/bin/hysteria) fi local unit for unit in "${UNITS[@]}"; do if systemctl list-unit-files --no-legend "$unit" 2>/dev/null | grep -q .; then leftovers+=("systemd unit: $unit") fi done if [ "${#leftovers[@]}" -gt 0 ]; then warn "после очистки остались объекты:" for path in "${leftovers[@]}"; do printf ' - %s\n' "$path" >&2 done fail "хост не является чистым; удалите перечисленное вручную" fi log "проверка пройдена: хост чист для установки HY2XS v1" } main() { parse_args "$@" require_root show_plan if [ "$APPLY" != "true" ]; then log "это был dry-run; для выполнения запустите: --apply --yes-i-know" return 0 fi log "выполняю очистку" stop_units detach_nftables_fragment remove_paths systemctl daemon-reload || true verify_clean_host log "готово. Теперь можно устанавливать HY2XS v1 с нуля." } main "$@"