action.yml 7.0 KB

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