name: 'Save Cache' 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' github_token: description: 'GitHub Token' required: true compression_level: description: 'Compression level (0-9). 0 = No compression, 1 = Fast, 19 = Best.' required: false type: number default: 6 working_dir: description: 'Path where .git folder exists (use only if you clone multiple repos)' required: false type: string delete_after_upload: description: 'Delete Source files after uploading' required: false type: boolean default: false debug: description: 'Enable Logs' required: false type: boolean default: false runs: using: 'composite' steps: - name: Archive, Split, and Upload Cache shell: bash env: GH_TOKEN: ${{ inputs.github_token }} TARGET_REPO: "${{ github.repository }}" run: | # Archive, Split, and Upload Cache if [ "${{ inputs.debug }}" = "true" ]; then set -x; fi if [ "${{ inputs.working_dir }}" != "" ]; then cd "${{ inputs.working_dir }}" fi 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 tag..." 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 echo "Tag pushed" else echo "Push failed, tag might have been created by a parallel job. Continuing..." fi else echo "Tag '$TAG_NAME' exists locally." fi if ! gh release view "$TAG_NAME" --repo "$TARGET_REPO" >/dev/null 2>&1; then echo "Release '$TAG_NAME' not found. Creating release..." # ensure tag is pushed before creating release git push origin "$TAG_NAME" --force >/dev/null 2>&1 || true gh release create "$TAG_NAME" --title "General Cache" --notes "Automated storage for build assets" --prerelease --repo "$TARGET_REPO" || true # verify if gh release view "$TAG_NAME" --repo "$TARGET_REPO" >/dev/null 2>&1; then echo "Release '$TAG_NAME' created." else echo "⚠ Failed to create release '$TAG_NAME' — will retry upload anyway" gh release view "$TAG_NAME" --repo "$TARGET_REPO" 2>&1 | head -n 20 || true 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.debug }}" = "true" ]; then if [ "${{ inputs.compression_level }}" -eq 0 ]; then tar --posix -h -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" else tar --posix -h -I "zstd -T0 -${{ inputs.compression_level }}" -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" fi else 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 fi FILE_SIZE=$(stat -c%s "$FILENAME") echo "Archive created: $FILENAME ($FILE_SIZE bytes)" echo "::endgroup::" ASSETS_URL="https://github.com/${TARGET_REPO}/releases/expanded_assets/$TAG_NAME" echo "::group:: Fetching Asset List" HTTP_RESPONSE=$(curl -sL -A "Mozilla/5.0" -w "%{http_code}" "$ASSETS_URL" -o assets.html || echo "404") if [ "$HTTP_RESPONSE" -eq 200 ]; then ALL_ASSETS=$(grep -oP "download/$TAG_NAME/\K[^\"' ]+" assets.html | sort || echo "") if [ "${{ inputs.debug }}" = "true" ]; then echo "Assets Found: $ALL_ASSETS" fi else echo "Status $HTTP_RESPONSE: Failed to fetch assets from scraper endpoint." ALL_ASSETS="" fi rm -f assets.html 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..." echo " File: $file ($(stat -c%s "$file" 2>/dev/null || echo "?") bytes) -> tag $TAG_NAME repo $TARGET_REPO" set +e tmp_log=$(mktemp) if [ "${{ inputs.debug }}" = "true" ]; then set -x timeout 10m gh release upload "$TAG_NAME" "$file" --clobber --repo "$TARGET_REPO" 2>&1 | tee "$tmp_log"; rc=${PIPESTATUS[0]} set +x else timeout 10m gh release upload "$TAG_NAME" "$file" --clobber --repo "$TARGET_REPO" >"$tmp_log" 2>&1; rc=$? cat "$tmp_log" fi set -e if [ $rc -eq 0 ]; then echo " Successfully uploaded $file" rm -f "$tmp_log" return 0 fi if [ $rc -eq 124 ]; then echo " ⚠ Attempt $i timed out after 10m (timeout exit 124)" else echo " ⚠ Attempt $i failed with exit $rc" fi echo " --- gh upload log ---" cat "$tmp_log" || true echo " --- end log ---" rm -f "$tmp_log" echo " GH_TOKEN set: $([ -n "$GH_TOKEN" ] && echo yes || echo no) | tag exists: $(git rev-parse "$TAG_NAME" >/dev/null 2>&1 && echo yes || echo no)" gh auth status 2>&1 | head -n 20 || true 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" NEW_PARTS=(${FILENAME}.part*) NEW_PARTS_COUNT=${#NEW_PARTS[@]} if echo "$ALL_ASSETS" | grep -xF "${FILENAME}" >/dev/null 2>&1; then echo "Removing old single asset to make way for split parts: ${FILENAME}" gh release delete-asset "$TAG_NAME" "${FILENAME}" --repo "$TARGET_REPO" -y || true fi OLD_PARTS=$(echo "$ALL_ASSETS" | grep -F "${FILENAME}.part" || echo "") if [ -n "$OLD_PARTS" ]; then OLD_PARTS_COUNT=$(echo "$OLD_PARTS" | wc -l) else OLD_PARTS_COUNT=0 fi if [ "$OLD_PARTS_COUNT" -gt "$NEW_PARTS_COUNT" ]; then echo "Old build had $OLD_PARTS_COUNT parts, new has $NEW_PARTS_COUNT. Cleaning up excess parts..." EXT_PARTS=$(echo "$OLD_PARTS" | awk -v skip="$NEW_PARTS_COUNT" 'NR>skip') for asset in $EXT_PARTS; do echo "Removing leftover trailing part: $asset" gh release delete-asset "$TAG_NAME" "$asset" --repo "$TARGET_REPO" -y || true done fi 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 OLD_PARTS=$(echo "$ALL_ASSETS" | grep -F "${FILENAME}.part" || echo "") if [ -n "$OLD_PARTS" ]; then echo "New build shrunk to single file. Cleaning up old split parts for ${FILENAME}..." while read -r asset; do [ -z "$asset" ] && continue echo "Removing ghost split part: $asset" gh release delete-asset "$TAG_NAME" "$asset" --repo "$TARGET_REPO" -y || true done <<< "$OLD_PARTS" fi 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 if [ "${{ inputs.delete_after_upload }}" = "true" ]; then rm -rf "${{ inputs.cache_path }}" fi echo "::endgroup::" fi