diff --git a/app.py b/app.py index 55fab99..854db0f 100644 --- a/app.py +++ b/app.py @@ -674,6 +674,91 @@ def api_panel_restart(): ) return jsonify({"ok": True}) +@app.route(f"{BASE}/api/panel/change-port", methods=["POST"]) +@login_required +def api_change_panel_port(): + data = request.get_json(silent=True) or {} + new_port = int(data.get("port", 0)) + if new_port < 1024 or new_port > 65535: + return jsonify({"ok": False, "error": "Порт должен быть от 1024 до 65535"}), 400 + + current_port = int(os.environ.get("PANEL_PORT", "8080")) + if new_port == current_port: + return jsonify({"ok": True, "port": new_port}) + + # Проверить, не занят ли порт + import socket + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.bind(("0.0.0.0", new_port)) + s.close() + except OSError: + return jsonify({"ok": False, "error": f"Порт {new_port} уже занят"}), 409 + + pc = load_panel_config() + access_mode = pc.get("access_mode", "ip") + bind_host = "127.0.0.1" if access_mode == "ssh" else "0.0.0.0" + + # Обновить panel.env + env_file = "/etc/mita/panel.env" + try: + lines = Path(env_file).read_text().splitlines() + except Exception: + lines = [] + new_lines = [] + found = False + for line in lines: + if line.startswith("PANEL_PORT="): + new_lines.append(f"PANEL_PORT={new_port}") + found = True + else: + new_lines.append(line) + if not found: + new_lines.append(f"PANEL_PORT={new_port}") + Path(env_file).write_text("\n".join(new_lines) + "\n") + + # Обновить start.sh + start_sh = "/opt/mita-panel/start.sh" + try: + Path(start_sh).write_text(f"""#!/bin/bash +set -a; source /etc/mita/panel.env; set +a +SSL_ARGS="" +if [[ -n "$SSL_CERT" && -n "$SSL_KEY" && -f "$SSL_CERT" && -f "$SSL_KEY" ]]; then + SSL_ARGS="--certfile=$SSL_CERT --keyfile=$SSL_KEY" +fi +exec /opt/mita-panel/venv/bin/gunicorn \\ + --bind {bind_host}:{new_port} \\ + --workers 2 --timeout 120 \\ + --access-logfile /var/log/mita-panel-access.log \\ + --error-logfile /var/log/mita-panel.log \\ + $SSL_ARGS app:app +""") + except Exception: + pass + + # Обновить firewall + if access_mode != "ssh": + _fw_close_port(current_port) + _fw_open_port(new_port) + + secret = os.environ.get("SECRET_PATH", "") + proto = "https" if os.environ.get("SSL_CERT") else "http" + server_ip = get_server_ip() + new_url = f"{proto}://{server_ip}:{new_port}/{secret}" + + # Отложенный перезапуск + subprocess.Popen( + ["bash","-c","sleep 2 && systemctl restart mita-panel"], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + ) + + return jsonify({ + "ok": True, + "port": new_port, + "new_url": new_url, + "old_port": current_port, + }) + @app.route(f"{BASE}/api/panel/access") @login_required def api_panel_access_get(): diff --git a/mita-ctl.sh b/mita-ctl.sh index 61ff07b..6089833 100755 --- a/mita-ctl.sh +++ b/mita-ctl.sh @@ -78,32 +78,34 @@ main_menu() { echo -e "${NC}" echo -e " ${BOLD}1.${NC} Сменить способ подключения к панели" echo -e " ${BOLD}2.${NC} Управление портами (firewall)" - echo -e " ${BOLD}3.${NC} Перевыпустить SSL-сертификат панели" - echo -e " ${BOLD}4.${NC} Управление пользователями mita" - echo -e " ${BOLD}5.${NC} Показать логин и пароль админа панели" - echo -e " ${BOLD}6.${NC} Изменить данные администратора панели" - echo -e " ${BOLD}7.${NC} Настройка fail2ban" - echo -e " ${BOLD}8.${NC} Рекомендации по безопасности VPS" - echo -e " ${RED}${BOLD}9.${NC} ${RED}Полное удаление mita, панели и WARP${NC}" - echo -e " ${BOLD}10.${NC} Показать текущую конфигурацию" - echo -e " ${BOLD}11.${NC} Управление Telegram-ботом" - echo -e " ${BOLD}12.${NC} ${CYAN}Обновить панель и утилиты из GitHub${NC}" + echo -e " ${BOLD}3.${NC} Сменить порт веб-панели" + echo -e " ${BOLD}4.${NC} Перевыпустить SSL-сертификат панели" + echo -e " ${BOLD}5.${NC} Управление пользователями mita" + echo -e " ${BOLD}6.${NC} Показать логин и пароль админа панели" + echo -e " ${BOLD}7.${NC} Изменить данные администратора панели" + echo -e " ${BOLD}8.${NC} Настройка fail2ban" + echo -e " ${BOLD}9.${NC} Рекомендации по безопасности VPS" + echo -e " ${RED}${BOLD}10.${NC} ${RED}Полное удаление mita, панели и WARP${NC}" + echo -e " ${BOLD}11.${NC} Показать текущую конфигурацию" + echo -e " ${BOLD}12.${NC} Управление Telegram-ботом" + echo -e " ${BOLD}13.${NC} ${CYAN}Обновить панель и утилиты из GitHub${NC}" echo -e " ${BOLD}0.${NC} Выход" echo "" read -r -p " Выберите пункт: " choice case "$choice" in 1) menu_panel_access ;; 2) menu_firewall ;; - 3) menu_ssl ;; - 4) menu_users ;; - 5) menu_show_admin ;; - 6) menu_change_admin ;; - 7) menu_fail2ban ;; - 8) menu_security ;; - 9) menu_uninstall ;; - 10) menu_show_config ;; - 11) menu_bot ;; - 12) menu_update ;; + 3) menu_change_panel_port ;; + 4) menu_ssl ;; + 5) menu_users ;; + 6) menu_show_admin ;; + 7) menu_change_admin ;; + 8) menu_fail2ban ;; + 9) menu_security ;; + 10) menu_uninstall ;; + 11) menu_show_config ;; + 12) menu_bot ;; + 13) menu_update ;; 0) exit 0 ;; *) warn "Неверный выбор" ;; esac @@ -373,7 +375,104 @@ _fw_close() { } # ════════════════════════════════════════════════════════════════════════════ -# 3. SSL +# 3. СМЕНИТЬ ПОРТ ВЕБ-ПАНЕЛИ +# ════════════════════════════════════════════════════════════════════════════ +menu_change_panel_port() { + section "Смена порта веб-панели" + + local current_port new_port + current_port=$(env_value "PANEL_PORT"); current_port=${current_port:-8080} + local access_mode + access_mode=$(panel_value "access_mode") + + echo -e " Текущий порт: ${YELLOW}${current_port}${NC}" + if [[ "$access_mode" == "ssh" ]]; then + echo -e " Режим доступа: ${YELLOW}SSH-туннель (панель на 127.0.0.1)${NC}" + fi + echo "" + + while true; do + read -r -p " Новый порт [${current_port}]: " new_port + new_port=${new_port:-$current_port} + if [[ "$new_port" == "$current_port" ]]; then + info "Порт не изменился" + pause + return + fi + if [[ ! "$new_port" =~ ^[0-9]+$ ]] || [[ $new_port -lt 1024 || $new_port -gt 65535 ]]; then + warn "Порт должен быть числом от 1024 до 65535" + continue + fi + if ss -tlnp 2>/dev/null | grep -q ":${new_port}[[:space:]]"; then + warn "Порт $new_port уже занят. Выберите другой." + continue + fi + break + done + + echo "" + info "Смена порта с $current_port на $new_port..." + + # 1. Обновить panel.env + _update_env_val "PANEL_PORT" "$new_port" + + # 2. Пересоздать start.sh с новым портом (сохраняя bind host) + local bind_host="0.0.0.0" + [[ "$access_mode" == "ssh" ]] && bind_host="127.0.0.1" + + cat > /opt/mita-panel/start.sh << STARTEOF +#!/bin/bash +set -a; source /etc/mita/panel.env; set +a +SSL_ARGS="" +if [[ -n "\$SSL_CERT" && -n "\$SSL_KEY" && -f "\$SSL_CERT" && -f "\$SSL_KEY" ]]; then + SSL_ARGS="--certfile=\$SSL_CERT --keyfile=\$SSL_KEY" +fi +exec /opt/mita-panel/venv/bin/gunicorn \\ + --bind ${bind_host}:${new_port} \\ + --workers 2 --timeout 120 \\ + --access-logfile /var/log/mita-panel-access.log \\ + --error-logfile /var/log/mita-panel.log \\ + \$SSL_ARGS app:app +STARTEOF + chmod +x /opt/mita-panel/start.sh + + # 3. Обновить firewall + if [[ "$access_mode" != "ssh" ]]; then + _fw_close "$current_port" "tcp" + _fw_open "$new_port" "tcp" + fi + + # 4. Перезапустить панель + systemctl restart mita-panel 2>/dev/null || true + sleep 1 + + if systemctl is-active --quiet mita-panel; then + ok "Панель перезапущена на порту $new_port" + local proto + proto=$(_panel_proto) + local secret server_ip + secret=$(env_value "SECRET_PATH") + server_ip=$(curl -s --max-time 5 ifconfig.me 2>/dev/null || hostname -I | awk '{print $1}') + echo "" + echo -e " Новый адрес: ${BOLD}${GREEN}${proto}://${server_ip}:${new_port}/${secret}/${NC}" + if [[ "$access_mode" == "ssh" ]]; then + local ssh_port + ssh_port=$(panel_value "ssh_port"); ssh_port=${ssh_port:-22} + print_ssh_instruction "$new_port" "$secret" "$ssh_port" + fi + else + error "Панель не запустилась. Проверьте: journalctl -u mita-panel -n 20" + warn "Возвращаю старый порт $current_port..." + _update_env_val "PANEL_PORT" "$current_port" + sed -i "s/--bind ${bind_host}:${new_port}/--bind ${bind_host}:${current_port}/" /opt/mita-panel/start.sh 2>/dev/null || true + systemctl restart mita-panel 2>/dev/null || true + fi + + pause +} + +# ════════════════════════════════════════════════════════════════════════════ +# 4. SSL # ════════════════════════════════════════════════════════════════════════════ menu_ssl() { section "Перевыпуск SSL-сертификата" @@ -439,7 +538,7 @@ _rebuild_start_sh() { } # ════════════════════════════════════════════════════════════════════════════ -# 4. ПОЛЬЗОВАТЕЛИ +# 5. ПОЛЬЗОВАТЕЛИ # ════════════════════════════════════════════════════════════════════════════ # ── Общая функция: безопасное применение конфига mita ─────────────────────── # Запускает mita если он не активен (нужно для apply config), @@ -728,7 +827,7 @@ KARINGEOF } # ════════════════════════════════════════════════════════════════════════════ -# 5. ПОКАЗАТЬ ДАННЫЕ АДМИНА +# 6. ПОКАЗАТЬ ДАННЫЕ АДМИНА # ════════════════════════════════════════════════════════════════════════════ # ── Общая функция: вывод логина/пароля/URL панели с учётом access_mode ────── _print_panel_access_info() { @@ -781,6 +880,9 @@ menu_show_admin() { pause } +# ════════════════════════════════════════════════════════════════════════════ +# 11. КОНФИГУРАЦИЯ СЕРВЕРА +# ════════════════════════════════════════════════════════════════════════════ menu_show_config() { section "Текущая конфигурация сервера" @@ -888,7 +990,7 @@ else: } # ════════════════════════════════════════════════════════════════════════════ -# 6. ИЗМЕНИТЬ ДАННЫЕ АДМИНА +# 7. ИЗМЕНИТЬ ДАННЫЕ АДМИНА # ════════════════════════════════════════════════════════════════════════════ menu_change_admin() { section "Изменение данных администратора" @@ -924,7 +1026,7 @@ with open('$PANEL_CONFIG','w') as f: json.dump(d,f,indent=2) } # ════════════════════════════════════════════════════════════════════════════ -# 7. БЕЗОПАСНОСТЬ VPS +# 8. БЕЗОПАСНОСТЬ VPS # ════════════════════════════════════════════════════════════════════════════ menu_security() { section "Рекомендации по безопасности VPS" @@ -995,7 +1097,7 @@ SECEOF } # ════════════════════════════════════════════════════════════════════════════ -# 8. FAIL2BAN +# 9. FAIL2BAN # ════════════════════════════════════════════════════════════════════════════ menu_fail2ban() { section "Настройка fail2ban для панели" @@ -1103,7 +1205,7 @@ EOF } # ════════════════════════════════════════════════════════════════════════════ -# 11. TELEGRAM БОТ +# 12. TELEGRAM БОТ # ════════════════════════════════════════════════════════════════════════════ menu_bot() { section "Управление Telegram-ботом" @@ -1263,7 +1365,7 @@ _bot_uninstall() { } # ════════════════════════════════════════════════════════════════════════════ -# 12. ОБНОВЛЕНИЕ ПАНЕЛИ И УТИЛИТ ИЗ GITHUB +# 13. ОБНОВЛЕНИЕ ПАНЕЛИ И УТИЛИТ ИЗ GITHUB # ════════════════════════════════════════════════════════════════════════════ menu_update() { section "Обновление панели и утилит из GitHub" @@ -1363,7 +1465,7 @@ menu_update() { } # ════════════════════════════════════════════════════════════════════════════ -# 9. ПОЛНОЕ УДАЛЕНИЕ +# 10. ПОЛНОЕ УДАЛЕНИЕ # ════════════════════════════════════════════════════════════════════════════ menu_uninstall() { clear diff --git a/templates/index.html b/templates/index.html index 0ec5802..3ee9a67 100644 --- a/templates/index.html +++ b/templates/index.html @@ -198,6 +198,21 @@
+ +
+
Сменить порт панели
+

+ Если порт заблокирован провайдером — смените на другой. Панель перезапустится, страница попробует переподключиться. +

+
+
+ + +
+
+ +
+
@@ -952,6 +967,31 @@ async function restartPanel(){ btn.innerHTML=' Перезапустить панель'; } +async function changePanelPort(){ + const port=parseInt(document.getElementById('new-panel-port').value); + if(!port||port<1024||port>65535){showToast('Порт должен быть от 1024 до 65535',true);return;} + if(!confirm(`Сменить порт на ${port}? Панель перезапустится, страница попробует переподключиться.`))return; + + const el=document.getElementById('change-port-result'); + el.innerHTML=' Меняю порт...'; + + try{ + const d=await api('/api/panel/change-port','POST',{port}); + if(d?.ok){ + el.innerHTML=`
✓ Порт изменён на ${d.port}. Перенаправление...
`; + showToast(`Новый порт: ${d.port}`); + if(d.new_url){ + // Ждём 3 секунды пока панель перезапустится и переходим на новый URL + setTimeout(()=>{window.location.href=d.new_url;},3000); + } + }else{ + el.innerHTML=`
Ошибка: ${escH(d?.error||'')}
`; + } + }catch(e){ + el.innerHTML=`
Ошибка: ${escH(e.message)}
`; + } +} + // ──────────────── TELEGRAM BOT ──────────────── async function loadBotPage() { const d = await api('/api/bot/status');