6b52501b8d
- bot.py: лимит 100 кнопок — имена пользователей вместо 2 кнопок на каждого, user_ меню - install-panel.sh: --retries 1, --timeout 5, timeout 30 на pip, зеркало aliyun - install-bot.sh: те же таймауты и зеркала - README: обновлён
399 lines
17 KiB
Bash
Executable File
399 lines
17 KiB
Bash
Executable File
#!/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
|