|
1 | 1 | name: R2 cleanup (purge beta builds) |
2 | 2 |
|
3 | 3 | # 一次性运维:清掉 Cloudflare R2 (bucket: openspeech) 里所有 beta 版本的二进制归档 |
4 | | -# (顶层前缀 v*-beta*/),保留全部正式版归档与顶层滚动 manifest(latest*.json / |
5 | | -# download*.json)。复用 release.yml 同一套 R2_* secrets。 |
| 4 | +# (首段前缀含 -beta,如 v0.2.49-beta.6/),保留全部正式版归档与顶层滚动 manifest |
| 5 | +# (latest*.json / download*.json)。复用 release.yml 同一套 R2_* secrets。 |
6 | 6 | # |
7 | | -# 用法:Actions → 本 workflow → Run workflow |
8 | | -# 1. 先用 mode=dry-run 跑一次,看 step 日志里的「将删除」清单与体积; |
9 | | -# 2. 确认无误后再用 mode=delete 跑一次真删。 |
10 | | -# |
11 | | -# 安全:delete step 对每个待删前缀二次校验必须含 `-beta` 才执行 rm,正式版与 |
12 | | -# 顶层 manifest 永不进入删除列表。 |
| 7 | +# 分组不依赖 S3 delimiter/CommonPrefixes(R2 上为空),而是 aws s3 ls --recursive 拉全部 |
| 8 | +# key 后按首段路径聚合。报告写成 artifact(report.txt / beta_prefixes.txt / totals.txt)。 |
| 9 | +# delete step 对每个待删前缀二次校验必须含 -beta;删完自校验,若仍有 -beta key 残留则 exit 1。 |
13 | 10 |
|
14 | 11 | on: |
15 | 12 | workflow_dispatch: |
@@ -37,81 +34,104 @@ jobs: |
37 | 34 | R2_BUCKET: ${{ secrets.R2_BUCKET }} |
38 | 35 | MODE: ${{ inputs.mode }} |
39 | 36 | steps: |
40 | | - - name: List & classify top-level prefixes |
| 37 | + - name: List & classify objects |
41 | 38 | run: | |
42 | 39 | set -euo pipefail |
43 | 40 | echo "Mode: $MODE | Bucket: $R2_BUCKET" |
44 | | - echo |
45 | 41 |
|
46 | | - echo "=== 顶层对象(manifest 等,保留)===" |
47 | | - aws s3api list-objects-v2 --bucket "$R2_BUCKET" --delimiter / \ |
48 | | - --query 'Contents[].Key' --output text 2>/dev/null \ |
49 | | - | tr '\t' '\n' | grep -v -e '^$' -e '^None$' || true |
50 | | - echo |
| 42 | + aws s3 ls "s3://$R2_BUCKET" --recursive > all_objects.txt |
| 43 | + total_all=$(wc -l < all_objects.txt) |
51 | 44 |
|
52 | | - echo "=== 顶层前缀分类 ===" |
53 | | - prefixes=$(aws s3api list-objects-v2 --bucket "$R2_BUCKET" --delimiter / \ |
54 | | - --query 'CommonPrefixes[].Prefix' --output text 2>/dev/null \ |
55 | | - | tr '\t' '\n' | grep -v -e '^$' -e '^None$' || true) |
| 45 | + # 按 key 首段路径聚合;不含 / 的顶层文件归到 (root) |
| 46 | + awk '{ |
| 47 | + key=$4; size=$3; |
| 48 | + n=index(key,"/"); |
| 49 | + grp=(n>0)?substr(key,1,n-1):"(root)"; |
| 50 | + objs[grp]++; bytes[grp]+=size; |
| 51 | + } END { for (g in objs) printf "%s\t%d\t%d\n", g, objs[g], bytes[g] }' \ |
| 52 | + all_objects.txt | sort > groups.txt |
56 | 53 |
|
57 | 54 | : > beta_prefixes.txt |
58 | | - total_del_objs=0 |
59 | | - total_del_bytes=0 |
60 | | - beta_count=0 |
61 | | - for p in $prefixes; do |
62 | | - summ=$(aws s3 ls "s3://$R2_BUCKET/$p" --recursive --summarize 2>/dev/null | tail -2 || true) |
63 | | - objs=$(printf '%s\n' "$summ" | awk '/Total Objects/{print $NF}') |
64 | | - bytes=$(printf '%s\n' "$summ" | awk '/Total Size/{print $NF}') |
65 | | - objs=${objs:-0}; bytes=${bytes:-0} |
66 | | - if [[ "$p" == *-beta* ]]; then |
67 | | - printf '%-32s objs=%-4s bytes=%-13s [BETA → 将删]\n' "$p" "$objs" "$bytes" |
68 | | - echo "$p" >> beta_prefixes.txt |
69 | | - total_del_objs=$((total_del_objs + objs)) |
70 | | - total_del_bytes=$((total_del_bytes + bytes)) |
71 | | - beta_count=$((beta_count + 1)) |
| 55 | + : > report.txt |
| 56 | + del_objs=0; del_bytes=0; beta_count=0; keep_objs=0; keep_bytes=0 |
| 57 | + while IFS=$'\t' read -r g o b; do |
| 58 | + if [[ "$g" == *-beta* ]]; then |
| 59 | + printf '%-34s objs=%-4s bytes=%s [BETA -> DELETE]\n' "$g" "$o" "$b" >> report.txt |
| 60 | + echo "$g/" >> beta_prefixes.txt |
| 61 | + del_objs=$((del_objs+o)); del_bytes=$((del_bytes+b)); beta_count=$((beta_count+1)) |
72 | 62 | else |
73 | | - printf '%-32s objs=%-4s bytes=%-13s [STABLE → 保留]\n' "$p" "$objs" "$bytes" |
| 63 | + printf '%-34s objs=%-4s bytes=%s [KEEP]\n' "$g" "$o" "$b" >> report.txt |
| 64 | + keep_objs=$((keep_objs+o)); keep_bytes=$((keep_bytes+b)) |
74 | 65 | fi |
75 | | - done |
| 66 | + done < groups.txt |
76 | 67 |
|
77 | | - echo |
78 | | - human=$(awk -v b="$total_del_bytes" 'BEGIN{printf "%.2f GiB", b/1073741824}') |
79 | | - echo "=== 将删除 ${total_del_objs} 个对象 / ${total_del_bytes} 字节 (${human}),覆盖 ${beta_count} 个 beta 前缀 ===" |
80 | | - echo |
81 | | - if [[ "$MODE" == "dry-run" ]]; then |
82 | | - echo "★ 当前 dry-run,未删除任何对象。确认清单后用 mode=delete 重新触发。" |
83 | | - fi |
| 68 | + { |
| 69 | + echo "total_all_objects=$total_all" |
| 70 | + echo "beta_prefix_count=$beta_count" |
| 71 | + echo "delete_objects=$del_objs" |
| 72 | + echo "delete_bytes=$del_bytes" |
| 73 | + echo "keep_objects=$keep_objs" |
| 74 | + echo "keep_bytes=$keep_bytes" |
| 75 | + } > totals.txt |
| 76 | +
|
| 77 | + # job summary(Actions 页可视) |
| 78 | + { |
| 79 | + echo "## R2 cleanup ($MODE)" |
| 80 | + echo '```' |
| 81 | + cat totals.txt |
| 82 | + echo '```' |
| 83 | + echo "### prefixes" |
| 84 | + echo '```' |
| 85 | + cat report.txt |
| 86 | + echo '```' |
| 87 | + } >> "$GITHUB_STEP_SUMMARY" |
| 88 | +
|
| 89 | + cat totals.txt |
| 90 | +
|
| 91 | + - name: Upload report artifact |
| 92 | + uses: actions/upload-artifact@v4 |
| 93 | + with: |
| 94 | + name: r2-cleanup-report |
| 95 | + path: | |
| 96 | + report.txt |
| 97 | + beta_prefixes.txt |
| 98 | + totals.txt |
| 99 | + retention-days: 7 |
84 | 100 |
|
85 | 101 | - name: Delete beta prefixes |
86 | 102 | if: ${{ inputs.mode == 'delete' }} |
87 | 103 | run: | |
88 | 104 | set -euo pipefail |
89 | 105 | if [[ ! -s beta_prefixes.txt ]]; then |
90 | | - echo "没有 beta 前缀可删,结束。" |
| 106 | + echo "No beta prefixes to delete." |
91 | 107 | exit 0 |
92 | 108 | fi |
93 | 109 | while IFS= read -r p; do |
94 | 110 | [[ -z "$p" ]] && continue |
95 | 111 | case "$p" in |
96 | 112 | *-beta*) ;; |
97 | | - *) echo "跳过非 beta 前缀(不应出现): $p"; continue ;; |
| 113 | + *) echo "ABORT: non-beta prefix in delete list: $p"; exit 1 ;; |
98 | 114 | esac |
99 | | - echo ">>> 删除 s3://$R2_BUCKET/$p" |
| 115 | + echo ">>> deleting s3://$R2_BUCKET/$p" |
100 | 116 | aws s3 rm "s3://$R2_BUCKET/$p" --recursive --only-show-errors |
101 | 117 | done < beta_prefixes.txt |
102 | | - echo "删除完成。" |
| 118 | + echo "Delete done." |
103 | 119 |
|
104 | 120 | - name: Verify after delete |
105 | 121 | if: ${{ inputs.mode == 'delete' }} |
106 | 122 | run: | |
107 | 123 | set -euo pipefail |
108 | | - echo "=== 删除后剩余顶层前缀 ===" |
109 | | - aws s3api list-objects-v2 --bucket "$R2_BUCKET" --delimiter / \ |
110 | | - --query 'CommonPrefixes[].Prefix' --output text 2>/dev/null \ |
111 | | - | tr '\t' '\n' | grep -v -e '^$' -e '^None$' || true |
112 | | - echo |
113 | | - echo "=== 桶总用量 ===" |
| 124 | + remaining=$(aws s3 ls "s3://$R2_BUCKET" --recursive | awk '{print $4}' | grep -c -- '-beta' || true) |
114 | 125 | summ=$(aws s3 ls "s3://$R2_BUCKET" --recursive --summarize | tail -2) |
115 | | - echo "$summ" |
116 | 126 | bytes=$(printf '%s\n' "$summ" | awk '/Total Size/{print $NF}') |
117 | | - awk -v b="${bytes:-0}" 'BEGIN{printf "总计 %.2f GiB\n", b/1073741824}' |
| 127 | + { |
| 128 | + echo "## R2 cleanup verify" |
| 129 | + echo "remaining_beta_keys=$remaining" |
| 130 | + echo "bucket_total_bytes=${bytes:-0}" |
| 131 | + } >> "$GITHUB_STEP_SUMMARY" |
| 132 | + echo "remaining_beta_keys=$remaining" |
| 133 | + echo "bucket_total_bytes=${bytes:-0}" |
| 134 | + if [[ "$remaining" -ne 0 ]]; then |
| 135 | + echo "ERROR: still $remaining beta keys left"; exit 1 |
| 136 | + fi |
| 137 | + echo "OK: no beta keys remain." |
0 commit comments