version 1.5: смена порта веб-панели (CLI + панель), фикс дубликата пункта 4

- mita-ctl.sh: пункт 3 «Сменить порт веб-панели», перезапуск с откатом при ошибке
- app.py: API /api/panel/change-port, обновление env/start.sh/firewall
- templates/index.html: UI-карточка смены порта с редиректом
- README: обновлён
This commit is contained in:
2026-07-02 18:43:02 +03:00
parent 8242027859
commit a7ba4677b2
3 changed files with 256 additions and 29 deletions
+85
View File
@@ -674,6 +674,91 @@ def api_panel_restart():
) )
return jsonify({"ok": True}) 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") @app.route(f"{BASE}/api/panel/access")
@login_required @login_required
def api_panel_access_get(): def api_panel_access_get():
+131 -29
View File
@@ -78,32 +78,34 @@ main_menu() {
echo -e "${NC}" echo -e "${NC}"
echo -e " ${BOLD}1.${NC} Сменить способ подключения к панели" echo -e " ${BOLD}1.${NC} Сменить способ подключения к панели"
echo -e " ${BOLD}2.${NC} Управление портами (firewall)" echo -e " ${BOLD}2.${NC} Управление портами (firewall)"
echo -e " ${BOLD}3.${NC} Перевыпустить SSL-сертификат панели" echo -e " ${BOLD}3.${NC} Сменить порт веб-панели"
echo -e " ${BOLD}4.${NC} Управление пользователями mita" echo -e " ${BOLD}4.${NC} Перевыпустить SSL-сертификат панели"
echo -e " ${BOLD}5.${NC} Показать логин и пароль админа панели" echo -e " ${BOLD}5.${NC} Управление пользователями mita"
echo -e " ${BOLD}6.${NC} Изменить данные администратора панели" echo -e " ${BOLD}6.${NC} Показать логин и пароль админа панели"
echo -e " ${BOLD}7.${NC} Настройка fail2ban" echo -e " ${BOLD}7.${NC} Изменить данные администратора панели"
echo -e " ${BOLD}8.${NC} Рекомендации по безопасности VPS" echo -e " ${BOLD}8.${NC} Настройка fail2ban"
echo -e " ${RED}${BOLD}9.${NC} ${RED}Полное удаление mita, панели и WARP${NC}" echo -e " ${BOLD}9.${NC} Рекомендации по безопасности VPS"
echo -e " ${BOLD}10.${NC} Показать текущую конфигурацию" echo -e " ${RED}${BOLD}10.${NC} ${RED}Полное удаление mita, панели и WARP${NC}"
echo -e " ${BOLD}11.${NC} Управление Telegram-ботом" echo -e " ${BOLD}11.${NC} Показать текущую конфигурацию"
echo -e " ${BOLD}12.${NC} ${CYAN}Обновить панель и утилиты из GitHub${NC}" echo -e " ${BOLD}12.${NC} Управление Telegram-ботом"
echo -e " ${BOLD}13.${NC} ${CYAN}Обновить панель и утилиты из GitHub${NC}"
echo -e " ${BOLD}0.${NC} Выход" echo -e " ${BOLD}0.${NC} Выход"
echo "" echo ""
read -r -p " Выберите пункт: " choice read -r -p " Выберите пункт: " choice
case "$choice" in case "$choice" in
1) menu_panel_access ;; 1) menu_panel_access ;;
2) menu_firewall ;; 2) menu_firewall ;;
3) menu_ssl ;; 3) menu_change_panel_port ;;
4) menu_users ;; 4) menu_ssl ;;
5) menu_show_admin ;; 5) menu_users ;;
6) menu_change_admin ;; 6) menu_show_admin ;;
7) menu_fail2ban ;; 7) menu_change_admin ;;
8) menu_security ;; 8) menu_fail2ban ;;
9) menu_uninstall ;; 9) menu_security ;;
10) menu_show_config ;; 10) menu_uninstall ;;
11) menu_bot ;; 11) menu_show_config ;;
12) menu_update ;; 12) menu_bot ;;
13) menu_update ;;
0) exit 0 ;; 0) exit 0 ;;
*) warn "Неверный выбор" ;; *) warn "Неверный выбор" ;;
esac 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() { menu_ssl() {
section "Перевыпуск SSL-сертификата" section "Перевыпуск SSL-сертификата"
@@ -439,7 +538,7 @@ _rebuild_start_sh() {
} }
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════
# 4. ПОЛЬЗОВАТЕЛИ # 5. ПОЛЬЗОВАТЕЛИ
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════
# ── Общая функция: безопасное применение конфига mita ─────────────────────── # ── Общая функция: безопасное применение конфига mita ───────────────────────
# Запускает mita если он не активен (нужно для apply config), # Запускает mita если он не активен (нужно для apply config),
@@ -728,7 +827,7 @@ KARINGEOF
} }
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════
# 5. ПОКАЗАТЬ ДАННЫЕ АДМИНА # 6. ПОКАЗАТЬ ДАННЫЕ АДМИНА
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════
# ── Общая функция: вывод логина/пароля/URL панели с учётом access_mode ────── # ── Общая функция: вывод логина/пароля/URL панели с учётом access_mode ──────
_print_panel_access_info() { _print_panel_access_info() {
@@ -781,6 +880,9 @@ menu_show_admin() {
pause pause
} }
# ════════════════════════════════════════════════════════════════════════════
# 11. КОНФИГУРАЦИЯ СЕРВЕРА
# ════════════════════════════════════════════════════════════════════════════
menu_show_config() { menu_show_config() {
section "Текущая конфигурация сервера" section "Текущая конфигурация сервера"
@@ -888,7 +990,7 @@ else:
} }
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════
# 6. ИЗМЕНИТЬ ДАННЫЕ АДМИНА # 7. ИЗМЕНИТЬ ДАННЫЕ АДМИНА
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════
menu_change_admin() { menu_change_admin() {
section "Изменение данных администратора" section "Изменение данных администратора"
@@ -924,7 +1026,7 @@ with open('$PANEL_CONFIG','w') as f: json.dump(d,f,indent=2)
} }
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════
# 7. БЕЗОПАСНОСТЬ VPS # 8. БЕЗОПАСНОСТЬ VPS
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════
menu_security() { menu_security() {
section "Рекомендации по безопасности VPS" section "Рекомендации по безопасности VPS"
@@ -995,7 +1097,7 @@ SECEOF
} }
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════
# 8. FAIL2BAN # 9. FAIL2BAN
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════
menu_fail2ban() { menu_fail2ban() {
section "Настройка fail2ban для панели" section "Настройка fail2ban для панели"
@@ -1103,7 +1205,7 @@ EOF
} }
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════
# 11. TELEGRAM БОТ # 12. TELEGRAM БОТ
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════
menu_bot() { menu_bot() {
section "Управление Telegram-ботом" section "Управление Telegram-ботом"
@@ -1263,7 +1365,7 @@ _bot_uninstall() {
} }
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════
# 12. ОБНОВЛЕНИЕ ПАНЕЛИ И УТИЛИТ ИЗ GITHUB # 13. ОБНОВЛЕНИЕ ПАНЕЛИ И УТИЛИТ ИЗ GITHUB
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════
menu_update() { menu_update() {
section "Обновление панели и утилит из GitHub" section "Обновление панели и утилит из GitHub"
@@ -1363,7 +1465,7 @@ menu_update() {
} }
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════
# 9. ПОЛНОЕ УДАЛЕНИЕ # 10. ПОЛНОЕ УДАЛЕНИЕ
# ════════════════════════════════════════════════════════════════════════════ # ════════════════════════════════════════════════════════════════════════════
menu_uninstall() { menu_uninstall() {
clear clear
+40
View File
@@ -198,6 +198,21 @@
</button> </button>
<div id="restart-result" style="margin-top:12px"></div> <div id="restart-result" style="margin-top:12px"></div>
</div> </div>
<div class="card">
<div class="card-title">Сменить порт панели</div>
<p style="color:var(--muted);font-size:.875rem;margin-bottom:14px;line-height:1.6">
Если порт заблокирован провайдером — смените на другой. Панель перезапустится, страница попробует переподключиться.
</p>
<div class="form-row">
<div class="form-group" style="max-width:180px">
<label>Новый порт</label>
<input type="number" id="new-panel-port" value="" min="1024" max="65535" placeholder="8080">
</div>
</div>
<button class="btn btn-warn" onclick="changePanelPort()">Сменить порт</button>
<div id="change-port-result" style="margin-top:12px"></div>
</div>
</div> </div>
<!-- SECURITY / FAIL2BAN --> <!-- SECURITY / FAIL2BAN -->
@@ -952,6 +967,31 @@ async function restartPanel(){
btn.innerHTML='<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg> Перезапустить панель'; btn.innerHTML='<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg> Перезапустить панель';
} }
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='<span class="spinner"></span> Меняю порт...';
try{
const d=await api('/api/panel/change-port','POST',{port});
if(d?.ok){
el.innerHTML=`<div class="alert alert-ok">✓ Порт изменён на ${d.port}. Перенаправление...</div>`;
showToast(`Новый порт: ${d.port}`);
if(d.new_url){
// Ждём 3 секунды пока панель перезапустится и переходим на новый URL
setTimeout(()=>{window.location.href=d.new_url;},3000);
}
}else{
el.innerHTML=`<div class="alert alert-err">Ошибка: ${escH(d?.error||'')}</div>`;
}
}catch(e){
el.innerHTML=`<div class="alert alert-err">Ошибка: ${escH(e.message)}</div>`;
}
}
// ──────────────── TELEGRAM BOT ──────────────── // ──────────────── TELEGRAM BOT ────────────────
async function loadBotPage() { async function loadBotPage() {
const d = await api('/api/bot/status'); const d = await api('/api/bot/status');