version0.4 Общие исправления

This commit is contained in:
2026-06-19 15:23:17 +03:00
parent b43691b03a
commit 621ce6723a
5 changed files with 422 additions and 79 deletions
Regular → Executable
+283 -60
View File
@@ -75,6 +75,7 @@ main_menu() {
echo -e " ${BOLD}7.${NC} Настройка fail2ban"
echo -e " ${BOLD}8.${NC} Рекомендации по безопасности VPS"
echo -e " ${RED}${BOLD}9.${NC} ${RED}Полное удаление mita, панели и WARP${NC}"
echo -e " ${BOLD}10.${NC} Показать текущую конфигурацию"
echo -e " ${BOLD}0.${NC} Выход"
echo ""
read -r -p " Выберите пункт: " choice
@@ -88,6 +89,7 @@ main_menu() {
7) menu_fail2ban ;;
8) menu_security ;;
9) menu_uninstall ;;
10) menu_show_config ;;
0) exit 0 ;;
*) warn "Неверный выбор" ;;
esac
@@ -411,6 +413,76 @@ _rebuild_start_sh() {
# ════════════════════════════════════════════════════════════════════════════
# 4. ПОЛЬЗОВАТЕЛИ
# ════════════════════════════════════════════════════════════════════════════
# ── Общая функция: безопасное применение конфига mita ───────────────────────
# Запускает mita если он не активен (нужно для apply config),
# применяет конфиг, возвращает демон в исходное состояние.
# Возвращает 0 при успехе, 1 при ошибке. Текст ошибки — в stdout.
_apply_mita_config() {
local MITA_WAS_STOPPED=false
local MITA_BG_PID=""
if ! systemctl is-active --quiet mita 2>/dev/null; then
systemctl reset-failed mita 2>/dev/null || true
rm -f /etc/mita/server.conf.pb 2>/dev/null
/usr/bin/mita run &
MITA_BG_PID=$!
sleep 2
MITA_WAS_STOPPED=true
fi
local apply_out apply_rc
apply_out=$(mita apply config "$MITA_CONFIG" 2>&1)
apply_rc=$?
if $MITA_WAS_STOPPED; then
kill "$MITA_BG_PID" 2>/dev/null || true
wait "$MITA_BG_PID" 2>/dev/null || true
sleep 1
fi
if [[ $apply_rc -ne 0 ]]; then
echo "$apply_out"
return 1
fi
[[ -f /etc/mita/server.conf.pb ]] && chown mita:mita /etc/mita/server.conf.pb 2>/dev/null
systemctl reset-failed mita 2>/dev/null || true
systemctl restart mita
sleep 1
return 0
}
_delete_user() {
local uname="$1"
local result
result=$(python3 << PYEOF
import json
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)
print("DELETED" if before>after else "NOTFOUND")
PYEOF
)
if [[ "$result" == "NOTFOUND" ]]; then
warn "Пользователь '$uname' не найден"
return 1
fi
if _apply_mita_config; then
if systemctl is-active --quiet mita; then
ok "Пользователь '$uname' удалён, mita работает"
else
warn "Пользователь удалён, но mita не запустился — проверьте: journalctl -u mita -n 20"
fi
else
error "Ошибка применения конфига после удаления пользователя"
fi
}
menu_users() {
section "Управление пользователями mita"
@@ -452,19 +524,7 @@ for u in users: print(f' - {u[\"name\"]}')
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
_delete_user "$uname"
pause ;;
4)
read -r -p "Имя пользователя: " uname
@@ -506,38 +566,80 @@ _create_user() {
local pass
pass=$(openssl rand -base64 48 | tr -d '/+=' | head -c 48)
# Записываем пользователя в JSON и проверяем результат явно
local py_result
py_result=$(python3 << PYEOF
import json, sys
try:
with open('$MITA_CONFIG') as f: d=json.load(f)
except Exception as e:
print(f"ERROR_READ:{e}")
sys.exit(1)
if any(u['name']=='$uname' for u in d.get('users',[])):
print("EXISTS")
sys.exit(0)
d.setdefault('users',[]).append({'name':'$uname','password':'$pass'})
try:
with open('$MITA_CONFIG','w') as f: json.dump(d,f,indent=2)
except Exception as e:
print(f"ERROR_WRITE:{e}")
sys.exit(1)
print("WRITTEN")
PYEOF
)
if [[ "$py_result" == "EXISTS" ]]; then
warn "Пользователь '$uname' уже существует"
return 1
fi
if [[ "$py_result" != "WRITTEN" ]]; then
error "Не удалось записать конфиг: $py_result"
return 1
fi
local apply_err
apply_err=$(_apply_mita_config)
if [[ $? -ne 0 ]]; then
error "mita apply config завершился с ошибкой:"
echo "$apply_err"
return 1
fi
if systemctl is-active --quiet mita; then
ok "Создан: $uname"
else
warn "Пользователь создан, но mita не запустился — проверьте: journalctl -u mita -n 20"
fi
echo " Пароль: $pass"
# Показать клиентский конфиг
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))
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")
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 ""
@@ -583,8 +685,8 @@ KARINGEOF
# ════════════════════════════════════════════════════════════════════════════
# 5. ПОКАЗАТЬ ДАННЫЕ АДМИНА
# ════════════════════════════════════════════════════════════════════════════
menu_show_admin() {
section "Данные администратора панели"
# ── Общая функция: вывод логина/пароля/URL панели с учётом access_mode ──────
_print_panel_access_info() {
local user pass secret panel_port access_mode
user=$(panel_value "admin_user")
pass=$(panel_value "admin_pass")
@@ -600,21 +702,134 @@ menu_show_admin() {
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
case "$access_mode" in
ssh)
local ssh_port
ssh_port=$(panel_value "ssh_port"); ssh_port=${ssh_port:-22}
echo -e " Способ доступа: ${YELLOW}SSH-туннель${NC} (панель не торчит наружу)"
print_ssh_instruction "$panel_port" "$secret" "$ssh_port"
;;
domain)
local proto="http" domain
[[ -n "$(panel_value ssl_cert)" ]] && proto="https"
domain=$(panel_value "ssl_domain")
echo -e " Способ доступа: ${YELLOW}Доменное имя${NC}"
if [[ -n "$domain" ]]; then
echo -e " Адрес: ${BOLD}${GREEN}${proto}://${domain}:${panel_port}/${secret}/${NC}"
else
warn "Домен не настроен в panel.json, показываю по IP"
echo -e " Адрес: ${BOLD}${GREEN}${proto}://${server_ip}:${panel_port}/${secret}/${NC}"
fi
;;
*)
local proto="http"
[[ -n "$(panel_value ssl_cert)" ]] && proto="https"
echo -e " Способ доступа: ${YELLOW}По IP-адресу${NC}"
echo -e " Адрес: ${BOLD}${GREEN}${proto}://${server_ip}:${panel_port}/${secret}/${NC}"
fi
;;
esac
}
menu_show_admin() {
section "Данные администратора панели"
_print_panel_access_info
pause
}
menu_show_config() {
section "Текущая конфигурация сервера"
# ── Доступ к панели ───────────────────────────────────────────
echo -e "${CYAN}── Веб-панель ──────────────────────────────────────────${NC}"
if [[ -f "$PANEL_CONFIG" ]]; then
_print_panel_access_info
else
warn "Панель не установлена (panel.json не найден)"
fi
echo ""
echo -e "${CYAN}── Статус сервисов ─────────────────────────────────────${NC}"
local mita_active panel_active warp_active f2b_active ufw_active
systemctl is-active --quiet mita 2>/dev/null && mita_active=true || mita_active=false
systemctl is-active --quiet mita-panel 2>/dev/null && panel_active=true || panel_active=false
systemctl is-active --quiet fail2ban 2>/dev/null && f2b_active=true || f2b_active=false
command -v ufw &>/dev/null && ufw status 2>/dev/null | grep -q "Status: active" && ufw_active=true || ufw_active=false
warp_active=false
if command -v docker &>/dev/null && docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^cloudflare-warp$"; then
warp_active=true
fi
_status_line() {
local label="$1" active="$2"
if [[ "$active" == "true" ]]; then
echo -e " ${label}: ${GREEN}● работает${NC}"
else
echo -e " ${label}: ${RED}● остановлен/не установлен${NC}"
fi
}
_status_line "mita сервер " "$mita_active"
_status_line "Веб-панель " "$panel_active"
_status_line "Cloudflare WARP " "$warp_active"
_status_line "fail2ban " "$f2b_active"
_status_line "UFW " "$ufw_active"
# ── mita: пользователи и порты ─────────────────────────────────
echo ""
echo -e "${CYAN}── mita сервер ─────────────────────────────────────────${NC}"
if [[ -f "$MITA_CONFIG" ]]; then
python3 -c "
import json
with open('$MITA_CONFIG') as f: d=json.load(f)
bindings = d.get('portBindings', [{}])
b = bindings[0] if bindings else {}
port = b.get('portRange', str(b.get('port','?')))
proto = b.get('protocol','?')
users = d.get('users', [])
print(f' Порты: {port} ({proto})')
print(f' Пользователей: {len(users)}')
for u in users:
print(f' - {u[\"name\"]}')
egress = d.get('egress', {})
if egress.get('rules'):
print(f' WARP egress: настроен ({len(egress[\"rules\"])} правил)')
else:
print(f' WARP egress: не настроен')
"
else
warn "Конфиг mita не найден ($MITA_CONFIG)"
fi
# ── fail2ban лимиты ───────────────────────────────────────────
echo ""
echo -e "${CYAN}── fail2ban (лимит входа в панель) ─────────────────────${NC}"
local max_retry ban_time
max_retry=$(panel_value "login_max_attempts"); max_retry=${max_retry:-"не настроено"}
ban_time=$(panel_value "login_ban_seconds"); ban_time=${ban_time:-"не настроено"}
echo -e " Попыток до блокировки: ${YELLOW}${max_retry}${NC}"
echo -e " Время блокировки: ${YELLOW}${ban_time}${NC} сек"
# ── SSL ───────────────────────────────────────────────────────
echo ""
echo -e "${CYAN}── SSL-сертификат панели ────────────────────────────────${NC}"
local ssl_type
ssl_type=$(panel_value "ssl_type")
if [[ -z "$ssl_type" || "$ssl_type" == "none" ]]; then
echo -e " Тип: ${RED}не настроен${NC} (панель работает по HTTP)"
else
echo -e " Тип: ${YELLOW}${ssl_type}${NC}"
[[ -n "$(panel_value ssl_domain)" ]] && echo -e " Домен: ${YELLOW}$(panel_value ssl_domain)${NC}"
echo -e " Файл: $(panel_value ssl_cert)"
fi
# ── Версии ────────────────────────────────────────────────────
echo ""
echo -e "${CYAN}── Версии ──────────────────────────────────────────────${NC}"
command -v mita &>/dev/null && echo -e " mita: $(mita version 2>/dev/null || echo '?')"
command -v docker &>/dev/null && echo -e " docker: $(docker --version 2>/dev/null | cut -d, -f1)"
pause
}
@@ -885,13 +1100,21 @@ menu_uninstall() {
info "Остановка mita..."
systemctl stop mita 2>/dev/null || true
systemctl disable mita 2>/dev/null || true
# Сначала пробуем штатное удаление через dpkg/apt — пока unit-файл
# ещё на месте, иначе pre/post-removal скрипты пакета падают
DEB_PKG=$(dpkg -l 2>/dev/null | grep -i "^ii.*mita" | awk '{print $2}' | head -1)
if [[ -n "$DEB_PKG" ]]; then
apt-get remove -y -qq "$DEB_PKG" 2>/dev/null \
|| dpkg --remove --force-remove-reinstreq "$DEB_PKG" 2>/dev/null \
|| true
fi
# Затем подчищаем то, что могло остаться вручную
rm -f /usr/lib/systemd/system/mita.service
rm -f /etc/systemd/system/mita.service
command -v mita &>/dev/null && {
DEB_PKG=$(dpkg -l 2>/dev/null | grep -i mita | awk '{print $2}' | head -1)
[[ -n "$DEB_PKG" ]] && apt-get remove -y -qq "$DEB_PKG" 2>/dev/null
}
rm -f /usr/bin/mita
dpkg --configure -a 2>/dev/null || true
ok "mita сервер удалён"
# ── 3. Удалить WARP (Docker) ───────────────────────────────────