mirror of
https://github.com/anten-ka/gotelegram_pro.git
synced 2026-05-19 16:46:03 +00:00
- i18n engine (lib/i18n.sh, lib/lang/en.sh, lib/lang/ru.sh)
- first-run language picker, persisted to .language + config.json
- install.sh, common.sh, backup.sh, templates_catalog.sh wired through t()/tf()
- backup.sh preserves .language marker and records language in metadata.json
- custom git template feature (first item in pro template picker)
* validates HTTPS URLs, rejects shell metachars
* 100MB size guard, 90s clone timeout
* auto-detects index.html in dist/public/build/_site/site/docs/out/www
- bot v2.4.0: i18n.py + lang/{en,ru}.json, /lang command, language toggle button
- bot: custom git template via text input with waiter gating
1265 lines
45 KiB
Bash
Executable File
1265 lines
45 KiB
Bash
Executable File
#!/bin/bash
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
# GoTelegram v2.4.0 — MTProxy powered by telemt (Rust + Tokio)
|
|
# Anti-DPI • Fake TLS • TCP Splice • JA3/JA4 Resistance • i18n (EN/RU)
|
|
#
|
|
# Install:
|
|
# curl -sL URL/install.sh | sudo bash
|
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
|
|
set -uo pipefail
|
|
|
|
# Script path and libraries
|
|
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
|
|
LIB_DIR="$SCRIPT_DIR/lib"
|
|
|
|
# Load libraries
|
|
source "$LIB_DIR/common.sh"
|
|
source "$LIB_DIR/i18n.sh"
|
|
source "$LIB_DIR/telemt.sh"
|
|
source "$LIB_DIR/telemt_config.sh"
|
|
source "$LIB_DIR/website.sh"
|
|
source "$LIB_DIR/templates_catalog.sh"
|
|
source "$LIB_DIR/backup.sh"
|
|
[ -f "$LIB_DIR/stats.sh" ] && source "$LIB_DIR/stats.sh"
|
|
|
|
# Load language (from config.json or marker file, default en)
|
|
load_language "$(detect_language)"
|
|
|
|
# ── Главное меню (Compact Dashboard + 5 Top-Level Items) ──────────────────────
|
|
show_main_menu() {
|
|
local proxy_status bot_status nginx_st mode domain secret port ip link ssl_expiry
|
|
proxy_status=$(telemt_status)
|
|
bot_status=$(bot_service_status)
|
|
nginx_st=$(nginx_status 2>/dev/null || echo "stopped")
|
|
mode=$(config_get mode 2>/dev/null || echo "—")
|
|
domain=$(config_get domain 2>/dev/null || echo "")
|
|
secret=$(get_config_value secret 2>/dev/null || echo "")
|
|
port=$(get_config_value port 2>/dev/null || echo "443")
|
|
ip=$(get_server_ip 2>/dev/null || echo "N/A")
|
|
|
|
local W=54
|
|
local line; line=$(printf '━%.0s' $(seq 1 $W))
|
|
local line2; line2=$(printf '─%.0s' $(seq 1 $W))
|
|
|
|
# ── Header (no right border — ANSI breaks alignment) ──
|
|
echo ""
|
|
echo -e " ${BOLD}${CYAN}━${line}━${NC}"
|
|
echo -e " ${BOLD}${WHITE} GoTelegram v${GOTELEGRAM_VERSION}${NC} ${DIM}— $(t dashboard_title)${NC}"
|
|
echo -e " ${BOLD}${CYAN}━${line}━${NC}"
|
|
|
|
# ── Service health ──
|
|
echo ""
|
|
echo -e " ${DIM}${line2}${NC}"
|
|
|
|
# Proxy
|
|
local proxy_icon proxy_color
|
|
case "$proxy_status" in
|
|
running) proxy_icon="●"; proxy_color="${GREEN}" ;;
|
|
stopped) proxy_icon="○"; proxy_color="${YELLOW}" ;;
|
|
*) proxy_icon="✗"; proxy_color="${RED}" ;;
|
|
esac
|
|
echo -e " ${proxy_color}${proxy_icon}${NC} $(t svc_proxy) ${proxy_color}${proxy_status}${NC} ${DIM}(telemt ${mode})${NC}"
|
|
|
|
# nginx
|
|
local nginx_icon nginx_color
|
|
case "$nginx_st" in
|
|
running) nginx_icon="●"; nginx_color="${GREEN}" ;;
|
|
*) nginx_icon="✗"; nginx_color="${RED}" ;;
|
|
esac
|
|
echo -e " ${nginx_icon}${nginx_color}${NC} $(t svc_nginx) ${nginx_color}${nginx_st}${NC} ${DIM}(127.0.0.1:8443)${NC}"
|
|
|
|
# Site (pro)
|
|
if [ "$mode" = "pro" ] && [ -n "$domain" ]; then
|
|
local site_icon site_color
|
|
if curl -sk --max-time 3 "https://${domain}/" -o /dev/null 2>/dev/null; then
|
|
site_icon="●"; site_color="${GREEN}"
|
|
else
|
|
site_icon="✗"; site_color="${RED}"
|
|
fi
|
|
echo -e " ${site_color}${site_icon}${NC} $(t svc_site) ${site_color}https://${domain}${NC}"
|
|
|
|
ssl_expiry=$(get_ssl_expiry "$domain" 2>/dev/null || echo "N/A")
|
|
echo -e " ${GREEN}●${NC} $(t svc_ssl) ${DIM}$(tf ssl_until "$ssl_expiry")${NC}"
|
|
fi
|
|
|
|
# Bot
|
|
case "$bot_status" in
|
|
running) echo -e " ${GREEN}●${NC} $(t svc_bot) ${GREEN}$(t running)${NC}" ;;
|
|
stopped) echo -e " ${YELLOW}○${NC} $(t svc_bot) ${YELLOW}$(t stopped)${NC}" ;;
|
|
esac
|
|
|
|
echo -e " ${DIM}${line2}${NC}"
|
|
|
|
# ── Network parameters ──
|
|
echo -e " ${WHITE}$(t net_ip)${NC} ${CYAN}${ip}${NC} ${WHITE}$(t net_port)${NC} ${CYAN}${port}${NC} ${WHITE}$(t net_mode)${NC} ${CYAN}${mode}${NC}"
|
|
if [ -n "$domain" ]; then
|
|
echo -e " ${WHITE}$(t net_domain)${NC} ${CYAN}${domain}${NC}"
|
|
fi
|
|
|
|
echo -e " ${DIM}${line2}${NC}"
|
|
|
|
# ── Proxy link + QR ──
|
|
local mask_host
|
|
mask_host=$(config_get mask_host 2>/dev/null || echo "")
|
|
if [ -n "$secret" ] && [ "$proxy_status" = "running" ]; then
|
|
if [ "$mode" = "pro" ] && [ -n "$domain" ]; then
|
|
link=$(generate_proxy_link "$domain" "$port" "$secret" "$domain")
|
|
else
|
|
link=$(generate_proxy_link "$ip" "$port" "$secret" "$mask_host")
|
|
fi
|
|
|
|
echo -e " ${BOLD}${WHITE}$(t connection_link)${NC}"
|
|
echo -e " ${GREEN}${link}${NC}"
|
|
|
|
if command -v qrencode &>/dev/null; then
|
|
echo ""
|
|
qrencode -t UTF8 -m 2 "$link" 2>/dev/null | while IFS= read -r qr_line; do
|
|
echo " ${qr_line}"
|
|
done
|
|
echo ""
|
|
fi
|
|
else
|
|
echo -e " ${DIM}$(t proxy_not_configured)${NC}"
|
|
echo ""
|
|
fi
|
|
|
|
# ── Menu ──
|
|
echo -e " ${DIM}${line2}${NC}"
|
|
echo -e " ${CYAN}1${NC}) $(t menu_proxy)"
|
|
echo -e " ${CYAN}2${NC}) $(t menu_stats)"
|
|
echo -e " ${CYAN}3${NC}) $(t menu_manage)"
|
|
echo -e " ${CYAN}4${NC}) $(t menu_telegram_bot)"
|
|
echo -e " ${CYAN}5${NC}) $(t menu_about)"
|
|
echo -e " ${CYAN}0${NC}) ${DIM}$(t exit)${NC}"
|
|
echo -e " ${DIM}${line2}${NC}"
|
|
echo -e " ${DIM}$(t auto_refresh_30s)${NC}"
|
|
echo -ne " ${WHITE}▸ ${NC}"
|
|
}
|
|
|
|
# ── Submenu: Proxy ──────────────────────────────────────────────────────────
|
|
submenu_proxy() {
|
|
while true; do
|
|
echo ""
|
|
echo -e " ${BOLD}${WHITE}$(t submenu_proxy_title)${NC}"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}"
|
|
echo -e " ${CYAN}1${NC}) $(t proxy_install_update)"
|
|
echo -e " ${CYAN}2${NC}) $(t proxy_status_detail)"
|
|
echo -e " ${CYAN}3${NC}) $(t proxy_copy_link)"
|
|
echo -e " ${CYAN}4${NC}) $(t proxy_share)"
|
|
echo -e " ${CYAN}5${NC}) $(t proxy_restart)"
|
|
echo -e " ${CYAN}6${NC}) $(t proxy_logs)"
|
|
echo -e " ${CYAN}7${NC}) $(t proxy_change_mode)"
|
|
echo -e " ${CYAN}0${NC}) $(t back)"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}"
|
|
echo -ne " ${WHITE}$(t choose):${NC} "
|
|
read -r ch
|
|
|
|
case "$ch" in
|
|
1) menu_install ;;
|
|
2) menu_status ;;
|
|
3) menu_link ;;
|
|
4) menu_share ;;
|
|
5) menu_restart ;;
|
|
6) menu_logs ;;
|
|
7) menu_change_mode ;;
|
|
0) break ;;
|
|
*) log_error "$(t invalid_choice)" ;;
|
|
esac
|
|
|
|
echo ""
|
|
echo -ne " ${DIM}$(t press_enter)${NC}"
|
|
read -r
|
|
done
|
|
}
|
|
|
|
# ── Submenu: Management ─────────────────────────────────────────────────────
|
|
submenu_manage() {
|
|
while true; do
|
|
echo ""
|
|
echo -e " ${BOLD}${WHITE}$(t submenu_manage_title)${NC}"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}"
|
|
echo -e " ${CYAN}1${NC}) $(t manage_backup)"
|
|
echo -e " ${CYAN}2${NC}) $(t manage_restore)"
|
|
echo -e " ${CYAN}3${NC}) $(t manage_update_telemt)"
|
|
echo -e " ${CYAN}4${NC}) $(t manage_site_ssl)"
|
|
echo -e " ${CYAN}5${NC}) $(t manage_remove)"
|
|
echo -e " ${CYAN}6${NC}) $(t manage_language)"
|
|
echo -e " ${CYAN}0${NC}) $(t back)"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}"
|
|
echo -ne " ${WHITE}$(t choose):${NC} "
|
|
read -r ch
|
|
|
|
case "$ch" in
|
|
1) interactive_backup ;;
|
|
2) interactive_restore ;;
|
|
3) update_telemt ;;
|
|
4) menu_website ;;
|
|
5) menu_remove ;;
|
|
6) menu_language ;;
|
|
0) break ;;
|
|
*) log_error "$(t invalid_choice)" ;;
|
|
esac
|
|
|
|
echo ""
|
|
echo -ne " ${DIM}$(t press_enter)${NC}"
|
|
read -r
|
|
done
|
|
}
|
|
|
|
# ── Submenu: About ──────────────────────────────────────────────────────────
|
|
submenu_about() {
|
|
while true; do
|
|
echo ""
|
|
echo -e " ${BOLD}${WHITE}$(t submenu_about_title)${NC}"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}"
|
|
echo -e " ${CYAN}1${NC}) $(t about_version_info)"
|
|
echo -e " ${CYAN}2${NC}) $(t about_promo)"
|
|
echo -e " ${CYAN}0${NC}) $(t back)"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}"
|
|
echo -ne " ${WHITE}$(t choose):${NC} "
|
|
read -r ch
|
|
|
|
case "$ch" in
|
|
1) menu_version ;;
|
|
2) menu_promo ;;
|
|
0) break ;;
|
|
*) log_error "$(t invalid_choice)" ;;
|
|
esac
|
|
|
|
echo ""
|
|
echo -ne " ${DIM}$(t press_enter)${NC}"
|
|
read -r
|
|
done
|
|
}
|
|
|
|
# ── Version info ────────────────────────────────────────────────────────────
|
|
menu_version() {
|
|
echo ""
|
|
echo -e " ${BOLD}${WHITE}$(t version_title)${NC}"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}"
|
|
echo -e " ${WHITE}$(t version_label)${NC} v${GOTELEGRAM_VERSION}"
|
|
echo -e " ${WHITE}$(t version_engine)${NC} telemt (Rust + Tokio)"
|
|
echo -e " ${WHITE}$(t version_tech)${NC} Anti-DPI, Fake TLS, TCP Splice"
|
|
echo -e " ${WHITE}$(t version_license)${NC} MIT"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..54})${NC}"
|
|
}
|
|
|
|
# ── Install: mode selection ─────────────────────────────────────────────────
|
|
menu_install() {
|
|
# Check for v1
|
|
if detect_v1_installation; then
|
|
echo ""
|
|
echo -e " ${YELLOW}$(t v1_detected)${NC}"
|
|
echo -e " ${DIM}$(tf v1_container "$V1_CONTAINER_NAME")${NC}"
|
|
echo ""
|
|
if ! migrate_v1_to_v2; then
|
|
return
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
echo -e " ${BOLD}${WHITE}$(t install_select_mode)${NC}"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
|
|
echo -e " ${CYAN}1)${NC} ${GREEN}$(t install_lite_title)${NC}"
|
|
echo -e " ${DIM}$(t install_lite_desc1)${NC}"
|
|
echo -e " ${DIM}$(t install_lite_desc2)${NC}"
|
|
echo ""
|
|
echo -e " ${CYAN}2)${NC} ${MAGENTA}$(t install_pro_title)${NC}"
|
|
echo -e " ${DIM}$(t install_pro_desc1)${NC}"
|
|
echo -e " ${DIM}$(t install_pro_desc2)${NC}"
|
|
echo -e " ${DIM}$(t install_pro_desc3)${NC}"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
|
|
echo -ne " ${WHITE}$(t install_mode_choice)${NC} "
|
|
read -r mode_choice
|
|
mode_choice="${mode_choice:-}"
|
|
|
|
case "$mode_choice" in
|
|
1) install_lite_mode ;;
|
|
2) install_pro_mode ;;
|
|
*) log_error "$(tf install_bad_choice "${mode_choice:-<empty>}")" ;;
|
|
esac
|
|
}
|
|
|
|
# ── Lite mode ───────────────────────────────────────────────────────────────
|
|
install_lite_mode() {
|
|
log_step "$(t install_lite_step)"
|
|
|
|
# Domain selection
|
|
local domain
|
|
domain=$(select_quick_domain)
|
|
[ $? -ne 0 ] && return
|
|
|
|
# Port selection
|
|
local port
|
|
port=$(select_port)
|
|
[ $? -ne 0 ] && return
|
|
|
|
# Generate secret
|
|
local secret
|
|
secret=$(generate_hex 32)
|
|
|
|
# Confirm
|
|
local ip
|
|
ip=$(get_server_ip)
|
|
echo ""
|
|
echo -e " ${BOLD}${WHITE}$(t install_config_title)${NC}"
|
|
echo -e " $(t install_cfg_ip) ${CYAN}${ip}${NC}"
|
|
echo -e " $(t install_cfg_port) ${CYAN}${port}${NC}"
|
|
echo -e " $(t install_cfg_mask) ${CYAN}${domain}${NC}"
|
|
echo -e " $(t install_cfg_mode) ${GREEN}Lite${NC}"
|
|
echo ""
|
|
|
|
if ! confirm "$(t install_confirm_proxy)"; then
|
|
return
|
|
fi
|
|
|
|
# Install
|
|
ensure_deps
|
|
install_telemt_full || return
|
|
|
|
# Generate telemt config
|
|
generate_telemt_toml "$secret" "$port" "lite" "$domain" "443"
|
|
|
|
# Validate
|
|
validate_telemt_config || return
|
|
|
|
# Start
|
|
start_telemt || return
|
|
|
|
# Save GoTelegram config
|
|
save_gotelegram_config "telemt" "lite" "$port" "$secret" "$domain" "" ""
|
|
|
|
# Credits
|
|
show_credits
|
|
|
|
# Result
|
|
show_proxy_info
|
|
log_success "$(tf install_done "$GOTELEGRAM_VERSION" "Lite")"
|
|
}
|
|
|
|
# ── Pro mode ────────────────────────────────────────────────────────────────
|
|
install_pro_mode() {
|
|
log_step "$(t install_pro_step)"
|
|
|
|
# Enter domain
|
|
echo ""
|
|
echo -ne " ${WHITE}$(t install_enter_domain)${NC} "
|
|
read -r user_domain
|
|
|
|
if [ -z "$user_domain" ] || ! validate_domain "$user_domain"; then
|
|
log_error "$(tf install_bad_domain "${user_domain:-<empty>}")"
|
|
return
|
|
fi
|
|
|
|
# Check DNS
|
|
local resolved_ip server_ip
|
|
resolved_ip=$(dig +short "$user_domain" A 2>/dev/null | head -1)
|
|
server_ip=$(get_server_ip)
|
|
|
|
if [ -n "$resolved_ip" ] && [ "$resolved_ip" != "$server_ip" ]; then
|
|
log_warning "$(tf install_dns_mismatch "$user_domain" "$resolved_ip" "$server_ip")"
|
|
if ! confirm "$(t install_continue_anyway)"; then
|
|
return
|
|
fi
|
|
fi
|
|
|
|
# Email for Let's Encrypt
|
|
echo -ne " ${WHITE}$(t install_enter_email)${NC} "
|
|
read -r ssl_email
|
|
|
|
# Template selection
|
|
local template_dir
|
|
template_dir=$(interactive_template_selection)
|
|
[ $? -ne 0 ] && return
|
|
|
|
# Pro architecture:
|
|
# telemt listens on 0.0.0.0:443 (accepts ALL connections)
|
|
# nginx listens on 127.0.0.1:8443 with SSL (serves website)
|
|
# MTProxy client → :443 → telemt (proxies)
|
|
# Regular browser → :443 → telemt → 127.0.0.1:8443 → nginx (website)
|
|
# ISP only sees HTTPS on 443 to domain
|
|
local nginx_internal_port=8443
|
|
echo ""
|
|
echo -e " ${DIM}$(t install_arch_desc1)${NC}"
|
|
echo -e " ${DIM}$(tf install_arch_desc2 "$nginx_internal_port")${NC}"
|
|
echo -e " ${DIM}$(tf install_arch_desc3 "$user_domain")${NC}"
|
|
|
|
# Generate fake-TLS secret (ee + secret + hex domain)
|
|
# ee prefix tells Telegram client to masquerade traffic as TLS to domain
|
|
local raw_secret
|
|
raw_secret=$(generate_hex 32)
|
|
local domain_hex
|
|
domain_hex=$(printf '%s' "$user_domain" | xxd -p | tr -d '\n')
|
|
local faketls_secret="ee${raw_secret}${domain_hex}"
|
|
|
|
# Confirmation
|
|
echo ""
|
|
echo -e " ${BOLD}${WHITE}$(t install_config_title)${NC}"
|
|
echo -e " $(t install_cfg_domain) ${CYAN}${user_domain}${NC}"
|
|
echo -e " $(t install_cfg_port) ${CYAN}443 (telemt + nginx)${NC}"
|
|
echo -e " $(t install_cfg_mode) ${MAGENTA}Pro (fake-TLS)${NC}"
|
|
echo ""
|
|
|
|
if ! confirm "$(t install_confirm_proxy_site)"; then
|
|
return
|
|
fi
|
|
|
|
# Install
|
|
ensure_deps
|
|
install_telemt_full || return
|
|
|
|
# telemt config: listen 443, masquerade to local nginx via dns_override
|
|
generate_telemt_toml "$raw_secret" "443" "pro" "$user_domain" "$nginx_internal_port"
|
|
|
|
# Website setup (nginx on internal port + certbot + template)
|
|
setup_pro_mode "$user_domain" "$template_dir" "$nginx_internal_port" "$ssl_email" || return
|
|
|
|
# Stop nginx on 443 before starting telemt (telemt will take 443)
|
|
# nginx already reconfigured to internal port
|
|
systemctl restart nginx 2>/dev/null
|
|
|
|
# Start telemt
|
|
start_telemt || return
|
|
|
|
# Save config
|
|
local tpl_id
|
|
tpl_id=$(basename "$template_dir")
|
|
save_gotelegram_config "telemt" "pro" "443" "$raw_secret" "$user_domain" "$user_domain" "$tpl_id"
|
|
|
|
# Result — use domain and fake-TLS link
|
|
show_proxy_info_pro "$user_domain" "$faketls_secret"
|
|
echo -e " ${WHITE}$(t svc_site):${NC} ${GREEN}https://${user_domain}${NC}"
|
|
log_success "$(tf install_done "$GOTELEGRAM_VERSION" "Pro")"
|
|
}
|
|
|
|
# ── Статус ───────────────────────────────────────────────────────────────────
|
|
menu_status() {
|
|
show_proxy_info
|
|
|
|
# Extras for pro
|
|
local mode
|
|
mode=$(config_get mode 2>/dev/null)
|
|
if [ "$mode" = "pro" ]; then
|
|
local domain
|
|
domain=$(config_get domain 2>/dev/null)
|
|
if [ -n "$domain" ]; then
|
|
local ssl_expiry
|
|
ssl_expiry=$(get_ssl_expiry "$domain")
|
|
local nginx_st
|
|
nginx_st=$(nginx_status)
|
|
echo -e " ${WHITE}$(t svc_nginx):${NC} ${nginx_st}"
|
|
echo -e " ${WHITE}$(t website_ssl_until)${NC} ${ssl_expiry}"
|
|
echo -e " ${WHITE}$(t svc_site):${NC} https://${domain}"
|
|
echo ""
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ── Ссылка ───────────────────────────────────────────────────────────────────
|
|
menu_link() {
|
|
local secret port ip link mode domain mask_host
|
|
secret=$(get_config_value secret)
|
|
port=$(get_config_value port)
|
|
ip=$(get_server_ip)
|
|
mode=$(config_get mode 2>/dev/null || echo "lite")
|
|
domain=$(config_get domain 2>/dev/null || echo "")
|
|
mask_host=$(config_get mask_host 2>/dev/null || echo "")
|
|
|
|
if [ "$mode" = "pro" ] && [ -n "$domain" ]; then
|
|
link=$(generate_proxy_link "$domain" "$port" "$secret" "$domain")
|
|
else
|
|
link=$(generate_proxy_link "$ip" "$port" "$secret" "$mask_host")
|
|
fi
|
|
|
|
echo ""
|
|
echo -e " ${BOLD}${WHITE}$(t link_title)${NC}"
|
|
echo ""
|
|
echo -e " ${GREEN}${link}${NC}"
|
|
echo ""
|
|
|
|
if command -v qrencode &>/dev/null; then
|
|
qrencode -t UTF8 -m 2 "$link" 2>/dev/null
|
|
fi
|
|
}
|
|
|
|
# ── Поделиться ───────────────────────────────────────────────────────────────
|
|
menu_share() {
|
|
local secret port ip link mode domain mask_host server_display
|
|
secret=$(get_config_value secret)
|
|
port=$(get_config_value port)
|
|
ip=$(get_server_ip)
|
|
mode=$(config_get mode 2>/dev/null || echo "lite")
|
|
domain=$(config_get domain 2>/dev/null || echo "")
|
|
mask_host=$(config_get mask_host 2>/dev/null || echo "")
|
|
|
|
if [ "$mode" = "pro" ] && [ -n "$domain" ]; then
|
|
link=$(generate_proxy_link "$domain" "$port" "$secret" "$domain")
|
|
server_display="$domain"
|
|
else
|
|
link=$(generate_proxy_link "$ip" "$port" "$secret" "$mask_host")
|
|
server_display="$ip"
|
|
fi
|
|
|
|
echo ""
|
|
echo -e " ${BOLD}$(t share_title)${NC}"
|
|
echo ""
|
|
printf "$(t share_line1)\n" "$GOTELEGRAM_VERSION"
|
|
echo ""
|
|
printf "$(t share_server)\n" "$server_display"
|
|
printf "$(t share_port)\n" "$port"
|
|
echo ""
|
|
echo "$(t share_connect_cta)"
|
|
echo "$link"
|
|
echo ""
|
|
echo "$(t share_footer)"
|
|
echo ""
|
|
}
|
|
|
|
# ── Перезапуск ───────────────────────────────────────────────────────────────
|
|
menu_restart() {
|
|
restart_telemt
|
|
local mode
|
|
mode=$(config_get mode 2>/dev/null)
|
|
if [ "$mode" = "pro" ]; then
|
|
restart_nginx
|
|
fi
|
|
}
|
|
|
|
# ── Logs ────────────────────────────────────────────────────────────────────
|
|
menu_logs() {
|
|
echo ""
|
|
echo -e " ${BOLD}${WHITE}$(tf logs_telemt_title 40)${NC}"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
|
|
telemt_logs 40
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
|
|
}
|
|
|
|
# ── Change mode / template ──────────────────────────────────────────────────
|
|
menu_change_mode() {
|
|
local current_mode
|
|
current_mode=$(config_get mode 2>/dev/null)
|
|
echo ""
|
|
echo -e " ${WHITE}$(t change_current_mode)${NC} ${CYAN}${current_mode}${NC}"
|
|
echo ""
|
|
echo -e " ${CYAN}1${NC}) $(t change_template)"
|
|
echo -e " ${CYAN}2${NC}) $(t change_mode_switch)"
|
|
echo -e " ${CYAN}0${NC}) $(t back)"
|
|
echo -ne " ${WHITE}$(t choose):${NC} "
|
|
read -r ch
|
|
|
|
case "$ch" in
|
|
1)
|
|
if [ "$current_mode" != "pro" ]; then
|
|
log_error "$(t change_only_pro)"
|
|
return
|
|
fi
|
|
local template_dir
|
|
template_dir=$(interactive_template_selection)
|
|
[ $? -ne 0 ] && return
|
|
switch_template "$template_dir"
|
|
;;
|
|
2)
|
|
log_warning "$(t change_requires_reinstall)"
|
|
if confirm "$(t change_reinstall_confirm)"; then
|
|
menu_install
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ── Website management ─────────────────────────────────────────────────────
|
|
menu_website() {
|
|
local mode
|
|
mode=$(config_get mode 2>/dev/null)
|
|
|
|
if [ "$mode" != "pro" ]; then
|
|
log_info "$(t website_only_pro)"
|
|
return
|
|
fi
|
|
|
|
local domain
|
|
domain=$(config_get domain 2>/dev/null)
|
|
|
|
echo ""
|
|
echo -e " ${BOLD}${WHITE}$(t website_title)${NC}"
|
|
echo -e " $(t website_domain) ${CYAN}${domain}${NC}"
|
|
echo -e " $(t website_ssl_until) $(get_ssl_expiry "$domain")"
|
|
echo ""
|
|
echo -e " ${CYAN}1${NC}) $(t website_renew_ssl)"
|
|
echo -e " ${CYAN}2${NC}) $(t website_restart_nginx)"
|
|
echo -e " ${CYAN}3${NC}) $(t website_change_template)"
|
|
echo -e " ${CYAN}0${NC}) $(t back)"
|
|
echo -ne " ${WHITE}$(t choose):${NC} "
|
|
read -r ch
|
|
|
|
case "$ch" in
|
|
1) renew_ssl_certificate ;;
|
|
2) restart_nginx ;;
|
|
3)
|
|
local template_dir
|
|
template_dir=$(interactive_template_selection)
|
|
[ $? -ne 0 ] && return
|
|
switch_template "$template_dir"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ── Remove ─────────────────────────────────────────────────────────────────
|
|
menu_remove() {
|
|
echo ""
|
|
echo -e " ${BOLD}${RED}$(t remove_title)${NC}"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
|
|
echo -e " ${CYAN}1${NC}) $(t remove_proxy_only)"
|
|
echo -e " ${CYAN}2${NC}) $(t remove_bot_only)"
|
|
echo -e " ${CYAN}3${NC}) $(t remove_all)"
|
|
echo -e " ${CYAN}0${NC}) $(t back)"
|
|
echo -ne " ${WHITE}$(t choose):${NC} "
|
|
read -r rm_choice
|
|
|
|
case "$rm_choice" in
|
|
1)
|
|
log_warning "$(t remove_warn_proxy)"
|
|
if ! confirm "$(t remove_confirm_proxy)"; then return; fi
|
|
if confirm "$(t remove_backup_before)"; then
|
|
interactive_backup
|
|
fi
|
|
remove_telemt
|
|
local mode
|
|
mode=$(config_get mode 2>/dev/null)
|
|
if [ "$mode" = "pro" ]; then
|
|
remove_pro_mode
|
|
fi
|
|
rm -f "$GOTELEGRAM_CONFIG"
|
|
log_success "$(t remove_proxy_done)"
|
|
;;
|
|
2)
|
|
bot_remove
|
|
;;
|
|
3)
|
|
log_warning "$(t remove_warn_all)"
|
|
if ! confirm "$(t remove_confirm_all)"; then return; fi
|
|
if confirm "$(t remove_backup_before)"; then
|
|
interactive_backup
|
|
fi
|
|
# Proxy
|
|
remove_telemt
|
|
local mode
|
|
mode=$(config_get mode 2>/dev/null)
|
|
if [ "$mode" = "pro" ]; then
|
|
remove_pro_mode
|
|
fi
|
|
rm -f "$GOTELEGRAM_CONFIG"
|
|
# Bot
|
|
if [ "$(bot_service_status)" != "not_installed" ]; then
|
|
systemctl stop "$BOT_SERVICE" 2>/dev/null
|
|
systemctl disable "$BOT_SERVICE" 2>/dev/null
|
|
rm -f "/etc/systemd/system/${BOT_SERVICE}.service"
|
|
systemctl daemon-reload
|
|
rm -rf "$BOT_DIR"
|
|
fi
|
|
log_success "$(t remove_all_done)"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ── Telegram-бот ────────────────────────────────────────────────────────────
|
|
BOT_DIR="/opt/gotelegram-bot"
|
|
BOT_SERVICE="gotelegram-bot"
|
|
|
|
bot_service_status() {
|
|
if ! systemctl list-unit-files "$BOT_SERVICE.service" &>/dev/null 2>&1; then
|
|
echo "not_installed"
|
|
elif systemctl is-active "$BOT_SERVICE" &>/dev/null 2>&1; then
|
|
echo "running"
|
|
else
|
|
echo "stopped"
|
|
fi
|
|
}
|
|
|
|
menu_bot() {
|
|
local st
|
|
st=$(bot_service_status)
|
|
|
|
echo ""
|
|
echo -e " ${BOLD}${WHITE}$(t bot_title)${NC}"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
|
|
|
|
case "$st" in
|
|
running)
|
|
echo -e " $(t bot_status_colon) ${GREEN}$(t bot_status_running)${NC}"
|
|
echo ""
|
|
echo -e " ${CYAN}1${NC}) $(t bot_menu_status)"
|
|
echo -e " ${CYAN}2${NC}) $(t bot_menu_logs)"
|
|
echo -e " ${CYAN}3${NC}) $(t bot_menu_restart)"
|
|
echo -e " ${CYAN}4${NC}) $(t bot_menu_stop)"
|
|
echo -e " ${CYAN}5${NC}) $(t bot_menu_settings)"
|
|
echo -e " ${CYAN}6${NC}) $(t bot_menu_remove)"
|
|
;;
|
|
stopped)
|
|
echo -e " $(t bot_status_colon) ${YELLOW}$(t bot_status_stopped)${NC}"
|
|
echo ""
|
|
echo -e " ${CYAN}1${NC}) $(t bot_menu_status)"
|
|
echo -e " ${CYAN}2${NC}) $(t bot_menu_logs)"
|
|
echo -e " ${CYAN}3${NC}) $(t bot_menu_start)"
|
|
echo -e " ${CYAN}5${NC}) $(t bot_menu_settings)"
|
|
echo -e " ${CYAN}6${NC}) $(t bot_menu_remove)"
|
|
;;
|
|
*)
|
|
echo -e " $(t bot_status_colon) ${RED}$(t bot_status_not_installed)${NC}"
|
|
echo ""
|
|
echo -e " ${DIM}$(t bot_intro1)${NC}"
|
|
echo -e " ${DIM}$(t bot_intro2)${NC}"
|
|
echo ""
|
|
echo -e " ${CYAN}1${NC}) $(t bot_menu_install)"
|
|
;;
|
|
esac
|
|
|
|
echo -e " ${CYAN}0${NC}) $(t back)"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
|
|
echo -ne " ${WHITE}$(t choose):${NC} "
|
|
read -r ch
|
|
|
|
case "$st" in
|
|
running)
|
|
case "$ch" in
|
|
1) bot_show_status ;;
|
|
2) bot_show_logs ;;
|
|
3) systemctl restart "$BOT_SERVICE" && log_success "$(t bot_restarted)" ;;
|
|
4) systemctl stop "$BOT_SERVICE" && log_info "$(t bot_stopped)" ;;
|
|
5) bot_edit_config ;;
|
|
6) bot_remove ;;
|
|
esac
|
|
;;
|
|
stopped)
|
|
case "$ch" in
|
|
1) bot_show_status ;;
|
|
2) bot_show_logs ;;
|
|
3) systemctl start "$BOT_SERVICE" && log_success "$(t bot_started)" ;;
|
|
5) bot_edit_config ;;
|
|
6) bot_remove ;;
|
|
esac
|
|
;;
|
|
*)
|
|
case "$ch" in
|
|
1) bot_install ;;
|
|
esac
|
|
;;
|
|
esac
|
|
}
|
|
|
|
bot_install() {
|
|
log_step "$(t bot_install_step)"
|
|
|
|
# Python
|
|
if ! command -v python3 &>/dev/null; then
|
|
log_info "$(t bot_install_python)"
|
|
if command -v apt-get &>/dev/null; then
|
|
apt-get update -qq && apt-get install -y -qq python3 python3-pip python3-venv
|
|
elif command -v dnf &>/dev/null; then
|
|
dnf install -y -q python3 python3-pip
|
|
elif command -v yum &>/dev/null; then
|
|
yum install -y -q python3 python3-pip
|
|
fi
|
|
fi
|
|
|
|
# Copy bot files
|
|
mkdir -p "$BOT_DIR"
|
|
if [ -f "$SCRIPT_DIR/gotelegram-bot/bot.py" ]; then
|
|
cp "$SCRIPT_DIR/gotelegram-bot/bot.py" "$BOT_DIR/"
|
|
cp "$SCRIPT_DIR/gotelegram-bot/requirements.txt" "$BOT_DIR/"
|
|
[ -f "$SCRIPT_DIR/gotelegram-bot/config.example.env" ] && \
|
|
cp "$SCRIPT_DIR/gotelegram-bot/config.example.env" "$BOT_DIR/"
|
|
# Copy i18n language files for bot
|
|
if [ -d "$SCRIPT_DIR/gotelegram-bot/lang" ]; then
|
|
mkdir -p "$BOT_DIR/lang"
|
|
cp -f "$SCRIPT_DIR/gotelegram-bot/lang/"*.json "$BOT_DIR/lang/" 2>/dev/null
|
|
fi
|
|
[ -f "$SCRIPT_DIR/gotelegram-bot/i18n.py" ] && \
|
|
cp "$SCRIPT_DIR/gotelegram-bot/i18n.py" "$BOT_DIR/"
|
|
else
|
|
log_error "$(tf bot_files_not_found "$SCRIPT_DIR/gotelegram-bot/")"
|
|
return 1
|
|
fi
|
|
|
|
# Templates catalog
|
|
[ -f "$SCRIPT_DIR/templates_catalog.json" ] && \
|
|
cp "$SCRIPT_DIR/templates_catalog.json" "$GOTELEGRAM_DIR/"
|
|
|
|
# Venv
|
|
if [ ! -d "$BOT_DIR/venv" ]; then
|
|
log_info "$(t bot_create_venv)"
|
|
python3 -m venv "$BOT_DIR/venv"
|
|
fi
|
|
log_info "$(t bot_install_deps)"
|
|
"$BOT_DIR/venv/bin/pip" install -r "$BOT_DIR/requirements.txt" -q
|
|
|
|
# Configuration
|
|
if [ ! -f "$BOT_DIR/.env" ]; then
|
|
echo ""
|
|
echo -e " ${YELLOW}$(t bot_enter_token)${NC}"
|
|
local token=""
|
|
while [ -z "$token" ]; do
|
|
echo -ne " ${WHITE}$(t bot_token)${NC} "
|
|
read -r token
|
|
token=$(echo "$token" | tr -d '[:space:]')
|
|
[ -z "$token" ] && log_error "$(t bot_token_empty)"
|
|
done
|
|
|
|
echo ""
|
|
echo -e " ${WHITE}$(t bot_add_admin_how)${NC}"
|
|
echo -e " ${CYAN}1${NC}) $(t bot_admin_auto)"
|
|
echo -e " ${CYAN}2${NC}) $(t bot_admin_manual)"
|
|
echo -ne " ${WHITE}$(t choose) [1]:${NC} "
|
|
read -r admin_mode
|
|
admin_mode="${admin_mode:-1}"
|
|
|
|
local admin_ids=""
|
|
if [ "$admin_mode" = "2" ]; then
|
|
echo -ne " ${WHITE}$(t bot_admin_ids_prompt)${NC} "
|
|
read -r admin_ids
|
|
admin_ids=$(echo "$admin_ids" | tr ' ' ',' | sed 's/,,*/,/g; s/^,//; s/,$//')
|
|
fi
|
|
|
|
# Propagate selected language to bot so UI matches
|
|
local bot_lang
|
|
bot_lang=$(get_language 2>/dev/null || echo en)
|
|
{
|
|
echo "BOT_TOKEN=$token"
|
|
[ -n "$admin_ids" ] && echo "ALLOWED_IDS=$admin_ids"
|
|
echo "BOT_LANG=$bot_lang"
|
|
} > "$BOT_DIR/.env"
|
|
chmod 600 "$BOT_DIR/.env"
|
|
log_success "$(t bot_env_created)"
|
|
else
|
|
log_info "$(t bot_env_exists)"
|
|
fi
|
|
|
|
# Systemd
|
|
cat > "/etc/systemd/system/${BOT_SERVICE}.service" << SVCEOF
|
|
[Unit]
|
|
Description=GoTelegram v${GOTELEGRAM_VERSION} Telegram Bot
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
WorkingDirectory=$BOT_DIR
|
|
ExecStart=$BOT_DIR/venv/bin/python $BOT_DIR/bot.py
|
|
Restart=always
|
|
RestartSec=5
|
|
Environment=PATH=$BOT_DIR/venv/bin:/usr/bin
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
SVCEOF
|
|
|
|
systemctl daemon-reload
|
|
systemctl enable "$BOT_SERVICE" &>/dev/null
|
|
systemctl restart "$BOT_SERVICE" 2>/dev/null || systemctl start "$BOT_SERVICE"
|
|
|
|
# If auto mode — wait until bot captures first admin
|
|
local has_ids
|
|
has_ids=$(grep "^ALLOWED_IDS=" "$BOT_DIR/.env" 2>/dev/null | cut -d= -f2)
|
|
if [ -z "$has_ids" ]; then
|
|
echo ""
|
|
echo -e " ${YELLOW}╔══════════════════════════════════════════════════════╗${NC}"
|
|
printf " ${YELLOW}║${NC} ${BOLD}%-52s${NC} ${YELLOW}║${NC}\n" "$(t bot_wait_admin_title)"
|
|
echo -e " ${YELLOW}║${NC} ${YELLOW}║${NC}"
|
|
printf " ${YELLOW}║${NC} %s ${CYAN}/start${NC}%*s${YELLOW}║${NC}\n" "$(t bot_wait_admin_msg1)" 0 ""
|
|
printf " ${YELLOW}║${NC} %-52s ${YELLOW}║${NC}\n" "$(t bot_wait_admin_msg2)"
|
|
echo -e " ${YELLOW}║${NC} ${YELLOW}║${NC}"
|
|
printf " ${YELLOW}║${NC} ${DIM}%-52s${NC} ${YELLOW}║${NC}\n" "$(t bot_wait_admin_skip)"
|
|
echo -e " ${YELLOW}╚══════════════════════════════════════════════════════╝${NC}"
|
|
echo ""
|
|
|
|
local frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
|
|
local i=0
|
|
local waited=0
|
|
local max_wait=300 # 5 min max
|
|
|
|
# Catch Ctrl+C to skip waiting without killing the script
|
|
local interrupted=0
|
|
trap 'interrupted=1' INT
|
|
|
|
while [ $waited -lt $max_wait ] && [ $interrupted -eq 0 ]; do
|
|
printf "\r ${CYAN}${frames[$i]}${NC} $(tf bot_wait_spinner "$waited") " >&2
|
|
i=$(( (i+1) % ${#frames[@]} ))
|
|
sleep 1
|
|
waited=$((waited + 1))
|
|
|
|
# Check if ALLOWED_IDS has appeared
|
|
has_ids=$(grep "^ALLOWED_IDS=" "$BOT_DIR/.env" 2>/dev/null | cut -d= -f2)
|
|
if [ -n "$has_ids" ]; then
|
|
break
|
|
fi
|
|
done
|
|
|
|
trap - INT
|
|
printf "\r\033[K" >&2 # clear spinner line
|
|
|
|
if [ -n "$has_ids" ]; then
|
|
echo ""
|
|
log_success "$(t bot_admin_assigned)"
|
|
echo -e " ${WHITE}ID:${NC} ${GREEN}${has_ids}${NC}"
|
|
elif [ $interrupted -eq 1 ]; then
|
|
echo ""
|
|
log_warning "$(t bot_wait_skipped)"
|
|
else
|
|
echo ""
|
|
log_warning "$(t bot_wait_timeout)"
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
log_success "$(t bot_installed)"
|
|
echo -e " ${DIM}systemctl status $BOT_SERVICE${NC}"
|
|
echo -e " ${DIM}journalctl -u $BOT_SERVICE -f${NC}"
|
|
}
|
|
|
|
bot_show_status() {
|
|
echo ""
|
|
echo -e " ${BOLD}${WHITE}$(t bot_status_title)${NC}"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
|
|
systemctl status "$BOT_SERVICE" --no-pager -l 2>/dev/null | head -15 | while IFS= read -r line; do
|
|
echo " $line"
|
|
done
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
|
|
|
|
if [ -f "$BOT_DIR/.env" ]; then
|
|
local has_token has_ids
|
|
has_token=$(grep -c "BOT_TOKEN=" "$BOT_DIR/.env" 2>/dev/null || echo 0)
|
|
has_ids=$(grep "ALLOWED_IDS=" "$BOT_DIR/.env" 2>/dev/null | cut -d= -f2)
|
|
if [ "${has_token:-0}" -gt 0 ]; then
|
|
echo -e " $(t bot_token) ${GREEN}✓ $(t bot_token_configured)${NC}"
|
|
fi
|
|
if [ -n "$has_ids" ]; then
|
|
echo -e " $(t bot_access_colon) $(tf bot_access_ids_fmt "$has_ids")"
|
|
else
|
|
echo -e " $(t bot_access_colon) ${YELLOW}$(t bot_access_open)${NC}"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
bot_show_logs() {
|
|
echo ""
|
|
echo -e " ${BOLD}${WHITE}$(t bot_logs_title)${NC}"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
|
|
journalctl -u "$BOT_SERVICE" --no-pager -n 30 2>/dev/null | while IFS= read -r line; do
|
|
echo " $line"
|
|
done
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
|
|
}
|
|
|
|
bot_edit_config() {
|
|
echo ""
|
|
echo -e " ${BOLD}${WHITE}$(t bot_settings_title)${NC}"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
|
|
|
|
if [ -f "$BOT_DIR/.env" ]; then
|
|
echo -e " ${DIM}$(t bot_current_env)${NC}"
|
|
while IFS= read -r line; do
|
|
# Mask token for security
|
|
if [[ "$line" == BOT_TOKEN=* ]]; then
|
|
local tok="${line#BOT_TOKEN=}"
|
|
echo -e " BOT_TOKEN=${tok:0:10}...${tok: -5}"
|
|
else
|
|
echo " $line"
|
|
fi
|
|
done < "$BOT_DIR/.env"
|
|
fi
|
|
|
|
echo ""
|
|
echo -e " ${CYAN}1${NC}) $(t bot_change_token)"
|
|
echo -e " ${CYAN}2${NC}) $(t bot_change_allowed)"
|
|
echo -e " ${CYAN}0${NC}) $(t back)"
|
|
echo -ne " ${WHITE}$(t choose):${NC} "
|
|
read -r ch
|
|
|
|
case "$ch" in
|
|
1)
|
|
echo -ne " ${WHITE}$(t bot_new_token)${NC} "
|
|
read -r new_token
|
|
new_token=$(echo "$new_token" | tr -d '[:space:]')
|
|
if [ -n "$new_token" ]; then
|
|
sed -i "s|^BOT_TOKEN=.*|BOT_TOKEN=$new_token|" "$BOT_DIR/.env"
|
|
systemctl restart "$BOT_SERVICE"
|
|
log_success "$(t bot_token_updated)"
|
|
else
|
|
log_error "$(t bot_token_empty_err)"
|
|
fi
|
|
;;
|
|
2)
|
|
echo -ne " ${WHITE}$(t bot_allowed_prompt)${NC} "
|
|
read -r new_ids
|
|
# Normalize: spaces and commas → commas, strip extras
|
|
new_ids=$(echo "$new_ids" | tr ' ' ',' | sed 's/,,*/,/g; s/^,//; s/,$//')
|
|
if grep -q "^ALLOWED_IDS=" "$BOT_DIR/.env" 2>/dev/null; then
|
|
if [ -n "$new_ids" ]; then
|
|
sed -i "s|^ALLOWED_IDS=.*|ALLOWED_IDS=$new_ids|" "$BOT_DIR/.env"
|
|
else
|
|
sed -i '/^ALLOWED_IDS=/d' "$BOT_DIR/.env"
|
|
fi
|
|
else
|
|
[ -n "$new_ids" ] && echo "ALLOWED_IDS=$new_ids" >> "$BOT_DIR/.env"
|
|
fi
|
|
systemctl restart "$BOT_SERVICE"
|
|
log_success "$(t bot_access_updated)"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
bot_remove() {
|
|
echo ""
|
|
log_warning "$(t bot_remove_warn)"
|
|
if ! confirm "$(t bot_remove_confirm)"; then
|
|
return
|
|
fi
|
|
|
|
systemctl stop "$BOT_SERVICE" 2>/dev/null
|
|
systemctl disable "$BOT_SERVICE" 2>/dev/null
|
|
rm -f "/etc/systemd/system/${BOT_SERVICE}.service"
|
|
systemctl daemon-reload
|
|
rm -rf "$BOT_DIR"
|
|
log_success "$(t bot_removed)"
|
|
}
|
|
|
|
# ── Promo ────────────────────────────────────────────────────────────────────
|
|
_promo_block() {
|
|
# Print a promo section without width-fragile box borders (i18n safe)
|
|
local line2; line2=$(printf '─%.0s' {1..54})
|
|
echo ""
|
|
echo -e " ${DIM}${line2}${NC}"
|
|
echo -e " ${BOLD}${YELLOW}$(t promo_host1_title)${NC}"
|
|
echo -e " $(t promo_link_label) ${CYAN}https://vk.cc/ct29NQ${NC}"
|
|
echo -e " ${WHITE}OFF60${NC} — $(tf promo_off60)"
|
|
echo -e " ${WHITE}antenka20${NC} — $(tf promo_ant20)"
|
|
echo -e " ${WHITE}antenka6${NC} — $(tf promo_ant6)"
|
|
echo -e " ${DIM}${line2}${NC}"
|
|
echo -e " ${BOLD}${YELLOW}$(t promo_host2_title)${NC}"
|
|
echo -e " $(t promo_link_label) ${CYAN}https://vk.cc/cUxAhj${NC}"
|
|
echo -e " ${WHITE}OFF60${NC} — $(tf promo_off60)"
|
|
echo -e " ${DIM}${line2}${NC}"
|
|
echo -e " ${BOLD}${YELLOW}$(t promo_tips_title)${NC}"
|
|
echo -e " ${CYAN}https://pay.cloudtips.ru/p/7410814f${NC}"
|
|
echo -e " ${DIM}${line2}${NC}"
|
|
echo ""
|
|
}
|
|
|
|
menu_promo() {
|
|
_promo_block
|
|
}
|
|
|
|
# ── Проверка: показывать ли промо (раз в сутки) ────────────────────────────
|
|
should_show_promo() {
|
|
local stamp_file="$GOTELEGRAM_DIR/.promo_last_shown"
|
|
if [ ! -f "$stamp_file" ]; then
|
|
return 0 # никогда не показывали
|
|
fi
|
|
local last_shown now diff
|
|
last_shown=$(cat "$stamp_file" 2>/dev/null || echo "0")
|
|
last_shown="${last_shown//[^0-9]/}"
|
|
last_shown="${last_shown:-0}"
|
|
now=$(date +%s)
|
|
diff=$(( now - last_shown ))
|
|
# 86400 = 24 часа
|
|
[ "$diff" -ge 86400 ]
|
|
}
|
|
|
|
mark_promo_shown() {
|
|
mkdir -p "$GOTELEGRAM_DIR"
|
|
date +%s > "$GOTELEGRAM_DIR/.promo_last_shown"
|
|
}
|
|
|
|
# ── Promo with QR + delay (on install + once per day) ───────────────────
|
|
show_promo_with_qr() {
|
|
_promo_block
|
|
|
|
# QR codes
|
|
if command -v qrencode &>/dev/null; then
|
|
echo -e " ${DIM}$(t promo_qr_host1)${NC}"
|
|
qrencode -t UTF8 -m 1 "https://vk.cc/ct29NQ" 2>/dev/null | while IFS= read -r qr_line; do
|
|
echo " $qr_line"
|
|
done
|
|
echo ""
|
|
echo -e " ${DIM}$(t promo_qr_host2)${NC}"
|
|
qrencode -t UTF8 -m 1 "https://vk.cc/cUxAhj" 2>/dev/null | while IFS= read -r qr_line; do
|
|
echo " $qr_line"
|
|
done
|
|
echo ""
|
|
echo -e " ${DIM}$(t promo_qr_tips)${NC}"
|
|
qrencode -t UTF8 -m 1 "https://pay.cloudtips.ru/p/7410814f" 2>/dev/null | while IFS= read -r qr_line; do
|
|
echo " $qr_line"
|
|
done
|
|
fi
|
|
|
|
mark_promo_shown
|
|
|
|
# 5-second countdown
|
|
for i in 5 4 3 2 1; do
|
|
echo -ne "\r ${DIM}$(tf promo_menu_in "$i")${NC} "
|
|
sleep 1
|
|
done
|
|
echo -ne "\r \r"
|
|
}
|
|
|
|
# ── First-run: pick language ─────────────────────────────────────────────────
|
|
first_run_language_picker() {
|
|
# Show picker only if language not yet saved
|
|
local marker="${GOTELEGRAM_DIR:-/opt/gotelegram}/.language"
|
|
local cfg_lang=""
|
|
if [ -f "$GOTELEGRAM_CONFIG" ] && command -v jq >/dev/null 2>&1; then
|
|
cfg_lang=$(jq -r '.language // empty' "$GOTELEGRAM_CONFIG" 2>/dev/null)
|
|
fi
|
|
if [ -f "$marker" ] || [ -n "$cfg_lang" ]; then
|
|
return 0
|
|
fi
|
|
|
|
local chosen
|
|
chosen=$(pick_language_interactive)
|
|
save_language "$chosen"
|
|
load_language "$chosen"
|
|
}
|
|
|
|
# ── Change language on demand ────────────────────────────────────────────────
|
|
menu_language() {
|
|
echo ""
|
|
echo -e " ${BOLD}${WHITE}$(t lang_change_prompt)${NC}"
|
|
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
|
|
echo -e " ${CYAN}1${NC}) English"
|
|
echo -e " ${CYAN}2${NC}) Русский"
|
|
echo -e " ${CYAN}0${NC}) $(t back)"
|
|
echo -ne " ${WHITE}$(t choose):${NC} "
|
|
read -r ch
|
|
case "$ch" in
|
|
1) save_language "en"; load_language "en"; log_success "$(tf lang_saved English)" ;;
|
|
2) save_language "ru"; load_language "ru"; log_success "$(tf lang_saved Русский)" ;;
|
|
esac
|
|
}
|
|
|
|
# ── Точка входа / Entry point ───────────────────────────────────────────────
|
|
main() {
|
|
check_root
|
|
init_dirs
|
|
|
|
# First-run language picker (before banner so banner appears in chosen lang)
|
|
first_run_language_picker
|
|
|
|
show_banner
|
|
|
|
# Pre-flight
|
|
check_os
|
|
check_disk_space 500
|
|
|
|
# Promo once per day
|
|
if should_show_promo; then
|
|
show_promo_with_qr
|
|
fi
|
|
|
|
while true; do
|
|
clear
|
|
show_main_menu
|
|
# Auto-refresh: 30 sec timeout
|
|
if read -t 30 -r choice; then
|
|
case "$choice" in
|
|
1) submenu_proxy ;;
|
|
2) submenu_stats ;;
|
|
3) submenu_manage ;;
|
|
4) menu_bot ;;
|
|
5) submenu_about ;;
|
|
0|q|exit) echo ""; log_info "$(t bye)"; exit 0 ;;
|
|
*) log_error "$(t invalid_choice)" ;;
|
|
esac
|
|
|
|
# Pause after submenu (except stats — it has its own loop)
|
|
if [ "$choice" != "2" ]; then
|
|
echo ""
|
|
echo -ne " ${DIM}$(t press_enter_to_return)${NC}"
|
|
read -r
|
|
fi
|
|
fi
|
|
# If read timed out, loop refreshes the dashboard
|
|
done
|
|
}
|
|
|
|
# ── Статистика (авто-обновление 1 сек, без мерцания) ───────────────────────
|
|
submenu_stats() {
|
|
# Инициализируем статистику при первом входе
|
|
if type stats_init &>/dev/null; then
|
|
stats_init 2>/dev/null
|
|
fi
|
|
|
|
local line2; line2=$(printf '─%.0s' {1..54})
|
|
local first_draw=1
|
|
|
|
# Скрываем курсор для плавного обновления
|
|
tput civis 2>/dev/null
|
|
|
|
# Восстанавливаем курсор при выходе из функции
|
|
trap 'tput cnorm 2>/dev/null; trap - RETURN' RETURN
|
|
|
|
while true; do
|
|
if [ "$first_draw" -eq 1 ]; then
|
|
clear
|
|
first_draw=0
|
|
else
|
|
# Перемещаем курсор в начало экрана вместо clear — нет мерцания
|
|
tput cup 0 0 2>/dev/null || printf '\033[H'
|
|
fi
|
|
|
|
# Draw the whole screen over the previous content
|
|
echo -e "\033[J" # erase from cursor to end (removes trails)
|
|
echo -e " ${BOLD}${WHITE}$(t stats_title)${NC}"
|
|
echo -e " ${DIM}${line2}${NC}"
|
|
|
|
if type show_traffic_stats &>/dev/null; then
|
|
show_traffic_stats
|
|
else
|
|
echo -e " ${DIM}$(t stats_module_missing)${NC}"
|
|
echo -e " ${DIM}$(t stats_file_missing)${NC}"
|
|
echo ""
|
|
fi
|
|
|
|
echo -e " ${DIM}${line2}${NC}"
|
|
local stats_on
|
|
stats_on=$(t stats_on)
|
|
if type toggle_stats &>/dev/null; then
|
|
local cfg_val
|
|
cfg_val=$(config_get stats_enabled 2>/dev/null || echo "true")
|
|
[ "$cfg_val" = "false" ] && stats_on=$(t stats_off)
|
|
fi
|
|
echo -e " ${CYAN}1${NC}) $(tf stats_toggle "$stats_on")"
|
|
echo -e " ${CYAN}2${NC}) $(t stats_install_collector)"
|
|
echo -e " ${CYAN}0${NC}) ${DIM}$(t back)${NC}"
|
|
echo -e " ${DIM}${line2}${NC}"
|
|
echo -e " ${DIM}$(t stats_auto_refresh)${NC}"
|
|
|
|
# Show cursor for input, then hide again
|
|
tput cnorm 2>/dev/null
|
|
echo -ne " ${WHITE}▸ ${NC}"
|
|
|
|
if read -t 3 -r ch; then
|
|
tput civis 2>/dev/null
|
|
case "$ch" in
|
|
1)
|
|
if type toggle_stats &>/dev/null; then
|
|
toggle_stats
|
|
echo -ne " ${DIM}$(t press_enter)${NC}"; read -r
|
|
first_draw=1 # full redraw after action
|
|
fi
|
|
;;
|
|
2)
|
|
if type install_stats_collector &>/dev/null; then
|
|
install_stats_collector
|
|
echo -ne " ${DIM}$(t press_enter)${NC}"; read -r
|
|
first_draw=1
|
|
fi
|
|
;;
|
|
0|"") return ;;
|
|
esac
|
|
fi
|
|
tput civis 2>/dev/null
|
|
done
|
|
}
|
|
|
|
main "$@"
|