Files
Dmitriy Fofanov 293e7259b6
Wiki Sync / Синхронизация Wiki (push) Successful in 2s
Fix: wiki-sync.yml runs-on native, без actions/checkout, fallback для rsync
2026-02-23 23:21:23 +03:00

460 lines
21 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ══════════════════════════════════════════════════════════════════════════════
# 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: native
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: |
# Добавляем стандартные пути установки Go
for p in /usr/local/go/bin /usr/local/bin /snap/bin "$HOME/go/bin" "$HOME/.go/bin" /opt/go/bin; do
[ -d "$p" ] && export PATH="$p:$PATH"
done
# Сохраняем PATH для всех последующих шагов
echo "PATH=$PATH" >> "$GITHUB_ENV"
if ! command -v go &>/dev/null; then
echo "ОШИБКА: Go не установлен на раннере."
echo "Установите Go ${{ env.GO_VERSION }} на сервер раннера."
echo "Проверенные пути: $PATH"
exit 1
fi
echo ">>> Go: $(go version)"
echo ">>> GOPATH: $(go env GOPATH)"
echo ">>> GOROOT: $(go env GOROOT)"
# ── Определение версии ──────────────────────────────────────────────
- 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: |
VER="${{ steps.ver.outputs.version }}"
LDFLAGS="-s -w -X main.version=${VER}"
GOFLAGS="-trimpath"
CMD="./cmd/genaudiobookinfo"
OUT="${BUILD_DIR}"
mkdir -p "${OUT}"
build() {
local goos=$1 goarch=$2 suffix=$3 extra_env="$4"
local outfile="${OUT}/${APP_NAME}-${suffix}"
echo ">>> ${goos}/${goarch} → ${APP_NAME}-${suffix}"
env GOOS=${goos} GOARCH=${goarch} ${extra_env} \
go build ${GOFLAGS} -ldflags "${LDFLAGS}" -o "${outfile}" ${CMD}
}
# Linux
build linux amd64 linux-amd64
build linux 386 linux-386
build linux arm64 linux-arm64
build linux arm linux-armv7 "GOARM=7"
build linux mips linux-mips "GOMIPS=softfloat"
build linux mipsle linux-mipsle "GOMIPS=softfloat"
build linux riscv64 linux-riscv64
# macOS
build darwin amd64 darwin-amd64
build darwin arm64 darwin-arm64
# Windows
build windows amd64 windows-amd64.exe
build windows 386 windows-386.exe
build windows arm64 windows-arm64.exe
# FreeBSD
build freebsd amd64 freebsd-amd64
build freebsd arm64 freebsd-arm64
# OpenBSD
build openbsd amd64 openbsd-amd64
# NetBSD
build netbsd amd64 netbsd-amd64
echo ""
echo "Собранные бинарники ($(ls ${OUT}/ | wc -l)):"
ls -lh ${OUT}/
# ── Архивы + контрольные суммы ──────────────────────────────────────
- 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}"
if command -v zip &>/dev/null; then
zip "archives/${ARCNAME}" "$f"
else
# Fallback: tar.gz вместо zip если zip не установлен
ARCNAME="${f%.exe}.tar.gz"
tar -czf "archives/${ARCNAME}" "$f"
fi
;;
${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 "──────────────────────────────────────"
# ── Публикация в DFGit ──────────────────────────────────────────────
- name: "Создание релиза в DFGit и загрузка файлов"
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-кодирование тела релиза ───────────────────────────────
# JSON-кодирование: python3 или чистый bash
if command -v python3 &>/dev/null; then
BODY_JSON=$(python3 -c "
import json
with open('/tmp/release_body.md', 'r') as f:
print(json.dumps(f.read()))
")
else
# Чистый bash: экранируем для JSON
BODY_RAW=$(cat /tmp/release_body.md)
BODY_JSON=$(printf '%s' "$BODY_RAW" | sed 's/\\/\\\\/g; s/"/\\"/g; s/ /\\t/g' | awk '{printf "%s\\n", $0}' | sed 's/\\n$//')
BODY_JSON="\"${BODY_JSON}\""
fi
# ── Создание релиза через 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
}")
# Извлекаем ID релиза: python3 или grep/sed
if command -v python3 &>/dev/null; then
RELEASE_ID=$(echo "$RELEASE_JSON" | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true)
else
RELEASE_ID=$(echo "$RELEASE_JSON" | grep -o '"id":[0-9]*' | head -1 | grep -o '[0-9]*')
fi
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "None" ]; then
echo "ОШИБКА: не удалось создать релиз!"
echo "Ответ API:"
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} опубликован успешно."