diff --git a/README.md b/README.md index e19bf70..dc336a5 100644 --- a/README.md +++ b/README.md @@ -92,10 +92,8 @@ curl -Ls https://raw.githubusercontent.com/grendervilll/mitactl/main/bootstrap.s - [x] Защита сессий: динамический Secure cookie, in-memory rate limiter - [x] Bootstrap-установщик (одна команда) - [x] Мониторинг сервера: CPU, RAM, диск на дашборде (автообновление каждые 2 сек) -- [x] Telegram-бот: inline-кнопки, создание/удаление/WARP/дашборд, конфиг/Karing, fail2ban-безопасность -- [x] Установка бота: веб-панель, CLI, полный установщик; автоопределение Telegram ID через API -- [x] Бот в venv (обход externally-managed), авто-SSL, SSH-туннель, обновление из GitHub -- [x] Fallback-установка: зеркала PyPI + таймауты → apt при недоступности pip +- [x] Telegram-бот: inline-кнопки, меню пользователя с конфигом/Karing, create/delete/WARP/дашборд/безопасность +- [x] Установка: веб-панель, CLI, полный установщик; авто-ID через API; venv + зеркала PyPI + fallback на apt ### ⬜ Запланировано diff --git a/bot.py b/bot.py index e0929a1..628731b 100644 --- a/bot.py +++ b/bot.py @@ -202,10 +202,18 @@ async def show_users(update: Update, context: ContextTypes.DEFAULT_TYPE): mon = f"{s.get('month_mb', 0):.1f} МБ" if s.get("month_mb") else "—" ename = escape_md(name) lines.append(f"{o} {ename} {w} день: {day} нед: {wk} мес: {mon}") - user_buttons.append([ - InlineKeyboardButton(f"📋 {name}", callback_data=f"config_{name}"), - InlineKeyboardButton(f"📱 Karing", callback_data=f"karing_{name}"), - ]) + # Каждый пользователь — одна кнопка, открывает меню с конфигом и Karing + user_buttons.append([InlineKeyboardButton( + f"{'🟢 ' if s.get('online') else ''}{name}", + callback_data=f"user_{name}" + )]) + + user_buttons.extend([ + [InlineKeyboardButton("➕ Создать", callback_data="create"), + InlineKeyboardButton("🗑 Удалить", callback_data="delete_menu")], + [InlineKeyboardButton("🔄 WARP", callback_data="warp_menu")], + [InlineKeyboardButton("« Назад", callback_data="main")], + ]) user_buttons.extend([ [InlineKeyboardButton("➕ Создать", callback_data="create"), @@ -413,6 +421,27 @@ def _build_client_config(name, password): } # ── config / Karing ────────────────────────────────────────────────────────── +@admin_only +async def show_user_menu(update: Update, context: ContextTypes.DEFAULT_TYPE): + query = update.callback_query + await query.answer() + name = query.data[len("user_"):] + cfg = load_mita_config() + user = next((u for u in cfg.get("users", []) if u["name"] == name), None) + if not user: + await query.answer("Пользователь не найден") + return + await query.edit_message_text( + f"👤 *{escape_md(name)}*", + reply_markup=InlineKeyboardMarkup([ + [InlineKeyboardButton("📋 JSON-конфиг", callback_data=f"config_{name}"), + InlineKeyboardButton("📱 Karing", callback_data=f"karing_{name}")], + [InlineKeyboardButton("« К пользователям", callback_data="users")], + [InlineKeyboardButton("« На главную", callback_data="main")], + ]), + parse_mode="MarkdownV2" + ) + @admin_only async def show_config(update: Update, context: ContextTypes.DEFAULT_TYPE): query = update.callback_query @@ -428,9 +457,9 @@ async def show_config(update: Update, context: ContextTypes.DEFAULT_TYPE): await query.edit_message_text( f"📋 Конфиг `{escape_md(name)}`:\n```json\n{data[:3500]}\n```", reply_markup=InlineKeyboardMarkup([ - [InlineKeyboardButton("« К пользователям", callback_data="users")], [InlineKeyboardButton("📱 Karing", callback_data=f"karing_{name}")], - [InlineKeyboardButton("« На главную", callback_data="main")], + [InlineKeyboardButton("« К пользователю", callback_data=f"user_{name}")], + [InlineKeyboardButton("« К списку", callback_data="users")], ]), parse_mode="MarkdownV2" ) @@ -466,8 +495,8 @@ async def show_karing(update: Update, context: ContextTypes.DEFAULT_TYPE): text, reply_markup=InlineKeyboardMarkup([ [InlineKeyboardButton("📋 JSON-конфиг", callback_data=f"config_{name}")], - [InlineKeyboardButton("« К пользователям", callback_data="users")], - [InlineKeyboardButton("« На главную", callback_data="main")], + [InlineKeyboardButton("« К пользователю", callback_data=f"user_{name}")], + [InlineKeyboardButton("« К списку", callback_data="users")], ]), parse_mode="MarkdownV2" ) @@ -690,6 +719,8 @@ async def router(update: Update, context: ContextTypes.DEFAULT_TYPE): return await warp_menu(update, context) elif data.startswith("warp_"): return await toggle_warp(update, context) + elif data.startswith("user_"): + return await show_user_menu(update, context) elif data == "security": return await show_security(update, context) elif data.startswith("config_"): diff --git a/install-bot.sh b/install-bot.sh index 394becf..d24ea2c 100644 --- a/install-bot.sh +++ b/install-bot.sh @@ -75,7 +75,7 @@ fi info "Установка python-telegram-bot (может занять до минуты)..." _bot_pip() { - timeout 120 "$BOT_VENV/bin/pip" install -q --disable-pip-version-check --timeout 15 "$@" + PIP_RETRIES=0 timeout 30 "$BOT_VENV/bin/pip" install -q --disable-pip-version-check --timeout 5 --retries 1 "$@" } bot_pip_ok=false for mirror in "https://pypi.org/simple/" "https://mirrors.aliyun.com/pypi/simple/"; do diff --git a/install-panel.sh b/install-panel.sh index ddb3365..09cbb7f 100755 --- a/install-panel.sh +++ b/install-panel.sh @@ -77,10 +77,9 @@ python3 -m venv "$PANEL_DIR/venv" info "Установка Flask и зависимостей (может занять до минуты)..." _pip_install() { - # Принудительно IPv4 + таймаут 15 секунд на соединение - timeout 120 "$PANEL_DIR/venv/bin/pip" install \ + PIP_RETRIES=0 timeout 30 "$PANEL_DIR/venv/bin/pip" install \ --no-input --quiet --disable-pip-version-check \ - --timeout 15 \ + --timeout 5 --retries 1 \ "$@" }