version 0.3 Добавлена инструкция для Karing, добавлена установка необходимых зависимостей

This commit is contained in:
2026-06-19 14:30:41 +03:00
parent a0f6ad2895
commit 23cd9a2c92
9 changed files with 3871 additions and 0 deletions
Executable
+834
View File
@@ -0,0 +1,834 @@
#!/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