name: 'Restore Cache' description: 'Restores cache from GitHub Releases.' inputs: cache_path: description: 'Path where cache should be restored' required: true type: string cache_key: description: 'Primary key to restore cache' required: true type: string restore_keys: description: 'Multiline list of keys (fallback prefixes)' required: false type: string default: "" cache_bucket: description: 'The tag name for the release (e.g., general-cache)' required: false default: 'general-cache' compression_level: description: 'Compression level (0-9). Kept for compatibility with save actions.' required: false type: number default: 6 debug: description: 'Enable debug logging' required: false type: boolean default: false outputs: cache-hit: description: 'Returns "true" if a cache was found and restored, otherwise "false"' value: ${{ steps.restore.outputs.cache-hit }} runs: using: 'composite' steps: - name: Detect and Download Cache id: restore shell: bash env: TARGET_REPO: "${{ github.repository }}" run: | set -euo pipefail if [ "${{ inputs.debug }}" = "true" ]; then set -x fi cd "$GITHUB_WORKSPACE" TAG_NAME="${{ inputs.cache_bucket }}" BASE_URL="https://github.com/${TARGET_REPO}/releases/download/$TAG_NAME" ASSETS_URL="https://github.com/${TARGET_REPO}/releases/expanded_assets/$TAG_NAME" ARIA2_OPTS=( "-x16" "-s16" "-k1M" "-j5" "--file-allocation=none" "--header=User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" "--header=Accept: */*" "--header=Connection: keep-alive" ) if [ "${{ inputs.debug }}" != "true" ]; then ARIA2_OPTS+=("--quiet" "--summary-interval=0" "--console-log-level=error") fi 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 -u || true) else echo "Status $HTTP_RESPONSE: Failed to fetch assets from scraper endpoint." ALL_ASSETS="" fi rm -f assets.html if [ -z "$ALL_ASSETS" ]; then echo "No assets found in bucket '$TAG_NAME'. Skipping search." echo "cache-hit=false" >> "$GITHUB_OUTPUT" echo "::endgroup::" exit 0 fi echo "Successfully fetched asset list. Has $(echo "$ALL_ASSETS" | wc -l) assets." echo "::endgroup::" SEARCH_LIST=$(printf "%s\n%s" "${{ inputs.cache_key }}" "${{ inputs.restore_keys }}" | sed '/^$/d') FOUND=false download_asset() { local filename="$1" local url="$2" local max_retries=3 local attempt=1 while [ "$attempt" -le "$max_retries" ]; do echo " Attempt $attempt: Downloading $filename..." rm -f "$filename" if command -v gh >/dev/null 2>&1; then if timeout 10m gh release download "$TAG_NAME" --repo "$TARGET_REPO" --pattern "$filename" -D . >/dev/null 2>&1; then [ -s "$filename" ] && return 0 fi fi if timeout 10m aria2c "${ARIA2_OPTS[@]}" --retry-wait=10 --max-tries=10 -o "$filename" "$url"; then [ -s "$filename" ] && return 0 echo " ⚠️ Downloaded file is empty. Retrying..." rm -f "$filename" fi echo " ⚠️ Download failed or timed out. Retrying in 5s..." sleep 5 attempt=$((attempt + 1)) done return 1 } while read -r KEY; do [ -z "$KEY" ] && continue echo "::group:: Searching Prefix: $KEY" MATCH=$(echo "$ALL_ASSETS" | grep -E "^cache-$KEY.*\.(tzst|tar)(\.part[a-z]{2})?$" | sort -r | head -n 1 || true) if [ -z "$MATCH" ]; then echo "Not found." echo "::endgroup::" continue fi BASE_FILENAME=$(echo "$MATCH" | sed -E 's/\.(tzst|tar)(\.part[a-z]{2})?$//') EXT=$(echo "$MATCH" | grep -oE '\.(tzst|tar)' | head -n 1) IS_SPLIT=false COMPRESSED=false case "$MATCH" in *.part*) IS_SPLIT=true ;; esac [ "$EXT" = ".tzst" ] && COMPRESSED=true FOUND=true echo "✅ Hit! Restoring $BASE_FILENAME$EXT" echo "::endgroup::" echo "::group:: Downloading Cache" if [ "$IS_SPLIT" = "true" ]; then for part in {a..z}{a..z}; do PART_NAME="$BASE_FILENAME$EXT.part$part" if echo "$ALL_ASSETS" | grep -q "^$PART_NAME$"; then if ! download_asset "$PART_NAME" "$BASE_URL/$PART_NAME"; then echo "::error::Failed to download $PART_NAME after multiple retries." exit 1 fi else break fi done else if ! download_asset "$BASE_FILENAME$EXT" "$BASE_URL/$BASE_FILENAME$EXT"; then echo "::error::Failed to download $BASE_FILENAME$EXT after multiple retries." exit 1 fi fi echo "::endgroup::" echo "::group:: Extracting Cache" mkdir -p "${{ inputs.cache_path }}" FINAL_ARCHIVE="$BASE_FILENAME$EXT" EXTRACT_DIR="$(dirname "${{ inputs.cache_path }}")" if [ "$IS_SPLIT" = "true" ]; then PARTS=$(ls "$FINAL_ARCHIVE.part"* | sort) if [ "$COMPRESSED" = "true" ]; then cat $PARTS | tar -I 'zstd -d -T0' -xf - -C "$EXTRACT_DIR" else cat $PARTS | tar -xf - -C "$EXTRACT_DIR" fi rm -f $PARTS else if [ "$COMPRESSED" = "true" ]; then tar -I 'zstd -d -T0' -xf "$FINAL_ARCHIVE" -C "$EXTRACT_DIR" else tar -xf "$FINAL_ARCHIVE" -C "$EXTRACT_DIR" fi rm -f "$FINAL_ARCHIVE" fi echo "✅ Restore complete." echo "::endgroup::" break done <<< "$SEARCH_LIST" if [ "$FOUND" = "false" ]; then echo "⚠️ No cache matches found. Proceeding with fresh run." echo "cache-hit=false" >> "$GITHUB_OUTPUT" else echo "cache-hit=true" >> "$GITHUB_OUTPUT" fi