--restore: копия с пустой папкой сертификата отклоняется до установки
Архив с root/cert/self или root/cert/custom без fullchain.pem/privkey.pem проходил проверку, ставил 3X-UI и падал на копировании сертификата. Теперь проверка архива требует оба непустых файла, а для копии со своим сертификатом (BACKUP_SSL=custom) – папку root/cert/custom. Отрицательные тесты – в tools/test/restore_archive_test.py.
This commit is contained in:
+1
-1
@@ -14,7 +14,7 @@
|
|||||||
* Сайт-заглушка теперь у каждого сервера своя: при установке она собирается из двадцати тем и пяти вёрсток со случайным названием, текстами и цветами. Пекарня, переводчик или мастерская по велосипедам – как повезёт. Раньше у многих серверов был один и тот же «облачный» сосед.
|
* Сайт-заглушка теперь у каждого сервера своя: при установке она собирается из двадцати тем и пяти вёрсток со случайным названием, текстами и цветами. Пекарня, переводчик или мастерская по велосипедам – как повезёт. Раньше у многих серверов был один и тот же «облачный» сосед.
|
||||||
* `kit-sub` на своём порту (`--multi-port`) отвечает по HTTP/1.1 и с `Server: nginx` без лишнего пробела – как настоящий nginx, а не его двоюродный брат.
|
* `kit-sub` на своём порту (`--multi-port`) отвечает по HTTP/1.1 и с `Server: nginx` без лишнего пробела – как настоящий nginx, а не его двоюродный брат.
|
||||||
* Исправил `kit net masq on`: Hysteria2 после него переставала слушать порт до перезапуска панели, а `kit net fp` и `kit net on` спотыкались о то же. Панель меняет подключение на ходу, а ядро такую правку не принимает. Теперь `kit` проверяет порты после каждой правки и, если что-то закрылось, перезапускает ядро и честно об этом говорит. А `kit check` научился смотреть, запущено ли ядро Xray и слушает ли каждое подключение свой порт, TCP и UDP отдельно: nginx на 443/tcp больше не выдаёт себя за Hysteria2 на 443/udp.
|
* Исправил `kit net masq on`: Hysteria2 после него переставала слушать порт до перезапуска панели, а `kit net fp` и `kit net on` спотыкались о то же. Панель меняет подключение на ходу, а ядро такую правку не принимает. Теперь `kit` проверяет порты после каждой правки и, если что-то закрылось, перезапускает ядро и честно об этом говорит. А `kit check` научился смотреть, запущено ли ядро Xray и слушает ли каждое подключение свой порт, TCP и UDP отдельно: nginx на 443/tcp больше не выдаёт себя за Hysteria2 на 443/udp.
|
||||||
* `--restore` на новый IP, где Let's Encrypt не выдал сертификат (лимит или закрытый порт 80), раньше говорил «Готово», а ядро Xray не запускалось: подключения ждали сертификат, которого нет. Теперь я ставлю самоподписанный, закрепляю его отпечаток в ссылках и прямо говорю об этом. А если ядро всё же не поднялось, «Готово» не будет: покажу, где смотреть.
|
* `--restore` на новый IP, где Let's Encrypt не выдал сертификат (лимит или закрытый порт 80), раньше говорил «Готово», а ядро Xray не запускалось: подключения ждали сертификат, которого нет. Теперь я ставлю самоподписанный, закрепляю его отпечаток в ссылках и прямо говорю об этом. А если ядро всё же не поднялось, «Готово» не будет: покажу, где смотреть. Копию с пустой папкой сертификата `--restore` теперь отклоняет сразу, а не на полпути установки.
|
||||||
* JSON-подписка снова открывается в приложениях на Xray: там, где у vless не было `encryption`, подписка теперь дописывает `none`.
|
* JSON-подписка снова открывается в приложениях на Xray: там, где у vless не было `encryption`, подписка теперь дописывает `none`.
|
||||||
* Отдельный установщик Hysteria2 ушёл на пенсию: Hysteria2 и так входит в основную установку, один из 11 протоколов. Кто ставил его отдельно – сервер работает как работал, а `hy2 update` вежливо расскажет, что дальше. Нужен один Hysteria2 без панели – официальный установщик: `bash <(curl -fsSL https://get.hy2.sh/)`.
|
* Отдельный установщик Hysteria2 ушёл на пенсию: Hysteria2 и так входит в основную установку, один из 11 протоколов. Кто ставил его отдельно – сервер работает как работал, а `hy2 update` вежливо расскажет, что дальше. Нужен один Hysteria2 без панели – официальный установщик: `bash <(curl -fsSL https://get.hy2.sh/)`.
|
||||||
|
|
||||||
|
|||||||
@@ -1861,6 +1861,13 @@ with tarfile.open(sys.argv[1], "r:gz") as t:
|
|||||||
for need in ("etc/x-ui/x-ui.db", "etc/x-ui/install-result.env", "kit-backup.env"):
|
for need in ("etc/x-ui/x-ui.db", "etc/x-ui/install-result.env", "kit-backup.env"):
|
||||||
if need not in names:
|
if need not in names:
|
||||||
sys.exit("нет " + need)
|
sys.exit("нет " + need)
|
||||||
|
# Каталог сертификата есть – в нём оба файла, иначе установка споткнётся на полпути.
|
||||||
|
files = {m.name for m in ms if m.isfile() and m.size > 0}
|
||||||
|
for c in ("root/cert/self", "root/cert/custom"):
|
||||||
|
if any(n == c or n.startswith(c + "/") for n in names):
|
||||||
|
for f in ("fullchain.pem", "privkey.pem"):
|
||||||
|
if c + "/" + f not in files:
|
||||||
|
sys.exit("нет " + c + "/" + f)
|
||||||
if hasattr(tarfile, "data_filter"):
|
if hasattr(tarfile, "data_filter"):
|
||||||
t.extractall(sys.argv[2], members=ms, filter="data")
|
t.extractall(sys.argv[2], members=ms, filter="data")
|
||||||
else:
|
else:
|
||||||
@@ -1884,6 +1891,8 @@ PY
|
|||||||
BACKUP_KIT_VERSION=$(env_get "$tmp/kit-backup.env" BACKUP_KIT_VERSION)
|
BACKUP_KIT_VERSION=$(env_get "$tmp/kit-backup.env" BACKUP_KIT_VERSION)
|
||||||
[[ $BACKUP_HOST =~ ^[A-Za-z0-9.:-]+$ ]] || die "Это не резервная копия 3X-UI KIT или она повреждена (адрес сервера). Ничего не менял."
|
[[ $BACKUP_HOST =~ ^[A-Za-z0-9.:-]+$ ]] || die "Это не резервная копия 3X-UI KIT или она повреждена (адрес сервера). Ничего не менял."
|
||||||
[[ $BACKUP_SSL =~ ^(ip|custom|none)$ ]] || die "В копии нет данных о сертификате."
|
[[ $BACKUP_SSL =~ ^(ip|custom|none)$ ]] || die "В копии нет данных о сертификате."
|
||||||
|
[[ $BACKUP_SSL != custom || -f $tmp/root/cert/custom/fullchain.pem ]] \
|
||||||
|
|| die "Это не резервная копия 3X-UI KIT или она повреждена (нет своего сертификата). Ничего не менял."
|
||||||
PANEL_SSL=$BACKUP_SSL
|
PANEL_SSL=$BACKUP_SSL
|
||||||
# Домен переезжает вместе с сервером (поменяйте A-запись), IP – нет.
|
# Домен переезжает вместе с сервером (поменяйте A-запись), IP – нет.
|
||||||
if [[ $BACKUP_HOST =~ ^[0-9.]+$ ]]; then
|
if [[ $BACKUP_HOST =~ ^[0-9.]+$ ]]; then
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
"""Сертификаты при --restore (3x-ui.sh): python3 tools/test/restore_archive_test.py"""
|
"""Проверка архива копии и сертификатов при --restore (3x-ui.sh): python3 tools/test/restore_archive_test.py"""
|
||||||
import json, os, sqlite3, subprocess, tempfile, unittest
|
import io, json, os, re, sqlite3, subprocess, tarfile, tempfile, unittest
|
||||||
|
|
||||||
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
src = open(os.path.join(root, "scripts", "3x-ui.sh"), encoding="utf-8").read()
|
src = open(os.path.join(root, "scripts", "3x-ui.sh"), encoding="utf-8").read()
|
||||||
@@ -12,10 +12,68 @@ def heredoc(after):
|
|||||||
return body[:body.index("\nPY\n") + 1]
|
return body[:body.index("\nPY\n") + 1]
|
||||||
|
|
||||||
|
|
||||||
|
CHECK = heredoc('python3 - "$file" "$tmp"')
|
||||||
CERT_FILES = heredoc("db_cert_files() {")
|
CERT_FILES = heredoc("db_cert_files() {")
|
||||||
PIN = heredoc("pin_cert_in_db() {")
|
PIN = heredoc("pin_cert_in_db() {")
|
||||||
|
|
||||||
|
|
||||||
|
def archive(extra=(), drop=()):
|
||||||
|
files = {"etc/x-ui/x-ui.db": b"db", "etc/x-ui/install-result.env": b"X=1\n", "kit-backup.env": b"BACKUP_SSL=ip\n"}
|
||||||
|
files.update({k: v for k, v in extra if v is not None})
|
||||||
|
d = tempfile.mkdtemp()
|
||||||
|
p = os.path.join(d, "b.tar.gz")
|
||||||
|
with tarfile.open(p, "w:gz") as t:
|
||||||
|
for name, data in list(files.items()) + [(k, v) for k, v in extra if v is None]:
|
||||||
|
if name in drop:
|
||||||
|
continue
|
||||||
|
ti = tarfile.TarInfo(name)
|
||||||
|
if data is None:
|
||||||
|
ti.type = tarfile.DIRTYPE
|
||||||
|
t.addfile(ti)
|
||||||
|
else:
|
||||||
|
ti.size = len(data)
|
||||||
|
t.addfile(ti, io.BytesIO(data))
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def check(p):
|
||||||
|
out = tempfile.mkdtemp()
|
||||||
|
return subprocess.run(["python3", "-", p, out], input=CHECK, capture_output=True, text=True)
|
||||||
|
|
||||||
|
|
||||||
|
PEM = b"-----BEGIN CERTIFICATE-----\nAA==\n-----END CERTIFICATE-----\n"
|
||||||
|
|
||||||
|
|
||||||
|
class Archive(unittest.TestCase):
|
||||||
|
def test_good(self):
|
||||||
|
r = check(archive())
|
||||||
|
self.assertEqual(r.returncode, 0, r.stderr)
|
||||||
|
|
||||||
|
def test_good_with_self_cert(self):
|
||||||
|
r = check(archive([("root/cert/self", None), ("root/cert/self/fullchain.pem", PEM), ("root/cert/self/privkey.pem", PEM)]))
|
||||||
|
self.assertEqual(r.returncode, 0, r.stderr)
|
||||||
|
|
||||||
|
def test_empty_self_dir(self):
|
||||||
|
r = check(archive([("root/cert/self", None)]))
|
||||||
|
self.assertNotEqual(r.returncode, 0)
|
||||||
|
self.assertIn("root/cert/self/fullchain.pem", r.stderr)
|
||||||
|
|
||||||
|
def test_empty_custom_dir(self):
|
||||||
|
self.assertNotEqual(check(archive([("root/cert/custom", None)])).returncode, 0)
|
||||||
|
|
||||||
|
def test_self_without_key(self):
|
||||||
|
self.assertNotEqual(check(archive([("root/cert/self/fullchain.pem", PEM)])).returncode, 0)
|
||||||
|
|
||||||
|
def test_self_empty_key(self):
|
||||||
|
self.assertNotEqual(check(archive([("root/cert/self/fullchain.pem", PEM), ("root/cert/self/privkey.pem", b"")])).returncode, 0)
|
||||||
|
|
||||||
|
def test_no_db(self):
|
||||||
|
self.assertNotEqual(check(archive(drop=("etc/x-ui/x-ui.db",))).returncode, 0)
|
||||||
|
|
||||||
|
def test_dotdot(self):
|
||||||
|
self.assertNotEqual(check(archive([("etc/x-ui/../../tmp/x", b"x")])).returncode, 0)
|
||||||
|
|
||||||
|
|
||||||
def make_db():
|
def make_db():
|
||||||
d = tempfile.mkdtemp()
|
d = tempfile.mkdtemp()
|
||||||
p = os.path.join(d, "x-ui.db")
|
p = os.path.join(d, "x-ui.db")
|
||||||
|
|||||||
Reference in New Issue
Block a user