chore: доделываем поднятие сервера

This commit is contained in:
protokey committed 2026-09-25 16:56:50 +04:00
1 parent adab286884
commit 8532557ef6
18 files changed
+749 -37

No files matched your search

+8
View File
@@ -40,3 +40,11 @@ Django приложение связывающее все части сайта
4. facts: `uname -a` для карточки сервера
"Переустановить" сбрасывает отпечаток ssh сервера и повторяет установку с новым паролем от провайдера или, по кнопке "Использовать старый", с паролем, присланным при добавлении сервера. Отпечаток можно сбросить и в админке.
# TODO:
-[ ] для панели 3x-ui нужно активировать ssl, панель умеет сама создавать сертификаты для домена или ip адреса.
-[ ] можно ли поставить домен после того как настроили сервер на ip? я имею ввиду поставить домен и выпустить ssl сертификат от letsencrypt.
-[ ] что нужно сделать чтобы сервис мог нормально работать и раздавать прокси интернет через cloudflare? когда домен за прокси клауда.
-[ ] если используется и прокси сервер и домен за claudflare, то будет работать?
-[ ] нужна функция установки прокси в исходящие (outbound)
@@ -0,0 +1,39 @@
from django.db import migrations, models
from serverus import generate_password, generate_port
def generate_subscription(apps, schema_editor):
# у уже установленных точек подписка появится после переустановки
Point = apps.get_model('App', 'Point')
for point in Point.objects.all():
point.sub_port = generate_port(exclude={point.panel_port})
point.sub_path = generate_password(12)
point.save(update_fields=['sub_port', 'sub_path'])
class Migration(migrations.Migration):
dependencies = [
('App', '0003_server_default_root_password'),
]
operations = [
migrations.AddField(
model_name='point',
name='panel_inbound_id',
field=models.PositiveIntegerField(blank=True, help_text='Подключение reality в панели', null=True),
),
migrations.AddField(
model_name='point',
name='sub_port',
field=models.PositiveIntegerField(default=0, help_text='Порт подписки панели'),
preserve_default=False,
),
migrations.AddField(
model_name='point',
name='sub_path',
field=models.CharField(default='', help_text='Путь подписки без слешей', max_length=64),
preserve_default=False,
),
migrations.RunPython(generate_subscription, migrations.RunPython.noop),
]
+9 -7
View File
@@ -4,10 +4,7 @@ from dataclasses import dataclass
from django.contrib.auth.base_user import BaseUserManager
from django.contrib.auth.models import AbstractUser
from django.db import models, transaction
from serverus import Host, generate_password
PANEL_PORT_RANGE = (10000, 20000)
"""Не пересекается с диапазоном ssh портов serverus."""
from serverus import Host, generate_password, generate_port, generate_username
class UserManager(BaseUserManager):
@@ -95,7 +92,7 @@ class PointManager(models.Manager.from_queryset(PointQuerySet)):
Точка без сервера панели не существует, поэтому панель обязательна. Имя точки и доступ к панели генерируются.
"""
low, high = PANEL_PORT_RANGE
panel_port = generate_port()
with transaction.atomic():
point = self.create(
owner=owner,
@@ -103,10 +100,12 @@ class PointManager(models.Manager.from_queryset(PointQuerySet)):
domain=domain,
cloudflare=cloudflare,
outbound_proxy=outbound_proxy,
panel_username=generate_password(12),
panel_username=generate_username(),
panel_password=generate_password(),
panel_port=low + secrets.randbelow(high - low),
panel_port=panel_port,
panel_path=generate_password(18),
sub_port=generate_port(exclude={panel_port}),
sub_path=generate_password(12),
)
Server.objects.create_server(point, Server.Role.PANEL, panel)
if mask:
@@ -152,6 +151,9 @@ class Point(models.Model):
panel_port = models.PositiveIntegerField()
panel_path = models.CharField(max_length=64)
panel_api_token = models.CharField(max_length=255, blank=True)
panel_inbound_id = models.PositiveIntegerField(null=True, blank=True, help_text='Подключение reality в панели')
sub_port = models.PositiveIntegerField(help_text='Порт подписки панели')
sub_path = models.CharField(max_length=64, help_text='Путь подписки без слешей')
created_at = models.DateTimeField(auto_now_add=True)
+7 -1
View File
@@ -82,15 +82,21 @@ def _bootstrap(server: Server):
def _install_panel(server: Server):
point = server.point
result = _check(server, serverus.XuiPanel(server.host()).install(
panel = serverus.XuiPanel(server.host())
result = _check(server, panel.install(
username=point.panel_username,
password=point.panel_password,
panel_port=point.panel_port,
web_base_path=point.panel_path,
sub_port=point.sub_port,
sub_path=point.sub_path,
domain=point.domain,
))
point.panel_api_token = result.data.get('api_token', '')
point.save(update_fields=['panel_api_token'])
result = _check(server, panel.create_inbound())
point.panel_inbound_id = result.data.get('inbound_id')
point.save(update_fields=['panel_inbound_id'])
def _redirect_to_panel(server: Server):
+17 -6
View File
@@ -5,7 +5,9 @@ from django.test import TestCase
from Telegram.models import User as TelegramUser
from . import services
from .models import PANEL_PORT_RANGE, Credentials, Point, Server
from serverus.credentials import SERVICE_PORT_RANGE
from .models import Credentials, Point, Server
class FakeRunner:
@@ -19,7 +21,11 @@ class FakeRunner:
self.calls.append((playbook, host, extravars))
if playbook == self.fail:
return serverus.Result(ok=False, status='failed', error='boom', known_hosts=f'kh {host.address}')
data = {'facts': {'uname': 'Linux test'}, 'xui': {'api_token': 'token'}}.get(playbook, {})
data = {
'facts': {'uname': 'Linux test'},
'xui': {'api_token': 'token'},
'xui_inbound': {'inbound_id': 7},
}.get(playbook, {})
return serverus.Result(ok=True, status='successful', data=data, known_hosts=f'kh {host.address}')
def playbooks(self, address):
@@ -48,9 +54,12 @@ class CreatePointTests(TestCase):
self.assertEqual(point.status, Point.Status.INSTALLING)
self.assertEqual((point.panel.address, point.panel.root_password), ('10.0.0.1', 'panel-pw'))
self.assertEqual((point.mask.address, point.mask.root_password), ('10.0.0.2', 'mask-pw'))
low, high = PANEL_PORT_RANGE
low, high = SERVICE_PORT_RANGE
self.assertTrue(low <= point.panel_port < high)
self.assertTrue(point.panel_username and point.panel_password and point.panel_path)
self.assertTrue(low <= point.sub_port < high)
self.assertNotEqual(point.panel_port, point.sub_port)
self.assertEqual(len(point.panel_username), 6)
self.assertTrue(point.panel_password and point.panel_path and point.sub_path)
self.assertEqual(point.panel.default_root_password, 'panel-pw')
self.assertEqual(point.entry, point.mask)
@@ -94,7 +103,8 @@ class InstallTests(TestCase):
point.refresh_from_db()
self.assertEqual(point.status, Point.Status.READY)
self.assertEqual(point.panel_api_token, 'token')
self.assertEqual(runner.playbooks('10.0.0.1'), ['bootstrap', 'xui', 'facts'])
self.assertEqual(point.panel_inbound_id, 7)
self.assertEqual(runner.playbooks('10.0.0.1'), ['bootstrap', 'xui', 'xui_inbound', 'facts'])
self.assertEqual(runner.playbooks('10.0.0.2'), ['bootstrap', 'proxy', 'facts'])
host, extravars = runner.call('bootstrap', '10.0.0.1')
@@ -111,6 +121,7 @@ class InstallTests(TestCase):
self.assertEqual((host.port, host.known_hosts), (panel.ssh_port, 'kh 10.0.0.1'))
self.assertEqual(extravars['domain'], 'example.com')
self.assertEqual(extravars['panel_port'], point.panel_port)
self.assertEqual((extravars['sub_port'], extravars['sub_path']), (point.sub_port, point.sub_path))
_, extravars = runner.call('proxy', '10.0.0.2')
self.assertEqual(extravars['target_ip'], '10.0.0.1')
@@ -120,7 +131,7 @@ class InstallTests(TestCase):
runner = FakeRunner()
with patch_runner(runner):
services.install_point(point)
self.assertEqual([playbook for playbook, _, _ in runner.calls], ['bootstrap', 'xui', 'facts'])
self.assertEqual([playbook for playbook, _, _ in runner.calls], ['bootstrap', 'xui', 'xui_inbound', 'facts'])
point.refresh_from_db()
self.assertEqual(point.status, Point.Status.READY)