The job reaches the Gitea API at its internal address, so html_url in the response pointed at http://10.10.1.129:3000 and every link in the notes was unreachable from outside. Build links from PUBLIC_URL (repo variable, defaults to https://gitea.hsrv.site). A cancelled Android build now shows "cancelled" instead of staying at "building". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9.2 KiB
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 собирается с отладочной подписью. |
Переменная репозитория PUBLIC_URL (Settings → Actions → Variables) — внешний адрес Gitea для ссылок
в тексте релиза, по умолчанию https://gitea.hsrv.site. Нужна потому, что job обращается к API по внутреннему
адресу, и ссылки из ответа API ведут туда же.
Деплой
Автодеплой не обязателен. На сервере в клоне репозитория: 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).