CF Pages 项目清理脚本
2026年07月09日
Cloudflare Pages 项目清理脚本教程
需要删除一个部署在 Cloudflare Pages 上的项目,但总是提示错误,错误信息是
Your project has too many deployments to be deleted, follow this guide to delete them: https://cfl.re/3CXesln。
原因
因为部署太多,导致无法删除,我测试得知,基本超过 100 次部署就会有这个问题。
特性
| 特性 | 说明 |
|---|---|
| 依赖检查 | 自动检查 curl 和 jq 是否已安装 |
| 帮助文档 | 运行 ./cleanup.sh --help 查看详细说明 |
| 详细注释 | 每个参数和函数都有中文注释说明 |
| 交互式菜单 | 无需提前配置,运行时输入凭证 |
| 演练模式 | 可安全预览,不执行实际删除 |
| 错误恢复 | 完整的错误处理和提示 |
| 步骤跟踪 | 清晰显示每个步骤的进度 |
| 日志输出 | 彩色输出便于快速定位问题 |
使用方法
bash
# 1. 赋予执行权限
chmod +x cleanup.sh
# 2. 查看帮助
./cleanup.sh --help
# 3. 运行脚本
./cleanup.sh
# 选择操作模式:
# 1) 交互式删除(实际删除)
# 2) 演练模式(模拟删除,推荐先用这个测试)
# 3) 退出
code
#!/bin/bash
# ==========================================
# Cloudflare Pages 项目删除脚本
# 安全版本 - 带交互确认
# ==========================================
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# ==========================================
# 默认值
# ==========================================
# ACCOUNT_ID: Cloudflare 账户 ID
# - 在 Cloudflare 仪表板右下角可以找到
# - 格式:32 位十六进制字符串(如 cb21865b274fa7dd58f1bb1dc8c10236)
# - 可以通过脚本交互式输入,或硬编码在此处
ACCOUNT_ID=""
# PROJECT_NAME: Cloudflare Pages 项目名称
# - 你想要删除的项目的名称
# - 示例:yufen-tingyong, my-blog, portfolio 等
# - 与 Cloudflare 仪表板中显示的名称必须完全一致
PROJECT_NAME=""
# API_TOKEN: Cloudflare API Token(用于身份验证)
# - 在 https://dash.cloudflare.com/profile/api-tokens 生成
# - 必须拥有以下权限:
# - Account.Pages:Edit (编辑 Pages 项目)
# - Account.User:Read (读取用户信息,用于 Token 验证)
# - ⚠️ 安全提示:不要在公开代码中硬编码,应该通过环境变量或交互式输入
# - 示例:cfut_9bCuG9C8u8uLFc6LLRZslPNoVhdXMicN4GQ2sdu28d5979ee
API_TOKEN=""
# KEEP_COUNT: 保留的最新部署数量
# - 0:删除所有部署(删除项目前必须删除所有部署)
# - 1-10:保留最新的 N 个部署,删除其他的
# - 示例:如果有 25 个部署,设为 5 会保留最新 5 个,删除 20 个
# - 注意:此脚本的主要功能是删除项目,所以通常设为 0
KEEP_COUNT=0
# DRY_RUN: 演练/模拟模式(安全测试)
# - false:实际执行删除操作(默认值,真实删除)
# - true:仅显示将要执行的操作,不实际删除任何内容
# - 建议:第一次运行时设为 true,预览后再改为 false 实际执行
# - 通常通过脚本菜单选择,不需要手动修改
DRY_RUN=true
# ==========================================
# 函数定义
# ==========================================
print_header() {
echo -e "${BLUE}"
echo "╔═══════════════════════════════════════════════════╗"
echo "║ Cloudflare Pages 项目删除工具 ║"
echo "║ (Safe Deletion Script) ║"
echo "╚═══════════════════════════════════════════════════╝"
echo -e "${NC}"
}
print_section() {
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${YELLOW}$1${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
confirm() {
local prompt=$1
local response
while true; do
echo -n -e "${YELLOW}$prompt (yes/no): ${NC}"
read -r response
case "$response" in
[yY][eE][sS]|[yY])
return 0
;;
[nN][oO]|[nN])
return 1
;;
*)
echo -e "${RED}请输入 yes 或 no${NC}"
;;
esac
done
}
# 输入 API 凭证
input_credentials() {
print_section "步骤 1: 配置 API 凭证"
echo -e "${YELLOW}请输入以下信息(从 Cloudflare 控制面板获取):${NC}"
echo ""
# 输入 Account ID
echo -n "Account ID: "
read -r ACCOUNT_ID
[ -z "$ACCOUNT_ID" ] && echo -e "${RED}✗ Account ID 不能为空${NC}" && return 1
# 输入项目名称
echo -n "Project Name: "
read -r PROJECT_NAME
[ -z "$PROJECT_NAME" ] && echo -e "${RED}✗ Project Name 不能为空${NC}" && return 1
# 输入 API Token(隐藏输入)
echo -n "API Token: "
read -rs API_TOKEN # -s 选项隐藏输入
echo ""
[ -z "$API_TOKEN" ] && echo -e "${RED}✗ API Token 不能为空${NC}" && return 1
echo ""
echo -e "${GREEN}✓ 凭证已输入${NC}"
return 0
}
# 验证 API Token 有效性
verify_token() {
print_section "步骤 2: 验证 API 凭证"
echo -e "${YELLOW}→ 正在验证 Token...${NC}"
# 调用 Cloudflare API 验证 Token
local verify_result
verify_result=$(curl -s "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-H "Authorization: Bearer $API_TOKEN" \
-w "\n%{http_code}")
local http_code=$(echo "$verify_result" | tail -n1)
local response=$(echo "$verify_result" | sed '$d')
if [ "$http_code" != "200" ]; then
echo -e "${RED}✗ Token 验证失败 (HTTP $http_code)${NC}"
echo "$response" | jq '.' 2>/dev/null || echo "$response"
return 1
fi
local verify_success=$(echo "$response" | jq -r '.success // false')
if [ "$verify_success" != "true" ]; then
echo -e "${RED}✗ Token 无效${NC}"
echo "$response" | jq '.errors'
return 1
fi
echo -e "${GREEN}✓ Token 有效${NC}"
return 0
}
# 获取项目部署列表
get_deployments() {
print_section "步骤 3: 获取部署列表"
local api_base="https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/pages/projects/$PROJECT_NAME"
echo -e "${YELLOW}→ 正在获取部署列表...${NC}"
# 调用 API 获取所有部署
local deployments_result
deployments_result=$(curl -s "$api_base/deployments" \
-H "Authorization: Bearer $API_TOKEN" \
-w "\n%{http_code}")
local http_code=$(echo "$deployments_result" | tail -n1)
local deployments=$(echo "$deployments_result" | sed '$d')
if [ "$http_code" != "200" ]; then
echo -e "${RED}✗ 获取部署列表失败 (HTTP $http_code)${NC}"
echo "$deployments" | jq '.' 2>/dev/null || echo "$deployments"
return 1
fi
local api_success=$(echo "$deployments" | jq -r '.success // false')
if [ "$api_success" != "true" ]; then
echo -e "${RED}✗ API 返回错误${NC}"
echo "$deployments" | jq '.errors'
return 1
fi
# 统计总部署数
TOTAL=$(echo "$deployments" | jq -r '.result | length')
echo -e "${GREEN}✓ 当前项目共有 $TOTAL 个部署${NC}"
echo ""
if [ "$TOTAL" -eq 0 ]; then
return 0
fi
# 显示最近的 10 个部署
echo -e "${YELLOW}最近的 10 个部署:${NC}"
echo "$deployments" | jq -r '.result[0:10][] |
" • \(.id | .[0:8])... - \(.created_on) - Status: \(.status)"'
if [ "$TOTAL" -gt 10 ]; then
echo -e "${YELLOW} ... 还有 $((TOTAL - 10)) 个部署 ...${NC}"
fi
echo ""
# 保存部署列表供后续使用
echo "$deployments" > /tmp/deployments.json
return 0
}
# 预览将要删除的内容
preview_deletion() {
print_section "步骤 4: 预览将要删除的内容"
if [ "$TOTAL" -eq 0 ]; then
echo -e "${YELLOW}→ 没有部署需要删除${NC}"
echo -e "${GREEN}→ 项目将被直接删除${NC}"
return
fi
local skip_count=$KEEP_COUNT
local delete_count=$((TOTAL - skip_count))
# 显示警告信息
echo -e "${RED}将要删除的内容:${NC}"
echo -e "${RED} • 删除 $delete_count 个部署${NC}"
echo -e "${RED} • 删除项目: $PROJECT_NAME${NC}"
echo -e "${RED} • Account ID: $ACCOUNT_ID${NC}"
echo ""
echo -e "${YELLOW}保留的部署:${NC}"
echo -e "${YELLOW} • 最新的 $KEEP_COUNT 个部署${NC}"
if [ "$KEEP_COUNT" -eq 0 ]; then
echo -e "${YELLOW} • (无,将删除所有部署)${NC}"
fi
echo ""
}
# 删除所有部署
delete_deployments() {
print_section "步骤 5: 删除部署"
if [ "$TOTAL" -eq 0 ]; then
echo -e "${YELLOW}→ 没有部署需要删除,跳过此步骤${NC}"
return 0
fi
if ! confirm "是否开始删除部署?"; then
echo -e "${RED}✗ 已取消操作${NC}"
return 1
fi
local api_base="https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/pages/projects/$PROJECT_NAME"
local skip_count=$KEEP_COUNT
local delete_ids=$(cat /tmp/deployments.json | jq -r '.result[].id' | tail -n +$((skip_count + 1)))
local total_delete=$(echo "$delete_ids" | grep -c .)
local current=0
local success_count=0
local fail_count=0
echo ""
echo -e "${YELLOW}开始删除部署...${NC}"
# 逐个删除部署
echo "$delete_ids" | while read -r id; do
[ -z "$id" ] && continue
current=$((current + 1))
echo -n -e "${YELLOW} [$current/$total_delete] 删除 ${id:0:8}... ${NC}"
# 演练模式:仅显示,不实际执行
if [ "$DRY_RUN" = true ]; then
echo -e "${GREEN}[DRY RUN]${NC}"
sleep 0.3
continue
fi
# 实际删除:调用 API 删除部署
local delete_resp
delete_resp=$(curl -s -X DELETE "$api_base/deployments/$id" \
-H "Authorization: Bearer $API_TOKEN" \
-w "\n%{http_code}")
local http_code=$(echo "$delete_resp" | tail -n1)
local response=$(echo "$delete_resp" | sed '$d')
local delete_success=$(echo "$response" | jq -r '.success // false')
if [ "$delete_success" == "true" ] && [ "$http_code" = "200" ]; then
echo -e "${GREEN}✓${NC}"
success_count=$((success_count + 1))
else
echo -e "${RED}✗${NC}"
echo "$response" | jq -r '.errors[0].message // "未知错误"' | sed 's/^/ /'
fail_count=$((fail_count + 1))
fi
# 延迟 0.8 秒,避免 API 限流
sleep 0.8
done
echo ""
echo -e "${GREEN}✓ 成功删除: $success_count${NC}"
[ "$fail_count" -gt 0 ] && echo -e "${RED}✗ 删除失败: $fail_count${NC}"
# 等待后再检查,确保 API 已处理
echo -e "${YELLOW}→ 等待 3 秒后重新检查...${NC}"
sleep 3
return 0
}
# 删除项目本身
delete_project() {
print_section "步骤 6: 删除项目"
if ! confirm "确定删除项目 \"$PROJECT_NAME\" 吗?此操作不可撤销!"; then
echo -e "${RED}✗ 已取消删除项目${NC}"
return 1
fi
local api_base="https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/pages/projects/$PROJECT_NAME"
echo -e "${YELLOW}→ 正在删除项目...${NC}"
# 演练模式:不实际执行
if [ "$DRY_RUN" = true ]; then
echo -e "${BLUE}[DRY RUN] 项目删除请求已准备,但未实际执行${NC}"
return 0
fi
# 实际删除:调用 API 删除项目
local delete_result
delete_result=$(curl -s -X DELETE "$api_base" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-w "\n%{http_code}")
local http_code=$(echo "$delete_result" | tail -n1)
local response=$(echo "$delete_result" | sed '$d')
local delete_success=$(echo "$response" | jq -r '.success // false')
if [ "$delete_success" ==
if [ "$delete_success" == "true" ] && [ "$http_code" = "200" ]; then
echo -e "${GREEN}✓ 项目已成功删除${NC}"
return 0
else
echo -e "${RED}✗ 项目删除失败${NC}"
echo "HTTP Code: $http_code"
echo "$response" | jq '.' 2>/dev/null || echo "$response"
echo ""
echo -e "${YELLOW}常见原因:${NC}"
echo -e "${YELLOW} 1. 项目中仍有部署(请确保上面已成功删除所有部署)${NC}"
echo -e "${YELLOW} 2. Token 权限不足(需要 Pages:Edit 权限)${NC}"
echo -e "${YELLOW} 3. 项目 ID 或账户 ID 错误${NC}"
return 1
fi
}
# 显示删除完成总结
show_summary() {
print_section "删除总结"
echo -e "${GREEN}✓ 项目 \"$PROJECT_NAME\" 已成功删除!${NC}"
echo ""
echo -e "${BLUE}删除的内容:${NC}"
echo -e " • Account: $ACCOUNT_ID"
echo -e " • Project: $PROJECT_NAME"
echo -e " • Deployments: $TOTAL 个"
echo ""
}
# 显示操作模式菜单
show_menu() {
print_header
echo ""
echo -e "${YELLOW}选择操作模式:${NC}"
echo " 1) 交互式删除(推荐)"
echo " 2) 演练模式(模拟删除,不实际执行)"
echo " 3) 退出"
echo ""
echo -n "请选择 [1-3]: "
}
# ==========================================
# 主程序入口
# ==========================================
# 主函数:控制整个流程
main() {
# 显示菜单并让用户选择操作模式
while true; do
show_menu
read -r choice
case "$choice" in
1)
# 实际删除模式
DRY_RUN=false
break
;;
2)
# 演练模式(模拟删除)
DRY_RUN=true
echo -e "${BLUE}[DRY RUN] 模式已启用,所有操作都不会真实执行${NC}"
break
;;
3)
# 用户选择退出
echo -e "${YELLOW}已退出${NC}"
exit 0
;;
*)
echo -e "${RED}✗ 无效选择,请重试${NC}"
;;
esac
done
# ========== 开始执行删除流程 ==========
# 步骤 1:输入 API 凭证
input_credentials || exit 1
echo ""
# 步骤 2:验证 Token 有效性
verify_token || exit 1
echo ""
# 步骤 3:获取部署列表
get_deployments || exit 1
echo ""
# 步骤 4:预览将要删除的内容
preview_deletion
echo ""
# 步骤 4.5:最终确认
if ! confirm "确认上述操作?"; then
echo -e "${RED}✗ 已取消操作${NC}"
exit 0
fi
echo ""
# 步骤 5:删除所有部署
delete_deployments || exit 1
echo ""
# 步骤 6:删除项目
delete_project || exit 1
echo ""
# 显示删除完成总结
show_summary
}
# ==========================================
# 脚本执行入口
# ==========================================
# 检查依赖工具
check_dependencies() {
echo -e "${YELLOW}→ 检查依赖工具...${NC}"
# 检查 curl
if ! command -v curl &> /dev/null; then
echo -e "${RED}✗ 未找到 curl,请先安装${NC}"
echo " Ubuntu/Debian: sudo apt-get install curl"
echo " macOS: brew install curl"
exit 1
fi
# 检查 jq (JSON 处理工具)
if ! command -v jq &> /dev/null; then
echo -e "${RED}✗ 未找到 jq,请先安装${NC}"
echo " Ubuntu/Debian: sudo apt-get install jq"
echo " macOS: brew install jq"
exit 1
fi
echo -e "${GREEN}✓ 所有依赖工具已安装${NC}"
}
# 显示脚本使用说明
show_usage() {
cat << EOF
${BLUE}使用说明:${NC}
${YELLOW}环境要求:${NC}
- bash 4.0+
- curl
- jq (JSON 命令行处理工具)
${YELLOW}安装依赖:${NC}
Ubuntu/Debian:
sudo apt-get install curl jq
macOS:
brew install curl jq
${YELLOW}快速开始:${NC}
1. 赋予脚本执行权限:
chmod +x cleanup.sh
2. 运行脚本:
./cleanup.sh
3. 按照提示输入信息即可
${YELLOW}API Token 获取方式:${NC}
1. 访问:https://dash.cloudflare.com/profile/api-tokens
2. 点击 "Create Token"
3. 选择模板 "Edit Cloudflare Workers"
4. 修改权限:确保包含 "Account.Pages:Edit"
5. 设置 TTL(可选)
6. 创建并复制 Token
${YELLOW}Account ID 获取方式:${NC}
1. 登录 Cloudflare 控制面板
2. 进入任意网站或账户
3. 在右下角可以看到 "Account ID"
4. 或者访问:https://dash.cloudflare.com/
${YELLOW}支持的操作:${NC}
• 删除单个或多个部署
• 保留最新的 N 个部署
• 删除整个 Pages 项目
• 演练模式(安全预览)
• 完整的错误处理和日志
${YELLOW}安全提示:${NC}
⚠️ 不要在公开代码仓库中包含 API Token
⚠️ 首次运行建议使用"演练模式"预览
⚠️ 删除项目后无法恢复,请谨慎操作
⚠️ 建议为 Token 设置 IP 白名单和 TTL
EOF
}
# 如果传递了 --help 或 -h 参数,显示帮助信息
if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
show_usage
exit 0
fi
# 检查依赖工具
check_dependencies
echo ""
# 运行主程序
main