# Релиз по тегу vMAJOR.MINOR.PATCH[-rc.N] (docs/ci-cd.md): проверки → подписанный APK → Gitea Release. # Docker-образы не публикуются: сервер собирает их сам из клонированного репозитория (deploy/update.sh). # # Секреты (Settings → Actions → Secrets): # RELEASE_TOKEN — создание релиза и загрузка ассетов (scope: write:repository) # ANDROID_KEYSTORE_BASE64, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD — подпись APK name: release on: push: tags: ["v*.*.*"] env: GO_VERSION: "1.24" NODE_VERSION: "22" jobs: verify: runs-on: ubuntu-latest outputs: version: ${{ steps.v.outputs.PIPU_VERSION }} version_code: ${{ steps.v.outputs.PIPU_VERSION_CODE }} prerelease: ${{ steps.v.outputs.PIPU_PRERELEASE }} steps: - uses: actions/checkout@v4 - id: v run: node scripts/version.mjs "${GITHUB_REF_NAME}" | tee -a "$GITHUB_OUTPUT" - uses: actions/setup-go@v5 with: { go-version: "${{ env.GO_VERSION }}", cache-dependency-path: services/api/go.sum } - uses: astral-sh/setup-uv@v6 with: { version: "0.10.2" } - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm } - run: pnpm install --frozen-lockfile - name: Go working-directory: services/api run: go vet ./... && go test ./... - name: Python working-directory: services/recommender run: uv sync --frozen && uv run ruff check . && uv run mypy pipu_rec && uv run pytest -q - name: TypeScript run: pnpm i18n:check && pnpm test:scripts && pnpm typecheck && pnpm test android-release: name: android + release needs: verify runs-on: ubuntu-latest env: PIPU_VERSION: ${{ needs.verify.outputs.version }} PIPU_VERSION_CODE: ${{ needs.verify.outputs.version_code }} steps: - uses: actions/checkout@v4 with: { fetch-depth: 0 } - uses: actions/setup-java@v4 with: { distribution: temurin, java-version: "17" } - uses: android-actions/setup-android@v3 with: # пакета `tools` (значение по умолчанию) в репозитории SDK больше нет — sdkmanager падает; # build-tools нужны шагу подписи (apksigner), версии — как у React Native 0.79 packages: platform-tools platforms;android-35 build-tools;35.0.0 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: { node-version: "${{ env.NODE_VERSION }}", cache: pnpm } - run: pnpm install --frozen-lockfile - name: Сборка APK working-directory: apps/mobile run: | npx expo prebuild --platform android --clean --no-install cd android && ./gradlew --no-daemon assembleRelease - name: Подпись APK env: PRERELEASE: ${{ needs.verify.outputs.prerelease }} KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} run: | set -euo pipefail src=apps/mobile/android/app/build/outputs/apk/release/app-release.apk out="pipu-music-${PIPU_VERSION}.apk" bt="$(ls -d "$ANDROID_HOME"/build-tools/* | sort -V | tail -1)" if [ -n "${KEYSTORE_B64}" ]; then echo "$KEYSTORE_B64" | base64 -d > release.keystore "$bt/apksigner" sign --ks release.keystore --ks-pass env:KEYSTORE_PASSWORD \ --ks-key-alias "$KEY_ALIAS" --key-pass env:KEY_PASSWORD --out "$out" "$src" rm release.keystore elif [ "$PRERELEASE" = "true" ]; then echo "::warning::ANDROID_KEYSTORE_BASE64 не задан — APK подписан отладочным ключом и не обновится поверх релизного" cp "$src" "$out" else echo "::error::ANDROID_KEYSTORE_BASE64 не задан — релиз с отладочной подписью не выпускается (rc-теги допускают)" exit 1 fi "$bt/apksigner" verify --print-certs "$out" | head -3 sha256sum "$out" | cut -d' ' -f1 > "$out.sha256" echo "APK=$out" >> "$GITHUB_ENV" - name: Changelog env: PRERELEASE: ${{ needs.verify.outputs.prerelease }} run: | # для финального релиза — изменения с прошлого финального, а не с последнего rc exclude=() [ "$PRERELEASE" = "true" ] || exclude=(--exclude '*-rc*') prev="$(git describe --tags --abbrev=0 "${exclude[@]}" "${GITHUB_REF_NAME}^" 2>/dev/null || true)" range="${prev:+$prev..}${GITHUB_REF_NAME}" { echo "## Pipu Music ${PIPU_VERSION}" echo for t in feat fix perf refactor docs ci chore; do lines="$(git log --no-merges --format='%s' "$range" | grep -E "^${t}(\(|:|!)" || true)" [ -n "$lines" ] || continue case $t in feat) h="Новое";; fix) h="Исправления";; perf) h="Производительность";; *) h="$t";; esac echo "### $h"; echo "$lines" | sed 's/^/- /'; echo done } > CHANGELOG_RELEASE.md cat CHANGELOG_RELEASE.md - name: Gitea Release env: TOKEN: ${{ secrets.RELEASE_TOKEN }} API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }} PRERELEASE: ${{ needs.verify.outputs.prerelease }} run: | set -euo pipefail body="$(jq -n --arg tag "$GITHUB_REF_NAME" --arg name "Pipu Music $PIPU_VERSION" \ --rawfile notes CHANGELOG_RELEASE.md --argjson pre "$PRERELEASE" \ '{tag_name:$tag, name:$name, body:$notes, prerelease:$pre, draft:false}')" id="$(curl -fsS -X POST -H "Authorization: token $TOKEN" -H 'Content-Type: application/json' \ -d "$body" "$API/releases" | jq -r .id)" for f in "$APK" "$APK.sha256"; do curl -fsS -X POST -H "Authorization: token $TOKEN" \ -F "attachment=@$f" "$API/releases/$id/assets?name=$(basename "$f")" > /dev/null echo "uploaded $(basename "$f")" done