name: 'Save Cache' description: 'Archives and uploads cache to GitHub Releases with intelligent splitting and retry logic' inputs: cache_path: description: 'Path where cache is saved' required: true type: string cache_key: description: 'Unique key for the cache' required: true type: string cache_bucket: description: 'The tag name for the release (e.g., general-cache)' required: true default: 'general-cache' compression_level: description: 'Compression level (0-9). 0 = No compression, 1 = Fast, 19 = Best.' required: false type: number default: 6 github_token: description: 'GitHub token for release upload authentication' required: true type: string runs: using: 'composite' steps: - name: Archive, Split, and Upload Cache shell: bash env: GITHUB_TOKEN: ${{ inputs.github_token }} TARGET_REPO: "${{ github.repository }}" run: | # Archive, Split, and Upload Cache git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" TAG_NAME="${{ inputs.cache_bucket }}" BASE_FILENAME="cache-${{ inputs.cache_key }}" if [ "${{ inputs.compression_level }}" -eq 0 ]; then FILENAME="$BASE_FILENAME.tar" else FILENAME="$BASE_FILENAME.tzst" fi echo "::group:: Checking Release State" git fetch --tags --force if ! git rev-parse "$TAG_NAME" >/dev/null 2>&1; then echo "Tag '$TAG_NAME' not found. Creating backdated release..." GIT_COMMITTER_DATE="2015-01-01T12:00:00" git tag -a "$TAG_NAME" -m "General Cache Storage" if git push origin "$TAG_NAME" --force; then gh release create "$TAG_NAME" --title "General Cache" --notes "Automated storage for build assets" --repo "$TARGET_REPO" || true else echo "Push failed, tag might have been created by a parallel job. Continuing..." fi else echo "Release '$TAG_NAME' already exists. Ready for upload." fi echo "::endgroup::" echo "::group:: Archiving Path" echo "Target: ${{ inputs.cache_path }}" if [ ! -d "${{ inputs.cache_path }}" ] && [ ! -f "${{ inputs.cache_path }}" ]; then echo "⚠️ Target path not found. Skipping cache save." echo "::endgroup::" else DIR_NAME=$(dirname "${{ inputs.cache_path }}") BASE_NAME=$(basename "${{ inputs.cache_path }}") if [ "${{ inputs.compression_level }}" -eq 0 ]; then tar --posix -h -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" > /dev/null 2>&1 else tar --posix -h -I "zstd -T0 -${{ inputs.compression_level }}" -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" > /dev/null 2>&1 fi FILE_SIZE=$(stat -c%s "$FILENAME") echo "Archive created: $FILENAME ($FILE_SIZE bytes)" echo "::endgroup::" echo "::group:: Uploading Assets" MAX_SIZE=2000000000 # ~1.86GB upload_with_retry() { local file=$1 local max_attempts=3 for ((i=1; i<=max_attempts; i++)); do echo " Attempt $i for $file..." if timeout 10m gh release upload "$TAG_NAME" "$file" --clobber --repo "$TARGET_REPO" > /dev/null 2>&1; then echo " Successfully uploaded $file" return 0 fi echo " ⚠ Attempt $i failed or timed out. Retrying..." sleep 5 done return 1 } if [ "$FILE_SIZE" -gt "$MAX_SIZE" ]; then echo "⚠ File > 2GB. Splitting..." split -b 1500M -a 2 "$FILENAME" "${FILENAME}.part" for part in "${FILENAME}".part*; do echo "Uploading $part..." if ! upload_with_retry "$part"; then echo "::error::Failed to upload part $part after multiple attempts." exit 1 fi rm -f "$part" done rm -rf "$FILENAME" else echo "Uploading $FILENAME..." if ! upload_with_retry "$FILENAME"; then echo "::error::Failed to upload $FILENAME after multiple attempts." exit 1 fi rm -f "$FILENAME" fi echo "::endgroup::" fi