# .woodpecker.yml trigger: # Запускать каждый час (или как вам нужно) cron: - 'sync-hourly@0 * * * *' # Имя крона@расписание pipeline: sync_releases: image: alpine/git # Базовый образ с git secrets: [ gitea_url, gitea_token, github_token ] # Подключаем секреты environment: # Передаем секреты и имя файла в окружение контейнера GITEA_URL: ${GITEA_URL} GITEA_TOKEN: ${GITEA_TOKEN} GITHUB_TOKEN: ${GITHUB_TOKEN} # Может быть пустым, если не используется REPO_LIST_FILE: repos_to_sync.txt # Имя файла со списком репозиториев commands: # 1. Установка зависимостей (curl и jq) - apk update && apk add curl jq bash # 2. Запуск скрипта синхронизации - | #!/bin/bash set -e # Останавливаться при ошибках # set -x # Для отладки можно раскомментировать echo "Starting release sync..." # --- Конфигурация --- REPO_FILE="${REPO_LIST_FILE:-repos_to_sync.txt}" # Используем переменную окружения или дефолтное имя # Проверка наличия файла репозиториев if [ ! -f "$REPO_FILE" ]; then echo "Error: Repository list file '$REPO_FILE' not found!" exit 1 fi # Проверка наличия GITEA_TOKEN if [ -z "$GITEA_TOKEN" ]; then echo "Error: GITEA_TOKEN secret is not set!" exit 1 fi # Заголовок авторизации для Gitea GITEA_AUTH_HEADER="Authorization: token ${GITEA_TOKEN}" GITEA_API_URL="${GITEA_URL}/api/v1" # Заголовок авторизации для GitHub (если токен есть) GITHUB_AUTH_HEADER="" if [ -n "$GITHUB_TOKEN" ]; then GITHUB_AUTH_HEADER="Authorization: token ${GITHUB_TOKEN}" fi GITHUB_API_URL="https://api.github.com" # --- Вспомогательные функции (без изменений) --- download_asset() { local url="$1" local filename="$2" echo "Downloading asset: $filename from $url" curl -L -H "$GITHUB_AUTH_HEADER" -o "$filename" "$url" if [ $? -ne 0 ]; then echo "Error downloading asset: $filename" return 1 fi return 0 } upload_asset() { local gitea_repo_path="$1" local release_id="$2" local filename="$3" echo "Uploading asset: $filename to Gitea release ID $release_id" UPLOAD_URL="${GITEA_API_URL}/repos/${gitea_repo_path}/releases/${release_id}/assets?name=$(basename "$filename")" # Используем --fail для curl, чтобы он возвращал ненулевой код при HTTP ошибках > 400 curl -sf --fail -X POST -H "$GITEA_AUTH_HEADER" -H "Content-Type: application/octet-stream" --data-binary "@$filename" "$UPLOAD_URL" if [ $? -ne 0 ]; then echo "Error uploading asset: $filename" # Оставляем файл для возможного анализа при ошибке return 1 else rm "$filename" # Удаляем локальный файл после успешной загрузки return 0 fi } # --- Основной цикл по репозиториям из файла --- echo "Reading repository list from $REPO_FILE" while IFS= read -r repo_pair || [[ -n "$repo_pair" ]]; do # Читаем строки из файла # Пропускаем пустые строки и комментарии if [[ -z "$repo_pair" || "$repo_pair" == \#* ]]; then continue fi # Проверяем формат строки if ! echo "$repo_pair" | grep -q ':'; then echo "Warning: Skipping invalid line in $REPO_FILE: $repo_pair (format should be github/repo:gitea/repo)" continue fi GITHUB_REPO_PATH=$(echo "$repo_pair" | cut -d':' -f1) GITEA_REPO_PATH=$(echo "$repo_pair" | cut -d':' -f2) echo "-------------------------------------" echo "Syncing releases for $GITHUB_REPO_PATH -> $GITEA_REPO_PATH" # 1. Получаем существующие теги релизов из Gitea echo "Fetching existing Gitea releases for $GITEA_REPO_PATH..." # Используем --fail, чтобы прервать пайплайн, если Gitea недоступен или репо нет GITEA_RELEASES_JSON=$(curl -sf --fail -H "$GITEA_AUTH_HEADER" "${GITEA_API_URL}/repos/${GITEA_REPO_PATH}/releases") || \ { echo "Error fetching Gitea releases for $GITEA_REPO_PATH, stopping sync."; exit 1; } EXISTING_GITEA_TAGS=$(echo "$GITEA_RELEASES_JSON" | jq -r '.[].tag_name') # echo "Found Gitea tags: $EXISTING_GITEA_TAGS" # Можно включить для отладки # 2. Получаем последние релизы из GitHub (например, 20 последних для большей надежности) echo "Fetching latest GitHub releases for $GITHUB_REPO_PATH..." # Используем --fail для обработки ошибок GitHub API GITHUB_RELEASES_JSON=$(curl -sf --fail -H "$GITHUB_AUTH_HEADER" "${GITHUB_API_URL}/repos/${GITHUB_REPO_PATH}/releases?per_page=20") || \ { echo "Error fetching GitHub releases for $GITHUB_REPO_PATH, skipping this repo."; continue; } # Пропускаем репо при ошибке GitHub # 3. Итерируем по релизам GitHub в ОБРАТНОМ порядке echo "$GITHUB_RELEASES_JSON" | jq -c '.[] | select(.draft == false)' | tac | while IFS= read -r release_json; do TAG_NAME=$(echo "$release_json" | jq -r '.tag_name') # Проверяем, есть ли уже релиз с таким тегом в Gitea if echo "$EXISTING_GITEA_TAGS" | grep -q -x -F "$TAG_NAME"; then # -x: точное совпадение строки, -F: строка, а не регэкс # echo "Release tag $TAG_NAME already exists in Gitea, skipping." continue # Переходим к следующему релизу GitHub fi echo ">>> Found new release tag in GitHub: $TAG_NAME. Processing..." # Извлекаем данные релиза RELEASE_NAME=$(echo "$release_json" | jq -r '.name') RELEASE_BODY=$(echo "$release_json" | jq -r '.body // ""') IS_PRERELEASE=$(echo "$release_json" | jq -r '.prerelease') ASSETS=$(echo "$release_json" | jq -c '.assets[]') # 4. Создаем релиз в Gitea echo "Creating release $RELEASE_NAME (tag: $TAG_NAME) in Gitea..." CREATE_PAYLOAD=$(jq -n --arg tag "$TAG_NAME" --arg name "$RELEASE_NAME" --arg body "$RELEASE_BODY" --argjson prerelease "$IS_PRERELEASE" \ '{tag_name: $tag, name: $name, body: $body, prerelease: $prerelease}') # Используем --fail для обработки ошибок создания релиза (например, нет тега) GITEA_NEW_RELEASE_JSON=$(curl -sf --fail -X POST -H "$GITEA_AUTH_HEADER" -H "Content-Type: application/json" \ -d "$CREATE_PAYLOAD" \ "${GITEA_API_URL}/repos/${GITEA_REPO_PATH}/releases") if [ $? -ne 0 ]; then echo "Error creating release $TAG_NAME in Gitea. Maybe the tag '$TAG_NAME' is not synced yet or another issue occurred. Skipping this release." continue # Пропускаем этот релиз, попробуем в следующий раз fi GITEA_RELEASE_ID=$(echo "$GITEA_NEW_RELEASE_JSON" | jq -r '.id') echo "Gitea release created with ID: $GITEA_RELEASE_ID" # 5. Скачиваем и загружаем ассеты UPLOAD_FAILED_COUNT=0 if [ -n "$ASSETS" ]; then echo "$ASSETS" | while IFS= read -r asset_json; do ASSET_NAME=$(echo "$asset_json" | jq -r '.name') ASSET_URL=$(echo "$asset_json" | jq -r '.browser_download_url') TMP_ASSET_FILE="asset_${ASSET_NAME}" # Временное имя файла if download_asset "$ASSET_URL" "$TMP_ASSET_FILE"; then if ! upload_asset "$GITEA_REPO_PATH" "$GITEA_RELEASE_ID" "$TMP_ASSET_FILE"; then ((UPLOAD_FAILED_COUNT++)) # Оставляем временный файл при ошибке загрузки для анализа fi else echo "Skipping asset $ASSET_NAME due to download error." ((UPLOAD_FAILED_COUNT++)) fi done else echo "No assets found for release $TAG_NAME." fi if [ $UPLOAD_FAILED_COUNT -eq 0 ]; then echo "Successfully processed release $TAG_NAME with all assets." else echo "Processed release $TAG_NAME, but $UPLOAD_FAILED_COUNT asset(s) failed to download or upload." fi done # Конец цикла по релизам GitHub echo "Finished sync for $GITHUB_REPO_PATH -> $GITEA_REPO_PATH" echo "-------------------------------------" done < "$REPO_FILE" # Перенаправляем файл в цикл while echo "Release sync finished."