#!/usr/bin/env bash # AWG Gus bot: install/update only this service; existing awg2 and Telegram bot stay in place. set -Eeuo pipefail umask 077 REPO_URL="${AWG_GUS_REPO_URL:-https://gitea.bvrdo.online/wenil/awg-gus-bot}" REF="${AWG_GUS_REF:-main}" BASE=/opt/awg-gus-bot CONF=/etc/awg-gus-bot.json STATE=/var/lib/awg-gus-bot SERVICE=awg-gus-bot.service SRC="" RECONFIGURE=0 die() { printf 'Ошибка: %s\n' "$*" >&2; exit 1; } usage() { cat <<'EOF' Установка/обновление отдельного AWG-бота для GusVoice: sudo bash install.sh sudo bash install.sh --src /path/to/awg-gus-bot sudo bash install.sh --configure Нужны: Linux с systemd, Python 3.10+, установленный awg2 с JSON API. При первой установке запросит HTTPS-адрес Гуся и токен, затем покажет код привязки. Существующая служба awg-bot не меняется. VPN-клиенты у обоих ботов общие. EOF } while (($#)); do case "$1" in --src) [[ $# -ge 2 ]] || die 'После --src нужен каталог'; SRC="$2"; shift 2 ;; --configure) RECONFIGURE=1; shift ;; -h|--help) usage; exit 0 ;; *) die "Неизвестный параметр: $1" ;; esac done [[ $EUID -eq 0 ]] || die 'Запустите через sudo bash install.sh' [[ $(uname -s) == Linux ]] || die 'Устанавливается на Linux-сервер с awg2' command -v systemctl >/dev/null || die 'Нужен systemd' [[ -d /run/systemd/system ]] || die 'systemd не запущен' command -v flock >/dev/null || die 'Нужен flock (пакет util-linux)' mkdir -p /run/lock exec 9>/run/lock/awg-gus-install.lock flock -n 9 || die 'Другой установщик уже работает' AWG2="$(command -v awg2 || true)" [[ -n "$AWG2" ]] || die 'Сначала установите AWG Toolza: команда awg2 не найдена' missing=() for pair in 'python3:python3' 'curl:curl' 'tar:tar' 'qrencode:qrencode'; do command -v "${pair%%:*}" >/dev/null || missing+=("${pair#*:}") done if ((${#missing[@]})); then command -v apt-get >/dev/null || die "Установите вручную: ${missing[*]} и ca-certificates" apt-get update -qq DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "${missing[@]}" ca-certificates fi PYTHON=$(command -v python3) python3 -c 'import sys; assert sys.version_info >= (3,10), "Нужен Python 3.10+"' "$AWG2" api version | python3 -c 'import sys,json; d=json.loads(sys.stdin.read().strip().splitlines()[-1]); sys.exit(0 if d.get("ok") is True else 1)' \ || die 'awg2 api version не отвечает. Обновите AWG Toolza штатным способом' TMP=$(mktemp -d /tmp/awg-gus-install.XXXXXXXX) old="" activated=0 service_stopped=0 configuring=0 was_active=0 systemctl is-active --quiet "$SERVICE" && was_active=1 cleanup() { local rc=$? trap - EXIT if ((rc != 0 && (activated || service_stopped || configuring))); then printf 'Запуск новой версии не удался. Откатываю только службу Гуся.\n' >&2 systemctl stop "$SERVICE" || true if ((activated)) && [[ -n "$old" ]]; then ln -sfn "$old" "$BASE/.current-rollback" mv -Tf "$BASE/.current-rollback" "$BASE/current" elif ((activated)); then rm -f "$BASE/current" fi if [[ -f "$TMP/config.before" ]]; then install -m 600 "$TMP/config.before" "$CONF" fi if [[ -f "$TMP/unit.before" ]]; then install -m 644 "$TMP/unit.before" "/etc/systemd/system/$SERVICE" elif ((activated)) && [[ -z "$old" ]]; then systemctl disable "$SERVICE" >/dev/null 2>&1 || true rm -f "/etc/systemd/system/$SERVICE" fi if [[ -f "$TMP/command.before" ]]; then install -m 755 "$TMP/command.before" /usr/local/bin/awg-gus-bot elif ((activated)) && [[ -z "$old" ]]; then rm -f /usr/local/bin/awg-gus-bot fi systemctl daemon-reload if ((was_active)) && [[ -n "$old" ]]; then systemctl start "$SERVICE" || true; fi fi [[ "$TMP" == /tmp/awg-gus-install.* && -d "$TMP" ]] && rm -rf -- "$TMP" exit "$rc" } trap cleanup EXIT if [[ -z "$SRC" ]]; then [[ "$REPO_URL" == https://* && "$REPO_URL" != *'@'* ]] || die 'Источник должен быть HTTPS без учётных данных в URL' [[ "$REF" =~ ^[A-Za-z0-9._-]+$ ]] || die 'Некорректная ветка/версия' printf 'Скачиваю AWG Gus bot из %s (%s)…\n' "$REPO_URL" "$REF" curl --proto '=https' --proto-redir '=https' --tlsv1.2 -fSL --retry 3 --connect-timeout 15 --max-time 180 \ "${REPO_URL%.git}/archive/$REF.tar.gz" -o "$TMP/source.tar.gz" mkdir "$TMP/source" # Reject paths escaping extraction root and any links/devices in the source archive. python3 - "$TMP/source.tar.gz" "$TMP/source" <<'PY' import pathlib, sys, tarfile root = pathlib.Path(sys.argv[2]).resolve() with tarfile.open(sys.argv[1]) as archive: for m in archive.getmembers(): p = (root / m.name).resolve() if not p.is_relative_to(root) or not (m.isfile() or m.isdir()): raise SystemExit('Небезопасный путь/тип в архиве') archive.extractall(root) PY dirs=("$TMP/source"/*) ((${#dirs[@]} == 1)) && [[ -d "${dirs[0]}" ]] || die 'Неожиданная структура архива' SRC="${dirs[0]}" fi SRC=$(cd "$SRC" && pwd -P) [[ -f "$SRC/run.py" && -f "$SRC/awg_gus/bot.py" && -f "$SRC/vendor/gusvoice_bot/client.py" && -f "$SRC/install.sh" ]] \ || die 'В источнике нет файлов AWG Gus bot' python3 -m compileall -q "$SRC/awg_gus" "$SRC/vendor/gusvoice_bot" "$SRC/run.py" mkdir -p "$BASE/releases" "$STATE" chmod 700 "$BASE" "$BASE/releases" "$STATE" release="$BASE/releases/$(date -u +%Y%m%dT%H%M%SZ)-$$" mkdir "$release" cp -a "$SRC/awg_gus" "$SRC/vendor" "$SRC/run.py" "$SRC/install.sh" "$SRC/awg-gus-bot" "$SRC/README.md" "$SRC/LICENSE" "$SRC/licenses" "$release/" chmod -R go-rwx "$release" printf '%s\n' "$REPO_URL" > "$release/source-url" printf '%s\n' "$REF" > "$release/source-ref" [[ ! -f "$CONF" ]] || cp -p "$CONF" "$TMP/config.before" [[ ! -f "/etc/systemd/system/$SERVICE" ]] || cp -p "/etc/systemd/system/$SERVICE" "$TMP/unit.before" [[ ! -f /usr/local/bin/awg-gus-bot ]] || cp -p /usr/local/bin/awg-gus-bot "$TMP/command.before" old=$(readlink "$BASE/current" || true) if [[ -n "$old" && "$old" != "$BASE/releases/"* ]]; then die 'current указывает вне каталога версий'; fi if [[ ! -s "$CONF" ]] || ((RECONFIGURE)); then [[ -t 0 ]] || die 'Для первого запуска нужен терминал: скачайте скрипт в файл, затем sudo bash install.sh' # Stop only our bot to release its polling connection while pairing. if ((was_active)); then systemctl stop "$SERVICE"; service_stopped=1; fi configuring=1 if ! python3 "$release/run.py" setup --config "$CONF" --awg2 "$AWG2" --state-dir "$STATE"; then die 'Настройка не завершена' fi fi chmod 600 "$CONF" python3 "$release/run.py" check --config "$CONF" activated=1 cat > "/etc/systemd/system/$SERVICE" </dev/null systemctl restart "$SERVICE" sleep 3 systemctl is-active --quiet "$SERVICE" || die 'Служба не запустилась. journalctl -u awg-gus-bot -n 30' activated=0 printf '\nГотово. В личной беседе с ботом Гуся отправьте /menu.\n' printf 'Служба: awg-gus-bot. Обновление: sudo awg-gus-bot update\n' printf 'Telegram-бот и awg2 не заменялись. Оба бота управляют общими VPN-клиентами.\n'