diff --git a/src/App/README.md b/src/App/README.md
index c526e2e..711cd37 100644
--- a/src/App/README.md
+++ b/src/App/README.md
@@ -4,21 +4,21 @@ Django приложение связывающее все части сайта
-[x] Реализует кастомный менеджер модели и кастомную модель пользователя с отключенным email.
-[x] Модель данных для поддержки интерфейса и функционала
--[ ] Настройки домена, прокси и cloudflare у готовой точки
+-[ ] Настройки домена и прокси у готовой точки
-[ ] Клиенты и статистика через API панели 3x-ui
Зависимости: `Telegram` (интерфейс бота, пользователи, уведомления) и `serverus` (настройка серверов).
## Модель данных
-- `Point` - точка доступа: сервер с панелью 3x-ui и необязательный сервер маскировки. Владелец - `Telegram.User`. Хранит домен, cloudflare, прокси на выходе и доступ к панели.
+- `Point` - точка доступа: сервер с панелью 3x-ui и необязательный сервер маскировки. Владелец - `Telegram.User`. Хранит домен, прокси на выходе и доступ к панели.
- `Server` - сервер точки с ролью `panel` или `mask`. Хранит текущие порт ssh, пароль root, ключ и отпечаток ssh, а также пароль root, присланный пользователем при добавлении (`default_root_password`).
С маскировкой клиенты подключаются к серверу маскировки, он перенаправляет трафик на панель и скрывает ее адрес. Маскировка требует домен.
Работа с записями идет через менеджеры и методы моделей, а не через `filter`/`create` напрямую:
-- создание: `Point.objects.create_point(owner, panel, mask=None, *, domain, cloudflare, outbound_proxy)` создает точку вместе с серверами, имя и доступ к панели генерирует сама. `Server.objects.create_server(point, role, credentials)`. `Credentials` - адрес и пароль root, которые прислал пользователь
+- создание: `Point.objects.create_point(owner, panel, mask=None, *, domain, outbound_proxy)` создает точку вместе с серверами, имя и доступ к панели генерирует сама. `Server.objects.create_server(point, role, credentials)`. `Credentials` - адрес и пароль root, которые прислал пользователь
- поиск: `Point.objects.owned_by(owner)`, дальше `find_by_name`, `find_by_pk`, `find_last`. У серверов `find_by_address` и `is_address_taken`, в том числе в пределах точки через `point.servers`
- изменение: `point.set_status(status, error)`, `server.reset_fingerprint()`
- удаление: `point.delete()`, серверы удаляются вместе с точкой
diff --git a/src/App/bot/interface.py b/src/App/bot/interface.py
index 176e640..1cd02fd 100644
--- a/src/App/bot/interface.py
+++ b/src/App/bot/interface.py
@@ -13,7 +13,6 @@ DRAFT = 'draft'
DRAFT_SERVER = 'draft.server'
DRAFT_MASK = 'draft.mask'
DRAFT_DOMAIN = 'draft.domain'
-DRAFT_CLOUDFLARE = 'draft.cloudflare'
DRAFT_PROXY = 'draft.proxy'
SELECT = 'select'
POINT = 'point'
@@ -103,10 +102,6 @@ class MyPointInterface(Interface):
def ask_domain(self, ctx):
self.ask(ctx, DRAFT_DOMAIN, texts.DOMAIN_PROMPT, [[texts.CANCEL, texts.DELETE]])
- @text(texts.CLOUDFLARE, state=DRAFT)
- def ask_cloudflare(self, ctx):
- self.ask(ctx, DRAFT_CLOUDFLARE, texts.CLOUDFLARE_PROMPT, [[texts.ENABLE, texts.DISABLE, texts.CANCEL]])
-
@text(texts.PROXY, state=DRAFT)
def ask_proxy(self, ctx):
self.ask(ctx, DRAFT_PROXY, texts.PROXY_PROMPT, [[texts.CF_WARP, texts.CANCEL, texts.DELETE]])
@@ -118,7 +113,6 @@ class MyPointInterface(Interface):
@text(texts.CANCEL, state=DRAFT_SERVER)
@text(texts.CANCEL, state=DRAFT_MASK)
@text(texts.CANCEL, state=DRAFT_DOMAIN)
- @text(texts.CANCEL, state=DRAFT_CLOUDFLARE)
@text(texts.CANCEL, state=DRAFT_PROXY)
def back_to_draft(self, ctx):
self.show_draft(ctx)
@@ -153,20 +147,6 @@ class MyPointInterface(Interface):
ctx.data['draft']['domain'] = ''
self.show_draft(ctx)
- @text(texts.ENABLE, state=DRAFT_CLOUDFLARE)
- def enable_cloudflare(self, ctx):
- ctx.data['draft']['cloudflare'] = True
- self.show_draft(ctx)
-
- @text(texts.DISABLE, state=DRAFT_CLOUDFLARE)
- def disable_cloudflare(self, ctx):
- ctx.data['draft']['cloudflare'] = False
- self.show_draft(ctx)
-
- @state(DRAFT_CLOUDFLARE)
- def repeat_cloudflare(self, ctx):
- self.ask_cloudflare(ctx)
-
@state(DRAFT_PROXY)
def set_proxy(self, ctx):
proxy = ctx.text.strip()
@@ -192,14 +172,11 @@ class MyPointInterface(Interface):
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'):
- return ctx.reply(texts.CLOUDFLARE_WITHOUT_DOMAIN)
point = Point.objects.create_point(
ctx.user,
panel=Credentials(**draft['server']),
mask=Credentials(**draft['mask']) if draft.get('mask') else None,
domain=draft.get('domain', ''),
- cloudflare=draft.get('cloudflare', False),
outbound_proxy=draft.get('proxy', ''),
)
tasks.install_point.send(point.pk, ctx.chat.pk)
@@ -305,8 +282,7 @@ class MyPointInterface(Interface):
@text(texts.POINT_DOMAIN, state=POINT)
@text(texts.POINT_PROXY, state=POINT)
- @text(texts.POINT_CLOUDFLARE, state=POINT)
@text(texts.CLIENTS, state=POINT)
def not_implemented(self, ctx):
- # TODO: настройки домена, прокси, cloudflare и клиенты через API панели 3x-ui
+ # TODO: настройки домена, прокси и клиенты через API панели 3x-ui
ctx.reply(texts.NOT_IMPLEMENTED)
diff --git a/src/App/bot/texts.py b/src/App/bot/texts.py
index 5c3a3a9..0886cae 100644
--- a/src/App/bot/texts.py
+++ b/src/App/bot/texts.py
@@ -20,11 +20,8 @@ NO = 'Нет'
SERVER = 'сервер'
MASK = 'маскировка'
DOMAIN = 'домен'
-CLOUDFLARE = 'cloudflare'
PROXY = 'прокси'
INSTALL = 'установить'
-ENABLE = 'включить'
-DISABLE = 'выключить'
CF_WARP = 'cf_warp'
# меню сервера
@@ -33,14 +30,13 @@ RELEASE = 'Вернуть'
REINSTALL = 'Переустановить'
POINT_DOMAIN = 'Домен'
POINT_PROXY = 'Прокси'
-POINT_CLOUDFLARE = 'Cloudflare'
CLIENTS = 'Клиенты'
USE_OLD = 'Использовать старый'
MAIN_KEYBOARD = [[FAQ, ADD_SERVER, HELP]]
MAIN_KEYBOARD_WITH_POINTS = [[FAQ, ADD_SERVER, HELP], [MANAGE]]
-DRAFT_KEYBOARD = [[DOMAIN, CLOUDFLARE, PROXY, MASK], [CANCEL, INSTALL, SERVER, HELP]]
-POINT_KEYBOARD = [[RELEASE, REINSTALL], [POINT_DOMAIN, POINT_PROXY, POINT_CLOUDFLARE], [CLIENTS], [MAIN_MENU]]
+DRAFT_KEYBOARD = [[DOMAIN, PROXY, MASK], [CANCEL, INSTALL, SERVER, HELP]]
+POINT_KEYBOARD = [[RELEASE, REINSTALL], [POINT_DOMAIN, POINT_PROXY], [CLIENTS], [MAIN_MENU]]
CANCEL_KEYBOARD = [[CANCEL]]
EMPTY = '-'
@@ -69,7 +65,7 @@ HELP_TEXT = '''Добавить сервер
1. "добавить сервер"
2. "сервер" - пришлите ip:пароль сервера для панели
3. по желанию "маскировка" - пришлите ip:пароль сервера маскировки, для нее нужен "домен"
-4. по желанию "домен", "cloudflare", "прокси"
+4. по желанию "домен", "прокси"
5. "установить" - установка идет в фоне, по окончании придет уведомление
Управлять сервером
@@ -83,12 +79,10 @@ ADDRESS_TAKEN = 'Сервер {address} уже подключен.'
ADDRESS_DUPLICATE = 'Панель и маскировка должны быть разными серверами.'
DOMAIN_PROMPT = 'Пришли доменное имя. Доменное имя лучше подходит для маскировки вашей точки доступа.'
DOMAIN_INVALID = 'Не похоже на доменное имя. Попробуй еще раз.'
-CLOUDFLARE_PROMPT = 'Если вы спрятали домен за клаудом, то нужно включить опцию тут.'
PROXY_PROMPT = 'Можно указать свои в формате protocol://username:password@host:port или включить cf_warp.'
PROXY_INVALID = 'Не похоже на protocol://username:password@host:port. Попробуй еще раз.'
DRAFT_INCOMPLETE = 'Нужен сервер для панели: "сервер".'
MASK_WITHOUT_DOMAIN = 'Сервер маскировки работает только с доменом. Укажи домен или убери маскировку.'
-CLOUDFLARE_WITHOUT_DOMAIN = 'Cloudflare работает только с доменом. Укажи домен или выключи cloudflare.'
INSTALL_STARTED = 'Запускаю установку сервера. Отправлю уведомление когда все будет готово.'
SELECT_PROMPT = 'Пришлите server_name'
@@ -119,7 +113,6 @@ def draft_card(draft: dict) -> str:
f"Сервер: {value(draft.get('server', {}).get('address', ''))}",
f"Сервер маскировки: {value(draft.get('mask', {}).get('address', ''))}",
f"Домен: {value(draft.get('domain', ''))}",
- f"Cloudflare: {'Да' if draft.get('cloudflare') else EMPTY}",
f"Прокси: {value(draft.get('proxy', ''))}",
])
@@ -141,7 +134,6 @@ def point_card(point: Point) -> str:
# TODO: онлайн/всего ключей из API панели
f'Ключи: {EMPTY}',
f'Домен: {value(point.domain)}',
- f"Cloudflare: {'Да' if point.cloudflare else EMPTY}",
f'Прокси: {value(point.outbound_proxy)}',
]
if point.error:
diff --git a/src/App/migrations/0005_point_remove_cloudflare.py b/src/App/migrations/0005_point_remove_cloudflare.py
new file mode 100644
index 0000000..ec0822b
--- /dev/null
+++ b/src/App/migrations/0005_point_remove_cloudflare.py
@@ -0,0 +1,17 @@
+# Generated by Django 6.1.1 on 2026-09-25 13:37
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('App', '0004_point_subscription_inbound'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='point',
+ name='cloudflare',
+ ),
+ ]
diff --git a/src/App/models.py b/src/App/models.py
index 7c1ab4a..2da4ae8 100644
--- a/src/App/models.py
+++ b/src/App/models.py
@@ -85,8 +85,7 @@ class PointQuerySet(models.QuerySet):
class PointManager(models.Manager.from_queryset(PointQuerySet)):
def create_point(
- self, owner, panel: Credentials, mask: Credentials | None = None, *, domain: str = '',
- cloudflare: bool = False, outbound_proxy: str = '',
+ self, owner, panel: Credentials, mask: Credentials | None = None, *, domain: str = '', outbound_proxy: str = '',
) -> 'Point':
"""Создает точку вместе с ее серверами. Вызывать через Point.objects.
@@ -98,7 +97,6 @@ class PointManager(models.Manager.from_queryset(PointQuerySet)):
owner=owner,
name=self._unique_name(),
domain=domain,
- cloudflare=cloudflare,
outbound_proxy=outbound_proxy,
panel_username=generate_username(),
panel_password=generate_password(),
@@ -141,7 +139,6 @@ class Point(models.Model):
error = models.TextField(blank=True)
domain = models.CharField(max_length=253, blank=True)
- cloudflare = models.BooleanField(default=False, help_text='Домен спрятан за Cloudflare')
outbound_proxy = models.CharField(
max_length=512, blank=True, help_text=f'protocol://user:password@host:port или {CF_WARP}',
)
diff --git a/src/App/services.py b/src/App/services.py
index de5d687..56558ce 100644
--- a/src/App/services.py
+++ b/src/App/services.py
@@ -60,8 +60,8 @@ def set_domain(point: Point, domain: str):
Домен должен уже указывать на панель или на сервер маскировки.
"""
- if not domain and (point.mask or point.cloudflare):
- raise ValueError('маскировка и cloudflare работают только с доменом')
+ if not domain and point.mask:
+ raise ValueError('маскировка работает только с доменом')
with _installing(point):
_check(point.panel, serverus.XuiPanel(point.panel.host()).issue_certificate(domain))
point.domain = domain
diff --git a/src/App/tests_bot.py b/src/App/tests_bot.py
index 7403f7b..ee500dd 100644
--- a/src/App/tests_bot.py
+++ b/src/App/tests_bot.py
@@ -72,19 +72,16 @@ class AddServerTests(BotTestCase):
self.fill(texts.MASK, '10.0.0.2:mask-pw')
self.say(texts.DOMAIN)
self.say('Example.COM.')
- self.say(texts.CLOUDFLARE)
- self.say(texts.ENABLE)
self.say(texts.PROXY)
card = self.say(texts.CF_WARP)
self.assertIn('Домен: example.com', card)
- self.assertIn('Cloudflare: Да', card)
self.assertIn('Прокси: cf_warp', card)
self.assertEqual(self.say(texts.INSTALL), texts.INSTALL_STARTED)
point = Point.objects.get()
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'))
- self.assertEqual((point.domain, point.cloudflare, point.outbound_proxy), ('example.com', True, 'cf_warp'))
+ self.assertEqual((point.domain, point.outbound_proxy), ('example.com', 'cf_warp'))
self.assertEqual(point.owner, TelegramUser.objects.get())
send.assert_called_once_with(point.pk, Chat.objects.get().pk)
# сообщения с паролями удалены из чата
@@ -123,13 +120,6 @@ class AddServerTests(BotTestCase):
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.assertEqual(self.say(texts.INSTALL), texts.CLOUDFLARE_WITHOUT_DOMAIN)
- send.assert_not_called()
-
def test_cancel_in_prompt_returns_to_draft(self, send):
self.say(texts.ADD_SERVER)
self.fill(texts.SERVER, '10.0.0.1:pw')