Skip to content

R2 cleanup (purge beta + prune old stable) #2

R2 cleanup (purge beta + prune old stable)

R2 cleanup (purge beta + prune old stable) #2

name: R2 cleanup (purge beta builds)
# 一次性运维:清掉 Cloudflare R2 (bucket: openspeech) 里所有 beta 版本的二进制归档
# (首段前缀含 -beta,如 v0.2.49-beta.6/),保留全部正式版归档与顶层滚动 manifest
# (latest*.json / download*.json)。复用 release.yml 同一套 R2_* secrets。
#
# 分组不依赖 S3 delimiter/CommonPrefixes(R2 上为空),而是 aws s3 ls --recursive 拉全部
# key 后按首段路径聚合。报告写成 artifact(report.txt / beta_prefixes.txt / totals.txt)。
# delete step 对每个待删前缀二次校验必须含 -beta;删完自校验,若仍有 -beta key 残留则 exit 1。
on:
workflow_dispatch:
inputs:
mode:
description: 'dry-run = 只列清单不删;delete = 真正删除 beta 归档'
required: true
type: choice
default: dry-run
options:
- dry-run
- delete
permissions:
contents: read
jobs:
cleanup:
runs-on: ubuntu-latest
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
AWS_ENDPOINT_URL: ${{ secrets.R2_ENDPOINT }}
AWS_DEFAULT_REGION: auto
R2_BUCKET: ${{ secrets.R2_BUCKET }}
MODE: ${{ inputs.mode }}
steps:
- name: List & classify objects
run: |
set -euo pipefail
echo "Mode: $MODE | Bucket: $R2_BUCKET"
aws s3 ls "s3://$R2_BUCKET" --recursive > all_objects.txt
total_all=$(wc -l < all_objects.txt)
# 按 key 首段路径聚合;不含 / 的顶层文件归到 (root)
awk '{
key=$4; size=$3;
n=index(key,"/");
grp=(n>0)?substr(key,1,n-1):"(root)";
objs[grp]++; bytes[grp]+=size;
} END { for (g in objs) printf "%s\t%d\t%d\n", g, objs[g], bytes[g] }' \
all_objects.txt | sort > groups.txt
: > beta_prefixes.txt
: > report.txt
del_objs=0; del_bytes=0; beta_count=0; keep_objs=0; keep_bytes=0
while IFS=$'\t' read -r g o b; do
if [[ "$g" == *-beta* ]]; then
printf '%-34s objs=%-4s bytes=%s [BETA -> DELETE]\n' "$g" "$o" "$b" >> report.txt
echo "$g/" >> beta_prefixes.txt
del_objs=$((del_objs+o)); del_bytes=$((del_bytes+b)); beta_count=$((beta_count+1))
else
printf '%-34s objs=%-4s bytes=%s [KEEP]\n' "$g" "$o" "$b" >> report.txt
keep_objs=$((keep_objs+o)); keep_bytes=$((keep_bytes+b))
fi
done < groups.txt
{
echo "total_all_objects=$total_all"
echo "beta_prefix_count=$beta_count"
echo "delete_objects=$del_objs"
echo "delete_bytes=$del_bytes"
echo "keep_objects=$keep_objs"
echo "keep_bytes=$keep_bytes"
} > totals.txt
# job summary(Actions 页可视)
{
echo "## R2 cleanup ($MODE)"
echo '```'
cat totals.txt
echo '```'
echo "### prefixes"
echo '```'
cat report.txt
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
cat totals.txt
- name: Upload report artifact
uses: actions/upload-artifact@v4
with:
name: r2-cleanup-report
path: |
report.txt
beta_prefixes.txt
totals.txt
retention-days: 7
- name: Delete beta prefixes
if: ${{ inputs.mode == 'delete' }}
run: |
set -euo pipefail
if [[ ! -s beta_prefixes.txt ]]; then
echo "No beta prefixes to delete."
exit 0
fi
while IFS= read -r p; do
[[ -z "$p" ]] && continue
case "$p" in
*-beta*) ;;
*) echo "ABORT: non-beta prefix in delete list: $p"; exit 1 ;;
esac
echo ">>> deleting s3://$R2_BUCKET/$p"
aws s3 rm "s3://$R2_BUCKET/$p" --recursive --only-show-errors
done < beta_prefixes.txt
echo "Delete done."
- name: Verify after delete
if: ${{ inputs.mode == 'delete' }}
run: |
set -euo pipefail
remaining=$(aws s3 ls "s3://$R2_BUCKET" --recursive | awk '{print $4}' | grep -c -- '-beta' || true)
summ=$(aws s3 ls "s3://$R2_BUCKET" --recursive --summarize | tail -2)
bytes=$(printf '%s\n' "$summ" | awk '/Total Size/{print $NF}')
{
echo "## R2 cleanup verify"
echo "remaining_beta_keys=$remaining"
echo "bucket_total_bytes=${bytes:-0}"
} >> "$GITHUB_STEP_SUMMARY"
echo "remaining_beta_keys=$remaining"
echo "bucket_total_bytes=${bytes:-0}"
if [[ "$remaining" -ne 0 ]]; then
echo "ERROR: still $remaining beta keys left"; exit 1
fi
echo "OK: no beta keys remain."