chore: сохраняем пароль которым ставили сервер
This commit is contained in:
1 parent
6d322f36a9
commit
c4300066f5
9 files changed
+133
-38
No files matched your search
+4
-4
@@ -11,10 +11,10 @@ Django приложение связывающее все части сайта
|
|||||||
|
|
||||||
## Модель данных
|
## Модель данных
|
||||||
|
|
||||||
- `Point` - точка доступа: кластер из сервера с панелью 3x-ui и сервера маскировки. Владелец - `Telegram.User`. Хранит домен, cloudflare, прокси на выходе и доступ к панели.
|
- `Point` - точка доступа: сервер с панелью 3x-ui и необязательный сервер маскировки. Владелец - `Telegram.User`. Хранит домен, cloudflare, прокси на выходе и доступ к панели.
|
||||||
- `Server` - сервер точки с ролью `panel` или `mask`. Хранит текущие порт ssh, пароль root, ключ и отпечаток ssh.
|
- `Server` - сервер точки с ролью `panel` или `mask`. Хранит текущие порт ssh, пароль root, ключ и отпечаток ssh, а также пароль root, присланный пользователем при добавлении (`default_root_password`).
|
||||||
|
|
||||||
Клиенты подключаются к серверу маскировки, он перенаправляет трафик на панель и скрывает ее адрес.
|
С маскировкой клиенты подключаются к серверу маскировки, он перенаправляет трафик на панель и скрывает ее адрес. Маскировка требует домен.
|
||||||
|
|
||||||
## Модули
|
## Модули
|
||||||
|
|
||||||
@@ -30,4 +30,4 @@ Django приложение связывающее все части сайта
|
|||||||
3. маскировка: перенаправление трафика на адрес панели
|
3. маскировка: перенаправление трафика на адрес панели
|
||||||
4. facts: `uname -a` для карточки сервера
|
4. facts: `uname -a` для карточки сервера
|
||||||
|
|
||||||
"Переустановить" сбрасывает отпечаток ssh сервера и повторяет установку. Отпечаток можно сбросить и в админке.
|
"Переустановить" сбрасывает отпечаток ssh сервера и повторяет установку с новым паролем от провайдера или, по кнопке "Использовать старый", с паролем, присланным при добавлении сервера. Отпечаток можно сбросить и в админке.
|
||||||
+3
-1
@@ -33,7 +33,9 @@ class UserAdmin(BaseUserAdmin):
|
|||||||
class ServerInline(admin.StackedInline):
|
class ServerInline(admin.StackedInline):
|
||||||
model = Server
|
model = Server
|
||||||
extra = 0
|
extra = 0
|
||||||
fields = ('role', 'address', 'ssh_port', 'root_password', 'public_key', 'known_hosts', 'uname')
|
fields = (
|
||||||
|
'role', 'address', 'ssh_port', 'root_password', 'default_root_password', 'public_key', 'known_hosts', 'uname',
|
||||||
|
)
|
||||||
readonly_fields = fields
|
readonly_fields = fields
|
||||||
can_delete = False
|
can_delete = False
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,12 @@ class MyPointInterface(Interface):
|
|||||||
|
|
||||||
@text(texts.MASK, state=DRAFT)
|
@text(texts.MASK, state=DRAFT)
|
||||||
def ask_mask(self, ctx):
|
def ask_mask(self, ctx):
|
||||||
self.ask(ctx, DRAFT_MASK, texts.CREDENTIALS_PROMPT)
|
self.ask(ctx, DRAFT_MASK, texts.CREDENTIALS_PROMPT, [[texts.CANCEL, texts.DELETE]])
|
||||||
|
|
||||||
|
@text(texts.DELETE, state=DRAFT_MASK)
|
||||||
|
def delete_mask(self, ctx):
|
||||||
|
ctx.data['draft'].pop('mask', None)
|
||||||
|
self.show_draft(ctx)
|
||||||
|
|
||||||
@text(texts.DOMAIN, state=DRAFT)
|
@text(texts.DOMAIN, state=DRAFT)
|
||||||
def ask_domain(self, ctx):
|
def ask_domain(self, ctx):
|
||||||
@@ -184,14 +189,16 @@ class MyPointInterface(Interface):
|
|||||||
@text(texts.INSTALL, state=DRAFT)
|
@text(texts.INSTALL, state=DRAFT)
|
||||||
def install(self, ctx):
|
def install(self, ctx):
|
||||||
draft = ctx.data['draft']
|
draft = ctx.data['draft']
|
||||||
if not (draft.get('server') and draft.get('mask')):
|
if not draft.get('server'):
|
||||||
return ctx.reply(texts.DRAFT_INCOMPLETE)
|
return ctx.reply(texts.DRAFT_INCOMPLETE)
|
||||||
|
if draft.get('mask') and not draft.get('domain'):
|
||||||
|
return ctx.reply(texts.MASK_WITHOUT_DOMAIN)
|
||||||
if draft.get('cloudflare') and not draft.get('domain'):
|
if draft.get('cloudflare') and not draft.get('domain'):
|
||||||
return ctx.reply(texts.CLOUDFLARE_WITHOUT_DOMAIN)
|
return ctx.reply(texts.CLOUDFLARE_WITHOUT_DOMAIN)
|
||||||
point = services.create_point(
|
point = services.create_point(
|
||||||
ctx.user,
|
ctx.user,
|
||||||
panel=services.Credentials(**draft['server']),
|
panel=services.Credentials(**draft['server']),
|
||||||
mask=services.Credentials(**draft['mask']),
|
mask=services.Credentials(**draft['mask']) if draft.get('mask') else None,
|
||||||
domain=draft.get('domain', ''),
|
domain=draft.get('domain', ''),
|
||||||
cloudflare=draft.get('cloudflare', False),
|
cloudflare=draft.get('cloudflare', False),
|
||||||
outbound_proxy=draft.get('proxy', ''),
|
outbound_proxy=draft.get('proxy', ''),
|
||||||
|
|||||||
+10
-9
@@ -48,13 +48,13 @@ EMPTY = '-'
|
|||||||
GREETING = 'привет. выбери действие'
|
GREETING = 'привет. выбери действие'
|
||||||
|
|
||||||
FAQ_TEXT = '''<b>Что это за бот?</b>
|
FAQ_TEXT = '''<b>Что это за бот?</b>
|
||||||
Бот настраивает ваш собственный VPN на ваших серверах: панель 3x-ui с подключениями vless + tls + reality и сервер маскировки перед ней.
|
Бот настраивает ваш собственный VPN на вашем сервере: панель 3x-ui с подключениями vless + tls + reality. По желанию перед ней ставится сервер маскировки.
|
||||||
|
|
||||||
<b>Что нужно от меня?</b>
|
<b>Что нужно от меня?</b>
|
||||||
Два арендованных сервера с Debian 13 (от 2 ядер, 2 Гб памяти, 20 Гб диска): ip и пароль root каждого. По желанию - домен и прокси на выходе.
|
Арендованный сервер с Debian 13 (от 2 ядер, 2 Гб памяти, 20 Гб диска): ip и пароль root. По желанию - второй такой же сервер для маскировки, домен и прокси на выходе.
|
||||||
|
|
||||||
<b>Зачем два сервера?</b>
|
<b>Зачем сервер маскировки?</b>
|
||||||
Клиенты подключаются к серверу маскировки, он пересылает трафик на панель. Адрес панели никто не видит.
|
Клиенты подключаются к серверу маскировки, он пересылает трафик на панель. Адрес панели никто не видит. Для маскировки нужен домен.
|
||||||
|
|
||||||
<b>Что бот делает с сервером?</b>
|
<b>Что бот делает с сервером?</b>
|
||||||
Прописывает свой ssh ключ, меняет пароль root, обновляет систему, переносит ssh на случайный порт с входом только по ключу, включает фаервол и ставит нужные сервисы.
|
Прописывает свой ssh ключ, меняет пароль root, обновляет систему, переносит ssh на случайный порт с входом только по ключу, включает фаервол и ставит нужные сервисы.
|
||||||
@@ -63,12 +63,12 @@ FAQ_TEXT = '''<b>Что это за бот?</b>
|
|||||||
Меню сервера - "Вернуть". Бот уберет свои доступы, откатит ssh и фаервол, поставит новый пароль root и пришлет его. Данные на сервере останутся.
|
Меню сервера - "Вернуть". Бот уберет свои доступы, откатит ssh и фаервол, поставит новый пароль root и пришлет его. Данные на сервере останутся.
|
||||||
|
|
||||||
<b>Сервер переустановили у провайдера</b>
|
<b>Сервер переустановили у провайдера</b>
|
||||||
Меню сервера - "Переустановить" и новый пароль root.'''
|
Меню сервера - "Переустановить" и новый пароль root. Если провайдер вернул пароль, который вы присылали боту при добавлении сервера, нажмите "Использовать старый".'''
|
||||||
|
|
||||||
HELP_TEXT = '''<b>Добавить сервер</b>
|
HELP_TEXT = '''<b>Добавить сервер</b>
|
||||||
1. "добавить сервер"
|
1. "добавить сервер"
|
||||||
2. "сервер" - пришлите <code>ip:пароль</code> сервера для панели
|
2. "сервер" - пришлите <code>ip:пароль</code> сервера для панели
|
||||||
3. "маскировка" - пришлите <code>ip:пароль</code> сервера маскировки
|
3. по желанию "маскировка" - пришлите <code>ip:пароль</code> сервера маскировки, для нее нужен "домен"
|
||||||
4. по желанию "домен", "cloudflare", "прокси"
|
4. по желанию "домен", "cloudflare", "прокси"
|
||||||
5. "установить" - установка идет в фоне, по окончании придет уведомление
|
5. "установить" - установка идет в фоне, по окончании придет уведомление
|
||||||
|
|
||||||
@@ -86,7 +86,8 @@ DOMAIN_INVALID = 'Не похоже на доменное имя. Попробу
|
|||||||
CLOUDFLARE_PROMPT = 'Если вы спрятали домен за клаудом, то нужно включить опцию тут.'
|
CLOUDFLARE_PROMPT = 'Если вы спрятали домен за клаудом, то нужно включить опцию тут.'
|
||||||
PROXY_PROMPT = 'Можно указать свои в формате <code>protocol://username:password@host:port</code> или включить cf_warp.'
|
PROXY_PROMPT = 'Можно указать свои в формате <code>protocol://username:password@host:port</code> или включить cf_warp.'
|
||||||
PROXY_INVALID = 'Не похоже на <code>protocol://username:password@host:port</code>. Попробуй еще раз.'
|
PROXY_INVALID = 'Не похоже на <code>protocol://username:password@host:port</code>. Попробуй еще раз.'
|
||||||
DRAFT_INCOMPLETE = 'Нужны оба сервера: "сервер" и "маскировка".'
|
DRAFT_INCOMPLETE = 'Нужен сервер для панели: "сервер".'
|
||||||
|
MASK_WITHOUT_DOMAIN = 'Сервер маскировки работает только с доменом. Укажи домен или убери маскировку.'
|
||||||
CLOUDFLARE_WITHOUT_DOMAIN = 'Cloudflare работает только с доменом. Укажи домен или выключи cloudflare.'
|
CLOUDFLARE_WITHOUT_DOMAIN = 'Cloudflare работает только с доменом. Укажи домен или выключи cloudflare.'
|
||||||
INSTALL_STARTED = 'Запускаю установку сервера. Отправлю уведомление когда все будет готово.'
|
INSTALL_STARTED = 'Запускаю установку сервера. Отправлю уведомление когда все будет готово.'
|
||||||
|
|
||||||
@@ -102,7 +103,7 @@ REINSTALL_PROMPT = '''Будет произведена повторная ус
|
|||||||
Вызывайте эту функцию, только после того, как в панели управления
|
Вызывайте эту функцию, только после того, как в панели управления
|
||||||
сервером в аккаунте хостинг провайдера вызовите переустановку сервера.
|
сервером в аккаунте хостинг провайдера вызовите переустановку сервера.
|
||||||
Если уверены, то пришлите <code>ip:новый_пароль</code> переустановленного сервера.
|
Если уверены, то пришлите <code>ip:новый_пароль</code> переустановленного сервера.
|
||||||
"Использовать старый" переустановит оба сервера с последними известными паролями.'''
|
"Использовать старый" переустановит все серверы точки с паролями, которые вы прислали при добавлении.'''
|
||||||
REINSTALL_UNKNOWN_ADDRESS = 'Сервер {address} не входит в эту точку.'
|
REINSTALL_UNKNOWN_ADDRESS = 'Сервер {address} не входит в эту точку.'
|
||||||
REINSTALL_STARTED = 'Запускаю переустановку. Отправлю уведомление когда все будет готово.'
|
REINSTALL_STARTED = 'Запускаю переустановку. Отправлю уведомление когда все будет готово.'
|
||||||
NOT_IMPLEMENTED = 'Пока не реализовано.'
|
NOT_IMPLEMENTED = 'Пока не реализовано.'
|
||||||
@@ -149,7 +150,7 @@ def point_card(point: Point) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def installed(point: Point) -> str:
|
def installed(point: Point) -> str:
|
||||||
return f'сервер {escape(point.mask.address)} - проименован как <code>{escape(point.name)}</code>, готов к работе.'
|
return f'сервер {escape(point.entry.address)} - проименован как <code>{escape(point.name)}</code>, готов к работе.'
|
||||||
|
|
||||||
|
|
||||||
def install_failed(point: Point, error: Exception) -> str:
|
def install_failed(point: Point, error: Exception) -> str:
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def copy_root_password(apps, schema_editor):
|
||||||
|
# пароль от пользователя для уже добавленных серверов не сохранился, лучшее что есть - текущий
|
||||||
|
Server = apps.get_model('App', 'Server')
|
||||||
|
Server.objects.update(default_root_password=models.F('root_password'))
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('App', '0002_point_server'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='server',
|
||||||
|
name='default_root_password',
|
||||||
|
field=models.CharField(
|
||||||
|
default='',
|
||||||
|
help_text='Пароль root, который пользователь прислал при добавлении сервера. '
|
||||||
|
'После сброса сервера у провайдера он снова может быть актуальным',
|
||||||
|
max_length=128,
|
||||||
|
),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.RunPython(copy_root_password, migrations.RunPython.noop),
|
||||||
|
]
|
||||||
+15
-5
@@ -51,9 +51,9 @@ class User(AbstractUser):
|
|||||||
|
|
||||||
|
|
||||||
class Point(models.Model):
|
class Point(models.Model):
|
||||||
"""Точка доступа: кластер из сервера с панелью 3x-ui и сервера маскировки перед ним.
|
"""Точка доступа: сервер с панелью 3x-ui и, по желанию, сервер маскировки перед ним.
|
||||||
|
|
||||||
Клиенты подключаются к серверу маскировки, он перенаправляет трафик на панель и скрывает ее адрес.
|
С маскировкой клиенты подключаются к серверу маскировки, он перенаправляет трафик на панель и скрывает ее адрес.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
CF_WARP = 'cf_warp'
|
CF_WARP = 'cf_warp'
|
||||||
@@ -94,17 +94,22 @@ class Point(models.Model):
|
|||||||
def is_busy(self) -> bool:
|
def is_busy(self) -> bool:
|
||||||
return self.status in (self.Status.INSTALLING, self.Status.RELEASING)
|
return self.status in (self.Status.INSTALLING, self.Status.RELEASING)
|
||||||
|
|
||||||
def server(self, role: str) -> 'Server':
|
def server(self, role: str) -> 'Server | None':
|
||||||
return self.servers.get(role=role)
|
return self.servers.filter(role=role).first()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def panel(self) -> 'Server':
|
def panel(self) -> 'Server':
|
||||||
return self.server(Server.Role.PANEL)
|
return self.server(Server.Role.PANEL)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mask(self) -> 'Server':
|
def mask(self) -> 'Server | None':
|
||||||
return self.server(Server.Role.MASK)
|
return self.server(Server.Role.MASK)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def entry(self) -> 'Server':
|
||||||
|
"""Сервер, к которому подключаются клиенты."""
|
||||||
|
return self.mask or self.panel
|
||||||
|
|
||||||
|
|
||||||
class Server(models.Model):
|
class Server(models.Model):
|
||||||
"""Сервер под управлением системы. Поля ssh отражают текущее состояние сервера."""
|
"""Сервер под управлением системы. Поля ssh отражают текущее состояние сервера."""
|
||||||
@@ -117,6 +122,11 @@ class Server(models.Model):
|
|||||||
role = models.CharField(max_length=8, choices=Role.choices)
|
role = models.CharField(max_length=8, choices=Role.choices)
|
||||||
address = models.GenericIPAddressField(unique=True)
|
address = models.GenericIPAddressField(unique=True)
|
||||||
ssh_port = models.PositiveIntegerField(default=22)
|
ssh_port = models.PositiveIntegerField(default=22)
|
||||||
|
default_root_password = models.CharField(
|
||||||
|
max_length=128,
|
||||||
|
help_text='Пароль root, который пользователь прислал при добавлении сервера. '
|
||||||
|
'После сброса сервера у провайдера он снова может быть актуальным',
|
||||||
|
)
|
||||||
root_password = models.CharField(max_length=128, help_text='Текущий пароль root')
|
root_password = models.CharField(max_length=128, help_text='Текущий пароль root')
|
||||||
private_key = models.TextField(blank=True)
|
private_key = models.TextField(blank=True)
|
||||||
public_key = models.TextField(blank=True)
|
public_key = models.TextField(blank=True)
|
||||||
|
|||||||
+12
-6
@@ -23,9 +23,10 @@ class Credentials:
|
|||||||
|
|
||||||
|
|
||||||
def create_point(
|
def create_point(
|
||||||
owner, panel: Credentials, mask: Credentials, domain: str = '', cloudflare: bool = False,
|
owner, panel: Credentials, mask: Credentials | None = None, domain: str = '', cloudflare: bool = False,
|
||||||
outbound_proxy: str = '',
|
outbound_proxy: str = '',
|
||||||
) -> Point:
|
) -> Point:
|
||||||
|
"""Создает точку. mask - необязательный сервер маскировки."""
|
||||||
low, high = PANEL_PORT_RANGE
|
low, high = PANEL_PORT_RANGE
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
point = Point.objects.create(
|
point = Point.objects.create(
|
||||||
@@ -40,12 +41,18 @@ def create_point(
|
|||||||
panel_path=serverus.generate_password(18),
|
panel_path=serverus.generate_password(18),
|
||||||
)
|
)
|
||||||
for role, credentials in ((Server.Role.PANEL, panel), (Server.Role.MASK, mask)):
|
for role, credentials in ((Server.Role.PANEL, panel), (Server.Role.MASK, mask)):
|
||||||
point.servers.create(role=role, address=credentials.address, root_password=credentials.password)
|
if credentials:
|
||||||
|
point.servers.create(
|
||||||
|
role=role,
|
||||||
|
address=credentials.address,
|
||||||
|
root_password=credentials.password,
|
||||||
|
default_root_password=credentials.password,
|
||||||
|
)
|
||||||
return point
|
return point
|
||||||
|
|
||||||
|
|
||||||
def install_point(point: Point):
|
def install_point(point: Point):
|
||||||
"""Настраивает оба сервера точки. Порядок не важен: маскировке нужен только адрес панели."""
|
"""Настраивает серверы точки. Порядок не важен: маскировке нужен только адрес панели."""
|
||||||
with _installing(point):
|
with _installing(point):
|
||||||
for server in point.servers.all():
|
for server in point.servers.all():
|
||||||
provision(server)
|
provision(server)
|
||||||
@@ -55,15 +62,14 @@ def reinstall(point: Point, address: str | None = None, password: str | None = N
|
|||||||
"""Повторная установка после переустановки ОС у провайдера.
|
"""Повторная установка после переустановки ОС у провайдера.
|
||||||
|
|
||||||
address - переустановленный сервер точки, без него переустанавливаются все.
|
address - переустановленный сервер точки, без него переустанавливаются все.
|
||||||
password - новый пароль root от провайдера, без него используется последний известный.
|
password - новый пароль root от провайдера, без него используется пароль, присланный при добавлении сервера.
|
||||||
"""
|
"""
|
||||||
servers = point.servers.filter(address=address) if address else point.servers.all()
|
servers = point.servers.filter(address=address) if address else point.servers.all()
|
||||||
with _installing(point):
|
with _installing(point):
|
||||||
for server in servers:
|
for server in servers:
|
||||||
server.known_hosts = ''
|
server.known_hosts = ''
|
||||||
server.ssh_port = 22
|
server.ssh_port = 22
|
||||||
if password:
|
server.root_password = password or server.default_root_password
|
||||||
server.root_password = password
|
|
||||||
server.save()
|
server.save()
|
||||||
provision(server)
|
provision(server)
|
||||||
|
|
||||||
|
|||||||
+19
-1
@@ -105,10 +105,27 @@ class AddServerTests(BotTestCase):
|
|||||||
self.assertEqual(self.fill(texts.PROXY, 'http://nope'), texts.PROXY_INVALID)
|
self.assertEqual(self.fill(texts.PROXY, 'http://nope'), texts.PROXY_INVALID)
|
||||||
send.assert_not_called()
|
send.assert_not_called()
|
||||||
|
|
||||||
def test_cloudflare_requires_domain(self, send):
|
def test_install_without_mask(self, send):
|
||||||
|
self.say(texts.ADD_SERVER)
|
||||||
|
self.fill(texts.SERVER, '10.0.0.1:pw')
|
||||||
|
self.assertEqual(self.say(texts.INSTALL), texts.INSTALL_STARTED)
|
||||||
|
point = Point.objects.get()
|
||||||
|
self.assertIsNone(point.mask)
|
||||||
|
send.assert_called_once_with(point.pk, mock.ANY)
|
||||||
|
|
||||||
|
def test_mask_requires_domain(self, send):
|
||||||
self.say(texts.ADD_SERVER)
|
self.say(texts.ADD_SERVER)
|
||||||
self.fill(texts.SERVER, '10.0.0.1:pw')
|
self.fill(texts.SERVER, '10.0.0.1:pw')
|
||||||
self.fill(texts.MASK, '10.0.0.2:pw')
|
self.fill(texts.MASK, '10.0.0.2:pw')
|
||||||
|
self.assertEqual(self.say(texts.INSTALL), texts.MASK_WITHOUT_DOMAIN)
|
||||||
|
self.say(texts.MASK)
|
||||||
|
self.assertIn('Сервер маскировки: -', self.say(texts.DELETE))
|
||||||
|
self.assertEqual(self.say(texts.INSTALL), texts.INSTALL_STARTED)
|
||||||
|
self.assertIsNone(Point.objects.get().mask)
|
||||||
|
|
||||||
|
def test_cloudflare_requires_domain(self, send):
|
||||||
|
self.say(texts.ADD_SERVER)
|
||||||
|
self.fill(texts.SERVER, '10.0.0.1:pw')
|
||||||
self.fill(texts.CLOUDFLARE, texts.ENABLE)
|
self.fill(texts.CLOUDFLARE, texts.ENABLE)
|
||||||
self.assertEqual(self.say(texts.INSTALL), texts.CLOUDFLARE_WITHOUT_DOMAIN)
|
self.assertEqual(self.say(texts.INSTALL), texts.CLOUDFLARE_WITHOUT_DOMAIN)
|
||||||
send.assert_not_called()
|
send.assert_not_called()
|
||||||
@@ -200,6 +217,7 @@ class TasksTests(TestCase):
|
|||||||
def test_install_notifies_success(self):
|
def test_install_notifies_success(self):
|
||||||
text = self.run_task(tasks.install_point, self.point.pk, self.chat.pk)
|
text = self.run_task(tasks.install_point, self.point.pk, self.chat.pk)
|
||||||
self.assertIn(self.point.name, text)
|
self.assertIn(self.point.name, text)
|
||||||
|
self.assertIn('10.0.0.2', text)
|
||||||
self.assertIn('готов к работе', text)
|
self.assertIn('готов к работе', text)
|
||||||
self.assertEqual(self.api.last_keyboard, [[texts.SERVER_MENU]])
|
self.assertEqual(self.api.last_keyboard, [[texts.SERVER_MENU]])
|
||||||
|
|
||||||
|
|||||||
@@ -34,11 +34,12 @@ def patch_runner(runner):
|
|||||||
return mock.patch('serverus.runner.run_playbook', runner)
|
return mock.patch('serverus.runner.run_playbook', runner)
|
||||||
|
|
||||||
|
|
||||||
def create_point(owner=None, **kwargs):
|
MASK = services.Credentials('10.0.0.2', 'mask-pw')
|
||||||
|
|
||||||
|
|
||||||
|
def create_point(owner=None, mask=MASK, **kwargs):
|
||||||
owner = owner or TelegramUser.objects.create(tg_id=1)
|
owner = owner or TelegramUser.objects.create(tg_id=1)
|
||||||
return services.create_point(
|
return services.create_point(owner, services.Credentials('10.0.0.1', 'panel-pw'), mask, **kwargs)
|
||||||
owner, services.Credentials('10.0.0.1', 'panel-pw'), services.Credentials('10.0.0.2', 'mask-pw'), **kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CreatePointTests(TestCase):
|
class CreatePointTests(TestCase):
|
||||||
@@ -50,6 +51,14 @@ class CreatePointTests(TestCase):
|
|||||||
low, high = services.PANEL_PORT_RANGE
|
low, high = services.PANEL_PORT_RANGE
|
||||||
self.assertTrue(low <= point.panel_port < high)
|
self.assertTrue(low <= point.panel_port < high)
|
||||||
self.assertTrue(point.panel_username and point.panel_password and point.panel_path)
|
self.assertTrue(point.panel_username and point.panel_password and point.panel_path)
|
||||||
|
self.assertEqual(point.panel.default_root_password, 'panel-pw')
|
||||||
|
self.assertEqual(point.entry, point.mask)
|
||||||
|
|
||||||
|
def test_without_mask(self):
|
||||||
|
point = create_point(mask=None)
|
||||||
|
self.assertIsNone(point.mask)
|
||||||
|
self.assertEqual(list(point.servers.all()), [point.panel])
|
||||||
|
self.assertEqual(point.entry, point.panel)
|
||||||
|
|
||||||
|
|
||||||
class InstallTests(TestCase):
|
class InstallTests(TestCase):
|
||||||
@@ -83,6 +92,15 @@ class InstallTests(TestCase):
|
|||||||
_, extravars = runner.call('proxy', '10.0.0.2')
|
_, extravars = runner.call('proxy', '10.0.0.2')
|
||||||
self.assertEqual(extravars['target_ip'], '10.0.0.1')
|
self.assertEqual(extravars['target_ip'], '10.0.0.1')
|
||||||
|
|
||||||
|
def test_install_point_without_mask(self):
|
||||||
|
point = create_point(mask=None)
|
||||||
|
runner = FakeRunner()
|
||||||
|
with patch_runner(runner):
|
||||||
|
services.install_point(point)
|
||||||
|
self.assertEqual([playbook for playbook, _, _ in runner.calls], ['bootstrap', 'xui', 'facts'])
|
||||||
|
point.refresh_from_db()
|
||||||
|
self.assertEqual(point.status, Point.Status.READY)
|
||||||
|
|
||||||
def test_install_failure_marks_point(self):
|
def test_install_failure_marks_point(self):
|
||||||
point = create_point()
|
point = create_point()
|
||||||
runner = FakeRunner(fail='xui')
|
runner = FakeRunner(fail='xui')
|
||||||
@@ -125,14 +143,18 @@ class ReinstallTests(TestCase):
|
|||||||
self.point.refresh_from_db()
|
self.point.refresh_from_db()
|
||||||
self.assertEqual(self.point.status, Point.Status.READY)
|
self.assertEqual(self.point.status, Point.Status.READY)
|
||||||
|
|
||||||
def test_reinstall_all_with_old_passwords(self):
|
def test_reinstall_all_with_passwords_from_user(self):
|
||||||
old_password = self.point.panel.root_password
|
self.assertNotEqual(self.point.panel.root_password, 'panel-pw')
|
||||||
runner = FakeRunner()
|
runner = FakeRunner()
|
||||||
with patch_runner(runner):
|
with patch_runner(runner):
|
||||||
services.reinstall(self.point)
|
services.reinstall(self.point)
|
||||||
_, extravars = runner.call('bootstrap', '10.0.0.1')
|
self.assertEqual(runner.call('bootstrap', '10.0.0.1')[1]['login_password'], 'panel-pw')
|
||||||
self.assertEqual(extravars['login_password'], old_password)
|
self.assertEqual(runner.call('bootstrap', '10.0.0.2')[1]['login_password'], 'mask-pw')
|
||||||
self.assertTrue(runner.playbooks('10.0.0.2'))
|
|
||||||
|
def test_new_password_does_not_replace_password_from_user(self):
|
||||||
|
with patch_runner(FakeRunner()):
|
||||||
|
services.reinstall(self.point, '10.0.0.1', 'new-provider-pw')
|
||||||
|
self.assertEqual(self.point.panel.default_root_password, 'panel-pw')
|
||||||
|
|
||||||
|
|
||||||
class ReleaseTests(TestCase):
|
class ReleaseTests(TestCase):
|
||||||
|
|||||||
Reference in new issue
Block a user