Skip to content

Instantly share code, notes, and snippets.

@lazzman
Last active October 16, 2025 09:42
Show Gist options
  • Select an option

  • Save lazzman/08f3c8c3c9fb8841ef8d19414633fce3 to your computer and use it in GitHub Desktop.

Select an option

Save lazzman/08f3c8c3c9fb8841ef8d19414633fce3 to your computer and use it in GitHub Desktop.
GoogleStudio API密钥批量创建脚本
#!/bin/bash
# ==============================================================================
#
# 密钥管理器修改版 v1.6
#
# - 该脚本完全免费,请勿用于任何商业行为
# - 作者: momo & ddddd1996 & KKTsN
#
# - v1.6 更新日志:
# - 在配置菜单中添加快速设置选项 (12项目/12并发)
# - 在执行报告中直接显示逗号分隔的密钥内容,便于复制使用
# - 修复从配置菜单返回时需要额外按回车的问题
# - 抑制后台任务提示,使进度条显示更清晰流畅
# - 修复执行时间显示不准确的问题,使用date +%s替代SECONDS变量
# - 并行化密钥状态检查,性能提升10倍(12个项目从30秒降至2-3秒)
#
# - v1.5 更新日志:
# - 初始优化版本
#
# - v1.4 更新日志:
# - 更改了横幅和一些介绍
# - 延长了等待时间,可能能增加一次开满75的概率
# - 去掉了配额检查
#
# ==============================================================================
# ===== 全局配置 =====
TIMESTAMP=$(date +%s)
RANDOM_CHARS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 4 | head -n 1)
EMAIL_USERNAME="${RANDOM_CHARS}${TIMESTAMP:(-4)}"
# 生成随机的项目前缀
RANDOM_PREFIX_PART=$(cat /dev/urandom | tr -dc 'a-z' | fold -w 5 | head -n 1)
RANDOM_SUFFIX_PART=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 3 | head -n 1)
PROJECT_PREFIX="${RANDOM_PREFIX_PART}Vul${RANDOM_SUFFIX_PART}"
TOTAL_PROJECTS=75
MAX_PARALLEL_JOBS=40
# 动态全局等待时间计算函数
calculate_global_wait_seconds() {
local calculated_seconds=$((TOTAL_PROJECTS * 2))
if [ $calculated_seconds -lt 60 ]; then
echo 60
else
echo $calculated_seconds
fi
}
MAX_RETRY_ATTEMPTS=3
MAX_RETRY_GLOBAL=1
SECONDS=0
# 文件和目录配置
PURE_KEY_FILE="key.txt"
COMMA_SEPARATED_KEY_FILE="comma_separated_keys_${EMAIL_USERNAME}.txt"
DELETION_LOG="project_deletion_$(date +%Y%m%d_%H%M%S).log"
CLEANUP_LOG="api_keys_cleanup_$(date +%Y%m%d_%H%M%S).log"
TEMP_DIR="/tmp/gcp_script_${TIMESTAMP}"
HEARTBEAT_PID=""
# 启动时创建临时目录
mkdir -p "$TEMP_DIR"
# ===== 工具函数 =====
# 统一日志函数
log() {
local level="$1"
local msg="$2"
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $msg" | tee -a "${TEMP_DIR}/script.log"
}
# 检查并安装jq
check_and_install_jq() {
if command -v jq &>/dev/null; then
log "INFO" "jq已安装,将使用jq进行JSON解析"
return 0
fi
log "WARN" "未检测到jq,尝试自动安装..."
# 检测操作系统并尝试安装
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
if command -v apt-get &>/dev/null; then
log "INFO" "检测到Debian/Ubuntu系统,使用apt-get安装jq..."
if sudo apt-get update && sudo apt-get install -y jq; then
log "INFO" "jq安装成功"
return 0
fi
elif command -v yum &>/dev/null; then
log "INFO" "检测到RHEL/CentOS系统,使用yum安装jq..."
if sudo yum install -y jq; then
log "INFO" "jq安装成功"
return 0
fi
fi
elif [[ "$OSTYPE" == "darwin"* ]]; then
if command -v brew &>/dev/null; then
log "INFO" "检测到macOS系统,使用Homebrew安装jq..."
if brew install jq; then
log "INFO" "jq安装成功"
return 0
fi
fi
fi
log "WARN" "jq安装失败,将使用备用的sed/grep方法解析JSON"
return 1
}
# 初始化时检查jq
check_and_install_jq
# 心跳机制 - 修复版
start_heartbeat() {
local message="$1"
local interval="${2:-20}"
stop_heartbeat
(
while true; do
# 修复:避免在并行处理时输出心跳,防止与进度条冲突
if [[ -z "${PROGRESS_ACTIVE:-}" ]]; then
log "HEARTBEAT" "${message:-"操作进行中,请耐心等待..."}"
fi
sleep "$interval"
done
) &
HEARTBEAT_PID=$!
}
stop_heartbeat() {
if [[ -n "$HEARTBEAT_PID" && -e /proc/$HEARTBEAT_PID ]]; then
kill "$HEARTBEAT_PID" 2>/dev/null
wait "$HEARTBEAT_PID" 2>/dev/null || true
fi
HEARTBEAT_PID=""
# 修复:停止心跳后确保光标位置正确
printf "\r\033[K"
}
# 优化版JSON解析函数
parse_json() {
local json_input="$1"
local field="$2"
local value=""
if command -v jq &>/dev/null; then
value=$(echo "$json_input" | jq -r "$field // \"\"")
else
# sed/grep fallback is complex for nested keys, jq is highly recommended.
# This simplified version will not work for ".response.keyString"
log "WARN" "jq not found. Using simplified parser which may fail for nested data."
local simple_field=$(basename "$field")
value=$(echo "$json_input" | grep -o "\"$simple_field\": *\"[^\"]*\"" | head -n 1 | cut -d'"' -f4)
fi
if [[ -n "$value" && "$value" != "null" ]]; then
echo "$value"; return 0;
else
return 1;
fi
}
# 文件写入函数 (带文件锁)
write_keys_to_files() {
local api_key="$1"
if [[ -z "$api_key" ]]; then return 1; fi
(
if flock -w 10 200; then
echo "$api_key" >> "$PURE_KEY_FILE"
if [[ -s "$COMMA_SEPARATED_KEY_FILE" ]]; then
echo -n "," >> "$COMMA_SEPARATED_KEY_FILE"
fi
echo -n "$api_key" >> "$COMMA_SEPARATED_KEY_FILE"
else
log "ERROR" "写入文件失败: 获取文件锁超时"
return 1
fi
) 200>"${TEMP_DIR}/key_files.lock"
}
# 改进的重试函数
retry_with_backoff() {
local max_attempts=$1; shift; local cmd_str="$1"; local attempt=1; local base_timeout=5
while (( attempt <= max_attempts )); do
local output; local error_msg;
exec 3>&1
error_msg=$({ output=$(eval "$cmd_str" 2>&1 >&3); } 2>&1)
exec 3>&-
local exit_code=$?
if (( exit_code == 0 )); then echo "$output"; return 0; fi
log "WARN" "命令失败 (尝试 $attempt/$max_attempts): $(echo "$cmd_str" | cut -d' ' -f1-4)..."
log "WARN" "--> 错误详情: $error_msg"
if [[ "$error_msg" == *"Permission denied"* || "$error_msg" == *"INVALID_ARGUMENT"* || "$error_msg" == *"already exists"* ]]; then
log "ERROR" "检测到不可重试错误,停止。"; return $exit_code;
fi
if [[ "$error_msg" == *"Quota exceeded"* || "$error_msg" == *"RESOURCE_EXHAUSTED"* ]]; then
local sleep_time=$((base_timeout * attempt * 2)); log "WARN" "检测到配额限制,等待 ${sleep_time}s"; sleep "$sleep_time"
elif (( attempt < max_attempts )); then
local sleep_time=$((base_timeout * attempt)); log "INFO" "等待 ${sleep_time}s 后重试..."; sleep "$sleep_time"
fi
((attempt++))
done
log "ERROR" "命令在 $max_attempts 次尝试后最终失败: $cmd_str"; return 1
}
# 进度条显示函数 - 修复版
show_progress() {
local completed=$1; local total=$2; local op_name=${3:-"进度"}
if (( total <= 0 )); then return; fi; if (( completed > total )); then completed=$total; fi
local percent=$((completed * 100 / total)); local bar_len=40
local filled_len=$((bar_len * percent / 100)); local bar; printf -v bar '%*s' "$filled_len" ''; bar=${bar// /█}
local empty; printf -v empty '%*s' "$((bar_len - filled_len))" ''; empty=${empty// /░}
# 修复:使用更兼容的清屏和输出方式
printf "\r\033[K[%s%s] %d%% (%d/%d) - %s" "$bar" "$empty" "$percent" "$completed" "$total" "$op_name"
# 确保输出立即刷新
if [[ "$completed" -eq "$total" ]]; then
printf "\n"
fi
}
generate_report() {
local success=$1 failed=$2 total=$3 operation=${4:-"处理"} duration=${5:-0}; local success_rate=0
if (( total > 0 )); then success_rate=$(awk "BEGIN {printf \"%.2f\", $success * 100 / $total}"); fi
local h=$((duration/3600)) m=$(((duration%3600)/60)) s=$((duration%60))
echo; echo "======================== 执 行 报 告 ========================";
printf " 操作类型 : %s\n" "$operation"; printf " 总计尝试 : %d\n" "$total"; printf " 成功数量 : %d\n" "$success";
printf " 失败数量 : %d\n" "$failed"; printf " 成功率 : %.2f%%\n" "$success_rate"; printf " 总执行时间 : %d小时 %d分钟 %d秒\n" "$h" "$m" "$s"
if (( success > 0 )) && [[ "$operation" == *"密钥"* ]]; then
local key_count; key_count=$(wc -l < "$PURE_KEY_FILE" 2>/dev/null || echo 0)
echo; echo " 输出文件:"; echo " - 纯密钥文件 : $PURE_KEY_FILE ($key_count 个密钥)"; echo " - 逗号分隔文件 : $COMMA_SEPARATED_KEY_FILE"
# 显示逗号分隔的密钥内容
if [ -f "$COMMA_SEPARATED_KEY_FILE" ]; then
echo
echo " 逗号分隔的密钥(可直接复制):"
echo " ----------------------------------------"
cat "$COMMA_SEPARATED_KEY_FILE"
echo
echo " ----------------------------------------"
fi
fi
echo "================================================================"
}
# ===== 健壮的任务函数 =====
task_create_project() {
local project_id="$1"; local success_file="$2"
if retry_with_backoff "$MAX_RETRY_ATTEMPTS" "gcloud projects create \"$project_id\" --name=\"$project_id\" --no-set-as-default --quiet"; then
(flock 200; echo "$project_id" >> "$success_file";) 200>"${success_file}.lock"; return 0
else return 1; fi
}
task_enable_api() {
local project_id="$1"; local success_file="$2"
if retry_with_backoff "$MAX_RETRY_ATTEMPTS" "gcloud services enable generativelanguage.googleapis.com --project=\"$project_id\" --quiet"; then
(flock 200; echo "$project_id" >> "$success_file";) 200>"${success_file}.lock"; return 0
else return 1; fi
}
task_create_key() {
local project_id="$1"; local success_file="$2"; local create_output
create_output=$(retry_with_backoff "$MAX_RETRY_ATTEMPTS" "gcloud services api-keys create --project=\"$project_id\" --display-name=\"Gemini-API-Key\" --format=json --quiet")
if [[ -z "$create_output" ]]; then log "ERROR" "为项目 $project_id 创建密钥失败 (无输出)。"; return 1; fi
local gcp_error_msg; gcp_error_msg=$(parse_json "$create_output" ".error.message")
if [[ -n "$gcp_error_msg" ]]; then log "ERROR" "为项目 $project_id 创建密钥时GCP返回错误: $gcp_error_msg"; log "DEBUG" "GCP错误详情: $create_output"; return 1; fi
# ===== THE BULLSEYE FIX =====
local api_key; api_key=$(parse_json "$create_output" ".response.keyString")
# ===== END OF FIX =====
if [[ -n "$api_key" ]]; then
write_keys_to_files "$api_key"; (flock 200; echo "$project_id" >> "$success_file";) 200>"${success_file}.lock"; return 0
else
log "ERROR" "为项目 $project_id 提取密钥失败 (无法解析 .response.keyString)。"; log "DEBUG" "gcloud返回内容: $create_output"; return 1
fi
}
task_delete_project() {
local project_id="$1"; local success_file="$2"
if retry_with_backoff 2 "gcloud projects delete \"$project_id\" --quiet"; then
(flock 200; echo "$project_id" >> "$success_file";) 200>"${success_file}.lock"; return 0
else return 1; fi
}
task_cleanup_keys() {
local project_id="$1"; local success_file="$2"; local key_names; readarray -t key_names < <(gcloud services api-keys list --project="$project_id" --format="value(name)" --quiet)
if [ ${#key_names[@]} -eq 0 ]; then
(flock 200; echo "$project_id" >> "$success_file";) 200>"${success_file}.lock"; return 0
fi
local all_success=true
for key_name in "${key_names[@]}"; do
if ! retry_with_backoff 2 "gcloud services api-keys delete \"$key_name\" --quiet"; then all_success=false; fi
sleep 0.5
done
if $all_success; then (flock 200; echo "$project_id" >> "$success_file";) 200>"${success_file}.lock"; return 0; else return 1; fi
}
check_project_has_keys() {
local project_id="$1"
local output_file="$2"
if ! gcloud services api-keys list --project="$project_id" --format="value(name)" --quiet 2>/dev/null | grep -q "."; then
(flock 200; echo "$project_id" >> "$output_file";) 200>"${output_file}.lock"
fi
}
cleanup_resources() {
log "INFO" "执行退出清理..."; stop_heartbeat; pkill -P $$ &>/dev/null; rm -rf "$TEMP_DIR";
}
# ===== 并行执行与报告 =====
run_parallel() {
local task_func="$1"; local description="$2"; local success_file="$3"; shift 3; local items=("$@"); local total_items=${#items[@]}
if (( total_items == 0 )); then log "INFO" "在 '$description' 阶段无项目处理。"; return; fi
log "INFO" "开始并行执行 '$description' (最大并发: $MAX_PARALLEL_JOBS)..."
# 关闭监视模式,抑制后台任务完成提示
set +m
local pids=(); local completed_count=0
# 修复:标记进度条活跃状态,避免心跳干扰
export PROGRESS_ACTIVE=1
export -f log retry_with_backoff parse_json write_keys_to_files "$task_func" show_progress; export MAX_RETRY_ATTEMPTS PURE_KEY_FILE COMMA_SEPARATED_KEY_FILE TEMP_DIR
> "$success_file"
# 显示初始进度条(0%)
show_progress 0 "$total_items" "$description"
for i in "${!items[@]}"; do
if (( ${#pids[@]} >= MAX_PARALLEL_JOBS )); then
wait -n "${pids[@]}"; for j in "${!pids[@]}"; do if ! kill -0 "${pids[j]}" 2>/dev/null; then unset 'pids[j]'; ((completed_count++)); fi; done
show_progress "$completed_count" "$total_items" "$description"
fi
( "$task_func" "${items[i]}" "$success_file" ) & pids+=($!)
done
for pid in "${pids[@]}"; do wait "$pid"; ((completed_count++)); show_progress "$completed_count" "$total_items" "$description"; done
wait; show_progress "$total_items" "$total_items" "$description 完成"
# 修复:清除进度条活跃状态
unset PROGRESS_ACTIVE
local success_count; success_count=$(wc -l < "$success_file" | xargs); local fail_count=$((total_items - success_count))
printf "\n"; log "INFO" "阶段 '$description' 完成。总数: $total_items, 成功: $success_count, 失败: $fail_count"
# 恢复监视模式
set -m
}
create_projects_phased() {
local start_time=$(date +%s)
local retry_count=0
local projects_to_create_count=$TOTAL_PROJECTS
while true; do
log "INFO" "==================== 功能1: 创建项目并获取密钥 (分阶段) ===================="
if [ $retry_count -eq 0 ]; then
log "INFO" "将创建 $projects_to_create_count 个新项目。用户名: $EMAIL_USERNAME, 项目前缀: $PROJECT_PREFIX"
> "$PURE_KEY_FILE"; > "$COMMA_SEPARATED_KEY_FILE"
else
log "INFO" "==================== 全局重试 (第 $retry_count 次) ===================="
log "INFO" "将重新创建 $projects_to_create_count 个失败的项目"
fi
# 计算项目ID的起始编号
local start_num=1
if [ $retry_count -gt 0 ]; then
# 在重试时,使用新的项目编号
start_num=$((TOTAL_PROJECTS - projects_to_create_count + 1))
fi
local projects_to_create=()
for i in $(seq $start_num $((start_num + projects_to_create_count - 1))); do
local p_id="${PROJECT_PREFIX}-${EMAIL_USERNAME}-$(printf "%03d" $i)"
p_id=$(echo "$p_id"|tr -cd 'a-z0-9-'|cut -c 1-30|sed 's/-$//')
projects_to_create+=("$p_id")
done
local CREATED_PROJECTS_FILE="${TEMP_DIR}/created_${retry_count}.txt"
run_parallel task_create_project "阶段1: 创建项目" "$CREATED_PROJECTS_FILE" "${projects_to_create[@]}"
local created_project_ids=()
mapfile -t created_project_ids < "$CREATED_PROJECTS_FILE"
if [ ${#created_project_ids[@]} -eq 0 ]; then
log "ERROR" "项目创建阶段完全失败。"
return 1
fi
GLOBAL_WAIT_SECONDS=$(calculate_global_wait_seconds)
log "INFO" "阶段2: 全局等待 ${GLOBAL_WAIT_SECONDS} 秒... (基于 $TOTAL_PROJECTS 个项目)"
start_heartbeat "全局等待中..."
sleep ${GLOBAL_WAIT_SECONDS}
stop_heartbeat
# 修复:确保输出格式正确
printf "\n"
local ENABLED_PROJECTS_FILE="${TEMP_DIR}/enabled_${retry_count}.txt"
run_parallel task_enable_api "阶段3: 启用API" "$ENABLED_PROJECTS_FILE" "${created_project_ids[@]}"
local enabled_project_ids=()
mapfile -t enabled_project_ids < "$ENABLED_PROJECTS_FILE"
if [ ${#enabled_project_ids[@]} -eq 0 ]; then
log "ERROR" "API启用阶段完全失败。"
return 1
fi
local KEYS_CREATED_FILE="${TEMP_DIR}/keys_created_${retry_count}.txt"
run_parallel task_create_key "阶段4: 创建密钥" "$KEYS_CREATED_FILE" "${enabled_project_ids[@]}"
local successful_keys
successful_keys=$(wc -l < "$KEYS_CREATED_FILE" 2>/dev/null || echo 0)
local failed_keys=$((${#enabled_project_ids[@]} - successful_keys))
local current_time=$(date +%s)
local duration=$((current_time - start_time))
generate_report "$successful_keys" "$failed_keys" "${#enabled_project_ids[@]}" "创建并获取密钥" "$duration"
# 检查是否有失败的项目
if [ $failed_keys -gt 0 ] && [ $retry_count -lt $MAX_RETRY_GLOBAL ]; then
echo
log "WARN" "检测到 $failed_keys 个项目提取密钥失败"
read -p "是否要重试创建这些失败的项目? [y/N]: " retry_choice
if [[ "$retry_choice" =~ ^[Yy]$ ]]; then
((retry_count++))
projects_to_create_count=$failed_keys
log "INFO" "准备重试创建 $projects_to_create_count 个项目..."
sleep 3
continue
else
log "INFO" "用户选择不重试"
break
fi
else
if [ $failed_keys -gt 0 ] && [ $retry_count -ge $MAX_RETRY_GLOBAL ]; then
log "WARN" "已达到最大全局重试次数 ($MAX_RETRY_GLOBAL),不再重试"
fi
break
fi
done
# 生成最终统计
if [ $retry_count -gt 0 ]; then
echo
log "INFO" "==================== 最终统计 ===================="
local total_keys
total_keys=$(wc -l < "$PURE_KEY_FILE" 2>/dev/null || echo 0)
log "INFO" "总共成功获取密钥数: $total_keys"
log "INFO" "执行了 $retry_count 次全局重试"
fi
}
# 兼容旧函数名
create_projects_and_get_keys_fast() {
create_projects_phased
}
create_projects_only() {
local start_time=$(date +%s)
log "INFO" "======================================================"
log "INFO" "功能: 仅创建项目(不提取API密钥)"
log "INFO" "======================================================"
log "INFO" "使用随机生成的用户名: ${EMAIL_USERNAME}"
# 询问要创建的项目数量
read -p "请输入要创建的项目数量 (1-75,默认为$TOTAL_PROJECTS): " custom_count
custom_count=${custom_count:-$TOTAL_PROJECTS}
if ! [[ "$custom_count" =~ ^[1-9][0-9]*$ ]] || [ "$custom_count" -gt 75 ]; then
log "ERROR" "无效的项目数量。请输入1-75之间的数字。"
return 1
fi
log "INFO" "将创建 $custom_count 个项目"
log "INFO" "在 3 秒后开始执行..."; sleep 3
local projects_to_create=()
for i in $(seq 1 $custom_count); do
local project_num=$(printf "%03d" $i)
local base_id="${PROJECT_PREFIX}-${EMAIL_USERNAME}-${project_num}"
local project_id=$(echo "$base_id" | tr -cd 'a-z0-9-' | cut -c 1-30 | sed 's/-$//')
if ! [[ "$project_id" =~ ^[a-z] ]]; then
project_id="g${project_id:1}"
project_id=$(echo "$project_id" | cut -c 1-30 | sed 's/-$//')
fi
projects_to_create+=("$project_id")
done
# 创建项目
local CREATED_PROJECTS_FILE="${TEMP_DIR}/created_projects_only.txt"
> "$CREATED_PROJECTS_FILE"
export -f task_create_project log retry_with_backoff
export TEMP_DIR MAX_RETRY_ATTEMPTS
run_parallel task_create_project "创建项目" "$CREATED_PROJECTS_FILE" "${projects_to_create[@]}"
local created_project_ids=()
if [ -f "$CREATED_PROJECTS_FILE" ]; then
mapfile -t created_project_ids < "$CREATED_PROJECTS_FILE"
fi
local success_count=${#created_project_ids[@]}
local failed_count=$((custom_count - success_count))
# 生成报告
echo ""
echo "========== 创建项目报告 =========="
echo "计划创建: $custom_count 个项目"
echo "成功创建: $success_count 个项目"
echo "创建失败: $failed_count 个项目"
if [ $success_count -gt 0 ]; then
echo ""
echo "成功创建的项目ID:"
for project_id in "${created_project_ids[@]}"; do
echo " - $project_id"
done
fi
echo "=========================="
log "INFO" "======================================================"
log "INFO" "项目创建完成。未启用API,未创建API密钥。"
log "INFO" "如需获取API密钥,请使用功能3从现有项目中提取。"
log "INFO" "======================================================"
}
delete_all_existing_projects() {
local start_time=$(date +%s)
log "INFO" "==================== 功能4: 删除所有项目 ===================="
local project_list; project_list=$(gcloud projects list --format='value(projectId)' --filter='projectId!~^sys-' --quiet)
if [ -z "$project_list" ]; then log "INFO" "未找到任何用户项目。"; return 0; fi
local projects_array; readarray -t projects_array <<< "$project_list"; log "WARN" "找到 ${#projects_array[@]} 个项目。"
# 添加配额释放说明
echo ""
echo "⚠️ 重要提示:"
echo " - 删除的项目会进入回收站,30天后永久删除"
echo " - 项目配额在永久删除后才会释放(通常需要30天)"
echo " - 如需还原已删除的项目,请访问:"
echo " https://console.cloud.google.com/cloud-resource-manager"
echo ""
read -p "!!! 危险 !!! 输入 'DELETE-ALL' 确认删除: " r; [[ "$r" == "DELETE-ALL" ]] || { log "INFO" "操作取消。"; return 1; }
local DELETED_FILE="${TEMP_DIR}/deleted.txt"; run_parallel task_delete_project "删除项目" "$DELETED_FILE" "${projects_array[@]}"
local success_count; success_count=$(wc -l < "$DELETED_FILE" | xargs)
local end_time=$(date +%s)
local duration=$((end_time - start_time))
generate_report "$success_count" $((${#projects_array[@]} - success_count)) "${#projects_array[@]}" "删除项目" "$duration"
# 删除完成后再次提醒
echo ""
echo "💡 温馨提示:"
echo " - 已删除的项目会保留在回收站30天"
echo " - 项目配额将在永久删除后释放"
echo " - 如需还原项目或查看回收站,请访问:"
echo " https://console.cloud.google.com/cloud-resource-manager"
echo ""
}
cleanup_project_api_keys() {
local start_time=$(date +%s)
log "INFO" "==================== 功能5: 清理API密钥 ===================="
local project_list; project_list=$(gcloud projects list --format='value(projectId)' --filter='projectId!~^sys-' --quiet)
if [ -z "$project_list" ]; then log "INFO" "未找到任何用户项目。"; return 0; fi
local projects_array; readarray -t projects_array <<< "$project_list"; log "WARN" "将清理 ${#projects_array[@]} 个项目中所有的API密钥。"
read -p "确认继续吗? [y/N]: " r; [[ "$r" =~ ^[Yy]$ ]] || { log "INFO" "操作取消。"; return 1; }
echo "API密钥清理日志 - $(date)" > "$CLEANUP_LOG"; local CLEANED_FILE="${TEMP_DIR}/cleaned.txt"
run_parallel task_cleanup_keys "清理API密钥" "$CLEANED_FILE" "${projects_array[@]}"
local success_count; success_count=$(wc -l < "$CLEANED_FILE" | xargs)
local end_time=$(date +%s)
local duration=$((end_time - start_time))
generate_report "$success_count" $((${#projects_array[@]} - success_count)) "${#projects_array[@]}" "清理API密钥" "$duration"
}
# 列出项目中现有的API密钥
list_existing_api_keys() {
local project_id="$1"
local error_log="${TEMP_DIR}/list_keys_${project_id}_error.log"
# 尝试列出现有的API密钥
local keys_output
if keys_output=$(gcloud services api-keys list --project="$project_id" --format="value(name,keyString)" --quiet 2>"$error_log"); then
if [ -n "$keys_output" ]; then
echo "$keys_output"
rm -f "$error_log"
return 0
fi
fi
rm -f "$error_log"
return 1
}
extract_keys_from_existing_projects() {
local start_time=$(date +%s)
log "INFO" "==================== 功能2: 从现有项目中提取密钥 ===================="
log "INFO" "正在获取项目列表..."; local project_list; project_list=$(gcloud projects list --format='value(projectId)' --filter='projectId!~^sys-' --quiet)
if [ -z "$project_list" ]; then log "INFO" "未找到任何用户项目。"; return 0; fi
local projects_array; readarray -t projects_array <<< "$project_list"; > "$PURE_KEY_FILE"; > "$COMMA_SEPARATED_KEY_FILE"
log "INFO" "正在并行检查 ${#projects_array[@]} 个项目的密钥状态..."
# 并行检查密钥状态
local CHECK_FILE="${TEMP_DIR}/projects_need_keys.txt"
> "$CHECK_FILE"
set +m # 关闭监视模式,抑制后台任务提示
export -f check_project_has_keys
export PROGRESS_ACTIVE=1 # 标记进度条活跃状态,避免心跳干扰
local pids=()
local completed_count=0
local total_projects=${#projects_array[@]}
# 显示初始进度条(0%)
show_progress 0 "$total_projects" "检查密钥状态"
for project_id in "${projects_array[@]}"; do
if (( ${#pids[@]} >= MAX_PARALLEL_JOBS )); then
wait -n "${pids[@]}"
for j in "${!pids[@]}"; do
if ! kill -0 "${pids[j]}" 2>/dev/null; then
unset 'pids[j]'
((completed_count++))
fi
done
show_progress "$completed_count" "$total_projects" "检查密钥状态"
fi
check_project_has_keys "$project_id" "$CHECK_FILE" &
pids+=($!)
done
# 等待剩余的后台任务并更新进度条
for pid in "${pids[@]}"; do
wait "$pid"
((completed_count++))
show_progress "$completed_count" "$total_projects" "检查密钥状态"
done
unset PROGRESS_ACTIVE # 清除进度条活跃状态
set -m # 恢复监视模式
local projects_to_process=()
if [ -f "$CHECK_FILE" ]; then
mapfile -t projects_to_process < "$CHECK_FILE"
fi
if [ ${#projects_to_process[@]} -eq 0 ]; then log "INFO" "所有项目均已有密钥,无需操作。"; return 0; fi
log "INFO" "将为 ${#projects_to_process[@]} 个没有密钥的项目创建新密钥。"
read -p "确认继续吗? [y/N]: " r; [[ "$r" =~ ^[Yy]$ ]] || { log "INFO" "操作已取消。"; return 1; }
local ENABLED_PROJECTS_FILE="${TEMP_DIR}/existing_enabled.txt"; run_parallel task_enable_api "启用API" "$ENABLED_PROJECTS_FILE" "${projects_to_process[@]}"
local enabled_project_ids=(); mapfile -t enabled_project_ids < "$ENABLED_PROJECTS_FILE"; if [ ${#enabled_project_ids[@]} -eq 0 ]; then log "ERROR" "API启用阶段失败。"; return 1; fi
local KEYS_CREATED_FILE="${TEMP_DIR}/existing_keys.txt"; run_parallel task_create_key "创建密钥" "$KEYS_CREATED_FILE" "${enabled_project_ids[@]}"
local successful_keys; successful_keys=$(wc -l < "$KEYS_CREATED_FILE" 2>/dev/null || echo 0)
local end_time=$(date +%s)
local duration=$((end_time - start_time))
generate_report "$successful_keys" $((${#projects_to_process[@]} - successful_keys)) "${#projects_to_process[@]}" "提取现有密钥" "$duration"
}
show_menu() {
clear; local current_account; current_account=$(gcloud auth list --filter=status:ACTIVE --format="value(account)" 2>/dev/null | head -n1)
echo " ______ ______ ____ __ __ __ "; echo " / ____/ / ____/ / __ \ / / / / ___ / / ____ ___ _____";
echo " / / __ / / / /_/ / / /_/ / / _ \ / / / __ \ / _ \ / ___/"; echo "/ /_/ / / /___ / ____/ / __ / / __/ / / / /_/ / / __/ / / ";
echo "\____/ \____/ /_/ /_/ /_/ \___/ /_/ / .___/ \___/ /_/ "; echo " /_/ v1.6"
echo "========================================================================"; echo " 当前账号: ${current_account:-未登录} | 并发数: $MAX_PARALLEL_JOBS | 等待时间: $(calculate_global_wait_seconds)s (动态)"
echo "========================================================================"
echo " 1. 一键创建 ${TOTAL_PROJECTS} 个项目并获取密钥 (推荐, 强健)"; echo " 2. 从现有项目中提取密钥 (智能检查)"; echo " 3. 仅创建项目 (不启用API,不取密钥)"
echo " 4. 删除所有现有项目 (危险操作)"; echo " 5. 清理所有项目中的API密钥 (危险操作)"; echo " 6. 修改配置参数"; echo; echo " 0. 退出程序"
echo "========================================================================"
read -p "请选择功能 [0-6]: " choice
case $choice in
1) create_projects_phased ;;
2) extract_keys_from_existing_projects ;;
3) create_projects_only ;;
4) delete_all_existing_projects ;;
5) cleanup_project_api_keys ;;
6) configure_settings; return ;;
0) log "INFO" "程序已退出。"; exit 0 ;;
*) echo "无效选项。" && sleep 1 ;;
esac
read -p "按回车键返回主菜单..."
}
configure_settings() {
while true; do
clear; echo "======================== 配置参数 ========================"
echo " 1. 项目创建数量 : $TOTAL_PROJECTS"; echo " 2. 项目前缀 : $PROJECT_PREFIX (随机生成)"; echo " 3. 最大并发数 : $MAX_PARALLEL_JOBS"
echo " 4. 全局等待时间(秒) : $(calculate_global_wait_seconds) (动态: 项目数×2, 最少60秒)"; echo " 5. 最大重试次数 : $MAX_RETRY_ATTEMPTS"
echo " 6. 最大全局重试次数 : $MAX_RETRY_GLOBAL"; echo " 7. 快速设置 (12项目/12并发)"; echo; echo " 0. 返回主菜单"
echo "================================================================"
read -p "选择要修改的设置 [0-7]: " choice
case $choice in
1) read -p "输入新的项目数量 (1-200): " val; if [[ "$val" =~ ^[1-9][0-9]*$ && "$val" -le 200 ]]; then TOTAL_PROJECTS=$val; log "INFO" "配置更新: 项目数量 -> $TOTAL_PROJECTS"; fi;;
2) echo "项目前缀现在是随机生成的,无法手动修改。" && sleep 2;;
3) read -p "输入最大并发数 (5-50): " val; if [[ "$val" =~ ^[0-9]+$ && "$val" -ge 5 && "$val" -le 50 ]]; then MAX_PARALLEL_JOBS=$val; log "INFO" "配置更新: 最大并发数 -> $MAX_PARALLEL_JOBS"; fi;;
4) echo "等待时间现在是动态计算的 (项目数×2, 最少60秒),无法手动修改。" && sleep 2;;
5) read -p "输入最大重试次数 (1-5): " val; if [[ "$val" =~ ^[1-5]$ ]]; then MAX_RETRY_ATTEMPTS=$val; log "INFO" "配置更新: 重试次数 -> $MAX_RETRY_ATTEMPTS"; fi;;
6) read -p "输入最大全局重试次数 (0-5): " val; if [[ "$val" =~ ^[0-5]$ ]]; then MAX_RETRY_GLOBAL=$val; log "INFO" "配置更新: 全局重试次数 -> $MAX_RETRY_GLOBAL"; fi;;
7) TOTAL_PROJECTS=12; MAX_PARALLEL_JOBS=12; log "INFO" "配置更新: 项目数量 -> 12, 最大并发数 -> 12"; sleep 1;;
0) return;;
*) echo "无效选项。" && sleep 1;;
esac
done
}
check_prerequisites() {
log "INFO" "执行前置检查..."; if ! command -v gcloud &> /dev/null; then log "ERROR" "未找到 'gcloud' 命令。"; return 1; fi
if ! gcloud auth list --filter=status:ACTIVE --format="value(account)" | grep -q "."; then log "WARN" "未检测到活跃GCP账号,请先登录。"; gcloud auth login || return 1; fi
if ! command -v jq &>/dev/null; then log "WARN" "强烈建议安装 'jq' 以获得最可靠的JSON解析!"; fi
log "INFO" "前置检查通过。"; return 0
}
# ===== 程序入口 =====
trap cleanup_resources EXIT SIGINT SIGTERM
if ! check_prerequisites; then log "ERROR" "前置检查失败,程序退出。"; exit 1; fi
log "INFO" "密钥管理器增强版 v1.6 已启动!"
sleep 1
while true; do show_menu; done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment