#!/bin/bash # 菜单函数,显示选项 show_menu() { echo "=====================================" echo " Google Cloud 管理脚本 v0.0.1 " echo "=====================================" echo "1. 添加账号" echo "2. 删除账号" echo "3. 查看所有账号" echo "4. 切换默认账号" echo "5. 添加项目" echo "6. 删除项目" echo "7. 查看所有项目" echo "8. 切换默认项目" echo "9. 查看虚拟机列表" echo "10. 创建虚拟机" echo "11. 删除虚拟机" echo "12. 查看该项目下 socks5 配置" echo "13. 配置防火墙" echo "14. 查看防火墙规则" echo "15. 删除防火墙规则" echo "16. 查看所有可用的结算账号" echo "0. 退出脚本" echo "=====================================" echo "请输入选项(0-16):" } # 获取当前日期,格式为 YYYYMMDD get_current_date() { date +%Y%m%d } # 生成四个随机字符 generate_random_suffix() { cat /dev/urandom | tr -dc 'a-z0-9' | head -c 4 } # 获取当前项目的实例配额和已使用数量 get_instance_quota() { local project=$1 local zone=$2 local region=$(echo "$zone" | sed 's/-[a-z]$//') if [ -z "$region" ]; then echo "无法从区域 $zone 提取大区域信息。" return 1 fi echo "正在获取区域 $region 的配额信息..." quota_info=$(gcloud compute regions describe "$region" --project="$project" --format="json(quotas)" 2>/dev/null) if [ -z "$quota_info" ]; then echo "无法获取配额信息,请检查 gcloud 配置、网络连接或区域是否正确。" return 1 fi quota_limit=$(echo "$quota_info" | grep -A 2 '"metric": "CPUS"' | grep '"limit"' | awk '{print $2}' | tr -d ',' | grep -o '[0-9]*') quota_usage=$(echo "$quota_info" | grep -A 2 '"metric": "CPUS"' | grep '"usage"' | awk '{print $2}' | tr -d ',' | grep -o '[0-9]*') if [ -z "$quota_limit" ] || [ -z "$quota_usage" ]; then echo "无法解析配额数据,可能是输出格式问题。" return 1 fi local available_cpus=$((quota_limit - quota_usage)) local max_instances=$((available_cpus / 2)) if [ "$max_instances" -lt 0 ]; then max_instances=0 fi echo "$max_instances" return 0 } # 动态获取支持 e2-micro 的区域和可用区,并分组(优化版) get_regions_and_zones() { echo "正在获取支持 e2-micro 机器类型的区域和可用区..." # 硬编码的支持 e2-micro 的区域和可用区列表,加速获取 declare -g -A region_map declare -g -a regions regions=("us-central1" "us-east1" "us-east4" "us-east5" "us-east7" "us-west1" "us-west2" "us-west3" "us-west4" "us-west8" "us-south1" "europe-central2" "europe-north1" "europe-north2" "europe-southwest1" "europe-west1" "europe-west2" "europe-west3" "europe-west4" "europe-west6" "europe-west8" "europe-west9" "europe-west10" "europe-west12" "asia-east1" "asia-east2" "asia-northeast1" "asia-northeast2" "asia-northeast3" "asia-south1" "asia-south2" "asia-southeast1" "asia-southeast2" "australia-southeast1" "australia-southeast2" "southamerica-east1" "southamerica-west1" "northamerica-northeast1" "northamerica-northeast2" "northamerica-south1" "me-central1" "me-central2" "me-west1" "africa-south1") # 初始化 region_map region_map["us-central1"]="us-central1-a us-central1-b us-central1-c us-central1-d us-central1-f" region_map["us-east1"]="us-east1-a us-east1-b us-east1-c us-east1-d" region_map["us-east4"]="us-east4-a us-east4-b us-east4-c" region_map["us-east5"]="us-east5-a us-east5-b us-east5-c" region_map["us-east7"]="us-east7-a us-east7-b us-east7-c" region_map["us-west1"]="us-west1-a us-west1-b us-west1-c" region_map["us-west2"]="us-west2-a us-west2-b us-west2-c" region_map["us-west3"]="us-west3-a us-west3-b us-west3-c" region_map["us-west4"]="us-west4-a us-west4-b us-west4-c" region_map["us-west8"]="us-west8-a us-west8-b us-west8-c" region_map["us-south1"]="us-south1-a us-south1-b us-south1-c" region_map["europe-central2"]="europe-central2-a europe-central2-b europe-central2-c" region_map["europe-north1"]="europe-north1-a europe-north1-b europe-north1-c" region_map["europe-north2"]="europe-north2-a europe-north2-b europe-north2-c" region_map["europe-southwest1"]="europe-southwest1-a europe-southwest1-b europe-southwest1-c" region_map["europe-west1"]="europe-west1-b europe-west1-c europe-west1-d" region_map["europe-west2"]="europe-west2-a europe-west2-b europe-west2-c" region_map["europe-west3"]="europe-west3-a europe-west3-b europe-west3-c" region_map["europe-west4"]="europe-west4-a europe-west4-b europe-west4-c" region_map["europe-west6"]="europe-west6-a europe-west6-b europe-west6-c" region_map["europe-west8"]="europe-west8-a europe-west8-b europe-west8-c" region_map["europe-west9"]="europe-west9-a europe-west9-b europe-west9-c" region_map["europe-west10"]="europe-west10-a europe-west10-b europe-west10-c" region_map["europe-west12"]="europe-west12-a europe-west12-b europe-west12-c" region_map["asia-east1"]="asia-east1-a asia-east1-b asia-east1-c" region_map["asia-east2"]="asia-east2-a asia-east2-b asia-east2-c" region_map["asia-northeast1"]="asia-northeast1-a asia-northeast1-b asia-northeast1-c" region_map["asia-northeast2"]="asia-northeast2-a asia-northeast2-b asia-northeast2-c" region_map["asia-northeast3"]="asia-northeast3-a asia-northeast3-b asia-northeast3-c" region_map["asia-south1"]="asia-south1-a asia-south1-b asia-south1-c" region_map["asia-south2"]="asia-south2-a asia-south2-b asia-south2-c" region_map["asia-southeast1"]="asia-southeast1-a asia-southeast1-b asia-southeast1-c" region_map["asia-southeast2"]="asia-southeast2-a asia-southeast2-b asia-southeast2-c" region_map["australia-southeast1"]="australia-southeast1-a australia-southeast1-b australia-southeast1-c" region_map["australia-southeast2"]="australia-southeast2-a australia-southeast2-b australia-southeast2-c" region_map["southamerica-east1"]="southamerica-east1-a southamerica-east1-b southamerica-east1-c" region_map["southamerica-west1"]="southamerica-west1-a southamerica-west1-b southamerica-west1-c" region_map["northamerica-northeast1"]="northamerica-northeast1-a northamerica-northeast1-b northamerica-northeast1-c" region_map["northamerica-northeast2"]="northamerica-northeast2-a northamerica-northeast2-b northamerica-northeast2-c" region_map["northamerica-south1"]="northamerica-south1-a northamerica-south1-b northamerica-south1-c" region_map["me-central1"]="me-central1-a me-central1-b me-central1-c" region_map["me-central2"]="me-central2-a me-central2-b me-central2-c" region_map["me-west1"]="me-west1-a me-west1-b me-west1-c" region_map["africa-south1"]="africa-south1-a africa-south1-b africa-south1-c" # 检查硬编码列表是否有效(例如,至少有一个区域) if [ ${#regions[@]} -eq 0 ]; then echo "硬编码区域列表为空,尝试动态获取支持 e2-micro 的区域和可用区..." zones=$(gcloud compute machine-types list --filter="name=e2-micro" --format="value(ZONE)" --limit=1000 2>/dev/null | sort | uniq) if [ -z "$zones" ]; then echo "无法获取可用区列表,请检查 gcloud 配置或网络连接。" return 1 fi regions=() IFS=$'\n' for zone in $zones; do region=$(echo "$zone" | cut -d'-' -f1) if [[ ! " ${regions[@]} " =~ " ${region} " ]]; then regions+=("$region") fi region_map["$region"]="${region_map[$region]} $zone" done unset IFS fi if [ ${#regions[@]} -eq 0 ]; then echo "未找到支持 e2-micro 的大区域。" return 1 fi return 0 } # 显示大区域选择菜单 show_region_menu() { echo "请选择大区域:" local i=1 for region in "${regions[@]}"; do location=${region_location_map["$region"]} if [ -n "$location" ]; then echo "$i. $region($location)" else echo "$i. $region" fi ((i++)) done echo "0. 取消选择" echo "请输入选项(0-$((i-1))):" } # 显示具体可用区选择菜单 show_zone_menu() { local region=$1 echo "请选择具体可用区($region):" local zones_list=(${region_map["$region"]}) if [ ${#zones_list[@]} -eq 0 ]; then echo "未找到 $region 区域下支持 e2-micro 的可用区。" echo "0. 取消选择" echo "请输入选项(0-0):" return 1 fi local i=1 location=${region_location_map["$region"]} for zone in "${zones_list[@]}"; do if [ -n "$location" ]; then echo "$i. $zone($location)" else echo "$i. $zone" fi ((i++)) done echo "0. 取消选择" echo "请输入选项(0-$((i-1))):" return 0 } # 显示地区选择方式菜单 show_zone_selection_method() { echo "请选择地区选择方式:" local default_zone=$(gcloud config get-value compute/zone 2>/dev/null) if [ -n "$default_zone" ]; then region=$(echo "$default_zone" | sed 's/-[a-z]$//') location=${region_location_map["$region"]} if [ -n "$location" ]; then echo "1. 使用默认地区:$default_zone($location)" else echo "1. 使用默认地区:$default_zone" fi else echo "1. 使用默认地区(未设置,需手动指定)" fi echo "2. 自动获取并选择支持 e2-micro 的区域" echo "3. 修改默认地区" echo "0. 取消选择" echo "请输入选项(0-3):" } # 获取当前项目下的虚拟机列表 get_instance_list() { local project=$1 instance_list=$(gcloud compute instances list --project="$project" --format="value(NAME,ZONE)" 2>/dev/null) if [ -z "$instance_list" ]; then echo "无法获取虚拟机列表,请检查 gcloud 配置或网络连接。" return 1 fi echo "$instance_list" return 0 } # 显示虚拟机列表并返回数组 show_instance_menu() { local instance_data="$1" declare -g -a instance_array instance_array=() echo "当前项目下的虚拟机列表:" if [ -z "$instance_data" ]; then echo "没有找到虚拟机。" return 1 fi local i=1 IFS=$'\n' for line in $instance_data; do if [ -z "$line" ]; then continue fi instance_name=$(echo "$line" | awk '{print $1}') instance_zone=$(echo "$line" | awk '{print $2}') if [ -n "$instance_name" ] && [ -n "$instance_zone" ]; then region=$(echo "$instance_zone" | sed 's/-[a-z]$//') location=${region_location_map["$region"]} if [ -n "$location" ]; then echo "$i. $instance_name (区域: $instance_zone - $location)" else echo "$i. $instance_name (区域: $instance_zone)" fi instance_array+=("$instance_name|$instance_zone") ((i++)) fi done unset IFS if [ ${#instance_array[@]} -eq 0 ]; then echo "没有找到虚拟机。" return 1 fi return 0 } # 获取所有账号列表 get_account_list() { account_list=$(gcloud auth list --format="value(ACCOUNT)" 2>/dev/null) if [ -z "$account_list" ]; then echo "无法获取账号列表,请检查 gcloud 配置或网络连接。" return 1 fi echo "$account_list" return 0 } # 显示账号列表并返回数组 show_account_menu() { local account_data="$1" declare -g -a account_array account_array=() echo "当前已配置的账号列表:" if [ -z "$account_data" ]; then echo "没有找到已配置的账号。" return 1 fi local i=1 IFS=$'\n' for account in $account_data; do if [ -n "$account" ]; then active_mark="" if [[ "$account" == *"(active)"* ]]; then active_mark=" (当前默认)" account=$(echo "$account" | sed 's/ (active)//') fi echo "$i. $account$active_mark" account_array+=("$account") ((i++)) fi done unset IFS if [ ${#account_array[@]} -eq 0 ]; then echo "没有找到已配置的账号。" return 1 fi return 0 } # 获取所有项目列表 get_project_list() { project_list=$(gcloud projects list --format="value(PROJECT_ID,NAME)" 2>/dev/null) if [ -z "$project_list" ]; then echo "无法获取项目列表,请检查 gcloud 配置或网络连接。" return 1 fi echo "$project_list" return 0 } # 显示项目列表并返回数组 show_project_menu() { local project_data="$1" declare -g -a project_array project_array=() echo "当前可用的项目列表:" if [ -z "$project_data" ]; then echo "没有找到项目。" return 1 fi local i=1 IFS=$'\n' for line in $project_data; do if [ -z "$line" ]; then continue fi project_id=$(echo "$line" | awk '{print $1}') project_name=$(echo "$line" | awk '{$1=""; print $0}' | sed 's/^ //') if [ -n "$project_id" ]; then current_mark="" current_project=$(gcloud config get-value project 2>/dev/null) if [ "$project_id" == "$current_project" ]; then current_mark=" (当前默认)" fi echo "$i. $project_id - $project_name$current_mark" project_array+=("$project_id") ((i++)) fi done unset IFS if [ ${#project_array[@]} -eq 0 ]; then echo "没有找到项目。" return 1 fi return 0 } # 获取可用结算账号列表 get_billing_accounts() { echo "正在获取可用的结算账号列表..." # 尝试列出状态为 OPEN 且当前用户有权关联的结算账号 billing_accounts_list=$(gcloud billing accounts list --filter='open=true' --format="value(ACCOUNT_ID, NAME)" 2>/dev/null) if [ -z "$billing_accounts_list" ]; then echo "未能获取到结算账号列表。可能是因为:" echo "1. 当前授权账号没有查看结算账号的权限。" echo "2. 没有可用的(状态为 OPEN)结算账号。" echo "3. gcloud 配置或网络连接问题。" return 1 # 指示失败或未找到账户 fi declare -g -a billing_account_array_ids # 仅存储账户 ID declare -g -a billing_account_array_display # 存储用于菜单显示的信息 billing_account_array_ids=() billing_account_array_display=() local i=1 IFS=$'\n' for line in $billing_accounts_list; do if [ -z "$line" ]; then continue fi account_id=$(echo "$line" | cut -d$'\t' -f1) # 获取账户 ID 之后的所有内容作为显示名称, 如果存在的话 display_name=$(echo "$line" | cut -d$'\t' -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') if [ -n "$account_id" ]; then billing_account_array_ids+=("$account_id") # 检查显示名称是否为空,如果为空则只显示账号ID,否则显示ID和名称 if [ -z "$display_name" ]; then billing_account_array_display+=("$i. $account_id") else billing_account_array_display+=("$i. $account_id - $display_name") fi ((i++)) fi done unset IFS if [ ${#billing_account_array_ids[@]} -eq 0 ]; then echo "未找到可用的结算账号。" return 1 # 指示未找到账户 fi return 0 # 成功 } # 显示结算账号选择菜单 show_billing_account_menu() { echo "请选择要关联的结算账号:" if [ ${#billing_account_array_display[@]} -eq 0 ]; then # 此情况理论上如果 get_billing_accounts 返回 0 则不会发生,但作为健壮性检查 echo "错误:billing_account_array_display 为空,但 get_billing_accounts 返回成功。" echo "0. 取消并返回主菜单" echo "m. 手动输入结算账号 ID" return 1 # 表示菜单显示有问题 fi for item in "${billing_account_array_display[@]}"; do echo "$item" done echo "-------------------------------------" echo "m. 手动输入结算账号 ID" echo "0. 取消并返回主菜单" echo "请输入选项 (1-${#billing_account_array_ids[@]}, m, 或 0):" return 0 } # 定义区域到地理位置的映射 declare -A region_location_map region_location_map["africa-south1"]="南非约翰内斯堡" region_location_map["us-central1"]="美国爱荷华州" region_location_map["us-east1"]="美国南卡罗来纳州" region_location_map["us-east4"]="美国弗吉尼亚州北部" region_location_map["us-east5"]="美国俄亥俄州" region_location_map["us-east7"]="美国弗吉尼亚州" region_location_map["us-west1"]="美国俄勒冈州" region_location_map["us-west2"]="美国洛杉矶" region_location_map["us-west3"]="美国盐湖城" region_location_map["us-west4"]="美国拉斯维加斯" region_location_map["us-west8"]="美国萨克拉门托" region_location_map["us-south1"]="美国达拉斯" region_location_map["europe-central2"]="波兰华沙" region_location_map["europe-north1"]="芬兰哈米纳" region_location_map["europe-north2"]="挪威奥斯陆" region_location_map["europe-southwest1"]="西班牙马德里" region_location_map["europe-west1"]="比利时圣吉斯兰" region_location_map["europe-west2"]="英国伦敦" region_location_map["europe-west3"]="德国法兰克福" region_location_map["europe-west4"]="荷兰埃姆斯哈文" region_location_map["europe-west6"]="瑞士苏黎世" region_location_map["europe-west8"]="意大利米兰" region_location_map["europe-west9"]="法国巴黎" region_location_map["europe-west10"]="德国柏林" region_location_map["europe-west12"]="意大利都灵" region_location_map["asia-east1"]="中国台湾彰化县" region_location_map["asia-east2"]="中国香港" region_location_map["asia-northeast1"]="日本东京" region_location_map["asia-northeast2"]="日本大阪" region_location_map["asia-northeast3"]="韩国首尔" region_location_map["asia-south1"]="印度孟买" region_location_map["asia-south2"]="印度德里" region_location_map["asia-southeast1"]="新加坡" region_location_map["asia-southeast2"]="印度尼西亚雅加达" region_location_map["australia-southeast1"]="澳大利亚悉尼" region_location_map["australia-southeast2"]="澳大利亚墨尔本" region_location_map["southamerica-east1"]="巴西圣保罗" region_location_map["southamerica-west1"]="智利圣地亚哥" region_location_map["northamerica-northeast1"]="加拿大蒙特利尔" region_location_map["northamerica-northeast2"]="加拿大多伦多" region_location_map["northamerica-south1"]="墨西哥城" region_location_map["me-central1"]="卡塔尔多哈" region_location_map["me-central2"]="沙特阿拉伯达曼" region_location_map["me-west1"]="以色列特拉维夫" # 主循环 while true; do show_menu read -e -r choice # 使用 -e 参数启用 readline 支持删除键等功能 case $choice in 1) echo "正在添加账号..." echo "请按照提示完成账号登录流程(浏览器将打开进行身份验证):" gcloud auth login --no-launch-browser if [ $? -eq 0 ]; then echo "账号添加成功!" else echo "账号添加失败,请检查网络连接或权限。" fi echo "按任意键返回菜单..." read -e -r -n 1 ;; 2) echo "正在删除账号..." account_data=$(get_account_list) if [ $? -ne 0 ]; then echo "按任意键返回菜单..." read -e -r -n 1 continue fi show_account_menu "$account_data" if [ $? -ne 0 ]; then echo "按任意键返回菜单..." read -e -r -n 1 continue fi echo "0. 取消选择" echo "请输入要删除的账号编号(多个编号用空格分隔,例如 '1 2',输入 'all' 删除全部非默认账号):" read -e -r input_selection if [ -z "$input_selection" ] || [ "$input_selection" == "0" ]; then echo "操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 处理用户选择 declare -a selected_accounts current_account=$(gcloud auth list --format="value(ACCOUNT)" | grep "(active)" | sed 's/ (active)//') if [ "$input_selection" == "all" ] || [ "$input_selection" == "ALL" ]; then for account in "${account_array[@]}"; do if [ "$account" != "$current_account" ]; then selected_accounts+=("$account") fi done else IFS=' ' read -ra selections <<< "$input_selection" for sel in "${selections[@]}"; do if [[ "$sel" =~ ^[0-9]+$ ]] && [ "$sel" -ge 1 ] && [ "$sel" -le "${#account_array[@]}" ]; then account="${account_array[$((sel-1))]}" if [ "$account" != "$current_account" ]; then selected_accounts+=("$account") else echo "无法删除当前默认账号:$account,已忽略。" fi else echo "无效选项:$sel,已忽略。" fi done unset IFS fi # 如果没有有效选择 if [ ${#selected_accounts[@]} -eq 0 ]; then echo "没有有效的账号被选中,操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 确认删除 echo "您选择了以下账号进行删除:" for account in "${selected_accounts[@]}"; do echo "- $account" done echo "确认删除这些账号吗?(输入 'yes' 确认,任意其他输入取消):" read -e -r confirm if [ "$confirm" != "yes" ] && [ "$confirm" != "YES" ]; then echo "删除操作已取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 执行删除操作 echo "正在删除选中的账号..." for account in "${selected_accounts[@]}"; do echo "删除 $account..." gcloud auth revoke "$account" --quiet 2>/dev/null if [ $? -eq 0 ]; then echo "账号 $account 删除成功!" else echo "账号 $account 删除失败,请检查错误信息。" fi done echo "按任意键返回菜单..." read -e -r -n 1 ;; 3) echo "正在查看所有账号..." account_data=$(get_account_list) if [ $? -ne 0 ]; then echo "按任意键返回菜单..." read -e -r -n 1 continue fi show_account_menu "$account_data" echo "按任意键返回菜单..." read -e -r -n 1 ;; 4) echo "正在切换默认账号..." account_data=$(get_account_list) if [ $? -ne 0 ]; then echo "按任意键返回菜单..." read -e -r -n 1 continue fi show_account_menu "$account_data" if [ $? -ne 0 ]; then echo "按任意键返回菜单..." read -e -r -n 1 continue fi echo "0. 取消选择" echo "请输入要设置为默认账号的编号:" read -e -r selection if [ -z "$selection" ] || [ "$selection" == "0" ]; then echo "操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi if [[ "$selection" =~ ^[0-9]+$ ]] && [ "$selection" -ge 1 ] && [ "$selection" -le "${#account_array[@]}" ]; then selected_account="${account_array[$((selection-1))]}" echo "正在将默认账号切换为 $selected_account..." gcloud config set account "$selected_account" if [ $? -eq 0 ]; then echo "默认账号已切换为 $selected_account!" else echo "切换默认账号失败,请检查错误信息。" fi else echo "无效选项,操作取消。" fi echo "按任意键返回菜单..." read -e -r -n 1 ;; 5) echo "正在添加项目 (并关联结算账号)..." echo "请输入项目 ID(6-30 个字符,小写字母开头,可包含小写字母、数字或连字符,例如 my-project-123):" read -e -r project_id if [ -z "$project_id" ]; then echo "未输入项目 ID,操作取消。" elif ! [[ "$project_id" =~ ^[a-z][a-z0-9-]{4,28}[a-z0-9]$ ]]; then echo "项目 ID 格式无效。要求:以小写字母开头,可包含小写字母、数字或连字符,总长度为 6 到 30 个字符。" project_id="" # 清空以便后续判断 fi if [ -z "$project_id" ]; then # 如果 project_id 无效或为空 echo "按任意键返回菜单..." read -e -r -n 1 continue fi echo "请输入项目名称(可选,可包含空格,例如 My Project,留空则默认使用项目 ID):" read -e -r project_name # 项目名称可以为空,GCP 会默认使用项目 ID selected_billing_account_id="" echo # 空行以改善可读性 get_billing_accounts billing_fetch_status=$? # 0 表示成功获取并有账户,1 表示获取失败或无账户 while true; do if [ "$billing_fetch_status" -eq 0 ]; then # 成功获取到账户列表 show_billing_account_menu else # 未能自动获取到账户 echo "未能自动获取结算账户列表。" echo "您可以选择:" echo "m. 手动输入结算账号 ID" echo "0. 取消并返回主菜单" echo "请输入选项 (m 或 0):" fi read -e -r billing_choice if [ "$billing_choice" == "0" ]; then echo "操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue 2 # 继续外层主循环 elif [ "$billing_choice" == "m" ] || [ "$billing_choice" == "M" ]; then echo "请输入要关联的结算账号 ID (格式: XXXXXX-XXXXXX-XXXXXX):" read -e -r manual_billing_id if [ -z "$manual_billing_id" ]; then echo "未输入结算账号 ID。请重新选择或输入。" # 如果之前获取失败,再次提示手动或取消 if [ "$billing_fetch_status" -ne 0 ]; then continue; fi elif ! [[ "$manual_billing_id" =~ ^[0-9A-F]{6}-[0-9A-F]{6}-[0-9A-F]{6}$ ]]; then echo "结算账号 ID 格式无效 (期望格式: XXXXXX-XXXXXX-XXXXXX)。请检查并重新输入。" if [ "$billing_fetch_status" -ne 0 ]; then continue; fi else selected_billing_account_id="$manual_billing_id" echo "将使用手动输入的结算账号:$selected_billing_account_id" break # 跳出结算账号选择循环 fi # 仅当 billing_fetch_status 为 0 (即列表成功显示) 时,才处理数字选项 elif [ "$billing_fetch_status" -eq 0 ] && [[ "$billing_choice" =~ ^[0-9]+$ ]] && [ "$billing_choice" -ge 1 ] && [ "$billing_choice" -le "${#billing_account_array_ids[@]}" ]; then selected_billing_account_id="${billing_account_array_ids[$((billing_choice-1))]}" echo "已选择结算账号:$selected_billing_account_id" break # 跳出结算账号选择循环 else echo "无效选项 '$billing_choice'。" if [ "$billing_fetch_status" -ne 0 ]; then echo "由于未能自动获取结算账户列表,您需要手动输入 (m) 或取消 (0)。" else echo "请输入列表中的数字、'm' 或 '0'。" fi fi done if [ -z "$selected_billing_account_id" ]; then # 如果循环结束还没有选定结算账号 echo "未指定有效的结算账号,项目创建取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi echo # 空行 echo "即将执行以下操作:" echo " 创建项目 ID: $project_id" if [ -n "$project_name" ]; then echo " 项目名称: $project_name" else echo " 项目名称: (将使用项目 ID)" fi echo " 关联结算账号: $selected_billing_account_id" echo "确认创建项目并关联结算账号吗? (输入 'yes' 确认):" read -e -r confirm_create if [[ "$confirm_create" != "yes" ]]; then echo "项目创建已取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 创建项目和关联结算账号分两步执行 echo "正在创建项目..." if [ -z "$project_name" ]; then gcloud projects create "$project_id" --quiet else gcloud projects create "$project_id" --name="$project_name" --quiet fi create_status=$? if [ $create_status -eq 0 ]; then echo "项目 '$project_id' 创建请求已提交。" echo "正在关联结算账号..." # 关联结算账号 gcloud billing projects link "$project_id" --billing-account="$selected_billing_account_id" --quiet billing_status=$? if [ $billing_status -eq 0 ]; then echo "项目 '$project_id' 已成功关联到结算账号 '$selected_billing_account_id'。" echo "注意:项目创建和结算账号关联可能是异步操作,可能需要一些时间才能完全生效并在控制台中正确显示。" echo "您可以稍后使用 'gcloud billing projects describe $project_id' 来验证结算信息。" else echo "结算账号关联失败 (退出码: $billing_status)。请检查以下可能的原因:" echo "1. 结算账号 ID '$selected_billing_account_id' 无效、您没有权限使用它,或者它不处于活动状态。" echo "2. 当前授权账号可能没有足够的权限(如 roles/billing.user)。" echo "3. Google Cloud API 可能遇到临时问题。" echo "您可以稍后手动关联结算账号:gcloud billing projects link $project_id --billing-account=$selected_billing_account_id" fi else echo "项目创建或结算账号关联失败 (退出码: $create_status)。请检查以下可能的原因:" echo "1. 项目 ID '$project_id' 可能已被占用或格式仍不符合最新 GCP 要求。" echo "2. 结算账号 ID '$selected_billing_account_id' 无效、您没有权限使用它,或者它不处于活动状态。" echo "3. 当前授权账号可能没有足够的权限(如 roles/billing.user, roles/resourcemanager.projectCreator)。" echo "4. Google Cloud API 可能遇到临时问题。" echo "请检查 gcloud 的详细错误输出(如果上述命令未加 --quiet,则会有输出)。" fi echo "按任意键返回菜单..." read -e -r -n 1 ;; 6) echo "正在删除项目..." project_data=$(get_project_list) if [ $? -ne 0 ]; then echo "按任意键返回菜单..." read -e -r -n 1 continue fi show_project_menu "$project_data" if [ $? -ne 0 ]; then echo "按任意键返回菜单..." read -e -r -n 1 continue fi echo "0. 取消选择" echo "请输入要删除的项目编号(多个编号用空格分隔,例如 '1 2',输入 'all' 删除全部非默认项目):" read -e -r input_selection if [ -z "$input_selection" ] || [ "$input_selection" == "0" ]; then echo "操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 处理用户选择 declare -a selected_projects current_project=$(gcloud config get-value project 2>/dev/null) if [ "$input_selection" == "all" ] || [ "$input_selection" == "ALL" ]; then for project in "${project_array[@]}"; do if [ "$project" != "$current_project" ]; then selected_projects+=("$project") fi done else IFS=' ' read -ra selections <<< "$input_selection" for sel in "${selections[@]}"; do if [[ "$sel" =~ ^[0-9]+$ ]] && [ "$sel" -ge 1 ] && [ "$sel" -le "${#project_array[@]}" ]; then project="${project_array[$((sel-1))]}" if [ "$project" != "$current_project" ]; then selected_projects+=("$project") else echo "无法删除当前默认项目:$project,已忽略。" fi else echo "无效选项:$sel,已忽略。" fi done unset IFS fi # 如果没有有效选择 if [ ${#selected_projects[@]} -eq 0 ]; then echo "没有有效的项目被选中,操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 确认删除 echo "您选择了以下项目进行删除:" for project in "${selected_projects[@]}"; do echo "- $project" done echo "确认删除这些项目吗?(输入 'yes' 确认,任意其他输入取消):" read -e -r confirm if [ "$confirm" != "yes" ] && [ "$confirm" != "YES" ]; then echo "删除操作已取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 执行删除操作 echo "正在删除选中的项目..." for project in "${selected_projects[@]}"; do echo "删除 $project..." gcloud projects delete "$project" --quiet 2>/dev/null if [ $? -eq 0 ]; then echo "项目 $project 删除成功!" else echo "项目 $project 删除失败,请检查错误信息或是否有权限。" fi done echo "按任意键返回菜单..." read -e -r -n 1 ;; 7) echo "正在查看所有项目..." project_data=$(get_project_list) if [ $? -ne 0 ]; then echo "按任意键返回菜单..." read -e -r -n 1 continue fi show_project_menu "$project_data" echo "按任意键返回菜单..." read -e -r -n 1 ;; 8) echo "正在切换默认项目..." project_data=$(get_project_list) if [ $? -ne 0 ]; then echo "按任意键返回菜单..." read -e -r -n 1 continue fi show_project_menu "$project_data" if [ $? -ne 0 ]; then echo "按任意键返回菜单..." read -e -r -n 1 continue fi echo "0. 取消选择" echo "请输入要设置为默认项目的编号:" read -e -r selection if [ -z "$selection" ] || [ "$selection" == "0" ]; then echo "操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi if [[ "$selection" =~ ^[0-9]+$ ]] && [ "$selection" -ge 1 ] && [ "$selection" -le "${#project_array[@]}" ]; then selected_project="${project_array[$((selection-1))]}" echo "正在将默认项目切换为 $selected_project..." gcloud config set project "$selected_project" if [ $? -eq 0 ]; then echo "默认项目已切换为 $selected_project!" else echo "切换默认项目失败,请检查错误信息或是否有权限。" fi else echo "无效选项,操作取消。" fi echo "按任意键返回菜单..." read -e -r -n 1 ;; 9) echo "正在查看虚拟机列表..." current_project=$(gcloud config get-value project) if [ -z "$current_project" ]; then echo "未找到默认项目,请先切换默认项目(选项 8)。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi echo "当前默认项目:$current_project" instance_data=$(get_instance_list "$current_project") if [ $? -ne 0 ]; then echo "按任意键返回菜单..." read -e -r -n 1 continue fi show_instance_menu "$instance_data" echo "按任意键返回菜单..." read -e -r -n 1 ;; 10) echo "正在创建虚拟机..." current_project=$(gcloud config get-value project) if [ -z "$current_project" ]; then echo "未找到默认项目,请先切换默认项目(选项 8)。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi echo "当前默认项目:$current_project" current_date=$(get_current_date) echo "当前日期:$current_date" echo "请输入机器标识符(如 us01, sg002):" read -e -r machine_id if [ -z "$machine_id" ]; then echo "未输入机器标识符,操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 下载启动脚本 # echo "正在从远程地址下载启动脚本..." # script_url="https://github.com/Lsmoisu/Toolbox/raw/refs/heads/main/#enablesshandcreatesocks5.sh" # if ! command -v curl &> /dev/null; then # echo "curl 未安装,尝试安装..." # if command -v apt-get &> /dev/null; then # sudo apt-get update && sudo apt-get install -y curl # elif command -v yum &> /dev/null; then # sudo yum install -y curl # else # echo "无法安装 curl,请手动安装后重试。" # echo "按任意键返回菜单..." # read -e -r -n 1 # continue # fi # fi # # # 使用 -L 参数跟随重定向,-sS 减少输出,-f 失败时返回错误码 # if curl -L -sS -f -o startup-script.sh "$script_url"; then # echo "启动脚本下载成功!" # # 检查文件是否为空 # if [ -s startup-script.sh ]; then # echo "脚本内容非空,下载验证通过。" # chmod +x startup-script.sh # else # echo "错误:下载的脚本文件为空,请检查URL内容或网络连接。" # rm -f startup-script.sh # echo "按任意键返回菜单..." # read -e -r -n 1 # continue # fi # else # echo "启动脚本下载失败,请检查网络连接或URL是否正确。" # echo "错误信息:" # curl -L -sS "$script_url" -o /dev/null -w "%{http_code}\n" # rm -f startup-script.sh # echo "按任意键返回菜单..." # read -e -r -n 1 # continue # fi #检测初始化脚本 echo "正在检查初始化脚本..." if [ ! -f "/opt/gcloud/startup-script.sh" ]; then echo "未找到初始化脚本 startup-script.sh,请确保脚本位于当前目录。" echo "按任意键返回菜单..." read -e -r -n 1 fi if [ -s /opt/gcloud/startup-script.sh ]; then echo "脚本内容非空,验证通过。" chmod +x /opt/gcloud/startup-script.sh else echo "错误:初始化脚本检查失败,请检查/opt/gcloud/startup-script.sh" echo "按任意键返回菜单..." read -e -r -n 1 continue fi region_location_map["us-east7"]="美国弗吉尼亚州" region_location_map["us-west1"]="美国俄勒冈州" region_location_map["us-west2"]="美国加利福尼亚州洛杉矶" region_location_map["us-west3"]="美国犹他州盐湖城" region_location_map["us-west4"]="美国内华达州拉斯维加斯" region_location_map["us-west8"]="美国德克萨斯州达拉斯" region_location_map["us-south1"]="美国得克萨斯州" region_location_map["europe-central2"]="波兰华沙" region_location_map["europe-north1"]="芬兰哈米纳" region_location_map["europe-north2"]="挪威斯塔万格" region_location_map["europe-southwest1"]="西班牙马德里" region_location_map["europe-west1"]="比利时圣吉斯兰" region_location_map["europe-west2"]="英国伦敦" region_location_map["europe-west3"]="德国法兰克福" region_location_map["europe-west4"]="荷兰埃姆斯哈文" region_location_map["europe-west6"]="瑞士苏黎世" region_location_map["europe-west8"]="意大利米兰" region_location_map["europe-west9"]="法国巴黎" region_location_map["europe-west10"]="德国柏林" region_location_map["europe-west12"]="意大利都灵" region_location_map["asia-east1"]="台湾彰化县" region_location_map["asia-east2"]="中国香港" region_location_map["asia-northeast1"]="日本东京" region_location_map["asia-northeast2"]="日本大阪" region_location_map["asia-northeast3"]="韩国首尔" region_location_map["asia-south1"]="印度孟买" region_location_map["asia-south2"]="印度德里" region_location_map["asia-southeast1"]="新加坡" region_location_map["asia-southeast2"]="亚太地区印度尼西亚雅加达" region_location_map["australia-southeast1"]="澳大利亚悉尼" region_location_map["australia-southeast2"]="澳大利亚墨尔本" region_location_map["southamerica-east1"]="巴西圣保罗" region_location_map["southamerica-west1"]="智利圣地亚哥" region_location_map["northamerica-northeast1"]="加拿大蒙特利尔" region_location_map["northamerica-northeast2"]="加拿大多伦多" region_location_map["northamerica-south1"]="美国南卡罗来纳州" region_location_map["me-central1"]="卡塔尔多哈" region_location_map["me-central2"]="沙特阿拉伯达曼" region_location_map["me-west1"]="以色列特拉维夫" # 选择地区选择方式 while true; do show_zone_selection_method read -e -r method_choice if [ "$method_choice" -eq 0 ]; then echo "操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue 2 elif [ "$method_choice" -eq 1 ]; then default_zone=$(gcloud config get-value compute/zone 2>/dev/null) if [ -n "$default_zone" ]; then region=$(echo "$default_zone" | cut -d'-' -f1-2) location=${region_location_map["$region"]} if [ -n "$location" ]; then echo "使用默认地区:$default_zone($location)" else echo "使用默认地区:$default_zone" fi zone="$default_zone" break else echo "未设置默认地区,请手动输入一个区域作为默认地区(如 us-central1-a):" read -e -r input_zone if [ -z "$input_zone" ]; then echo "未输入区域,操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue 2 fi echo "正在设置默认地区为 $input_zone..." gcloud config set compute/zone "$input_zone" if [ $? -eq 0 ]; then region=$(echo "$input_zone" | cut -d'-' -f1-2) location=${region_location_map["$region"]} if [ -n "$location" ]; then echo "默认地区已设置为 $input_zone($location)" else echo "默认地区已设置为 $input_zone" fi zone="$input_zone" break else echo "设置默认地区失败,请检查区域格式或权限。" echo "按任意键返回菜单..." read -e -r -n 1 continue 2 fi fi elif [ "$method_choice" -eq 2 ]; then echo "正在获取支持 e2-micro 机器类型的区域和可用区..." get_regions_and_zones if [ $? -ne 0 ]; then echo "按任意键返回菜单..." read -e -r -n 1 continue 2 fi while true; do echo "请选择大区域:" for i in "${!regions[@]}"; do region="${regions[$i]}" location=${region_location_map["$region"]} if [ -n "$location" ]; then printf "%2d. %s (%s)\n" "$((i+1))" "$region" "$location" else printf "%2d. %s\n" "$((i+1))" "$region" fi done echo "0. 取消选择" echo "请输入选项(0-${#regions[@]}):" read -e -r region_choice if [ "$region_choice" -eq 0 ]; then echo "操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue 3 fi if [ "$region_choice" -ge 1 ] && [ "$region_choice" -le "${#regions[@]}" ]; then region="${regions[$((region_choice-1))]}" break else echo "无效选项,请选择 0-${#regions[@]} 之间的数字。" fi done echo "选择的大区域:$region" while true; do show_zone_menu "$region" if [ $? -ne 0 ]; then echo "操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue 3 fi read -e -r zone_choice zones_list=(${region_map["$region"]}) if [ "$zone_choice" -eq 0 ]; then echo "操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue 3 fi if [ "$zone_choice" -ge 1 ] && [ "$zone_choice" -le "${#zones_list[@]}" ]; then zone="${zones_list[$((zone_choice-1))]}" # 自动设置选择的区域为默认区域 echo "正在设置默认地区为 $zone..." gcloud config set compute/zone "$zone" region=$(echo "$zone" | cut -d'-' -f1-2) location=${region_location_map["$region"]} if [ $? -eq 0 ]; then if [ -n "$location" ]; then echo "默认地区已设置为 $zone($location)" else echo "默认地区已设置为 $zone" fi else if [ -n "$location" ]; then echo "设置默认地区失败,请检查区域格式或权限,但将继续使用 $zone($location) 创建虚拟机。" else echo "设置默认地区失败,请检查区域格式或权限,但将继续使用 $zone 创建虚拟机。" fi fi break else echo "无效选项,请选择 0-${#zones_list[@]} 之间的数字。" fi done region=$(echo "$zone" | cut -d'-' -f1-2) location=${region_location_map["$region"]} if [ -n "$location" ]; then echo "选择的可用区:$zone($location)" else echo "选择的可用区:$zone" fi break elif [ "$method_choice" -eq 3 ]; then echo "请输入新的默认地区(如 us-central1-a):" read -e -r new_zone if [ -z "$new_zone" ]; then echo "未输入区域,操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue 2 fi echo "正在设置默认地区为 $new_zone..." gcloud config set compute/zone "$new_zone" if [ $? -eq 0 ]; then region=$(echo "$new_zone" | cut -d'-' -f1-2) location=${region_location_map["$region"]} if [ -n "$location" ]; then echo "默认地区已更新为 $new_zone($location)" else echo "默认地区已更新为 $new_zone" fi else echo "设置默认地区失败,请检查区域格式或权限。" echo "按任意键返回菜单..." read -e -r -n 1 continue 2 fi else echo "无效选项,请选择 0-3 之间的数字。" fi done # 直接提示用户输入创建数量(保持不变) while true; do echo "请输入要创建的虚拟机数量(大于 0 的整数):" read -e -r instance_count if [[ ! "$instance_count" =~ ^[0-9]+$ ]] || [ "$instance_count" -le 0 ]; then echo "无效输入,请输入大于 0 的整数。" continue fi break done # 批量创建虚拟机(保持不变) echo "将创建 $instance_count 台虚拟机..." for ((i=1; i<=instance_count; i++)); do random_suffix=$(generate_random_suffix) instance_name="instance-${current_date}-${machine_id}-${random_suffix}" device_name="$instance_name" echo "正在创建第 $i 台虚拟机:$instance_name..." gcloud compute instances create "$instance_name" \ --project="$current_project" \ --zone="$zone" \ --machine-type=e2-micro \ --network-interface=network-tier=PREMIUM,stack-type=IPV4_ONLY,subnet=default \ --metadata-from-file startup-script=/opt/gcloud/startup-script.sh \ --maintenance-policy=MIGRATE \ --provisioning-model=STANDARD \ --scopes=https://www.googleapis.com/auth/devstorage.read_only,https://www.googleapis.com/auth/logging.write,https://www.googleapis.com/auth/monitoring.write,https://www.googleapis.com/auth/service.management.readonly,https://www.googleapis.com/auth/servicecontrol,https://www.googleapis.com/auth/trace.append \ --create-disk=auto-delete=yes,boot=yes,device-name="$device_name",image=projects/debian-cloud/global/images/debian-12-bookworm-v20250513,mode=rw,size=10,type=pd-standard \ --no-shielded-secure-boot \ --shielded-vtpm \ --shielded-integrity-monitoring \ --labels=goog-ec-src=vm_add-gcloud \ --reservation-affinity=any if [ $? -eq 0 ]; then echo "第 $i 台虚拟机 $instance_name 创建成功!" else echo "第 $i 台虚拟机 $instance_name 创建失败,请检查错误信息。" fi done echo "按任意键返回菜单..." read -e -r -n 1 ;; 11) echo "正在删除虚拟机..." current_project=$(gcloud config get-value project) if [ -z "$current_project" ]; then echo "未找到默认项目,请先切换默认项目(选项 8)。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi echo "当前默认项目:$current_project" # 获取虚拟机列表 instance_data=$(get_instance_list "$current_project") if [ $? -ne 0 ]; then echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 显示虚拟机列表 show_instance_menu "$instance_data" if [ $? -ne 0 ]; then echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 提示用户选择要删除的虚拟机 echo "0. 取消选择" echo "请输入要删除的虚拟机编号(多个编号用空格分隔,例如 '1 2 3',输入 'all' 删除全部):" read -e -r input_selection if [ -z "$input_selection" ] || [ "$input_selection" == "0" ]; then echo "操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 处理用户选择 declare -a selected_instances if [ "$input_selection" == "all" ] || [ "$input_selection" == "ALL" ]; then for instance in "${instance_array[@]}"; do selected_instances+=("$instance") done else IFS=' ' read -ra selections <<< "$input_selection" for sel in "${selections[@]}"; do if [[ "$sel" =~ ^[0-9]+$ ]] && [ "$sel" -ge 1 ] && [ "$sel" -le "${#instance_array[@]}" ]; then selected_instances+=("${instance_array[$((sel-1))]}") else echo "无效选项:$sel,已忽略。" fi done unset IFS fi # 如果没有有效选择 if [ ${#selected_instances[@]} -eq 0 ]; then echo "没有有效的虚拟机被选中,操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 确认删除 echo "您选择了以下虚拟机进行删除:" for inst in "${selected_instances[@]}"; do instance_name=$(echo "$inst" | cut -d'|' -f1) instance_zone=$(echo "$inst" | cut -d'|' -f2) region=$(echo "$instance_zone" | sed 's/-[a-z]$//') location=${region_location_map["$region"]} if [ -n "$location" ]; then echo "- $instance_name (区域: $instance_zone($location))" else echo "- $instance_name (区域: $instance_zone)" fi done echo "确认删除这些虚拟机吗?(输入 'yes' 确认,任意其他输入取消):" read -e -r confirm if [ "$confirm" != "yes" ] && [ "$confirm" != "YES" ]; then echo "删除操作已取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 执行删除操作 echo "正在删除选中的虚拟机..." for inst in "${selected_instances[@]}"; do instance_name=$(echo "$inst" | cut -d'|' -f1) instance_zone=$(echo "$inst" | cut -d'|' -f2) echo "删除 $instance_name (区域: $instance_zone)..." gcloud compute instances delete "$instance_name" \ --project="$current_project" \ --zone="$instance_zone" \ --quiet 2>/dev/null if [ $? -eq 0 ]; then echo "虚拟机 $instance_name 删除成功!" else echo "虚拟机 $instance_name 删除失败,请检查错误信息。" fi done echo "按任意键返回菜单..." read -e -r -n 1 ;; 12) echo "正在查看当前项目下所有实例的 socks5 配置..." current_project=$(gcloud config get-value project) if [ -z "$current_project" ]; then echo "未找到默认项目,请先切换默认项目(选项 8)。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi echo "当前默认项目:$current_project" instance_data=$(get_instance_list "$current_project") if [ $? -ne 0 ]; then echo "按任意键返回菜单..." read -e -r -n 1 continue fi region_location_map["us-east7"]="美国弗吉尼亚州" region_location_map["us-west1"]="美国俄勒冈州" region_location_map["us-west2"]="美国加利福尼亚州洛杉矶" region_location_map["us-west3"]="美国犹他州盐湖城" region_location_map["us-west4"]="美国内华达州拉斯维加斯" region_location_map["us-west8"]="美国德克萨斯州达拉斯" region_location_map["us-south1"]="美国得克萨斯州" region_location_map["europe-central2"]="波兰华沙" region_location_map["europe-north1"]="芬兰哈米纳" region_location_map["europe-north2"]="挪威斯塔万格" region_location_map["europe-southwest1"]="西班牙马德里" region_location_map["europe-west1"]="比利时圣吉斯兰" region_location_map["europe-west2"]="英国伦敦" region_location_map["europe-west3"]="德国法兰克福" region_location_map["europe-west4"]="荷兰埃姆斯哈文" region_location_map["europe-west6"]="瑞士苏黎世" region_location_map["europe-west8"]="意大利米兰" region_location_map["europe-west9"]="法国巴黎" region_location_map["europe-west10"]="德国柏林" region_location_map["europe-west12"]="意大利都灵" region_location_map["asia-east1"]="台湾彰化县" region_location_map["asia-east2"]="中国香港" region_location_map["asia-northeast1"]="日本东京" region_location_map["asia-northeast2"]="日本大阪" region_location_map["asia-northeast3"]="韩国首尔" region_location_map["asia-south1"]="印度孟买" region_location_map["asia-south2"]="印度德里" region_location_map["asia-southeast1"]="新加坡" region_location_map["asia-southeast2"]="亚太地区印度尼西亚雅加达" region_location_map["australia-southeast1"]="澳大利亚悉尼" region_location_map["australia-southeast2"]="澳大利亚墨尔本" region_location_map["southamerica-east1"]="巴西圣保罗" region_location_map["southamerica-west1"]="智利圣地亚哥" region_location_map["northamerica-northeast1"]="加拿大蒙特利尔" region_location_map["northamerica-northeast2"]="加拿大多伦多" region_location_map["northamerica-south1"]="美国南卡罗来纳州" region_location_map["me-central1"]="卡塔尔多哈" region_location_map["me-central2"]="沙特阿拉伯达曼" region_location_map["me-west1"]="以色列特拉维夫" # 显示虚拟机列表并获取数组 show_instance_menu "$instance_data" if [ $? -ne 0 ]; then echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 遍历每个实例,读取 /opt/socks.txt 文件内容 echo "正在读取每个实例的 /opt/socks.txt 文件内容..." for inst in "${instance_array[@]}"; do instance_name=$(echo "$inst" | cut -d'|' -f1) instance_zone=$(echo "$inst" | cut -d'|' -f2) echo "----------------------------------------" region=$(echo "$instance_zone" | sed 's/-[a-z]$//') location=${region_location_map["$region"]} if [ -n "$location" ]; then echo "实例:$instance_name (区域: $instance_zone($location))" else echo "实例:$instance_name (区域: $instance_zone)" fi # 使用 gcloud compute ssh 远程读取文件内容 socks_content=$(gcloud compute ssh "$instance_name" \ --project="$current_project" \ --zone="$instance_zone" \ --command="cat /opt/socks.txt" \ --quiet 2>/dev/null) if [ $? -eq 0 ] && [ -n "$socks_content" ]; then echo "$socks_content" else echo "无法读取文件内容,可能是文件不存在、权限不足或 SSH 连接失败。" fi echo "----------------------------------------" done echo "按任意键返回菜单..." read -e -r -n 1 ;; 13) echo "正在配置防火墙规则..." current_project=$(gcloud config get-value project) if [ -z "$current_project" ]; then echo "未找到默认项目,请先切换默认项目(选项 8)。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi echo "当前默认项目:$current_project" # 提示用户输入防火墙规则名称 echo "请输入防火墙规则名称(小写字母、数字或短划线组成,留空取消操作):" read -e -r rule_name if [ -z "$rule_name" ]; then echo "未输入规则名称,操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 验证规则名称格式(仅允许小写字母、数字和短划线) if [[ ! "$rule_name" =~ ^[a-z0-9][a-z0-9-]*[a-z0-9]$ ]] || [[ "$rule_name" =~ ^- ]] || [[ "$rule_name" =~ -$ ]]; then echo "规则名称格式无效,仅允许小写字母、数字和短划线,且不能以短划线开头或结尾。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 检查规则名称是否已存在 echo "正在检查规则名称是否已存在..." if gcloud compute firewall-rules list --project="$current_project" | grep -q "^$rule_name "; then echo "规则名称 '$rule_name' 已存在,请使用其他名称。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 提示用户选择协议类型 echo "请选择协议类型:" echo "1. TCP" echo "2. UDP" echo "3. TCP 和 UDP" echo "0. 取消操作" read -e -r protocol_choice case $protocol_choice in 1) protocol="tcp" ;; 2) protocol="udp" ;; 3) protocol="tcp,udp" ;; 0) echo "操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue ;; *) echo "无效选项,操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue ;; esac # 提示用户输入端口号或范围 echo "请输入端口号(支持单个端口如 '8080',非连续端口如 '8080,8081',连续端口范围如 '8000-9000'):" read -e -r port_input if [ -z "$port_input" ]; then echo "未输入端口号,操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 验证端口输入格式 if [[ ! "$port_input" =~ ^[0-9]+(-[0-9]+)?([,][0-9]+(-[0-9]+)?)*$ ]]; then echo "端口格式无效,仅支持数字、逗号和短划线(用于范围)。示例:8080 或 8000-9000 或 8080,8081,9000-9100" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 拆分端口输入,检查每个端口或范围是否合法 IFS=',' read -ra port_entries <<< "$port_input" for entry in "${port_entries[@]}"; do if [[ "$entry" =~ ^[0-9]+-[0-9]+$ ]]; then # 范围端口 IFS='-' read -ra range <<< "$entry" start_port=${range[0]} end_port=${range[1]} if [ "$start_port" -lt 1 ] || [ "$start_port" -gt 65535 ] || [ "$end_port" -lt 1 ] || [ "$end_port" -gt 65535 ] || [ "$start_port" -gt "$end_port" ]; then echo "端口范围 $entry 无效,端口必须在 1-65535 之间,且起始端口不能大于结束端口。" echo "按任意键返回菜单..." read -e -r -n 1 continue 2 fi elif [[ "$entry" =~ ^[0-9]+$ ]]; then # 单个端口 if [ "$entry" -lt 1 ] || [ "$entry" -gt 65535 ]; then echo "端口 $entry 无效,端口必须在 1-65535 之间。" echo "按任意键返回菜单..." read -e -r -n 1 continue 2 fi else echo "端口格式 $entry 无效。" echo "按任意键返回菜单..." read -e -r -n 1 continue 2 fi done unset IFS # 提示用户输入来源 IP 范围(默认为 0.0.0.0/0,即所有来源) echo "请输入允许的来源 IP 范围(CIDR 格式,如 0.0.0.0/0 表示所有 IP,留空使用默认值 0.0.0.0/0):" read -e -r source_ranges if [ -z "$source_ranges" ]; then source_ranges="0.0.0.0/0" fi # 验证 CIDR 格式(简单检查) if [[ ! "$source_ranges" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$ ]]; then echo "来源 IP 范围格式无效,应为 CIDR 格式(如 0.0.0.0/0 或 192.168.1.0/24)。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 确认创建防火墙规则 echo "即将创建以下防火墙规则:" echo "规则名称:$rule_name" echo "协议:$protocol" echo "端口:$port_input" echo "来源 IP 范围:$source_ranges" echo "确认创建吗?(输入 'y' 确认,其他取消):" read -e -r -n 1 confirm echo "" if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then echo "创建操作已取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 格式化 --rules 参数 rules="" IFS=',' read -ra protocols <<< "$protocol" IFS=',' read -ra ports <<< "$port_input" for proto in "${protocols[@]}"; do for port in "${ports[@]}"; do if [ -n "$rules" ]; then rules="$rules," fi rules="$rules$proto:$port" done done unset IFS # 创建防火墙规则 echo "正在创建防火墙规则 '$rule_name'..." gcloud compute firewall-rules create "$rule_name" \ --project="$current_project" \ --direction=INGRESS \ --priority=1000 \ --network=default \ --action=ALLOW \ --rules="$rules" \ --source-ranges="$source_ranges" if [ $? -eq 0 ]; then echo "防火墙规则 '$rule_name' 创建成功!" else echo "创建防火墙规则失败,可能是规则名称已存在或没有权限。" echo "错误信息如上,请检查。" fi echo "按任意键返回菜单..." read -e -r -n 1 ;; 14) echo "正在查看当前防火墙规则..." current_project=$(gcloud config get-value project) if [ -z "$current_project" ]; then echo "未找到默认项目,请先切换默认项目(选项 8)。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi echo "当前默认项目:$current_project" echo "列出所有防火墙规则:" # 使用 gcloud 命令列出防火墙规则 gcloud compute firewall-rules list --project="$current_project" --format="table(name,network,direction,priority,allow,disabled)" 2>/dev/null if [ $? -eq 0 ]; then echo "防火墙规则列表显示完毕。" else echo "无法获取防火墙规则列表,请检查是否有权限或项目是否正确。" fi echo "按任意键返回菜单..." read -e -r -n 1 ;; 15) echo "正在准备删除防火墙规则..." current_project=$(gcloud config get-value project) if [ -z "$current_project" ]; then echo "未找到默认项目,请先切换默认项目(选项 8)。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi echo "当前默认项目:$current_project" echo "当前防火墙规则列表:" # 列出所有防火墙规则供用户参考 gcloud compute firewall-rules list --project="$current_project" --format="table(name,network,direction,priority,allow,disabled)" 2>/dev/null if [ $? -ne 0 ]; then echo "无法获取防火墙规则列表,请检查是否有权限或项目是否正确。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 提示用户输入要删除的规则名称 echo "请输入要删除的防火墙规则名称(留空取消操作):" read -e -r rule_name if [ -z "$rule_name" ]; then echo "未输入规则名称,操作取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 确认删除操作 echo "确认要删除防火墙规则 '$rule_name' 吗?(输入 'y' 确认,其他取消):" read -e -r -n 1 confirm echo "" if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then echo "删除操作已取消。" echo "按任意键返回菜单..." read -e -r -n 1 continue fi # 执行删除操作 echo "正在删除防火墙规则 '$rule_name'..." gcloud compute firewall-rules delete "$rule_name" --project="$current_project" --quiet 2>/dev/null if [ $? -eq 0 ]; then echo "防火墙规则 '$rule_name' 删除成功。" else echo "删除防火墙规则失败,可能是规则不存在或没有权限。" fi echo "按任意键返回菜单..." read -e -r -n 1 ;; 16) if get_billing_accounts; then echo "可用的结算账号列表:" for item in "${billing_account_array_display[@]}"; do echo "$item" done else echo "无法获取结算账号列表。" fi echo "按任意键返回菜单..." read -e -r -n 1 ;; 0) echo "退出脚本..." exit 0 ;; *) echo "无效选项,请选择 0-12 之间的数字。" echo "按任意键返回菜单..." read -e -r -n 1 ;; esac done