Files
awg-gus-bot/install.sh
T

192 lines
8.2 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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" <<EOF
[Unit]
Description=AWG Toolza bot for GusVoice
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=simple
User=root
WorkingDirectory=$BASE/current
ExecStart=$PYTHON $BASE/current/run.py run --config $CONF
Environment=PYTHONUNBUFFERED=1
UMask=0077
Restart=on-failure
RestartSec=10
TimeoutStopSec=20
KillMode=control-group
[Install]
WantedBy=multi-user.target
EOF
ln -sfn "$release" "$BASE/.current-next"
mv -Tf "$BASE/.current-next" "$BASE/current"
install -m 755 "$release/awg-gus-bot" /usr/local/bin/awg-gus-bot
systemctl daemon-reload
systemctl enable "$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'