From c2c8a43f2f1b0a0f471fb251dd8ea7da3e45569f Mon Sep 17 00:00:00 2001 From: agentHits <140916359+agentHits@users.noreply.github.com> Date: Fri, 24 Apr 2026 19:21:18 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B8=D0=BB=20in?= =?UTF-8?q?stall.sh:=20=D1=80=D1=83=D1=81=D1=81=D0=BA=D0=B8=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=D1=81=D0=BA=D0=B0=D0=B7=D0=BA=D0=B8,=20=D0=B1?= =?UTF-8?q?=D0=B5=D0=B7=D0=BE=D0=BF=D0=B0=D1=81=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D1=8C=20=D0=B8=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавил подробную справку через --help для новичков и массовых установок. - Добавил режим удаления: --uninstall, --yes, --remove-warp, --remove-go. - Усилил безопасность: проверки root/systemd/Debian, валидация домена/email/пароля/fake site. - Добавил проверку checksum Go, caddy validate перед запуском и systemd-сервис. --- install.sh | 896 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 787 insertions(+), 109 deletions(-) mode change 100644 => 100755 install.sh diff --git a/install.sh b/install.sh old mode 100644 new mode 100755 index ff1d40c..06f47b6 --- a/install.sh +++ b/install.sh @@ -1,161 +1,839 @@ -#!/bin/bash +#!/usr/bin/env bash +# +# Помощник установки NaiveProxy/Caddy. +# +# Быстрый запуск: +# chmod +x install.sh +# sudo ./install.sh +# +# Справка: +# ./install.sh --help + +set -Eeuo pipefail +IFS=$'\n\t' +umask 077 + +GO_VERSION="${GO_VERSION:-go1.26.2}" +XCADDY_MODULE="${XCADDY_MODULE:-github.com/caddyserver/xcaddy/cmd/xcaddy@v0.4.4}" +FORWARDPROXY_MODULE="${FORWARDPROXY_MODULE:-github.com/caddyserver/forwardproxy@caddy2=github.com/klzgrad/forwardproxy@naive}" +WARP_PROXY_PORT="${WARP_PROXY_PORT:-40000}" +RUN_SYSTEM_UPGRADE="${RUN_SYSTEM_UPGRADE:-0}" +UNINSTALL=false +ASSUME_YES=false +REMOVE_GO=false +REMOVE_WARP=false + +WORK_DIR="" +SCRIPT_NAME="${0##*/}" + +log() { + printf '[+] %s\n' "$*" +} + +warn() { + printf '[!] %s\n' "$*" >&2 +} + +die() { + printf '[x] %s\n' "$*" >&2 + exit 1 +} + +lowercase() { + printf '%s' "$1" | tr '[:upper:]' '[:lower:]' +} + +print_usage() { + cat < "${tmp_file}" || true + cat "${tmp_file}" > "${profile}" + rm -f "${tmp_file}" +} + +uninstall_warp() { + [[ "${REMOVE_WARP}" == "true" ]] || return 0 + require_debian_like + + log "Удаление Cloudflare WARP" + systemctl disable --now warp-svc >/dev/null 2>&1 || true + apt-get purge -y cloudflare-warp || true + remove_if_exists /etc/apt/sources.list.d/cloudflare-client.list + remove_if_exists /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg + apt-get update || warn "Не удалось обновить список пакетов после удаления WARP." +} + +uninstall_go() { + [[ "${REMOVE_GO}" == "true" ]] || return 0 + + log "Удаление Go из /usr/local/go" + remove_if_exists /usr/local/go + remove_if_exists /usr/local/bin/xcaddy + remove_profile_go_path +} + +uninstall_installed_stack() { + local service_file="/etc/systemd/system/caddy.service" + local remove_usr_bin_caddy=false + + confirm_uninstall + + if [[ -f "${service_file}" ]] && grep -Eq 'NaiveProxy|forward proxy|/usr/bin/caddy|/usr/local/bin/caddy' "${service_file}"; then + remove_usr_bin_caddy=true + fi + + log "Остановка сервиса Caddy, если он существует" + systemctl disable --now caddy >/dev/null 2>&1 || true + remove_if_exists "${service_file}" + systemctl daemon-reload + systemctl reset-failed caddy >/dev/null 2>&1 || true + + remove_if_exists /usr/local/bin/caddy + if [[ "${remove_usr_bin_caddy}" == "true" ]]; then + remove_if_exists /usr/bin/caddy + else + warn "Не удаляю /usr/bin/caddy автоматически: файл может принадлежать системному пакету." + fi + + remove_if_exists /etc/caddy + remove_if_exists /var/lib/caddy + remove_if_exists /var/log/caddy + remove_if_exists /root/naiveproxy-client.json + + if id -u caddy >/dev/null 2>&1; then + log "Удаление пользователя caddy" + userdel caddy >/dev/null 2>&1 || warn "Не удалось удалить пользователя caddy." + fi + if getent group caddy >/dev/null 2>&1; then + log "Удаление группы caddy" + groupdel caddy >/dev/null 2>&1 || warn "Не удалось удалить группу caddy." + fi + + uninstall_warp + uninstall_go + + printf '\n' + printf '============================================================\n' + printf 'Удаление завершено\n' + printf '============================================================\n' + printf 'Проверить, что сервис удален:\n' + printf ' systemctl status caddy\n' + printf '\n' + if [[ "${REMOVE_WARP}" != "true" ]]; then + printf 'WARP не удалялся. Чтобы удалить его тоже:\n' + printf ' sudo ./%s --uninstall --remove-warp\n' "${SCRIPT_NAME}" + fi + if [[ "${REMOVE_GO}" != "true" ]]; then + printf 'Go не удалялся. Чтобы удалить его тоже:\n' + printf ' sudo ./%s --uninstall --remove-go\n' "${SCRIPT_NAME}" + fi +} + +cleanup() { + if [[ -n "${WORK_DIR}" && -d "${WORK_DIR}" ]]; then + rm -rf "${WORK_DIR}" + fi +} +trap cleanup EXIT + +require_root() { + [[ "${EUID}" -eq 0 ]] || die "Запусти скрипт от root или через sudo." +} + +require_linux_systemd() { + [[ "$(uname -s)" == "Linux" ]] || die "Этот установщик поддерживает только Linux." + command -v systemctl >/dev/null 2>&1 || die "Для установки нужен systemd." + [[ -d /run/systemd/system ]] || die "Похоже, systemd на этом сервере не запущен." +} + +require_debian_like() { + [[ -r /etc/os-release ]] || die "Не найден файл /etc/os-release." + # shellcheck disable=SC1091 + . /etc/os-release + case "${ID:-}:${ID_LIKE:-}" in + debian:*|ubuntu:*|*:debian*|*:ubuntu*) ;; + *) die "Этот установщик рассчитан на Debian/Ubuntu или совместимую систему с apt." ;; + esac + command -v apt-get >/dev/null 2>&1 || die "Для установки нужен apt-get." +} + +prompt_value() { + local var_name="$1" + local prompt="$2" + local default_value="$3" + local secret="${4:-false}" + local value="" + + if [[ -n "${!var_name:-}" ]]; then + return + fi + + if [[ -t 0 ]]; then + if [[ "${secret}" == "true" ]]; then + read -r -s -p "${prompt}" value || true + printf '\n' + else + read -r -p "${prompt}" value || true + fi + value="${value:-${default_value}}" + else + value="${default_value}" + fi + + printf -v "${var_name}" '%s' "${value}" +} + +prompt_bool() { + local var_name="$1" + local prompt="$2" + local default_value="$3" + local value="" + + if [[ -n "${!var_name:-}" ]]; then + value="${!var_name}" + elif [[ -t 0 ]]; then + read -r -p "${prompt}" value || true + value="${value:-${default_value}}" + else + value="${default_value}" + fi + + case "$(lowercase "${value}")" in + y|yes|true|1) printf -v "${var_name}" '%s' "true" ;; + n|no|false|0|"") printf -v "${var_name}" '%s' "false" ;; + *) die "Неверное значение для ${var_name}: ${value}. Используй y или n." ;; + esac +} -set -e +validate_domain() { + local value="$1" + [[ "${value}" =~ ^([A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,63}$ ]] \ + || die "Неверный домен: ${value}" +} -echo "==============================" -echo "VPN INSTALLER - SETUP" -echo "==============================" +validate_email() { + local value="$1" + [[ "${value}" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,63}$ ]] \ + || die "Неверный email: ${value}" +} -read -p "🌐 Домен (например your-domain.com): " DOMAIN -DOMAIN=${DOMAIN:-your-domain.com} +is_private_ipv4() { + local ip="$1" + local a b c d + IFS=. read -r a b c d <<< "${ip}" + [[ "${a}" =~ ^[0-9]+$ && "${b}" =~ ^[0-9]+$ && "${c}" =~ ^[0-9]+$ && "${d}" =~ ^[0-9]+$ ]] || return 1 + (( a >= 0 && a <= 255 && b >= 0 && b <= 255 && c >= 0 && c <= 255 && d >= 0 && d <= 255 )) || return 1 + (( a == 10 )) && return 0 + (( a == 127 )) && return 0 + (( a == 169 && b == 254 )) && return 0 + (( a == 172 && b >= 16 && b <= 31 )) && return 0 + (( a == 192 && b == 168 )) && return 0 + return 1 +} -read -p "📧 Email для SSL (example@example.com): " EMAIL -EMAIL=${EMAIL:-example@example.com} +validate_fake_site() { + local value="$1" + local host_port host -read -p "🎭 Fake site (https://demo.cloudreve.org): " FAKE_SITE -FAKE_SITE=${FAKE_SITE:-https://demo.cloudreve.org} + [[ "${value}" =~ ^https?://[A-Za-z0-9.-]+(:[0-9]{1,5})?$ ]] \ + || die "Сайт-приманка должен выглядеть как https://example.com или https://example.com:443" -read -p "👤 Username (оставь пустым = авто): " USER_NAME -read -p "🔑 Password (оставь пустым = авто): " USER_PASS + host_port="${value#*://}" + host="${host_port%%:*}" + case "$(lowercase "${host}")" in + localhost|*.localhost) die "Сайт-приманка не должен указывать на localhost." ;; + esac -read -p "⚡ Установить WARP? (y/n): " WARP_INPUT -INSTALL_WARP=false -if [[ "$WARP_INPUT" == "y" || "$WARP_INPUT" == "Y" ]]; then - INSTALL_WARP=true -fi + if is_private_ipv4 "${host}" && [[ "${ALLOW_PRIVATE_FAKE_SITE:-0}" != "1" ]]; then + die "Сайт-приманка указывает на частный IPv4-адрес. Если это точно нужно, запусти с ALLOW_PRIVATE_FAKE_SITE=1." + fi +} -echo "" -echo "[+] Обновление системы..." -apt update -y && apt upgrade -y +validate_credential() { + local name="$1" + local value="$2" + local min_length="$3" + local max_length="$4" -echo "[+] Установка базовых пакетов..." -apt install -y wget curl tar openssl gnupg lsb-release + (( ${#value} >= min_length && ${#value} <= max_length )) \ + || die "${name}: длина должна быть от ${min_length} до ${max_length} символов." + [[ "${value}" =~ ^[A-Za-z0-9._~-]+$ ]] \ + || die "${name}: разрешены только латинские буквы, цифры, точка, нижнее подчеркивание, тильда и дефис." +} -echo "[+] Включение BBR..." -grep -q "fq" /etc/sysctl.conf || echo "net.core.default_qdisc=fq" >> /etc/sysctl.conf -grep -q "bbr" /etc/sysctl.conf || echo "net.ipv4.tcp_congestion_control=bbr" >> /etc/sysctl.conf -sysctl -p +generate_credentials() { + if [[ -z "${USER_NAME:-}" ]]; then + USER_NAME="u$(openssl rand -hex 6)" + fi + if [[ -z "${USER_PASS:-}" ]]; then + USER_PASS="$(openssl rand -hex 18)" + fi +} -echo "[+] Установка Go..." -cd /tmp -wget -q https://go.dev/dl/go1.22.0.linux-amd64.tar.gz -rm -rf /usr/local/go -tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz +detect_go_arch() { + case "$(uname -m)" in + x86_64|amd64) + GO_ARCH="amd64" + GO_SHA256="990e6b4bbba816dc3ee129eaeaf4b42f17c2800b88a2166c265ac1a200262282" + ;; + aarch64|arm64) + GO_ARCH="arm64" + GO_SHA256="c958a1fe1b361391db163a485e21f5f228142d6f8b584f6bef89b26f66dc5b23" + ;; + *) + die "Неподдерживаемая архитектура CPU: $(uname -m)" + ;; + esac +} -export PATH=$PATH:/usr/local/go/bin -echo 'export PATH=$PATH:/usr/local/go/bin' >> /root/.profile +ensure_line() { + local file="$1" + local line="$2" -echo "[+] Установка xcaddy..." -go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest + touch "${file}" + grep -Fqx "${line}" "${file}" || printf '%s\n' "${line}" >> "${file}" +} -echo "[+] Подготовка TMP..." -mkdir -p /root/tmp -export TMPDIR=/root/tmp +backup_file() { + local path="$1" + local backup_path -echo "[+] Сборка Caddy..." -~/go/bin/xcaddy build --with github.com/caddyserver/forwardproxy@caddy2=github.com/klzgrad/forwardproxy@naive + if [[ -e "${path}" || -L "${path}" ]]; then + backup_path="${path}.bak.$(date +%Y%m%d%H%M%S)" + cp -a "${path}" "${backup_path}" + log "Резервная копия ${path}: ${backup_path}" + fi +} -echo "[+] Генерация логина/пароля..." +install_packages() { + export DEBIAN_FRONTEND=noninteractive + + log "Обновление списка пакетов apt" + apt-get update + + if [[ "${RUN_SYSTEM_UPGRADE}" == "1" ]]; then + log "Запуск apt-get upgrade, потому что RUN_SYSTEM_UPGRADE=1" + apt-get upgrade -y + else + log "Полное обновление системы пропущено. Чтобы включить его, запусти с RUN_SYSTEM_UPGRADE=1." + fi + + log "Установка базовых пакетов" + apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + gnupg \ + lsb-release \ + openssl \ + tar \ + wget +} -if [ -z "$USER_NAME" ]; then - USER_NAME=$(openssl rand -base64 64 | tr -dc 'A-Za-z0-9' | head -c 12) -fi +enable_bbr() { + log "Включение BBR и настройка sysctl" + cat > /etc/sysctl.d/99-naiveproxy-bbr.conf <<'EOF' +net.core.default_qdisc=fq +net.ipv4.tcp_congestion_control=bbr +EOF -if [ -z "$USER_PASS" ]; then - USER_PASS=$(openssl rand -base64 64 | tr -dc 'A-Za-z0-9' | head -c 16) -fi + if ! sysctl --system; then + warn "Не удалось применить sysctl сразу. Проверь, поддерживает ли ядро BBR." + fi +} + +install_go() { + local archive="${GO_VERSION}.linux-${GO_ARCH}.tar.gz" + local url="https://go.dev/dl/${archive}" + + log "Скачивание ${archive}" + curl -fL --proto '=https' --tlsv1.2 -o "${WORK_DIR}/${archive}" "${url}" + + log "Проверка контрольной суммы архива Go" + ( + cd "${WORK_DIR}" + printf '%s %s\n' "${GO_SHA256}" "${archive}" | sha256sum -c - + ) + + log "Установка Go ${GO_VERSION}" + rm -rf /usr/local/go + tar -C /usr/local -xzf "${WORK_DIR}/${archive}" + export PATH="/usr/local/go/bin:/usr/local/bin:${PATH}" + ensure_line /root/.profile 'export PATH="/usr/local/go/bin:$PATH"' +} -echo "Логин: $USER_NAME" -echo "Пароль: $USER_PASS" +build_caddy() { + log "Установка xcaddy" + GOBIN=/usr/local/bin go install "${XCADDY_MODULE}" -echo "[+] Создание конфигурации Caddy..." -mkdir -p /etc/caddy + log "Сборка Caddy с модулем NaiveProxy forward_proxy" + ( + cd "${WORK_DIR}" + /usr/local/bin/xcaddy build --with "${FORWARDPROXY_MODULE}" + ) -cat < /etc/caddy/Caddyfile -:443, $DOMAIN -tls $EMAIL + [[ -x "${WORK_DIR}/caddy" ]] || die "Сборка Caddy не создала исполняемый файл." +} -route { - forward_proxy { - basic_auth $USER_NAME $USER_PASS - hide_ip - hide_via - probe_resistance - } +ensure_caddy_user() { + if ! getent group caddy >/dev/null 2>&1; then + log "Создание системной группы caddy" + groupadd --system caddy + fi + + if ! id -u caddy >/dev/null 2>&1; then + log "Создание системного пользователя caddy" + useradd --system --gid caddy --home-dir /var/lib/caddy --shell /usr/sbin/nologin caddy + fi + + install -d -o root -g caddy -m 0750 /etc/caddy + install -d -o caddy -g caddy -m 0750 /var/lib/caddy + install -d -o caddy -g caddy -m 0750 /var/lib/caddy/.config + install -d -o caddy -g caddy -m 0750 /var/log/caddy +} - reverse_proxy $FAKE_SITE { - header_up Host {upstream_hostport} - header_up X-Forwarded-Host {host} - } +write_caddyfile() { + local warp_upstream="" + + if [[ "${INSTALL_WARP}" == "true" ]]; then + warp_upstream=" upstream socks5://127.0.0.1:${WARP_PROXY_PORT}" + fi + + log "Создание конфигурации Caddy: /etc/caddy/Caddyfile" + backup_file /etc/caddy/Caddyfile + + cat > /etc/caddy/Caddyfile < /etc/systemd/system/caddy.service + cat > /etc/systemd/system/caddy.service <<'EOF' [Unit] -Description=Caddy with NaiveProxy -After=network.target network-online.target -Requires=network-online.target +Description=Caddy с NaiveProxy +Documentation=https://caddyserver.com/docs/ +After=network-online.target +Wants=network-online.target [Service] Type=notify -ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile -ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile --force -Restart=always +User=caddy +Group=caddy +Environment=XDG_DATA_HOME=/var/lib/caddy +Environment=XDG_CONFIG_HOME=/var/lib/caddy/.config +ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile +ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/Caddyfile --force +Restart=on-failure RestartSec=5s LimitNOFILE=1048576 AmbientCapabilities=CAP_NET_BIND_SERVICE +CapabilityBoundingSet=CAP_NET_BIND_SERVICE +NoNewPrivileges=true +PrivateTmp=true +ProtectHome=true +ProtectSystem=full +ReadWritePaths=/var/lib/caddy /var/log/caddy [Install] WantedBy=multi-user.target EOF +} -systemctl daemon-reload -systemctl enable --now caddy +install_warp() { + local codename -echo "[+] Запуск..." -caddy start --config /etc/caddy/Caddyfile || true + [[ "${INSTALL_WARP}" == "true" ]] || return 0 -### ===== WARP ===== -if [ "$INSTALL_WARP" = true ]; then - echo "[+] Установка WARP..." + log "Установка Cloudflare WARP" + install -d -m 0755 /usr/share/keyrings + curl -fsSL --proto '=https' --tlsv1.2 \ + https://pkg.cloudflareclient.com/pubkey.gpg \ + -o "${WORK_DIR}/cloudflare-warp-pubkey.gpg" + gpg --batch --yes --dearmor \ + --output /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg \ + "${WORK_DIR}/cloudflare-warp-pubkey.gpg" + chmod 0644 /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg - curl -fsSL https://pkg.cloudflareclient.com/pubkey.gpg | gpg --yes --dearmor --output /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg + codename="$(lsb_release -cs)" + [[ "${codename}" =~ ^[A-Za-z0-9._-]+$ ]] || die "Неверный код релиза дистрибутива: ${codename}" - echo "deb [signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] https://pkg.cloudflareclient.com/ $(lsb_release -cs) main" \ - > /etc/apt/sources.list.d/cloudflare-client.list + cat > /etc/apt/sources.list.d/cloudflare-client.list </dev/null +} + +write_client_config() { + local path="/root/naiveproxy-client.json" + + log "Запись клиентского конфига в ${path}" + cat > "${path}" <