mirror of
https://github.com/anten-ka/gotelegram_pro.git
synced 2026-06-15 21:52:57 +00:00
Compare commits
7 Commits
v2.4.3
...
8f249c35e5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f249c35e5 | ||
|
|
bb2502e1fc | ||
|
|
b10ea54ce9 | ||
|
|
2f3045bcc0 | ||
|
|
eb5175ccab | ||
|
|
3403975636 | ||
|
|
3919f201f5 |
2
bootstrap.sh
Normal file → Executable file
2
bootstrap.sh
Normal file → Executable file
@@ -6,7 +6,7 @@ set -euo pipefail
|
||||
|
||||
REPO="anten-ka/gotelegram_pro"
|
||||
BRANCH="${GOTELEGRAM_BRANCH:-test}"
|
||||
PAT="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"
|
||||
PAT="${GOTELEGRAM_PAT:-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}"
|
||||
INSTALL_DIR="/opt/gotelegram"
|
||||
# Use raw.githubusercontent.com (CDN) — faster and avoids Contents API caching
|
||||
# issues that occasionally return 404 for recently added files on non-default branches.
|
||||
|
||||
@@ -100,7 +100,7 @@ logger = logging.getLogger(__name__)
|
||||
# CONFIGURATION
|
||||
# ============================================================================
|
||||
|
||||
GOTELEGRAM_VERSION = "2.4.3"
|
||||
GOTELEGRAM_VERSION = "2.4.7"
|
||||
GOTELEGRAM_CONFIG = "/opt/gotelegram/config.json"
|
||||
TELEMT_CONFIG = "/etc/telemt/config.toml"
|
||||
TELEMT_SERVICE = "telemt"
|
||||
|
||||
122
install.sh
122
install.sh
@@ -751,11 +751,36 @@ menu_bot() {
|
||||
bot_install() {
|
||||
log_step "$(t bot_install_step)"
|
||||
|
||||
# Python
|
||||
if ! command -v python3 &>/dev/null; then
|
||||
# Python + venv + pip (always ensure — python3 can be present without venv/pip)
|
||||
local need_py=0
|
||||
command -v python3 &>/dev/null || need_py=1
|
||||
# python3-venv not having its own command; probe by trying 'python3 -m venv --help'
|
||||
if ! python3 -m venv --help &>/dev/null; then need_py=1; fi
|
||||
# pip check
|
||||
if ! python3 -m pip --version &>/dev/null; then need_py=1; fi
|
||||
|
||||
if [ "$need_py" = "1" ]; 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
|
||||
# Detect Python version for versioned venv package (Debian 12 / Ubuntu 24.04 need python3.12-venv)
|
||||
local py_ver=""
|
||||
if command -v python3 &>/dev/null; then
|
||||
py_ver=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null)
|
||||
fi
|
||||
|
||||
apt_update
|
||||
|
||||
# Build package list with versioned venv fallback
|
||||
local pkg_list=(python3 python3-venv python3-pip)
|
||||
[ -n "$py_ver" ] && pkg_list+=("python${py_ver}-venv")
|
||||
# python3-full optional
|
||||
if ! apt_install "${pkg_list[@]}" python3-full; then
|
||||
log_warning "python3-full unavailable, installing core packages only..."
|
||||
apt_install "${pkg_list[@]}" || {
|
||||
log_error "Failed to install Python packages. Run manually: apt install ${pkg_list[*]}"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
elif command -v dnf &>/dev/null; then
|
||||
dnf install -y -q python3 python3-pip
|
||||
elif command -v yum &>/dev/null; then
|
||||
@@ -782,17 +807,60 @@ bot_install() {
|
||||
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"
|
||||
# Templates catalog — skip if source and dest are the same file (symlink install case)
|
||||
if [ -f "$SCRIPT_DIR/templates_catalog.json" ]; then
|
||||
local src_tc="$SCRIPT_DIR/templates_catalog.json"
|
||||
local dst_tc="$GOTELEGRAM_DIR/templates_catalog.json"
|
||||
if [ "$(readlink -f "$src_tc" 2>/dev/null)" != "$(readlink -f "$dst_tc" 2>/dev/null)" ]; then
|
||||
cp "$src_tc" "$dst_tc"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Venv — create, and verify pip exists (python3-venv can silently create broken venv)
|
||||
if [ ! -d "$BOT_DIR/venv" ] || [ ! -x "$BOT_DIR/venv/bin/pip" ]; then
|
||||
log_info "$(t bot_create_venv)"
|
||||
rm -rf "$BOT_DIR/venv"
|
||||
if ! python3 -m venv "$BOT_DIR/venv" 2>/tmp/venv_err; then
|
||||
log_error "venv creation failed:"
|
||||
cat /tmp/venv_err >&2 2>/dev/null
|
||||
# Try to fix by installing versioned python3.X-venv package
|
||||
if command -v apt-get &>/dev/null; then
|
||||
local py_ver
|
||||
py_ver=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null)
|
||||
log_info "reinstalling python${py_ver}-venv..."
|
||||
apt_install python3-venv python3-pip "python${py_ver}-venv" python3-full || \
|
||||
apt_install python3-venv python3-pip "python${py_ver}-venv" || true
|
||||
rm -rf "$BOT_DIR/venv"
|
||||
python3 -m venv "$BOT_DIR/venv" || { log_error "venv still broken, aborting. Manual fix: apt install python${py_ver}-venv python3-pip"; return 1; }
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -x "$BOT_DIR/venv/bin/pip" ]; then
|
||||
log_info "bootstrapping pip via ensurepip..."
|
||||
"$BOT_DIR/venv/bin/python" -m ensurepip --upgrade 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ ! -x "$BOT_DIR/venv/bin/pip" ]; then
|
||||
log_error "pip missing in venv — install python3-venv manually: apt install python3-venv python3-pip"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "$(t bot_install_deps)"
|
||||
"$BOT_DIR/venv/bin/pip" install -r "$BOT_DIR/requirements.txt" -q
|
||||
if ! "$BOT_DIR/venv/bin/pip" install -r "$BOT_DIR/requirements.txt" -q 2>/tmp/pip_err; then
|
||||
log_error "pip install failed:"
|
||||
tail -n 5 /tmp/pip_err >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Sanity check: verify critical imports succeed
|
||||
if ! "$BOT_DIR/venv/bin/python" -c "import telegram, toml, dotenv" 2>/tmp/imp_err; then
|
||||
log_error "dependency import check failed:"
|
||||
cat /tmp/imp_err >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Configuration
|
||||
if [ ! -f "$BOT_DIR/.env" ]; then
|
||||
@@ -862,14 +930,13 @@ SVCEOF
|
||||
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}"
|
||||
# Simple bullet-style block (no box — printf %-Ns breaks on UTF-8 multibyte chars)
|
||||
echo -e " ${YELLOW}▸${NC} ${BOLD}$(t bot_wait_admin_title)${NC}"
|
||||
echo ""
|
||||
echo -e " $(t bot_wait_admin_msg1) ${CYAN}/start${NC}"
|
||||
echo -e " $(t bot_wait_admin_msg2)"
|
||||
echo ""
|
||||
echo -e " ${DIM}$(t bot_wait_admin_skip)${NC}"
|
||||
echo ""
|
||||
|
||||
local frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
|
||||
@@ -1071,21 +1138,14 @@ mark_promo_shown() {
|
||||
}
|
||||
|
||||
# ── Promo with QR + delay (on install + once per day) ───────────────────
|
||||
# QR показываем ТОЛЬКО для чаевых/донатов. Для хостеров оставлены только
|
||||
# текстовые ссылки и промокоды (см. _promo_block) — QR-коды хостеров
|
||||
# визуально конкурировали с чаевыми и перегружали экран.
|
||||
show_promo_with_qr() {
|
||||
_promo_block
|
||||
|
||||
# QR codes
|
||||
# QR только для чаевых
|
||||
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"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Colors, logging, spinner, system helpers, v1 compat, i18n-aware
|
||||
|
||||
# ── Version ───────────────────────────────────────────────────────────────────
|
||||
GOTELEGRAM_VERSION="2.4.3"
|
||||
GOTELEGRAM_VERSION="2.4.7"
|
||||
GOTELEGRAM_NAME="GoTelegram"
|
||||
|
||||
# ── Пути ──────────────────────────────────────────────────────────────────────
|
||||
@@ -240,13 +240,68 @@ get_pkg_manager() {
|
||||
install_pkg() {
|
||||
local pkg="$1"
|
||||
case "$(get_pkg_manager)" in
|
||||
apt) apt-get install -y -qq "$pkg" ;;
|
||||
apt) apt_install "$pkg" ;;
|
||||
dnf) dnf install -y -q "$pkg" ;;
|
||||
yum) yum install -y -q "$pkg" ;;
|
||||
*) log_error "$(_t_or err_bad_pkg_mgr 'Unknown package manager')"; return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ── apt lock wait + install ─────────────────────────────────────────────────
|
||||
# На свежих Ubuntu/Debian unattended-upgrades часто держит dpkg lock на старте
|
||||
# → любой apt-get install падает с "Could not get lock /var/lib/dpkg/lock-frontend".
|
||||
# Эти функции ждут освобождения лока до 300с, потом запускают apt с нативным
|
||||
# таймаутом DPkg::Lock::Timeout. Использовать везде, где раньше был
|
||||
# "apt-get install ...".
|
||||
apt_lock_wait() {
|
||||
local max_wait="${1:-300}"
|
||||
local waited=0
|
||||
local warned=0
|
||||
while fuser /var/lib/dpkg/lock-frontend &>/dev/null \
|
||||
|| fuser /var/lib/dpkg/lock &>/dev/null \
|
||||
|| fuser /var/lib/apt/lists/lock &>/dev/null \
|
||||
|| pgrep -f '^/usr/bin/unattended-upgrade' &>/dev/null; do
|
||||
if [ "$warned" = "0" ]; then
|
||||
log_warning "apt/dpkg locked by unattended-upgrades, waiting up to ${max_wait}s..."
|
||||
warned=1
|
||||
fi
|
||||
sleep 3
|
||||
waited=$((waited + 3))
|
||||
if [ "$waited" -ge "$max_wait" ]; then
|
||||
log_error "apt lock not released after ${max_wait}s"
|
||||
log_dim "Manual fix: systemctl stop unattended-upgrades && killall -9 unattended-upgr 2>/dev/null; dpkg --configure -a"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
[ "$warned" = "1" ] && log_success "apt lock released (waited ${waited}s)"
|
||||
return 0
|
||||
}
|
||||
|
||||
# apt_install <pkg> [pkg2 ...] — ждёт lock + ставит пакеты + показывает ошибку
|
||||
apt_install() {
|
||||
[ $# -eq 0 ] && return 0
|
||||
apt_lock_wait || return 1
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
local opts="-o DPkg::Lock::Timeout=120"
|
||||
local err_file; err_file=$(mktemp 2>/dev/null || echo /tmp/apt_err.$$)
|
||||
if ! apt-get $opts install -y -qq "$@" 2>"$err_file"; then
|
||||
log_error "apt-get install failed: $*"
|
||||
[ -s "$err_file" ] && tail -n 5 "$err_file" | sed 's/^/ /' >&2
|
||||
rm -f "$err_file"
|
||||
return 1
|
||||
fi
|
||||
rm -f "$err_file"
|
||||
return 0
|
||||
}
|
||||
|
||||
# apt_update — тихий update с ожиданием лока
|
||||
apt_update() {
|
||||
apt_lock_wait || return 1
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get -o DPkg::Lock::Timeout=120 update -qq 2>/dev/null || true
|
||||
return 0
|
||||
}
|
||||
|
||||
# ── Зависимости GoTelegram ──────────────────────────────────────────────────
|
||||
# Полный список внешних команд, которые скрипт использует. Для каждой команды
|
||||
# указан пакет на apt и dnf/yum (имена различаются: например dig = dnsutils на
|
||||
@@ -344,9 +399,8 @@ ensure_deps() {
|
||||
|
||||
case "$pkg_mgr" in
|
||||
apt)
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -qq 2>/dev/null
|
||||
apt-get install -y -qq "${uniq_pkgs[@]}" 2>/dev/null
|
||||
apt_update
|
||||
apt_install "${uniq_pkgs[@]}" || true
|
||||
;;
|
||||
dnf) dnf install -y -q "${uniq_pkgs[@]}" 2>/dev/null ;;
|
||||
yum) yum install -y -q "${uniq_pkgs[@]}" 2>/dev/null ;;
|
||||
@@ -358,7 +412,7 @@ ensure_deps() {
|
||||
|
||||
# Фолбэки для xxd: на некоторых системах нужен vim-common вместо xxd
|
||||
if ! command -v xxd &>/dev/null && [ "$pkg_mgr" = "apt" ]; then
|
||||
apt-get install -y -qq vim-common 2>/dev/null
|
||||
apt_install vim-common || true
|
||||
fi
|
||||
|
||||
# Повторная проверка критических команд
|
||||
|
||||
87
lib/telemt.sh
Executable file → Normal file
87
lib/telemt.sh
Executable file → Normal file
@@ -19,27 +19,55 @@ get_latest_telemt_version() {
|
||||
}
|
||||
|
||||
get_telemt_download_url() {
|
||||
local arch
|
||||
# 1) Сначала пробуем GitHub Releases API — он отдаёт точное имя ассета
|
||||
# последнего релиза (в т.ч. если в репо есть несколько архитектур,
|
||||
# pre-release и т.д.). Это наш предпочтительный путь.
|
||||
local resp url arch
|
||||
arch=$(get_arch)
|
||||
local resp
|
||||
resp=$(curl -s --max-time 10 "$TELEMT_RELEASE_API" 2>/dev/null)
|
||||
if [ -z "$resp" ]; then return 1; fi
|
||||
if [ -n "$resp" ]; then
|
||||
url=$(echo "$resp" | jq -r --arg a "$arch" '
|
||||
.assets[]?.browser_download_url
|
||||
| select(test("linux"))
|
||||
| select(
|
||||
($a == "amd64" and (test("x86_64|amd64"))) or
|
||||
($a == "arm64" and (test("aarch64|arm64"))) or
|
||||
($a == "armv7" and (test("armv7"))) or
|
||||
(test($a))
|
||||
)
|
||||
| select(test("gnu"))
|
||||
' 2>/dev/null | head -1)
|
||||
if [ -n "$url" ] && [ "$url" != "null" ]; then
|
||||
echo "$url"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# URL format: telemt-x86_64-linux-gnu.tar.gz (arch BEFORE linux)
|
||||
local arch_pattern
|
||||
# 2) Fallback: API не ответил / отдал 403 (rate limit на shared-IP VPS),
|
||||
# отдал пустой JSON, или jq не нашёл подходящий ассет.
|
||||
# Берём прямой "magic redirect" CDN-URL — он не считается в API rate
|
||||
# limit и всегда указывает на последний релиз.
|
||||
local arch_name
|
||||
case "$arch" in
|
||||
amd64) arch_pattern="(amd64|x86_64)" ;;
|
||||
arm64) arch_pattern="(arm64|aarch64)" ;;
|
||||
armv7) arch_pattern="(armv7|arm)" ;;
|
||||
*) arch_pattern="${arch}" ;;
|
||||
amd64) arch_name="x86_64" ;;
|
||||
arm64) arch_name="aarch64" ;;
|
||||
armv7) arch_name="armv7" ;;
|
||||
*) arch_name="$arch" ;;
|
||||
esac
|
||||
echo "https://github.com/${TELEMT_GITHUB}/releases/latest/download/telemt-${arch_name}-linux-gnu.tar.gz"
|
||||
}
|
||||
|
||||
echo "$resp" | jq -r ".assets[].browser_download_url" 2>/dev/null \
|
||||
| grep -iE "$arch_pattern" \
|
||||
| grep -i "linux" \
|
||||
| grep -v "sha256" \
|
||||
| grep "gnu" \
|
||||
| head -1
|
||||
# Fallback URL using musl libc (some minimal distros / older glibc)
|
||||
get_telemt_download_url_musl() {
|
||||
local arch arch_name
|
||||
arch=$(get_arch)
|
||||
case "$arch" in
|
||||
amd64) arch_name="x86_64" ;;
|
||||
arm64) arch_name="aarch64" ;;
|
||||
armv7) arch_name="armv7" ;;
|
||||
*) arch_name="$arch" ;;
|
||||
esac
|
||||
echo "https://github.com/${TELEMT_GITHUB}/releases/latest/download/telemt-${arch_name}-linux-musl.tar.gz"
|
||||
}
|
||||
|
||||
# ── Установленная версия ─────────────────────────────────────────────────────
|
||||
@@ -69,18 +97,35 @@ download_telemt() {
|
||||
log_info "Скачивание: $url"
|
||||
|
||||
if ! curl -L -s --max-time 120 -o "$tmp_file" "$url"; then
|
||||
log_error "Ошибка скачивания telemt"
|
||||
rm -f "$tmp_file"
|
||||
return 1
|
||||
log_warning "Не удалось скачать gnu-сборку, пробую musl..."
|
||||
url=$(get_telemt_download_url_musl)
|
||||
log_info "Скачивание: $url"
|
||||
if ! curl -L -s --max-time 120 -o "$tmp_file" "$url"; then
|
||||
log_error "Ошибка скачивания telemt (gnu и musl)"
|
||||
rm -f "$tmp_file"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Проверяем что файл не пустой и не HTML
|
||||
local file_size
|
||||
file_size=$(stat -c%s "$tmp_file" 2>/dev/null || echo 0)
|
||||
if [ "$file_size" -lt 1000 ]; then
|
||||
log_error "Скачанный файл слишком маленький ($file_size байт) — возможна ошибка сети"
|
||||
rm -f "$tmp_file"
|
||||
return 1
|
||||
# Try musl as fallback if gnu came back empty/error-html
|
||||
log_warning "Файл подозрительно мал ($file_size байт), пробую musl-сборку..."
|
||||
url=$(get_telemt_download_url_musl)
|
||||
log_info "Скачивание: $url"
|
||||
if ! curl -L -s --max-time 120 -o "$tmp_file" "$url"; then
|
||||
log_error "Ошибка скачивания telemt (musl fallback)"
|
||||
rm -f "$tmp_file"
|
||||
return 1
|
||||
fi
|
||||
file_size=$(stat -c%s "$tmp_file" 2>/dev/null || echo 0)
|
||||
if [ "$file_size" -lt 1000 ]; then
|
||||
log_error "Скачанный файл слишком маленький ($file_size байт) — возможна ошибка сети"
|
||||
rm -f "$tmp_file"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Определяем тип файла и распаковываем
|
||||
|
||||
@@ -9,9 +9,9 @@ install_nginx() {
|
||||
fi
|
||||
log_info "Установка nginx..."
|
||||
case "$(get_pkg_manager)" in
|
||||
apt) apt-get update -qq && apt-get install -y -qq nginx ;;
|
||||
dnf) dnf install -y -q nginx ;;
|
||||
yum) yum install -y -q nginx ;;
|
||||
apt) apt_update && apt_install nginx || return 1 ;;
|
||||
dnf) dnf install -y -q nginx || return 1 ;;
|
||||
yum) yum install -y -q nginx || return 1 ;;
|
||||
esac
|
||||
systemctl enable nginx 2>/dev/null
|
||||
}
|
||||
@@ -24,9 +24,9 @@ install_certbot() {
|
||||
fi
|
||||
log_info "Установка certbot..."
|
||||
case "$(get_pkg_manager)" in
|
||||
apt) apt-get install -y -qq certbot python3-certbot-nginx ;;
|
||||
dnf) dnf install -y -q certbot python3-certbot-nginx ;;
|
||||
yum) yum install -y -q certbot python3-certbot-nginx ;;
|
||||
apt) apt_install certbot python3-certbot-nginx || return 1 ;;
|
||||
dnf) dnf install -y -q certbot python3-certbot-nginx || return 1 ;;
|
||||
yum) yum install -y -q certbot python3-certbot-nginx || return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user