Files
MSWP/mita-ctl.sh
T

835 lines
40 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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