Files
pipu-music/deploy/update.sh
T
mrleo1nid a6c9fd8a00
ci / api / api (push) Successful in 50s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m0s
ci / db / db (push) Successful in 58s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m27s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m39s
ci / ts / ts (packages + web) (push) Successful in 2m40s
fix(ui): reflect update availability and config changes; expand mobile player metadata
2026-10-23 00:20:59 +03:00

171 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
# Установка / обновление / откат из клонированного репозитория: образы собираются локально.
# ./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"
# Share a lock with manual deployments and config application.
if [ ! -f .management.lock ]; then (umask 022; : > .management.lock); fi
exec 9<.management.lock
flock -n 9 || { echo 'Another deployment is running' >&2; exit 1; }
touch config.local
chmod 600 config.local
compose() { docker compose --env-file config.local --env-file .env "$@"; }
[ -f .env ] || { echo "нет .env — сначала sudo ./install.sh" >&2; exit 1; }
# Ключи подписи (токены — M1, ссылки на обложки и аудио — M2): создаются один раз,
# в том числе в .env, сделанном до их появления.
for key in JWT_PRIVATE_KEY STREAM_SIGNING_KEY; do
if ! grep -q "^$key=." .env; then
sed -i "/^$key=\$/d" .env
echo "$key=$(head -c 32 /dev/urandom | base64)" >> .env
echo "→ в .env добавлен $key"
fi
done
# Самообновление Android: адрес Gitea и репозиторий — из origin этого клона, если в .env пусто
# или осталась заглушка из .env.example (https://host/owner/repo, git@host:owner/repo, ssh://git@host:port/owner/repo).
fill_gitea() {
local url repo origin host path
url="$(sed -n 's/^GITEA_URL=//p' .env)"
repo="$(sed -n 's/^GITEA_REPO=//p' .env)"
case "$url" in "" | *example.*) url="" ;; esac
case "$repo" in "" | owner/*) repo="" ;; esac
[ -n "$url" ] && [ -n "$repo" ] && return 0
origin="$(git -c safe.directory='*' remote get-url origin 2>/dev/null || true)"
[ -n "$origin" ] || return 0
case "$origin" in
http://* | https://*)
host="$(printf '%s' "$origin" | sed -E 's#^(https?://)([^@/]*@)?([^/]+)/.*#\1\3#')"
path="$(printf '%s' "$origin" | sed -E 's#^https?://[^/]+/##')" ;;
ssh://*)
host="https://$(printf '%s' "$origin" | sed -E 's#^ssh://([^@/]*@)?([^:/]+).*#\2#')"
path="$(printf '%s' "$origin" | sed -E 's#^ssh://[^/]+/##')" ;;
*:*)
host="https://$(printf '%s' "$origin" | sed -E 's#^([^@]*@)?([^:]+):.*#\2#')"
path="${origin#*:}" ;;
*) return 0 ;;
esac
path="${path%.git}"
[ -n "$url" ] || url="$host"
[ -n "$repo" ] || repo="$path"
for kv in "GITEA_URL=$url" "GITEA_REPO=$repo"; do
if grep -q "^${kv%%=*}=" .env; then sed -i "s|^${kv%%=*}=.*|$kv|" .env; else echo "$kv" >> .env; fi
done
echo "→ самообновление Android: $url, $repo (из git remote; поменять — в .env)"
}
fill_gitea
# Папка с музыкой пуста — почти всегда это несмонтированный диск: api отдавал бы ошибки воспроизведения.
LIBRARY_HOST="$(sed -n 's/^LIBRARY_HOST=//p' .env)"
if [ -n "$LIBRARY_HOST" ] && [ -z "$(ls -A "$LIBRARY_HOST" 2>/dev/null)" ] && [ "${PIPU_ALLOW_EMPTY_LIBRARY:-}" != 1 ]; then
echo "LIBRARY_HOST=$LIBRARY_HOST пуст или не существует — диск с музыкой не смонтирован?" >&2
echo " проверьте: findmnt $LIBRARY_HOST; смонтируйте (sudo mount -a) и повторите." >&2
echo " Если папка и должна быть пустой: PIPU_ALLOW_EMPTY_LIBRARY=1 ./update.sh ${1:-}" >&2
exit 1
fi
REF="${1:-}"
if [ -n "${PIPU_UPDATE_SOURCE:-}" ]; then
[[ "${PIPU_UPDATE_COMMIT:-}" =~ ^[a-f0-9]{40,64}$ ]] || { echo 'Invalid commit' >&2; exit 1; }
[[ "${PIPU_UPDATE_REF:-}" =~ ^refs/(heads/main|tags/v?[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?)$ ]] || { echo 'Invalid ref' >&2; exit 1; }
git -c protocol.file.allow=never -c protocol.ext.allow=never fetch --quiet -- "$PIPU_UPDATE_SOURCE" "$PIPU_UPDATE_REF"
FETCHED="$(git rev-parse 'FETCH_HEAD^{commit}')"
[ "$FETCHED" = "$PIPU_UPDATE_COMMIT" ] || { echo 'Source changed; select update again' >&2; exit 1; }
else
git fetch --tags --prune --quiet origin
fi
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)"
CURRENT_COMMIT="$(sed -n 's/^PIPU_COMMIT=//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 " копию базы сделайте внешними средствами и проверьте, что старая версия совместима со схемой." >&2
if [ "${PIPU_ALLOW_DOWNGRADE:-}" != 1 ]; then
echo " Чтобы продолжить: PIPU_ALLOW_DOWNGRADE=1 ./update.sh $REF" >&2
exit 1
fi
fi
echo "→ переключаемся на $REF"
# Refuse to overwrite local source changes.
git diff --quiet && git diff --cached --quiet || { echo 'Repository has local changes' >&2; exit 1; }
if [ -n "${PIPU_UPDATE_COMMIT:-}" ]; then
git checkout --quiet --detach "$PIPU_UPDATE_COMMIT"
elif 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
# Use the target revision's setup, even when this updater was copied before
# checkout. Existing deployments must gain new host services during upgrades.
if [ -f ./install-management.sh ]; then
bash ./install-management.sh --ensure
fi
if [ -n "${PIPU_UPDATE_COMMIT:-}" ]; then
if [ "$REF" = main ]; then VERSION="main-$(git rev-parse --short HEAD)"; else VERSION="$REF"; fi
else
VERSION="$(git describe --tags --always --dirty)"
fi
VERSION="${VERSION#v}"
COMMIT="$(git rev-parse HEAD)"
if grep -q '^PIPU_COMMIT=' .env; then
sed -i "s|^PIPU_COMMIT=.*|PIPU_COMMIT=$COMMIT|" .env
else
echo "PIPU_COMMIT=$COMMIT" >> .env
fi
sed -i "s|^PIPU_VERSION=.*|PIPU_VERSION=$VERSION|" .env
echo "→ версия $VERSION, сборка образов (первая сборка — 5–15 минут)"
compose config --quiet
if ! compose build --pull; then
# A build failure leaves running containers untouched; keep their version in .env.
sed -i "s|^PIPU_VERSION=.*|PIPU_VERSION=$CURRENT|" .env
sed -i "s|^PIPU_COMMIT=.*|PIPU_COMMIT=$CURRENT_COMMIT|" .env
echo 'Build failed; running containers were not replaced' >&2
exit 1
fi
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 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