feat(v2.4.2): bot non-interactive install.sh actions

- install.sh: new bot_action_dispatch entry point for --action=X --json CLI invocation from the bot/scripts
- install.sh: bot_action_change_template — reuses download_template + deploy_template_to_nginx, updates config.json template_id
- install.sh: bot_action_change_lite_domain — regenerates telemt TOML with new fake-TLS mask domain, restarts telemt
- bot.py: run_bot_action() subprocess wrapper parses JSON result
- bot.py: cb_pro_confirm now performs real change-template when already in pro mode (fresh install still routes to CLI)
- bot.py: cb_lite_domain now performs real change-lite-domain when already in lite mode
- version -> 2.4.2
This commit is contained in:
anten-ka
2026-04-10 13:19:26 +03:00
parent 7b53566dad
commit fc28a1a099
3 changed files with 555 additions and 53 deletions

View File

@@ -1136,11 +1136,257 @@ menu_language() {
esac
}
# ══════════════════════════════════════════════════════════════════════════════
# Non-interactive action dispatcher (bot / CI / scripting interface)
# ══════════════════════════════════════════════════════════════════════════════
# Usage examples:
# gotelegram --action=change-template --template=th_ariclaw --json
# gotelegram --action=change-lite-domain --domain=google.com --json
#
# Rules for action handlers:
# - Only JSON may be written to stdout (the caller parses it).
# - All human-oriented logging must go to stderr (log_* already do that).
# - Exit code 0 on success, non-zero on failure (caller still parses JSON).
# ══════════════════════════════════════════════════════════════════════════════
bot_emit_json() {
# bot_emit_json <status> <message> [key=value ...]
local status="$1"; shift
local message="$1"; shift
local extra="" kv k v
for kv in "$@"; do
k="${kv%%=*}"
v="${kv#*=}"
# escape backslashes and double quotes in value
v="${v//\\/\\\\}"
v="${v//\"/\\\"}"
extra="${extra},\"${k}\":\"${v}\""
done
# escape message
local msg_esc="${message//\\/\\\\}"
msg_esc="${msg_esc//\"/\\\"}"
printf '{"status":"%s","message":"%s"%s}\n' "$status" "$msg_esc" "$extra"
}
# Update a single key in config.json without rewriting the whole file
bot_update_config_field() {
local key="$1"
local value="$2"
if [ ! -f "$GOTELEGRAM_CONFIG" ]; then
return 1
fi
local tmp
tmp=$(mktemp) || return 1
if jq --arg k "$key" --arg v "$value" \
'.[$k] = $v | .updated_at = (now | todate)' \
"$GOTELEGRAM_CONFIG" > "$tmp" 2>/dev/null; then
mv "$tmp" "$GOTELEGRAM_CONFIG"
chmod 600 "$GOTELEGRAM_CONFIG"
return 0
fi
rm -f "$tmp"
return 1
}
# ── Action: change-template (pro mode only) ──────────────────────────────────
bot_action_change_template() {
local tpl_id="$1"
local json_out="${2:-0}"
if [ -z "$tpl_id" ]; then
[ "$json_out" = "1" ] && bot_emit_json "error" "template id is required" "code=missing_arg"
log_error "change-template: --template is required"
return 2
fi
# Must be in pro mode
local mode
mode=$(config_get mode 2>/dev/null || echo "")
if [ "$mode" != "pro" ]; then
[ "$json_out" = "1" ] && bot_emit_json "error" "change-template requires pro mode (current: ${mode:-none})" "code=wrong_mode"
log_error "change-template: current mode is '${mode:-none}', requires 'pro'"
return 3
fi
# Validate template id exists in catalog
if ! get_template_info "$tpl_id" >/dev/null 2>&1; then
[ "$json_out" = "1" ] && bot_emit_json "error" "unknown template: $tpl_id" "code=unknown_template"
log_error "change-template: template not found in catalog: $tpl_id"
return 4
fi
# Make sure git (and other deps) are present. download_template uses git
# clone under the hood — on a minimal host (bootstrap-only install) git may
# not be installed yet, and the clone would fail silently.
ensure_deps >&2
log_info "change-template: downloading $tpl_id..."
local template_dir
template_dir=$(download_template "$tpl_id")
if [ $? -ne 0 ] || [ -z "$template_dir" ] || [ ! -d "$template_dir" ] || [ ! -f "$template_dir/index.html" ]; then
[ "$json_out" = "1" ] && bot_emit_json "error" "download failed for $tpl_id" "code=download_failed"
log_error "change-template: download_template failed for $tpl_id"
return 5
fi
log_info "change-template: deploying to nginx..."
if ! deploy_template_to_nginx "$template_dir" >&2; then
[ "$json_out" = "1" ] && bot_emit_json "error" "deploy failed" "code=deploy_failed"
return 6
fi
# Reload nginx (no full restart needed for static files — but be safe)
systemctl reload nginx 2>/dev/null || systemctl restart nginx 2>/dev/null
# Update config.json template_id field
bot_update_config_field "template_id" "$tpl_id" || \
log_warning "change-template: could not update config.json template_id"
local domain
domain=$(config_get domain 2>/dev/null || echo "")
log_success "change-template: $tpl_id deployed"
if [ "$json_out" = "1" ]; then
bot_emit_json "success" "template changed to $tpl_id" \
"template=$tpl_id" "domain=$domain" "mode=pro"
fi
return 0
}
# ── Action: change-lite-domain ───────────────────────────────────────────────
# Regenerates telemt TOML with a new fake-TLS mask domain. Lite mode only.
bot_action_change_lite_domain() {
local new_domain="$1"
local json_out="${2:-0}"
if [ -z "$new_domain" ]; then
[ "$json_out" = "1" ] && bot_emit_json "error" "domain is required" "code=missing_arg"
log_error "change-lite-domain: --domain is required"
return 2
fi
if ! validate_domain "$new_domain" 2>/dev/null; then
[ "$json_out" = "1" ] && bot_emit_json "error" "invalid domain: $new_domain" "code=invalid_domain"
log_error "change-lite-domain: invalid domain: $new_domain"
return 3
fi
local mode
mode=$(config_get mode 2>/dev/null || echo "")
if [ "$mode" != "lite" ]; then
[ "$json_out" = "1" ] && bot_emit_json "error" "change-lite-domain requires lite mode (current: ${mode:-none})" "code=wrong_mode"
log_error "change-lite-domain: current mode is '${mode:-none}', requires 'lite'"
return 4
fi
local secret port
secret=$(get_config_value secret 2>/dev/null || echo "")
port=$(get_config_value port 2>/dev/null || echo "443")
if [ -z "$secret" ]; then
[ "$json_out" = "1" ] && bot_emit_json "error" "no secret in config" "code=no_secret"
log_error "change-lite-domain: no secret in config.json"
return 5
fi
log_info "change-lite-domain: regenerating telemt TOML..."
generate_telemt_toml "$secret" "$port" "lite" "$new_domain" "443" >&2 || {
[ "$json_out" = "1" ] && bot_emit_json "error" "config generation failed" "code=gen_failed"
return 6
}
validate_telemt_config >&2 || {
[ "$json_out" = "1" ] && bot_emit_json "error" "config validation failed" "code=validate_failed"
return 7
}
restart_telemt >&2 || {
[ "$json_out" = "1" ] && bot_emit_json "error" "telemt restart failed" "code=restart_failed"
return 8
}
# Update both domain and mask_host fields in config.json
bot_update_config_field "mask_host" "$new_domain" || \
log_warning "change-lite-domain: could not update mask_host"
bot_update_config_field "domain" "$new_domain" || \
log_warning "change-lite-domain: could not update domain"
log_success "change-lite-domain: switched to $new_domain"
if [ "$json_out" = "1" ]; then
bot_emit_json "success" "lite mask domain changed to $new_domain" \
"domain=$new_domain" "mode=lite" "port=$port"
fi
return 0
}
# Main dispatcher — called from main() when --action=X is present
bot_action_dispatch() {
local action="" tpl_id="" domain="" json_out=0 arg
for arg in "$@"; do
case "$arg" in
--action=*) action="${arg#--action=}" ;;
--template=*) tpl_id="${arg#--template=}" ;;
--domain=*) domain="${arg#--domain=}" ;;
--json) json_out=1 ;;
esac
done
case "$action" in
change-template)
bot_action_change_template "$tpl_id" "$json_out"
return $?
;;
change-lite-domain)
bot_action_change_lite_domain "$domain" "$json_out"
return $?
;;
"")
log_error "no --action specified"
return 64
;;
*)
[ "$json_out" = "1" ] && bot_emit_json "error" "unknown action: $action" "code=unknown_action"
log_error "unknown action: $action"
return 64
;;
esac
}
# ── Точка входа / Entry point ───────────────────────────────────────────────
main() {
# Non-interactive action mode: if --action=X is in args, dispatch and exit.
# Must run BEFORE interactive banner/menus so the bot gets clean JSON.
local a has_action=0
for a in "$@"; do
case "$a" in --action=*) has_action=1; break ;; esac
done
if [ "$has_action" = "1" ]; then
check_root
init_dirs
# Для bot-экшенов тоже нужны зависимости (git для change-template), но
# без шумного apt-get update если всё уже на месте.
if ! check_deps_present; then
ensure_deps >&2 || exit 1
fi
bot_action_dispatch "$@"
exit $?
fi
check_root
init_dirs
# Первый запуск: если критические зависимости отсутствуют — ставим их ДО
# того как пользователь дойдёт до меню. На последующих запусках это просто
# дёшево проверяет command -v по всем командам и ничего не делает.
if ! check_deps_present; then
log_step "Первый запуск: проверяю зависимости..."
ensure_deps || {
log_error "Не удалось установить зависимости. См. сообщения выше."
exit 1
}
fi
# First-run language picker (before banner so banner appears in chosen lang)
first_run_language_picker