#!/bin/bash # ============================================================================= # Установка mita Web Panel # ============================================================================= 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}[ERROR]${NC} $*"; exit 1; } section() { echo -e "\n${CYAN}══════ $* ══════${NC}"; } [[ $EUID -ne 0 ]] && error "Запустите от root: sudo bash install-panel.sh" # ── проверка зависимостей ────────────────────────────────────────── section "Проверка окружения" command -v mita &>/dev/null || error "mita не установлен — сначала запустите install.sh" # ── выбор порта ──────────────────────────────────────────────────── section "Выбор порта панели" find_free_port() { local start=$1 for port in $(seq $start 65535); do if ! ss -tlnp 2>/dev/null | grep -q ":${port} " && \ ! ss -tlnp 2>/dev/null | grep -q ":${port}$"; then echo $port; return fi done echo 8080 } echo -e "Введите порт для панели (Enter = автовыбор из свободных начиная с 8080):" read -r -p "Порт: " USER_PORT if [[ -z "$USER_PORT" ]]; then PANEL_PORT=$(find_free_port 8080) info "Автовыбран свободный порт: $PANEL_PORT" elif [[ "$USER_PORT" =~ ^[0-9]+$ ]] && [[ $USER_PORT -ge 1024 && $USER_PORT -le 65535 ]]; then if ss -tlnp 2>/dev/null | grep -q ":${USER_PORT}[[:space:]]"; then warn "Порт $USER_PORT занят, выбираем следующий свободный..." PANEL_PORT=$(find_free_port $((USER_PORT+1))) info "Выбран порт: $PANEL_PORT" else PANEL_PORT=$USER_PORT ok "Порт: $PANEL_PORT" fi else error "Неверный порт: $USER_PORT" fi # ── установка Python, Flask и системных утилит ────────────────────── section "Установка зависимостей" apt-get update -qq apt-get install -y -qq \ python3 python3-pip python3-venv \ openssl curl wget jq \ fail2ban \ certbot \ iptables ufw \ ca-certificates gnupg lsb-release \ net-tools \ 2>/dev/null ok "Системные пакеты установлены (Python, fail2ban, certbot, iptables, ufw)" # fail2ban не запускаем автоматически — пользователь настроит лимиты # через раздел "Безопасность" в панели или mita-ctl → пункт 7 systemctl enable fail2ban 2>/dev/null || true ok "fail2ban установлен (настройка — через панель или mita-ctl)" PANEL_DIR="/opt/mita-panel" mkdir -p "$PANEL_DIR" python3 -m venv "$PANEL_DIR/venv" info "Установка Flask и зависимостей (может занять до минуты)..." _pip_install() { PIP_RETRIES=0 timeout 30 "$PANEL_DIR/venv/bin/pip" install \ --no-input --quiet --disable-pip-version-check \ --timeout 5 --retries 1 \ "$@" } MIRRORS=( "https://pypi.org/simple/" "https://mirrors.aliyun.com/pypi/simple/" ) pip_ok=false for mirror in "${MIRRORS[@]}"; do info "Пробую зеркало: $mirror" if _pip_install --index-url "$mirror" flask gunicorn pyyaml psutil; then pip_ok=true break fi warn "Зеркало $mirror недоступно, пробую дальше..." done if ! $pip_ok; then warn "pip не смог установить зависимости. Пробую через apt..." apt-get install -y -qq python3-flask python3-gunicorn python3-yaml python3-psutil 2>/dev/null || { error "Не удалось установить Flask ни через pip, ни через apt" exit 1 } # Создаём venv с системными пакетами чтобы gunicorn был доступен rm -rf "$PANEL_DIR/venv" python3 -m venv --system-site-packages "$PANEL_DIR/venv" ok "Flask установлен через системные пакеты (venv с --system-site-packages)" else ok "Python venv, Flask и зависимости установлены" fi # ── копирование файлов ───────────────────────────────────────────── section "Копирование файлов панели" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cp "$SCRIPT_DIR/app.py" "$PANEL_DIR/" cp -r "$SCRIPT_DIR/templates" "$PANEL_DIR/" [[ -d "$SCRIPT_DIR/static" ]] && cp -r "$SCRIPT_DIR/static" "$PANEL_DIR/" # Копируем установщик бота рядом — чтобы работала установка через веб-панель и mita-ctl [[ -f "$SCRIPT_DIR/install-bot.sh" ]] && cp "$SCRIPT_DIR/install-bot.sh" "$PANEL_DIR/" [[ -f "$SCRIPT_DIR/bot.py" ]] && cp "$SCRIPT_DIR/bot.py" "$PANEL_DIR/" ok "Файлы скопированы в $PANEL_DIR" # ── генерация секретов ───────────────────────────────────────────── section "Генерация учётных данных" gen_pass() { openssl rand -base64 32 | tr -d '/+=' | head -c 24 } gen_path() { openssl rand -hex 10 } ADMIN_USER="admin" ADMIN_PASS=$(gen_pass) SECRET_PATH=$(gen_path) FLASK_SECRET=$(openssl rand -hex 32) ok "Логин и секретный путь сгенерированы" # ── выбор способа подключения ────────────────────────────────────── section "Способ подключения к панели" echo -e " ${BOLD}1.${NC} По IP-адресу (открытый доступ)" echo -e " ${BOLD}2.${NC} SSH-туннель (панель только на 127.0.0.1, недоступна снаружи)" echo -e " ${BOLD}3.${NC} По доменному имени с Let's Encrypt" echo "" read -r -p "Выберите способ [1]: " ACCESS_MODE_CHOICE ACCESS_MODE_CHOICE=${ACCESS_MODE_CHOICE:-1} ACCESS_MODE="ip" PANEL_BIND="0.0.0.0" SSL_DOMAIN="" SSH_ACCESS_PORT=22 LE_DOMAIN="" case "$ACCESS_MODE_CHOICE" in 2) ACCESS_MODE="ssh" PANEL_BIND="127.0.0.1" read -r -p "SSH-порт вашего VPS [22]: " SSH_ACCESS_PORT SSH_ACCESS_PORT=${SSH_ACCESS_PORT:-22} info "Панель будет доступна только через SSH-туннель" ;; 3) ACCESS_MODE="domain" PANEL_BIND="0.0.0.0" read -r -p "Домен для панели (например panel.example.com): " LE_DOMAIN [[ -z "$LE_DOMAIN" ]] && { warn "Домен не указан, переключаемся на IP"; ACCESS_MODE="ip"; } ;; esac # ── конфиг панели ───────────────────────────────────────────────── PANEL_CONFIG="/etc/mita/panel.json" mkdir -p /etc/mita # Получить WARP_PORT из существующего env если есть WARP_PORT=40000 [[ -f /etc/mita/panel.env ]] && WARP_PORT=$(grep '^WARP_PORT=' /etc/mita/panel.env | cut -d= -f2 || echo 40000) cat > "$PANEL_CONFIG" << EOF { "admin_user": "$ADMIN_USER", "admin_pass": "$ADMIN_PASS", "secret_path": "$SECRET_PATH", "panel_port": $PANEL_PORT, "access_mode": "$ACCESS_MODE", "ssh_port": $SSH_ACCESS_PORT, "ssl_type": "none", "warp_users": [] } EOF ok "Конфиг панели: $PANEL_CONFIG" # ── env файл ────────────────────────────────────────────────────── cat > /etc/mita/panel.env << EOF PANEL_PORT=$PANEL_PORT SECRET_PATH=$SECRET_PATH FLASK_SECRET=$FLASK_SECRET PANEL_CONFIG=$PANEL_CONFIG MITA_CONFIG=/etc/mita/server_config.json WARP_PORT=$WARP_PORT SSL_CERT= SSL_KEY= EOF ok "Env файл: /etc/mita/panel.env" # ── враппер запуска (динамически подхватывает SSL из panel.env) ── section "Создание systemd сервиса" cat > /opt/mita-panel/start.sh << 'STARTEOF' #!/bin/bash # Читаем актуальный panel.env каждый раз при старте 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 0.0.0.0:${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 ok "Враппер запуска создан: /opt/mita-panel/start.sh" cat > /etc/systemd/system/mita-panel.service << EOF [Unit] Description=mita Web Panel After=network.target mita.service Wants=mita.service [Service] Type=simple User=root WorkingDirectory=$PANEL_DIR ExecStart=/opt/mita-panel/start.sh Restart=always RestartSec=5 [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable mita-panel systemctl restart mita-panel sleep 2 if systemctl is-active --quiet mita-panel; then ok "mita-panel запущен" else warn "Не удалось запустить — проверьте: journalctl -u mita-panel -n 30" fi # ── firewall ────────────────────────────────────────────────────── section "Открытие порта в firewall" if command -v ufw &>/dev/null && ufw status | grep -q "Status: active"; then ufw allow "$PANEL_PORT/tcp" comment "mita-panel" 2>/dev/null || true ok "UFW: порт $PANEL_PORT открыт" elif command -v iptables &>/dev/null; then iptables -I INPUT -p tcp --dport "$PANEL_PORT" -j ACCEPT 2>/dev/null || true ok "iptables: порт $PANEL_PORT открыт" fi # ── авто-генерация самоподписного сертификата ───────────────────── section "SSL-сертификат" SSL_DIR="/etc/mita/ssl" mkdir -p "$SSL_DIR" SSL_CERT="$SSL_DIR/selfsigned.crt" SSL_KEY="$SSL_DIR/selfsigned.key" info "Создание самоподписного сертификата (10 лет)..." openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \ -keyout "$SSL_KEY" -out "$SSL_CERT" \ -subj "/CN=mita-panel/O=mita/C=XX" 2>/dev/null echo "SSL_CERT=$SSL_CERT" >> /etc/mita/panel.env echo "SSL_KEY=$SSL_KEY" >> /etc/mita/panel.env python3 -c " import json with open('$PANEL_CONFIG') as f: d=json.load(f) d.update({'ssl_type':'selfsigned','ssl_cert':'$SSL_CERT','ssl_key':'$SSL_KEY'}) with open('$PANEL_CONFIG','w') as f: json.dump(d,f,indent=2) " systemctl restart mita-panel 2>/dev/null || true ok "Самоподписной сертификат установлен, панель перезапущена с HTTPS" PROTO="https" # ── итог ────────────────────────────────────────────────────────── section "Установка завершена!" SERVER_IP=$(curl -s --max-time 5 ifconfig.me 2>/dev/null || hostname -I | awk '{print $1}') echo "" echo -e "${GREEN}╔══════════════════════════════════════════════════════════════╗" echo -e "║ ПАНЕЛЬ УПРАВЛЕНИЯ УСТАНОВЛЕНА ║" echo -e "╚══════════════════════════════════════════════════════════════╝${NC}" echo "" echo -e "${CYAN}Адрес панели:${NC}" echo -e " ${BOLD}${YELLOW}${PROTO}://${SERVER_IP}:${PANEL_PORT}/${SECRET_PATH}/${NC}" echo "" echo -e "${CYAN}Учётные данные:${NC}" echo -e " Логин: ${BOLD}${ADMIN_USER}${NC}" echo -e " Пароль: ${BOLD}${YELLOW}${ADMIN_PASS}${NC}" echo "" echo -e "${CYAN}Секретный путь:${NC}" echo -e " ${BOLD}/${SECRET_PATH}/${NC}" echo -e " ${RED}Без него панель недоступна (возвращает 404)${NC}" echo "" echo -e "${CYAN}Полная ссылка для браузера:${NC}" echo -e " ${BOLD}${GREEN}${PROTO}://${SERVER_IP}:${PANEL_PORT}/${SECRET_PATH}/${NC}" echo "" echo -e "${CYAN}SSL:${NC}" echo -e " Сертификат: самоподписной (10 лет), уже установлен" echo -e " Браузер покажет предупреждение — примите его" echo -e " Заменить на Let's Encrypt: раздел SSL в панели или mita-ctl → пункт 3" echo "" echo -e "${CYAN}Полезные команды:${NC}" echo -e " ${YELLOW}systemctl status mita-panel${NC} — статус" echo -e " ${YELLOW}journalctl -u mita-panel -f${NC} — логи" echo -e " ${YELLOW}systemctl restart mita-panel${NC} — перезапуск" echo "" echo -e "${RED}⚠ Сохраните ссылку и пароль — они больше не отобразятся!${NC}" echo "" # ── SSH туннель: финальные инструкции ───────────────────────────── if [[ "$ACCESS_MODE" == "ssh" ]]; then 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 ${PANEL_PORT}:127.0.0.1:${PANEL_PORT} -p ${SSH_ACCESS_PORT} root@${SERVER_IP} -N${NC}" echo "" echo -e " Затем откройте в браузере:" echo -e " ${BOLD}${GREEN}${PROTO}://127.0.0.1:${PANEL_PORT}/${SECRET_PATH}/${NC}" echo "" echo -e " Для фонового режима:" echo -e " ${YELLOW}ssh -fN -L ${PANEL_PORT}:127.0.0.1:${PANEL_PORT} -p ${SSH_ACCESS_PORT} root@${SERVER_IP}${NC}" echo "" fi # ── Let's Encrypt ───────────────────────────────────────────────── if [[ "$ACCESS_MODE" == "domain" && -n "$LE_DOMAIN" ]]; then info "Получение Let's Encrypt сертификата для $LE_DOMAIN..." command -v certbot &>/dev/null || apt-get install -y -qq certbot if certbot certonly --standalone --non-interactive --agree-tos \ --register-unsafely-without-email -d "$LE_DOMAIN" 2>/dev/null; then LE_CERT="/etc/letsencrypt/live/${LE_DOMAIN}/fullchain.pem" LE_KEY="/etc/letsencrypt/live/${LE_DOMAIN}/privkey.pem" echo "SSL_CERT=$LE_CERT" >> /etc/mita/panel.env echo "SSL_KEY=$LE_KEY" >> /etc/mita/panel.env python3 -c " import json with open('$PANEL_CONFIG') as f: d=json.load(f) d.update({'ssl_type':'letsencrypt','ssl_domain':'$LE_DOMAIN','ssl_cert':'$LE_CERT','ssl_key':'$LE_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 "Сертификат получен" PROTO="https" else warn "Не удалось получить сертификат Let's Encrypt. Убедитесь что $LE_DOMAIN указывает на этот сервер и порт 80 открыт." PROTO="http" fi fi # ── Установка mita-ctl как системной команды ────────────────────── section "Установка утилиты mita-ctl" SCRIPT_DIR_CTL="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" CTL_SRC="$SCRIPT_DIR_CTL/mita-ctl.sh" if [[ -f "$CTL_SRC" ]]; then cp "$CTL_SRC" /usr/local/bin/mita-ctl chmod +x /usr/local/bin/mita-ctl ok "mita-ctl установлен. Запуск: sudo mita-ctl" else warn "mita-ctl.sh не найден рядом — пропускаем" warn "Чтобы установить вручную: cp mita-ctl.sh /usr/local/bin/mita-ctl && chmod +x /usr/local/bin/mita-ctl" fi