Skip to content

Commit 994f6e2

Browse files
committed
refactor: share release hook helpers
1 parent 751efd3 commit 994f6e2

4 files changed

Lines changed: 365 additions & 556 deletions

File tree

hooks/lib/release_utils.sh

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
#!/bin/bash
2+
# shellcheck source=hooks/lib/utils.sh
3+
4+
# Shared helpers for GitHub release driven build hooks.
5+
6+
hook_sha256() {
7+
local file="$1"
8+
9+
if command -v sha256sum >/dev/null 2>&1; then
10+
sha256sum "$file" | awk '{print $1}'
11+
elif command -v shasum >/dev/null 2>&1; then
12+
shasum -a 256 "$file" | awk '{print $1}'
13+
elif command -v openssl >/dev/null 2>&1; then
14+
openssl dgst -sha256 "$file" | awk '{print $2}'
15+
fi
16+
}
17+
18+
hook_lower() {
19+
printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
20+
}
21+
22+
hook_current_version() {
23+
local version_file="$1"
24+
25+
if [ -f "$version_file" ]; then
26+
cat "$version_file"
27+
else
28+
printf '%s\n' "none"
29+
fi
30+
}
31+
32+
github_latest_tag() {
33+
local repo="$1"
34+
35+
gh release view --repo "$repo" --json tagName --template '{{.tagName}}' 2>/dev/null || true
36+
}
37+
38+
github_asset_names() {
39+
local repo="$1"
40+
local tag="$2"
41+
local api_json
42+
43+
gh release view "$tag" --repo "$repo" --json assets --jq '.assets[].name' 2>/dev/null && return 0
44+
45+
api_json=$(gh api "repos/$repo/releases/tags/$tag" 2>/dev/null || true)
46+
[ -n "$api_json" ] || return 1
47+
48+
if command -v jq >/dev/null 2>&1; then
49+
printf '%s\n' "$api_json" | jq -r '.assets[].name'
50+
else
51+
printf '%s\n' "$api_json" | grep -o '"name":[[:space:]]*"[^"]*"' | sed -E 's/"name":[[:space:]]*"([^"]*)"/\1/'
52+
fi
53+
}
54+
55+
hook_select_asset() {
56+
local asset_names="$1"
57+
local pattern
58+
local asset
59+
60+
shift
61+
for pattern in "$@"; do
62+
asset=$(printf '%s\n' "$asset_names" | grep -iE "$pattern" | head -n1 || true)
63+
if [ -n "$asset" ]; then
64+
printf '%s\n' "$asset"
65+
return 0
66+
fi
67+
done
68+
69+
return 1
70+
}
71+
72+
github_asset_digest() {
73+
local repo="$1"
74+
local tag="$2"
75+
local asset="$3"
76+
local digest
77+
local api_json
78+
local line_number
79+
80+
digest=$(gh release view "$tag" --repo "$repo" --json assets --jq ".assets[] | select(.name==\"$asset\") | .digest" 2>/dev/null || true)
81+
if [ -n "$digest" ] && [ "$digest" != "null" ]; then
82+
printf '%s\n' "${digest#sha256:}"
83+
return 0
84+
fi
85+
86+
api_json=$(gh api "repos/$repo/releases/tags/$tag" 2>/dev/null || true)
87+
[ -n "$api_json" ] || return 1
88+
89+
if command -v jq >/dev/null 2>&1; then
90+
printf '%s\n' "$api_json" |
91+
jq -r --arg NAME "$asset" '.assets[] | select(.name==$NAME) | .digest' 2>/dev/null |
92+
sed 's/^sha256://'
93+
return 0
94+
fi
95+
96+
line_number=$(printf '%s\n' "$api_json" | grep -n "\"name\"[[:space:]]*:[[:space:]]*\"$asset\"" | head -n1 | cut -d: -f1) || true
97+
[ -n "$line_number" ] || return 1
98+
99+
printf '%s\n' "$api_json" |
100+
tail -n +"$line_number" |
101+
head -n 20 |
102+
grep -m1 '"digest"' |
103+
sed -E 's/.*"digest":[[:space:]]*"(sha256:)?([0-9a-fA-F]+)".*/\2/'
104+
}
105+
106+
github_download_asset() {
107+
local repo="$1"
108+
local tag="$2"
109+
local asset="$3"
110+
local output_dir="$4"
111+
local fallback_url="${5:-}"
112+
local output_path="$output_dir/$asset"
113+
114+
mkdir -p "$output_dir"
115+
rm -f "$output_path" 2>/dev/null || true
116+
117+
if gh release download "$tag" --repo "$repo" --pattern "$asset" --dir "$output_dir" --clobber >/dev/null 2>&1; then
118+
printf '%s\n' "$output_path"
119+
return 0
120+
fi
121+
122+
if [ -z "$fallback_url" ]; then
123+
fallback_url="https://github.com/$repo/releases/download/$tag/$asset"
124+
fi
125+
126+
if curl -L -o "$output_path" "$fallback_url"; then
127+
printf '%s\n' "$output_path"
128+
return 0
129+
fi
130+
131+
rm -f "$output_path" 2>/dev/null || true
132+
return 1
133+
}
134+
135+
github_verify_asset_digest() {
136+
local repo="$1"
137+
local tag="$2"
138+
local asset="$3"
139+
local file="$4"
140+
local remote_hash
141+
local local_hash
142+
143+
remote_hash=$(github_asset_digest "$repo" "$tag" "$asset" 2>/dev/null || true)
144+
remote_hash="${remote_hash#sha256:}"
145+
146+
if [ -z "$remote_hash" ] || [ "$remote_hash" = "null" ]; then
147+
log_warn "未获取到远端 sha256,跳过校验"
148+
return 0
149+
fi
150+
151+
local_hash=$(hook_sha256 "$file" 2>/dev/null || true)
152+
if [ -z "$local_hash" ]; then
153+
log_warn "无法计算本地文件 sha256,跳过校验"
154+
return 0
155+
fi
156+
157+
if [ "$(hook_lower "$local_hash")" != "$(hook_lower "$remote_hash")" ]; then
158+
log_error "错误:下载文件 sha256 校验失败 (local=$local_hash remote=$remote_hash)"
159+
return 1
160+
fi
161+
162+
log_info "sha256 校验通过"
163+
}
164+
165+
hook_make_temp_dir() {
166+
mktemp -d "${KAM_MODULE_ROOT}/.tmp.release.XXXXXX" 2>/dev/null || mktemp -d
167+
}
168+
169+
hook_extract_binary() {
170+
local archive="$1"
171+
local tmp_dir="$2"
172+
local exact_name="$3"
173+
local fuzzy_name="${4:-*}"
174+
local base_name
175+
176+
case "$archive" in
177+
*.tar.gz|*.tgz)
178+
require_command tar "tar not found!"
179+
tar -xzf "$archive" -C "$tmp_dir"
180+
;;
181+
*.gz)
182+
require_command gunzip "gunzip not found!"
183+
cp "$archive" "$tmp_dir/"
184+
base_name=$(basename "$archive")
185+
gunzip -f "$tmp_dir/$base_name"
186+
;;
187+
*.zip)
188+
require_command unzip "unzip not found!"
189+
unzip -o "$archive" -d "$tmp_dir" >/dev/null
190+
;;
191+
*)
192+
cp "$archive" "$tmp_dir/"
193+
;;
194+
esac
195+
196+
HOOK_EXTRACTED_BINARY=$(find "$tmp_dir" -type f -name "$exact_name" -print -quit 2>/dev/null || true)
197+
if [ -z "$HOOK_EXTRACTED_BINARY" ]; then
198+
HOOK_EXTRACTED_BINARY=$(find "$tmp_dir" -type f -name "$fuzzy_name" -print -quit 2>/dev/null || true)
199+
fi
200+
201+
[ -n "$HOOK_EXTRACTED_BINARY" ] && [ -f "$HOOK_EXTRACTED_BINARY" ]
202+
}
203+
204+
github_download_if_changed() {
205+
local repo="$1"
206+
local tag="$2"
207+
local asset="$3"
208+
local fallback_url="$4"
209+
local local_file="$5"
210+
local hash_file="$6"
211+
local remote_hash
212+
local current_hash
213+
local tmp
214+
215+
remote_hash=$(github_asset_digest "$repo" "$tag" "$asset" 2>/dev/null || true)
216+
remote_hash="${remote_hash#sha256:}"
217+
218+
if [ -n "$remote_hash" ] && [ "$remote_hash" != "null" ] && [ -f "$hash_file" ] && [ -f "$local_file" ] && [ "$(cat "$hash_file")" = "$remote_hash" ]; then
219+
log_info "$asset: up to date (remote hash matched)"
220+
return 0
221+
fi
222+
223+
if { [ -z "$remote_hash" ] || [ "$remote_hash" = "null" ]; } && [ -f "$hash_file" ] && [ -f "$local_file" ]; then
224+
current_hash=$(hook_sha256 "$local_file" 2>/dev/null || true)
225+
if [ -n "$current_hash" ] && [ "$current_hash" = "$(cat "$hash_file")" ]; then
226+
log_info "$asset: up to date (local hash matched)"
227+
return 0
228+
fi
229+
fi
230+
231+
log_info "$asset: downloading..."
232+
tmp="$local_file.tmp"
233+
rm -f "$tmp" 2>/dev/null || true
234+
235+
github_download_asset "$repo" "$tag" "$asset" "$(dirname "$local_file")" "$fallback_url" >/dev/null || {
236+
log_error "$asset: download failed"
237+
return 1
238+
}
239+
240+
mv "$(dirname "$local_file")/$asset" "$tmp"
241+
github_verify_asset_digest "$repo" "$tag" "$asset" "$tmp" || {
242+
rm -f "$tmp"
243+
return 1
244+
}
245+
246+
current_hash=$(hook_sha256 "$tmp" 2>/dev/null || true)
247+
mv "$tmp" "$local_file"
248+
249+
if [ -n "$remote_hash" ] && [ "$remote_hash" != "null" ]; then
250+
printf '%s\n' "$remote_hash" > "$hash_file"
251+
elif [ -n "$current_hash" ]; then
252+
printf '%s\n' "$current_hash" > "$hash_file"
253+
else
254+
rm -f "$hash_file" 2>/dev/null || true
255+
fi
256+
257+
log_success "$asset: updated"
258+
}

0 commit comments

Comments
 (0)