#!/usr/bin/env bash
# Развёртывание и обновление STT-сервиса (GigaAM на CPU) с хранилища.
#
# Первый запуск на устройстве:
#   wget https://www.store.neuro-city.ru/speach-to-text/deploy.sh
#   sudo bash deploy.sh --token SECRET [--port 4286] [--url <другое хранилище>]
#
# После первой установки скрипт доступен как stt-deploy, адрес хранилища
# сохранён в /etc/stt-deploy.conf:
#   sudo stt-deploy                   поставить версию из LATEST
#   sudo stt-deploy --version 1.3.0   поставить конкретную версию
#   sudo stt-deploy --check           показать, что будет сделано, ничего не меняя
#   sudo stt-deploy --status          текущая версия, состояние, health
#   sudo stt-deploy --rollback        вернуть предыдущую версию
#
# Сначала качается маленький пакет обновления кода. Полный пакет (Python-пакеты,
# модели) качается, только если сервиса ещё нет или изменились зависимости.
#
# Раскладка на хранилище:
#   <url>/LATEST                                        версия для установки
#   <url>/deploy.sh                                     этот скрипт
#   <url>/<ver>/stt-cpu-{app,full}-<ver>.tar.gz(.sha256)
set -euo pipefail

PREFIX="${STT_PREFIX:-/opt/stt}"
CONF="/etc/stt-deploy.conf"
DEFAULT_URL="https://www.store.neuro-city.ru/speach-to-text"
SELF_BIN="/usr/local/sbin/stt-deploy"
# Полный пакет: архив + распакованные файлы + новое Python-окружение.
MIN_FREE_MB="${STT_MIN_FREE_MB:-6000}"

log()  { echo "==> $*"; }
warn() { echo "    ПРЕДУПРЕЖДЕНИЕ: $*"; }
die()  { echo "ОШИБКА: $*" >&2; exit 1; }

STT_STORE_URL=""
# shellcheck disable=SC1090
[ -f "$CONF" ] && . "$CONF"
URL="${STT_STORE_URL:-$DEFAULT_URL}"

VERSION=""
MODE="deploy"
FORCE=0
INSTALL_ARGS=()

while [ $# -gt 0 ]; do
    case "$1" in
        --url) URL="$2"; shift 2 ;;
        --version) VERSION="$2"; shift 2 ;;
        --port|--host|--token) INSTALL_ARGS+=("$1" "$2"); shift 2 ;;
        --check) MODE="check"; shift ;;
        --force) FORCE=1; shift ;;
        --status|--rollback) MODE="${1#--}"; shift ;;
        -h|--help) sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
        *) die "неизвестный параметр: $1 (см. --help)" ;;
    esac
done

[ "$(id -u)" -eq 0 ] || die "запустите через sudo"

# --status / --rollback выполняет install.sh, сохранённый при последней установке.
if [ "$MODE" = "status" ] || [ "$MODE" = "rollback" ]; then
    [ -x "$PREFIX/install.sh" ] || die "сервис не установлен через deploy.sh (нет $PREFIX/install.sh)"
    exec "$PREFIX/install.sh" "--$MODE"
fi

URL="${URL%/}"
for cmd in wget sha256sum tar flock systemctl; do
    command -v "$cmd" >/dev/null 2>&1 || die "нет команды $cmd"
done

mkdir -p "$PREFIX/logs"
exec 9>"$PREFIX/.deploy.lock"
flock -n 9 || die "уже идёт другое развёртывание"

# Качаем рядом с /opt/stt, а не в /tmp: полный пакет весит гигабайты, /tmp бывает tmpfs.
WORK="$(mktemp -d "$PREFIX/.deploy.XXXXXX")"
trap 'rm -rf "$WORK"' EXIT

fetch() {  # fetch <путь на хранилище> <куда>
    wget -q --show-progress --tries=3 --waitretry=5 --timeout=60 -O "$2" "$URL/$1" \
        || die "не удалось скачать $URL/$1"
}

save_conf() {
    (
        umask 077
        printf 'STT_STORE_URL=%q\n' "$URL" > "$CONF.tmp"
    )
    mv -f "$CONF.tmp" "$CONF"
}

# Ставит stt-deploy: из пакета, а если там его нет — запущенный файл.
# Через rename, чтобы не перезаписать файл, который bash сейчас исполняет.
install_self() {
    local src="$1"
    [ -f "$src" ] || src="${BASH_SOURCE[0]}"
    [ -f "$src" ] || { warn "не удалось поставить $SELF_BIN"; return 0; }
    [ "$(readlink -f "$src")" = "$SELF_BIN" ] && return 0
    install -m 755 "$src" "$SELF_BIN.tmp"
    mv -f "$SELF_BIN.tmp" "$SELF_BIN"
}

# Скачивает, проверяет и распаковывает пакет; путь к нему — в PACK.
get_pack() {
    local name="stt-cpu-$1-$VERSION"
    log "Скачиваю $name.tar.gz"
    fetch "$VERSION/$name.tar.gz.sha256" "$WORK/$name.tar.gz.sha256"
    fetch "$VERSION/$name.tar.gz" "$WORK/$name.tar.gz"
    (cd "$WORK" && sha256sum -c --quiet "$name.tar.gz.sha256") \
        || die "контрольная сумма $name.tar.gz не совпадает"
    tar -C "$WORK" -xzf "$WORK/$name.tar.gz"
    rm -f "$WORK/$name.tar.gz"
    [ -x "$WORK/$name/install.sh" ] || die "в $name нет install.sh"
    PACK="$WORK/$name"
}

# Почему пакета обновления кода не хватит (пусто — хватит).
# Хэш окружения считается так же, как venv_for() в install.sh.
full_pack_reason() {
    local hash
    [ -L "$PREFIX/app" ] || { echo "сервис ещё не установлен"; return; }
    hash="$(sha256sum "$1/app/requirements.lock" | cut -c1-12)"
    [ -x "$PREFIX/venvs/$hash/bin/python" ] || { echo "изменились Python-зависимости"; return; }
    if ! ls "$PREFIX"/models/gigaam/*.ckpt >/dev/null 2>&1 \
            || [ ! -f "$PREFIX/models/silero_vad_v4.onnx" ]; then
        echo "нет моделей"; return
    fi
    command -v ffmpeg >/dev/null 2>&1 || echo "нет ffmpeg"
}

# ------------------------------------------------------------- версия
if [ -z "$VERSION" ]; then
    fetch LATEST "$WORK/LATEST"
    VERSION="$(tr -d ' \t\r\n' < "$WORK/LATEST")"
    [ -n "$VERSION" ] || die "файл $URL/LATEST пустой"
fi
CURRENT=""
[ -L "$PREFIX/app" ] && CURRENT="$(basename "$(readlink -f "$PREFIX/app")")"
log "Хранилище: $URL"
log "Версия для установки: $VERSION, установлена: ${CURRENT:-нет}"

if [ "$CURRENT" = "$VERSION" ] && [ "$FORCE" -eq 0 ] && [ ${#INSTALL_ARGS[@]} -eq 0 ]; then
    if systemctl is-active --quiet stt; then
        log "Эта версия уже установлена и работает"
        [ "$MODE" = "check" ] || save_conf
        exit 0
    fi
    warn "версия та же, но служба stt не работает — переустанавливаю"
fi

# ------------------------------------------------------------- пакет
get_pack app
REASON="$(full_pack_reason "$PACK")"
if [ -n "$REASON" ]; then
    log "Нужен полный пакет: $REASON"
    if [ "$MODE" = "check" ]; then exit 0; fi
    FREE_MB="$(df -Pm "$PREFIX" | awk 'NR==2 {print $4}')"
    [ "$FREE_MB" -ge "$MIN_FREE_MB" ] \
        || die "мало места в $PREFIX: ${FREE_MB} МБ, нужно не меньше ${MIN_FREE_MB} МБ"
    rm -rf "$PACK"
    get_pack full
else
    log "Хватит пакета обновления кода"
    if [ "$MODE" = "check" ]; then exit 0; fi
fi

# ------------------------------------------------------------- установка
"$PACK/install.sh" "${INSTALL_ARGS[@]}" || die "установка $VERSION не удалась (см. вывод выше)"

# Для --status / --rollback без пакета на руках.
install -m 755 "$PACK/install.sh" "$PREFIX/install.sh"
install_self "$PACK/deploy.sh"
save_conf
log "Развёрнуто: $VERSION. Дальше обновлять командой: sudo stt-deploy"
