Toolbox/vps_manager.sh
chunyu 81e2226a19 docs: 添加初始化脚本检查的提示信息
在检测初始化脚本时,添加了提示信息“正在检查初始化脚本...”以增强用户体验,明确当前操作状态。
2025-05-21 13:31:38 +08:00

1722 lines
76 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 "请输入项目 ID6-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