Files
pipu-music/docs/ci-cd.md
T
mrleo1nidandClaude Opus 5.5 718125b719
ci / api / api (push) Successful in 54s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 54s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 53s
ci / recommender / recommender (push) Successful in 38s
ci / ts / ts (packages + web) (push) Successful in 37s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m47s
release / release (версия и заметки) (push) Successful in 7s
release / android (APK в релиз) (push) Canceled after 4m25s
ci: split CI by area with path filters and speed up releases
- ci.yml becomes ci-api, ci-contract, ci-recommender, ci-ts, ci-mobile and
  docker workflows, each filtered by paths; docs-only commits run nothing
- docker builds all four images in one job with plain docker build on the
  runner host, so the layer cache survives between runs
- release no longer re-runs the test suite (the tagged commit already passed
  CI on main); version and release notes are one job
- the android job mounts persistent Gradle and Android SDK caches from
  /opt/ci-cache (when the runner allows the volumes) and uses --build-cache
- RELEASE_TOKEN is optional: fall back to the built-in job token
- docs: CI layout and act_runner settings (capacity, cache, valid_volumes)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 03:55:45 +03:00

8.8 KiB
Raw Permalink Blame History

CI/CD (Gitea Actions)

Workflows лежат в .gitea/workflows/. Синтаксис совместим с GitHub Actions; runner — существующий act_runner с доступом к Docker.

Сборке APK нужно ~15 ГБ свободного места на хосте раннера (Android SDK, NDK ~2.5 ГБ, кэш Gradle, C++-сборка React Native). APK собирается только под arm64-v8a. Если job падает с No space left on device, освободите место на хосте раннера: docker system prune -af и старые кэши act в ~/.cache/act.

Версионирование

  • SemVer, тег vMAJOR.MINOR.PATCH (предрелизы: v1.2.0-rc.1 — помечаются как pre-release, мобильное их предлагает только при включённой настройке «бета-версии»).
  • Одна версия на весь продукт: образы, APK и веб собираются с одним номером.
  • Android versionCode = MAJOR·1 000 000 + MINOR·10 000 + PATCH·100 + (99 для релиза | N для -rc.N), так что 1.2.0-rc.1 < 1.2.0-rc.2 < 1.2.0 < 1.2.1; MINOR и PATCH ≤ 99. Считает scripts/version.mjs (тесты — pnpm test:scripts).
  • Changelog — git-cliff из Conventional Commits.

CI — на каждый push в main и PR

Проверки разбиты на workflow по областям, у каждого фильтр путей (on.push.paths): запускается только то, что затронуто коммитом. Коммиты только в docs/ и *.md CI не запускают вовсе.

Workflow Job Что делает Когда
ci-api.yml api gofmt, go vet, go test -race, сборка services/api/**, api/openapi.yaml
ci-contract.yml contract регенерация Go-сервера, TS-клиента и токенов; всё ли закоммичено openapi.yaml, gen/, packages/api-client, packages/design-tokens, общие файлы JS
ci-recommender.yml recommender ruff, mypy, pytest services/recommender/**
ci-ts.yml ts i18n, тесты скриптов, typecheck (вкл. mobile), vitest, сборка веба apps/**, packages/**, scripts/**, общие файлы JS
ci-mobile.yml mobile expo-doctor, Metro-бандл Android apps/mobile/**, packages/**, общие файлы JS
docker.yml docker сборка всех 4 образов без push Dockerfile, .dockerignore, deploy/**, манифесты зависимостей

Общие файлы JS: package.json, pnpm-lock.yaml, pnpm-workspace.yaml, turbo.json, tsconfig.base.json. Изменение самого workflow запускает его. У каждого workflow своя группа concurrency: новый push в ту же ветку отменяет устаревший прогон.

docker собирает образы обычным docker build через Docker хоста раннера, а не в buildx-контейнере: кэш слоёв переживает job, и повторная сборка занимает секунды. Кэш растёт — на хосте раннера раз в сутки: docker builder prune -af --filter until=72h (см. настройку раннера).

release.yml — по тегу

on:
  push:
    tags: ['v*.*.*']

jobs:
  release:         # версия из тега (scripts/version.mjs) и сразу Gitea Release (scripts/release.mjs create)
  android:         # needs: release — собирает APK и докладывает его в релиз

Тесты в релизе не повторяются: тег ставится на коммит, который CI уже проверил в main. Ставьте тег после зелёного CI — релиз сам этого не проверяет.

Релиз появляется в первую же минуту, не дожидаясь APK. Текст строит scripts/release.mjs:

  • статус Android вверху: «⏳ собирается» со ссылкой на ход сборки → «✅ собран» (имя файла, размер, versionCode, SHA-256; предупреждение, если подпись отладочная) или «❌ не удалась» со ссылкой на лог;
  • изменения — коммиты (Conventional Commits) с прошлого тега по разделам «Новое», «Исправления», «Сборка и CI» и т. д., отдельно — несовместимые (feat!:); для финального релиза отсчёт от прошлого финального, а не от последнего -rc; ссылка на полный diff;
  • обновление сервера — бэкап, ./update.sh vX.Y.Z, проверка версии; установка APK и проверка SHA-256.

Job android: Android SDK, expo prebuild, ./gradlew assembleRelease --build-cache (только arm64-v8a), подпись apksigner, затем release.mjs android success <apk> прикладывает APK и .sha256 и меняет статус. При любом сбое — release.mjs android failure. Перезапуск job безопасен: статус снова «собирается», файлы заменяются. Перезапуск всего workflow обновляет текст существующего релиза, а не создаёт второй.

Docker-образы не публикуются: сервер собирает их сам из клонированного репозитория (ADR-013). Чтобы сборка на сервере не ломалась, job docker в ci.yml собирает все образы без push на каждый PR.

Настройка раннера

Раннер — act_runner на отдельной VM. Что ускоряет CI/CD (config.yaml раннера, затем перезапуск сервиса):

runner:
  capacity: 2            # два job параллельно; только если у VM ≥ 8 ГБ RAM (иначе APK-сборка упирается в OOM)
cache:
  enabled: true          # кэш actions/cache: pnpm store, Go-модули, uv
  host: 10.10.1.x        # IP хоста раннера, доступный из job-контейнеров (не 127.0.0.1)
  port: 8088             # фиксированный порт; открыть его для docker-сетей
container:
  valid_volumes:
    - /opt/ci-cache/**   # постоянные кэши Gradle и Android SDK для job android в release.yml

Без valid_volumes раннер пропускает тома из release.yml, и каждый релиз заново качает Gradle и NDK (~4 мин). Признак выключенного кэша actions в логах — Cache action is only supported on GHES….

Обслуживание хоста раннера (cron, раз в сутки):

0 4 * * * docker image prune -af --filter "until=72h"; docker builder prune -af --filter "until=72h"

Кэши в /opt/ci-cache занимают ~5–6 ГБ (Gradle, SDK, NDK); если место кончается — их можно удалить целиком, следующий релиз скачает всё заново.

Секреты

Секрет Назначение
RELEASE_TOKEN Необязательно. Создание и редактирование релиза, загрузка ассетов (scope write:repository). Без него используется встроенный токен job (GITHUB_TOKEN), у которого есть права на запись в этот репозиторий
ANDROID_KEYSTORE_BASE64, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD Подпись APK. Ключ один на всю жизнь приложения — хранить резервную копию вне Gitea. Без них финальный релиз падает; -rc собирается с отладочной подписью.

Деплой

Автодеплой не обязателен. На сервере в клоне репозитория: deploy/update.sh [vX.Y.Z] — checkout тега, docker compose build и up -d; миграции применяются при старте api (install-guide.md). Опционально — job deploy в release.yml по SSH на сервер для не-pre-release тегов (с ручным approval через environment).