chore: реализована работа с самоподписными сертификатами

This commit is contained in:
protokey committed 2026-09-26 14:12:01 +04:00
1 parent 9bb3a2fc5d
commit 602199d510
23 files changed
+524 -108

No files matched your search

+1 -1
View File
@@ -47,7 +47,7 @@ FAQ_TEXT = '''<b>Что это за бот?</b>
Бот настраивает ваш собственный VPN на вашем сервере: панель 3x-ui с подключениями vless + tls + reality. По желанию перед ней ставится сервер маскировки.
<b>Что нужно от меня?</b>
Арендованный сервер с Debian 13 (от 2 ядер, 2 Гб памяти, 20 Гб диска): ip и пароль root. По желанию - второй такой же сервер для маскировки, домен и прокси на выходе.
Арендованный сервер с Debian 12 или 13 (от 2 ядер, 2 Гб памяти, 20 Гб диска): ip и пароль root. По желанию - второй такой же сервер для маскировки, домен и прокси на выходе.
<b>Зачем сервер маскировки?</b>
Клиенты подключаются к серверу маскировки, он пересылает трафик на панель. Адрес панели никто не видит. Для маскировки нужен домен.
+11
View File
@@ -167,6 +167,17 @@ class Point(models.Model):
def is_busy(self) -> bool:
return self.status in (self.Status.INSTALLING, self.Status.RELEASING)
@property
def panel_host(self) -> str:
"""Адрес панели для https и подписки: домен, без него - ip панели. По ssh ходим только по ip."""
return self.domain or self.panel.address
def panel_url(self) -> str:
return f'https://{self.panel_host}:{self.panel_port}/{self.panel_path}'
def subscription_url(self, sub_id: str) -> str:
return f'https://{self.panel_host}:{self.sub_port}/{self.sub_path}/{sub_id}'
def set_status(self, status: str, error: str = ''):
self.status = status
self.error = error
+16 -1
View File
@@ -2,6 +2,7 @@
from contextlib import contextmanager
import serverus
from django.conf import settings
from .models import Point, Server
@@ -63,7 +64,7 @@ def set_domain(point: Point, domain: str):
if not domain and point.mask:
raise ValueError('маскировка работает только с доменом')
with _installing(point):
_check(point.panel, serverus.XuiPanel(point.panel.host()).issue_certificate(domain))
_check(point.panel, serverus.XuiPanel(point.panel.host()).issue_certificate(domain, local_ca()))
point.domain = domain
point.save(update_fields=['domain'])
@@ -78,6 +79,19 @@ def set_outbound_proxy(point: Point, proxy: str):
point.save(update_fields=['outbound_proxy'])
def local_ca() -> serverus.LocalCA | None:
"""Свой CA, если панели работают не с Let's Encrypt (SERVERUS_CERTIFICATES=local)."""
if settings.SERVERUS_CERTIFICATES == 'local':
return serverus.LocalCA(settings.SERVERUS_CA_DIR)
return None
def xui_api(point: Point) -> serverus.XuiApi:
"""API панели точки: по домену, без него - по ip панели."""
ca = local_ca()
return serverus.XuiApi(point.panel_url(), point.panel_api_token, ca_file=str(ca.cert_path) if ca else None)
def provision(server: Server):
"""Базовая настройка сервера и специализация по его роли."""
_bootstrap(server)
@@ -114,6 +128,7 @@ def _install_panel(server: Server):
sub_port=point.sub_port,
sub_path=point.sub_path,
domain=point.domain,
ca=local_ca(),
))
point.panel_api_token = result.data.get('api_token', '')
point.save(update_fields=['panel_api_token'])
+25 -1
View File
@@ -1,7 +1,8 @@
import tempfile
from unittest import mock
import serverus
from django.test import TestCase
from django.test import TestCase, override_settings
from Telegram.models import User as TelegramUser
from . import services
@@ -161,6 +162,29 @@ class InstallTests(TestCase):
self.assertEqual(Server.objects.get(pk=panel.pk).private_key, private_key)
def test_install_with_local_ca(self):
point = create_point(mask=None)
runner = FakeRunner()
with tempfile.TemporaryDirectory() as directory, patch_runner(runner):
with override_settings(SERVERUS_CERTIFICATES='local', SERVERUS_CA_DIR=directory):
services.install_point(point)
api = services.xui_api(point)
self.assertIn('BEGIN CERTIFICATE', runner.call('xui', '10.0.0.1')[1]['cert_fullchain'])
self.assertIsNotNone(api.context)
def test_letsencrypt_by_default(self):
point = create_point(mask=None)
runner = FakeRunner()
with patch_runner(runner):
services.install_point(point)
self.assertNotIn('cert_fullchain', runner.call('xui', '10.0.0.1')[1])
def test_panel_urls_use_domain_or_panel_ip(self):
point = create_point(mask=None)
self.assertTrue(point.panel_url().startswith(f'https://10.0.0.1:{point.panel_port}/'))
point.domain = 'example.com'
self.assertEqual(point.subscription_url('abc'), f'https://example.com:{point.sub_port}/{point.sub_path}/abc')
def test_install_sets_outbound_proxy(self):
point = create_point(mask=None, outbound_proxy=Point.CF_WARP)
runner = FakeRunner()