v2.4.9: UBF v2.0 backup + manual secret recovery

- lib/backup.sh: complete rewrite for Unified Backup Format v2.0
  * metadata.json with backup_id (GT-YYMMDD-<last6hex>) and SHA-256 fingerprint
  * secrets.json with raw_secret, faketls_secret, proxy_link, bot_token
  * structured dirs: telemt/, gotelegram/, nginx/, letsencrypt/, site/, bot/
  * auto-detect and auto-migrate v1.1 -> v2.0 on restore
  * parse_manual_secret: accepts tg://proxy URL, ee-prefix, or raw 32-hex
  * manual_secret_input: interactive entry with env var export
- install.sh: new 3-option menu_install (new / restore / existing key)
  * install_lite_mode + install_pro_mode respect GOTELEGRAM_EXISTING_* env vars
- lib/lang/ru.sh + en.sh: v2.4.9 i18n strings (backup_*, manual_secret_*, install_menu_*)
- lib/common.sh + gotelegram-bot/bot.py: version bump to 2.4.9
This commit is contained in:
anten-ka
2026-04-12 00:07:03 +03:00
parent 9d9d12e150
commit 0e38c2b5b6
6 changed files with 605 additions and 119 deletions

View File

@@ -258,6 +258,49 @@ menu_install() {
fi
fi
# Always start from a clean slate — any leftover env vars from a previous
# manual-key entry must not leak into a "new install" flow.
unset GOTELEGRAM_EXISTING_SECRET GOTELEGRAM_EXISTING_DOMAIN GOTELEGRAM_EXISTING_PORT
# ── Step 1: install source picker ────────────────────────────────────────
echo ""
echo -e " ${BOLD}${WHITE}$(_t_or install_source_title 'Источник установки')${NC}"
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
echo -e " ${CYAN}1)${NC} ${GREEN}$(_t_or install_menu_new 'Новая установка')${NC}"
echo -e " ${DIM}$(_t_or install_menu_new_desc 'Сгенерировать новый ключ и настроить с нуля')${NC}"
echo ""
echo -e " ${CYAN}2)${NC} ${BLUE}$(_t_or install_menu_restore 'Восстановить из бекапа')${NC}"
echo -e " ${DIM}$(_t_or install_menu_restore_desc 'Полное восстановление из файла .tar.gz[.enc]')${NC}"
echo ""
echo -e " ${CYAN}3)${NC} ${YELLOW}$(_t_or install_menu_existing_key 'Использовать существующий ключ')${NC}"
echo -e " ${DIM}$(_t_or install_menu_existing_key_desc 'Ввести ссылку tg://proxy или ключ вручную')${NC}"
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
echo -ne " ${WHITE}$(_t_or install_source_choice 'Выберите источник')${NC} "
read -r src_choice
src_choice="${src_choice:-}"
case "$src_choice" in
1) : ;; # fall through to mode picker
2)
if type interactive_restore &>/dev/null; then
interactive_restore
else
log_error "backup.sh not loaded"
fi
return
;;
3)
if type manual_secret_input &>/dev/null; then
manual_secret_input || return
else
log_error "backup.sh not loaded"
return
fi
;;
*) log_error "$(tf install_bad_choice "${src_choice:-<empty>}")" ; return ;;
esac
# ── Step 2: lite/pro mode picker ─────────────────────────────────────────
echo ""
echo -e " ${BOLD}${WHITE}$(t install_select_mode)${NC}"
echo -e " ${DIM}$(printf '─%.0s' {1..55})${NC}"
@@ -274,11 +317,19 @@ menu_install() {
read -r mode_choice
mode_choice="${mode_choice:-}"
# If user provided an ee-prefixed key with a domain, hint at pro mode
if [ -n "${GOTELEGRAM_EXISTING_DOMAIN:-}" ] && [ "$mode_choice" = "1" ]; then
log_warning "$(_t_or install_hint_pro_mode 'Ключ содержит домен — обычно это Pro режим')"
fi
case "$mode_choice" in
1) install_lite_mode ;;
2) install_pro_mode ;;
*) log_error "$(tf install_bad_choice "${mode_choice:-<empty>}")" ;;
esac
# Clean up env vars after install, just in case
unset GOTELEGRAM_EXISTING_SECRET GOTELEGRAM_EXISTING_DOMAIN GOTELEGRAM_EXISTING_PORT
}
# ── Lite mode ───────────────────────────────────────────────────────────────
@@ -290,10 +341,15 @@ install_lite_mode() {
domain=$(select_quick_domain)
[ $? -ne 0 ] && return
# Port selection
# Port selection — if user provided a port via existing-key flow, reuse it
local port
port=$(select_port)
[ $? -ne 0 ] && return
if [ -n "${GOTELEGRAM_EXISTING_PORT:-}" ] && [[ "$GOTELEGRAM_EXISTING_PORT" =~ ^[0-9]+$ ]]; then
port="$GOTELEGRAM_EXISTING_PORT"
log_info "$(_t_or install_reuse_port 'Используется порт из ключа'): ${port}"
else
port=$(select_port)
[ $? -ne 0 ] && return
fi
# Preflight: port conflict check (checks the external port only for lite)
if ! preflight_check "lite" "$port"; then
@@ -301,9 +357,14 @@ install_lite_mode() {
return
fi
# Generate secret
# Secret: reuse if provided via manual_secret_input, otherwise generate new
local secret
secret=$(generate_hex 32)
if [ -n "${GOTELEGRAM_EXISTING_SECRET:-}" ]; then
secret="$GOTELEGRAM_EXISTING_SECRET"
log_info "$(_t_or install_reuse_secret 'Используется переданный ключ'): ${secret:0:8}...${secret: -4}"
else
secret=$(generate_hex 32)
fi
# Confirm
local ip
@@ -354,10 +415,16 @@ install_pro_mode() {
return
fi
# Enter domain
echo ""
echo -ne " ${WHITE}$(t install_enter_domain)${NC} "
read -r user_domain
# Enter domain — if provided via existing-key flow, reuse it
local user_domain=""
if [ -n "${GOTELEGRAM_EXISTING_DOMAIN:-}" ]; then
user_domain="$GOTELEGRAM_EXISTING_DOMAIN"
log_info "$(_t_or install_reuse_domain 'Используется домен из ключа'): ${user_domain}"
else
echo ""
echo -ne " ${WHITE}$(t install_enter_domain)${NC} "
read -r user_domain
fi
if [ -z "$user_domain" ] || ! validate_domain "$user_domain"; then
log_error "$(tf install_bad_domain "${user_domain:-<empty>}")"
@@ -399,8 +466,14 @@ install_pro_mode() {
# Generate fake-TLS secret (ee + secret + hex domain)
# ee prefix tells Telegram client to masquerade traffic as TLS to domain
# Reuse existing secret if manual_secret_input provided it
local raw_secret
raw_secret=$(generate_hex 32)
if [ -n "${GOTELEGRAM_EXISTING_SECRET:-}" ]; then
raw_secret="$GOTELEGRAM_EXISTING_SECRET"
log_info "$(_t_or install_reuse_secret 'Используется переданный ключ'): ${raw_secret:0:8}...${raw_secret: -4}"
else
raw_secret=$(generate_hex 32)
fi
local domain_hex
domain_hex=$(printf '%s' "$user_domain" | xxd -p | tr -d '\n')
local faketls_secret="ee${raw_secret}${domain_hex}"