#!/usr/bin/env bash # Установка / обновление / откат из клонированного репозитория: образы собираются локально. # ./update.sh — последний релизный тег (без -rc) # ./update.sh v0.2.0 — конкретная версия (можно без «v») # ./update.sh main — текущая ветка main (для тестов, не для продакшена) set -euo pipefail # git checkout может заменить этот файл прямо во время работы — выполняемся из временной копии. if [ -z "${PIPU_UPDATE_REEXEC:-}" ]; then tmp="$(mktemp)" cp "$0" "$tmp" PIPU_UPDATE_REEXEC=1 PIPU_DEPLOY_DIR="$(cd "$(dirname "$0")" && pwd)" exec bash "$tmp" "$@" fi trap 'rm -f "$0"' EXIT cd "$PIPU_DEPLOY_DIR" [ -f .env ] || { echo "нет .env — сначала sudo ./install.sh" >&2; exit 1; } # Ключ подписи токенов (M1): создаётся один раз, в том числе в .env, сделанном до его появления. if ! grep -q '^JWT_PRIVATE_KEY=.' .env; then sed -i '/^JWT_PRIVATE_KEY=$/d' .env echo "JWT_PRIVATE_KEY=$(head -c 32 /dev/urandom | base64)" >> .env echo "→ в .env добавлен JWT_PRIVATE_KEY" fi REF="${1:-}" git fetch --tags --prune --quiet origin if [ -z "$REF" ]; then REF="$(git tag -l 'v*' --sort=-v:refname | grep -v -- '-rc' | head -1)" [ -n "$REF" ] || { echo "в репозитории нет релизных тегов; укажите версию или main" >&2; exit 1; } elif [[ "$REF" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then REF="v$REF" fi # Откат на более старую версию: миграции БД назад не применяются, старый api может не понять новую схему. CURRENT="$(sed -n 's/^PIPU_VERSION=//p' .env)" TARGET="${REF#v}" core() { [[ "$1" =~ ^([0-9]+\.[0-9]+\.[0-9]+) ]] && echo "${BASH_REMATCH[1]}"; } cur_core="$(core "$CURRENT" || true)" tgt_core="$(core "$TARGET" || true)" # сравниваем только MAJOR.MINOR.PATCH: переход 0.2.0-rc.1 → 0.2.0 откатом не считается if [ -n "$cur_core" ] && [ -n "$tgt_core" ] && [ "$cur_core" != "$tgt_core" ] && [ "$(printf '%s\n%s\n' "$cur_core" "$tgt_core" | sort -V | head -1)" = "$tgt_core" ]; then echo "внимание: $CURRENT → $TARGET — это откат. Миграции БД не откатываются;" >&2 echo " сделайте резервную копию (docs/deployment.md) и проверьте, что старая версия совместима со схемой." >&2 if [ "${PIPU_ALLOW_DOWNGRADE:-}" != 1 ]; then echo " Чтобы продолжить: PIPU_ALLOW_DOWNGRADE=1 ./update.sh $REF" >&2 exit 1 fi fi echo "→ переключаемся на $REF" if git show-ref --verify --quiet "refs/remotes/origin/$REF"; then git checkout --quiet "$REF" git merge --ff-only --quiet "origin/$REF" else git checkout --quiet --detach "$REF" fi VERSION="$(git describe --tags --always --dirty)" VERSION="${VERSION#v}" sed -i "s|^PIPU_VERSION=.*|PIPU_VERSION=$VERSION|" .env echo "→ версия $VERSION, сборка образов (первая сборка — 5–15 минут)" docker compose build --pull docker compose up -d --remove-orphans docker image prune -f >/dev/null echo "→ ждём готовности api" for _ in $(seq 1 60); do # 127.0.0.1, а не localhost: в alpine localhost — сначала ::1, а nginx слушает только IPv4 if docker compose exec -T nginx wget -qO- http://127.0.0.1/readyz >/dev/null 2>&1; then echo "pipu $VERSION запущен" exit 0 fi sleep 2 done echo "api не стал готов за 2 минуты: docker compose logs api" >&2 exit 1