diff --git a/FORK-CHANGELOG.md b/FORK-CHANGELOG.md index d640862..edc1bb1 100644 --- a/FORK-CHANGELOG.md +++ b/FORK-CHANGELOG.md @@ -1,3 +1,14 @@ +## v1.2.26: лимит Let's Encrypt виден прямо, готовый сертификат переиспользуется + +* **Причина отказа теперь называется честно.** Let's Encrypt разрешает 5 сертификатов на точный набор имён + за 7 суток; при повторных установках лимит выбирается, и acme.sh отвечает `rateLimited`, а установщик + писал про «A-запись, порт 80 или Cloudflare» — то есть отправлял искать причину не там. Теперь в выводе + дата, после которой повтор сработает, и подсказка взять другое имя (`node3.…` — лимит считается на набор имён). +* **Готовый сертификат переиспользуется.** Если у acme.sh уже лежит годный сертификат на этот домен, + новый не заказывается — лишний заказ и упирался в лимит. Заодно не тратится время на повторные попытки. +* Итоговая ошибка установщика больше не повторяет догадки про порт и DNS: причина печатается выше, + а лог остаётся в /var/log/kit-domain-cert.log. + ## v1.2.25: вопрос про панель возвращён, блок про защиту переписан понятнее * **Снова спрашиваем, где открывать панель и подписку.** У автора этот вопрос задавался только когда diff --git a/VERSION b/VERSION index 8060c02..cba64f4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.25 +1.2.26 diff --git a/scripts/3x-ui-local.sh b/scripts/3x-ui-local.sh index e40a8ed..c9891d0 100644 --- a/scripts/3x-ui-local.sh +++ b/scripts/3x-ui-local.sh @@ -44,7 +44,7 @@ set -Eeuo pipefail -KIT_VERSION="1.2.25" # версия сборки форка; должна совпадать с VERSION релиза +KIT_VERSION="1.2.26" # версия сборки форка; должна совпадать с VERSION релиза GITEA="${KIT_FORK_BASE:-https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS}" NS="3x-ui-kit-release" SIG_ID="releases@3x-ui-kit" diff --git a/scripts/3x-ui.sh b/scripts/3x-ui.sh index dafb4d5..3457166 100755 --- a/scripts/3x-ui.sh +++ b/scripts/3x-ui.sh @@ -30,7 +30,7 @@ wait_apt_idle() { XUI_VERSION="v3.9.0" # SHA256 установщика 3X-UI этой версии: тег могут передвинуть, а хеш – нет (проверено 2026-09-30). XUI_INSTALL_SHA256="18616fe26c8f6c92db6daa2dcd7cd53c5143ee69ecfd26d8ea6dc9b2c78607a6" -KIT_VERSION="1.2.25" +KIT_VERSION="1.2.26" # kit и kit-sub берём из того же релиза, что и этот скрипт, а не из меняющейся ветки main. KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$KIT_VERSION" # Ядро Xray для панели. С 26.7.x клиенты на Mihomo и sing-box (Hiddify, FlClash, @@ -286,20 +286,43 @@ issue_domain_cert() { return 0 fi [[ -x $acme ]] || { warn "Не нашёл acme.sh, которым установщик 3X-UI получает сертификаты: не могу выпустить сертификат для $DOMAIN."; return 1; } + # Наш шов: лимит Let's Encrypt считается на точный набор имён (5 сертификатов за 7 суток). Если у acme.sh + # уже лежит годный сертификат на этот домен — берём его и не заказываем новый, лишний заказ упирается в лимит. + local need_issue=yes + if [[ -s /root/.acme.sh/${DOMAIN}_ecc/fullchain.cer ]] \ + && openssl x509 -in "/root/.acme.sh/${DOMAIN}_ecc/fullchain.cer" -noout -checkhost "$DOMAIN" 2>/dev/null | grep -q 'does match' \ + && openssl x509 -in "/root/.acme.sh/${DOMAIN}_ecc/fullchain.cer" -noout -checkend 86400 >/dev/null 2>&1; then + ok "у acme.sh уже есть годный сертификат для ${B}$DOMAIN${N} — беру его, новый не заказываю" + need_issue=no + fi port_busy 80 tcp && { warn "Порт 80/tcp занят: Let's Encrypt не сможет проверить домен $DOMAIN."; return 1; } say "Получаю сертификат Let's Encrypt для ${B}$DOMAIN${N}" install -m 600 /dev/null "$log" # Сбои бывают временными (например, ошибка TLS при скачивании готового сертификата): до трёх попыток. - local try - for try in 1 2 3; do - rc=0 - "$acme" --issue -d "$DOMAIN" --standalone --httpport 80 --server letsencrypt --keylength ec-256 ${rc_force:-} >>"$log" 2>&1 || rc=$? - [[ $rc == 0 || $rc == 2 ]] && break - local rc_force="--force" - sleep 10 - done + if [[ $need_issue == yes ]]; then + local try + for try in 1 2 3; do + rc=0 + "$acme" --issue -d "$DOMAIN" --standalone --httpport 80 --server letsencrypt --keylength ec-256 ${rc_force:-} >>"$log" 2>&1 || rc=$? + [[ $rc == 0 || $rc == 2 ]] && break + local rc_force="--force" + sleep 10 + done + else + rc=2 # сертификат взяли готовый: для кода ниже это то же, что «уже свежий» + fi # 2 – сертификат уже свежий, выпускать заново не нужно. - [[ $rc == 0 || $rc == 2 ]] || { warn "Let's Encrypt не выдал сертификат для $DOMAIN. Обычно дело в одном из трёх: A-запись ещё не обновилась, порт 80 закрыт у хостера или домен за проксированием Cloudflare. Лог: $log"; return 1; } + [[ $rc == 0 || $rc == 2 ]] || { + # Наш шов: лимит Let's Encrypt — частая причина, и она не про сервер. Говорим прямо и с датой повтора. + if grep -qE 'rateLimited|too many certificates' "$log"; then + local till + till=$(grep -oE 'retry after [0-9-]+ [0-9:]+ UTC' "$log" | tail -1 | sed 's/^retry after //') + warn "Лимит Let's Encrypt: на имя ${B}$DOMAIN${N} за 7 суток уже выпущено 5 сертификатов${till:+ — повтор возможен после $till}. С вашим сервером всё в порядке: DNS, порты и ufw не при чём. Подождите до этой даты либо возьмите другое имя (например node3.…): лимит считается на точный набор имён." + else + warn "Let's Encrypt не выдал сертификат для $DOMAIN. Обычно дело в одном из трёх: A-запись ещё не обновилась, порт 80 закрыт у хостера или домен за проксированием Cloudflare. Лог: $log" + fi + return 1 + } # Каталог и ключ – только для root (nginx читает их от root). install -d -m 700 "$DOMAIN_CERT_DIR" (umask 077; "$acme" --install-cert -d "$DOMAIN" --ecc --fullchain-file "$DOMAIN_CERT_DIR/fullchain.pem" \ @@ -2140,7 +2163,7 @@ PY # позже (после сборки nginx), поэтому на свежем сервере проверка Let's Encrypt доходила до сервера и # «проглатывалась» — в логе acme это «Timeout during connect (likely firewall problem)». command -v ufw >/dev/null 2>&1 && ufw allow 80/tcp >/dev/null 2>&1 || true - [[ -n $DOMAIN ]] && { issue_domain_cert || die "Не удалось получить сертификат для $DOMAIN: нужен свободный порт 80, открытый в ufw (и у хостера), и A-запись домена на этот сервер. Лог: /var/log/kit-domain-cert.log"; } + [[ -n $DOMAIN ]] && { issue_domain_cert || die "Без сертификата для $DOMAIN продолжать нельзя — причина выше, подробности в /var/log/kit-domain-cert.log"; } setup_nginx fi diff --git a/scripts/hysteria2.sh b/scripts/hysteria2.sh index aa842dd..b323d9e 100755 --- a/scripts/hysteria2.sh +++ b/scripts/hysteria2.sh @@ -8,7 +8,7 @@ # Код выхода 1 при любом запуске (и при `__update_binary`) – старый `hy2 update` на нём # останавливается и не заменяет команду hy2. -KIT_VERSION="1.2.25" +KIT_VERSION="1.2.26" cat >&2 <<'MSG' Отдельный установщик Hysteria2 больше не поддерживается. diff --git a/scripts/kit.sh b/scripts/kit.sh index f1b0885..fdeb858 100644 --- a/scripts/kit.sh +++ b/scripts/kit.sh @@ -12,7 +12,7 @@ set -Eeuo pipefail export LC_ALL=C.UTF-8 # ширина колонок по символам, а не байтам -KIT_VERSION="1.2.25" +KIT_VERSION="1.2.26" KIT_RAW="https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/branch/main" # Файлы новой версии берём из её тега, а не из меняющейся ветки main. kit_ref_raw() { echo "https://git.rukavishnikoff.ru/hacker07/3X-UI_autoVPS/raw/tag/v$1"; } diff --git a/tools/fork_apply.py b/tools/fork_apply.py index 7e47f3f..247c75c 100644 --- a/tools/fork_apply.py +++ b/tools/fork_apply.py @@ -289,6 +289,27 @@ def main(): ' names=$(nearby_scan "$base" | grep -vE "^(${me})$" || true)\n [[ -n $names ]] || continue\n out=$(while IFS= read -r n; do\n [[ $n =~ $SNI_DYN_RE || $n =~ $SNI_BRAND_RE || $n =~ $SNI_BAD_RE ]] && continue\n printf \'%s\\n\' "$n"\n done <<<"$names" | xargs -P 8 -I{} bash -c \'same_net "$1" && sni_alive "$1" && sni_trusted "$1" && sni_quality "$1" && printf "%s\\n" "$1"\' _ {} 2>/dev/null | head -8)\n [[ -n $out ]] && { printf \'%s\\n\' "$out"; return 0; }\n', 'авто-шов: replace, строк 6', ), + ( + 'scripts/3x-ui.sh', + ' [[ -x $acme ]] || { warn "Не нашёл acme.sh, которым установщик 3X-UI получает сертификаты: не могу выпустить сертификат для $DOMAIN."; return 1; }\n', + False, + ' [[ -x $acme ]] || { warn "Не нашёл acme.sh, которым установщик 3X-UI получает сертификаты: не могу выпустить сертификат для $DOMAIN."; return 1; }\n # Наш шов: лимит Let\'s Encrypt считается на точный набор имён (5 сертификатов за 7 суток). Если у acme.sh\n # уже лежит годный сертификат на этот домен — берём его и не заказываем новый, лишний заказ упирается в лимит.\n local need_issue=yes\n if [[ -s /root/.acme.sh/${DOMAIN}_ecc/fullchain.cer ]] \\\n && openssl x509 -in "/root/.acme.sh/${DOMAIN}_ecc/fullchain.cer" -noout -checkhost "$DOMAIN" 2>/dev/null | grep -q \'does match\' \\\n && openssl x509 -in "/root/.acme.sh/${DOMAIN}_ecc/fullchain.cer" -noout -checkend 86400 >/dev/null 2>&1; then\n ok "у acme.sh уже есть годный сертификат для ${B}$DOMAIN${N} — беру его, новый не заказываю"\n need_issue=no\n fi\n', + 'авто-шов: insert, строк 1', + ), + ( + 'scripts/3x-ui.sh', + ' local try\n for try in 1 2 3; do\n rc=0\n "$acme" --issue -d "$DOMAIN" --standalone --httpport 80 --server letsencrypt --keylength ec-256 ${rc_force:-} >>"$log" 2>&1 || rc=$?\n [[ $rc == 0 || $rc == 2 ]] && break\n local rc_force="--force"\n sleep 10\n done\n', + False, + ' if [[ $need_issue == yes ]]; then\n local try\n for try in 1 2 3; do\n rc=0\n "$acme" --issue -d "$DOMAIN" --standalone --httpport 80 --server letsencrypt --keylength ec-256 ${rc_force:-} >>"$log" 2>&1 || rc=$?\n [[ $rc == 0 || $rc == 2 ]] && break\n local rc_force="--force"\n sleep 10\n done\n else\n rc=2 # сертификат взяли готовый: для кода ниже это то же, что «уже свежий»\n fi\n', + 'авто-шов: replace, строк 8', + ), + ( + 'scripts/3x-ui.sh', + ' [[ $rc == 0 || $rc == 2 ]] || { warn "Let\'s Encrypt не выдал сертификат для $DOMAIN. Обычно дело в одном из трёх: A-запись ещё не обновилась, порт 80 закрыт у хостера или домен за проксированием Cloudflare. Лог: $log"; return 1; }\n', + False, + ' [[ $rc == 0 || $rc == 2 ]] || {\n # Наш шов: лимит Let\'s Encrypt — частая причина, и она не про сервер. Говорим прямо и с датой повтора.\n if grep -qE \'rateLimited|too many certificates\' "$log"; then\n local till\n till=$(grep -oE \'retry after [0-9-]+ [0-9:]+ UTC\' "$log" | tail -1 | sed \'s/^retry after //\')\n warn "Лимит Let\'s Encrypt: на имя ${B}$DOMAIN${N} за 7 суток уже выпущено 5 сертификатов${till:+ — повтор возможен после $till}. С вашим сервером всё в порядке: DNS, порты и ufw не при чём. Подождите до этой даты либо возьмите другое имя (например node3.…): лимит считается на точный набор имён."\n else\n warn "Let\'s Encrypt не выдал сертификат для $DOMAIN. Обычно дело в одном из трёх: A-запись ещё не обновилась, порт 80 закрыт у хостера или домен за проксированием Cloudflare. Лог: $log"\n fi\n return 1\n }\n', + 'авто-шов: replace, строк 1', + ), ( 'scripts/3x-ui.sh', " _ _ _ _ _ _ _\n (_) |_ ___ _ __ ___ | |_| | ___ _| |__ _ __(_) ___| | __\n | | __/ __| '_ \\ / _ \\| __| |/ / | | | '_ \\| '__| |/ __| |/ /\n | | |_\\__ \\ | | | (_) | |_| <| |_| | |_) | | | | (__| <\n |_|\\__|___/_| |_|\\___/ \\__|_|\\_\\\\__,_|_.__/|_| |_|\\___|_|\\_\\\n", @@ -517,7 +538,7 @@ def main(): 'scripts/3x-ui.sh', ' [[ -n $DOMAIN ]] && { issue_domain_cert || die "Не удалось получить сертификат для $DOMAIN: порт 80 должен быть свободен, а A-запись домена указывать на этот сервер. Лог: /var/log/kit-domain-cert.log"; }\n', False, - ' # Наш шов: открываем 80 в ufw ПРЯМО ПЕРЕД выпуском сертификата. Установщик открывает список портов\n # позже (после сборки nginx), поэтому на свежем сервере проверка Let\'s Encrypt доходила до сервера и\n # «проглатывалась» — в логе acme это «Timeout during connect (likely firewall problem)».\n command -v ufw >/dev/null 2>&1 && ufw allow 80/tcp >/dev/null 2>&1 || true\n [[ -n $DOMAIN ]] && { issue_domain_cert || die "Не удалось получить сертификат для $DOMAIN: нужен свободный порт 80, открытый в ufw (и у хостера), и A-запись домена на этот сервер. Лог: /var/log/kit-domain-cert.log"; }\n', + ' # Наш шов: открываем 80 в ufw ПРЯМО ПЕРЕД выпуском сертификата. Установщик открывает список портов\n # позже (после сборки nginx), поэтому на свежем сервере проверка Let\'s Encrypt доходила до сервера и\n # «проглатывалась» — в логе acme это «Timeout during connect (likely firewall problem)».\n command -v ufw >/dev/null 2>&1 && ufw allow 80/tcp >/dev/null 2>&1 || true\n [[ -n $DOMAIN ]] && { issue_domain_cert || die "Без сертификата для $DOMAIN продолжать нельзя — причина выше, подробности в /var/log/kit-domain-cert.log"; }\n', 'авто-шов: replace, строк 1', ), (