diff --git a/gotelegram-bot/bot.py b/gotelegram-bot/bot.py index 8c4ade7..999e49a 100644 --- a/gotelegram-bot/bot.py +++ b/gotelegram-bot/bot.py @@ -100,7 +100,7 @@ logger = logging.getLogger(__name__) # CONFIGURATION # ============================================================================ -GOTELEGRAM_VERSION = "2.4.2" +GOTELEGRAM_VERSION = "2.4.3" GOTELEGRAM_CONFIG = "/opt/gotelegram/config.json" TELEMT_CONFIG = "/etc/telemt/config.toml" TELEMT_SERVICE = "telemt" diff --git a/install.sh b/install.sh index fa1c0cf..4e0e8be 100755 --- a/install.sh +++ b/install.sh @@ -1323,8 +1323,45 @@ bot_action_change_lite_domain() { return 0 } -# Main dispatcher — called from main() when --action=X is present +# Main dispatcher — called from main() when --action=X is present. +# Uses a file lock (flock) so concurrent CLI invocations (from multiple bot +# users, or from bot + manual CLI) serialize cleanly. Without this, two +# parallel `change-lite-domain` calls raced on the jq-rewrite of config.json +# and one process would see a truncated file ("no secret in config"). bot_action_dispatch() { + local lock_file="/var/lock/gotelegram-bot-action.lock" + # Make sure /var/lock exists (it does on Debian/Ubuntu; be defensive for minimal images) + [ -d /var/lock ] || mkdir -p /var/lock 2>/dev/null || true + + if command -v flock >/dev/null 2>&1; then + # Wait up to 30 seconds for the lock — bot actions are fast (<5s + # typical), so 30s is plenty for legitimate serialization but short + # enough to surface a stuck process. + ( + flock -w 30 9 || { + # If we time out, emit JSON error for the bot parent. + local json_out=0 a + for a in "$@"; do + [ "$a" = "--json" ] && json_out=1 + done + if [ "$json_out" = "1" ]; then + bot_emit_json "error" "another action in progress (lock timeout)" "code=lock_timeout" + fi + exit 75 # EX_TEMPFAIL + } + _bot_action_dispatch_locked "$@" + ) 9>"$lock_file" + return $? + else + # No flock installed — run unlocked with a warning. ensure_deps/check_deps + # normally ensures util-linux is present, so this branch is defensive. + log_warning "flock not available — bot actions not serialized" + _bot_action_dispatch_locked "$@" + return $? + fi +} + +_bot_action_dispatch_locked() { local action="" tpl_id="" domain="" json_out=0 arg for arg in "$@"; do case "$arg" in diff --git a/lib/common.sh b/lib/common.sh old mode 100755 new mode 100644 index 4366676..536e1a8 --- a/lib/common.sh +++ b/lib/common.sh @@ -3,7 +3,7 @@ # Colors, logging, spinner, system helpers, v1 compat, i18n-aware # ── Version ─────────────────────────────────────────────────────────────────── -GOTELEGRAM_VERSION="2.4.2" +GOTELEGRAM_VERSION="2.4.3" GOTELEGRAM_NAME="GoTelegram" # ── Пути ────────────────────────────────────────────────────────────────────── @@ -277,6 +277,7 @@ apt_pkg_for_cmd() { host) echo "dnsutils" ;; ss) echo "iproute2" ;; netstat) echo "net-tools" ;; + flock) echo "util-linux" ;; *) echo "$1" ;; # команда == имя пакета esac } @@ -287,13 +288,17 @@ dnf_pkg_for_cmd() { xxd) echo "vim-common" ;; ss) echo "iproute" ;; netstat) echo "net-tools" ;; + flock) echo "util-linux" ;; *) echo "$1" ;; esac } ensure_deps() { - # Критические зависимости — без них скрипт не работает - local critical=(curl jq openssl git xxd tar dig) + # Критические зависимости — без них скрипт не работает. + # flock используется bot_action_dispatch для сериализации параллельных + # вызовов (иначе гонка на config.json при одновременных change-template / + # change-lite-domain из бота). + local critical=(curl jq openssl git xxd tar dig flock) # Желательные — есть fallback, устанавливать всё равно, но не падать если не смогли local optional=(qrencode bc) @@ -386,7 +391,7 @@ ensure_deps() { # main() чтобы не дёргать apt-get update при каждом запуске меню. check_deps_present() { local cmd - for cmd in curl jq openssl git xxd tar dig; do + for cmd in curl jq openssl git xxd tar dig flock; do command -v "$cmd" &>/dev/null || return 1 done return 0