# Релиз по тегу vMAJOR.MINOR.PATCH[-rc.N] (docs/ci-cd.md): # проверки → Docker-образы в Gitea Container Registry → подписанный APK → Gitea Release с ассетами. # # Переменные репозитория (Settings → Actions → Variables): # REGISTRY — хост реестра, например git.home.lan (по умолчанию — хост сервера Gitea) # Секреты (Settings → Actions → Secrets): # REGISTRY_USER, REGISTRY_TOKEN — push образов (scope: write:package) # 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 images: needs: verify runs-on: ubuntu-latest strategy: fail-fast: true matrix: include: - { name: api, dockerfile: services/api/Dockerfile, target: api } - { name: storage-agent, dockerfile: services/api/Dockerfile, target: storage-agent } - { name: recommender, dockerfile: services/recommender/Dockerfile, target: "" } - { name: web, dockerfile: apps/web/Dockerfile, target: "" } steps: - uses: actions/checkout@v4 - name: Имя образа id: img run: | registry="${{ vars.REGISTRY }}" [ -n "$registry" ] || registry="$(echo '${{ github.server_url }}' | sed -E 's#^https?://##')" owner="$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" echo "registry=$registry" >> "$GITHUB_OUTPUT" echo "image=$registry/$owner/pipu-music-${{ matrix.name }}" >> "$GITHUB_OUTPUT" - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ${{ steps.img.outputs.registry }} username: ${{ secrets.REGISTRY_USER }} password: ${{ secrets.REGISTRY_TOKEN }} - uses: docker/build-push-action@v6 with: context: . file: ${{ matrix.dockerfile }} target: ${{ matrix.target }} push: true build-args: VERSION=${{ needs.verify.outputs.version }} tags: | ${{ steps.img.outputs.image }}:${{ needs.verify.outputs.version }} ${{ needs.verify.outputs.prerelease == 'false' && format('{0}:latest', steps.img.outputs.image) || '' }} android-release: name: android + release needs: [verify, images] 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 - 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: 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 else echo "::warning::ANDROID_KEYSTORE_BASE64 не задан — APK подписан отладочным ключом и не обновится поверх релизного" cp "$src" "$out" fi "$bt/apksigner" verify --print-certs "$out" | head -3 sha256sum "$out" | cut -d' ' -f1 > "$out.sha256" echo "APK=$out" >> "$GITHUB_ENV" - name: Changelog run: | prev="$(git describe --tags --abbrev=0 "${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" deploy/compose.yaml deploy/.env.example deploy/install.sh deploy/update.sh; 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