From fde72d9275937c85c4441c193576077eeba09c83 Mon Sep 17 00:00:00 2001 From: zhenlonghe Date: Fri, 24 Oct 2025 18:27:10 +0800 Subject: [PATCH] chore(workflow): replace hourly GHCR cleanup with daily retention policy - Removed the existing hourly cleanup workflow for GHCR packages. - Introduced a new daily retention workflow that deletes container versions older than 30 days while keeping the 5 most recent versions. - Updated the scheduling to run at UTC 00:00 for better resource management. --- .github/workflows/ghcr-cleanup.yml | 242 --------------------------- .github/workflows/ghcr-retention.yml | 30 ++++ 2 files changed, 30 insertions(+), 242 deletions(-) delete mode 100644 .github/workflows/ghcr-cleanup.yml create mode 100644 .github/workflows/ghcr-retention.yml diff --git a/.github/workflows/ghcr-cleanup.yml b/.github/workflows/ghcr-cleanup.yml deleted file mode 100644 index cb4060e58..000000000 --- a/.github/workflows/ghcr-cleanup.yml +++ /dev/null @@ -1,242 +0,0 @@ -name: 'Clean up GHCR Packages' - -on: - # 每小时执行一次(快速清理模式) - schedule: - - cron: '0 * * * *' - # Docker 发布完成后自动触发 - workflow_run: - workflows: ['Docker Release'] - types: - - completed - # 支持手动触发 - workflow_dispatch: - inputs: - dry_run: - description: 'Dry run mode (preview only, no actual deletion)' - required: false - type: boolean - default: false - retention_days: - description: 'Number of days to keep package versions' - required: false - type: number - default: 30 - -jobs: - cleanup: - runs-on: ubuntu-latest - timeout-minutes: 120 - permissions: - packages: write - contents: read - steps: - - name: Checkout repository - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - - name: Cleanup old GHCR package versions - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }} - RETENTION_DAYS: ${{ github.event.inputs.retention_days || '30' }} - PACKAGE_NAME: rsshub - OWNER: ${{ github.repository_owner }} - run: | - set -e - - echo "==================================================" - echo "GHCR Package Cleanup (Fast Mode - Hourly)" - echo "==================================================" - echo "Package: ghcr.io/${OWNER}/${PACKAGE_NAME}" - echo "Retention period: ${RETENTION_DAYS} days" - echo "Dry run mode: ${DRY_RUN}" - echo "Target: ~900 deletions per hour" - echo "Schedule: Every hour (56K versions ~66 hours)" - echo "==================================================" - echo "" - - # 计算截止日期(UTC时间) - CUTOFF_DATE=$(date -u -d "${RETENTION_DAYS} days ago" +%Y-%m-%dT%H:%M:%SZ) - CUTOFF_TIMESTAMP=$(date -d "${CUTOFF_DATE}" +%s) - - echo "Current time (UTC): $(date -u +%Y-%m-%dT%H:%M:%SZ)" - echo "Cutoff date (UTC): ${CUTOFF_DATE}" - echo "" - - # 创建临时目录 - TEMP_DIR=$(mktemp -d) - - echo "Fetching package versions (fast mode)..." - - # 快速获取前1000个版本(最多10页) - gh api --paginate \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/orgs/${OWNER}/packages/container/${PACKAGE_NAME}/versions?per_page=100" \ - --jq '.[] | select((.created_at | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) < '"$CUTOFF_TIMESTAMP"') | .id' \ - 2>/dev/null > "${TEMP_DIR}/to_delete.txt" || \ - gh api --paginate \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/users/${OWNER}/packages/container/${PACKAGE_NAME}/versions?per_page=100" \ - --jq '.[] | select((.created_at | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) < '"$CUTOFF_TIMESTAMP"') | .id' \ - > "${TEMP_DIR}/to_delete.txt" - - TOTAL_TO_DELETE=$(wc -l < "${TEMP_DIR}/to_delete.txt" | tr -d ' ') - - # 限制每次运行删除数量(避免超时,保持在1小时内) - MAX_PER_RUN=850 - - if [[ $TOTAL_TO_DELETE -gt $MAX_PER_RUN ]]; then - echo "Found ${TOTAL_TO_DELETE} versions to delete" - echo "Limiting to ${MAX_PER_RUN} this run (to stay within rate limits)" - head -n $MAX_PER_RUN "${TEMP_DIR}/to_delete.txt" > "${TEMP_DIR}/to_delete_limited.txt" - mv "${TEMP_DIR}/to_delete_limited.txt" "${TEMP_DIR}/to_delete.txt" - DELETED_COUNT=$MAX_PER_RUN - REMAINING=$((TOTAL_TO_DELETE - MAX_PER_RUN)) - else - DELETED_COUNT=$TOTAL_TO_DELETE - REMAINING=0 - fi - - echo "==================================================" - echo "Analysis Complete" - echo "==================================================" - echo "Versions to delete this run: ${DELETED_COUNT}" - if [[ $REMAINING -gt 0 ]]; then - echo "Remaining for future runs: ${REMAINING}" - RUNS_NEEDED=$(( (REMAINING + MAX_PER_RUN - 1) / MAX_PER_RUN )) - echo "Estimated additional runs needed: ${RUNS_NEEDED}" - fi - echo "" - - if [[ $DELETED_COUNT -eq 0 ]]; then - echo "✅ No versions need to be deleted." - rm -rf "$TEMP_DIR" - exit 0 - fi - - # 显示一些样本 - echo "Sample of versions to delete:" - head -n 5 "${TEMP_DIR}/to_delete.txt" | while read -r version_id; do - echo " Version ID: ${version_id}" - done - - if [[ $DELETED_COUNT -gt 5 ]]; then - echo " ... and $((DELETED_COUNT - 5)) more versions" - fi - echo "" - - if [[ "$DRY_RUN" == "true" ]]; then - echo "⚠️ DRY RUN MODE - No versions were actually deleted" - echo " To perform actual deletion, set dry_run to false" - rm -rf "$TEMP_DIR" - exit 0 - fi - - echo "Starting deletion (optimized for speed)..." - echo "⚠️ Using 4-second intervals (900 requests/hour)" - echo "" - - SUCCESS_COUNT=0 - ERROR_COUNT=0 - PROCESSED=0 - START_TIME=$(date +%s) - - # 串行删除,带重试机制 - while read -r version_id; do - PROCESSED=$((PROCESSED + 1)) - RETRY_COUNT=0 - MAX_RETRIES=3 - SUCCESS=false - - while [[ $RETRY_COUNT -lt $MAX_RETRIES ]] && [[ "$SUCCESS" == "false" ]]; do - # 尝试组织 API - DELETE_RESULT=$(gh api \ - --method DELETE \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/orgs/${OWNER}/packages/container/${PACKAGE_NAME}/versions/${version_id}" 2>&1 || echo "ORG_DELETE_FAILED") - - # 如果组织 API 失败,尝试用户 API - if [[ "$DELETE_RESULT" == "ORG_DELETE_FAILED" ]] || echo "$DELETE_RESULT" | grep -q "Not Found"; then - DELETE_RESULT=$(gh api \ - --method DELETE \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "/users/${OWNER}/packages/container/${PACKAGE_NAME}/versions/${version_id}" 2>&1 || echo "DELETE_FAILED") - fi - - # 检查是否成功 - if [[ "$DELETE_RESULT" == *"FAILED"* ]] || [[ -n "$DELETE_RESULT" ]]; then - # 检查是否是速率限制错误 - if echo "$DELETE_RESULT" | grep -q "rate limit"; then - RETRY_COUNT=$((RETRY_COUNT + 1)) - if [[ $RETRY_COUNT -lt $MAX_RETRIES ]]; then - echo " Rate limit hit, waiting 60s before retry ${RETRY_COUNT}/${MAX_RETRIES}..." - sleep 60 - else - echo " Version ${version_id}: Failed after ${MAX_RETRIES} retries (rate limit)" - echo "ERROR:${version_id}:Rate limit exceeded" >> "${TEMP_DIR}/errors.log" - ERROR_COUNT=$((ERROR_COUNT + 1)) - fi - else - echo " Version ${version_id}: Failed - ${DELETE_RESULT}" - echo "ERROR:${version_id}:${DELETE_RESULT}" >> "${TEMP_DIR}/errors.log" - ERROR_COUNT=$((ERROR_COUNT + 1)) - break - fi - else - SUCCESS=true - SUCCESS_COUNT=$((SUCCESS_COUNT + 1)) - fi - done - - # 显示进度(每50个版本) - if [[ $((PROCESSED % 50)) -eq 0 ]]; then - CURRENT_TIME=$(date +%s) - ELAPSED=$((CURRENT_TIME - START_TIME)) - RATE=$((PROCESSED * 3600 / ELAPSED)) - echo "Progress: ${PROCESSED}/${DELETED_COUNT} (Success: ${SUCCESS_COUNT}, Errors: ${ERROR_COUNT}) - Rate: ${RATE}/hour" - fi - - # 请求间延迟(4秒 = 900 requests/hour,低于1000限制) - sleep 4 - done < "${TEMP_DIR}/to_delete.txt" - - END_TIME=$(date +%s) - TOTAL_TIME=$((END_TIME - START_TIME)) - ACTUAL_RATE=$((PROCESSED * 3600 / TOTAL_TIME)) - - echo "" - echo "==================================================" - echo "Cleanup Complete" - echo "==================================================" - echo "Versions successfully deleted: ${SUCCESS_COUNT}" - echo "Versions failed to delete: ${ERROR_COUNT}" - echo "Total time: ${TOTAL_TIME} seconds" - echo "Actual rate: ${ACTUAL_RATE} deletions/hour" - - if [[ $REMAINING -gt 0 ]]; then - echo "" - echo "ℹ️ Note: This was a partial cleanup run." - echo " Remaining versions will be deleted in the next scheduled run." - echo " Estimated runs needed: $(( (REMAINING + MAX_PER_RUN - 1) / MAX_PER_RUN ))" - fi - - if [[ $ERROR_COUNT -gt 0 ]]; then - echo "" - echo "Errors encountered:" - head -n 5 "${TEMP_DIR}/errors.log" 2>/dev/null || echo "(No error log found)" - fi - - echo "==================================================" - - # 清理临时文件 - rm -rf "$TEMP_DIR" - - # 如果错误率超过50%,返回失败状态 - if [[ $ERROR_COUNT -gt $((SUCCESS_COUNT / 2)) ]]; then - echo "⚠️ High error rate detected" - exit 1 - fi \ No newline at end of file diff --git a/.github/workflows/ghcr-retention.yml b/.github/workflows/ghcr-retention.yml new file mode 100644 index 000000000..3bbcbeeb6 --- /dev/null +++ b/.github/workflows/ghcr-retention.yml @@ -0,0 +1,30 @@ +name: 'Clean up GHCR Packages' + +on: + # 每天 UTC 00:00 执行 + schedule: + - cron: '0 0 * * *' + # Docker 发布完成后自动触发 + workflow_run: + workflows: ['Docker Release'] + types: + - completed + # 支持手动触发 + workflow_dispatch: + +jobs: + cleanup: + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: + - name: Delete old container versions (30+ days) + uses: snok/container-retention-policy@v3.0.1 + with: + account: ${{ github.repository_owner }} + token: ${{ secrets.GITHUB_TOKEN }} + image-names: "rsshub" + cut-off: 30d + keep-n-most-recent: 5 + dry-run: false \ No newline at end of file