name: 'Save Cache' description: '' 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 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: | set -euo pipefail # Archive, Split, and Upload Cache echo "🔍 DEBUG: cache-save action started" echo "🔍 DEBUG: inputs.cache_path=${{ inputs.cache_path }}" echo "🔍 DEBUG: inputs.cache_key=${{ inputs.cache_key }}" echo "🔍 DEBUG: inputs.cache_bucket=${{ inputs.cache_bucket }}" echo "🔍 DEBUG: inputs.compression_level=${{ inputs.compression_level }}" echo "🔍 DEBUG: inputs.working_dir=${{ inputs.working_dir }}" echo "🔍 DEBUG: inputs.debug=${{ inputs.debug }}" echo "🔍 DEBUG: GITHUB_WORKSPACE=$GITHUB_WORKSPACE" echo "🔍 DEBUG: TARGET_REPO=$TARGET_REPO" if [ "${{ inputs.working_dir }}" != "" ]; then echo "🔍 DEBUG: Changing to working_dir: ${{ inputs.working_dir }}" cd "${{ inputs.working_dir }}" echo "🔍 DEBUG: Current directory after change: $(pwd)" else echo "🔍 DEBUG: No working_dir specified, using GITHUB_WORKSPACE" fi git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" echo "🔍 DEBUG: Git config set for github-actions[bot]" 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 "🔍 DEBUG: TAG_NAME=$TAG_NAME" echo "🔍 DEBUG: BASE_FILENAME=$BASE_FILENAME" echo "🔍 DEBUG: FILENAME=$FILENAME" echo "🔍 DEBUG: compression_level=${{ inputs.compression_level }}" echo "::group:: Checking Release State" echo "🔍 DEBUG: Checking if git tag '$TAG_NAME' exists" git fetch --tags --force echo "🔍 DEBUG: Git tags fetched successfully" if ! git rev-parse "$TAG_NAME" >/dev/null 2>&1; then echo "🔍 DEBUG: Tag '$TAG_NAME' not found, creating new release" 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" echo "🔍 DEBUG: Git tag created, attempting push" if git push origin "$TAG_NAME" --force; then echo "🔍 DEBUG: Push successful, creating GitHub release" gh release create "$TAG_NAME" --title "General Cache" --notes "Automated storage for build assets" --repo "$TARGET_REPO" || true else echo "🔍 DEBUG: Push failed (may be parallel job conflict)" echo "Push failed, tag might have been created by a parallel job. Continuing..." fi else echo "🔍 DEBUG: Tag '$TAG_NAME' already exists" echo "Release '$TAG_NAME' already exists. Ready for upload." fi echo "::endgroup::" echo "🔍 DEBUG: Release state check complete" echo "::group:: Archiving Path" echo "🔍 DEBUG: Archiving process started" echo "Target: ${{ inputs.cache_path }}" echo "🔍 DEBUG: Checking if target path exists" if [ ! -d "${{ inputs.cache_path }}" ] && [ ! -f "${{ inputs.cache_path }}" ]; then echo "🔍 DEBUG: Target path does not exist" echo "⚠️ Target path not found. Skipping cache save." echo "::endgroup::" else echo "🔍 DEBUG: Target path exists, proceeding with archive" DIR_NAME=$(dirname "${{ inputs.cache_path }}") BASE_NAME=$(basename "${{ inputs.cache_path }}") echo "🔍 DEBUG: DIR_NAME=$DIR_NAME" echo "🔍 DEBUG: BASE_NAME=$BASE_NAME" 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::" echo "::group:: Uploading Assets" echo "🔍 DEBUG: Upload process started" MAX_SIZE=2000000000 # ~1.86GB echo "🔍 DEBUG: MAX_SIZE=$MAX_SIZE bytes ($(( MAX_SIZE / 1024 / 1024 )) MB)" upload_with_retry() { local file=$1 local max_attempts=3 local timeout_duration="10m" echo "🔍 DEBUG: upload_with_retry() called for file=$file" for ((i=1; i<=max_attempts; i++)); do echo " Attempt $i for $file..." echo "🔍 DEBUG: Upload attempt $i for $file" if [ "${{ inputs.debug }}" = "true" ]; then echo "🔍 DEBUG: Running gh release upload in debug mode" if timeout "$timeout_duration" gh release upload "$TAG_NAME" "$file" --clobber --repo "$TARGET_REPO"; then echo " Successfully uploaded $file" echo "🔍 DEBUG: Upload successful for $file" return 0 fi else echo "🔍 DEBUG: Running gh release upload in silent mode" if timeout "$timeout_duration" gh release upload "$TAG_NAME" "$file" --clobber --repo "$TARGET_REPO" > /dev/null 2>&1; then echo " Successfully uploaded $file" echo "🔍 DEBUG: Upload successful for $file" return 0 fi fi echo " ⚠ Attempt $i failed or timed out after $timeout_duration. Retrying..." echo "🔍 DEBUG: Upload attempt $i failed, timeout=$timeout_duration" if [ "$i" -lt "$max_attempts" ]; then echo "🔍 DEBUG: Sleeping 5 seconds before retry" sleep 5 fi done echo "🔍 DEBUG: All $max_attempts attempts exhausted for $file" return 1 } # Fetch existing assets to clean up stale entries echo "🔍 DEBUG: Fetching existing assets for cleanup" ASSETS_URL="https://github.com/${TARGET_REPO}/releases/expanded_assets/$TAG_NAME" echo "🔍 DEBUG: ASSETS_URL=$ASSETS_URL" HTTP_RESPONSE=$(curl -sL -A "Mozilla/5.0" -w "%{http_code}" "$ASSETS_URL" -o assets.html || echo "404") echo "🔍 DEBUG: HTTP_RESPONSE=$HTTP_RESPONSE" if [ "$HTTP_RESPONSE" -eq 200 ]; then ALL_ASSETS=$(grep -oP "download/$TAG_NAME/\K[^\"' ]+" assets.html | sort || echo "") echo "🔍 DEBUG: Parsed ALL_ASSETS, count=$(echo "$ALL_ASSETS" | wc -l)" else ALL_ASSETS="" echo "🔍 DEBUG: Failed to fetch assets, ALL_ASSETS is empty" fi rm -f assets.html echo "🔍 DEBUG: assets.html removed" chunk_size="1500M" split_required=false echo "🔍 DEBUG: Evaluating split requirement" echo "🔍 DEBUG: FILE_SIZE=$FILE_SIZE, MAX_SIZE=$MAX_SIZE" if [ "$FILE_SIZE" -gt "$MAX_SIZE" ]; then split_required=true echo "🔍 DEBUG: Split required (FILE_SIZE > MAX_SIZE)" else echo "🔍 DEBUG: No split required (FILE_SIZE <= MAX_SIZE)" fi if [ "$split_required" = "true" ]; then echo "⚠ Splitting into ${chunk_size} chunks..." echo "🔍 DEBUG: Executing split command with chunk_size=1500M" split -b "$chunk_size" -a 2 "$FILENAME" "${FILENAME}.part" NEW_PARTS=(${FILENAME}.part*) NEW_PARTS_COUNT=${#NEW_PARTS[@]} echo "🔍 DEBUG: Split complete, NEW_PARTS_COUNT=$NEW_PARTS_COUNT" # Remove old single-file asset if switching to split parts if echo "$ALL_ASSETS" | grep -xF "${FILENAME}" >/dev/null 2>&1; then echo "🔍 DEBUG: Found old single-file asset, removing it" echo "Removing old single asset to make way for split parts: ${FILENAME}" gh release delete-asset "$TAG_NAME" "${FILENAME}" --repo "$TARGET_REPO" -y || true else echo "🔍 DEBUG: No old single-file asset to remove" fi # Clean up excess trailing parts from previous builds OLD_PARTS=$(echo "$ALL_ASSETS" | grep -F "${FILENAME}.part" || echo "") echo "🔍 DEBUG: OLD_PARTS count=$(echo "$OLD_PARTS" | grep -c . || echo 0)" if [ -n "$OLD_PARTS" ]; then OLD_PARTS_COUNT=$(echo "$OLD_PARTS" | wc -l) echo "🔍 DEBUG: OLD_PARTS_COUNT=$OLD_PARTS_COUNT, NEW_PARTS_COUNT=$NEW_PARTS_COUNT" 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 "🔍 DEBUG: Removing old part: $asset" echo "Removing leftover trailing part: $asset" gh release delete-asset "$TAG_NAME" "$asset" --repo "$TARGET_REPO" -y || true done else echo "🔍 DEBUG: No excess parts to clean up" fi fi for part in "${FILENAME}".part*; do echo "🔍 DEBUG: Uploading part: $part" echo "Uploading $part..." if ! upload_with_retry "$part"; then echo "::error::Failed to upload part $part after multiple attempts." echo "🔍 DEBUG: CRITICAL - Upload failed for $part" exit 1 fi echo "🔍 DEBUG: Successfully uploaded $part, removing local copy" rm -f "$part" done echo "🔍 DEBUG: All parts uploaded, removing original archive" rm -rf "$FILENAME" else # Remove old split parts if new build fits in a single file echo "🔍 DEBUG: Checking for old split parts to clean up" 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}..." echo "🔍 DEBUG: Found old split parts, removing them" while read -r asset; do [ -z "$asset" ] && continue echo "🔍 DEBUG: Removing ghost split part: $asset" echo "Removing ghost split part: $asset" gh release delete-asset "$TAG_NAME" "$asset" --repo "$TARGET_REPO" -y || true done <<< "$OLD_PARTS" else echo "🔍 DEBUG: No old split parts to clean up" fi echo "🔍 DEBUG: Uploading single file: $FILENAME" echo "Uploading $FILENAME..." if ! upload_with_retry "$FILENAME"; then echo "::error::Failed to upload $FILENAME after multiple attempts." echo "🔍 DEBUG: CRITICAL - Upload failed for $FILENAME" exit 1 fi echo "🔍 DEBUG: Successfully uploaded $FILENAME, removing local copy" rm -f "$FILENAME" fi # Cleanup any leftover archive artifacts (single-file or split parts) echo "🔍 DEBUG: Final cleanup phase" if [ "${{ inputs.debug }}" = "true" ]; then echo "Cleaning archive artifacts: ${BASE_FILENAME}*" echo "🔍 DEBUG: Listing files matching ${BASE_FILENAME}*" ls -la "${BASE_FILENAME}"* 2>/dev/null || true fi rm -f "${BASE_FILENAME}"* || true echo "🔍 DEBUG: Cleanup complete" echo "::endgroup::" echo "🔍 DEBUG: cache-save action finished successfully" fi