From 91f5c428595ae28c8323ae824b8d3220de7bc9c4 Mon Sep 17 00:00:00 2001 From: Dmitriy Fofanov Date: Mon, 23 Feb 2026 22:46:25 +0300 Subject: [PATCH] =?UTF-8?q?Fix:=20=D1=83=D0=B1=D1=80=D0=B0=D0=BD=D1=8B=20u?= =?UTF-8?q?ses=20actions,=20=D0=B7=D0=B0=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20=D0=BD=D0=B0=20run=20(=D1=81=D0=BE=D0=B2=D0=BC=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D0=BC=D0=BE=D1=81=D1=82=D1=8C=20=D1=81=20dfg?= =?UTF-8?q?it)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/release.yml | 389 ++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 34 ++- 2 files changed, 415 insertions(+), 8 deletions(-) create mode 100644 .gitea/workflows/release.yml diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..f5b69b9 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,389 @@ +# ══════════════════════════════════════════════════════════════════════════════ +# CI/CD Pipeline: Сборка и публикация релиза GenAudioBookInfo +# ══════════════════════════════════════════════════════════════════════════════ +# +# ЗАПУСК: +# make release VERSION=2.1.0 +# Создаёт git-тег v2.1.0, пушит его → запускается этот workflow. +# +# ────────────────────────────────────────────────────────────────────────────── +# ЕДИНЫЙ JOB (без передачи артефактов между jobs): +# +# 1. Качество кода: go vet + go test +# 2. Кросс-компиляция: 16 бинарников для всех платформ +# 3. Архивирование: Windows → ZIP, Unix → tar.gz, SHA256 +# 4. Описание релиза: авто-changelog из git-коммитов на русском языке +# 5. Публикация: Gitea Release + загрузка всех файлов +# +# ────────────────────────────────────────────────────────────────────────────── +# НЕОБХОДИМАЯ НАСТРОЙКА: +# +# В настройках репозитория Gitea → Settings → Secrets: +# GIT_TOKEN (или GITEA_TOKEN) — токен с правами write:repository +# +# ══════════════════════════════════════════════════════════════════════════════ + +name: "Release CI/CD" + +on: + push: + tags: + - "v*" + +env: + GO_VERSION: "1.24" + APP_NAME: genaudiobookinfo + BUILD_DIR: build + +# ══════════════════════════════════════════════════════════════════════════════ +jobs: + + release: + name: "Сборка и публикация релиза" + runs-on: ubuntu-latest + + steps: + # ── Исходный код (полная история для changelog) ────────────────────── + - name: "Получение исходного кода" + shell: bash + env: + GIT_TOKEN: ${{ secrets.GIT_TOKEN }} + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + TOKEN="${GIT_TOKEN:-$GITEA_TOKEN}" + SERVER="${{ github.server_url }}" + REPO="${{ github.repository }}" + TAG="${GITHUB_REF_NAME}" + # Убираем https:// для вставки токена + HOST="${SERVER#https://}" + HOST="${HOST#http://}" + CLONE_URL="https://${TOKEN}@${HOST}/${REPO}.git" + echo ">>> Клонирование ${REPO} (тег ${TAG})..." + git clone --branch "${TAG}" "${CLONE_URL}" . 2>&1 | grep -v "${TOKEN}" || true + git fetch --tags --force 2>&1 | grep -v "${TOKEN}" || true + echo ">>> Исходный код получен: $(git log --oneline -1)" + + - name: "Проверка Go" + shell: bash + run: | + if ! command -v go &>/dev/null; then + echo "ОШИБКА: Go не установлен на раннере." + echo "Установите Go ${{ env.GO_VERSION }} на сервер раннера." + exit 1 + fi + echo ">>> Go: $(go version)" + + # ── Определение версии ────────────────────────────────────────────── + - name: "Определение версии и предыдущего тега" + id: ver + shell: bash + run: | + TAG="${GITHUB_REF_NAME}" + VER="${TAG#v}" + echo "version=${VER}" >> "$GITHUB_OUTPUT" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + + PREV_TAG=$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || echo "") + echo "prev_tag=${PREV_TAG}" >> "$GITHUB_OUTPUT" + + echo "══════════════════════════════════════" + echo " Версия: ${VER}" + echo " Git-тег: ${TAG}" + echo " Пред. тег: ${PREV_TAG:-<первый релиз>}" + echo " Коммит: ${GITHUB_SHA:0:8}" + echo "══════════════════════════════════════" + + # ── Качество кода ─────────────────────────────────────────────────── + - name: "Проверка качества кода (vet + tests)" + shell: bash + run: | + echo ">>> Загрузка зависимостей..." + go mod download + echo ">>> Статический анализ (go vet)..." + go vet ./... + echo ">>> Unit-тесты..." + go test ./... -count=1 -timeout 5m + echo ">>> Качество кода — ОК" + + # ── Кросс-компиляция ──────────────────────────────────────────────── + - name: "Кросс-компиляция для всех платформ (16 бинарников)" + shell: bash + run: | + make build-all VERSION=${{ steps.ver.outputs.version }} + echo "" + echo "Собранные бинарники:" + ls -lh ${BUILD_DIR}/ + + # ── Архивы + контрольные суммы ────────────────────────────────────── + - name: "Создание архивов и контрольных сумм" + shell: bash + run: | + cd "${BUILD_DIR}" + mkdir -p archives + + echo "=== Создание архивов ===" + for f in *; do + [ -f "$f" ] || continue + case "$f" in + *.exe) + ARCNAME="${f%.exe}.zip" + echo " ${f} → archives/${ARCNAME}" + zip "archives/${ARCNAME}" "$f" + ;; + ${APP_NAME}-*) + ARCNAME="${f}.tar.gz" + echo " ${f} → archives/${ARCNAME}" + tar -czf "archives/${ARCNAME}" "$f" + ;; + esac + done + + echo "" + echo "=== Контрольные суммы SHA256 ===" + cd archives + sha256sum * > checksums-sha256.txt + cat checksums-sha256.txt + echo "" + echo "Готово: $(ls | wc -l) файлов" + + # ── Генерация описания релиза на русском языке ────────────────────── + - name: "Генерация описания релиза" + shell: bash + run: | + TAG="${{ steps.ver.outputs.tag }}" + VER="${{ steps.ver.outputs.version }}" + PREV_TAG="${{ steps.ver.outputs.prev_tag }}" + COMMIT="${{ github.sha }}" + DATE=$(date -u '+%d.%m.%Y %H:%M UTC') + + # ── Changelog из git-коммитов ────────────────────────────────── + if [ -n "${PREV_TAG}" ]; then + RAW_LOG=$(git log --pretty=format:"%s" "${PREV_TAG}..${TAG}" --no-merges 2>/dev/null || echo "") + else + RAW_LOG=$(git log --pretty=format:"%s" --no-merges 2>/dev/null | head -50 || echo "") + fi + + # Категоризация коммитов + FEATURES="" + FIXES="" + REFACTOR="" + OTHER="" + + while IFS= read -r line; do + [ -z "$line" ] && continue + line_lower=$(echo "$line" | tr '[:upper:]' '[:lower:]') + case "$line_lower" in + функция:*|feat:*|feature:*|добавлен*|реализован*|новое:*) + FEATURES="${FEATURES} + - ${line}" + ;; + исправлен*|fix:*|bugfix:*|баг:*|ошибка:*) + FIXES="${FIXES} + - ${line}" + ;; + рефакторинг:*|refactor:*|оптимизац*|улучшен*) + REFACTOR="${REFACTOR} + - ${line}" + ;; + ci:*|docs:*|ci/*|build:*) + OTHER="${OTHER} + - ${line}" + ;; + *) + OTHER="${OTHER} + - ${line}" + ;; + esac + done <<< "$RAW_LOG" + + # ── Начинаем формировать тело релиза ─────────────────────────── + { + echo "## GenAudioBookInfo ${TAG}" + echo "" + + # Пользовательские заметки (RELEASE_NOTES.md — приоритет) + if [ -f "RELEASE_NOTES.md" ]; then + cat RELEASE_NOTES.md + echo "" + fi + + # Автоматический changelog + HAS_CHANGES=false + + if [ -n "${FEATURES}" ]; then + HAS_CHANGES=true + echo "### 🚀 Новые возможности" + echo "${FEATURES}" + echo "" + fi + + if [ -n "${FIXES}" ]; then + HAS_CHANGES=true + echo "### 🐛 Исправления" + echo "${FIXES}" + echo "" + fi + + if [ -n "${REFACTOR}" ]; then + HAS_CHANGES=true + echo "### ♻️ Рефакторинг и оптимизация" + echo "${REFACTOR}" + echo "" + fi + + if [ -n "${OTHER}" ]; then + HAS_CHANGES=true + echo "### 📝 Прочие изменения" + echo "${OTHER}" + echo "" + fi + + if [ "${HAS_CHANGES}" = "false" ]; then + echo "- Первый релиз" + echo "" + fi + + echo "---" + echo "" + echo "### Поддерживаемые платформы" + echo "" + echo "| ОС | Архитектура | Файл |" + echo "|---|---|---|" + echo "| Windows | amd64 (64-бит) | \`${APP_NAME}-windows-amd64.zip\` |" + echo "| Windows | 386 (32-бит) | \`${APP_NAME}-windows-386.zip\` |" + echo "| Windows | arm64 | \`${APP_NAME}-windows-arm64.zip\` |" + echo "| Linux | amd64 | \`${APP_NAME}-linux-amd64.tar.gz\` |" + echo "| Linux | arm64 | \`${APP_NAME}-linux-arm64.tar.gz\` |" + echo "| Linux | armv7 | \`${APP_NAME}-linux-armv7.tar.gz\` |" + echo "| Linux | 386 | \`${APP_NAME}-linux-386.tar.gz\` |" + echo "| Linux | MIPS | \`${APP_NAME}-linux-mips.tar.gz\` |" + echo "| Linux | MIPSle | \`${APP_NAME}-linux-mipsle.tar.gz\` |" + echo "| Linux | RISC-V 64 | \`${APP_NAME}-linux-riscv64.tar.gz\` |" + echo "| macOS | amd64 (Intel) | \`${APP_NAME}-darwin-amd64.tar.gz\` |" + echo "| macOS | arm64 (Apple Silicon) | \`${APP_NAME}-darwin-arm64.tar.gz\` |" + echo "| FreeBSD | amd64 | \`${APP_NAME}-freebsd-amd64.tar.gz\` |" + echo "| FreeBSD | arm64 | \`${APP_NAME}-freebsd-arm64.tar.gz\` |" + echo "| OpenBSD | amd64 | \`${APP_NAME}-openbsd-amd64.tar.gz\` |" + echo "| NetBSD | amd64 | \`${APP_NAME}-netbsd-amd64.tar.gz\` |" + echo "" + echo "> 🔒 Контрольные суммы: \`checksums-sha256.txt\`" + echo "" + echo "### Информация о сборке" + echo "" + echo "| Параметр | Значение |" + echo "|---|---|" + echo "| Коммит | \`${COMMIT}\` |" + echo "| Дата сборки | ${DATE} |" + echo "| Go | ${{ env.GO_VERSION }} |" + } > /tmp/release_body.md + + echo ">>> Описание релиза сформировано:" + echo "──────────────────────────────────────" + cat /tmp/release_body.md + echo "──────────────────────────────────────" + + # ── Публикация в Gitea ────────────────────────────────────────────── + - name: "Создание релиза в Gitea и загрузка файлов" + shell: bash + env: + GIT_TOKEN: ${{ secrets.GIT_TOKEN }} + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + TOKEN="${GIT_TOKEN:-$GITEA_TOKEN}" + if [ -z "${TOKEN}" ]; then + echo "ОШИБКА: не задан секрет GIT_TOKEN (или GITEA_TOKEN)." + echo "Добавьте токен в Settings → Secrets репозитория." + exit 1 + fi + + TAG="${{ steps.ver.outputs.tag }}" + COMMIT="${{ github.sha }}" + GITEA_URL="${{ github.server_url }}" + REPO="${{ github.repository }}" + + echo "══════════════════════════════════════" + echo " Сервер: ${GITEA_URL}" + echo " Репо: ${REPO}" + echo " Тег: ${TAG}" + echo " Коммит: ${COMMIT:0:8}" + echo "══════════════════════════════════════" + + # ── JSON-кодирование тела релиза ─────────────────────────────── + BODY_JSON=$(python3 -c " + import json + with open('/tmp/release_body.md', 'r') as f: + print(json.dumps(f.read())) + ") + + # ── Создание релиза через Gitea API ──────────────────────────── + echo "" + echo ">>> Создаём релиз ${TAG}..." + + RELEASE_JSON=$(curl -sf -X POST \ + -H "Authorization: token ${TOKEN}" \ + -H "Content-Type: application/json" \ + "${GITEA_URL}/api/v1/repos/${REPO}/releases" \ + -d "{ + \"tag_name\": \"${TAG}\", + \"target_commitish\": \"${COMMIT}\", + \"name\": \"${APP_NAME} ${TAG}\", + \"body\": ${BODY_JSON}, + \"draft\": false, + \"prerelease\": false + }") + + RELEASE_ID=$(echo "$RELEASE_JSON" | \ + python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true) + + if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "None" ]; then + echo "ОШИБКА: не удалось создать релиз!" + echo "Ответ API:" + echo "$RELEASE_JSON" | python3 -m json.tool 2>/dev/null || echo "$RELEASE_JSON" + exit 1 + fi + + echo ">>> Релиз создан: ID=${RELEASE_ID}" + + # ── Загрузка артефактов ──────────────────────────────────────── + echo "" + echo ">>> Загрузка артефактов..." + UPLOAD_OK=0 + UPLOAD_FAIL=0 + + for FILE in ./${BUILD_DIR}/archives/*; do + [ -f "$FILE" ] || continue + FILENAME=$(basename "$FILE") + FILESIZE=$(du -sh "$FILE" | cut -f1) + + printf " %-55s [%5s] " "${FILENAME}" "${FILESIZE}" + + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ + -X POST \ + -H "Authorization: token ${TOKEN}" \ + -H "Content-Type: application/octet-stream" \ + --data-binary @"${FILE}" \ + "${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${FILENAME}") + + if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then + echo "✓ (HTTP ${HTTP_CODE})" + UPLOAD_OK=$((UPLOAD_OK + 1)) + else + echo "✗ (HTTP ${HTTP_CODE})" + UPLOAD_FAIL=$((UPLOAD_FAIL + 1)) + fi + done + + # ── Итог ─────────────────────────────────────────────────────── + echo "" + echo "══════════════════════════════════════════════" + echo " Загружено: ${UPLOAD_OK}" + echo " Ошибок: ${UPLOAD_FAIL}" + echo " Релиз: ${GITEA_URL}/${REPO}/releases/tag/${TAG}" + echo "══════════════════════════════════════════════" + + if [ "$UPLOAD_FAIL" -gt 0 ]; then + echo "ОШИБКА: часть файлов не удалось загрузить!" + exit 1 + fi + + echo ">>> Релиз ${TAG} опубликован успешно." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a0c32d5..f5b69b9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,15 +45,33 @@ jobs: steps: # ── Исходный код (полная история для changelog) ────────────────────── - name: "Получение исходного кода" - uses: actions/checkout@v4 - with: - fetch-depth: 0 + shell: bash + env: + GIT_TOKEN: ${{ secrets.GIT_TOKEN }} + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + TOKEN="${GIT_TOKEN:-$GITEA_TOKEN}" + SERVER="${{ github.server_url }}" + REPO="${{ github.repository }}" + TAG="${GITHUB_REF_NAME}" + # Убираем https:// для вставки токена + HOST="${SERVER#https://}" + HOST="${HOST#http://}" + CLONE_URL="https://${TOKEN}@${HOST}/${REPO}.git" + echo ">>> Клонирование ${REPO} (тег ${TAG})..." + git clone --branch "${TAG}" "${CLONE_URL}" . 2>&1 | grep -v "${TOKEN}" || true + git fetch --tags --force 2>&1 | grep -v "${TOKEN}" || true + echo ">>> Исходный код получен: $(git log --oneline -1)" - - name: "Установка Go ${{ env.GO_VERSION }}" - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - cache: true + - name: "Проверка Go" + shell: bash + run: | + if ! command -v go &>/dev/null; then + echo "ОШИБКА: Go не установлен на раннере." + echo "Установите Go ${{ env.GO_VERSION }} на сервер раннера." + exit 1 + fi + echo ">>> Go: $(go version)" # ── Определение версии ────────────────────────────────────────────── - name: "Определение версии и предыдущего тега"