action.yml 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. name: 'Restore Cache'
  2. description: 'Restores cache from GitHub Releases.'
  3. inputs:
  4. cache_path:
  5. description: 'Path where cache should be restored'
  6. required: true
  7. type: string
  8. cache_key:
  9. description: 'Primary key to restore cache'
  10. required: true
  11. type: string
  12. restore_keys:
  13. description: 'Multiline list of keys (fallback prefixes)'
  14. required: false
  15. type: string
  16. default: ""
  17. cache_bucket:
  18. description: 'The tag name for the release (e.g., general-cache)'
  19. required: false
  20. default: 'general-cache'
  21. compression_level:
  22. description: 'Compression level (0-9). Kept for compatibility with save actions.'
  23. required: false
  24. type: number
  25. default: 6
  26. debug:
  27. description: 'Enable debug logging'
  28. required: false
  29. type: boolean
  30. default: false
  31. outputs:
  32. cache-hit:
  33. description: 'Returns "true" if a cache was found and restored, otherwise "false"'
  34. value: ${{ steps.restore.outputs.cache-hit }}
  35. runs:
  36. using: 'composite'
  37. steps:
  38. - name: Detect and Download Cache
  39. id: restore
  40. shell: bash
  41. env:
  42. TARGET_REPO: "${{ github.repository }}"
  43. run: |
  44. set -euo pipefail
  45. if [ "${{ inputs.debug }}" = "true" ]; then
  46. set -x
  47. fi
  48. cd "$GITHUB_WORKSPACE"
  49. TAG_NAME="${{ inputs.cache_bucket }}"
  50. BASE_URL="https://github.com/${TARGET_REPO}/releases/download/$TAG_NAME"
  51. ASSETS_URL="https://github.com/${TARGET_REPO}/releases/expanded_assets/$TAG_NAME"
  52. ARIA2_OPTS=(
  53. "-x16" "-s16" "-k1M" "-j5" "--file-allocation=none"
  54. "--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"
  55. "--header=Accept: */*"
  56. "--header=Connection: keep-alive"
  57. )
  58. if [ "${{ inputs.debug }}" != "true" ]; then
  59. ARIA2_OPTS+=("--quiet" "--summary-interval=0" "--console-log-level=error")
  60. fi
  61. echo "::group:: Fetching Asset List"
  62. HTTP_RESPONSE=$(curl -sL -A "Mozilla/5.0" -w "%{http_code}" "$ASSETS_URL" -o assets.html || echo "404")
  63. if [ "$HTTP_RESPONSE" -eq 200 ]; then
  64. ALL_ASSETS=$(grep -oP "download/$TAG_NAME/\K[^\"' ]+" assets.html | sort -u || true)
  65. else
  66. echo "Status $HTTP_RESPONSE: Failed to fetch assets from scraper endpoint."
  67. ALL_ASSETS=""
  68. fi
  69. rm -f assets.html
  70. if [ -z "$ALL_ASSETS" ]; then
  71. echo "No assets found in bucket '$TAG_NAME'. Skipping search."
  72. echo "cache-hit=false" >> "$GITHUB_OUTPUT"
  73. echo "::endgroup::"
  74. exit 0
  75. fi
  76. echo "Successfully fetched asset list. Has $(echo "$ALL_ASSETS" | wc -l) assets."
  77. echo "::endgroup::"
  78. SEARCH_LIST=$(printf "%s\n%s" "${{ inputs.cache_key }}" "${{ inputs.restore_keys }}" | sed '/^$/d')
  79. FOUND=false
  80. download_asset() {
  81. local filename="$1"
  82. local url="$2"
  83. local max_retries=3
  84. local attempt=1
  85. while [ "$attempt" -le "$max_retries" ]; do
  86. echo " Attempt $attempt: Downloading $filename..."
  87. rm -f "$filename"
  88. if command -v gh >/dev/null 2>&1; then
  89. if timeout 10m gh release download "$TAG_NAME" --repo "$TARGET_REPO" --pattern "$filename" -D . >/dev/null 2>&1; then
  90. [ -s "$filename" ] && return 0
  91. fi
  92. fi
  93. if timeout 10m aria2c "${ARIA2_OPTS[@]}" --retry-wait=10 --max-tries=10 -o "$filename" "$url"; then
  94. [ -s "$filename" ] && return 0
  95. echo " ⚠️ Downloaded file is empty. Retrying..."
  96. rm -f "$filename"
  97. fi
  98. echo " ⚠️ Download failed or timed out. Retrying in 5s..."
  99. sleep 5
  100. attempt=$((attempt + 1))
  101. done
  102. return 1
  103. }
  104. while read -r KEY; do
  105. [ -z "$KEY" ] && continue
  106. echo "::group:: Searching Prefix: $KEY"
  107. MATCH=$(echo "$ALL_ASSETS" | grep -E "^cache-$KEY.*\.(tzst|tar)(\.part[a-z]{2})?$" | sort -r | head -n 1 || true)
  108. if [ -z "$MATCH" ]; then
  109. echo "Not found."
  110. echo "::endgroup::"
  111. continue
  112. fi
  113. BASE_FILENAME=$(echo "$MATCH" | sed -E 's/\.(tzst|tar)(\.part[a-z]{2})?$//')
  114. EXT=$(echo "$MATCH" | grep -oE '\.(tzst|tar)' | head -n 1)
  115. IS_SPLIT=false
  116. COMPRESSED=false
  117. case "$MATCH" in
  118. *.part*) IS_SPLIT=true ;;
  119. esac
  120. [ "$EXT" = ".tzst" ] && COMPRESSED=true
  121. FOUND=true
  122. echo "✅ Hit! Restoring $BASE_FILENAME$EXT"
  123. echo "::endgroup::"
  124. echo "::group:: Downloading Cache"
  125. if [ "$IS_SPLIT" = "true" ]; then
  126. for part in {a..z}{a..z}; do
  127. PART_NAME="$BASE_FILENAME$EXT.part$part"
  128. if echo "$ALL_ASSETS" | grep -q "^$PART_NAME$"; then
  129. if ! download_asset "$PART_NAME" "$BASE_URL/$PART_NAME"; then
  130. echo "::error::Failed to download $PART_NAME after multiple retries."
  131. exit 1
  132. fi
  133. else
  134. break
  135. fi
  136. done
  137. else
  138. if ! download_asset "$BASE_FILENAME$EXT" "$BASE_URL/$BASE_FILENAME$EXT"; then
  139. echo "::error::Failed to download $BASE_FILENAME$EXT after multiple retries."
  140. exit 1
  141. fi
  142. fi
  143. echo "::endgroup::"
  144. echo "::group:: Extracting Cache"
  145. mkdir -p "${{ inputs.cache_path }}"
  146. FINAL_ARCHIVE="$BASE_FILENAME$EXT"
  147. EXTRACT_DIR="$(dirname "${{ inputs.cache_path }}")"
  148. if [ "$IS_SPLIT" = "true" ]; then
  149. PARTS=$(ls "$FINAL_ARCHIVE.part"* | sort)
  150. if [ "$COMPRESSED" = "true" ]; then
  151. cat $PARTS | tar -I 'zstd -d -T0' -xf - -C "$EXTRACT_DIR"
  152. else
  153. cat $PARTS | tar -xf - -C "$EXTRACT_DIR"
  154. fi
  155. rm -f $PARTS
  156. else
  157. if [ "$COMPRESSED" = "true" ]; then
  158. tar -I 'zstd -d -T0' -xf "$FINAL_ARCHIVE" -C "$EXTRACT_DIR"
  159. else
  160. tar -xf "$FINAL_ARCHIVE" -C "$EXTRACT_DIR"
  161. fi
  162. rm -f "$FINAL_ARCHIVE"
  163. fi
  164. echo "✅ Restore complete."
  165. echo "::endgroup::"
  166. break
  167. done <<< "$SEARCH_LIST"
  168. if [ "$FOUND" = "false" ]; then
  169. echo "⚠️ No cache matches found. Proceeding with fresh run."
  170. echo "cache-hit=false" >> "$GITHUB_OUTPUT"
  171. else
  172. echo "cache-hit=true" >> "$GITHUB_OUTPUT"
  173. fi