164 lines
9.1 KiB
Python
164 lines
9.1 KiB
Python
"""Кнопки и тексты бота. Сообщения размечены HTML, данные пользователя экранируются."""
|
|
from html import escape
|
|
|
|
from ..models import Point, Server
|
|
|
|
# главное меню
|
|
MAIN_MENU = 'главное меню'
|
|
FAQ = 'ФАК'
|
|
ADD_SERVER = 'добавить сервер'
|
|
HELP = 'помощь'
|
|
MANAGE = 'управление сервером'
|
|
|
|
# общие
|
|
CANCEL = 'отмена'
|
|
DELETE = 'удалить'
|
|
YES = 'Да'
|
|
NO = 'Нет'
|
|
|
|
# меню добавления сервера
|
|
SERVER = 'сервер'
|
|
MASK = 'маскировка'
|
|
DOMAIN = 'домен'
|
|
CLOUDFLARE = 'cloudflare'
|
|
PROXY = 'прокси'
|
|
INSTALL = 'установить'
|
|
ENABLE = 'включить'
|
|
DISABLE = 'выключить'
|
|
CF_WARP = 'cf_warp'
|
|
|
|
# меню сервера
|
|
SERVER_MENU = 'меню сервера'
|
|
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]]
|
|
CANCEL_KEYBOARD = [[CANCEL]]
|
|
|
|
EMPTY = '-'
|
|
|
|
GREETING = 'привет. выбери действие'
|
|
|
|
FAQ_TEXT = '''<b>Что это за бот?</b>
|
|
Бот настраивает ваш собственный VPN на ваших серверах: панель 3x-ui с подключениями vless + tls + reality и сервер маскировки перед ней.
|
|
|
|
<b>Что нужно от меня?</b>
|
|
Два арендованных сервера с Debian 13 (от 2 ядер, 2 Гб памяти, 20 Гб диска): ip и пароль root каждого. По желанию - домен и прокси на выходе.
|
|
|
|
<b>Зачем два сервера?</b>
|
|
Клиенты подключаются к серверу маскировки, он пересылает трафик на панель. Адрес панели никто не видит.
|
|
|
|
<b>Что бот делает с сервером?</b>
|
|
Прописывает свой ssh ключ, меняет пароль root, обновляет систему, переносит ssh на случайный порт с входом только по ключу, включает фаервол и ставит нужные сервисы.
|
|
|
|
<b>Как забрать сервер назад?</b>
|
|
Меню сервера - "Вернуть". Бот уберет свои доступы, откатит ssh и фаервол, поставит новый пароль root и пришлет его. Данные на сервере останутся.
|
|
|
|
<b>Сервер переустановили у провайдера</b>
|
|
Меню сервера - "Переустановить" и новый пароль root.'''
|
|
|
|
HELP_TEXT = '''<b>Добавить сервер</b>
|
|
1. "добавить сервер"
|
|
2. "сервер" - пришлите <code>ip:пароль</code> сервера для панели
|
|
3. "маскировка" - пришлите <code>ip:пароль</code> сервера маскировки
|
|
4. по желанию "домен", "cloudflare", "прокси"
|
|
5. "установить" - установка идет в фоне, по окончании придет уведомление
|
|
|
|
<b>Управлять сервером</b>
|
|
"управление сервером" и имя сервера из главного меню.
|
|
|
|
"главное меню" или /start всегда возвращают в начало.'''
|
|
|
|
CREDENTIALS_PROMPT = 'отправь данные для подключения к серверу\n<code>ip:root_password</code>'
|
|
CREDENTIALS_INVALID = 'Не похоже на <code>ip:root_password</code>. Попробуй еще раз.'
|
|
ADDRESS_TAKEN = 'Сервер {address} уже подключен.'
|
|
ADDRESS_DUPLICATE = 'Панель и маскировка должны быть разными серверами.'
|
|
DOMAIN_PROMPT = 'Пришли доменное имя. Доменное имя лучше подходит для маскировки вашей точки доступа.'
|
|
DOMAIN_INVALID = 'Не похоже на доменное имя. Попробуй еще раз.'
|
|
CLOUDFLARE_PROMPT = 'Если вы спрятали домен за клаудом, то нужно включить опцию тут.'
|
|
PROXY_PROMPT = 'Можно указать свои в формате <code>protocol://username:password@host:port</code> или включить cf_warp.'
|
|
PROXY_INVALID = 'Не похоже на <code>protocol://username:password@host:port</code>. Попробуй еще раз.'
|
|
DRAFT_INCOMPLETE = 'Нужны оба сервера: "сервер" и "маскировка".'
|
|
CLOUDFLARE_WITHOUT_DOMAIN = 'Cloudflare работает только с доменом. Укажи домен или выключи cloudflare.'
|
|
INSTALL_STARTED = 'Запускаю установку сервера. Отправлю уведомление когда все будет готово.'
|
|
|
|
SELECT_PROMPT = 'Пришлите server_name'
|
|
POINT_NOT_FOUND = 'Сервер не найден. Пришлите server_name из списка.'
|
|
POINT_BUSY = 'Сервер сейчас {status}. Дождитесь окончания.'
|
|
RELEASE_CONFIRM = '''Сервер будет освобожден из-под моего контроля,
|
|
будет установлен случайный пароль рута,
|
|
мои доступы будут убраны, данные на сервере сохранены.
|
|
Будет отправлено уведомление с новым паролем. Продолжить?'''
|
|
RELEASE_STARTED = 'Освобождаю серверы. Пришлю новые пароли, когда закончу.'
|
|
REINSTALL_PROMPT = '''Будет произведена повторная установка всех компонентов.
|
|
Вызывайте эту функцию, только после того, как в панели управления
|
|
сервером в аккаунте хостинг провайдера вызовите переустановку сервера.
|
|
Если уверены, то пришлите <code>ip:новый_пароль</code> переустановленного сервера.
|
|
"Использовать старый" переустановит оба сервера с последними известными паролями.'''
|
|
REINSTALL_UNKNOWN_ADDRESS = 'Сервер {address} не входит в эту точку.'
|
|
REINSTALL_STARTED = 'Запускаю переустановку. Отправлю уведомление когда все будет готово.'
|
|
NOT_IMPLEMENTED = 'Пока не реализовано.'
|
|
FALLBACK = 'Не понял. Воспользуйся кнопками меню.'
|
|
|
|
|
|
def value(text: str) -> str:
|
|
return escape(text) if text else EMPTY
|
|
|
|
|
|
def draft_card(draft: dict) -> str:
|
|
return '\n'.join([
|
|
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', ''))}",
|
|
])
|
|
|
|
|
|
def points_list(points) -> str:
|
|
return '\n'.join(f'<code>{escape(point.name)}</code>\t{point.get_status_display()}' for point in points)
|
|
|
|
|
|
def point_card(point: Point) -> str:
|
|
servers = {server.role: server for server in point.servers.all()}
|
|
lines = [
|
|
f'Сервер: <code>{escape(point.name)}</code>',
|
|
f'Состояние: {point.get_status_display()}',
|
|
]
|
|
for role in Server.Role:
|
|
server = servers.get(role)
|
|
lines.append(f'{role.label.capitalize()}: {escape(server.address) if server else EMPTY}')
|
|
lines += [
|
|
# TODO: онлайн/всего ключей из API панели
|
|
f'Ключи: {EMPTY}',
|
|
f'Домен: {value(point.domain)}',
|
|
f"Cloudflare: {'Да' if point.cloudflare else EMPTY}",
|
|
f'Прокси: {value(point.outbound_proxy)}',
|
|
]
|
|
if point.error:
|
|
lines.append(f'Ошибка: {escape(point.error)}')
|
|
return '\n'.join(lines)
|
|
|
|
|
|
def installed(point: Point) -> str:
|
|
return f'сервер {escape(point.mask.address)} - проименован как <code>{escape(point.name)}</code>, готов к работе.'
|
|
|
|
|
|
def install_failed(point: Point, error: Exception) -> str:
|
|
return f'Не удалось настроить <code>{escape(point.name)}</code>:\n{escape(str(error))}'
|
|
|
|
|
|
def released(name: str, passwords: dict[str, str], errors: dict[str, str]) -> str:
|
|
lines = [f'Серверы <code>{escape(name)}</code> возвращены.' if not errors else f'<code>{escape(name)}</code>:']
|
|
lines += [f'{escape(address)} - пароль root <code>{escape(password)}</code>' for address, password in passwords.items()]
|
|
lines += [f'{escape(address)} - не удалось вернуть: {escape(error)}' for address, error in errors.items()]
|
|
return '\n'.join(lines)
|