ci / api / api (push) Successful in 22s
ci / db / db (push) Successful in 2m4s
release / release (версия и заметки) (push) Successful in 5s
release / android (APK в релиз) (push) Successful in 4m11s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m33s
The server kept the git.example.lan placeholder from .env.example, so the /android page and in-app updates had no release. install.sh and update.sh now fill GITEA_URL and GITEA_REPO from the clone's origin (https, ssh and scp forms, credentials stripped) when they are empty or still the placeholder, and the API treats the placeholder as not configured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
132 lines
6.5 KiB
Bash
Executable File
132 lines
6.5 KiB
Bash
Executable File
#!/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, ссылки на обложки и аудио — 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:-}"
|
||
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
|
||
|
||
# Копия базы перед обновлением: миграции назад не откатываются (deploy/restore.sh — вернуть).
|
||
if [ -n "$(docker compose ps -q --status running postgres 2>/dev/null)" ]; then
|
||
echo "→ резервная копия базы"
|
||
docker compose run --rm --no-deps backup once >/dev/null
|
||
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
|