version0.4 Общие исправления
This commit is contained in:
@@ -662,6 +662,63 @@ def api_warp_rules_set():
|
|||||||
|
|
||||||
return jsonify({"ok": True})
|
return jsonify({"ok": True})
|
||||||
|
|
||||||
|
def _get_geosite_data():
|
||||||
|
"""
|
||||||
|
Скачивает (с кэшированием на 7 дней) единый YAML файл со всеми
|
||||||
|
geosite-категориями и возвращает распарсенный dict.
|
||||||
|
Старый способ (отдельный .txt на каждую категорию на ветке release)
|
||||||
|
больше не поддерживается проектом v2fly — теперь только единый
|
||||||
|
dlc.dat_plain.yml в latest release.
|
||||||
|
"""
|
||||||
|
import urllib.request, os, time, yaml
|
||||||
|
|
||||||
|
cache_path = "/var/cache/mita-geosite.yml"
|
||||||
|
url = "https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat_plain.yml"
|
||||||
|
|
||||||
|
need_download = True
|
||||||
|
if os.path.exists(cache_path):
|
||||||
|
age_days = (time.time() - os.path.getmtime(cache_path)) / 86400
|
||||||
|
if age_days < 7:
|
||||||
|
need_download = False
|
||||||
|
|
||||||
|
if need_download:
|
||||||
|
try:
|
||||||
|
os.makedirs(os.path.dirname(cache_path), exist_ok=True)
|
||||||
|
req = urllib.request.Request(url, headers={"User-Agent": "mita-panel"})
|
||||||
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
|
data = resp.read()
|
||||||
|
with open(cache_path, "wb") as f:
|
||||||
|
f.write(data)
|
||||||
|
except Exception:
|
||||||
|
pass # используем старый кэш, если есть
|
||||||
|
|
||||||
|
if not os.path.exists(cache_path):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(cache_path) as f:
|
||||||
|
return yaml.safe_load(f)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _geosite_category_domains(category):
|
||||||
|
"""Возвращает список доменов для одной geosite-категории."""
|
||||||
|
data = _get_geosite_data()
|
||||||
|
if not data:
|
||||||
|
return []
|
||||||
|
domains = []
|
||||||
|
cat_lower = category.lower()
|
||||||
|
for entry in data.get("lists", []):
|
||||||
|
if entry.get("name", "").lower() == cat_lower:
|
||||||
|
for rule in entry.get("rules", []):
|
||||||
|
for prefix in ("domain:", "full:"):
|
||||||
|
if rule.startswith(prefix):
|
||||||
|
domains.append(rule[len(prefix):])
|
||||||
|
break
|
||||||
|
# regexp: и include: пропускаем — не прямые доменные правила
|
||||||
|
break
|
||||||
|
return domains
|
||||||
|
|
||||||
def _rebuild_egress(pc):
|
def _rebuild_egress(pc):
|
||||||
"""
|
"""
|
||||||
Собирает egress.rules из warp_rules всех пользователей и записывает в mita config.
|
Собирает egress.rules из warp_rules всех пользователей и записывает в mita config.
|
||||||
@@ -675,7 +732,6 @@ def _rebuild_egress(pc):
|
|||||||
all_domains = set()
|
all_domains = set()
|
||||||
all_ips = set()
|
all_ips = set()
|
||||||
|
|
||||||
GEOSITE_BASE = "https://raw.githubusercontent.com/v2fly/domain-list-community/release"
|
|
||||||
GEOIP_BASE = "https://raw.githubusercontent.com/herrbischoff/country-ip-blocks/master/ipv4"
|
GEOIP_BASE = "https://raw.githubusercontent.com/herrbischoff/country-ip-blocks/master/ipv4"
|
||||||
|
|
||||||
for uname, rules in pc.get("warp_rules", {}).items():
|
for uname, rules in pc.get("warp_rules", {}).items():
|
||||||
@@ -690,12 +746,7 @@ def _rebuild_egress(pc):
|
|||||||
try:
|
try:
|
||||||
if src.startswith("geosite:"):
|
if src.startswith("geosite:"):
|
||||||
cat = src[len("geosite:"):]
|
cat = src[len("geosite:"):]
|
||||||
url = f"{GEOSITE_BASE}/{cat}.txt"
|
all_domains.update(_geosite_category_domains(cat))
|
||||||
lines = urllib.request.urlopen(url, timeout=10).read().decode().splitlines()
|
|
||||||
for line in lines:
|
|
||||||
line = line.strip()
|
|
||||||
if line and not line.startswith("#") and "include:" not in line and "regexp:" not in line:
|
|
||||||
all_domains.add(line.lstrip("full:"))
|
|
||||||
elif src.startswith("geoip:"):
|
elif src.startswith("geoip:"):
|
||||||
country = src[len("geoip:"):]
|
country = src[len("geoip:"):]
|
||||||
url = f"{GEOIP_BASE}/{country}.cidr"
|
url = f"{GEOIP_BASE}/{country}.cidr"
|
||||||
|
|||||||
+1
-1
@@ -74,7 +74,7 @@ PANEL_DIR="/opt/mita-panel"
|
|||||||
mkdir -p "$PANEL_DIR"
|
mkdir -p "$PANEL_DIR"
|
||||||
|
|
||||||
python3 -m venv "$PANEL_DIR/venv"
|
python3 -m venv "$PANEL_DIR/venv"
|
||||||
"$PANEL_DIR/venv/bin/pip" install -q flask gunicorn 2>/dev/null
|
"$PANEL_DIR/venv/bin/pip" install -q flask gunicorn pyyaml 2>/dev/null
|
||||||
ok "Python venv и Flask установлены"
|
ok "Python venv и Flask установлены"
|
||||||
|
|
||||||
# ── копирование файлов ─────────────────────────────────────────────
|
# ── копирование файлов ─────────────────────────────────────────────
|
||||||
|
|||||||
+79
-10
@@ -88,8 +88,9 @@ apt-get install -y -qq \
|
|||||||
iptables ipset ufw \
|
iptables ipset ufw \
|
||||||
fail2ban openssl \
|
fail2ban openssl \
|
||||||
net-tools \
|
net-tools \
|
||||||
|
python3 python3-yaml \
|
||||||
2>/dev/null
|
2>/dev/null
|
||||||
ok "Базовые пакеты установлены (включая fail2ban, ufw, ipset)"
|
ok "Базовые пакеты установлены (включая fail2ban, ufw, ipset, python3-yaml)"
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# УСТАНОВКА MITA
|
# УСТАНОВКА MITA
|
||||||
@@ -264,7 +265,8 @@ WARP_PORT=$WARP_PORT
|
|||||||
MITA_PORT_RANGE="$MITA_PORT_RANGE"
|
MITA_PORT_RANGE="$MITA_PORT_RANGE"
|
||||||
MITA_PROTOCOL="$MITA_PROTOCOL"
|
MITA_PROTOCOL="$MITA_PROTOCOL"
|
||||||
MITA_LOG_LEVEL="$MITA_LOG_LEVEL"
|
MITA_LOG_LEVEL="$MITA_LOG_LEVEL"
|
||||||
GEOSITE_BASE="https://raw.githubusercontent.com/v2fly/domain-list-community/release"
|
GEOSITE_YML_URL="https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat_plain.yml"
|
||||||
|
GEOSITE_CACHE="/var/cache/mita-geosite.yml"
|
||||||
GEOIP_BASE="https://raw.githubusercontent.com/herrbischoff/country-ip-blocks/master/ipv4"
|
GEOIP_BASE="https://raw.githubusercontent.com/herrbischoff/country-ip-blocks/master/ipv4"
|
||||||
|
|
||||||
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] \$*" | tee -a /var/log/mita-update.log; }
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] \$*" | tee -a /var/log/mita-update.log; }
|
||||||
@@ -276,6 +278,31 @@ TMP_DOMAINS=\$(mktemp)
|
|||||||
# Пользовательские домены
|
# Пользовательские домены
|
||||||
grep -v '^\s*#' "\$LISTS_DIR/custom_domains.txt" | grep -v '^\s*$' >> "\$TMP_DOMAINS" || true
|
grep -v '^\s*#' "\$LISTS_DIR/custom_domains.txt" | grep -v '^\s*$' >> "\$TMP_DOMAINS" || true
|
||||||
|
|
||||||
|
# Проверяем, нужен ли geosite YAML (есть хотя бы одна строка geosite: в auto_domains.txt)
|
||||||
|
NEED_GEOSITE=false
|
||||||
|
grep -q '^geosite:' "\$LISTS_DIR/auto_domains.txt" 2>/dev/null && NEED_GEOSITE=true
|
||||||
|
|
||||||
|
if \$NEED_GEOSITE; then
|
||||||
|
mkdir -p "\$(dirname "\$GEOSITE_CACHE")"
|
||||||
|
CACHE_AGE_DAYS=999
|
||||||
|
if [[ -f "\$GEOSITE_CACHE" ]]; then
|
||||||
|
CACHE_AGE_SEC=\$(( \$(date +%s) - \$(stat -c %Y "\$GEOSITE_CACHE" 2>/dev/null || echo 0) ))
|
||||||
|
CACHE_AGE_DAYS=\$(( CACHE_AGE_SEC / 86400 ))
|
||||||
|
fi
|
||||||
|
if [[ \$CACHE_AGE_DAYS -ge 7 ]]; then
|
||||||
|
log "Загрузка базы geosite (dlc.dat_plain.yml, ~10-15 МБ, кэш обновляется раз в 7 дней)..."
|
||||||
|
if curl -sfL --max-time 60 "\$GEOSITE_YML_URL" -o "\$GEOSITE_CACHE.tmp" 2>/dev/null; then
|
||||||
|
mv "\$GEOSITE_CACHE.tmp" "\$GEOSITE_CACHE"
|
||||||
|
log "База geosite обновлена"
|
||||||
|
else
|
||||||
|
log "WARN: не удалось загрузить базу geosite — используется старый кэш (если есть)"
|
||||||
|
rm -f "\$GEOSITE_CACHE.tmp"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "База geosite в кэше актуальна (обновлена \$CACHE_AGE_DAYS дн. назад)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Автоматические источники
|
# Автоматические источники
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
line="\$(echo "\$line" | xargs)"
|
line="\$(echo "\$line" | xargs)"
|
||||||
@@ -284,10 +311,40 @@ while IFS= read -r line; do
|
|||||||
if [[ "\$line" == geosite:* ]]; then
|
if [[ "\$line" == geosite:* ]]; then
|
||||||
category="\${line#geosite:}"
|
category="\${line#geosite:}"
|
||||||
log " geosite:\$category"
|
log " geosite:\$category"
|
||||||
curl -sf "\$GEOSITE_BASE/\${category}.txt" 2>/dev/null \
|
if [[ -f "\$GEOSITE_CACHE" ]]; then
|
||||||
| grep -v '^\s*#' | grep -v '^\s*$' | grep -v 'include:' | grep -v 'regexp:' \
|
python3 -c "
|
||||||
| sed 's/^full://' \
|
import yaml, sys
|
||||||
>> "\$TMP_DOMAINS" || log " WARN: не удалось загрузить geosite:\$category"
|
try:
|
||||||
|
with open('\$GEOSITE_CACHE') as f:
|
||||||
|
data = yaml.safe_load(f)
|
||||||
|
found = False
|
||||||
|
for entry in data.get('lists', []):
|
||||||
|
if entry.get('name','').lower() == '\$category'.lower():
|
||||||
|
found = True
|
||||||
|
for rule in entry.get('rules', []):
|
||||||
|
d = rule
|
||||||
|
for prefix in ('domain:', 'full:'):
|
||||||
|
if d.startswith(prefix):
|
||||||
|
d = d[len(prefix):]
|
||||||
|
print(d)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# regexp: и include: пропускаем — не доменные правила
|
||||||
|
pass
|
||||||
|
if not found:
|
||||||
|
sys.stderr.write('NOTFOUND\\n')
|
||||||
|
except Exception as e:
|
||||||
|
sys.stderr.write(f'ERROR:{e}\\n')
|
||||||
|
" >> "\$TMP_DOMAINS" 2>/tmp/geosite_err.log
|
||||||
|
if grep -q "NOTFOUND" /tmp/geosite_err.log 2>/dev/null; then
|
||||||
|
log " WARN: категория geosite:\$category не найдена в базе"
|
||||||
|
elif grep -q "ERROR" /tmp/geosite_err.log 2>/dev/null; then
|
||||||
|
log " WARN: ошибка парсинга geosite:\$category — $(cat /tmp/geosite_err.log)"
|
||||||
|
fi
|
||||||
|
rm -f /tmp/geosite_err.log
|
||||||
|
else
|
||||||
|
log " WARN: база geosite недоступна, пропускаю geosite:\$category"
|
||||||
|
fi
|
||||||
|
|
||||||
elif [[ "\$line" == http* ]]; then
|
elif [[ "\$line" == http* ]]; then
|
||||||
log " URL: \$line"
|
log " URL: \$line"
|
||||||
@@ -598,12 +655,24 @@ echo ""
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
section "Веб-панель управления"
|
section "Веб-панель управления"
|
||||||
|
|
||||||
PANEL_INSTALLER="$SCRIPT_DIR/mita-panel-pkg/install-panel.sh"
|
# Ищем install-panel.sh в нескольких возможных местах:
|
||||||
|
# 1) Прямо рядом с install.sh (актуальная структура архива)
|
||||||
|
# 2) В подпапке mita-panel-pkg/ (старая структура архива)
|
||||||
|
PANEL_INSTALLER=""
|
||||||
|
for candidate in \
|
||||||
|
"$SCRIPT_DIR/install-panel.sh" \
|
||||||
|
"$SCRIPT_DIR/mita-panel-pkg/install-panel.sh"
|
||||||
|
do
|
||||||
|
if [[ -f "$candidate" ]]; then
|
||||||
|
PANEL_INSTALLER="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
if [[ ! -f "$PANEL_INSTALLER" ]]; then
|
if [[ -z "$PANEL_INSTALLER" ]]; then
|
||||||
warn "Файл install-panel.sh не найден рядом со скриптом."
|
warn "Файл install-panel.sh не найден рядом со скриптом."
|
||||||
warn "Ожидаемый путь: $PANEL_INSTALLER"
|
warn "Искал в: $SCRIPT_DIR/ и $SCRIPT_DIR/mita-panel-pkg/"
|
||||||
warn "Скачайте архив mita-panel.zip, распакуйте рядом с install.sh и запустите install-panel.sh вручную."
|
warn "Убедитесь что install-panel.sh, app.py и templates/ распакованы рядом с install.sh."
|
||||||
else
|
else
|
||||||
echo -e "Установить веб-панель управления mita? (логин/пароль и секретный путь генерируются автоматически)"
|
echo -e "Установить веб-панель управления mita? (логин/пароль и секретный путь генерируются автоматически)"
|
||||||
echo -e " ${GREEN}y${NC} — да, установить сейчас"
|
echo -e " ${GREEN}y${NC} — да, установить сейчас"
|
||||||
|
|||||||
Regular → Executable
+271
-48
@@ -75,6 +75,7 @@ main_menu() {
|
|||||||
echo -e " ${BOLD}7.${NC} Настройка fail2ban"
|
echo -e " ${BOLD}7.${NC} Настройка fail2ban"
|
||||||
echo -e " ${BOLD}8.${NC} Рекомендации по безопасности VPS"
|
echo -e " ${BOLD}8.${NC} Рекомендации по безопасности VPS"
|
||||||
echo -e " ${RED}${BOLD}9.${NC} ${RED}Полное удаление mita, панели и WARP${NC}"
|
echo -e " ${RED}${BOLD}9.${NC} ${RED}Полное удаление mita, панели и WARP${NC}"
|
||||||
|
echo -e " ${BOLD}10.${NC} Показать текущую конфигурацию"
|
||||||
echo -e " ${BOLD}0.${NC} Выход"
|
echo -e " ${BOLD}0.${NC} Выход"
|
||||||
echo ""
|
echo ""
|
||||||
read -r -p " Выберите пункт: " choice
|
read -r -p " Выберите пункт: " choice
|
||||||
@@ -88,6 +89,7 @@ main_menu() {
|
|||||||
7) menu_fail2ban ;;
|
7) menu_fail2ban ;;
|
||||||
8) menu_security ;;
|
8) menu_security ;;
|
||||||
9) menu_uninstall ;;
|
9) menu_uninstall ;;
|
||||||
|
10) menu_show_config ;;
|
||||||
0) exit 0 ;;
|
0) exit 0 ;;
|
||||||
*) warn "Неверный выбор" ;;
|
*) warn "Неверный выбор" ;;
|
||||||
esac
|
esac
|
||||||
@@ -411,6 +413,76 @@ _rebuild_start_sh() {
|
|||||||
# ════════════════════════════════════════════════════════════════════════════
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
# 4. ПОЛЬЗОВАТЕЛИ
|
# 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() {
|
menu_users() {
|
||||||
section "Управление пользователями mita"
|
section "Управление пользователями mita"
|
||||||
|
|
||||||
@@ -452,19 +524,7 @@ for u in users: print(f' - {u[\"name\"]}')
|
|||||||
pause ;;
|
pause ;;
|
||||||
3)
|
3)
|
||||||
read -r -p "Имя пользователя для удаления: " uname
|
read -r -p "Имя пользователя для удаления: " uname
|
||||||
python3 << PYEOF
|
_delete_user "$uname"
|
||||||
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 ;;
|
pause ;;
|
||||||
4)
|
4)
|
||||||
read -r -p "Имя пользователя: " uname
|
read -r -p "Имя пользователя: " uname
|
||||||
@@ -506,38 +566,80 @@ _create_user() {
|
|||||||
local pass
|
local pass
|
||||||
pass=$(openssl rand -base64 48 | tr -d '/+=' | head -c 48)
|
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
|
python3 << PYEOF
|
||||||
import json, subprocess
|
import json, subprocess
|
||||||
with open('$MITA_CONFIG') as f: d=json.load(f)
|
with open('$MITA_CONFIG') as f: d=json.load(f)
|
||||||
if any(u['name']=='$uname' for u in d.get('users',[])):
|
server_ip=subprocess.run(['curl','-s','--max-time','5','ifconfig.me'],capture_output=True,text=True).stdout.strip()
|
||||||
print(f"Пользователь '$uname' уже существует")
|
cfg=d.get('portBindings',[{}])[0]
|
||||||
else:
|
port=cfg.get('portRange',str(cfg.get('port','2100-2110')))
|
||||||
d.setdefault('users',[]).append({'name':'$uname','password':'$pass'})
|
proto=cfg.get('protocol','TCP')
|
||||||
with open('$MITA_CONFIG','w') as f: json.dump(d,f,indent=2)
|
client={
|
||||||
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'},
|
'profiles':[{'profileName':'default','user':{'name':'$uname','password':'$pass'},
|
||||||
'servers':[{'ipAddress':server_ip,'portBindings':[{'portRange':port,'protocol':proto}]}]}],
|
'servers':[{'ipAddress':server_ip,'portBindings':[{'portRange':port,'protocol':proto}]}]}],
|
||||||
'activeProfile':'default','rpcPort':8964,'socks5Port':1080
|
'activeProfile':'default','rpcPort':8964,'socks5Port':1080
|
||||||
}
|
}
|
||||||
print(f"\n Клиентский конфиг (mieru CLI):")
|
print(f"\n Клиентский конфиг (mieru CLI):")
|
||||||
print(json.dumps(client,indent=2,ensure_ascii=False))
|
print(json.dumps(client,indent=2,ensure_ascii=False))
|
||||||
|
|
||||||
first_port = port.split('-')[0] if '-' in port else port
|
first_port = port.split('-')[0] if '-' in port else port
|
||||||
print(f"\n Параметры для ручного ввода в Karing/sing-box клиентах:")
|
print(f"\n Параметры для ручного ввода в Karing/sing-box клиентах:")
|
||||||
print(f" server: {server_ip}")
|
print(f" server: {server_ip}")
|
||||||
print(f" server port: {first_port}")
|
print(f" server port: {first_port}")
|
||||||
print(f" username: $uname")
|
print(f" username: $uname")
|
||||||
print(f" password: $pass")
|
print(f" password: $pass")
|
||||||
print(f" transport: TCP")
|
print(f" transport: TCP")
|
||||||
print(f" multiplexing: multiplexing_low")
|
print(f" multiplexing: multiplexing_low")
|
||||||
PYEOF
|
PYEOF
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
@@ -583,8 +685,8 @@ KARINGEOF
|
|||||||
# ════════════════════════════════════════════════════════════════════════════
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
# 5. ПОКАЗАТЬ ДАННЫЕ АДМИНА
|
# 5. ПОКАЗАТЬ ДАННЫЕ АДМИНА
|
||||||
# ════════════════════════════════════════════════════════════════════════════
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
menu_show_admin() {
|
# ── Общая функция: вывод логина/пароля/URL панели с учётом access_mode ──────
|
||||||
section "Данные администратора панели"
|
_print_panel_access_info() {
|
||||||
local user pass secret panel_port access_mode
|
local user pass secret panel_port access_mode
|
||||||
user=$(panel_value "admin_user")
|
user=$(panel_value "admin_user")
|
||||||
pass=$(panel_value "admin_pass")
|
pass=$(panel_value "admin_pass")
|
||||||
@@ -600,21 +702,134 @@ menu_show_admin() {
|
|||||||
echo -e " Секретный путь: ${BOLD}/${secret}/${NC}"
|
echo -e " Секретный путь: ${BOLD}/${secret}/${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [[ "$access_mode" == "ssh" ]]; then
|
case "$access_mode" in
|
||||||
|
ssh)
|
||||||
local ssh_port
|
local ssh_port
|
||||||
ssh_port=$(panel_value "ssh_port"); ssh_port=${ssh_port:-22}
|
ssh_port=$(panel_value "ssh_port"); ssh_port=${ssh_port:-22}
|
||||||
|
echo -e " Способ доступа: ${YELLOW}SSH-туннель${NC} (панель не торчит наружу)"
|
||||||
print_ssh_instruction "$panel_port" "$secret" "$ssh_port"
|
print_ssh_instruction "$panel_port" "$secret" "$ssh_port"
|
||||||
else
|
;;
|
||||||
local proto="http"
|
domain)
|
||||||
|
local proto="http" domain
|
||||||
[[ -n "$(panel_value ssl_cert)" ]] && proto="https"
|
[[ -n "$(panel_value ssl_cert)" ]] && proto="https"
|
||||||
local domain
|
|
||||||
domain=$(panel_value "ssl_domain")
|
domain=$(panel_value "ssl_domain")
|
||||||
|
echo -e " Способ доступа: ${YELLOW}Доменное имя${NC}"
|
||||||
if [[ -n "$domain" ]]; then
|
if [[ -n "$domain" ]]; then
|
||||||
echo -e " Адрес: ${BOLD}${GREEN}${proto}://${domain}:${panel_port}/${secret}/${NC}"
|
echo -e " Адрес: ${BOLD}${GREEN}${proto}://${domain}:${panel_port}/${secret}/${NC}"
|
||||||
else
|
else
|
||||||
|
warn "Домен не настроен в panel.json, показываю по IP"
|
||||||
echo -e " Адрес: ${BOLD}${GREEN}${proto}://${server_ip}:${panel_port}/${secret}/${NC}"
|
echo -e " Адрес: ${BOLD}${GREEN}${proto}://${server_ip}:${panel_port}/${secret}/${NC}"
|
||||||
fi
|
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}"
|
||||||
|
;;
|
||||||
|
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
|
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
|
pause
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -885,13 +1100,21 @@ menu_uninstall() {
|
|||||||
info "Остановка mita..."
|
info "Остановка mita..."
|
||||||
systemctl stop mita 2>/dev/null || true
|
systemctl stop mita 2>/dev/null || true
|
||||||
systemctl disable 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 /usr/lib/systemd/system/mita.service
|
||||||
rm -f /etc/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
|
rm -f /usr/bin/mita
|
||||||
|
dpkg --configure -a 2>/dev/null || true
|
||||||
ok "mita сервер удалён"
|
ok "mita сервер удалён"
|
||||||
|
|
||||||
# ── 3. Удалить WARP (Docker) ───────────────────────────────────
|
# ── 3. Удалить WARP (Docker) ───────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user