action.yml 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  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. echo "🔍 DEBUG: Starting cache restore process"
  50. echo "🔍 DEBUG: GITHUB_WORKSPACE=$GITHUB_WORKSPACE"
  51. echo "🔍 DEBUG: inputs.cache_path=${{ inputs.cache_path }}"
  52. echo "🔍 DEBUG: inputs.cache_key=${{ inputs.cache_key }}"
  53. echo "🔍 DEBUG: inputs.restore_keys=${{ inputs.restore_keys }}"
  54. echo "🔍 DEBUG: inputs.cache_bucket=${{ inputs.cache_bucket }}"
  55. echo "🔍 DEBUG: inputs.debug=${{ inputs.debug }}"
  56. TAG_NAME="${{ inputs.cache_bucket }}"
  57. BASE_URL="https://github.com/${TARGET_REPO}/releases/download/$TAG_NAME"
  58. ASSETS_URL="https://github.com/${TARGET_REPO}/releases/expanded_assets/$TAG_NAME"
  59. echo "🔍 DEBUG: TAG_NAME=$TAG_NAME"
  60. echo "🔍 DEBUG: BASE_URL=$BASE_URL"
  61. echo "🔍 DEBUG: ASSETS_URL=$ASSETS_URL"
  62. echo "🔍 DEBUG: TARGET_REPO=$TARGET_REPO"
  63. ARIA2_OPTS=(
  64. "-x16" "-s16" "-k1M" "-j5" "--file-allocation=none"
  65. "--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"
  66. "--header=Accept: */*"
  67. "--header=Connection: keep-alive"
  68. )
  69. if [ "${{ inputs.debug }}" != "true" ]; then
  70. ARIA2_OPTS+=("--quiet" "--summary-interval=0" "--console-log-level=error")
  71. fi
  72. echo "🔍 DEBUG: Fetching asset list from GitHub Releases..."
  73. echo "🔍 DEBUG: HTTP request to ASSETS_URL=$ASSETS_URL"
  74. echo "::group:: Fetching Asset List"
  75. HTTP_RESPONSE=$(curl -sL -A "Mozilla/5.0" -w "%{http_code}" "$ASSETS_URL" -o assets.html || echo "404")
  76. echo "🔍 DEBUG: HTTP_RESPONSE=$HTTP_RESPONSE"
  77. if [ "$HTTP_RESPONSE" -eq 200 ]; then
  78. ALL_ASSETS=$(grep -oP "download/$TAG_NAME/\K[^\"' ]+" assets.html | sort -u || true)
  79. echo "🔍 DEBUG: Parsed ALL_ASSETS count=$(echo "$ALL_ASSETS" | wc -l)"
  80. echo "🔍 DEBUG: First 10 assets: $(echo "$ALL_ASSETS" | head -n 10 | tr '\n' ' ')"
  81. else
  82. echo "Status $HTTP_RESPONSE: Failed to fetch assets from scraper endpoint."
  83. ALL_ASSETS=""
  84. echo "🔍 DEBUG: No assets parsed due to HTTP failure"
  85. fi
  86. rm -f assets.html
  87. echo "🔍 DEBUG: assets.html removed"
  88. if [ -z "$ALL_ASSETS" ]; then
  89. echo "🔍 DEBUG: ALL_ASSETS is empty, exiting early"
  90. echo "No assets found in bucket '$TAG_NAME'. Skipping search."
  91. echo "cache-hit=false" >> "$GITHUB_OUTPUT"
  92. echo "::endgroup::"
  93. exit 0
  94. fi
  95. echo "Successfully fetched asset list. Has $(echo "$ALL_ASSETS" | wc -l) assets."
  96. echo "🔍 DEBUG: Full asset list (first 20 lines):"
  97. echo "$ALL_ASSETS" | head -n 20
  98. echo "::endgroup::"
  99. SEARCH_LIST=$(printf "%s\n%s" "${{ inputs.cache_key }}" "${{ inputs.restore_keys }}" | sed '/^$/d')
  100. echo "🔍 DEBUG: SEARCH_LIST (keys to search):"
  101. echo "$SEARCH_LIST"
  102. echo "🔍 DEBUG: FOUND=false initialized"
  103. download_asset() {
  104. local filename="$1"
  105. local url="$2"
  106. local max_retries=3
  107. local attempt=1
  108. echo "🔍 DEBUG: download_asset() function called with filename=$filename, url=$url"
  109. echo "🔍 DEBUG: max_retries=$max_retries, current attempt=$attempt"
  110. while [ "$attempt" -le "$max_retries" ]; do
  111. echo "🔍 DEBUG: Attempt $attempt: Downloading $filename..."
  112. echo "🔍 DEBUG: Removing old file $filename if exists"
  113. rm -f "$filename"
  114. if command -v gh >/dev/null 2>&1; then
  115. echo "🔍 DEBUG: 'gh' command available, attempting download via gh CLI"
  116. if timeout 10m gh release download "$TAG_NAME" --repo "$TARGET_REPO" --pattern "$filename" -D . >/dev/null 2>&1; then
  117. echo "🔍 DEBUG: gh CLI download succeeded, checking file size"
  118. [ -s "$filename" ] && { echo "🔍 DEBUG: File $filename is non-empty, returning success"; return 0; }
  119. echo "🔍 DEBUG: File $filename is empty after gh download"
  120. else
  121. echo "🔍 DEBUG: gh CLI download failed or timed out"
  122. fi
  123. else
  124. echo "🔍 DEBUG: 'gh' command not available, skipping gh CLI download"
  125. fi
  126. echo "🔍 DEBUG: Trying aria2c download with URL=$url"
  127. echo "🔍 DEBUG: aria2c options: ${ARIA2_OPTS[*]}"
  128. if timeout 10m aria2c "${ARIA2_OPTS[@]}" --retry-wait=10 --max-tries=10 -o "$filename" "$url"; then
  129. echo "🔍 DEBUG: aria2c download succeeded, checking file size"
  130. [ -s "$filename" ] && { echo "🔍 DEBUG: File $filename is non-empty, returning success"; return 0; }
  131. echo " ⚠️ Downloaded file is empty. Retrying..."
  132. echo "🔍 DEBUG: File $filename is empty after aria2c download"
  133. rm -f "$filename"
  134. else
  135. echo "🔍 DEBUG: aria2c download failed or timed out"
  136. fi
  137. echo " ⚠️ Download failed or timed out. Retrying in 5s..."
  138. echo "🔍 DEBUG: Sleeping 5 seconds before retry"
  139. sleep 5
  140. attempt=$((attempt + 1))
  141. echo "🔍 DEBUG: Incremented attempt to $attempt"
  142. done
  143. return 1
  144. }
  145. while read -r KEY; do
  146. echo "🔍 DEBUG: Reading KEY from SEARCH_LIST: KEY=$KEY"
  147. [ -z "$KEY" ] && { echo "🔍 DEBUG: KEY is empty, skipping"; continue; }
  148. echo "::group:: Searching Prefix: $KEY"
  149. echo "🔍 DEBUG: Searching for assets matching prefix: cache-$KEY"
  150. MATCH=$(echo "$ALL_ASSETS" | grep -E "^cache-$KEY.*\.(tzst|tar)(\.part[a-z]{2})?$" | sort -r | head -n 1 || true)
  151. echo "🔍 DEBUG: MATCH result for KEY=$KEY: MATCH=$MATCH"
  152. if [ -z "$MATCH" ]; then
  153. echo "🔍 DEBUG: No match found for KEY=$KEY, continuing to next key"
  154. echo "Not found."
  155. echo "::endgroup::"
  156. continue
  157. fi
  158. BASE_FILENAME=$(echo "$MATCH" | sed -E 's/\.(tzst|tar)(\.part[a-z]{2})?$//')
  159. EXT=$(echo "$MATCH" | grep -oE '\.(tzst|tar)' | head -n 1)
  160. IS_SPLIT=false
  161. COMPRESSED=false
  162. echo "🔍 DEBUG: Parsed MATCH: BASE_FILENAME=$BASE_FILENAME, EXT=$EXT"
  163. case "$MATCH" in
  164. *.part*) IS_SPLIT=true ;;
  165. esac
  166. echo "🔍 DEBUG: IS_SPLIT=$IS_SPLIT (based on .part* suffix)"
  167. [ "$EXT" = ".tzst" ] && COMPRESSED=true
  168. echo "🔍 DEBUG: COMPRESSED=$COMPRESSED (EXT=$EXT)"
  169. FOUND=true
  170. echo "✅ Hit! Restoring $BASE_FILENAME$EXT"
  171. echo "::endgroup::"
  172. echo "::group:: Downloading Cache"
  173. echo "🔍 DEBUG: Download mode: IS_SPLIT=$IS_SPLIT, COMPRESSED=$COMPRESSED"
  174. if [ "$IS_SPLIT" = "true" ]; then
  175. echo "🔍 DEBUG: Downloading split archive parts"
  176. for part in {a..z}{a..z}; do
  177. PART_NAME="$BASE_FILENAME$EXT.part$part"
  178. echo "🔍 DEBUG: Checking if PART_NAME=$PART_NAME exists in ALL_ASSETS"
  179. if echo "$ALL_ASSETS" | grep -q "^$PART_NAME$"; then
  180. echo "🔍 DEBUG: PART_NAME=$PART_NAME found, downloading..."
  181. if ! download_asset "$PART_NAME" "$BASE_URL/$PART_NAME"; then
  182. echo "::error::Failed to download $PART_NAME after multiple retries."
  183. echo "🔍 DEBUG: CRITICAL: Failed to download $PART_NAME"
  184. exit 1
  185. fi
  186. echo "🔍 DEBUG: Successfully downloaded $PART_NAME"
  187. else
  188. echo "🔍 DEBUG: PART_NAME=$PART_NAME not found, breaking loop"
  189. break
  190. fi
  191. done
  192. else
  193. echo "🔍 DEBUG: Downloading single file: $BASE_FILENAME$EXT"
  194. if ! download_asset "$BASE_FILENAME$EXT" "$BASE_URL/$BASE_FILENAME$EXT"; then
  195. echo "::error::Failed to download $BASE_FILENAME$EXT after multiple retries."
  196. echo "🔍 DEBUG: CRITICAL: Failed to download $BASE_FILENAME$EXT"
  197. exit 1
  198. fi
  199. echo "🔍 DEBUG: Successfully downloaded $BASE_FILENAME$EXT"
  200. fi
  201. echo "::endgroup::"
  202. echo "::group:: Extracting Cache"
  203. echo "🔍 DEBUG: Starting extraction process"
  204. echo "🔍 DEBUG: cache_path=${{ inputs.cache_path }}"
  205. mkdir -p "${{ inputs.cache_path }}"
  206. echo "🔍 DEBUG: Created/verified cache_path directory"
  207. FINAL_ARCHIVE="$BASE_FILENAME$EXT"
  208. EXTRACT_DIR="$(dirname "${{ inputs.cache_path }}")"
  209. echo "🔍 DEBUG: FINAL_ARCHIVE=$FINAL_ARCHIVE"
  210. echo "🔍 DEBUG: EXTRACT_DIR=$EXTRACT_DIR"
  211. echo "🔍 DEBUG: IS_SPLIT=$IS_SPLIT, COMPRESSED=$COMPRESSED"
  212. if [ "$IS_SPLIT" = "true" ]; then
  213. echo "🔍 DEBUG: Extracting split archive"
  214. PARTS=$(ls "$FINAL_ARCHIVE.part"* | sort)
  215. echo "🔍 DEBUG: Found parts: $PARTS"
  216. echo "🔍 DEBUG: Parts list size: $(echo "$PARTS" | wc -w)"
  217. if [ "$COMPRESSED" = "true" ]; then
  218. echo "🔍 DEBUG: Using zstd decompression"
  219. cat $PARTS | tar -I 'zstd -d -T0' -xf - -C "$EXTRACT_DIR"
  220. else
  221. echo "🔍 DEBUG: Using plain tar extraction"
  222. cat $PARTS | tar -xf - -C "$EXTRACT_DIR"
  223. fi
  224. echo "🔍 DEBUG: Removing part files"
  225. rm -f $PARTS
  226. else
  227. echo "🔍 DEBUG: Extracting single archive"
  228. if [ "$COMPRESSED" = "true" ]; then
  229. echo "🔍 DEBUG: Using zstd decompression for single file"
  230. tar -I 'zstd -d -T0' -xf "$FINAL_ARCHIVE" -C "$EXTRACT_DIR"
  231. else
  232. echo "🔍 DEBUG: Using plain tar extraction for single file"
  233. tar -xf "$FINAL_ARCHIVE" -C "$EXTRACT_DIR"
  234. fi
  235. echo "🔍 DEBUG: Removing archive file"
  236. rm -f "$FINAL_ARCHIVE"
  237. fi
  238. echo "🔍 DEBUG: Verifying extracted contents"
  239. ls -la "${{ inputs.cache_path }}" || true
  240. echo "✅ Restore complete."
  241. echo "::endgroup::"
  242. break
  243. done <<< "$SEARCH_LIST"
  244. echo "🔍 DEBUG: Final check - FOUND=$FOUND"
  245. if [ "$FOUND" = "false" ]; then
  246. echo "⚠️ No cache matches found. Proceeding with fresh run."
  247. echo "🔍 DEBUG: Setting cache-hit=false"
  248. echo "cache-hit=false" >> "$GITHUB_OUTPUT"
  249. else
  250. echo "🔍 DEBUG: Cache was found and restored"
  251. echo "cache-hit=true" >> "$GITHUB_OUTPUT"
  252. fi
  253. echo "🔍 DEBUG: Cache restore process finished. cache-hit=$(cat $GITHUB_OUTPUT | grep cache-hit | cut -d= -f2)"