#!/bin/bash # ============================================================================= # mita-ctl — утилита управления mita сервером # Запуск: sudo bash mita-ctl.sh # ============================================================================= set -euo pipefail RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' BLUE='\033[0;34m'; CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m' info() { echo -e "${BLUE}[INFO]${NC} $*"; } ok() { echo -e "${GREEN}[OK]${NC} $*"; } warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } error() { echo -e "${RED}[ERR]${NC} $*"; } section() { echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"; echo -e "${CYAN} $*${NC}"; echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"; } hr() { echo -e "${BLUE}─────────────────────────────────────────────────${NC}"; } pause() { echo ""; read -r -p "Нажмите Enter для продолжения..." _; } [[ $EUID -ne 0 ]] && { echo -e "${RED}Запустите от root: sudo bash mita-ctl.sh${NC}"; exit 1; } PANEL_CONFIG="/etc/mita/panel.json" PANEL_ENV="/etc/mita/panel.env" MITA_CONFIG="/etc/mita/server_config.json" load_panel() { [[ -f "$PANEL_CONFIG" ]] && cat "$PANEL_CONFIG" || echo "{}" } panel_value() { load_panel | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$1',''))" 2>/dev/null || echo ""; } env_value() { [[ -f "$PANEL_ENV" ]] && grep "^$1=" "$PANEL_ENV" | cut -d= -f2- || echo ""; } gen_pass() { openssl rand -base64 48 | tr -d '/+=' | head -c 32; } # ── SSH tunnel helper ────────────────────────────────────────────────────── print_ssh_instruction() { local port="$1" local secret="$2" local ssh_port="${3:-22}" local server_ip server_ip=$(curl -s --max-time 5 ifconfig.me 2>/dev/null || hostname -I | awk '{print $1}') echo "" echo -e "${CYAN}┌─────────────────────────────────────────────────────────┐${NC}" echo -e "${CYAN}│ Подключение к панели через SSH-туннель │${NC}" echo -e "${CYAN}└─────────────────────────────────────────────────────────┘${NC}" echo "" echo -e " Выполните на ${BOLD}вашем компьютере${NC}:" echo "" echo -e " ${YELLOW}ssh -L ${port}:127.0.0.1:${port} -p ${ssh_port} root@${server_ip} -N${NC}" echo "" echo -e " Затем откройте в браузере:" echo -e " ${BOLD}${GREEN}http://127.0.0.1:${port}/${secret}/${NC}" echo "" echo -e " ${BLUE}Флаг -N означает что SSH не выполняет команды, только туннель.${NC}" echo -e " ${BLUE}Для фонового режима добавьте -f:${NC}" echo -e " ${YELLOW}ssh -fN -L ${port}:127.0.0.1:${port} -p ${ssh_port} root@${server_ip}${NC}" echo "" } # ════════════════════════════════════════════════════════════════════════════ # ГЛАВНОЕ МЕНЮ # ════════════════════════════════════════════════════════════════════════════ main_menu() { while true; do clear echo -e "${CYAN}" echo " ╔══════════════════════════════════════════════════════╗" echo " ║ mita-ctl — Управление сервером ║" echo " ╚══════════════════════════════════════════════════════╝" 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 " ${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 ;; 0) exit 0 ;; *) warn "Неверный выбор" ;; esac done } # ════════════════════════════════════════════════════════════════════════════ # 1. СПОСОБ ПОДКЛЮЧЕНИЯ К ПАНЕЛИ # ════════════════════════════════════════════════════════════════════════════ menu_panel_access() { section "Способ подключения к панели" local panel_port secret ssh_port panel_port=$(env_value "PANEL_PORT"); panel_port=${panel_port:-8080} secret=$(panel_value "secret_path") echo -e " Текущий порт панели: ${YELLOW}${panel_port}${NC}" echo -e " Текущий секретный путь: ${YELLOW}/${secret}/${NC}" echo "" echo -e " ${BOLD}1.${NC} SSH-туннель (панель недоступна снаружи)" echo -e " ${BOLD}2.${NC} По IP-адресу (текущий способ)" echo -e " ${BOLD}3.${NC} По доменному имени с Let's Encrypt" echo -e " ${BOLD}0.${NC} Назад" echo "" read -r -p " Выбор: " choice case "$choice" in 1) section "SSH-туннель" echo "Панель будет слушать только на 127.0.0.1 (недоступна снаружи)." echo "" read -r -p "SSH-порт вашего VPS [22]: " ssh_port ssh_port=${ssh_port:-22} # Привязать gunicorn к 127.0.0.1 _update_panel_bind "127.0.0.1" "$panel_port" # Закрыть порт панели в firewall _fw_close "$panel_port" "tcp" # Обновить panel.json python3 -c " import json with open('$PANEL_CONFIG') as f: d=json.load(f) d['access_mode']='ssh' d['ssh_port']=$ssh_port with open('$PANEL_CONFIG','w') as f: json.dump(d,f,indent=2) " systemctl restart mita-panel 2>/dev/null || true ok "Панель переключена на SSH-туннель" print_ssh_instruction "$panel_port" "$secret" "$ssh_port" pause ;; 2) section "Доступ по IP" local server_ip server_ip=$(curl -s --max-time 5 ifconfig.me 2>/dev/null || hostname -I | awk '{print $1}') _update_panel_bind "0.0.0.0" "$panel_port" _fw_open "$panel_port" "tcp" python3 -c " import json with open('$PANEL_CONFIG') as f: d=json.load(f) d['access_mode']='ip' with open('$PANEL_CONFIG','w') as f: json.dump(d,f,indent=2) " systemctl restart mita-panel 2>/dev/null || true ok "Панель доступна по IP" echo "" echo -e " Адрес: ${BOLD}${GREEN}http://${server_ip}:${panel_port}/${secret}/${NC}" pause ;; 3) section "Let's Encrypt" read -r -p "Домен (например panel.example.com): " domain read -r -p "Email для Let's Encrypt (Enter = пропустить): " email [[ -z "$domain" ]] && { warn "Домен не указан"; pause; return; } # Установить certbot command -v certbot &>/dev/null || apt-get install -y -qq certbot local le_args=("certbot" "certonly" "--standalone" "--non-interactive" "--agree-tos" "-d" "$domain") [[ -n "$email" ]] && le_args+=("--email" "$email") || le_args+=("--register-unsafely-without-email") info "Получение сертификата Let's Encrypt..." if "${le_args[@]}"; then local cert="/etc/letsencrypt/live/${domain}/fullchain.pem" local key="/etc/letsencrypt/live/${domain}/privkey.pem" _update_env_val "SSL_CERT" "$cert" _update_env_val "SSL_KEY" "$key" _update_panel_bind "0.0.0.0" "$panel_port" _fw_open "$panel_port" "tcp" python3 -c " import json with open('$PANEL_CONFIG') as f: d=json.load(f) d.update({'access_mode':'domain','ssl_type':'letsencrypt','ssl_domain':'$domain','ssl_cert':'$cert','ssl_key':'$key'}) with open('$PANEL_CONFIG','w') as f: json.dump(d,f,indent=2) " # Настроить автопродление (crontab -l 2>/dev/null; echo "0 3 * * * certbot renew --quiet && systemctl restart mita-panel") | sort -u | crontab - systemctl restart mita-panel 2>/dev/null || true ok "Сертификат получен" echo -e " Адрес: ${BOLD}${GREEN}https://${domain}:${panel_port}/${secret}/${NC}" else error "Не удалось получить сертификат. Убедитесь что домен указывает на этот сервер и порт 80 открыт." fi pause ;; 0) return ;; esac } _update_panel_bind() { local host="$1" port="$2" # Обновить ExecStart в systemd unit через враппер start.sh sed -i "s/--bind [0-9.:]*:[0-9]*/--bind ${host}:${port}/" /opt/mita-panel/start.sh 2>/dev/null || true # Обновить PANEL_PORT в env _update_env_val "PANEL_BIND_HOST" "$host" # Пересоздать start.sh с нужным bind 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 ${host}:\${PANEL_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 } _update_env_val() { local key="$1" val="$2" if grep -q "^${key}=" "$PANEL_ENV" 2>/dev/null; then sed -i "s|^${key}=.*|${key}=${val}|" "$PANEL_ENV" else echo "${key}=${val}" >> "$PANEL_ENV" fi } # ════════════════════════════════════════════════════════════════════════════ # 2. FIREWALL # ════════════════════════════════════════════════════════════════════════════ menu_firewall() { section "Управление портами (Firewall)" # Определить активный firewall local FW="none" command -v ufw &>/dev/null && ufw status | grep -q "Status: active" && FW="ufw" [[ "$FW" == "none" ]] && command -v iptables &>/dev/null && FW="iptables" info "Активный firewall: $FW" echo "" echo -e " ${BOLD}1.${NC} Открыть порт" echo -e " ${BOLD}2.${NC} Закрыть порт" echo -e " ${BOLD}3.${NC} Применить рекомендуемые правила (только нужные порты)" echo -e " ${BOLD}4.${NC} Показать текущие правила" echo -e " ${BOLD}0.${NC} Назад" echo "" read -r -p " Выбор: " choice case "$choice" in 1) read -r -p "Порт: " p; read -r -p "Протокол [tcp]: " proto; proto=${proto:-tcp} _fw_open "$p" "$proto" && ok "Порт $p/$proto открыт" pause ;; 2) read -r -p "Порт: " p; read -r -p "Протокол [tcp]: " proto; proto=${proto:-tcp} _fw_close "$p" "$proto" && ok "Порт $p/$proto закрыт" pause ;; 3) section "Рекомендуемые правила" local panel_port mita_range panel_port=$(env_value "PANEL_PORT"); panel_port=${panel_port:-8080} mita_range=$(python3 -c " import json with open('$MITA_CONFIG') as f: d=json.load(f) b=d.get('portBindings',[{}])[0] print(b.get('portRange',b.get('port','2100-2110'))) " 2>/dev/null || echo "2100-2110") read -r -p "SSH-порт вашего VPS [22]: " ssh_port ssh_port=${ssh_port:-22} local access_mode access_mode=$(panel_value "access_mode") info "Настраиваю правила:" info " SSH: $ssh_port/tcp" info " mita: $mita_range/tcp" [[ "$access_mode" != "ssh" ]] && info " Панель: $panel_port/tcp" || info " Панель: закрыта (SSH-туннель)" if [[ "$FW" == "ufw" ]]; then ufw --force reset 2>/dev/null ufw default deny incoming ufw default allow outgoing ufw allow "$ssh_port/tcp" comment "SSH" # mita ports local start_p end_p if [[ "$mita_range" == *"-"* ]]; then start_p="${mita_range%-*}"; end_p="${mita_range#*-}" ufw allow "${start_p}:${end_p}/tcp" comment "mita" else ufw allow "$mita_range/tcp" comment "mita" fi [[ "$access_mode" != "ssh" ]] && ufw allow "$panel_port/tcp" comment "mita-panel" ufw --force enable else iptables -F INPUT 2>/dev/null iptables -P INPUT DROP iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -p tcp --dport "$ssh_port" -j ACCEPT if [[ "$mita_range" == *"-"* ]]; then start_p="${mita_range%-*}"; end_p="${mita_range#*-}" iptables -A INPUT -p tcp --dport "${start_p}:${end_p}" -j ACCEPT else iptables -A INPUT -p tcp --dport "$mita_range" -j ACCEPT fi [[ "$access_mode" != "ssh" ]] && iptables -A INPUT -p tcp --dport "$panel_port" -j ACCEPT command -v iptables-save &>/dev/null && iptables-save > /etc/iptables/rules.v4 2>/dev/null || true fi ok "Правила применены" pause ;; 4) echo "" if [[ "$FW" == "ufw" ]]; then ufw status numbered else iptables -L INPUT -n --line-numbers; fi pause ;; 0) return ;; esac } _fw_open() { local port="$1" proto="${2:-tcp}" command -v ufw &>/dev/null && ufw status | grep -q "Status: active" \ && ufw allow "$port/$proto" 2>/dev/null || true iptables -I INPUT -p "$proto" --dport "$port" -j ACCEPT 2>/dev/null || true } _fw_close() { local port="$1" proto="${2:-tcp}" command -v ufw &>/dev/null && ufw status | grep -q "Status: active" \ && ufw delete allow "$port/$proto" 2>/dev/null || true iptables -D INPUT -p "$proto" --dport "$port" -j ACCEPT 2>/dev/null || true } # ════════════════════════════════════════════════════════════════════════════ # 3. SSL # ════════════════════════════════════════════════════════════════════════════ menu_ssl() { section "Перевыпуск SSL-сертификата" echo -e " ${BOLD}1.${NC} Самоподписной (10 лет)" echo -e " ${BOLD}2.${NC} Let's Encrypt" echo -e " ${BOLD}0.${NC} Назад" echo "" read -r -p " Выбор: " choice case "$choice" in 1) local cert_dir="/etc/mita/ssl" mkdir -p "$cert_dir" openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \ -keyout "$cert_dir/selfsigned.key" -out "$cert_dir/selfsigned.crt" \ -subj "/CN=mita-panel/O=mita/C=XX" 2>/dev/null _update_env_val "SSL_CERT" "$cert_dir/selfsigned.crt" _update_env_val "SSL_KEY" "$cert_dir/selfsigned.key" python3 -c " import json with open('$PANEL_CONFIG') as f: d=json.load(f) d.update({'ssl_type':'selfsigned','ssl_cert':'$cert_dir/selfsigned.crt','ssl_key':'$cert_dir/selfsigned.key'}) with open('$PANEL_CONFIG','w') as f: json.dump(d,f,indent=2) " _rebuild_start_sh systemctl restart mita-panel 2>/dev/null || true ok "Самоподписной сертификат создан и применён" pause ;; 2) local domain read -r -p "Домен: " domain [[ -z "$domain" ]] && { warn "Не указан домен"; pause; return; } command -v certbot &>/dev/null || apt-get install -y -qq certbot if certbot renew --cert-name "$domain" --quiet 2>/dev/null || \ certbot certonly --standalone --non-interactive --agree-tos \ --register-unsafely-without-email -d "$domain"; then local cert="/etc/letsencrypt/live/${domain}/fullchain.pem" local key="/etc/letsencrypt/live/${domain}/privkey.pem" _update_env_val "SSL_CERT" "$cert" _update_env_val "SSL_KEY" "$key" python3 -c " import json with open('$PANEL_CONFIG') as f: d=json.load(f) d.update({'ssl_type':'letsencrypt','ssl_domain':'$domain','ssl_cert':'$cert','ssl_key':'$key'}) with open('$PANEL_CONFIG','w') as f: json.dump(d,f,indent=2) " _rebuild_start_sh systemctl restart mita-panel 2>/dev/null || true ok "Сертификат Let's Encrypt выпущен и применён" else error "Не удалось выпустить сертификат" fi pause ;; 0) return ;; esac } _rebuild_start_sh() { local bind_host bind_host=$(panel_value "access_mode") [[ "$bind_host" == "ssh" ]] && bind_host="127.0.0.1" || bind_host="0.0.0.0" _update_panel_bind "$bind_host" "$(env_value PANEL_PORT)" } # ════════════════════════════════════════════════════════════════════════════ # 4. ПОЛЬЗОВАТЕЛИ # ════════════════════════════════════════════════════════════════════════════ menu_users() { section "Управление пользователями mita" # Показать текущих пользователей echo -e "${CYAN}Текущие пользователи:${NC}" python3 -c " import json with open('$MITA_CONFIG') as f: d=json.load(f) users=d.get('users',[]) if not users: print(' (нет пользователей)') for u in users: print(f' - {u[\"name\"]}') " echo "" echo -e " ${BOLD}1.${NC} Создать одного пользователя (своё имя)" echo -e " ${BOLD}2.${NC} Создать несколько с рандомными именами" echo -e " ${BOLD}3.${NC} Удалить пользователя" echo -e " ${BOLD}4.${NC} Показать конфиг существующего пользователя" echo -e " ${BOLD}0.${NC} Назад" echo "" read -r -p " Выбор: " choice case "$choice" in 1) read -r -p "Имя пользователя: " uname [[ -z "$uname" ]] && { warn "Имя не указано"; pause; return; } _create_user "$uname" pause ;; 2) read -r -p "Количество пользователей: " cnt [[ ! "$cnt" =~ ^[0-9]+$ ]] && { warn "Неверное число"; pause; return; } for i in $(seq 1 "$cnt"); do local adj noun rnd uname adj=$(shuf -n1 -e swift brave quiet cool sharp calm bright dark wild free 2>/dev/null || echo "user") noun=$(shuf -n1 -e fox hawk river storm ember peak orbit tide frost spark 2>/dev/null || echo "node") rnd=$((RANDOM % 9000 + 1000)) uname="${adj}_${noun}_${rnd}" _create_user "$uname" done pause ;; 3) read -r -p "Имя пользователя для удаления: " uname python3 << PYEOF import json, subprocess with open('$MITA_CONFIG') as f: d=json.load(f) before=len(d.get('users',[])) d['users']=[u for u in d.get('users',[]) if u['name']!='$uname'] after=len(d['users']) with open('$MITA_CONFIG','w') as f: json.dump(d,f,indent=2) if before>after: subprocess.run(['mita','apply','config','$MITA_CONFIG'],capture_output=True) print(f"Пользователь '$uname' удалён") else: print(f"Пользователь '$uname' не найден") PYEOF pause ;; 4) read -r -p "Имя пользователя: " uname _show_user_config "$uname" pause ;; 0) return ;; esac } _show_user_config() { local uname="$1" python3 << PYEOF import json with open('$MITA_CONFIG') as f: d=json.load(f) user = next((u for u in d.get('users',[]) if u['name']=='$uname'), None) if not user: print("Пользователь '$uname' не найден") else: import subprocess server_ip=subprocess.run(['curl','-s','--max-time','5','ifconfig.me'],capture_output=True,text=True).stdout.strip() cfg=d.get('portBindings',[{}])[0] port=cfg.get('portRange',str(cfg.get('port','2100-2110'))) first_port = port.split('-')[0] if '-' in port else port print(f"\n Параметры для ручного ввода в Karing/sing-box клиентах:") print(f" server: {server_ip}") print(f" server port: {first_port}") print(f" username: {user['name']}") print(f" password: {user['password']}") print(f" transport: TCP") print(f" multiplexing: multiplexing_low") PYEOF echo "" read -r -p "Показать подробную инструкцию по добавлению в Karing? [y/N]: " show_karing [[ "${show_karing,,}" == "y" ]] && print_karing_instructions } _create_user() { local uname="$1" local pass pass=$(openssl rand -base64 48 | tr -d '/+=' | head -c 48) python3 << PYEOF import json, subprocess with open('$MITA_CONFIG') as f: d=json.load(f) if any(u['name']=='$uname' for u in d.get('users',[])): print(f"Пользователь '$uname' уже существует") else: d.setdefault('users',[]).append({'name':'$uname','password':'$pass'}) with open('$MITA_CONFIG','w') as f: json.dump(d,f,indent=2) r=subprocess.run(['mita','apply','config','$MITA_CONFIG'],capture_output=True,text=True) print(f"✓ Создан: $uname") print(f" Пароль: $pass") # Показать клиентский конфиг server_ip=subprocess.run(['curl','-s','--max-time','5','ifconfig.me'],capture_output=True,text=True).stdout.strip() cfg=d.get('portBindings',[{}])[0] port=cfg.get('portRange',str(cfg.get('port','2100-2110'))) proto=cfg.get('protocol','TCP') client={ 'profiles':[{'profileName':'default','user':{'name':'$uname','password':'$pass'}, 'servers':[{'ipAddress':server_ip,'portBindings':[{'portRange':port,'protocol':proto}]}]}], 'activeProfile':'default','rpcPort':8964,'socks5Port':1080 } print(f"\n Клиентский конфиг (mieru CLI):") print(json.dumps(client,indent=2,ensure_ascii=False)) first_port = port.split('-')[0] if '-' in port else port print(f"\n Параметры для ручного ввода в Karing/sing-box клиентах:") print(f" server: {server_ip}") print(f" server port: {first_port}") print(f" username: $uname") print(f" password: $pass") print(f" transport: TCP") print(f" multiplexing: multiplexing_low") PYEOF echo "" read -r -p "Показать подробную инструкцию по добавлению в Karing? [y/N]: " show_karing [[ "${show_karing,,}" == "y" ]] && print_karing_instructions } print_karing_instructions() { echo "" echo -e "${CYAN}┌─────────────────────────────────────────────────────────────────┐${NC}" echo -e "${CYAN}│ Ручное добавление сервера в Karing │${NC}" echo -e "${CYAN}└─────────────────────────────────────────────────────────────────┘${NC}" cat << 'KARINGEOF' 1. Откройте Karing → добавить профиль 2. Выберите тип "Custom" и впишите любое имя 3. Перейдите в раздел "My profiles" 4. Нажмите "+" в поле только что созданного профиля 5. В выпадающем списке протоколов выберите "Mieru" 6. В поле "tag" впишите любое название (например: proxy) 7. Заполните "server" и "server port" — значения выше (IP и порт) 8. Заполните "username" и "password" — значения выше 9. В поле "multiplexing" выберите "multiplexing_low" 10. В поле "transport" впишите ЗАГЛАВНЫМИ БУКВАМИ: TCP Затем нажмите ✓ в правом верхнем углу. Убедитесь что тумблер профиля в "My profiles" зелёный, затем нажмите кнопку подключения в главном меню Karing. 11. Diversion Rules (разделение трафика) — направить конкретные сайты/приложения мимо VPN или через него: a) Нажмите карандаш (✏) в правом верхнем углу "Diversion Rules" b) Нажмите "⋮" там же → "+ Add" c) Введите название правила, прокрутите список вниз, выберите созданное правило d) Добавьте нужные адреса/приложения, нажмите ✓ вверху справа e) Вернитесь в "Diversion Rules", найдите правило внизу списка f) Нажмите на него → "Direct" (мимо VPN) или раскройте имя профиля → выберите его (через VPN) KARINGEOF pause } # ════════════════════════════════════════════════════════════════════════════ # 5. ПОКАЗАТЬ ДАННЫЕ АДМИНА # ════════════════════════════════════════════════════════════════════════════ menu_show_admin() { section "Данные администратора панели" local user pass secret panel_port access_mode user=$(panel_value "admin_user") pass=$(panel_value "admin_pass") secret=$(panel_value "secret_path") panel_port=$(env_value "PANEL_PORT"); panel_port=${panel_port:-8080} access_mode=$(panel_value "access_mode") local server_ip server_ip=$(curl -s --max-time 5 ifconfig.me 2>/dev/null || hostname -I | awk '{print $1}') echo "" echo -e " Логин: ${BOLD}${YELLOW}${user}${NC}" echo -e " Пароль: ${BOLD}${YELLOW}${pass}${NC}" echo -e " Секретный путь: ${BOLD}/${secret}/${NC}" echo "" if [[ "$access_mode" == "ssh" ]]; then local ssh_port ssh_port=$(panel_value "ssh_port"); ssh_port=${ssh_port:-22} print_ssh_instruction "$panel_port" "$secret" "$ssh_port" else local proto="http" [[ -n "$(panel_value ssl_cert)" ]] && proto="https" local domain domain=$(panel_value "ssl_domain") if [[ -n "$domain" ]]; then echo -e " Адрес: ${BOLD}${GREEN}${proto}://${domain}:${panel_port}/${secret}/${NC}" else echo -e " Адрес: ${BOLD}${GREEN}${proto}://${server_ip}:${panel_port}/${secret}/${NC}" fi fi pause } # ════════════════════════════════════════════════════════════════════════════ # 6. ИЗМЕНИТЬ ДАННЫЕ АДМИНА # ════════════════════════════════════════════════════════════════════════════ menu_change_admin() { section "Изменение данных администратора" echo -e " ${BOLD}1.${NC} Сгенерировать новый пароль автоматически" echo -e " ${BOLD}2.${NC} Задать свой пароль" echo -e " ${BOLD}0.${NC} Назад" echo "" read -r -p " Выбор: " choice local new_pass case "$choice" in 1) new_pass=$(gen_pass) ;; 2) read -r -s -p "Новый пароль (мин. 12 символов): " new_pass; echo "" [[ ${#new_pass} -lt 12 ]] && { warn "Пароль слишком короткий"; pause; return; } ;; 0) return ;; *) warn "Неверный выбор"; pause; return ;; esac python3 -c " import json with open('$PANEL_CONFIG') as f: d=json.load(f) d['admin_pass']='$new_pass' with open('$PANEL_CONFIG','w') as f: json.dump(d,f,indent=2) " systemctl restart mita-panel 2>/dev/null || true ok "Пароль изменён" echo "" echo -e " Новый пароль: ${BOLD}${YELLOW}${new_pass}${NC}" echo -e " Логин: ${BOLD}$(panel_value admin_user)${NC}" pause } # ════════════════════════════════════════════════════════════════════════════ # 7. БЕЗОПАСНОСТЬ VPS # ════════════════════════════════════════════════════════════════════════════ menu_security() { section "Рекомендации по безопасности VPS" echo "" cat << 'SECEOF' ┌─────────────────────────────────────────────────────────────────┐ │ КРИТИЧЕСКИ ВАЖНО │ └─────────────────────────────────────────────────────────────────┘ 1. ВХОД ПО SSH-КЛЮЧУ (отключить пароли) # Сгенерируйте ключ на своём компьютере: ssh-keygen -t ed25519 -C "vps-key" # Скопируйте на сервер: ssh-copy-id -p 22 root@YOUR_IP # Отключите вход по паролю (/etc/ssh/sshd_config): PasswordAuthentication no PubkeyAuthentication yes systemctl restart sshd 2. СМЕНИТЕ ПОРТ SSH (заменить 22 на нестандартный) # В /etc/ssh/sshd_config: Port 2222 # любой свободный порт systemctl restart sshd # Не забудьте открыть новый порт в firewall! 3. FAIL2BAN (блокировка перебора паролей) apt-get install -y fail2ban systemctl enable fail2ban --now ┌─────────────────────────────────────────────────────────────────┐ │ ВАЖНО │ └─────────────────────────────────────────────────────────────────┘ 4. АВТООБНОВЛЕНИЕ БЕЗОПАСНОСТИ apt-get install -y unattended-upgrades dpkg-reconfigure -plow unattended-upgrades 5. МИНИМУМ ОТКРЫТЫХ ПОРТОВ Используйте пункт 2 этого меню → "Рекомендуемые правила". Открытыми должны быть только: SSH, порты mita, порт панели (если не SSH-туннель). 6. ПАНЕЛЬ ЧЕРЕЗ SSH-ТУННЕЛЬ Самый безопасный вариант — панель вообще не торчит наружу. Используйте пункт 1 главного меню для переключения. 7. СЕКРЕТНЫЙ ПУТЬ ПАНЕЛИ Никогда не делитесь ссылкой на панель. Без секретного пути сервер возвращает 404 на все запросы. ┌─────────────────────────────────────────────────────────────────┐ │ ДОПОЛНИТЕЛЬНО │ └─────────────────────────────────────────────────────────────────┘ 8. МОНИТОРИНГ ВХОДОВ last # последние входы journalctl -u ssh --since "1 hour ago" grep "Failed" /var/log/auth.log | tail -20 9. ПРОВЕРКА ОТКРЫТЫХ ПОРТОВ ss -tlnp # что слушает на сервере nmap -p- YOUR_IP # что видно снаружи (запускать со своего ПК) 10. РЕГУЛЯРНЫЕ ОБНОВЛЕНИЯ apt-get update && apt-get upgrade -y SECEOF pause } # ════════════════════════════════════════════════════════════════════════════ # 8. FAIL2BAN # ════════════════════════════════════════════════════════════════════════════ menu_fail2ban() { section "Настройка fail2ban для панели" local installed=false active=false command -v fail2ban-client &>/dev/null && installed=true $installed && systemctl is-active --quiet fail2ban && active=true echo -e " fail2ban установлен: $($installed && echo "${GREEN}да${NC}" || echo "${RED}нет${NC}")" $installed && echo -e " fail2ban активен: $($active && echo "${GREEN}да${NC}" || echo "${RED}нет${NC}")" echo "" local pc_maxretry pc_bantime pc_maxretry=$(python3 -c "import json; d=json.load(open('$PANEL_CONFIG')); print(d.get('login_max_attempts',5))" 2>/dev/null || echo 5) pc_bantime=$(python3 -c "import json; d=json.load(open('$PANEL_CONFIG')); print(d.get('login_ban_seconds',3600))" 2>/dev/null || echo 3600) echo -e " Текущие лимиты: ${YELLOW}${pc_maxretry} попыток${NC} / ${YELLOW}${pc_bantime} сек${NC}" echo "" echo -e " ${BOLD}1.${NC} Строгий — 3 попытки / 30 минут" echo -e " ${BOLD}2.${NC} Стандарт — 5 попыток / 1 час" echo -e " ${BOLD}3.${NC} Мягкий — 10 попыток / 15 минут" echo -e " ${BOLD}4.${NC} Жёсткий — 3 попытки / 24 часа" echo -e " ${BOLD}5.${NC} Ручная настройка" echo -e " ${BOLD}6.${NC} Установить/переустановить fail2ban" echo -e " ${BOLD}0.${NC} Назад" echo "" read -r -p " Выбор: " choice local max_retry ban_time case "$choice" in 1) max_retry=3; ban_time=1800 ;; 2) max_retry=5; ban_time=3600 ;; 3) max_retry=10; ban_time=900 ;; 4) max_retry=3; ban_time=86400 ;; 5) read -r -p " Попыток до блокировки [5]: " max_retry max_retry=${max_retry:-5} echo " Время блокировки:" echo " 1) 5 минут 2) 15 минут 3) 30 минут" echo " 4) 1 час 5) 24 часа 6) 7 дней 7) Своё (сек)" read -r -p " Выбор [4]: " bt_choice case "${bt_choice:-4}" in 1) ban_time=300 ;; 2) ban_time=900 ;; 3) ban_time=1800 ;; 4) ban_time=3600 ;; 5) ban_time=86400 ;; 6) ban_time=604800;; 7) read -r -p " Секунды: " ban_time ;; *) ban_time=3600 ;; esac ;; 6) info "Установка fail2ban..." apt-get install -y -qq fail2ban systemctl enable fail2ban --now ok "fail2ban установлен и запущен" pause; return ;; 0) return ;; *) warn "Неверный выбор"; pause; return ;; esac # Применить _apply_fail2ban "$max_retry" "$ban_time" pause } _apply_fail2ban() { local max_retry="$1" ban_time="$2" # Обновить panel.json python3 -c " import json with open('$PANEL_CONFIG') as f: d=json.load(f) d['login_max_attempts']=$max_retry d['login_ban_seconds']=$ban_time with open('$PANEL_CONFIG','w') as f: json.dump(d,f,indent=2) " # Записать fail2ban конфиг if command -v fail2ban-client &>/dev/null; then mkdir -p /etc/fail2ban/filter.d /etc/fail2ban/jail.d cat > /etc/fail2ban/filter.d/mita-panel.conf << EOF [Definition] failregex = ^.*"POST /[^"]+/login[^"]*" 4(01|29).*$ ignoreregex = EOF cat > /etc/fail2ban/jail.d/mita-panel.conf << EOF [mita-panel] enabled = true filter = mita-panel backend = auto logpath = /var/log/mita-panel-access.log maxretry = $max_retry bantime = $ban_time findtime = $ban_time action = iptables-multiport[name=mita-panel, port="http,https,8080,8443", protocol=tcp] EOF systemctl restart fail2ban 2>/dev/null || true ok "fail2ban настроен: $max_retry попыток / ${ban_time}с" else warn "fail2ban не установлен — лимиты сохранены только в panel.json" fi # Перезапустить панель чтобы подхватила новые лимиты systemctl restart mita-panel 2>/dev/null || true ok "Настройки применены. Попыток: $max_retry, бан: ${ban_time}с" } # ── Точка входа ─────────────────────────────────────────────────────────── main_menu